feat(工作台): 联调工作台测试计划一揽子率

This commit is contained in:
xinxin.wu 2024-11-18 18:45:05 +08:00 committed by 刘瑞斌
parent d4191a07d8
commit 954ecca484
14 changed files with 191 additions and 93 deletions

View File

@ -13,6 +13,8 @@ import type {
PassRateDataType, PassRateDataType,
SelectedCardItem, SelectedCardItem,
WorkHomePageDetail, WorkHomePageDetail,
WorkTestPlanDetail,
WorkTestPlanRageDetail,
} from '@/models/workbench/homePage'; } from '@/models/workbench/homePage';
import { import {
@ -45,6 +47,7 @@ import {
WorkProOverviewDetailUrl, WorkProOverviewDetailUrl,
WorkReviewListUrl, WorkReviewListUrl,
WorkScenarioCaseCountDetailUrl, WorkScenarioCaseCountDetailUrl,
WorkTestPlanRageUrl,
WorkTodoBugListUrl, WorkTodoBugListUrl,
WorkTodoPlanListUrl, WorkTodoPlanListUrl,
WorkTodoReviewListUrl, WorkTodoReviewListUrl,
@ -196,6 +199,10 @@ export function workPlanLegacyBug(data: WorkHomePageDetail) {
export function workApiCountCoverRage(projectId: string) { export function workApiCountCoverRage(projectId: string) {
return MSR.get<ApiCoverageData>({ url: WorkApiCountCoverRateUrl, params: projectId }, { ignoreCancelToken: true }); return MSR.get<ApiCoverageData>({ url: WorkApiCountCoverRateUrl, params: projectId }, { ignoreCancelToken: true });
} }
// 工作台-首页-测试计划数量
export function workTestPlanRage(data: WorkTestPlanDetail) {
return MSR.post<WorkTestPlanRageDetail>({ url: WorkTestPlanRageUrl, data }, { ignoreCancelToken: true });
}
// 待办-用例评审列表 // 待办-用例评审列表
export function workbenchTodoReviewList(data: TableQueryParams) { export function workbenchTodoReviewList(data: TableQueryParams) {

View File

@ -30,3 +30,4 @@ export const WorkBugByMeCreatedUrl = '/dashboard/create_bug_by_me'; // 工作台
export const WorkBugHandleByMeUrl = '/dashboard/handle_bug_by_me'; // 工作台-首页-待我处理的缺陷 export const WorkBugHandleByMeUrl = '/dashboard/handle_bug_by_me'; // 工作台-首页-待我处理的缺陷
export const WorkPlanLegacyBugUrl = '/dashboard/plan_legacy_bug'; // 工作台-首页-测试计划遗留缺陷 export const WorkPlanLegacyBugUrl = '/dashboard/plan_legacy_bug'; // 工作台-首页-测试计划遗留缺陷
export const WorkApiCountCoverRateUrl = '/api/definition/rage'; // 工作台-首页-覆盖率 export const WorkApiCountCoverRateUrl = '/api/definition/rage'; // 工作台-首页-覆盖率
export const WorkTestPlanRageUrl = '/test-plan/rage'; // 工作台-首页-测试计划数

View File

@ -180,7 +180,7 @@ export const defaultValueMap: Record<string, any> = {
}, },
pass: { pass: {
defaultList: cloneDeep(defaultPass), defaultList: cloneDeep(defaultPass),
color: ['#D4D4D8', '#00C261'], color: ['#00C261', '#D4D4D8'],
defaultName: 'workbench.homePage.passRate', defaultName: 'workbench.homePage.passRate',
}, },
complete: { complete: {

View File

@ -39,6 +39,24 @@ export interface WorkHomePageDetail extends TableQueryParams {
organizationId: string; organizationId: string;
} }
export interface WorkTestPlanDetail {
dayNumber: number | string;
startTime: number | null;
endTime: number | null;
projectId: string;
}
export interface WorkTestPlanRageDetail {
unExecute: number;
executed: number;
passed: number;
notPassed: number;
finished: number;
running: number;
prepared: number;
archived: number;
errorCode: number;
}
export interface TimeFormParams { export interface TimeFormParams {
dayNumber: number | string; dayNumber: number | string;
startTime: number; startTime: number;

View File

@ -176,7 +176,7 @@
return props.item.key === WorkCardEnum.API_CASE_COUNT return props.item.key === WorkCardEnum.API_CASE_COUNT
? { ? {
name: t('workbench.homePage.caseExecutionRate'), name: t('workbench.homePage.caseExecutionRate'),
color: ['#00C261', '#EDEDF1'], color: ['#EDEDF1', '#00C261'],
tooltipText: t('workbench.homePage.apiCaseCountExecuteRateTooltip'), tooltipText: t('workbench.homePage.apiCaseCountExecuteRateTooltip'),
} }
: { : {
@ -190,12 +190,12 @@
return props.item.key === WorkCardEnum.API_CASE_COUNT return props.item.key === WorkCardEnum.API_CASE_COUNT
? { ? {
name: t('workbench.homePage.casePassedRate'), name: t('workbench.homePage.casePassedRate'),
color: ['#00C261', '#ED0303'], color: ['#ED0303', '#00C261'],
tooltipText: t('workbench.homePage.apiCaseCountPassRateTooltip'), tooltipText: t('workbench.homePage.apiCaseCountPassRateTooltip'),
} }
: { : {
name: t('workbench.homePage.executionRate'), name: t('workbench.homePage.executionRate'),
color: ['#00C261', '#ED0303'], color: ['#ED0303', '#00C261'],
tooltipText: t('workbench.homePage.scenarioCaseCountPassRateTooltip'), tooltipText: t('workbench.homePage.scenarioCaseCountPassRateTooltip'),
}; };
}); });
@ -212,6 +212,7 @@
props.item.key === WorkCardEnum.API_CASE_COUNT ? unCoverWithApiCase : unCoverWithApiScenario; props.item.key === WorkCardEnum.API_CASE_COUNT ? unCoverWithApiCase : unCoverWithApiScenario;
const coverWithCase = WorkCardEnum.API_CASE_COUNT ? coverWithApiCase : coverWithApiScenario; const coverWithCase = WorkCardEnum.API_CASE_COUNT ? coverWithApiCase : coverWithApiScenario;
coverData.value = cloneDeep(initCoverRate);
coverData.value = [ coverData.value = [
{ {
value: Number(coverage.split('%')[0]), value: Number(coverage.split('%')[0]),

View File

@ -115,7 +115,7 @@
const apiCountOptions = ref({}); const apiCountOptions = ref({});
const hasPermission = ref<boolean>(false); const hasPermission = ref<boolean>(false);
function handleCoverData(detail: ApiCoverageData) { async function handleCoverData(detail: ApiCoverageData) {
const { unCoverWithApiDefinition, coverWithApiDefinition, apiCoverage } = detail; const { unCoverWithApiDefinition, coverWithApiDefinition, apiCoverage } = detail;
const coverData: { const coverData: {
name: string; name: string;
@ -139,8 +139,8 @@
hasPermission.value, hasPermission.value,
`${props.item.key}-cover` `${props.item.key}-cover`
); );
coverValueList.value = coverList; coverValueList.value = [...coverList];
coverOptions.value = covOptions; coverOptions.value = { ...covOptions };
} }
async function initApiCountRate() { async function initApiCountRate() {
try { try {
@ -213,6 +213,15 @@
} }
); );
watch(
() => hasPermission.value,
() => {
if (props.cover) {
handleCoverData(props.cover);
}
}
);
watch( watch(
() => props.status, () => props.status,
(val) => { (val) => {

View File

@ -220,7 +220,7 @@
...item, ...item,
children: item.children.filter( children: item.children.filter(
(child) => (child) =>
child.label.toLowerCase().includes(searchKeyword.value.toLowerCase()) && t(child.label).toLowerCase().includes(searchKeyword.value.toLowerCase()) &&
!innerSelectedIds.value.includes(child.value) !innerSelectedIds.value.includes(child.value)
), ),
})); }));

View File

@ -138,20 +138,20 @@
function initOptions() { function initOptions() {
const { name, color } = props.rateConfig; const { name, color } = props.rateConfig;
options.value.series.data = [...props.data.slice(1)];
options.value.legend.formatter = (seriousName: string) => {
const item = props.data.find((e) => e.name === seriousName);
return `{a|${seriousName}} {b|${addCommasToNumber(item?.value || 0)}}`;
};
if (props.hasPermission) { if (props.hasPermission) {
options.value.series.data = props.data.slice(1);
options.value.legend.formatter = (seriousName: string) => {
const item = props.data.find((e) => e.name === seriousName);
return `{a|${seriousName}} {b|${addCommasToNumber(item?.value || 0)}}`;
};
options.value.title.subtext = `${props.data[0].value ?? 0}%`; options.value.title.subtext = `${props.data[0].value ?? 0}%`;
} else { } else {
options.value.series.data = []; options.value.series.data = [];
options.value.title.subtext = `-%`; options.value.title.subtext = `-%`;
} }
options.value.graphic.invisible = !!props.hasPermission; options.value.graphic.invisible = !!props.hasPermission;
options.value.tooltip.show = !!props.hasPermission; options.value.tooltip.show = !!props.hasPermission;
options.value.title.text = name; options.value.title.text = name;
@ -168,6 +168,13 @@
} }
); );
watch(
() => props.hasPermission,
() => {
initOptions();
}
);
onMounted(() => { onMounted(() => {
initOptions(); initOptions();
}); });

View File

@ -50,10 +50,16 @@
import PassRatePie from './passRatePie.vue'; import PassRatePie from './passRatePie.vue';
import TabCard from './tabCard.vue'; import TabCard from './tabCard.vue';
import { workTestPlanRage } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage'; import type {
SelectedCardItem,
TimeFormParams,
WorkTestPlanDetail,
WorkTestPlanRageDetail,
} from '@/models/workbench/homePage';
import { handlePieData, handleUpdateTabPie } from '../utils'; import { handlePieData, handleUpdateTabPie } from '../utils';
@ -83,33 +89,6 @@
}) })
); );
// TODO
const detail = ref<PassRateDataType>({
statusStatisticsMap: {
execute: [
{ name: '覆盖率', count: 10 },
{ name: '已覆盖', count: 2 },
{ name: '未覆盖', count: 1 },
],
pass: [
{ name: '覆盖率', count: 10 },
{ name: '已覆盖', count: 2 },
{ name: '未覆盖', count: 1 },
],
complete: [
{ name: '覆盖率', count: 10 },
{ name: '已覆盖', count: 2 },
{ name: '未覆盖', count: 1 },
],
},
statusPercentList: [
{ status: 'HTTP', count: 1, percentValue: '10%' },
{ status: 'TCP', count: 3, percentValue: '0%' },
{ status: 'BBB', count: 6, percentValue: '0%' },
],
errorCode: 109001,
});
const executionOptions = ref<Record<string, any>>({}); const executionOptions = ref<Record<string, any>>({});
const passOptions = ref<Record<string, any>>({}); const passOptions = ref<Record<string, any>>({});
const completeOptions = ref<Record<string, any>>({}); const completeOptions = ref<Record<string, any>>({});
@ -146,42 +125,112 @@
}); });
const testPlanCountOptions = ref({}); const testPlanCountOptions = ref({});
//
const hasPermission = ref<boolean>(false); const hasPermission = ref<boolean>(false);
async function initTestPlanCount() { async function initTestPlanCount() {
try { try {
const { startTime, endTime, dayNumber } = timeForm.value; const { startTime, endTime, dayNumber } = timeForm.value;
const params = { const params: WorkTestPlanDetail = {
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime, startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime, endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null, dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value, projectId: innerProjectIds.value[0],
organizationId: appStore.currentOrgId,
handleUsers: [],
}; };
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value; const detail: WorkTestPlanRageDetail = await workTestPlanRage(params);
const { unExecute, executed, passed, notPassed, finished, running, prepared, archived, errorCode } = detail;
hasPermission.value = errorCode !== 109001; hasPermission.value = errorCode !== 109001;
testPlanCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
const executeRate =
executed + unExecute > 0 ? parseFloat(((executed / (executed + unExecute)) * 100).toFixed(2)) : 0;
const executeData: {
name: string;
count: number;
}[] = [
{
name: t('workbench.homePage.executeRate'),
count: executeRate,
},
{
name: t('common.unExecute'),
count: unExecute,
},
{
name: t('common.executed'),
count: executed,
},
];
const passRate = passed + notPassed > 0 ? parseFloat(((passed / (passed + notPassed)) * 100).toFixed(2)) : 0;
const passData = [
{
name: t('workbench.homePage.passRate'),
count: passRate,
},
{
name: t('workbench.homePage.havePassed'),
count: passed,
},
{
name: t('workbench.homePage.notPass'),
count: notPassed,
},
];
const statusPercentList = [
{ status: t('common.notStarted'), count: prepared, percentValue: '0%' },
{ status: t('common.inProgress'), count: running, percentValue: '0%' },
{ status: t('common.completed'), count: finished, percentValue: '0%' },
{ status: t('common.archived'), count: archived, percentValue: '0%' },
];
const total = statusPercentList.reduce((sum, item) => sum + item.count, 0);
const listStatusPercentList = statusPercentList.map((item) => ({
...item,
percentValue: total > 0 ? `${((item.count / total) * 100).toFixed(2)}%` : '0%',
}));
const completeRate = total > 0 ? parseFloat(((finished / total) * 100).toFixed(2)) : 0;
const completeData = [
{
name: t('workbench.homePage.completeRate'),
count: completeRate,
},
{
name: t('common.completed'),
count: finished,
},
{
name: t('common.inProgress'),
count: running,
},
{
name: t('common.notStarted'),
count: prepared,
},
];
testPlanCountOptions.value = handlePieData(props.item.key, hasPermission.value, listStatusPercentList);
// //
const { options: executedOptions, valueList: executedList } = handleUpdateTabPie( const { options: executedOptions, valueList: executedList } = handleUpdateTabPie(
statusStatisticsMap?.execute || [], executeData,
hasPermission.value, hasPermission.value,
`${props.item.key}-execute` `${props.item.key}-execute`
); );
// //
const { options: passedOptions, valueList: passList } = handleUpdateTabPie( const { options: passedOptions, valueList: passList } = handleUpdateTabPie(
statusStatisticsMap?.pass || [], passData,
hasPermission.value, hasPermission.value,
`${props.item.key}-pass` `${props.item.key}-pass`
); );
// //
const { options: comOptions, valueList: completeList } = handleUpdateTabPie( const { options: comOptions, valueList: completeList } = handleUpdateTabPie(
statusStatisticsMap?.complete || [], completeData,
hasPermission.value, hasPermission.value,
`${props.item.key}-complete` `${props.item.key}-complete`
); );

View File

@ -95,7 +95,7 @@
:type="item.key" :type="item.key"
:item="item" :item="item"
:status="projectLoadingStatus[item.projectIds[0]]" :status="projectLoadingStatus[item.projectIds[0]]"
:cover="requestResults.get(item.projectIds[0])" :cover="requestResults[item.projectIds[0]]"
@change="changeHandler" @change="changeHandler"
/> />
<ApiChangeList <ApiChangeList
@ -123,7 +123,7 @@
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:status="projectLoadingStatus[item.projectIds[0]]" :status="projectLoadingStatus[item.projectIds[0]]"
:item="item" :item="item"
:cover="requestResults.get(item.projectIds[0])" :cover="requestResults[item.projectIds[0]]"
@change="changeHandler" @change="changeHandler"
/> />
<TestPlanCount <TestPlanCount
@ -173,7 +173,7 @@
import { sleep } from '@/utils'; import { sleep } from '@/utils';
import { getLocalStorage, setLocalStorage } from '@/utils/local-storage'; import { getLocalStorage, setLocalStorage } from '@/utils/local-storage';
import { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage'; import { ApiCoverageData, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import { WorkCardEnum } from '@/enums/workbenchEnum';
const userStore = useUserStore(); const userStore = useUserStore();
@ -235,33 +235,9 @@
const defaultWorkList = ref<SelectedCardItem[]>([]); const defaultWorkList = ref<SelectedCardItem[]>([]);
const showNoData = ref(false); const showNoData = ref(false);
async function initDefaultList() {
try {
appStore.showLoading();
const result = await getDashboardLayout(appStore.currentOrgId);
defaultWorkList.value = result;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
if (!defaultWorkList.value.length) {
showNoData.value = true;
}
appStore.hideLoading();
}
}
async function changeHandler() {
try {
await editDashboardLayout(defaultWorkList.value, appStore.currentOrgId);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
// key ID // key ID
const requestResults = ref(new Map()); const requestResults = ref<Record<string, ApiCoverageData>>({});
// //
const requestedIds = ref(new Set()); const requestedIds = ref(new Set());
@ -271,7 +247,7 @@
try { try {
projectLoadingStatus.value[projectId] = true; projectLoadingStatus.value[projectId] = true;
const result = await workApiCountCoverRage(projectId); const result = await workApiCountCoverRage(projectId);
requestResults.value.set(projectId, result); requestResults.value[projectId] = result;
projectLoadingStatus.value[projectId] = false; projectLoadingStatus.value[projectId] = false;
await sleep(300); await sleep(300);
} catch (error) { } catch (error) {
@ -285,6 +261,8 @@
// id // id
async function requestQueue() { async function requestQueue() {
requestedIds.value = new Set([]); requestedIds.value = new Set([]);
requestResults.value = {};
const awaitType = [WorkCardEnum.API_COUNT, WorkCardEnum.API_CASE_COUNT, WorkCardEnum.SCENARIO_COUNT]; const awaitType = [WorkCardEnum.API_COUNT, WorkCardEnum.API_CASE_COUNT, WorkCardEnum.SCENARIO_COUNT];
const queueList = defaultWorkList.value.filter((item) => awaitType.includes(item.key)); const queueList = defaultWorkList.value.filter((item) => awaitType.includes(item.key));
for (let i = 0; i < queueList.length; i++) { for (let i = 0; i < queueList.length; i++) {
@ -297,13 +275,40 @@
} }
} }
// async function initDefaultList() {
async function handleRefresh() { if (appStore.currentOrgId) {
await initDefaultList(); try {
requestQueue(); appStore.showLoading();
const result = await getDashboardLayout(appStore.currentOrgId);
defaultWorkList.value = result;
requestQueue();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
if (!defaultWorkList.value.length) {
showNoData.value = true;
}
appStore.hideLoading();
}
}
} }
onMounted(async () => { async function changeHandler() {
try {
await editDashboardLayout(defaultWorkList.value, appStore.currentOrgId);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
async function handleRefresh() {
initDefaultList();
}
onMounted(() => {
const defaultTime = getLocalStorage(`WORK_TIME_${userStore.id}`); const defaultTime = getLocalStorage(`WORK_TIME_${userStore.id}`);
if (!defaultTime) { if (!defaultTime) {
setLocalStorage(`WORK_TIME_${userStore.id}`, JSON.stringify(timeForm.value)); setLocalStorage(`WORK_TIME_${userStore.id}`, JSON.stringify(timeForm.value));
@ -314,8 +319,7 @@
rangeTime.value = [startTime, endTime]; rangeTime.value = [startTime, endTime];
} }
} }
await initDefaultList(); initDefaultList();
requestQueue();
}); });
const time = ref({ ...timeForm.value }); const time = ref({ ...timeForm.value });

View File

@ -42,6 +42,7 @@ export default {
'workbench.homePage.associationCASE': 'Association CASE', 'workbench.homePage.associationCASE': 'Association CASE',
'workbench.homePage.associatedScene': 'Associated scenarios', 'workbench.homePage.associatedScene': 'Associated scenarios',
'workbench.homePage.pendingDefect': 'Defects for me to deal with', 'workbench.homePage.pendingDefect': 'Defects for me to deal with',
'workbench.homePage.defectProcessingNumber': 'Statistics of defect handlers',
'workbench.homePage.defectTotal': 'Total defect', 'workbench.homePage.defectTotal': 'Total defect',
'workbench.homePage.legacyDefectsNumber': 'Legacy defects number', 'workbench.homePage.legacyDefectsNumber': 'Legacy defects number',
'workbench.homePage.createdBugByMe': 'My created defects', 'workbench.homePage.createdBugByMe': 'My created defects',

View File

@ -42,7 +42,7 @@ export default {
'workbench.homePage.associationCASE': '关联CASE', 'workbench.homePage.associationCASE': '关联CASE',
'workbench.homePage.associatedScene': '关联场景', 'workbench.homePage.associatedScene': '关联场景',
'workbench.homePage.pendingDefect': '待我处理的缺陷', 'workbench.homePage.pendingDefect': '待我处理的缺陷',
'workbench.homePage.defectProcessingNumber': '缺陷处理人', 'workbench.homePage.defectProcessingNumber': '缺陷处理人统计',
'workbench.homePage.defectTotal': '缺陷总数', 'workbench.homePage.defectTotal': '缺陷总数',
'workbench.homePage.legacyDefectsNumber': '遗留缺陷数', 'workbench.homePage.legacyDefectsNumber': '遗留缺陷数',
'workbench.homePage.createdBugByMe': '我创建的缺陷', 'workbench.homePage.createdBugByMe': '我创建的缺陷',

View File

@ -446,6 +446,7 @@ export function handlePieData(
options.title.subtext = addCommasToNumber(totalCount); options.title.subtext = addCommasToNumber(totalCount);
if (!hasPermission) { if (!hasPermission) {
options.title.subtext = '-'; options.title.subtext = '-';
options.series.data = [];
} }
// 设置图例的格式化函数,显示百分比 // 设置图例的格式化函数,显示百分比

View File

@ -16,12 +16,12 @@
v-model:model-value="features" v-model:model-value="features"
:options="featureOptions" :options="featureOptions"
:allow-search="false" :allow-search="false"
allow-clear
class="!w-[240px]" class="!w-[240px]"
:prefix="t('project.messageManagement.function')" :prefix="t('project.messageManagement.function')"
:multiple="true" :multiple="true"
:has-all-select="true" :has-all-select="true"
:default-all-select="true" :default-all-select="true"
:at-least-one="true"
/> />
<a-button type="outline" class="arco-btn-outline--secondary p-[10px]" @click="() => handleRefresh()"> <a-button type="outline" class="arco-btn-outline--secondary p-[10px]" @click="() => handleRefresh()">
<MsIcon type="icon-icon_reset_outlined" size="14" /> <MsIcon type="icon-icon_reset_outlined" size="14" />