summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-14 22:23:40 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-15 15:55:56 +0200
commit803e13b32cf0ff8b496649fb0a4d6e32400e98a4 (patch)
tree263a6f9741c5ca0dd64ecf3f7f07b580331aec9d /opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js
parente200dbfdc076ac6263c9ac6f9dabdcc475f01d6e (diff)
feat(ui): Migrate to PatternFly 4 design framework
This change is a rewrite of the existing OpenDC frontend in order to migrate to the PatternFly 4 design framework. PatternFly is used by Red Hat for various computing related services such as OpenShift, Red Hat Virtualization and Cockpit. Since their design requirements are very similar to those of OpenDC (modeling computing services), migrating to PatternFly 4 allows us to re-use design choices from these services. See https://www.patternfly.org/v4/ for more information about PatternFly.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js')
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js134
1 files changed, 134 insertions, 0 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js
new file mode 100644
index 00000000..6a96c70c
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js
@@ -0,0 +1,134 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts'
+import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_NAMES_SHORT, METRIC_UNITS } from '../../../util/available-metrics'
+import { mean, std } from 'mathjs'
+import approx from 'approximate-number'
+import {
+ Bullseye,
+ Card,
+ CardActions,
+ CardBody,
+ CardHeader,
+ CardTitle,
+ EmptyState,
+ EmptyStateBody,
+ EmptyStateIcon,
+ Grid,
+ GridItem,
+ Spinner,
+ Title,
+} from '@patternfly/react-core'
+import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons'
+import { usePortfolioScenarios } from '../../../data/project'
+import NewScenario from '../../projects/NewScenario'
+import PortfolioResultInfo from './PortfolioResultInfo'
+
+const PortfolioResults = ({ portfolioId }) => {
+ const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId)
+
+ if (status === 'loading') {
+ return (
+ <Bullseye>
+ <EmptyState>
+ <EmptyStateIcon variant="container" component={Spinner} />
+ <Title size="lg" headingLevel="h4">
+ Loading Results
+ </Title>
+ </EmptyState>
+ </Bullseye>
+ )
+ } else if (status === 'error') {
+ return (
+ <Bullseye>
+ <EmptyState>
+ <EmptyStateIcon variant="container" component={ErrorCircleOIcon} />
+ <Title size="lg" headingLevel="h4">
+ Unable to connect
+ </Title>
+ <EmptyStateBody>
+ There was an error retrieving data. Check your connection and try again.
+ </EmptyStateBody>
+ </EmptyState>
+ </Bullseye>
+ )
+ } else if (scenarios.length === 0) {
+ return (
+ <Bullseye>
+ <EmptyState>
+ <EmptyStateIcon variant="container" component={CubesIcon} />
+ <Title size="lg" headingLevel="h4">
+ No results
+ </Title>
+ <EmptyStateBody>
+ No results are currently available for this portfolio. Run a scenario to obtain simulation
+ results.
+ </EmptyStateBody>
+ <NewScenario portfolioId={portfolioId} />
+ </EmptyState>
+ </Bullseye>
+ )
+ }
+
+ const dataPerMetric = {}
+
+ AVAILABLE_METRICS.forEach((metric) => {
+ dataPerMetric[metric] = scenarios
+ .filter((scenario) => scenario.results)
+ .map((scenario) => ({
+ name: scenario.name,
+ value: mean(scenario.results[metric]),
+ errorX: std(scenario.results[metric]),
+ }))
+ })
+
+ return (
+ <Grid hasGutter>
+ {AVAILABLE_METRICS.map((metric) => (
+ <GridItem xl={6} lg={12} key={metric}>
+ <Card>
+ <CardHeader>
+ <CardActions>
+ <PortfolioResultInfo metric={metric} />
+ </CardActions>
+ <CardTitle>{METRIC_NAMES[metric]}</CardTitle>
+ </CardHeader>
+ <CardBody>
+ <ResponsiveContainer aspect={16 / 9} width="100%">
+ <ComposedChart
+ data={dataPerMetric[metric]}
+ margin={{ left: 35, bottom: 15 }}
+ layout="vertical"
+ >
+ <CartesianGrid strokeDasharray="3 3" />
+ <XAxis
+ tickFormatter={(tick) => approx(tick)}
+ label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }}
+ type="number"
+ />
+ <YAxis dataKey="name" type="category" />
+ <Bar dataKey="value" fill="#3399FF" isAnimationActive={false} />
+ <Scatter dataKey="value" opacity={0} isAnimationActive={false}>
+ <ErrorBar
+ dataKey="errorX"
+ width={10}
+ strokeWidth={3}
+ stroke="#FF6600"
+ direction="x"
+ />
+ </Scatter>
+ </ComposedChart>
+ </ResponsiveContainer>
+ </CardBody>
+ </Card>
+ </GridItem>
+ ))}
+ </Grid>
+ )
+}
+
+PortfolioResults.propTypes = {
+ portfolioId: PropTypes.string,
+}
+
+export default PortfolioResults