diff --git a/frontend/README.md b/frontend/README.md index 8032d5a0..0b01bbaf 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,7 +1,4 @@ -Currently, two official plugins are available: - - # Openshift Performance Dashbaord ## Dashboard directory structure diff --git a/frontend/src/assets/constants/grafanaConstants.js b/frontend/src/assets/constants/grafanaConstants.js new file mode 100644 index 00000000..6458c52c --- /dev/null +++ b/frontend/src/assets/constants/grafanaConstants.js @@ -0,0 +1,48 @@ +export const GRAFANA_BASE_URL = + "https://grafana.rdu2.scalelab.redhat.com:3000/d/"; +export const DASHBOARD_KUBE_BURNER = + "D5E8c5XVz/kube-burner-report-mode?orgId=1"; +export const DASHBOARD_INGRESS = + "d6105ff8-bc26-4d64-951e-56da771b703d/ingress-perf?orgId=1&var-termination=edge&" + + "var-termination=http&var-termination=passthrough&" + + "var-termination=reencrypt&var-latency_metric=p99_lat_us" + + "&var-compare_by=uuid.keyword"; +export const DASHBOARD_NET_PERF = + "wINGhybVz/k8s-netperf?orgId=1&var-samples=3&var-service=All&" + + "var-parallelism=All&var-profile=All&var-messageSize=All&" + + "var-driver=netperf&var-hostNetwork=true&var-hostNetwork=false"; +export const DASHBOARD_QUAY = + "7nkJIoXVk/quay-dashboard-v2?orgId=1&var-api_endpoints_datasource=Quay%20QE%20-%20quay-vegeta&" + + "var-quay_push_pull_datasource=Quay%20QE%20-%20quay-push-pull"; + +export const PROW_DATASOURCE_NETPERF = "&var-datasource=QE+K8s+netperf"; +export const PROW_DATASOURCE_INGRESS = "&var-datasource=QE+Ingress-perf"; +export const JENKINS_DATASOURCE_NETPERF = "&var-datasource=k8s-netperf"; +export const JENKINS_DATASOURCE_INGRESS = "&var-datasource=QE+Ingress-perf"; +export const PROW_DATASOURCE = "&var-Datasource=QE+kube-burner"; +export const DEFAULT_DATASOURCE = + "&var-Datasource=AWS+Pro+-+ripsaw-kube-burner"; +export const QUAY_LOAD_TEST = "quay-load-test"; + +export const ciSystemMap = { + prow: { + "k8s-netperf": { + dataSource: PROW_DATASOURCE_NETPERF, + dashboardURL: DASHBOARD_NET_PERF, + }, + "ingress-perf": { + dataSource: PROW_DATASOURCE_INGRESS, + dashboardURL: DASHBOARD_INGRESS, + }, + }, + jenkins: { + "k8s-netperf": { + dataSource: JENKINS_DATASOURCE_NETPERF, + dashboardURL: DASHBOARD_NET_PERF, + }, + "ingress-perf": { + dataSource: JENKINS_DATASOURCE_INGRESS, + dashboardURL: DASHBOARD_INGRESS, + }, + }, +}; diff --git a/frontend/src/commons/DisplayGrafana.js b/frontend/src/commons/DisplayGrafana.js deleted file mode 100644 index d200527a..00000000 --- a/frontend/src/commons/DisplayGrafana.js +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react'; -import { FaCheck, FaExclamationCircle, FaExclamationTriangle } from "react-icons/fa"; -import {formatTime} from "../../helpers/Formatters"; -import {SplitView} from "../PatternflyComponents/Split/SplitView"; -import CardView from "../PatternflyComponents/Card/CardView"; -import ListView from "../PatternflyComponents/List/ListView"; -import {Text6} from "../PatternflyComponents/Text/Text"; -import { getBuildLink } from './commons'; - - -export function DisplayGrafana({benchmarkConfigs}) { - - - - const icons = { - "failed": , - "failure": , - "success": , - "upstream_failed": , - - } - - const { getGrafanaLink, getTimeFormat, status } = getGrafanaData(benchmarkConfigs) - - return ( - ] - } - /> || } - /> - ) - -} - -const getGrafanaData = (benchmarkConfigs) => { - const grafanaURL = "https://grafana.rdu2.scalelab.redhat.com:3000/d/"; - const dashboardKubeBurner = "D5E8c5XVz/kube-burner-report-mode?orgId=1" - const dashboardIngress = "d6105ff8-bc26-4d64-951e-56da771b703d/ingress-perf?orgId=1&var-termination=edge&" + - "var-termination=http&var-termination=passthrough&" + - "var-termination=reencrypt&var-latency_metric=p99_lat_us" + - "&var-compare_by=uuid.keyword" - const dashboardNetPerf = "wINGhybVz/k8s-netperf?orgId=1&var-samples=3&var-service=All&" + - "var-parallelism=All&var-profile=All&var-messageSize=All&" + - "var-driver=netperf&var-hostNetwork=true&var-hostNetwork=false" - const quayDashboard = "7nkJIoXVk/quay-dashboard-v2?orgId=1&var-api_endpoints_datasource=Quay%20QE%20-%20quay-vegeta&" + - "var-quay_push_pull_datasource=Quay%20QE%20-%20quay-push-pull" - let status = null; - let getTimeFormat = null; - let getGrafanaLink = null; - let getGrafanaUrl = null; - if(benchmarkConfigs){ - const startDate = new Date((benchmarkConfigs.start_date && benchmarkConfigs.start_date) || benchmarkConfigs.startDate).valueOf() - const endDate = new Date((benchmarkConfigs.end_date && benchmarkConfigs.end_date) || benchmarkConfigs.endDate).valueOf() - status = benchmarkConfigs.job_status && benchmarkConfigs.job_status || benchmarkConfigs.jobStatus - - let dashboardURL = dashboardKubeBurner - let dataSource = "&var-Datasource=AWS+Pro+-+ripsaw-kube-burner" - if (benchmarkConfigs.ciSystem === "PROW"){ - dataSource = "&var-Datasource=QE+kube-burner" - if (benchmarkConfigs.benchmark === "k8s-netperf") { - dataSource = "&var-datasource=QE+K8s+netperf" - dashboardURL = dashboardNetPerf - } - if (benchmarkConfigs.benchmark === "ingress-perf") { - dataSource = "&var-datasource=QE+Ingress-perf" - dashboardURL = dashboardIngress - } - } else if(benchmarkConfigs.ciSystem === "JENKINS") { - if (benchmarkConfigs.benchmark === "k8s-netperf") { - dataSource = "&var-datasource=k8s-netperf" - dashboardURL = dashboardNetPerf - } - if (benchmarkConfigs.benchmark === "ingress-perf") { - dataSource = "&var-datasource=QE+Ingress-perf" - dashboardURL = dashboardIngress - } - } - if (benchmarkConfigs.benchmark === "quay-load-test") { - getGrafanaUrl = grafanaURL+quayDashboard+ - "&from="+startDate+"&to="+endDate+ - "&var-uuid="+benchmarkConfigs.uuid - - } else { - getGrafanaUrl = grafanaURL+dashboardURL+ - "&from="+startDate+"&to="+endDate+ - dataSource+"&var-platform="+benchmarkConfigs.platform+ - "&var-uuid="+benchmarkConfigs.uuid+ - "&var-workload="+benchmarkConfigs.benchmark - } - - getTimeFormat = status !== "upstream_failed" ? - formatTime(benchmarkConfigs.job_duration && - benchmarkConfigs.job_duration || benchmarkConfigs.jobDuration) - : "Skipped" - getGrafanaLink = - {"Grafana - - } - return { - getGrafanaLink, getTimeFormat, status - } -} diff --git a/frontend/src/components/atoms/LinkIcon/index.jsx b/frontend/src/components/atoms/LinkIcon/index.jsx new file mode 100644 index 00000000..be63dfda --- /dev/null +++ b/frontend/src/components/atoms/LinkIcon/index.jsx @@ -0,0 +1,21 @@ +import Proptypes from "prop-types"; +const LinkIcon = (props) => { + const { link, target, src, altText, height, width } = props; + + return ( + <> + + {altText} + + + ); +}; +LinkIcon.propTypes = { + link: Proptypes.string, + target: Proptypes.string, + src: Proptypes.node, + altText: Proptypes.string, + height: Proptypes.number, + width: Proptypes.number, +}; +export default LinkIcon; diff --git a/frontend/src/components/molecules/ExpandedRow/index.jsx b/frontend/src/components/molecules/ExpandedRow/index.jsx index 5d6e930f..0edff39e 100644 --- a/frontend/src/components/molecules/ExpandedRow/index.jsx +++ b/frontend/src/components/molecules/ExpandedRow/index.jsx @@ -1,10 +1,13 @@ +import "./index.less"; + import * as CONSTANTS from "@/assets/constants/metadataConstants"; -import { Card, CardBody, Grid, GridItem } from "@patternfly/react-core"; +import { Card, CardBody, Grid, GridItem, Title } from "@patternfly/react-core"; import MetadataRow from "../MetaDataRow"; import PlotGraph from "@/components/atoms/PlotGraph"; import PropTypes from "prop-types"; +import TasksInfo from "@/components/molecules/TasksInfo"; import { uid } from "@/utils/helper.js"; import { useSelector } from "react-redux"; @@ -43,6 +46,10 @@ const RowContent = (props) => {
))} + + Tasks ran + + diff --git a/frontend/src/components/molecules/ExpandedRow/index.less b/frontend/src/components/molecules/ExpandedRow/index.less new file mode 100644 index 00000000..02499992 --- /dev/null +++ b/frontend/src/components/molecules/ExpandedRow/index.less @@ -0,0 +1,3 @@ +.type_heading { + padding: 10px 0; +} \ No newline at end of file diff --git a/frontend/src/components/molecules/MetaDataRow/index.jsx b/frontend/src/components/molecules/MetaDataRow/index.jsx index 6e4eb6dd..c6d46e57 100644 --- a/frontend/src/components/molecules/MetaDataRow/index.jsx +++ b/frontend/src/components/molecules/MetaDataRow/index.jsx @@ -28,7 +28,9 @@ const MetadataRow = (props) => { return ( <> - {props.heading} + + {props.heading} + diff --git a/frontend/src/components/molecules/TasksInfo/index.jsx b/frontend/src/components/molecules/TasksInfo/index.jsx new file mode 100644 index 00000000..4c385ec3 --- /dev/null +++ b/frontend/src/components/molecules/TasksInfo/index.jsx @@ -0,0 +1,108 @@ +import "./index.less"; + +import * as CONSTANTS from "@/assets/constants/grafanaConstants"; + +import { + CheckCircleIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, +} from "@patternfly/react-icons"; + +import GrafanaIcon from "@/assets/images/grafana-icon.png"; +import JenkinsIcon from "@/assets/images/jenkins-icon.svg"; +import LinkIcon from "@/components/atoms/LinkIcon"; +import Proptypes from "prop-types"; +import ProwIcon from "@/assets/images/prow-icon.png"; +import { formatTime } from "@/helpers/Formatters.js"; +import { useMemo } from "react"; + +const TasksInfo = (props) => { + const { config } = props; + + const startDate = useMemo( + () => new Date(config?.startDate).valueOf(), + [config?.startDate] + ); + const endDate = useMemo( + () => new Date(config?.endDate).valueOf(), + [config?.endDate] + ); + + const status = useMemo( + () => config.jobStatus?.toLowerCase(), + [config.jobStatus] + ); + + const grafanaLink = useMemo(() => { + const ciSystem_lCase = config.ciSystem?.toLowerCase(); + const isProw = ciSystem_lCase === "prow"; + const discreteBenchmark = + CONSTANTS.ciSystemMap[ciSystem_lCase]?.[ciSystem_lCase?.benchmark]; + + const hasBenchmark = Object.prototype.hasOwnProperty.call( + CONSTANTS.ciSystemMap?.[ciSystem_lCase], + config.benchmark + ); + const datasource = isProw + ? CONSTANTS.PROW_DATASOURCE + : hasBenchmark + ? discreteBenchmark?.dataSource + : CONSTANTS.DEFAULT_DATASOURCE; + + const dashboardURL = + discreteBenchmark?.dashboardURL ?? CONSTANTS.DASHBOARD_KUBE_BURNER; + + const datePart = `&from=${startDate}&to=${endDate}`; + const uuidPart = `&var-uuid=${config.uuid}`; + + if (config.benchmark === CONSTANTS.QUAY_LOAD_TEST) + return `${CONSTANTS.GRAFANA_BASE_URL}${CONSTANTS.DASHBOARD_QUAY}${datePart}${uuidPart}`; + return `${CONSTANTS.GRAFANA_BASE_URL}${dashboardURL}${datasource}${datePart}&var-platform=${config.platform}"&var-workload=${config.benchmark}${uuidPart}`; + }, [ + config.benchmark, + config.ciSystem, + config.platform, + config.uuid, + endDate, + startDate, + ]); + + const icons = useMemo( + () => ({ + failed: , + failure: , + success: , + upstream_failed: , + }), + [] + ); + return ( + <> +
+
{icons[status] ?? status.toUpperCase()}
+
{config.benchmark}
+
{`(${formatTime(config?.jobDuration)})`}
+ + +
+ + ); +}; +TasksInfo.propTypes = { + config: Proptypes.object, +}; +export default TasksInfo; diff --git a/frontend/src/components/molecules/TasksInfo/index.less b/frontend/src/components/molecules/TasksInfo/index.less new file mode 100644 index 00000000..dd088b46 --- /dev/null +++ b/frontend/src/components/molecules/TasksInfo/index.less @@ -0,0 +1,5 @@ +.info-wrapper { + display: flex; + justify-content: space-between; + width: 50%; +} \ No newline at end of file diff --git a/frontend/src/helpers/Formatters.js b/frontend/src/helpers/Formatters.js index 275660a1..8e6c5776 100644 --- a/frontend/src/helpers/Formatters.js +++ b/frontend/src/helpers/Formatters.js @@ -1,31 +1,29 @@ export function formatTime(time) { - // Hours, minutes and seconds - var hrs = ~~(time / 3600); - var mins = ~~((time % 3600) / 60); - var secs = ~~time % 60; + // Hours, minutes and seconds + let hrs = ~~(time / 3600); + let mins = ~~((time % 3600) / 60); + let secs = ~~time % 60; - // Output like "1:01" or "4:03:59" or "123:03:59" - var ret = ""; - if (hrs > 0) { - ret += "" + hrs + ":" + (mins < 10 ? "0" : ""); - } - ret += "" + mins + ":" + (secs < 10 ? "0" : ""); - ret += "" + secs; - return ret; + // Output like "1:01" or "4:03:59" or "123:03:59" + let ret = ""; + if (hrs > 0) { + ret += "" + hrs + ":" + (mins < 10 ? "0" : ""); + } + ret += "" + mins + ":" + (secs < 10 ? "0" : ""); + ret += "" + secs; + return ret; } export function getFormattedDate(date) { - let d = new Date(); - if (typeof date === 'string' || date instanceof String) - d = new Date(date); - else - d = new Date(date.props.value); + let d = new Date(); + if (typeof date === "string" || date instanceof String) d = new Date(date); + else d = new Date(date.props.value); - var year = d.getFullYear(); - var month = ("0" + (d.getMonth() + 1)).slice(-2); - var day = ("0" + (d.getDate())).slice(-2); - var hour = ("0" + (d.getHours())).slice(-2); - var min = ("0" + (d.getMinutes())).slice(-2); - var sec = ("0" + (d.getSeconds())).slice(-2); - return year + "-" + month + "-" + day + " " + hour + ":" + min + ":" + sec; + let year = d.getFullYear(); + let month = ("0" + (d.getMonth() + 1)).slice(-2); + let day = ("0" + d.getDate()).slice(-2); + let hour = ("0" + d.getHours()).slice(-2); + let min = ("0" + d.getMinutes()).slice(-2); + let sec = ("0" + d.getSeconds()).slice(-2); + return year + "-" + month + "-" + day + " " + hour + ":" + min + ":" + sec; } diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 575c3e66..d2526bd7 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -2,7 +2,6 @@ import { defineConfig } from "vite"; import path from "path"; import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ - export default defineConfig({ plugins: [react()], esbuild: {