From 7af70399a12aab4cbbee1f363423dde1cb33750f Mon Sep 17 00:00:00 2001 From: Takeshi Kimata <117462761+kimatata@users.noreply.github.com> Date: Sun, 28 Apr 2024 12:26:03 +0900 Subject: [PATCH] Apply test run status to progress donut chart --- backend/routes/runs/show.js | 32 ++++---- .../[projectId]/runs/[runId]/RunEditor.tsx | 43 ++++++++-- .../runs/[runId]/RunPregressDonutChart.tsx | 82 +++++++++---------- frontend/config/selection.ts | 10 +-- frontend/types/run.ts | 9 +- 5 files changed, 102 insertions(+), 74 deletions(-) diff --git a/backend/routes/runs/show.js b/backend/routes/runs/show.js index 8357051..ae08920 100644 --- a/backend/routes/runs/show.js +++ b/backend/routes/runs/show.js @@ -1,14 +1,12 @@ const express = require("express"); const router = express.Router(); const defineRun = require("../../models/runs"); -// const defineCase = require("../../models/cases"); -const { DataTypes } = require("sequelize"); +const defineRunCase = require("../../models/runCases"); +const { DataTypes, literal } = require("sequelize"); module.exports = function (sequelize) { const Run = defineRun(sequelize, DataTypes); - // const Case = defineCase(sequelize, DataTypes); - // Run.belongsToMany(Case, { through: "runCases" }); - // Case.belongsToMany(Run, { through: "runCases" }); + const RunCase = defineRunCase(sequelize, DataTypes); router.get("/:runId", async (req, res) => { const runId = req.params.runId; @@ -18,19 +16,21 @@ module.exports = function (sequelize) { } try { - // const project = await Run.findByPk(runId, { - // include: [ - // { - // model: Case, - // through: { attributes: ["status"] }, - // }, - // ], - // }); - const project = await Run.findByPk(runId); - if (!project) { + const run = await Run.findByPk(runId); + if (!run) { return res.status(404).send("Run not found"); } - res.json(project); + + // Counts test case status belonging to the run + const statusCounts = await RunCase.findAll({ + attributes: ["status", [literal("COUNT(*)"), "count"]], + where: { + runId: run.id, + }, + group: ["status"], + }); + + res.json({ run, statusCounts }); } catch (error) { console.error(error); res.status(500).send("Internal Server Error"); diff --git a/frontend/app/projects/[projectId]/runs/[runId]/RunEditor.tsx b/frontend/app/projects/[projectId]/runs/[runId]/RunEditor.tsx index 7395635..674f4de 100644 --- a/frontend/app/projects/[projectId]/runs/[runId]/RunEditor.tsx +++ b/frontend/app/projects/[projectId]/runs/[runId]/RunEditor.tsx @@ -25,11 +25,17 @@ import { ChevronDown, CopyPlus, CopyMinus, + RotateCw, } from "lucide-react"; -import RunProgressDounut from "./RunPregressDonutChart"; +import RunProgressChart from "./RunPregressDonutChart"; import TestCaseSelector from "./TestCaseSelector"; import { testRunStatus } from "@/config/selection"; -import { RunType, RunCaseType, RunCaseInfoType } from "@/types/run"; +import { + RunType, + RunCaseType, + RunCaseInfoType, + RunStatusCountType, +} from "@/types/run"; import { CaseType } from "@/types/case"; import { FolderType } from "@/types/folder"; import { @@ -63,6 +69,8 @@ export default function RunEditor({ projectId, runId }: Props) { const [testRun, setTestRun] = useState(defaultTestRun); const [folders, setFolders] = useState([]); const [runCases, setRunCases] = useState([]); + const [runStatusCounts, setRunStatusCounts] = + useState(); const [selectedKeys, setSelectedKeys] = useState(new Set([])); const [selectedFolder, setSelectedFolder] = useState({}); const [testcases, setTestCases] = useState([]); @@ -70,11 +78,16 @@ export default function RunEditor({ projectId, runId }: Props) { const [isUpdating, setIsUpdating] = useState(false); const router = useRouter(); + const fetchRunAndStatusCount = async () => { + const { run, statusCounts } = await fetchRun(runId); + setTestRun(run); + setRunStatusCounts(statusCounts); + }; + useEffect(() => { async function fetchDataEffect() { try { - const runData = await fetchRun(runId); - setTestRun(runData); + await fetchRunAndStatusCount(); const foldersData = await fetchFolders(projectId); setFolders(foldersData); setSelectedFolder(foldersData[0]); @@ -232,7 +245,23 @@ export default function RunEditor({ projectId, runId }: Props) {
- +
+
+

