feat(任务中心): 任务中心接口联调

This commit is contained in:
baiqi 2024-10-22 15:05:18 +08:00 committed by Craftsman
parent c1a59f4cc0
commit e7ea9f975e
14 changed files with 466 additions and 235 deletions

View File

@ -1,7 +1,7 @@
import MSR from '@/api/http';
import * as reportUrl from '@/api/requrls/api-test/report';
import type { GetShareId, ReportDetail, ReportStepDetail } from '@/models/apiTest/report';
import type { GetShareId, ReportDetail, ReportStepDetail, ReportStepDetailItem } from '@/models/apiTest/report';
import type { TableQueryParams } from '@/models/common';
import { ReportEnum } from '@/enums/reportEnum';
@ -111,4 +111,17 @@ export function downloadFile(data: string | undefined) {
return MSR.post({ url: reportUrl.DownloadFileUrl, data, responseType: 'blob' }, { isTransformResponse: false });
}
export default {};
// 获取用例任务报告
export function getCaseTaskReport(taskId: string) {
return MSR.get<ReportStepDetailItem[]>({ url: `${reportUrl.caseTaskReportUrl}/${taskId}` });
}
// 获取场景任务报告
export function getScenarioTaskReport(taskId: string) {
return MSR.get<ReportDetail>({ url: `${reportUrl.scenarioTaskReportUrl}/${taskId}` });
}
// 获取场景任务报告-步骤
export function getScenarioTaskReportStep(taskId: string, stepId: string) {
return MSR.get<ReportStepDetailItem>({ url: `${reportUrl.scenarioTaskReportStepUrl}/${taskId}/${stepId}` });
}

View File

@ -47,3 +47,12 @@ export const getShareReportInfoUrl = '/api/report/share/get';
export const getShareTimeUrl = '/api/report/share/get-share-time';
// 下载文件地址
export const DownloadFileUrl = '/api/test/download';
// 用例任务报告
export const caseTaskReportUrl = '/api/report/case/task-report';
// 场景任务报告
export const scenarioTaskReportUrl = '/api/report/scenario/task-step';
// 场景任务报告-步骤
export const scenarioTaskReportStepUrl = '/api/report/scenario/task-report';

View File

