From 1cca4b0b2b54675f7fb57011490420fcec85b021 Mon Sep 17 00:00:00 2001 From: Artsiom Kharytonchyk Date: Sun, 21 Apr 2024 10:51:41 +0300 Subject: [PATCH] fix: coverage color scheme --- src/App.tsx | 4 +- src/components/CoverageCell.tsx | 17 ++++ src/components/CoverageDiffIcon.tsx | 21 +++++ src/components/CoverageRow.tsx | 26 ++++++ src/components/SummaryRow.tsx | 28 ++++++ src/models/CoverageDetails.ts | 8 ++ src/models/CoverageResponse.ts | 5 ++ src/models/CoverageValues.ts | 6 ++ src/pages/Coverage.tsx | 135 ++++------------------------ src/utils/RYGGradient.ts | 45 ++++++++++ 10 files changed, 174 insertions(+), 121 deletions(-) create mode 100644 src/components/CoverageCell.tsx create mode 100644 src/components/CoverageDiffIcon.tsx create mode 100644 src/components/CoverageRow.tsx create mode 100644 src/components/SummaryRow.tsx create mode 100644 src/models/CoverageDetails.ts create mode 100644 src/models/CoverageResponse.ts create mode 100644 src/models/CoverageValues.ts create mode 100644 src/utils/RYGGradient.ts diff --git a/src/App.tsx b/src/App.tsx index 03cbb6f..dae1f1e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,7 @@ export const ConfigContext = React.createContext<{ octokit: GitService | null; repositorySettings: Record; handleRepositorySelect: (repository: string, selected: boolean) => void; -}>({ octokit: null, repositorySettings: {}, handleRepositorySelect: () => {} }); +}>({ octokit: null, repositorySettings: {}, handleRepositorySelect: () => {}}); function App() { @@ -94,7 +94,7 @@ function App() { return ( <> = ({ + coverage, previous, +}) => { + const icon = React.useMemo( + () => CoverageDiffIcon(coverage - (previous), previous), + [coverage, previous] + ); + const styles = React.useMemo(() => RYGGradient(coverage), [coverage]); + return ( + {Math.round(coverage)}% {icon} + ); +}; diff --git a/src/components/CoverageDiffIcon.tsx b/src/components/CoverageDiffIcon.tsx new file mode 100644 index 0000000..d0a18c9 --- /dev/null +++ b/src/components/CoverageDiffIcon.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { Tooltip } from "@mui/material"; +import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; + +export const CoverageDiffIcon = (diff: number, prev: number) => { + if (prev === 0) return; + if (diff > 0) { + return ( + + + + ); + } + if (diff < 0) { + return ( + + + + ); + } +}; diff --git a/src/components/CoverageRow.tsx b/src/components/CoverageRow.tsx new file mode 100644 index 0000000..117a64c --- /dev/null +++ b/src/components/CoverageRow.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { TableCell, TableRow } from "@mui/material"; +import { CoverageDetails } from "../models/CoverageDetails"; +import { CoverageCell } from "./CoverageCell"; + +export const CoverageRow: React.FC<{ data: CoverageDetails; name: string; }> = ({ + name, data, +}) => { + return ( + + {name} + + + + + + ); +}; diff --git a/src/components/SummaryRow.tsx b/src/components/SummaryRow.tsx new file mode 100644 index 0000000..9d92dca --- /dev/null +++ b/src/components/SummaryRow.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { TableCell, TableRow } from "@mui/material"; +import { CoverageDetails } from "../models/CoverageDetails"; +import { CoverageCell } from "./CoverageCell"; + +export const SummaryRow: React.FC<{ data: CoverageDetails[]; }> = ({ data }) => { + const summaryData = React.useMemo(() => { + return { + lines: data.reduce((acc, curr) => acc + curr.current.lines, 0) / data.length, + statements: data.reduce((acc, curr) => acc + curr.current.statements, 0) / + data.length, + functions: data.reduce((acc, curr) => acc + curr.current.functions, 0) / + data.length, + branches: data.reduce((acc, curr) => acc + curr.current.branches, 0) / + data.length, + }; + }, [data]); + + return ( + + Summary + + + + + + ); +}; diff --git a/src/models/CoverageDetails.ts b/src/models/CoverageDetails.ts new file mode 100644 index 0000000..ad24d7f --- /dev/null +++ b/src/models/CoverageDetails.ts @@ -0,0 +1,8 @@ +import { CoverageValues } from "./CoverageValues"; + +export interface CoverageDetails { + current: CoverageValues; + previous: CoverageValues; + diff: CoverageValues; + changed: string; +} diff --git a/src/models/CoverageResponse.ts b/src/models/CoverageResponse.ts new file mode 100644 index 0000000..11bcba3 --- /dev/null +++ b/src/models/CoverageResponse.ts @@ -0,0 +1,5 @@ +import { CoverageDetails } from "./CoverageDetails"; + +export interface CoverageResponse { + [key: string]: CoverageDetails; +} diff --git a/src/models/CoverageValues.ts b/src/models/CoverageValues.ts new file mode 100644 index 0000000..94c8c58 --- /dev/null +++ b/src/models/CoverageValues.ts @@ -0,0 +1,6 @@ +export interface CoverageValues { + lines: number; + statements: number; + functions: number; + branches: number; +} diff --git a/src/pages/Coverage.tsx b/src/pages/Coverage.tsx index 32362e3..90285ef 100644 --- a/src/pages/Coverage.tsx +++ b/src/pages/Coverage.tsx @@ -10,128 +10,15 @@ import { TableContainer, TableHead, TableRow, - Tooltip, Typography, } from "@mui/material"; import { Navigate } from "react-router-dom"; import { ConfigContext } from "../App"; import ErrorMessage from "../components/MissingCoverageErrorMessage"; import lz from "lz-string"; -import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; - -interface CoverageDetails { - current: CoverageValues; - previous: CoverageValues; - diff: CoverageValues; - changed: string; -} - -interface CoverageValues { - lines: number; - statements: number; - functions: number; - branches: number; -} - -interface CoverageResponse { - [key: string]: CoverageDetails; -} - -const coverageStyles = (coverage: number) => { - console.log(coverage, coverage < 60); - if (coverage < 60) return { color: "#f44336", fontWeight: "bolder" }; - if (coverage < 80) return { color: "#ed6c02", fontWeight: "bold" }; - return { color: "#4caf50", fontWeight: "normal" }; -}; - -const coverageDiffIcon = (diff: number, prev: number) => { - if (prev === 0) return; - if (diff > 0) { - return ( - - - - ); - } - if (diff < 0) { - return ( - - - - ); - } -}; - -const CoverageCell: React.FC<{ coverage: number; previous: number }> = ({ - coverage, - previous, -}) => { - const icon = React.useMemo( - () => coverageDiffIcon(coverage - previous, previous), - [coverage, previous] - ); - const styles = React.useMemo(() => coverageStyles(coverage), [coverage]); - return ( - - {coverage.toFixed(2)} {icon} - - ); -}; - -const CoverageRow: React.FC<{ data: CoverageDetails; name: string }> = ({ - name, - data, -}) => { - return ( - - {name} - - - - - - ); -}; - -const SummaryRow: React.FC<{ data: CoverageDetails[] }> = ({ data }) => { - const summaryData = React.useMemo(() => { - return { - lines: - data.reduce((acc, curr) => acc + curr.current.lines, 0) / data.length, - statements: - data.reduce((acc, curr) => acc + curr.current.statements, 0) / - data.length, - functions: - data.reduce((acc, curr) => acc + curr.current.functions, 0) / - data.length, - branches: - data.reduce((acc, curr) => acc + curr.current.branches, 0) / - data.length, - }; - }, [data]); - - return ( - - Summary - - - - - - ); -}; +import { CoverageResponse } from "../models/CoverageResponse"; +import { CoverageRow } from "../components/CoverageRow"; +import { SummaryRow } from "../components/SummaryRow"; export const Coverage: React.FC = () => { const { data, isLoading, isError } = useQuery({ @@ -150,7 +37,10 @@ export const Coverage: React.FC = () => { retry: false, }); - const dataLength = React.useMemo(() => Object.keys(data || {}).length, [data]); + const dataLength = React.useMemo( + () => Object.keys(data || {}).length, + [data] + ); const { repositorySettings } = React.useContext(ConfigContext); @@ -178,10 +68,17 @@ export const Coverage: React.FC = () => { if (!localStorage.getItem("token")) { return ; } - + return ( -

Coverage

+ +

Coverage

+
{isLoading && } {isError && } {data && dataLength === 0 && ( diff --git a/src/utils/RYGGradient.ts b/src/utils/RYGGradient.ts new file mode 100644 index 0000000..7e98ed7 --- /dev/null +++ b/src/utils/RYGGradient.ts @@ -0,0 +1,45 @@ +interface StyleProps { + color: string; + fontWeight: string; + textShadow?: string; + backgroundColor?: string; +} + +const RYGGradient = (coverage: number): StyleProps => { + let r: number = 0; + let g: number = 0; + let b: number = 0; + + const red = { r: 185, g: 28, b: 28 }; + const yellow = { r: 234, g: 179, b: 8 }; + const green = { r: 101, g: 163, b: 13 }; + + if (coverage <= 45) { + r = red.r; + g = red.g; + b = red.b; + } else if (coverage <= 60) { + const ratio = (coverage - 45) / 15; + r = red.r + (yellow.r - red.r) * ratio; + g = red.g + (yellow.g - red.g) * ratio; + b = red.b + (yellow.b - red.b) * ratio; + } else if (coverage <= 85) { + const ratio = (coverage - 60) / 25; + r = yellow.r + (green.r - yellow.r) * ratio; + g = yellow.g + (green.g - yellow.g) * ratio; + b = yellow.b + (green.b - yellow.b) * ratio; + } else { + r = green.r; + g = green.g; + b = green.b; + } + + const fontWeight = coverage < 45 ? "bolder" : "bold"; + const color = `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`; + + return { color, fontWeight }; +}; + +export default RYGGradient; + +