From 3d1c02e50ee619598bcd7fad4368be8b4a039e84 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Mon, 7 Mar 2022 18:19:21 +0100 Subject: refactor(web/ui): Fix compatibility with new API This change updates the web interface in React to be compatible with the new API written in Kotlin. Several changes have been made in the new API to ensure consistency. --- .../opendc-web-ui/src/components/AppNavigation.js | 30 +------------- .../src/components/context/ContextSelector.js | 21 +++++----- .../components/context/ContextSelector.module.scss | 1 - .../src/components/context/PortfolioSelector.js | 24 ++++++----- .../src/components/context/ProjectSelector.js | 17 ++++---- .../src/components/context/TopologySelector.js | 23 +++++------ .../src/components/portfolios/NewScenario.js | 20 ++++----- .../src/components/portfolios/NewScenarioModal.js | 48 +++++++++++----------- .../src/components/portfolios/PortfolioOverview.js | 23 +++++------ .../src/components/portfolios/PortfolioResults.js | 19 +++++---- .../src/components/portfolios/ScenarioState.js | 6 +-- .../src/components/portfolios/ScenarioTable.js | 31 ++++++-------- .../src/components/projects/NewPortfolio.js | 6 +-- .../src/components/projects/NewPortfolioModal.js | 2 +- .../src/components/projects/NewProject.js | 4 +- .../src/components/projects/NewTopology.js | 16 ++------ .../src/components/projects/NewTopologyModal.js | 22 ++++++---- .../src/components/projects/PortfolioTable.js | 27 ++++-------- .../src/components/projects/ProjectOverview.js | 2 +- .../src/components/projects/ProjectTable.js | 11 ++--- .../src/components/projects/TopologyTable.js | 19 ++++----- .../src/components/topologies/RoomTable.js | 9 ++-- .../src/components/topologies/TopologyOverview.js | 15 ++++--- .../src/components/topologies/map/TileContainer.js | 2 +- .../topologies/map/elements/ImageComponent.js | 1 + .../components/topologies/map/groups/RackGroup.js | 4 +- .../components/topologies/map/groups/RoomGroup.js | 6 +-- .../topologies/map/layers/RoomHoverLayer.js | 4 +- .../topologies/sidebar/machine/MachineSidebar.js | 2 +- .../topologies/sidebar/machine/UnitAddComponent.js | 2 +- .../sidebar/machine/UnitListContainer.js | 2 +- .../topologies/sidebar/rack/AddPrefab.js | 7 +--- .../sidebar/rack/MachineListContainer.js | 2 +- .../topologies/sidebar/rack/RackNameContainer.js | 4 +- .../components/topologies/sidebar/room/RoomName.js | 4 +- 35 files changed, 194 insertions(+), 242 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/components') diff --git a/opendc-web/opendc-web-ui/src/components/AppNavigation.js b/opendc-web/opendc-web-ui/src/components/AppNavigation.js index 178c3ec0..77c683a2 100644 --- a/opendc-web/opendc-web-ui/src/components/AppNavigation.js +++ b/opendc-web/opendc-web-ui/src/components/AppNavigation.js @@ -23,15 +23,9 @@ import { Nav, NavItem, NavList } from '@patternfly/react-core' import { useRouter } from 'next/router' import NavItemLink from './util/NavItemLink' -import { useProject } from '../data/project' export function AppNavigation() { - const { pathname, query } = useRouter() - const { project: projectId } = query - const { data: project } = useProject(projectId) - - const nextTopologyId = project?.topologyIds?.[0] - const nextPortfolioId = project?.portfolioIds?.[0] + const { pathname } = useRouter() return ( ) diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js index 3712cfa0..a99b60c0 100644 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js @@ -22,13 +22,11 @@ import PropTypes from 'prop-types' import { ContextSelector as PFContextSelector, ContextSelectorItem } from '@patternfly/react-core' -import { useMemo, useState, useReducer } from 'react' +import { useMemo, useState } from 'react' import { contextSelector } from './ContextSelector.module.scss' -function ContextSelector({ activeItem, items, onSelect, label }) { - const [isOpen, toggle] = useReducer((isOpen) => !isOpen, false) +function ContextSelector({ activeItem, items, onSelect, onToggle, isOpen, label }) { const [searchValue, setSearchValue] = useState('') - const filteredItems = useMemo( () => items.filter(({ name }) => name.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1) || items, [items, searchValue] @@ -36,23 +34,22 @@ function ContextSelector({ activeItem, items, onSelect, label }) { return ( setSearchValue(value)} searchInputValue={searchValue} isOpen={isOpen} - onToggle={toggle} + onToggle={(_, isOpen) => onToggle(isOpen)} onSelect={(event) => { - const targetId = event.target.value - const target = items.find((item) => item._id === targetId) + const targetId = +event.target.value + const target = items.find((item) => item.id === targetId) - toggle() onSelect(target) + onToggle(!isOpen) }} > {filteredItems.map((item) => ( - + {item.name} ))} @@ -61,7 +58,7 @@ function ContextSelector({ activeItem, items, onSelect, label }) { } const Item = PropTypes.shape({ - _id: PropTypes.string.isRequired, + id: PropTypes.any.isRequired, name: PropTypes.string.isRequired, }) @@ -69,6 +66,8 @@ ContextSelector.propTypes = { activeItem: Item, items: PropTypes.arrayOf(Item).isRequired, onSelect: PropTypes.func.isRequired, + onToggle: PropTypes.func.isRequired, + isOpen: PropTypes.bool, label: PropTypes.string, } diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss index fefba41f..0aa63ee6 100644 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss @@ -21,7 +21,6 @@ */ .contextSelector { - width: auto; margin-right: 20px; --pf-c-context-selector__toggle--PaddingTop: var(--pf-global--spacer--sm); diff --git a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js index 694681ac..c4f2d50e 100644 --- a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js +++ b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js @@ -21,27 +21,31 @@ */ import { useRouter } from 'next/router' -import { useMemo } from 'react' -import { useProjectPortfolios } from '../../data/project' +import { useState } from 'react' +import { usePortfolios } from '../../data/project' +import { Portfolio } from '../../shapes' import ContextSelector from './ContextSelector' -function PortfolioSelector() { +function PortfolioSelector({ activePortfolio }) { const router = useRouter() - const { project, portfolio: activePortfolioId } = router.query - const { data: portfolios = [] } = useProjectPortfolios(project) - const activePortfolio = useMemo(() => portfolios.find((portfolio) => portfolio._id === activePortfolioId), [ - activePortfolioId, - portfolios, - ]) + + const [isOpen, setOpen] = useState(false) + const { data: portfolios = [] } = usePortfolios(activePortfolio?.project?.id, { enabled: isOpen }) return ( router.push(`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`)} + onSelect={(portfolio) => router.push(`/projects/${portfolio.project.id}/portfolios/${portfolio.number}`)} + onToggle={setOpen} + isOpen={isOpen} /> ) } +PortfolioSelector.propTypes = { + activePortfolio: Portfolio, +} + export default PortfolioSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js index 753632ab..7721e04c 100644 --- a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js +++ b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js @@ -20,29 +20,32 @@ * SOFTWARE. */ -import PropTypes from 'prop-types' import { useRouter } from 'next/router' -import { useMemo } from 'react' +import { useState } from 'react' import { useProjects } from '../../data/project' +import { Project } from '../../shapes' import ContextSelector from './ContextSelector' -function ProjectSelector({ projectId }) { +function ProjectSelector({ activeProject }) { const router = useRouter() - const { data: projects = [] } = useProjects() - const activeProject = useMemo(() => projects.find((project) => project._id === projectId), [projectId, projects]) + + const [isOpen, setOpen] = useState(false) + const { data: projects = [] } = useProjects({ enabled: isOpen }) return ( router.push(`/projects/${project._id}`)} + onSelect={(project) => router.push(`/projects/${project.id}`)} + onToggle={setOpen} + isOpen={isOpen} /> ) } ProjectSelector.propTypes = { - projectId: PropTypes.string, + activeProject: Project, } export default ProjectSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js index d5e51c6c..9cae4cbf 100644 --- a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js +++ b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js @@ -20,33 +20,32 @@ * SOFTWARE. */ -import PropTypes from 'prop-types' import { useRouter } from 'next/router' -import { useMemo } from 'react' -import { useProjectTopologies } from '../../data/topology' +import { useState } from 'react' +import { useTopologies } from '../../data/topology' +import { Topology } from '../../shapes' import ContextSelector from './ContextSelector' -function TopologySelector({ projectId, topologyId }) { +function TopologySelector({ activeTopology }) { const router = useRouter() - const { data: topologies = [] } = useProjectTopologies(projectId) - const activeTopology = useMemo(() => topologies.find((topology) => topology._id === topologyId), [ - topologyId, - topologies, - ]) + + const [isOpen, setOpen] = useState(false) + const { data: topologies = [] } = useTopologies(activeTopology?.project?.id, { enabled: isOpen }) return ( router.push(`/projects/${topology.projectId}/topologies/${topology._id}`)} + onSelect={(topology) => router.push(`/projects/${topology.project.id}/topologies/${topology.number}`)} + onToggle={setOpen} + isOpen={isOpen} /> ) } TopologySelector.propTypes = { - projectId: PropTypes.string, - topologyId: PropTypes.string, + activeTopology: Topology, } export default TopologySelector diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js index 856282a7..fd9a72d2 100644 --- a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js @@ -24,21 +24,15 @@ import PropTypes from 'prop-types' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import { useMutation } from 'react-query' +import { useNewScenario } from '../../data/project' import NewScenarioModal from './NewScenarioModal' -function NewScenario({ portfolioId }) { +function NewScenario({ projectId, portfolioId }) { const [isVisible, setVisible] = useState(false) - const { mutate: addScenario } = useMutation('addScenario') + const { mutate: addScenario } = useNewScenario() - const onSubmit = (name, portfolioId, trace, topology, operational) => { - addScenario({ - portfolioId, - name, - trace, - topology, - operational, - }) + const onSubmit = (projectId, portfolioNumber, data) => { + addScenario({ projectId, portfolioNumber, data }) setVisible(false) } @@ -48,6 +42,7 @@ function NewScenario({ portfolioId }) { New Scenario { - const { data: portfolio } = usePortfolio(portfolioId) - const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId) - const { data: traces = [] } = useTraces() - const { data: schedulers = [] } = useSchedulers() +function NewScenarioModal({ projectId, portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) { + const { data: portfolio } = usePortfolio(projectId, portfolioId) + const { data: topologies = [] } = useTopologies(projectId, { enabled: isOpen }) + const { data: traces = [] } = useTraces({ enabled: isOpen }) + const { data: schedulers = [] } = useSchedulers({ enabled: isOpen }) // eslint-disable-next-line no-unused-vars const [isSubmitted, setSubmitted] = useState(false) @@ -51,22 +51,19 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC const name = nameInput.current.value - onSubmitUpstream( + onSubmitUpstream(portfolio.project.id, portfolio.number, { name, - portfolio._id, - { - traceId: trace || traces[0]._id, - loadSamplingFraction: traceLoad / 100, + workload: { + trace: trace || traces[0].id, + samplingFraction: traceLoad / 100, }, - { - topologyId: topology || topologies[0]._id, + topology: topology || topologies[0].number, + phenomena: { + failures: failuresEnabled, + interference: opPhenEnabled, }, - { - failuresEnabled, - performanceInterferenceEnabled: opPhenEnabled, - schedulerName: scheduler || schedulers[0].name, - } - ) + schedulerName: scheduler || schedulers[0], + }) resetState() return true @@ -84,8 +81,8 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC id="name" name="name" type="text" - isDisabled={portfolio?.scenarioIds?.length === 0} - defaultValue={portfolio?.scenarioIds?.length === 0 ? 'Base scenario' : ''} + isDisabled={portfolio?.scenarios?.length === 0} + defaultValue={portfolio?.scenarios?.length === 0 ? 'Base scenario' : ''} ref={nameInput} /> @@ -93,7 +90,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC {traces.map((trace) => ( - + ))} @@ -115,7 +112,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC {topologies.map((topology) => ( - + ))} @@ -123,7 +120,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC {schedulers.map((scheduler) => ( - + ))} @@ -150,7 +147,8 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC } NewScenarioModal.propTypes = { - portfolioId: PropTypes.string, + projectId: PropTypes.number, + portfolioId: PropTypes.number, isOpen: PropTypes.bool.isRequired, onSubmit: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js index 580b0a29..e561b655 100644 --- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js @@ -43,8 +43,8 @@ import { METRIC_NAMES } from '../../util/available-metrics' import NewScenario from './NewScenario' import ScenarioTable from './ScenarioTable' -function PortfolioOverview({ portfolioId }) { - const { data: portfolio } = usePortfolio(portfolioId) +function PortfolioOverview({ projectId, portfolioId }) { + const { status, data: portfolio } = usePortfolio(projectId, portfolioId) return ( @@ -62,16 +62,16 @@ function PortfolioOverview({ portfolioId }) { Scenarios - {portfolio?.scenarioIds.length ?? } + {portfolio?.scenarios?.length ?? } Metrics - {portfolio?.targets?.enabledMetrics ? ( - portfolio.targets.enabledMetrics.length > 0 ? ( + {portfolio ? ( + portfolio.targets.metrics.length > 0 ? ( - {portfolio.targets.enabledMetrics.map((metric) => ( + {portfolio.targets.metrics.map((metric) => ( {METRIC_NAMES[metric]} @@ -88,9 +88,7 @@ function PortfolioOverview({ portfolioId }) { Repeats per Scenario - {portfolio?.targets?.repeatsPerScenario ?? ( - - )} + {portfolio?.targets?.repeats ?? } @@ -101,12 +99,12 @@ function PortfolioOverview({ portfolioId }) { - + Scenarios - + @@ -115,7 +113,8 @@ function PortfolioOverview({ portfolioId }) { } PortfolioOverview.propTypes = { - portfolioId: PropTypes.string, + projectId: PropTypes.number, + portfolioId: PropTypes.number, } export default PortfolioOverview diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js index 00023d9e..f63f0c7f 100644 --- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js @@ -42,12 +42,14 @@ import { Title, } from '@patternfly/react-core' import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' -import { usePortfolioScenarios } from '../../data/project' +import { usePortfolio } from '../../data/project' import PortfolioResultInfo from './PortfolioResultInfo' import NewScenario from './NewScenario' -const PortfolioResults = ({ portfolioId }) => { - const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) +const PortfolioResults = ({ projectId, portfolioId }) => { + const { status, data: scenarios = [] } = usePortfolio(projectId, portfolioId, { + select: (portfolio) => portfolio.scenarios, + }) if (status === 'loading') { return ( @@ -86,7 +88,7 @@ const PortfolioResults = ({ portfolioId }) => { No results are currently available for this portfolio. Run a scenario to obtain simulation results. - + ) @@ -96,11 +98,11 @@ const PortfolioResults = ({ portfolioId }) => { AVAILABLE_METRICS.forEach((metric) => { dataPerMetric[metric] = scenarios - .filter((scenario) => scenario.results) + .filter((scenario) => scenario.job?.results) .map((scenario) => ({ name: scenario.name, - value: mean(scenario.results[metric]), - errorX: std(scenario.results[metric]), + value: mean(scenario.job.results[metric]), + errorX: std(scenario.job.results[metric]), })) }) @@ -150,7 +152,8 @@ const PortfolioResults = ({ portfolioId }) => { } PortfolioResults.propTypes = { - portfolioId: PropTypes.string, + projectId: PropTypes.number, + portfolioId: PropTypes.number, } export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js index 66691580..99d83f64 100644 --- a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js @@ -20,13 +20,13 @@ * SOFTWARE. */ -import PropTypes from 'prop-types' import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' +import { JobState } from '../../shapes' function ScenarioState({ state }) { switch (state) { + case 'PENDING': case 'CLAIMED': - case 'QUEUED': return ( Queued @@ -56,7 +56,7 @@ function ScenarioState({ state }) { } ScenarioState.propTypes = { - state: PropTypes.oneOf(['QUEUED', 'CLAIMED', 'RUNNING', 'FINISHED', 'FAILED']), + state: JobState.isRequired, } export default ScenarioState diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js index 9966e3ba..68647957 100644 --- a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js @@ -20,44 +20,38 @@ * SOFTWARE. */ -import PropTypes from 'prop-types' import Link from 'next/link' import { Table, TableBody, TableHeader } from '@patternfly/react-table' import React from 'react' +import { Portfolio, Status } from '../../shapes' import TableEmptyState from '../util/TableEmptyState' import ScenarioState from './ScenarioState' -import { usePortfolio, usePortfolioScenarios } from '../../data/project' -import { useProjectTopologies } from '../../data/topology' -import { useMutation } from 'react-query' +import { useDeleteScenario } from '../../data/project' -const ScenarioTable = ({ portfolioId }) => { - const { data: portfolio } = usePortfolio(portfolioId) - const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) - const { data: topologies } = useProjectTopologies(portfolio?.projectId, { - select: (topologies) => new Map(topologies.map((topology) => [topology._id, topology])), - }) - - const { mutate: deleteScenario } = useMutation('deleteScenario') +function ScenarioTable({ portfolio, status }) { + const { mutate: deleteScenario } = useDeleteScenario() + const projectId = portfolio?.project?.id + const scenarios = portfolio?.scenarios ?? [] const columns = ['Name', 'Topology', 'Trace', 'State'] const rows = scenarios.length > 0 ? scenarios.map((scenario) => { - const topology = topologies?.get(scenario.topology.topologyId) + const topology = scenario.topology return [ scenario.name, { title: topology ? ( - + {topology.name} ) : ( 'Unknown Topology' ), }, - scenario.trace.traceId, - { title: }, + `${scenario.workload.trace.name} (${scenario.workload.samplingFraction * 100}%)`, + { title: }, ] }) : [ @@ -82,7 +76,7 @@ const ScenarioTable = ({ portfolioId }) => { const actionResolver = (_, { rowIndex }) => [ { title: 'Delete Scenario', - onClick: (_, rowId) => deleteScenario(scenarios[rowId]._id), + onClick: (_, rowId) => deleteScenario({ projectId: projectId, number: scenarios[rowId].number }), isDisabled: rowIndex === 0, }, ] @@ -102,7 +96,8 @@ const ScenarioTable = ({ portfolioId }) => { } ScenarioTable.propTypes = { - portfolioId: PropTypes.string, + portfolio: Portfolio, + status: Status.isRequired, } export default ScenarioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js index 87ea059d..aebcc3c9 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js @@ -24,12 +24,12 @@ import PropTypes from 'prop-types' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import { useMutation } from 'react-query' +import { useNewPortfolio } from '../../data/project' import NewPortfolioModal from './NewPortfolioModal' function NewPortfolio({ projectId }) { const [isVisible, setVisible] = useState(false) - const { mutate: addPortfolio } = useMutation('addPortfolio') + const { mutate: addPortfolio } = useNewPortfolio() const onSubmit = (name, targets) => { addPortfolio({ projectId, name, targets }) @@ -47,7 +47,7 @@ function NewPortfolio({ projectId }) { } NewPortfolio.propTypes = { - projectId: PropTypes.string, + projectId: PropTypes.number, } export default NewPortfolio diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js index 4276d7d4..ba4bc819 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js @@ -67,7 +67,7 @@ const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUps setErrors({ name: true }) return false } else { - onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats }) + onSubmitUpstream(name, { metrics: selectedMetrics, repeats }) } clearState() diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js index 984264dc..bfa7c01a 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { Button } from '@patternfly/react-core' -import { useMutation } from 'react-query' import { PlusIcon } from '@patternfly/react-icons' +import { useNewProject } from '../../data/project' import { buttonContainer } from './NewProject.module.scss' import TextInputModal from '../util/modals/TextInputModal' @@ -10,7 +10,7 @@ import TextInputModal from '../util/modals/TextInputModal' */ const NewProject = () => { const [isVisible, setVisible] = useState(false) - const { mutate: addProject } = useMutation('addProject') + const { mutate: addProject } = useNewProject() const onSubmit = (name) => { if (name) { diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js index 77c57d26..4c569c56 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js @@ -21,25 +21,17 @@ */ import PropTypes from 'prop-types' -import produce from 'immer' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import { useMutation } from "react-query"; -import { useProjectTopologies } from "../../data/topology"; +import { useNewTopology } from '../../data/topology' import NewTopologyModal from './NewTopologyModal' function NewTopology({ projectId }) { const [isVisible, setVisible] = useState(false) - const { data: topologies = [] } = useProjectTopologies(projectId) - const { mutate: addTopology } = useMutation('addTopology') + const { mutate: addTopology } = useNewTopology() - const onSubmit = (name, duplicateId) => { - const candidate = topologies.find((topology) => topology._id === duplicateId) || { projectId, rooms: [] } - const topology = produce(candidate, (draft) => { - delete draft._id - draft.name = name - }) + const onSubmit = (topology) => { addTopology(topology) setVisible(false) } @@ -59,7 +51,7 @@ function NewTopology({ projectId }) { } NewTopology.propTypes = { - projectId: PropTypes.string, + projectId: PropTypes.number, } export default NewTopology diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js index a495f73e..be4256e3 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js @@ -20,10 +20,11 @@ * SOFTWARE. */ +import produce from 'immer' import PropTypes from 'prop-types' import React, { useRef, useState } from 'react' import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' -import { useProjectTopologies } from '../../data/topology' +import { useTopologies } from '../../data/topology' import Modal from '../util/modals/Modal' const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { @@ -32,10 +33,12 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan const [originTopology, setOriginTopology] = useState(-1) const [errors, setErrors] = useState({}) - const { data: topologies = [] } = useProjectTopologies(projectId) + const { data: topologies = [] } = useTopologies(projectId, { enabled: isOpen }) const clearState = () => { - nameInput.current.value = '' + if (nameInput.current) { + nameInput.current.value = '' + } setSubmitted(false) setOriginTopology(-1) setErrors({}) @@ -53,10 +56,13 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan if (!name) { setErrors({ name: true }) return false - } else if (originTopology === -1) { - onSubmitUpstream(name) } else { - onSubmitUpstream(name, originTopology) + const candidate = topologies.find((topology) => topology.id === originTopology) || { projectId, rooms: [] } + const topology = produce(candidate, (draft) => { + delete draft.id + draft.name = name + }) + onSubmitUpstream(topology) } clearState() @@ -84,7 +90,7 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan {topologies.map((topology) => ( - + ))} @@ -94,7 +100,7 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan } NewTopologyModal.propTypes = { - projectId: PropTypes.string, + projectId: PropTypes.number, isOpen: PropTypes.bool.isRequired, onSubmit: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, diff --git a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js index 45e399ed..aa679843 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js @@ -25,12 +25,11 @@ import Link from 'next/link' import { Table, TableBody, TableHeader } from '@patternfly/react-table' import React from 'react' import TableEmptyState from '../util/TableEmptyState' -import { useProjectPortfolios } from '../../data/project' -import { useMutation } from 'react-query' +import { usePortfolios, useDeletePortfolio } from '../../data/project' const PortfolioTable = ({ projectId }) => { - const { status, data: portfolios = [] } = useProjectPortfolios(projectId) - const { mutate: deletePortfolio } = useMutation('deletePortfolio') + const { status, data: portfolios = [] } = usePortfolios(projectId) + const { mutate: deletePortfolio } = useDeletePortfolio() const columns = ['Name', 'Scenarios', 'Metrics', 'Repeats'] const rows = @@ -38,20 +37,12 @@ const PortfolioTable = ({ projectId }) => { ? portfolios.map((portfolio) => [ { title: ( - - {portfolio.name} - + {portfolio.name} ), }, - - portfolio.scenarioIds.length === 1 ? '1 scenario' : `${portfolio.scenarioIds.length} scenarios`, - - portfolio.targets.enabledMetrics.length === 1 - ? '1 metric' - : `${portfolio.targets.enabledMetrics.length} metrics`, - portfolio.targets.repeatsPerScenario === 1 - ? '1 repeat' - : `${portfolio.targets.repeatsPerScenario} repeats`, + portfolio.scenarios.length === 1 ? '1 scenario' : `${portfolio.scenarios.length} scenarios`, + portfolio.targets.metrics.length === 1 ? '1 metric' : `${portfolio.targets.metrics.length} metrics`, + portfolio.targets.repeats === 1 ? '1 repeat' : `${portfolio.targets.repeats} repeats`, ]) : [ { @@ -77,7 +68,7 @@ const PortfolioTable = ({ projectId }) => { ? [ { title: 'Delete Portfolio', - onClick: (_, rowId) => deletePortfolio(portfolios[rowId]._id), + onClick: (_, rowId) => deletePortfolio({ projectId, number: portfolios[rowId].number }), }, ] : [] @@ -91,7 +82,7 @@ const PortfolioTable = ({ projectId }) => { } PortfolioTable.propTypes = { - projectId: PropTypes.string, + projectId: PropTypes.number, } export default PortfolioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js index 65b8f5a0..3e1656f6 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js @@ -92,7 +92,7 @@ function ProjectOverview({ projectId }) { } ProjectOverview.propTypes = { - projectId: PropTypes.string, + projectId: PropTypes.number, } export default ProjectOverview diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js index a7290259..6921578c 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js @@ -5,26 +5,23 @@ import { Project, Status } from '../../shapes' import { Table, TableBody, TableHeader } from '@patternfly/react-table' import { parseAndFormatDateTime } from '../../util/date-time' import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' -import { useAuth } from '../../auth' import TableEmptyState from '../util/TableEmptyState' const ProjectTable = ({ status, projects, onDelete, isFiltering }) => { - const { user } = useAuth() const columns = ['Project name', 'Last edited', 'Access Rights'] const rows = projects.length > 0 ? projects.map((project) => { - const { level } = project.authorizations.find((auth) => auth.userId === user.sub) - const Icon = AUTH_ICON_MAP[level] + const Icon = AUTH_ICON_MAP[project.role] return [ { - title: {project.name}, + title: {project.name}, }, - parseAndFormatDateTime(project.datetimeLastEdited), + parseAndFormatDateTime(project.updatedAt), { title: ( <> - {AUTH_DESCRIPTION_MAP[level]} + {AUTH_DESCRIPTION_MAP[project.role]} ), }, diff --git a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js index 80099ece..ced5304a 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js @@ -26,26 +26,21 @@ import { Table, TableBody, TableHeader } from '@patternfly/react-table' import React from 'react' import TableEmptyState from '../util/TableEmptyState' import { parseAndFormatDateTime } from '../../util/date-time' -import { useMutation } from 'react-query' -import { useProjectTopologies } from '../../data/topology' +import { useTopologies, useDeleteTopology } from '../../data/topology' const TopologyTable = ({ projectId }) => { - const { status, data: topologies = [] } = useProjectTopologies(projectId) - const { mutate: deleteTopology } = useMutation('deleteTopology') + const { status, data: topologies = [] } = useTopologies(projectId) + const { mutate: deleteTopology } = useDeleteTopology() const columns = ['Name', 'Rooms', 'Last Edited'] const rows = topologies.length > 0 ? topologies.map((topology) => [ { - title: ( - - {topology.name} - - ), + title: {topology.name}, }, topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`, - parseAndFormatDateTime(topology.datetimeLastEdited), + parseAndFormatDateTime(topology.updatedAt), ]) : [ { @@ -69,7 +64,7 @@ const TopologyTable = ({ projectId }) => { const actionResolver = (_, { rowIndex }) => [ { title: 'Delete Topology', - onClick: (_, rowId) => deleteTopology(topologies[rowId]._id), + onClick: (_, rowId) => deleteTopology({ projectId, number: topologies[rowId].number }), isDisabled: rowIndex === 0, }, ] @@ -89,7 +84,7 @@ const TopologyTable = ({ projectId }) => { } TopologyTable.propTypes = { - projectId: PropTypes.string, + projectId: PropTypes.number, } export default TopologyTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js index 9bf369e9..49e5f095 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js @@ -7,11 +7,11 @@ import { Table, TableBody, TableHeader } from '@patternfly/react-table' import { deleteRoom } from '../../redux/actions/topology/room' import TableEmptyState from '../util/TableEmptyState' -function RoomTable({ topologyId, onSelect }) { +function RoomTable({ projectId, topologyId, onSelect }) { const dispatch = useDispatch() - const { status, data: topology } = useTopology(topologyId) + const { status, data: topology } = useTopology(projectId, topologyId) - const onDelete = (room) => dispatch(deleteRoom(room._id)) + const onDelete = (room) => dispatch(deleteRoom(room.id)) const columns = ['Name', 'Tiles', 'Racks'] const rows = @@ -62,7 +62,8 @@ function RoomTable({ topologyId, onSelect }) { } RoomTable.propTypes = { - topologyId: PropTypes.string, + projectId: PropTypes.number, + topologyId: PropTypes.number, onSelect: PropTypes.func, } diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js index 213a4868..f8ee4990 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js @@ -38,8 +38,8 @@ import { useTopology } from '../../data/topology' import { parseAndFormatDateTime } from '../../util/date-time' import RoomTable from './RoomTable' -function TopologyOverview({ topologyId, onSelect }) { - const { data: topology } = useTopology(topologyId) +function TopologyOverview({ projectId, topologyNumber, onSelect }) { + const { data: topology } = useTopology(projectId, topologyNumber) return ( @@ -57,7 +57,7 @@ function TopologyOverview({ topologyId, onSelect }) { Last edited {topology ? ( - parseAndFormatDateTime(topology.datetimeLastEdited) + parseAndFormatDateTime(topology.updatedAt) ) : ( )} @@ -71,7 +71,11 @@ function TopologyOverview({ topologyId, onSelect }) { Rooms - onSelect('room', room)} /> + onSelect('room', room)} + /> @@ -80,7 +84,8 @@ function TopologyOverview({ topologyId, onSelect }) { } TopologyOverview.propTypes = { - topologyId: PropTypes.string, + projectId: PropTypes.number, + topologyNumber: PropTypes.number, onSelect: PropTypes.func, } diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js index 411a5ca7..21be3c79 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js @@ -33,7 +33,7 @@ function TileContainer({ tileId, ...props }) { const dispatch = useDispatch() const onClick = (tile) => { if (tile.rack) { - dispatch(goFromRoomToRack(tile._id)) + dispatch(goFromRoomToRack(tile.id)) } } return diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js index 7d304b6b..fdae53f2 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js @@ -21,6 +21,7 @@ function ImageComponent({ src, x, y, width, height, opacity }) { } }, [src]) + // eslint-disable-next-line jsx-a11y/alt-text return } diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js index 46030135..dad2d62d 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js @@ -11,8 +11,8 @@ function RackGroup({ tile }) { - - + + ) diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js index a42e7bb7..3f8b3089 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js @@ -7,7 +7,7 @@ import TileContainer from '../TileContainer' import WallContainer from '../WallContainer' function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) { - if (currentRoomInConstruction === room._id) { + if (currentRoomInConstruction === room.id) { return ( {room.tiles.map((tileId) => ( @@ -22,7 +22,7 @@ function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick {(() => { if ( (interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') && - interactionLevel.roomId === room._id + interactionLevel.roomId === room.id ) { return [ room.tiles @@ -37,7 +37,7 @@ function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick return room.tiles.map((tileId) => ) } })()} - + ) } diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js index 5e351691..727f4e25 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js @@ -40,8 +40,8 @@ function RoomHoverLayer() { .map((id) => ({ ...state.topology.rooms[id] })) .filter( (room) => - state.topology.root.rooms.indexOf(room._id) !== -1 && - room._id !== state.construction.currentRoomInConstruction + state.topology.root.rooms.indexOf(room.id) !== -1 && + room.id !== state.construction.currentRoomInConstruction ) ;[...oldRooms, newRoom].forEach((room) => { diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js index 9268f615..6f89e10b 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js @@ -17,7 +17,7 @@ function MachineSidebar({ tileId, position }) { const rack = topology.racks[topology.tiles[tileId].rack] return topology.machines[rack.machines[position - 1]] }) - const machineId = machine._id + const machineId = machine.id return (
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js index 88591208..4507b409 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js @@ -22,7 +22,7 @@ function UnitAddComponent({ units, onAdd }) { selections={selected} > {units.map((unit) => ( - + {unit.name} ))} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js index 6dcc414f..25e750c4 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js @@ -33,7 +33,7 @@ function UnitListContainer({ machineId, unitType }) { return machine[unitType].map((id) => state.topology[unitType][id]) }) - const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id)) + const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit.id)) return } diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js index e944c2e8..6a0c3ff3 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js @@ -22,14 +22,11 @@ import PropTypes from 'prop-types' import React from 'react' -import { useDispatch } from 'react-redux' import { Button } from '@patternfly/react-core' import { SaveIcon } from '@patternfly/react-icons' -import { addPrefab } from '../../../../api/prefabs' -function AddPrefab({ tileId }) { - const dispatch = useDispatch() - const onClick = () => dispatch(addPrefab('name', tileId)) +function AddPrefab() { + const onClick = () => {} // TODO return (