Progress

+ + + +
+ + +
Select test cases
- {selectedKeys.size > 0 || selectedKeys === "all" ? ( + {(selectedKeys.size > 0 || selectedKeys === "all") && (
diff --git a/frontend/app/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx b/frontend/app/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx index da446b2..d7fa6de 100644 --- a/frontend/app/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx +++ b/frontend/app/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx @@ -1,55 +1,51 @@ import React from "react"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import dynamic from "next/dynamic"; -import { Button, Tooltip } from "@nextui-org/react"; -import { RotateCw } from "lucide-react"; +import { testRunCaseStatus } from "@/config/selection"; +import { RunStatusCountType } from "@/types/run"; const Chart = dynamic(() => import("react-apexcharts"), { ssr: false }); -const testCases = [ - { name: "Passed", value: 12, color: "#059669" }, - { name: "Failed", value: 3, color: "#f87171" }, - { name: "Retest", value: 4, color: "#fbbf24" }, - { name: "Skipped", value: 14, color: "#4b5563" }, - { name: "Untested", value: 32, color: "#e5e7eb" }, -]; +type Props = { + statusCounts: RunStatusCountType[]; +}; -export default function RunProgressDounut() { +export default function RunProgressDounut({ statusCounts }: Props) { const [chartData, setChartData] = useState({ - series: testCases.map((entry) => { - return entry.value; - }), + series: [], options: { - labels: testCases.map((entry) => { - return entry.name; - }), - colors: testCases.map((entry) => { - return entry.color; - }), + labels: [], + colors: [], }, }); - return ( -
-
-

Progress

- - - -
- -
+ useEffect(() => { + const updateChartDate = () => { + if (statusCounts) { + const series = testRunCaseStatus.map((entry, index) => { + const found = statusCounts.find((itr) => itr.status === index); + return found ? found.count : 0; + }); + + const labels = testRunCaseStatus.map((entry) => entry.name); + const colors = testRunCaseStatus.map((entry) => entry.chartColor); + + setChartData({ + series, + options: { labels, colors }, + }); + } + }; + + updateChartDate(); + }, [statusCounts]); + + return ( + ); } diff --git a/frontend/config/selection.ts b/frontend/config/selection.ts index 7fe633e..b74769d 100644 --- a/frontend/config/selection.ts +++ b/frontend/config/selection.ts @@ -43,11 +43,11 @@ const testRunStatus = [ ]; const testRunCaseStatus = [ - { name: "Untested", uid: "untested", color: "primary" }, - { name: "Passed", uid: "passed", color: "success" }, - { name: "Retest", uid: "retest", color: "warning" }, - { name: "Failed", uid: "failed", color: "danger" }, - { name: "Skipped", uid: "skipped", color: "primary" }, + { name: "Untested", uid: "untested", color: "primary", chartColor: "#e5e7eb" }, + { name: "Passed", uid: "passed", color: "success", chartColor: "#059669" }, + { name: "Failed", uid: "failed", color: "danger", chartColor: "#f87171" }, + { name: "Retest", uid: "retest", color: "warning", chartColor: "#fbbf24" }, + { name: "Skipped", uid: "skipped", color: "primary", chartColor: "#4b5563" }, ]; export { diff --git a/frontend/types/run.ts b/frontend/types/run.ts index f4ea648..50a5a74 100644 --- a/frontend/types/run.ts +++ b/frontend/types/run.ts @@ -19,6 +19,11 @@ type RunCaseType = { type RunCaseInfoType = { runId: number; caseId: number; -} +}; -export { RunType, RunCaseType, RunCaseInfoType }; +type RunStatusCountType = { + status: number; + count: number; +}; + +export { RunType, RunCaseType, RunCaseInfoType, RunStatusCountType };