Display information of project on home tab
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
const priorities = [
|
const priorities = [
|
||||||
{ uid: "critical", color: "#d00002" },
|
{ uid: "critical", color: "#d00002", chartColor: "#d00002" },
|
||||||
{ uid: "high", color: "#ee6b4e" },
|
{ uid: "high", color: "#ee6b4e", chartColor: "#ee6b4e" },
|
||||||
{ uid: "medium", color: "#fccb69" },
|
{ uid: "medium", color: "#fccb69", chartColor: "#fccb69" },
|
||||||
{ uid: "low", color: "#0b62e8" },
|
{ uid: "low", color: "#0b62e8", chartColor: "#0b62e8" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const testTypes = [
|
const testTypes = [
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"Project": {
|
"Project": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"test_cases": "Test Cases",
|
"test_cases": "Test Cases",
|
||||||
"test_run": "Test Runs"
|
"test_runs": "Test Runs"
|
||||||
},
|
},
|
||||||
"Home": {
|
"Home": {
|
||||||
"Folders": "Folders",
|
"Folders": "Folders",
|
||||||
@@ -49,7 +49,12 @@
|
|||||||
"destructive": "Destructive",
|
"destructive": "Destructive",
|
||||||
"regression": "Regression",
|
"regression": "Regression",
|
||||||
"automated": "Automated",
|
"automated": "Automated",
|
||||||
"manual": "Manual"
|
"manual": "Manual",
|
||||||
|
"priority": "Priority",
|
||||||
|
"critical": "Critical",
|
||||||
|
"high": "High",
|
||||||
|
"medium": "Medium",
|
||||||
|
"low": "Low"
|
||||||
},
|
},
|
||||||
"Folders": {
|
"Folders": {
|
||||||
"folder": "Folder",
|
"folder": "Folder",
|
||||||
|
|||||||
@@ -49,7 +49,12 @@
|
|||||||
"destructive": "破壊",
|
"destructive": "破壊",
|
||||||
"regression": "回帰",
|
"regression": "回帰",
|
||||||
"automated": "自動",
|
"automated": "自動",
|
||||||
"manual": "手動"
|
"manual": "手動",
|
||||||
|
"priority": "優先度",
|
||||||
|
"critical": "致",
|
||||||
|
"high": "高",
|
||||||
|
"medium": "中",
|
||||||
|
"low": "低"
|
||||||
},
|
},
|
||||||
"Folders": {
|
"Folders": {
|
||||||
"folder": "フォルダー",
|
"folder": "フォルダー",
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { priorities } from "@/config/selection";
|
||||||
|
import { CasePriorityCountType } from "@/types/case";
|
||||||
|
import { HomeMessages } from "./page";
|
||||||
|
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
priorityCounts: CasePriorityCountType[];
|
||||||
|
messages: HomeMessages;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TestPriorityDonutChart({
|
||||||
|
priorityCounts,
|
||||||
|
messages,
|
||||||
|
}: Props) {
|
||||||
|
const [chartData, setChartData] = useState({
|
||||||
|
series: [],
|
||||||
|
options: {
|
||||||
|
labels: [],
|
||||||
|
colors: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateChartDate = () => {
|
||||||
|
if (priorityCounts) {
|
||||||
|
const series = priorities.map((entry, index) => {
|
||||||
|
const found = priorityCounts.find((itr) => itr.priority === index);
|
||||||
|
return found ? found.count : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const labels = priorities.map((entry) => messages[entry.uid]);
|
||||||
|
const colors = priorities.map((entry) => entry.chartColor);
|
||||||
|
|
||||||
|
setChartData({
|
||||||
|
series,
|
||||||
|
options: { labels, colors },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateChartDate();
|
||||||
|
}, [priorityCounts]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chart
|
||||||
|
options={chartData.options}
|
||||||
|
series={chartData.series}
|
||||||
|
type="donut"
|
||||||
|
width={"100%"}
|
||||||
|
height={"100%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { ProjectType } from "@/types/project";
|
||||||
|
import { testTypes, priorities } from "@/config/selection";
|
||||||
|
|
||||||
|
// aggregate folder, case, run mum
|
||||||
|
function aggregateBasicInfo(project: ProjectType) {
|
||||||
|
const folderNum = project.Folders.length;
|
||||||
|
const runNum = project.Runs.length;
|
||||||
|
|
||||||
|
let caseNum = 0;
|
||||||
|
project.Folders.forEach((folder) => {
|
||||||
|
caseNum += folder.Cases.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { folderNum, runNum, caseNum };
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregate test types of each case
|
||||||
|
function aggregateTestType(project: ProjectType) {
|
||||||
|
const typesCounts = {};
|
||||||
|
project.Folders.forEach((folder) => {
|
||||||
|
folder.Cases.forEach((testcase) => {
|
||||||
|
const type = testcase.type;
|
||||||
|
typesCounts[type] = (typesCounts[type] || 0) + 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
for (let type = 0; type <= testTypes.length; type++) {
|
||||||
|
result.push({ type: type, count: typesCounts[type] || 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function aggregateTestPriority(project: ProjectType) {
|
||||||
|
const priorityCounts = {};
|
||||||
|
project.Folders.forEach((folder) => {
|
||||||
|
folder.Cases.forEach((testcase) => {
|
||||||
|
const priority = testcase.priority;
|
||||||
|
priorityCounts[priority] = (priorityCounts[priority] || 0) + 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
for (let priority = 0; priority <= priorities.length; priority++) {
|
||||||
|
result.push({ priority: priority, count: priorityCounts[priority] || 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { aggregateBasicInfo, aggregateTestType, aggregateTestPriority };
|
||||||
@@ -4,10 +4,15 @@ import { Divider } from "@nextui-org/react";
|
|||||||
import { title } from "@/components/primitives";
|
import { title } from "@/components/primitives";
|
||||||
import { Card, CardBody, Chip } from "@nextui-org/react";
|
import { Card, CardBody, Chip } from "@nextui-org/react";
|
||||||
import { Folder, Clipboard, FlaskConical } from "lucide-react";
|
import { Folder, Clipboard, FlaskConical } from "lucide-react";
|
||||||
import { CaseTypeCountType } from "@/types/case";
|
import { CaseTypeCountType, CasePriorityCountType } from "@/types/case";
|
||||||
import { HomeMessages } from "./page";
|
import { HomeMessages } from "./page";
|
||||||
import { testTypes } from "@/config/selection";
|
import {
|
||||||
|
aggregateBasicInfo,
|
||||||
|
aggregateTestPriority,
|
||||||
|
aggregateTestType,
|
||||||
|
} from "./aggregate";
|
||||||
import TestTypesChart from "./TestTypesDonutChart";
|
import TestTypesChart from "./TestTypesDonutChart";
|
||||||
|
import TestPriorityChart from "./TestPriorityDonutChart";
|
||||||
import Config from "@/config/config";
|
import Config from "@/config/config";
|
||||||
const apiServer = Config.apiServer;
|
const apiServer = Config.apiServer;
|
||||||
|
|
||||||
@@ -47,6 +52,8 @@ export function Home({ projectId, messages }: Props) {
|
|||||||
const [caseNum, setCaseNum] = useState(0);
|
const [caseNum, setCaseNum] = useState(0);
|
||||||
const [runNum, setRunNum] = useState(0);
|
const [runNum, setRunNum] = useState(0);
|
||||||
const [typesCounts, setTypesCounts] = useState<CaseTypeCountType[]>();
|
const [typesCounts, setTypesCounts] = useState<CaseTypeCountType[]>();
|
||||||
|
const [priorityCounts, setPriorityCounts] =
|
||||||
|
useState<CasePriorityCountType[]>();
|
||||||
const url = `${apiServer}/home/${projectId}`;
|
const url = `${apiServer}/home/${projectId}`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -65,42 +72,21 @@ export function Home({ projectId, messages }: Props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function aggregate() {
|
async function aggregate() {
|
||||||
aggregateBasicInfo();
|
const { folderNum, runNum, caseNum } = aggregateBasicInfo(project);
|
||||||
aggregateTestType();
|
setFolderNum(folderNum);
|
||||||
|
setRunNum(runNum);
|
||||||
|
setCaseNum(caseNum);
|
||||||
|
|
||||||
|
const typeRet = aggregateTestType(project);
|
||||||
|
setTypesCounts([...typeRet]);
|
||||||
|
|
||||||
|
const priorityRet = aggregateTestPriority(project);
|
||||||
|
setPriorityCounts([...priorityRet]);
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregate();
|
aggregate();
|
||||||
}, [project]);
|
}, [project]);
|
||||||
|
|
||||||
// aggregate folder, case, run mum
|
|
||||||
function aggregateBasicInfo() {
|
|
||||||
let num = 0;
|
|
||||||
setFolderNum(project.Folders.length);
|
|
||||||
setRunNum(project.Runs.length);
|
|
||||||
project.Folders.forEach((folder) => {
|
|
||||||
num += folder.Cases.length;
|
|
||||||
});
|
|
||||||
setCaseNum(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// aggregate test types of each case
|
|
||||||
function aggregateTestType() {
|
|
||||||
const tempTypesCounts = {};
|
|
||||||
project.Folders.forEach((folder) => {
|
|
||||||
folder.Cases.forEach((testcase) => {
|
|
||||||
const type = testcase.type;
|
|
||||||
tempTypesCounts[type] = (tempTypesCounts[type] || 0) + 1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
for (let type = 0; type <= testTypes.length; type++) {
|
|
||||||
result.push({ type: type, count: tempTypesCounts[type] || 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
setTypesCounts([...result]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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">
|
||||||
<h1 className={title({ size: "sm" })}>{project.name}</h1>
|
<h1 className={title({ size: "sm" })}>{project.name}</h1>
|
||||||
@@ -133,17 +119,21 @@ export function Home({ projectId, messages }: Props) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Divider className="my-6" />
|
<Divider className="my-6" />
|
||||||
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div style={{ width: "32rem", height: "32rem" }}>
|
<div style={{ width: "32rem", height: "18rem" }}>
|
||||||
<h3>{messages.testTypes}</h3>
|
<h3>{messages.testTypes}</h3>
|
||||||
<TestTypesChart typesCounts={typesCounts} messages={messages} />
|
<TestTypesChart typesCounts={typesCounts} messages={messages} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ width: "32rem", height: "32rem" }}>
|
<div style={{ width: "30rem", height: "18rem" }}>
|
||||||
<h3>{messages.testTypes}</h3>
|
<h3>{messages.priority}</h3>
|
||||||
<TestTypesChart typesCounts={typesCounts} messages={messages} />
|
<TestPriorityChart
|
||||||
|
priorityCounts={priorityCounts}
|
||||||
|
messages={messages}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Divider className="my-6" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export type HomeMessages = {
|
|||||||
regression: string;
|
regression: string;
|
||||||
automated: string;
|
automated: string;
|
||||||
manual: string;
|
manual: string;
|
||||||
|
priority: string;
|
||||||
|
critical: string;
|
||||||
|
high: string;
|
||||||
|
medium: string;
|
||||||
|
low: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Page({ params }: { params: { projectId: string } }) {
|
export default function Page({ params }: { params: { projectId: string } }) {
|
||||||
@@ -41,6 +46,11 @@ export default function Page({ params }: { params: { projectId: string } }) {
|
|||||||
regression: t("regression"),
|
regression: t("regression"),
|
||||||
automated: t("automated"),
|
automated: t("automated"),
|
||||||
manual: t("manual"),
|
manual: t("manual"),
|
||||||
|
priority: t("priority"),
|
||||||
|
critical: t("critical"),
|
||||||
|
high: t("high"),
|
||||||
|
medium: t("medium"),
|
||||||
|
low: t("low"),
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ type CaseTypeCountType = {
|
|||||||
count: number;
|
count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CasePriorityCountType = {
|
||||||
|
priority: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type CasesMessages = {
|
export type CasesMessages = {
|
||||||
testCaseList: string;
|
testCaseList: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -119,4 +124,4 @@ export type CaseMessages = {
|
|||||||
maxFileSize: string;
|
maxFileSize: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { CaseType, StepType, AttachmentType, CaseTypeCountType, CasesMessages, CaseMessages };
|
export { CaseType, StepType, AttachmentType, CaseTypeCountType, CasePriorityCountType, CasesMessages, CaseMessages };
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { CaseType } from "./case";
|
||||||
|
|
||||||
export type FolderType = {
|
export type FolderType = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -6,6 +8,7 @@ export type FolderType = {
|
|||||||
parentFolderId: number | null;
|
parentFolderId: number | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
Cases: CaseType[]; // additional property
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FoldersMessages = {
|
export type FoldersMessages = {
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import { FolderType } from "./folder";
|
||||||
|
import { RunType } from "./run";
|
||||||
|
|
||||||
export type ProjectType = {
|
export type ProjectType = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
detail: string;
|
detail: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
Folders: FolderType[]; // additional property
|
||||||
|
Runs: RunType[]; // additional property
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProjectsMessages = {
|
export type ProjectsMessages = {
|
||||||
|
|||||||
Reference in New Issue
Block a user