Apply test run status to progress donut chart
This commit is contained in:
@@ -1,14 +1,12 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const defineRun = require("../../models/runs");
|
const defineRun = require("../../models/runs");
|
||||||
// const defineCase = require("../../models/cases");
|
const defineRunCase = require("../../models/runCases");
|
||||||
const { DataTypes } = require("sequelize");
|
const { DataTypes, literal } = require("sequelize");
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
const Run = defineRun(sequelize, DataTypes);
|
const Run = defineRun(sequelize, DataTypes);
|
||||||
// const Case = defineCase(sequelize, DataTypes);
|
const RunCase = defineRunCase(sequelize, DataTypes);
|
||||||
// Run.belongsToMany(Case, { through: "runCases" });
|
|
||||||
// Case.belongsToMany(Run, { through: "runCases" });
|
|
||||||
|
|
||||||
router.get("/:runId", async (req, res) => {
|
router.get("/:runId", async (req, res) => {
|
||||||
const runId = req.params.runId;
|
const runId = req.params.runId;
|
||||||
@@ -18,19 +16,21 @@ module.exports = function (sequelize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// const project = await Run.findByPk(runId, {
|
const run = await Run.findByPk(runId);
|
||||||
// include: [
|
if (!run) {
|
||||||
// {
|
|
||||||
// model: Case,
|
|
||||||
// through: { attributes: ["status"] },
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
const project = await Run.findByPk(runId);
|
|
||||||
if (!project) {
|
|
||||||
return res.status(404).send("Run not found");
|
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) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
res.status(500).send("Internal Server Error");
|
res.status(500).send("Internal Server Error");
|
||||||
|
|||||||
@@ -25,11 +25,17 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
CopyPlus,
|
CopyPlus,
|
||||||
CopyMinus,
|
CopyMinus,
|
||||||
|
RotateCw,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import RunProgressDounut from "./RunPregressDonutChart";
|
import RunProgressChart from "./RunPregressDonutChart";
|
||||||
import TestCaseSelector from "./TestCaseSelector";
|
import TestCaseSelector from "./TestCaseSelector";
|
||||||
import { testRunStatus } from "@/config/selection";
|
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 { CaseType } from "@/types/case";
|
||||||
import { FolderType } from "@/types/folder";
|
import { FolderType } from "@/types/folder";
|
||||||
import {
|
import {
|
||||||
@@ -63,6 +69,8 @@ export default function RunEditor({ projectId, runId }: Props) {
|
|||||||
const [testRun, setTestRun] = useState<RunType>(defaultTestRun);
|
const [testRun, setTestRun] = useState<RunType>(defaultTestRun);
|
||||||
const [folders, setFolders] = useState([]);
|
const [folders, setFolders] = useState([]);
|
||||||
const [runCases, setRunCases] = useState<RunCaseType[]>([]);
|
const [runCases, setRunCases] = useState<RunCaseType[]>([]);
|
||||||
|
const [runStatusCounts, setRunStatusCounts] =
|
||||||
|
useState<RunStatusCountType[]>();
|
||||||
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
|
||||||
const [selectedFolder, setSelectedFolder] = useState<FolderType>({});
|
const [selectedFolder, setSelectedFolder] = useState<FolderType>({});
|
||||||
const [testcases, setTestCases] = useState<CaseType[]>([]);
|
const [testcases, setTestCases] = useState<CaseType[]>([]);
|
||||||
@@ -70,11 +78,16 @@ export default function RunEditor({ projectId, runId }: Props) {
|
|||||||
const [isUpdating, setIsUpdating] = useState<boolean>(false);
|
const [isUpdating, setIsUpdating] = useState<boolean>(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const fetchRunAndStatusCount = async () => {
|
||||||
|
const { run, statusCounts } = await fetchRun(runId);
|
||||||
|
setTestRun(run);
|
||||||
|
setRunStatusCounts(statusCounts);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDataEffect() {
|
async function fetchDataEffect() {
|
||||||
try {
|
try {
|
||||||
const runData = await fetchRun(runId);
|
await fetchRunAndStatusCount();
|
||||||
setTestRun(runData);
|
|
||||||
const foldersData = await fetchFolders(projectId);
|
const foldersData = await fetchFolders(projectId);
|
||||||
setFolders(foldersData);
|
setFolders(foldersData);
|
||||||
setSelectedFolder(foldersData[0]);
|
setSelectedFolder(foldersData[0]);
|
||||||
@@ -232,7 +245,23 @@ export default function RunEditor({ projectId, runId }: Props) {
|
|||||||
<div className="container mx-auto max-w-5xl pt-6 px-6 flex-grow">
|
<div className="container mx-auto max-w-5xl pt-6 px-6 flex-grow">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div>
|
<div>
|
||||||
<RunProgressDounut />
|
<div className="w-96 h-72">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<h4 className="font-bold">Progress</h4>
|
||||||
|
<Tooltip content="Refresh">
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
size="sm"
|
||||||
|
className="rounded-full bg-transparent ms-1"
|
||||||
|
onPress={fetchRunAndStatusCount}
|
||||||
|
>
|
||||||
|
<RotateCw size={16} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RunProgressChart statusCounts={runStatusCounts} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<Input
|
<Input
|
||||||
@@ -290,7 +319,7 @@ export default function RunEditor({ projectId, runId }: Props) {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h6 className="h-8 font-bold">Select test cases</h6>
|
<h6 className="h-8 font-bold">Select test cases</h6>
|
||||||
<div>
|
<div>
|
||||||
{selectedKeys.size > 0 || selectedKeys === "all" ? (
|
{(selectedKeys.size > 0 || selectedKeys === "all") && (
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
<Button
|
<Button
|
||||||
@@ -316,8 +345,6 @@ export default function RunEditor({ projectId, runId }: Props) {
|
|||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,55 +1,51 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { Button, Tooltip } from "@nextui-org/react";
|
import { testRunCaseStatus } from "@/config/selection";
|
||||||
import { RotateCw } from "lucide-react";
|
import { RunStatusCountType } from "@/types/run";
|
||||||
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
|
||||||
const testCases = [
|
type Props = {
|
||||||
{ name: "Passed", value: 12, color: "#059669" },
|
statusCounts: RunStatusCountType[];
|
||||||
{ name: "Failed", value: 3, color: "#f87171" },
|
};
|
||||||
{ name: "Retest", value: 4, color: "#fbbf24" },
|
|
||||||
{ name: "Skipped", value: 14, color: "#4b5563" },
|
|
||||||
{ name: "Untested", value: 32, color: "#e5e7eb" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function RunProgressDounut() {
|
export default function RunProgressDounut({ statusCounts }: Props) {
|
||||||
const [chartData, setChartData] = useState({
|
const [chartData, setChartData] = useState({
|
||||||
series: testCases.map((entry) => {
|
series: [],
|
||||||
return entry.value;
|
|
||||||
}),
|
|
||||||
options: {
|
options: {
|
||||||
labels: testCases.map((entry) => {
|
labels: [],
|
||||||
return entry.name;
|
colors: [],
|
||||||
}),
|
|
||||||
colors: testCases.map((entry) => {
|
|
||||||
return entry.color;
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return (
|
|
||||||
<div className="w-96 h-72">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<h4 className="font-bold">Progress</h4>
|
|
||||||
<Tooltip content="Refresh">
|
|
||||||
<Button
|
|
||||||
isIconOnly
|
|
||||||
size="sm"
|
|
||||||
className="rounded-full bg-transparent ms-1"
|
|
||||||
onPress={() => console.log("refresh")}
|
|
||||||
>
|
|
||||||
<RotateCw size={16} />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Chart
|
useEffect(() => {
|
||||||
options={chartData.options}
|
const updateChartDate = () => {
|
||||||
series={chartData.series}
|
if (statusCounts) {
|
||||||
type="donut"
|
const series = testRunCaseStatus.map((entry, index) => {
|
||||||
width={"100%"}
|
const found = statusCounts.find((itr) => itr.status === index);
|
||||||
height={"100%"}
|
return found ? found.count : 0;
|
||||||
/>
|
});
|
||||||
</div>
|
|
||||||
|
const labels = testRunCaseStatus.map((entry) => entry.name);
|
||||||
|
const colors = testRunCaseStatus.map((entry) => entry.chartColor);
|
||||||
|
|
||||||
|
setChartData({
|
||||||
|
series,
|
||||||
|
options: { labels, colors },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateChartDate();
|
||||||
|
}, [statusCounts]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={chartData.options}
|
||||||
|
series={chartData.series}
|
||||||
|
type="donut"
|
||||||
|
width={"100%"}
|
||||||
|
height={"100%"}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ const testRunStatus = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const testRunCaseStatus = [
|
const testRunCaseStatus = [
|
||||||
{ name: "Untested", uid: "untested", color: "primary" },
|
{ name: "Untested", uid: "untested", color: "primary", chartColor: "#e5e7eb" },
|
||||||
{ name: "Passed", uid: "passed", color: "success" },
|
{ name: "Passed", uid: "passed", color: "success", chartColor: "#059669" },
|
||||||
{ name: "Retest", uid: "retest", color: "warning" },
|
{ name: "Failed", uid: "failed", color: "danger", chartColor: "#f87171" },
|
||||||
{ name: "Failed", uid: "failed", color: "danger" },
|
{ name: "Retest", uid: "retest", color: "warning", chartColor: "#fbbf24" },
|
||||||
{ name: "Skipped", uid: "skipped", color: "primary" },
|
{ name: "Skipped", uid: "skipped", color: "primary", chartColor: "#4b5563" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ type RunCaseType = {
|
|||||||
type RunCaseInfoType = {
|
type RunCaseInfoType = {
|
||||||
runId: number;
|
runId: number;
|
||||||
caseId: number;
|
caseId: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export { RunType, RunCaseType, RunCaseInfoType };
|
type RunStatusCountType = {
|
||||||
|
status: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { RunType, RunCaseType, RunCaseInfoType, RunStatusCountType };
|
||||||
|
|||||||
Reference in New Issue
Block a user