diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2022-04-04 17:00:31 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-04-04 17:00:31 +0200 |
| commit | 38769373c7e89783d33849283586bfa0b62e8251 (patch) | |
| tree | 4fda128ee6b30018c1aa14c584cc53ade80e67f7 /opendc-web/opendc-web-ui/src/components/projects | |
| parent | 6021aa4278bebb34bf5603ead4b5daeabcdc4c19 (diff) | |
| parent | 527ae2230f5c2dd22f496f45d5d8e3bd4acdb854 (diff) | |
merge: Migrate to Quarkus-based web API
This pull request changes the web API to a Quarkus-based version. Currently, the OpenDC web API is written in Python (using Flask). Although Python is a powerful language to develop web services, having another language next to Kotlin/Java and JavaScript introduces some challenges.
For instance, the web API and UI lack integration with our Gradle-based build pipeline and require additional steps from the developer to start working with. Furthermore, deploying OpenDC requires having Python installed in addition to the JVM.
By converting the web API into a Quarkus application, we can enjoy further integration with our Gradle-based build pipeline and simplify the development/deployment process of OpenDC, by requiring only the JVM and Node to work with OpenDC.
## Implementation Notes :hammer_and_pick:
* Move build dependencies into version catalog
* Design unified communication protocol
* Add Quarkus API implementation
* Add new web client implementation
* Update runner to use new web client
* Fix compatibility with React.js UI
* Remove Python build steps from CI pipeline
* Update Docker deployment for new web API
* Remove obsolete database configuration
## External Dependencies :four_leaf_clover:
* Quarkus
## Breaking API Changes :warning:
* The new web API only supports SQL-based databases for storing user-data, as opposed to MongoDB currently. We intend to use H2 for development and Postgres for production.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components/projects')
9 files changed, 45 insertions, 64 deletions
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 <FormSelect id="origin" name="origin" value={originTopology} onChange={setOriginTopology}> <FormSelectOption value={-1} key={-1} label="None - start from scratch" /> {topologies.map((topology) => ( - <FormSelectOption value={topology._id} key={topology._id} label={topology.name} /> + <FormSelectOption value={topology.id} key={topology.id} label={topology.name} /> ))} </FormSelect> </FormGroup> @@ -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: ( - <Link href={`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`}> - {portfolio.name} - </Link> + <Link href={`/projects/${projectId}/portfolios/${portfolio.number}`}>{portfolio.name}</Link> ), }, - - 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: <Link href={`/projects/${project._id}`}>{project.name}</Link>, + title: <Link href={`/projects/${project.id}`}>{project.name}</Link>, }, - parseAndFormatDateTime(project.datetimeLastEdited), + parseAndFormatDateTime(project.updatedAt), { title: ( <> - <Icon className="pf-u-mr-md" key="auth" /> {AUTH_DESCRIPTION_MAP[level]} + <Icon className="pf-u-mr-md" key="auth" /> {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: ( - <Link href={`/projects/${topology.projectId}/topologies/${topology._id}`}> - {topology.name} - </Link> - ), + title: <Link href={`/projects/${projectId}/topologies/${topology.number}`}>{topology.name}</Link>, }, 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 |