@ -9,7 +9,7 @@
multiple
allow-clear
check-strictly
:max-tag-count="maxTagCount"
:max-tag-count="props.shouldCalculateMaxTag ? maxTagCount : 1"
:virtual-list-props="props.virtualListProps"
:placeholder="props.placeholder"
:loading="props.loading"
@ -51,11 +51,11 @@
v-model:model-value="innerValue"
class="ms-cascader"
:options="props.options"
:trigger-props="{ contentClass: `ms-cascader-popper ms-cascader-popper--${props.optionSize}` }"
:trigger-props="{ contentClass: `ms-cascader-popper-native ms-cascader-popper--${props.optionSize}` }"
:multiple="props.multiple"
allow-clear
:check-strictly="props.strictly"
:max-tag-count="maxTagCount"
:max-tag-count="props.shouldCalculateMaxTag ? maxTagCount : 1"
:placeholder="props.placeholder"
:virtual-list-props="props.virtualListProps"
:loading="props.loading"
@ -121,6 +121,7 @@
labelPathMode?: boolean; // label
valueKey?: string;
labelKey?: string; // labelKey
shouldCalculateMaxTag?: boolean; //
}
const props = withDefaults(defineProps<MsCascaderProps>(), {
@ -129,6 +130,7 @@
pathMode: false,
valueKey: 'value',
labelKey: 'label',
shouldCalculateMaxTag: true,
});
const emit = defineEmits(['update:modelValue', 'update:level', 'change']);
@ -161,6 +163,9 @@
//
innerLevel.value = val[0] as string;
}
if (props.shouldCalculateMaxTag !== false && props.multiple) {
calculateMaxTag();
}
},
{
immediate: true,
@ -175,11 +180,11 @@
selectedLabelObj = {};
for (let i = 0; i < val.length; i++) {
const item = val[i];
const value = typeof item === 'object' ? item.value : item;
const value = typeof item === 'object' ? item[props.valueKey] : item;
if (!props.labelPathMode) {
selectedLabelObj[value] = t((item.label || '').split('/').pop() || '');
selectedLabelObj[value] = t((item[props.labelKey] || '').split('/').pop() || '');
} else {
selectedLabelObj[value] = t(item.label || '');
selectedLabelObj[value] = t(item[props.labelKey] || '');
}
}
}
@ -218,7 +223,9 @@
innerLevel.value = '';
}
}
calculateMaxTag();
if (props.shouldCalculateMaxTag) {
calculateMaxTag();
}
}
// TODO: arco-design cascader label/ path-mode
@ -258,7 +265,8 @@
}
}
}
.ms-cascader-popper {
.ms-cascader-popper,
.ms-cascader-popper-native {
.arco-cascader-panel {
.arco-cascader-panel-column {
.arco-cascader-column-content {
@ -275,6 +283,10 @@
background-color: rgb(var(--primary-1));
}
}
}
}
.ms-cascader-popper {
.arco-cascader-panel {
.arco-cascader-panel-column:first-child {
.arco-checkbox {
@apply hidden;

View File

@ -76,6 +76,7 @@ export enum TableKeyEnum {
TASK_CENTER_CASE_TASK = 'taskCenterCaseTask',
TASK_CENTER_CASE_TASK_DETAIL = 'taskCenterCaseTaskDetail',
TASK_CENTER_SYSTEM_TASK = 'taskCenterSystemTask',
TASK_CENTER_BATCH_TASK = 'taskCenterBatchTask',
TASK_SCHEDULE_TASK_API_IMPORT_SYSTEM = 'taskCenterScheduleApiImportSystem',
TASK_SCHEDULE_TASK_API_SCENARIO_SYSTEM = 'taskCenterScheduleApiScenarioSystem',
TASK_SCHEDULE_TASK_API_IMPORT_ORGANIZATION = 'taskCenterScheduleApiImportOrganization',

View File

@ -1,5 +1,6 @@
import { RequestResult } from '@/models/apiTest/common';
import type { RequestMethods } from '@/enums/apiEnum';
import type { ExecuteStatusEnum } from '@/enums/taskCenter';
export interface LegendData {
label: string;
@ -73,7 +74,7 @@ export interface ReportStepDetailItem {
id: string;
reportId: string;
stepId: string;
status: string;
status: ExecuteStatusEnum;
fakeCode: string;
requestName: string;
requestTime: number;

View File

@ -1,4 +1,4 @@
import type { ExecuteTaskType, ExecuteTriggerMode } from '@/enums/taskCenter';
import type { ExecuteStatusEnum, ExecuteTaskType, ExecuteTriggerMode } from '@/enums/taskCenter';
import type { TableQueryParams } from './common';
@ -59,7 +59,7 @@ export interface TaskCenterTaskDetailItem {
resourceId: string;
resourceName: string;
taskOrigin: string; // 任务来源
status: string; // 执行状态
status: ExecuteStatusEnum; // 执行状态
result: string; // 执行结果
resourcePoolId: string; // 资源池ID
resourcePoolNode: string; // 资源池节点

View File

@ -151,6 +151,8 @@
const props = defineProps<{
mode: 'tiled' | 'tab'; // | tab
hideResponseTime?: boolean; //
static?: boolean; //
staticInfo?: ReportStepDetailItem; //
stepItem?: ScenarioItemType; //
console?: string;
isPriorityLocalExec?: boolean;
@ -386,7 +388,10 @@
const originStepId = ref<string | undefined>('');
watchEffect(() => {
if (props.stepItem?.stepId && props.mode === 'tiled') {
if (props.static && props.staticInfo) {
activeStepDetail.value = props.staticInfo;
activeStepDetailCopy.value = cloneDeep(props.staticInfo);
} else if (props.stepItem?.stepId && props.mode === 'tiled') {
const stepIds = props.stepItem?.stepChildren || [];
getStepDetail(isShowLoopControl.value ? stepIds[controlCurrent.value - 1].stepId : props.stepItem.stepId);
}
@ -394,7 +399,10 @@
onMounted(() => {
originStepId.value = props.stepItem?.stepId;
if (props.stepItem?.stepId && !props.stepItem.fold) {
if (props.static && props.staticInfo) {
activeStepDetail.value = props.staticInfo;
activeStepDetailCopy.value = cloneDeep(props.staticInfo);
} else if (props.stepItem?.stepId && !props.stepItem.fold) {
const stepIds = props.stepItem?.stepChildren || [];
getStepDetail(isShowLoopControl.value ? stepIds[controlCurrent.value - 1].stepId : props.stepItem.stepId);
}

View File

@ -4,7 +4,7 @@
<template #name="{ record }">
<a-button type="text" class="max-w-full justify-start px-0" @click="showReportDetail(record)">
<div class="one-line-text">
{{ record.num }}
{{ record.name }}
</div>
</a-button>
</template>
@ -25,7 +25,8 @@
<ExecutionStatus :module-type="props.moduleType" :status="filterContent.value" />
</template>
<template #execStatus="{ record }">
<ExecStatus :status="record.execStatus" />
<ExecStatus v-if="record.execStatus" :status="record.execStatus" />
<span v-else>-</span>
</template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT]="{ filterContent }">
<ExecStatus :status="filterContent.value" />
@ -38,12 +39,37 @@
<template #triggerMode="{ record }">
<span>{{ t(TriggerModeLabel[record.triggerMode as keyof typeof TriggerModeLabel]) }}</span>
</template>
<template #operationTime="{ record }">
<span>{{ dayjs(record.operationTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
<template #createTime="{ record }">
<span>{{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</ms-base-table>
</MsDrawer>
<ReportDrawer v-model:visible="reportVisible" :report-id="independentReportId" />
<CaseReportDrawer
v-model:visible="showCaseDetailDrawer"
:report-id="activeDetailId"
:active-report-index="activeReportIndex"
:table-data="propsRes.data"
:page-change="propsEvent.pageChange"
:pagination="{
current: 1,
pageSize: 10,
total: 1,
}"
:share-time="shareTime"
/>
<ReportDetailDrawer
v-model:visible="showDetailDrawer"
:report-id="activeDetailId"
:active-report-index="activeReportIndex"
:table-data="propsRes.data"
:page-change="propsEvent.pageChange"
:pagination="{
current: 1,
pageSize: 10,
total: 1,
}"
:share-time="shareTime"
/>
</template>
<script setup lang="ts">
@ -54,31 +80,35 @@
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import CaseReportDrawer from '@/views/api-test/report/component/caseReportDrawer.vue';
import ReportDetailDrawer from '@/views/api-test/report/component/reportDetailDrawer.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import ExecStatus from '@/views/test-plan/report/component/execStatus.vue';
import ReportDrawer from '@/views/test-plan/testPlan/detail/reportDrawer.vue';
import { getShareTime } from '@/api/modules/api-test/report';
import { organizationBatchTaskReportList } from '@/api/modules/taskCenter/organization';
import { projectBatchTaskReportList } from '@/api/modules/taskCenter/project';
import { systemBatchTaskReportList } from '@/api/modules/taskCenter/system';
import { useI18n } from '@/hooks/useI18n';
import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app';
import { TaskCenterBatchTaskReportItem } from '@/models/taskCenter';
import { TaskCenterTaskItem } from '@/models/taskCenter';
import { ReportExecStatus } from '@/enums/apiEnum';
import { ReportEnum, ReportStatus, TriggerModeLabel } from '@/enums/reportEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { ExecuteTaskType } from '@/enums/taskCenter';
import { executeMethodMap } from './config';
const props = defineProps<{
range: 'system' | 'project' | 'org';
type: 'CASE' | 'SCENARIO';
moduleType: keyof typeof ReportEnum;
taskId: string;
batchType: ExecuteTaskType;
}>();
const { t } = useI18n();
const tableStore = useTableStore();
const appStore = useAppStore();
const visible = defineModel<boolean>('visible', { required: true });
@ -125,13 +155,13 @@
sortDirections: ['ascend', 'descend'],
sorter: true,
},
ellipsis: true,
fixed: 'left',
},
{
title: 'report.type',
slotName: 'integrated',
dataIndex: 'integrated',
width: 150,
width: 120,
},
{
title: 'report.result',
@ -146,7 +176,7 @@
sorter: true,
},
showInTable: true,
width: 200,
width: 120,
},
{
title: 'report.status',
@ -161,27 +191,34 @@
sorter: true,
},
showInTable: true,
width: 200,
width: 120,
},
{
title: 'report.trigger.mode',
dataIndex: 'triggerMode',
slotName: 'triggerMode',
showInTable: true,
width: 150,
filterConfig: {
options: Object.keys(executeMethodMap).map((key) => ({
label: t(executeMethodMap[key]),
value: key,
})),
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_METHOD,
},
width: 100,
},
{
title: 'report.operator',
slotName: 'createUserName',
dataIndex: 'createUserName',
showInTable: true,
width: 300,
width: 150,
showTooltip: true,
},
{
title: 'report.operating',
dataIndex: 'startTime',
slotName: 'startTime',
dataIndex: 'createTime',
slotName: 'createTime',
width: 180,
sortable: {
sortDirections: ['ascend', 'descend'],
@ -190,8 +227,6 @@
},
];
await tableStore.initColumn(TableKeyEnum.API_TEST_REPORT, columns, 'drawer');
const currentList = {
system: systemBatchTaskReportList,
org: organizationBatchTaskReportList,
@ -200,7 +235,7 @@
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
currentList,
{
tableKey: TableKeyEnum.API_TEST_REPORT,
columns,
scroll: {
x: '100%',
},
@ -224,6 +259,8 @@
keyword: keyword.value,
projectId: appStore.currentProjectId,
moduleType: props.moduleType,
taskId: props.taskId,
batchType: props.batchType,
filter: {
integrated: typeFilter.value,
...filterParams,
@ -236,13 +273,55 @@
initData(dataIndex, value);
}
const reportVisible = ref(false);
const independentReportId = ref<string>('');
/**
* 报告详情 showReportDetail
*/
const activeDetailId = ref<string>('');
const activeReportIndex = ref<number>(0);
const showDetailDrawer = ref<boolean>(false);
const showCaseDetailDrawer = ref<boolean>(false);
function showReportDetail(record: TaskCenterBatchTaskReportItem) {
independentReportId.value = record.id;
reportVisible.value = true;
function showReportDetail(record: TaskCenterTaskItem) {
activeDetailId.value = record.id;
if ([ExecuteTaskType.API_SCENARIO_BATCH, ExecuteTaskType.TEST_PLAN_API_SCENARIO_BATCH].includes(props.batchType)) {
showDetailDrawer.value = true;
} else {
showCaseDetailDrawer.value = true;
}
}
const shareTime = ref<string>('');
async function getTime() {
try {
const res = await getShareTime(appStore.currentProjectId);
const match = res.match(/^(\d+)([MYHD])$/);
if (match) {
const value = parseInt(match[1], 10);
const type = match[2];
const translations: Record<string, string> = {
M: t('msTimeSelector.month'),
Y: t('msTimeSelector.year'),
H: t('msTimeSelector.hour'),
D: t('msTimeSelector.day'),
};
shareTime.value = value + (translations[type] || translations.D);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
watch(
() => visible.value,
(val) => {
if (val) {
initData();
getTime();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped></style>

View File

@ -2,44 +2,47 @@
<MsDrawer v-model:visible="visible" :width="800" :footer="false">
<template #title>
<div class="flex items-center gap-[8px]">
<a-tag :color="executeResultMap[detail.executeResult]?.color">
{{ t(executeResultMap[detail.executeResult]?.label) }}
<a-tag :color="executeResultMap[props.record.result]?.color">
{{ t(executeResultMap[props.record.result]?.label) }}
</a-tag>
<div>{{ detail.name }}</div>
</div>
<div class="flex flex-1 justify-end">
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="refresh">
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="init">
<MsIcon type="icon-icon_reset_outlined" class="mr-[8px]" size="14" />
{{ t('common.refresh') }}
</MsButton>
</div>
</template>
<MsDescription :descriptions="detail.description" :column="3" :line-gap="8" one-line-value>
<template #value="{ item }">
<execStatus v-if="item.key === 'status'" :status="item.value as ReportExecStatus" size="small" />
<a-tooltip
v-else
:content="`${item.value}`"
:disabled="item.value === undefined || item.value === null || item.value?.toString() === ''"
:position="item.tooltipPosition ?? 'tl'"
>
<div class="w-[fit-content]">
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
</div>
</a-tooltip>
</template>
</MsDescription>
<div class="mt-[8px]">
<StepDetailContent
mode="tiled"
show-type="CASE"
:step-item="detail.scenarioDetail"
:console="detail.console"
:is-definition="true"
:get-report-step-detail="props.getReportStepDetail"
:report-id="detail.scenarioDetail?.reportId"
/>
</div>
<a-spin :loading="loading" class="block">
<MsDescription :descriptions="detail.description" :column="3" :line-gap="8" one-line-value>
<template #value="{ item }">
<execStatus v-if="item.key === 'status'" :status="props.record.status" size="small" />
<a-tooltip
v-else
:content="`${item.value}`"
:disabled="item.value === undefined || item.value === null || item.value?.toString() === ''"
:position="item.tooltipPosition ?? 'tl'"
>
<div class="w-[fit-content]">
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
</div>
</a-tooltip>
</template>
</MsDescription>
<div class="mt-[8px]">
<StepDetailContent
v-if="visible && detail.content"
mode="tiled"
show-type="CASE"
static
:static-info="detail"
:is-definition="true"
:get-report-step-detail="getCaseTaskReport"
:report-id="detail.reportId"
/>
</div>
</a-spin>
</MsDrawer>
</template>
@ -53,75 +56,85 @@
import execStatus from './execStatus.vue';
import StepDetailContent from '@/views/api-test/components/requestComposition/response/result/index.vue';
import { getCaseTaskReport } from '@/api/modules/api-test/report';
import { useI18n } from '@/hooks/useI18n';
import { ReportExecStatus } from '@/enums/apiEnum';
import { TaskCenterTaskDetailItem } from '@/models/taskCenter';
import { executeResultMap } from './config';
import { executeResultMap, executeStatusMap } from './config';
const props = defineProps<{
id: string;
getReportStepDetail?: (...args: any) => Promise<any>; //
record: TaskCenterTaskDetailItem;
}>();
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
const loading = ref(false);
const detail = ref<any>({ description: [] });
async function init() {
try {
loading.value = true;
const res = await getCaseTaskReport(props.record.id);
const [caseDetail] = res;
detail.value = {
name: caseDetail.requestName,
description: [
{
label: t('ms.taskCenter.executeStatus'),
key: 'status',
value: t(executeStatusMap[props.record.status].label),
},
{
label: t('ms.taskCenter.operationUser'),
value: props.record.executor,
},
{
label: t('ms.taskCenter.taskCreateTime'),
value: dayjs(props.record.startTime).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.taskResource'),
value: props.record.resourceName,
},
{
label: t('ms.taskCenter.threadID'),
value: props.record.threadId,
},
{
label: t('ms.taskCenter.taskStartTime'),
value: dayjs(props.record.startTime).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.executeEnvInfo'),
value: 'DEV 资源池1 10.11.1.1',
class: '!w-[calc(100%/3*2)]',
},
{
label: t('ms.taskCenter.taskEndTime'),
value: dayjs(props.record.endTime).format('YYYY-MM-DD HH:mm:ss'),
},
] as Description[],
...caseDetail,
};
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
watch(
() => props.id,
async () => {
if (props.id) {
detail.value = {
id: props.id,
name: '测试用例名称',
executeResult: 'SUCCESS',
description: [
{
label: t('ms.taskCenter.executeStatus'),
key: 'status',
value: 'COMPLETED',
},
{
label: t('ms.taskCenter.operationUser'),
value: 'admin',
},
{
label: t('ms.taskCenter.taskCreateTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.taskResource'),
value: '测试计划',
},
{
label: t('ms.taskCenter.threadID'),
value: '1231231231',
},
{
label: t('ms.taskCenter.taskStartTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.executeEnvInfo'),
value: 'DEV 资源池1 10.11.1.1',
class: '!w-[calc(100%/3*2)]',
},
{
label: t('ms.taskCenter.taskEndTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
] as Description[],
};
() => visible.value,
(val) => {
if (props.record.id && val) {
init();
}
},
{ immediate: true }
);
function refresh() {
console.log('refresh');
}
</script>
<style lang="less" scoped>

View File

@ -12,15 +12,18 @@
<MsCascader
v-model:model-value="resourcePool"
mode="native"
multiple
:options="resourcePoolOptions"
:placeholder="t('common.pleaseSelect')"
option-size="small"
label-key="name"
value-key="id"
class="w-[240px]"
:prefix="t('ms.taskCenter.resourcePool')"
:virtual-list-props="{ height: 200 }"
strictly
label-path-mode
@change="searchTask"
@clear="searchTask"
@popup-visible-change="handleResourcePoolVisibleChange"
@change="handleResourcePoolChange"
>
</MsCascader>
<MsTag no-margin size="large" :tooltip-disabled="true" class="cursor-pointer" theme="outline" @click="searchTask">
@ -69,13 +72,13 @@
>
{{ t('ms.taskCenter.rerun') }}
</MsButton> -->
<MsButton v-if="record.status === ExecuteStatusEnum.COMPLETED" @click="checkExecuteResult(record)">
<MsButton v-if="record.status !== ExecuteStatusEnum.PENDING" @click="checkExecuteResult(record)">
{{ t('ms.taskCenter.executeResult') }}
</MsButton>
</template>
</ms-base-table>
<caseExecuteResultDrawer :id="executeResultId" v-model:visible="caseExecuteResultDrawerVisible" />
<scenarioExecuteResultDrawer :id="executeResultId" v-model:visible="scenarioExecuteResultDrawerVisible" />
<caseExecuteResultDrawer v-model:visible="caseExecuteResultDrawerVisible" :record="activeRecord" />
<scenarioExecuteResultDrawer v-model:visible="scenarioExecuteResultDrawerVisible" :record="activeRecord" />
</template>
<script setup lang="ts">
@ -119,7 +122,8 @@
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
import { characterLimit } from '@/utils';
import { useAppStore } from '@/store';
import { characterLimit, mapTree } from '@/utils';
import { TaskCenterTaskDetailItem } from '@/models/taskCenter';
import { TableKeyEnum } from '@/enums/tableEnum';
@ -135,10 +139,11 @@
const { t } = useI18n();
const { openModal } = useModal();
const appStore = useAppStore();
const tableStore = useTableStore();
const keyword = ref('');
const resourcePool = ref([]);
const resourcePool = ref<string[]>([]);
const resourcePoolOptions = ref<CascaderOption[]>([]);
const tableSelected = ref<string[]>([]);
const batchModalParams = ref();
@ -334,11 +339,57 @@
}
);
const resourcePoolIds = ref<Set<string>>(new Set([]));
const resourcePoolNodes = ref<Set<string>>(new Set([]));
function searchTask() {
setLoadListParams({ keyword: keyword.value, resourcePools: resourcePool.value });
setLoadListParams({
keyword: keyword.value,
resourcePoolIds: Array.from(resourcePoolIds.value),
resourcePoolNodes: Array.from(resourcePoolNodes.value),
});
loadList();
}
function handleResourcePoolVisibleChange(val: boolean) {
if (!val) {
searchTask();
}
}
function handleResourcePoolChange(value: string[]) {
if (resourcePool.value.length < value.length) {
//
const lastValue = value[value.length - 1];
const resourceClass = resourcePoolOptions.value.find((e) => e.value === lastValue);
if (resourceClass && resourceClass.children && resourceClass.children.length > 0) {
const childIds = resourceClass.children.map((e) => e.value as string);
resourcePool.value.push(...value, ...childIds);
resourcePool.value = Array.from(new Set(resourcePool.value));
childIds.forEach((e) => {
resourcePoolNodes.value.add(e);
});
}
if (resourceClass) {
//
resourcePoolIds.value.add(resourceClass.value as string);
} else {
//
resourcePoolNodes.value.add(lastValue);
}
} else {
//
const lastValue = value[value.length - 1];
const resourceClass = resourcePoolOptions.value.find((e) => e.value === lastValue);
if (resourceClass) {
//
resourcePoolIds.value.delete(resourceClass.value as string);
} else {
//
resourcePoolNodes.value.delete(lastValue);
}
}
}
const currentStopTask = {
system: systemStopTaskDetail,
project: projectStopTaskDetail,
@ -409,11 +460,11 @@
}
}
const executeResultId = ref('');
const activeRecord = ref<TaskCenterTaskDetailItem>({} as TaskCenterTaskDetailItem);
const caseExecuteResultDrawerVisible = ref(false);
const scenarioExecuteResultDrawerVisible = ref(false);
function checkExecuteResult(record: TaskCenterTaskDetailItem) {
executeResultId.value = record.id;
activeRecord.value = record;
if (record.resourceType === 'API_SCENARIO') {
scenarioExecuteResultDrawerVisible.value = true;
} else {
@ -422,15 +473,19 @@
}
const currentResourcePoolRequest = {
system: getProjectTaskCenterResourcePools,
project: getOrgTaskCenterResourcePools,
org: getSystemTaskCenterResourcePools,
system: getSystemTaskCenterResourcePools,
project: getProjectTaskCenterResourcePools,
org: getOrgTaskCenterResourcePools,
}[props.type];
async function initResourcePools() {
try {
const res = await currentResourcePoolRequest();
resourcePoolOptions.value = res;
resourcePoolOptions.value = mapTree(res, (node) => ({
label: node.name,
value: node.id,
children: node.children,
}));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -518,6 +573,13 @@
}
);
watch(
() => appStore.currentProjectId,
() => {
searchTask();
}
);
onMounted(async () => {
if (props.id) {
keyword.value = props.id;

View File

@ -131,9 +131,11 @@
</ms-base-table>
<batchTaskReportDrawer
v-model:visible="taskReportDrawerVisible"
:range="reportModuleType"
:type="reportModuleType"
:range="props.type"
:type="reportType"
:module-type="reportModuleType"
:task-id="reportBatchTaskId"
:batch-type="reportBatchType"
/>
<CaseReportDrawer
v-model:visible="showCaseDetailDrawer"
@ -257,7 +259,7 @@
title: 'ms.taskCenter.executeStatus',
dataIndex: 'status',
slotName: 'status',
width: 90,
width: 100,
filterConfig: {
options: Object.keys(executeStatusMap).map((key) => ({
label: t(executeStatusMap[key as ExecuteStatusEnum].label),
@ -270,7 +272,7 @@
title: 'ms.taskCenter.executeMethod',
dataIndex: 'triggerMode',
slotName: 'triggerMode',
width: 90,
width: 100,
filterConfig: {
options: Object.keys(executeMethodMap).map((key) => ({
label: t(executeMethodMap[key]),
@ -283,7 +285,7 @@
title: 'ms.taskCenter.executeResult',
dataIndex: 'result',
slotName: 'result',
width: 90,
width: 100,
filterConfig: {
options: Object.keys(executeResultMap).map((key) => ({
label: t(executeResultMap[key].label),
@ -686,13 +688,17 @@
const taskReportDrawerVisible = ref(false);
const reportModuleType = ref();
const reportBatchType = ref();
const reportType = ref<'CASE' | 'SCENARIO'>('CASE');
const reportBatchType = ref<ExecuteTaskType>(ExecuteTaskType.API_CASE);
const reportBatchTaskId = ref('');
function checkReport(record: TaskCenterTaskItem) {
if (record.taskType.includes('BATCH')) {
reportModuleType.value = record.taskType.includes('CASE')
? ReportEnum.API_REPORT
: ReportEnum.API_SCENARIO_REPORT;
reportBatchType.value = record.taskType.includes('CASE') ? 'CASE' : 'SCENARIO';
reportType.value = record.taskType.includes('CASE') ? 'CASE' : 'SCENARIO';
reportBatchType.value = record.taskType;
reportBatchTaskId.value = record.id;
taskReportDrawerVisible.value = true;
} else if (
[
@ -726,6 +732,13 @@
}
);
watch(
() => appStore.currentProjectId,
() => {
searchTask();
}
);
await tableStore.initColumn(TableKeyEnum.TASK_CENTER_CASE_TASK, columns, 'drawer');
</script>

View File

@ -12,11 +12,12 @@
import { useI18n } from '@/hooks/useI18n';
import { ReportExecStatus } from '@/enums/apiEnum';
import { ExecuteStatusEnum } from '@/enums/taskCenter';
import { executeStatusMap } from './config';
const props = defineProps<{
status: ReportExecStatus;
status: ReportExecStatus | ExecuteStatusEnum;
size?: 'small' | 'medium' | 'large';
}>();
const { t } = useI18n();

View File

@ -2,53 +2,55 @@
<MsDrawer v-model:visible="visible" :width="800" :footer="false">
<template #title>
<div class="flex items-center gap-[8px]">
<a-tag :color="executeResultMap[detail.executeResult]?.color">
{{ t(executeResultMap[detail.executeResult]?.label) }}
<a-tag :color="executeResultMap[props.record.result]?.color">
{{ t(executeResultMap[props.record.result]?.label) }}
</a-tag>
<div>{{ detail.name }}</div>
</div>
<div class="flex flex-1 justify-end">
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="refresh">
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="init">
<MsIcon type="icon-icon_reset_outlined" class="mr-[8px]" size="14" />
{{ t('common.refresh') }}
</MsButton>
</div>
</template>
<MsDescription :descriptions="detail.description" :column="3" :line-gap="8" one-line-value>
<template #value="{ item }">
<execStatus v-if="item.key === 'status'" :status="item.value as ReportExecStatus" size="small" />
<a-tooltip
v-else
:content="`${item.value}`"
:disabled="item.value === undefined || item.value === null || item.value?.toString() === ''"
:position="item.tooltipPosition ?? 'tl'"
>
<div class="w-[fit-content]">
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
</div>
</a-tooltip>
</template>
</MsDescription>
<div class="mt-[8px]">
<reportInfoHeader
v-model:keywordName="keywordName"
v-model:keyword="cascaderKeywords"
v-model:active-tab="activeTab"
show-type="API"
@search="searchHandler"
@reset="resetHandler"
/>
<TiledList
ref="tiledListRef"
v-model:keyword-name="keywordName"
:key-words="cascaderKeywords"
show-type="API"
:get-report-step-detail="props.getReportStepDetail"
:active-type="activeTab"
:report-detail="detail || []"
class="p-[16px]"
/>
</div>
<a-spin :loading="loading" class="block">
<MsDescription :descriptions="detail.description" :column="3" :line-gap="8" one-line-value>
<template #value="{ item }">
<execStatus v-if="item.key === 'status'" :status="item.value as ReportExecStatus" size="small" />
<a-tooltip
v-else
:content="`${item.value}`"
:disabled="item.value === undefined || item.value === null || item.value?.toString() === ''"
:position="item.tooltipPosition ?? 'tl'"
>
<div class="w-[fit-content]">
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
</div>
</a-tooltip>
</template>
</MsDescription>
<div class="mt-[8px]">
<reportInfoHeader
v-model:keywordName="keywordName"
v-model:keyword="cascaderKeywords"
v-model:active-tab="activeTab"
show-type="API"
@search="searchHandler"
@reset="resetHandler"
/>
<TiledList
ref="tiledListRef"
v-model:keyword-name="keywordName"
:key-words="cascaderKeywords"
show-type="API"
:get-report-step-detail="getScenarioTaskReportStep"
:active-type="activeTab"
:report-detail="detail || []"
class="p-[16px]"
/>
</div>
</a-spin>
</MsDrawer>
</template>
@ -63,68 +65,80 @@
import reportInfoHeader from '@/views/api-test/report/component/step/reportInfoHeaders.vue';
import TiledList from '@/views/api-test/report/component/tiledList.vue';
import { getScenarioTaskReport, getScenarioTaskReportStep } from '@/api/modules/api-test/report';
import { useI18n } from '@/hooks/useI18n';
import { TaskCenterTaskDetailItem } from '@/models/taskCenter';
import { ReportExecStatus } from '@/enums/apiEnum';
import { executeResultMap } from './config';
import { executeResultMap, executeStatusMap } from './config';
const props = defineProps<{
id: string;
getReportStepDetail?: (...args: any) => Promise<any>; //
record: TaskCenterTaskDetailItem;
}>();
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
const loading = ref(false);
const detail = ref<any>({ description: [], children: [] });
async function init() {
try {
loading.value = true;
const res = await getScenarioTaskReport(props.record.id);
detail.value = {
description: [
{
label: t('ms.taskCenter.executeStatus'),
key: 'status',
value: t(executeStatusMap[props.record.status].label),
},
{
label: t('ms.taskCenter.operationUser'),
value: res.creatUserName,
},
{
label: t('ms.taskCenter.taskCreateTime'),
value: dayjs(res.startTime).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.taskResource'),
value: props.record.resourceName,
},
{
label: t('ms.taskCenter.threadID'),
value: props.record.threadId,
},
{
label: t('ms.taskCenter.taskStartTime'),
value: dayjs(res.startTime).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.executeEnvInfo'),
value: 'DEV 资源池1 10.11.1.1',
class: '!w-[calc(100%/3*2)]',
},
{
label: t('ms.taskCenter.taskEndTime'),
value: res.endTime ? dayjs(res.endTime).format('YYYY-MM-DD HH:mm:ss') : '-',
},
] as Description[],
...res,
};
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
watch(
() => props.id,
async () => {
if (props.id) {
detail.value = {
id: props.id,
name: '测试用例名称',
executeResult: 'SUCCESS',
description: [
{
label: t('ms.taskCenter.executeStatus'),
key: 'status',
value: 'COMPLETED',
},
{
label: t('ms.taskCenter.operationUser'),
value: 'admin',
},
{
label: t('ms.taskCenter.taskCreateTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.taskResource'),
value: '测试计划',
},
{
label: t('ms.taskCenter.threadID'),
value: '1231231231',
},
{
label: t('ms.taskCenter.taskStartTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.executeEnvInfo'),
value: 'DEV 资源池1 10.11.1.1',
class: '!w-[calc(100%/3*2)]',
},
{
label: t('ms.taskCenter.taskEndTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
] as Description[],
children: [],
};
() => props.record.id,
() => {
if (props.record.id) {
init();
}
},
{ immediate: true }
@ -146,10 +160,6 @@
function resetHandler() {
tiledListRef.value?.initStepTree();
}
function refresh() {
console.log('refresh');
}
</script>
<style lang="less" scoped>

View File

@ -104,6 +104,7 @@
import useModal from '@/hooks/useModal';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useTableStore from '@/hooks/useTableStore';
import { useAppStore } from '@/store';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
@ -123,6 +124,7 @@
const { openModal } = useModal();
const { openNewPage } = useOpenNewPage();
const tableStore = useTableStore();
const appStore = useAppStore();
const keyword = ref('');
const batchModalParams = ref();
@ -464,6 +466,13 @@
onMounted(() => {
loadList();
});
watch(
() => appStore.currentProjectId,
() => {
searchTask();
}
);
</script>
<style lang="less" scoped></style>