feat(工作台): 联调工作台测试计划一揽子率
This commit is contained in:
parent
d4191a07d8
commit
954ecca484
|
@ -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<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) {
|
||||
|
|
|
@ -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'; // 工作台-首页-测试计划数
|
||||
|
|
|
@ -180,7 +180,7 @@ export const defaultValueMap: Record<string, any> = {
|
|||
},
|
||||
pass: {
|
||||
defaultList: cloneDeep(defaultPass),
|
||||
color: ['#D4D4D8', '#00C261'],
|
||||
color: ['#00C261', '#D4D4D8'],
|
||||
defaultName: 'workbench.homePage.passRate',
|
||||
},
|
||||
complete: {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
|
||||
const apiCountOptions = ref({});
|
||||
const hasPermission = ref<boolean>(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) => {
|
||||
|
|
|
@ -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)
|
||||
),
|
||||
}));
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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<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 passOptions = ref<Record<string, any>>({});
|
||||
const completeOptions = ref<Record<string, any>>({});
|
||||
|
@ -146,42 +125,112 @@
|
|||
});
|
||||
|
||||
const testPlanCountOptions = ref({});
|
||||
// 测试计划权限
|
||||
const hasPermission = ref<boolean>(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`
|
||||
);
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
<ApiChangeList
|
||||
|
@ -123,7 +123,7 @@
|
|||
v-model:projectIds="item.projectIds"
|
||||
:status="projectLoadingStatus[item.projectIds[0]]"
|
||||
:item="item"
|
||||
:cover="requestResults.get(item.projectIds[0])"
|
||||
:cover="requestResults[item.projectIds[0]]"
|
||||
@change="changeHandler"
|
||||
/>
|
||||
<TestPlanCount
|
||||
|
@ -173,7 +173,7 @@
|
|||
import { sleep } from '@/utils';
|
||||
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';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
@ -235,33 +235,9 @@
|
|||
const defaultWorkList = ref<SelectedCardItem[]>([]);
|
||||
|
||||
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<Record<string, ApiCoverageData>>({});
|
||||
// 用来存储已请求过的项目
|
||||
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 });
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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': '我创建的缺陷',
|
||||
|
|
|
@ -446,6 +446,7 @@ export function handlePieData(
|
|||
options.title.subtext = addCommasToNumber(totalCount);
|
||||
if (!hasPermission) {
|
||||
options.title.subtext = '-';
|
||||
options.series.data = [];
|
||||
}
|
||||
|
||||
// 设置图例的格式化函数,显示百分比
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[10px]" @click="() => handleRefresh()">
|
||||
<MsIcon type="icon-icon_reset_outlined" size="14" />
|
||||
|
|
Loading…
Reference in New Issue