From 954ecca4845c217a9c3cf2e1c22d297b5d4e396d Mon Sep 17 00:00:00 2001 From: "xinxin.wu" Date: Mon, 18 Nov 2024 18:45:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=B7=A5=E4=BD=9C=E5=8F=B0):=20=E8=81=94?= =?UTF-8?q?=E8=B0=83=E5=B7=A5=E4=BD=9C=E5=8F=B0=E6=B5=8B=E8=AF=95=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E4=B8=80=E6=8F=BD=E5=AD=90=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/modules/workbench.ts | 7 + frontend/src/api/requrls/workbench.ts | 1 + frontend/src/config/workbench.ts | 2 +- frontend/src/models/workbench/homePage.ts | 18 +++ .../components/apiAndScenarioCase.vue | 7 +- .../homePage/components/apiCount.vue | 15 +- .../homePage/components/cardSettingList.vue | 2 +- .../homePage/components/ratioPie.vue | 21 ++- .../homePage/components/testPlanCount.vue | 129 ++++++++++++------ .../src/views/workbench/homePage/index.vue | 76 ++++++----- .../views/workbench/homePage/locale/en-US.ts | 1 + .../views/workbench/homePage/locale/zh-CN.ts | 2 +- .../src/views/workbench/homePage/utils.ts | 1 + frontend/src/views/workbench/myToDo/index.vue | 2 +- 14 files changed, 191 insertions(+), 93 deletions(-) diff --git a/frontend/src/api/modules/workbench.ts b/frontend/src/api/modules/workbench.ts index 02a63f42b1..747042dc7b 100644 --- a/frontend/src/api/modules/workbench.ts +++ b/frontend/src/api/modules/workbench.ts @@ -13,6 +13,8 @@ import type { PassRateDataType, SelectedCardItem, WorkHomePageDetail, + WorkTestPlanDetail, + WorkTestPlanRageDetail, } from '@/models/workbench/homePage'; import { @@ -45,6 +47,7 @@ import { WorkProOverviewDetailUrl, WorkReviewListUrl, WorkScenarioCaseCountDetailUrl, + WorkTestPlanRageUrl, WorkTodoBugListUrl, WorkTodoPlanListUrl, WorkTodoReviewListUrl, @@ -196,6 +199,10 @@ export function workPlanLegacyBug(data: WorkHomePageDetail) { export function workApiCountCoverRage(projectId: string) { return MSR.get({ url: WorkApiCountCoverRateUrl, params: projectId }, { ignoreCancelToken: true }); } +// 工作台-首页-测试计划数量 +export function workTestPlanRage(data: WorkTestPlanDetail) { + return MSR.post({ url: WorkTestPlanRageUrl, data }, { ignoreCancelToken: true }); +} // 待办-用例评审列表 export function workbenchTodoReviewList(data: TableQueryParams) { diff --git a/frontend/src/api/requrls/workbench.ts b/frontend/src/api/requrls/workbench.ts index 508b3646f1..f1cfc39997 100644 --- a/frontend/src/api/requrls/workbench.ts +++ b/frontend/src/api/requrls/workbench.ts @@ -30,3 +30,4 @@ export const WorkBugByMeCreatedUrl = '/dashboard/create_bug_by_me'; // 工作台 export const WorkBugHandleByMeUrl = '/dashboard/handle_bug_by_me'; // 工作台-首页-待我处理的缺陷 export const WorkPlanLegacyBugUrl = '/dashboard/plan_legacy_bug'; // 工作台-首页-测试计划遗留缺陷 export const WorkApiCountCoverRateUrl = '/api/definition/rage'; // 工作台-首页-覆盖率 +export const WorkTestPlanRageUrl = '/test-plan/rage'; // 工作台-首页-测试计划数 diff --git a/frontend/src/config/workbench.ts b/frontend/src/config/workbench.ts index a80707874e..f9662b8f56 100644 --- a/frontend/src/config/workbench.ts +++ b/frontend/src/config/workbench.ts @@ -180,7 +180,7 @@ export const defaultValueMap: Record = { }, pass: { defaultList: cloneDeep(defaultPass), - color: ['#D4D4D8', '#00C261'], + color: ['#00C261', '#D4D4D8'], defaultName: 'workbench.homePage.passRate', }, complete: { diff --git a/frontend/src/models/workbench/homePage.ts b/frontend/src/models/workbench/homePage.ts index 4a45abee8a..f635756e3a 100644 --- a/frontend/src/models/workbench/homePage.ts +++ b/frontend/src/models/workbench/homePage.ts @@ -39,6 +39,24 @@ export interface WorkHomePageDetail extends TableQueryParams { 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 { dayNumber: number | string; startTime: number; diff --git a/frontend/src/views/workbench/homePage/components/apiAndScenarioCase.vue b/frontend/src/views/workbench/homePage/components/apiAndScenarioCase.vue index 7baf8d559c..9adbec290c 100644 --- a/frontend/src/views/workbench/homePage/components/apiAndScenarioCase.vue +++ b/frontend/src/views/workbench/homePage/components/apiAndScenarioCase.vue @@ -176,7 +176,7 @@ return props.item.key === WorkCardEnum.API_CASE_COUNT ? { name: t('workbench.homePage.caseExecutionRate'), - color: ['#00C261', '#EDEDF1'], + color: ['#EDEDF1', '#00C261'], tooltipText: t('workbench.homePage.apiCaseCountExecuteRateTooltip'), } : { @@ -190,12 +190,12 @@ return props.item.key === WorkCardEnum.API_CASE_COUNT ? { name: t('workbench.homePage.casePassedRate'), - color: ['#00C261', '#ED0303'], + color: ['#ED0303', '#00C261'], tooltipText: t('workbench.homePage.apiCaseCountPassRateTooltip'), } : { name: t('workbench.homePage.executionRate'), - color: ['#00C261', '#ED0303'], + color: ['#ED0303', '#00C261'], tooltipText: t('workbench.homePage.scenarioCaseCountPassRateTooltip'), }; }); @@ -212,6 +212,7 @@ props.item.key === WorkCardEnum.API_CASE_COUNT ? unCoverWithApiCase : unCoverWithApiScenario; const coverWithCase = WorkCardEnum.API_CASE_COUNT ? coverWithApiCase : coverWithApiScenario; + coverData.value = cloneDeep(initCoverRate); coverData.value = [ { value: Number(coverage.split('%')[0]), diff --git a/frontend/src/views/workbench/homePage/components/apiCount.vue b/frontend/src/views/workbench/homePage/components/apiCount.vue index 8a30eb572f..9c71fa5754 100644 --- a/frontend/src/views/workbench/homePage/components/apiCount.vue +++ b/frontend/src/views/workbench/homePage/components/apiCount.vue @@ -115,7 +115,7 @@ const apiCountOptions = ref({}); const hasPermission = ref(false); - function handleCoverData(detail: ApiCoverageData) { + async function handleCoverData(detail: ApiCoverageData) { const { unCoverWithApiDefinition, coverWithApiDefinition, apiCoverage } = detail; const coverData: { name: string; @@ -139,8 +139,8 @@ hasPermission.value, `${props.item.key}-cover` ); - coverValueList.value = coverList; - coverOptions.value = covOptions; + coverValueList.value = [...coverList]; + coverOptions.value = { ...covOptions }; } async function initApiCountRate() { try { @@ -213,6 +213,15 @@ } ); + watch( + () => hasPermission.value, + () => { + if (props.cover) { + handleCoverData(props.cover); + } + } + ); + watch( () => props.status, (val) => { diff --git a/frontend/src/views/workbench/homePage/components/cardSettingList.vue b/frontend/src/views/workbench/homePage/components/cardSettingList.vue index fcd5421550..e55ebe50f1 100644 --- a/frontend/src/views/workbench/homePage/components/cardSettingList.vue +++ b/frontend/src/views/workbench/homePage/components/cardSettingList.vue @@ -220,7 +220,7 @@ ...item, children: item.children.filter( (child) => - child.label.toLowerCase().includes(searchKeyword.value.toLowerCase()) && + t(child.label).toLowerCase().includes(searchKeyword.value.toLowerCase()) && !innerSelectedIds.value.includes(child.value) ), })); diff --git a/frontend/src/views/workbench/homePage/components/ratioPie.vue b/frontend/src/views/workbench/homePage/components/ratioPie.vue index d85c288a50..c622336f59 100644 --- a/frontend/src/views/workbench/homePage/components/ratioPie.vue +++ b/frontend/src/views/workbench/homePage/components/ratioPie.vue @@ -138,20 +138,20 @@ function initOptions() { 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) { - 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}%`; } else { options.value.series.data = []; options.value.title.subtext = `-%`; } + options.value.graphic.invisible = !!props.hasPermission; options.value.tooltip.show = !!props.hasPermission; options.value.title.text = name; @@ -168,6 +168,13 @@ } ); + watch( + () => props.hasPermission, + () => { + initOptions(); + } + ); + onMounted(() => { initOptions(); }); diff --git a/frontend/src/views/workbench/homePage/components/testPlanCount.vue b/frontend/src/views/workbench/homePage/components/testPlanCount.vue index c4b2b35a29..1786d94d92 100644 --- a/frontend/src/views/workbench/homePage/components/testPlanCount.vue +++ b/frontend/src/views/workbench/homePage/components/testPlanCount.vue @@ -50,10 +50,16 @@ import PassRatePie from './passRatePie.vue'; import TabCard from './tabCard.vue'; + import { workTestPlanRage } from '@/api/modules/workbench'; import { useI18n } from '@/hooks/useI18n'; 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'; @@ -83,33 +89,6 @@ }) ); - // TODO 假数据后边几个要用 - const detail = ref({ - 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>({}); const passOptions = ref>({}); const completeOptions = ref>({}); @@ -146,42 +125,112 @@ }); const testPlanCountOptions = ref({}); + // 测试计划权限 const hasPermission = ref(false); async function initTestPlanCount() { try { const { startTime, endTime, dayNumber } = timeForm.value; - const params = { - current: 1, - pageSize: 5, + const params: WorkTestPlanDetail = { startTime: dayNumber ? null : startTime, endTime: dayNumber ? null : endTime, dayNumber: dayNumber ?? null, - projectIds: innerProjectIds.value, - organizationId: appStore.currentOrgId, - handleUsers: [], + projectId: innerProjectIds.value[0], }; - 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; - 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( - statusStatisticsMap?.execute || [], + executeData, hasPermission.value, `${props.item.key}-execute` ); // 通过率 const { options: passedOptions, valueList: passList } = handleUpdateTabPie( - statusStatisticsMap?.pass || [], + passData, hasPermission.value, `${props.item.key}-pass` ); // 完成率 const { options: comOptions, valueList: completeList } = handleUpdateTabPie( - statusStatisticsMap?.complete || [], + completeData, hasPermission.value, `${props.item.key}-complete` ); diff --git a/frontend/src/views/workbench/homePage/index.vue b/frontend/src/views/workbench/homePage/index.vue index ffcd3f0966..a87d4bbb47 100644 --- a/frontend/src/views/workbench/homePage/index.vue +++ b/frontend/src/views/workbench/homePage/index.vue @@ -95,7 +95,7 @@ :type="item.key" :item="item" :status="projectLoadingStatus[item.projectIds[0]]" - :cover="requestResults.get(item.projectIds[0])" + :cover="requestResults[item.projectIds[0]]" @change="changeHandler" /> ([]); 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 - const requestResults = ref(new Map()); + const requestResults = ref>({}); // 用来存储已请求过的项目 const requestedIds = ref(new Set()); @@ -271,7 +247,7 @@ try { projectLoadingStatus.value[projectId] = true; const result = await workApiCountCoverRage(projectId); - requestResults.value.set(projectId, result); + requestResults.value[projectId] = result; projectLoadingStatus.value[projectId] = false; await sleep(300); } catch (error) { @@ -285,6 +261,8 @@ // 针对项目id不重复的依次请求 async function requestQueue() { requestedIds.value = new Set([]); + requestResults.value = {}; + const awaitType = [WorkCardEnum.API_COUNT, WorkCardEnum.API_CASE_COUNT, WorkCardEnum.SCENARIO_COUNT]; const queueList = defaultWorkList.value.filter((item) => awaitType.includes(item.key)); for (let i = 0; i < queueList.length; i++) { @@ -297,13 +275,40 @@ } } - // 刷新 - async function handleRefresh() { - await initDefaultList(); - requestQueue(); + async function initDefaultList() { + if (appStore.currentOrgId) { + try { + 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}`); if (!defaultTime) { setLocalStorage(`WORK_TIME_${userStore.id}`, JSON.stringify(timeForm.value)); @@ -314,8 +319,7 @@ rangeTime.value = [startTime, endTime]; } } - await initDefaultList(); - requestQueue(); + initDefaultList(); }); const time = ref({ ...timeForm.value }); diff --git a/frontend/src/views/workbench/homePage/locale/en-US.ts b/frontend/src/views/workbench/homePage/locale/en-US.ts index bc16c4f31f..8904d99329 100644 --- a/frontend/src/views/workbench/homePage/locale/en-US.ts +++ b/frontend/src/views/workbench/homePage/locale/en-US.ts @@ -42,6 +42,7 @@ export default { 'workbench.homePage.associationCASE': 'Association CASE', 'workbench.homePage.associatedScene': 'Associated scenarios', 'workbench.homePage.pendingDefect': 'Defects for me to deal with', + 'workbench.homePage.defectProcessingNumber': 'Statistics of defect handlers', 'workbench.homePage.defectTotal': 'Total defect', 'workbench.homePage.legacyDefectsNumber': 'Legacy defects number', 'workbench.homePage.createdBugByMe': 'My created defects', diff --git a/frontend/src/views/workbench/homePage/locale/zh-CN.ts b/frontend/src/views/workbench/homePage/locale/zh-CN.ts index fa23fcf173..95a769692e 100644 --- a/frontend/src/views/workbench/homePage/locale/zh-CN.ts +++ b/frontend/src/views/workbench/homePage/locale/zh-CN.ts @@ -42,7 +42,7 @@ export default { 'workbench.homePage.associationCASE': '关联CASE', 'workbench.homePage.associatedScene': '关联场景', 'workbench.homePage.pendingDefect': '待我处理的缺陷', - 'workbench.homePage.defectProcessingNumber': '缺陷处理人数', + 'workbench.homePage.defectProcessingNumber': '缺陷处理人统计', 'workbench.homePage.defectTotal': '缺陷总数', 'workbench.homePage.legacyDefectsNumber': '遗留缺陷数', 'workbench.homePage.createdBugByMe': '我创建的缺陷', diff --git a/frontend/src/views/workbench/homePage/utils.ts b/frontend/src/views/workbench/homePage/utils.ts index c45885e560..7d909a9df1 100644 --- a/frontend/src/views/workbench/homePage/utils.ts +++ b/frontend/src/views/workbench/homePage/utils.ts @@ -446,6 +446,7 @@ export function handlePieData( options.title.subtext = addCommasToNumber(totalCount); if (!hasPermission) { options.title.subtext = '-'; + options.series.data = []; } // 设置图例的格式化函数,显示百分比 diff --git a/frontend/src/views/workbench/myToDo/index.vue b/frontend/src/views/workbench/myToDo/index.vue index e7c4000ba9..5d197b0705 100644 --- a/frontend/src/views/workbench/myToDo/index.vue +++ b/frontend/src/views/workbench/myToDo/index.vue @@ -16,12 +16,12 @@ v-model:model-value="features" :options="featureOptions" :allow-search="false" - allow-clear class="!w-[240px]" :prefix="t('project.messageManagement.function')" :multiple="true" :has-all-select="true" :default-all-select="true" + :at-least-one="true" />