diff --git a/frontend/src/api/modules/test-plan/report.ts b/frontend/src/api/modules/test-plan/report.ts index a57ea67942..df13c52416 100644 --- a/frontend/src/api/modules/test-plan/report.ts +++ b/frontend/src/api/modules/test-plan/report.ts @@ -39,4 +39,9 @@ export function updateReportDetail(data: UpdateReportDetailParams) { return MSR.post({ url: reportUrl.UpdateReportDetailUrl, data }); } +// 测试计划-报告-详情 +export function getReportDetail(id: string) { + return MSR.get({ url: `${reportUrl.PlanReportDetailUrl}/${id}` }); +} + export default {}; diff --git a/frontend/src/api/requrls/test-plan/report.ts b/frontend/src/api/requrls/test-plan/report.ts index 8ae8126098..5ed230342a 100644 --- a/frontend/src/api/requrls/test-plan/report.ts +++ b/frontend/src/api/requrls/test-plan/report.ts @@ -6,6 +6,8 @@ export const PlanReportRenameUrl = '/test-plan/report/rename'; export const PlanDeleteUrl = '/test-plan/report/delete'; // 批量删除报告 export const PlanBatchDeleteUrl = '/test-plan/report/batch-delete'; +// 测试计划-报告-详情 +export const PlanReportDetailUrl = '/test-plan/report/get'; // 测试计划-报告-详情-缺陷分页查询 export const ReportBugListUrl = '/test-plan/report/detail/bug/page'; // 测试计划-报告-详情-功能用例分页查询 diff --git a/frontend/src/assets/svg/bugTotal.svg b/frontend/src/assets/svg/bugTotal.svg new file mode 100644 index 0000000000..435ba36517 --- /dev/null +++ b/frontend/src/assets/svg/bugTotal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/passRate.svg b/frontend/src/assets/svg/passRate.svg new file mode 100644 index 0000000000..c26947a319 --- /dev/null +++ b/frontend/src/assets/svg/passRate.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/threshold.svg b/frontend/src/assets/svg/threshold.svg new file mode 100644 index 0000000000..31da7f7204 --- /dev/null +++ b/frontend/src/assets/svg/threshold.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/components/business/ms-case-associate/executeResult.vue b/frontend/src/components/business/ms-case-associate/executeResult.vue index 59608492a6..923c648fd3 100644 --- a/frontend/src/components/business/ms-case-associate/executeResult.vue +++ b/frontend/src/components/business/ms-case-associate/executeResult.vue @@ -40,7 +40,7 @@ label: 'BLOCKED', icon: StatusType.BLOCKED, statusText: t('caseManagement.featureCase.chokeUp'), - color: 'rgb(var(--warning-6))', + color: 'rgb(var(--primary-3))', }, ERROR: { label: 'ERROR', diff --git a/frontend/src/config/testPlan.ts b/frontend/src/config/testPlan.ts index 337d606623..b8b6b9ef07 100644 --- a/frontend/src/config/testPlan.ts +++ b/frontend/src/config/testPlan.ts @@ -1,6 +1,6 @@ import type { PassRateCountDetail, planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan'; +import type { PlanReportDetail, StatusListType } from '@/models/testPlan/testPlanReport'; import { LastExecuteResults } from '@/enums/caseEnum'; - // TODO: 对照后端字段 // 测试计划详情 export const testPlanDefaultDetail: TestPlanDetail = { @@ -46,5 +46,77 @@ export const defaultExecuteForm = { planCommentFileIds: [], notifier: [] as string[], }; +// 报告详情 +export const defaultReportDetail: PlanReportDetail = { + id: '', + name: '', + startTime: 0, + executeTime: 0, // 报告执行开始时间 + endTime: 0, + summary: '', + passThreshold: 0, // 通过阈值 + passRate: 0, // 通过率 + executeRate: 0, // 执行完成率 + bugCount: 0, + caseTotal: 0, + executeCount: { + success: 0, + error: 0, + fakeError: 0, + block: 0, + pending: 0, + }, + functionalCount: { + success: 0, + error: 0, + fakeError: 0, + block: 0, + pending: 0, + }, +}; + +export const statusConfig: StatusListType[] = [ + { + label: 'common.success', + value: 'success', + color: '#00C261', + class: 'bg-[rgb(var(--success-6))]', + rateKey: 'requestPassRate', + key: 'SUCCESS', + }, + // TODO 这个版本不展示误报 + // { + // label: 'common.fakeError', + // value: 'fakeError', + // color: '#FFC14E', + // class: 'bg-[rgb(var(--warning-6))]', + // rateKey: 'requestFakeErrorRate', + // key: 'FAKE_ERROR', + // }, + { + label: 'common.fail', + value: 'error', + color: '#ED0303', + class: 'bg-[rgb(var(--danger-6))]', + rateKey: 'requestErrorRate', + key: 'ERROR', + }, + { + label: 'common.unExecute', + value: 'pending', + color: '#D4D4D8', + class: 'bg-[var(--color-text-input-border)]', + rateKey: 'requestPendingRate', + key: 'PENDING', + }, + { + label: 'common.block', + value: 'block', + color: '#B379C8', + class: 'bg-[rgb(var(--primary-3))]', + rateKey: 'requestPendingRate', + key: 'BLOCK', + }, +]; export default {}; diff --git a/frontend/src/models/testPlan/testPlanReport.ts b/frontend/src/models/testPlan/testPlanReport.ts new file mode 100644 index 0000000000..d9be327840 --- /dev/null +++ b/frontend/src/models/testPlan/testPlanReport.ts @@ -0,0 +1,34 @@ +export interface countDetail { + success: number; + error: number; + fakeError: number; + block: number; + pending: number; +} +export interface PlanReportDetail { + id: string; + name: string; + startTime: number; + executeTime: number; // 报告执行开始时间 + endTime: number; + summary: string; + passThreshold: number; // 通过阈值 + passRate: number; // 通过率 + executeRate: number; // 执行完成率 + bugCount: number; + caseTotal: number; + executeCount: countDetail; + functionalCount: countDetail; + // TOTO 这个版本不展示场景和接口 + // apiCaseCount: countDetail; // 接口场景用例分析-用例数 + // apiScenarioCount: countDetail; // 接口场景用例分析-用例数 +} + +export interface StatusListType { + label: string; + value: keyof countDetail; + color: string; + class: string; + rateKey: string; + key: string; +} diff --git a/frontend/src/views/api-test/report/component/case/IndepReportChart.vue b/frontend/src/views/api-test/report/component/case/IndepReportChart.vue deleted file mode 100644 index 0f1eb67608..0000000000 --- a/frontend/src/views/api-test/report/component/case/IndepReportChart.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - - - {{ t('report.detail.api.total') }} - 1 - - - - - - - - - {{ item.label }} - - {{ item.count || 0 }} - {{ item.rote || 0 }}% - - - - - - - - diff --git a/frontend/src/views/api-test/report/component/case/setReportChart.vue b/frontend/src/views/api-test/report/component/case/setReportChart.vue index a628559df3..5d150d7975 100644 --- a/frontend/src/views/api-test/report/component/case/setReportChart.vue +++ b/frontend/src/views/api-test/report/component/case/setReportChart.vue @@ -1,7 +1,7 @@ - + - + {{ t('report.detail.api.total') }} @@ -18,7 +18,7 @@ - + @@ -40,8 +40,10 @@ {{ item.label }} - {{ item.count || 0 }} - {{ item.rote || 0 }} % + {{ item.count || 0 }} + {{ item.rote || 0 }} @@ -56,7 +58,7 @@ import MsChart from '@/components/pure/chart/index.vue'; import { useI18n } from '@/hooks/useI18n'; - import { addCommasToNumber, formatDuration } from '@/utils'; + import { addCommasToNumber } from '@/utils'; import type { LegendData } from '@/models/apiTest/report'; @@ -67,7 +69,11 @@ options: Record; legendData: LegendData[]; requestTotal: number; + size?: string; + offset?: string; }>(); + + const defaultOffset = ref('top-[30%] right-0 bottom-0 left-0'); diff --git a/frontend/src/views/test-plan/report/detail/index.vue b/frontend/src/views/test-plan/report/detail/index.vue index 3d85b701b9..f40d848b36 100644 --- a/frontend/src/views/test-plan/report/detail/index.vue +++ b/frontend/src/views/test-plan/report/detail/index.vue @@ -1,5 +1,136 @@ - + + + {{ t('report.name') }} + {{ detail.name }} + + + + + + + {{ t('report.detail.api.executionTime') }} + {{ detail.executeTime ? dayjs(detail.executeTime).format('YYYY-MM-DD HH:mm:ss') : '-' }} + {{ t('report.detail.api.executionTimeTo') }} + {{ detail.endTime ? dayjs(detail.endTime).format('YYYY-MM-DD HH:mm:ss') : '-' }} + + + + {{ t('report.detail.api.executionTime') }} + + {{ dayjs(detail.executeTime).format('YYYY-MM-DD HH:mm:ss') }} + + + + + + + {{ t('common.share') }} + + + + + + + {{ t('report.detail.api.requestAnalysis') }} + + + + + {{ t('report.detail.threshold') }} + + + {{ detail.passThreshold }} + (%) + + + + + + {{ t('report.detail.reportPassRate') }} + + + {{ detail.passRate }} + (%) + + + + + + {{ t('report.detail.performCompletion') }} + + + {{ detail.executeRate }} + (%) + + + + + + {{ t('report.detail.totalDefects') }} + + + {{ addCommasToNumber(detail.bugCount) }} + ({{ t('report.detail.number') }}) + + + + + + + + {{ t('report.detail.executionAnalysis') }} + + + + + + {{ t('report.detail.useCaseAnalysis') }} + + + + + + + + + + {{ t('report.passRate') }} + + + {{ detail.passRate }}% + + + + {{ t('report.passRate') }} + {{ detail.passRate }} % + + + + + + + + + + + {{ t('report.detail.reportSummary') }} import { ref } from 'vue'; import { useRoute } from 'vue-router'; + import { cloneDeep } from 'lodash-es'; + import dayjs from 'dayjs'; + import MsChart from '@/components/pure/chart/index.vue'; + import MsButton from '@/components/pure/ms-button/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue'; import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue'; import MsTab from '@/components/pure/ms-tab/index.vue'; + import SingleStatusProgress from '../component/singleStatusProgress.vue'; import BugTable from './component/bugTable.vue'; import FeatureCaseTable from './component/featureCaseTable.vue'; + import SetReportChart from '@/views/api-test/report/component/case/setReportChart.vue'; import { editorUploadFile } from '@/api/modules/case-management/featureCase'; + import { getReportDetail } from '@/api/modules/test-plan/report'; import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase'; + import { defaultReportDetail, statusConfig } from '@/config/testPlan'; import { useI18n } from '@/hooks/useI18n'; + import { addCommasToNumber } from '@/utils'; + + import type { LegendData } from '@/models/apiTest/report'; + import type { PlanReportDetail, StatusListType } from '@/models/testPlan/testPlanReport'; + + import { getIndicators } from '@/views/api-test/report/utils'; const { t } = useI18n(); + const route = useRoute(); - const reportId = ref(route.query.id as string); + const reportId = ref(route.query.id as string); + + const detail = ref({ ...cloneDeep(defaultReportDetail) }); + + // 分享 + const shareLoading = ref(false); + + function shareHandler() {} + + const legendData = ref([]); + + const charOptions = ref({ + tooltip: { + show: false, + trigger: 'item', + }, + legend: { + show: false, + }, + series: { + name: '', + type: 'pie', + radius: ['65%', '80%'], + avoidLabelOverlap: false, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: false, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: [ + { + value: 0, + name: t('common.success'), + itemStyle: { + color: '#00C261', + }, + }, + { + value: 0, + name: t('common.fakeError'), + itemStyle: { + color: '#FFC14E', + }, + }, + { + value: 0, + name: t('common.fail'), + itemStyle: { + color: '#ED0303', + }, + }, + { + value: 0, + name: t('common.unExecute'), + itemStyle: { + color: '#D4D4D8', + }, + }, + { + value: 0, + name: t('common.block'), + itemStyle: { + color: '#B379C8', + }, + }, + ], + }, + }); + + const functionCaseOptions = ref({ + tooltip: { + show: false, + trigger: 'item', + }, + legend: { + show: false, + }, + series: { + name: '', + type: 'pie', + radius: ['65%', '80%'], + avoidLabelOverlap: false, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: false, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: [ + { + value: 0, + name: t('common.success'), + itemStyle: { + color: '#00C261', + }, + }, + ], + }, + }); + + // 初始化图表 + function initOptionsData() { + charOptions.value.series.data = statusConfig.map((item: StatusListType) => { + return { + value: detail.value.executeCount[item.value] || 0, + name: t(item.label), + itemStyle: { + color: item.color, + }, + }; + }); + legendData.value = statusConfig.map((item: StatusListType) => { + const rate = (detail.value.executeCount[item.value] / detail.value.caseTotal) * 100; + return { + ...item, + label: t(item.label), + count: detail.value.executeCount[item.value] || 0, + rote: `${Number.isNaN(rate) ? 0 : rate.toFixed(2)}%`, + }; + }) as unknown as LegendData[]; + + functionCaseOptions.value.series.data = statusConfig.map((item: StatusListType) => { + return { + value: detail.value.functionalCount[item.value] || 0, + name: t(item.label), + itemStyle: { + color: item.color, + }, + }; + }); + } + + async function getDetail() { + try { + detail.value = await getReportDetail(reportId.value); + } catch (error) { + console.log(error); + } + } + + onMounted(async () => { + await getDetail(); + initOptionsData(); + }); + const activeTab = ref('bug'); const contentTabList = ref([ { @@ -76,4 +383,43 @@ } - + diff --git a/frontend/src/views/test-plan/report/locale/en-US.ts b/frontend/src/views/test-plan/report/locale/en-US.ts index 387c864bbb..4d1d2cb24b 100644 --- a/frontend/src/views/test-plan/report/locale/en-US.ts +++ b/frontend/src/views/test-plan/report/locale/en-US.ts @@ -36,4 +36,11 @@ export default { 'report.detail.reportSummary': 'Report summary', 'report.detail.bugDetails': 'Bug details', 'report.detail.featureCaseDetails': 'Feature case details', + 'report.detail.executionAnalysis': 'Execution Analysis', + 'report.detail.threshold': 'Pass threshold', + 'report.detail.reportPassRate': 'The report pass', + 'report.detail.performCompletion': 'Perform completion', + 'report.detail.totalDefects': 'Total defects', + 'report.detail.useCaseAnalysis': 'Function of use case analysis', + 'report.detail.number': 'number', }; diff --git a/frontend/src/views/test-plan/report/locale/zh-CN.ts b/frontend/src/views/test-plan/report/locale/zh-CN.ts index 2cfd7c4e19..bfd5baaf99 100644 --- a/frontend/src/views/test-plan/report/locale/zh-CN.ts +++ b/frontend/src/views/test-plan/report/locale/zh-CN.ts @@ -36,4 +36,11 @@ export default { 'report.detail.reportSummary': '报告总结', 'report.detail.bugDetails': '缺陷明细', 'report.detail.featureCaseDetails': '功能用例明细', + 'report.detail.executionAnalysis': '执行分析', + 'report.detail.threshold': '通过阈值', + 'report.detail.reportPassRate': '报告通过率', + 'report.detail.performCompletion': '执行完成率', + 'report.detail.totalDefects': '缺陷总数', + 'report.detail.useCaseAnalysis': '功能用例分析', + 'report.detail.number': '个', }; diff --git a/frontend/src/views/test-plan/testPlan/components/statusProgress.vue b/frontend/src/views/test-plan/testPlan/components/statusProgress.vue index 8cce16cf9a..54ac21eecd 100644 --- a/frontend/src/views/test-plan/testPlan/components/statusProgress.vue +++ b/frontend/src/views/test-plan/testPlan/components/statusProgress.vue @@ -32,7 +32,8 @@ {{ detailCount.errorCount }} - + + - + {{ t('common.block') }} diff --git a/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue b/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue index 19c6854544..d98734dc07 100644 --- a/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue +++ b/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue @@ -14,7 +14,7 @@ {{ t('common.success') }} - + {{ t('common.block') }} diff --git a/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/index.vue b/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/index.vue index b7ea1b05fd..c49fb6adf5 100644 --- a/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/index.vue +++ b/frontend/src/views/test-plan/testPlan/detail/featureCase/detail/index.vue @@ -397,12 +397,6 @@ activeId.value = item.id; } } - watch( - () => activeId.value, - () => { - loadCaseDetail(); - } - ); async function loadCase() { await loadCaseList(); @@ -481,7 +475,7 @@ async function getBugTotal() { try { const params = { - testPlanCaseId: route.query.testPlanCaseId, + testPlanCaseId: activeId.value, caseId: activeCaseId.value, projectId: appStore.currentProjectId, current: 1, @@ -562,6 +556,14 @@ initBugList(); await loadCase(); }); + watch( + () => activeId.value, + () => { + loadCaseDetail(); + initBugList(); + getBugTotal(); + } + ); watch( () => activeTab.value,