feat(缺陷管理): 缺陷管理增加严重程度 , 状态, 处理人、创建人, 更新人的筛选支持

--bug=1036448 --user=宋天阳 【缺陷管理】缺陷列表部分字段表头需支持筛选 https://www.tapd.cn/55049933/s/1472937
This commit is contained in:
song-tianyang 2024-03-11 16:10:56 +08:00 committed by 刘瑞斌
parent 010ff1c687
commit dfa60cc45c
11 changed files with 233 additions and 28 deletions

View File

@ -6,10 +6,10 @@ import io.metersphere.bug.constants.BugExportColumns;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.dto.BugSyncResult;
import io.metersphere.bug.dto.request.*;
import io.metersphere.bug.dto.response.BugColumnsOptionResponse;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.dto.response.BugDetailDTO;
import io.metersphere.bug.service.*;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.project.dto.ProjectTemplateOptionDTO;
import io.metersphere.project.service.ProjectTemplateService;
import io.metersphere.sdk.constants.PermissionConstants;
@ -66,20 +66,12 @@ public class BugController {
return bugService.getHeaderCustomFields(projectId);
}
@GetMapping("/header/status-option/{projectId}")
@GetMapping("/header/columns-option/{projectId}")
@Operation(summary = "缺陷管理-列表-获取表头状态选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<SelectOption> getHeaderStatusOption(@PathVariable String projectId) {
return bugStatusService.getHeaderStatusOption(projectId);
}
@GetMapping("/header/handler-option/{projectId}")
@Operation(summary = "缺陷管理-列表-获取表头处理人选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<SelectOption> getHeaderHandleOption(@PathVariable String projectId) {
return bugCommonService.getHeaderHandlerOption(projectId);
public BugColumnsOptionResponse getHeaderStatusOption(@PathVariable String projectId) {
return bugStatusService.getColumnsOption(projectId);
}
@PostMapping("/page")

View File

@ -0,0 +1,21 @@
package io.metersphere.bug.dto.response;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BugColumnsOptionResponse {
@Schema(description = "用户相关的下拉选项")
List<SelectOption> userOption;
@Schema(description = "处理人下拉选项")
List<SelectOption> handleUserOption;
@Schema(description = "状态下拉选项")
List<SelectOption> statusOption;
}

View File

@ -143,7 +143,7 @@
<if test="request.projectId">
and b.project_id = #{request.projectId}
</if>
<if test="request.keyword != null">
<if test="request.keyword != null and request.keyword != ''">
and (
b.title like concat('%', #{request.keyword},'%')
or b.num like concat('%', #{request.keyword},'%')

View File

@ -92,9 +92,11 @@
</if>
</select>
<select id="countByCaseId" resultType="java.lang.Long">
SELECT count(id)
SELECT count(fc.id)
FROM bug_relation_case brc
INNER JOIN functional_case fc ON brc.case_id = fc.id
where brc.bug_id = #{caseId}
AND fc.deleted = false
</select>
<sql id="queryWhereConditionByProvider">

View File

@ -2,8 +2,10 @@ package io.metersphere.bug.service;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.domain.BugExample;
import io.metersphere.bug.dto.response.BugColumnsOptionResponse;
import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.bug.mapper.ExtBugMapper;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.service.ProjectApplicationService;
@ -24,10 +26,14 @@ import java.util.List;
public class BugStatusService {
@Resource
private BugMapper bugMapper;
@Resource
private ExtBugMapper extBugMapper;
@Resource
private ProjectApplicationService projectApplicationService;
@Resource
private BaseStatusFlowSettingService baseStatusFlowSettingService;
@Resource
private BugCommonService bugCommonService;
/**
* 获取表头缺陷状态选项
@ -111,4 +117,12 @@ public class BugStatusService {
return StringUtils.EMPTY;
}
}
public BugColumnsOptionResponse getColumnsOption(String projectId) {
return new BugColumnsOptionResponse(
bugCommonService.getLocalHandlerOption(projectId),
bugCommonService.getHeaderHandlerOption(projectId),
getHeaderStatusOption(projectId)
);
}
}

View File

@ -72,8 +72,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class BugControllerTests extends BaseTest {
public static final String BUG_HEADER_CUSTOM_FIELD = "/bug/header/custom-field";
public static final String BUG_HEADER_STATUS_OPTION = "/bug/header/status-option";
public static final String BUG_HEADER_HANDLER_OPTION = "/bug/header/handler-option";
public static final String BUG_HEADER_COLUMNS_OPTION = "/bug/header/columns-option";
public static final String BUG_PAGE = "/bug/page";
public static final String BUG_EDIT_POS = "/bug/edit/pos";
public static final String BUG_ADD = "/bug/add";
@ -136,8 +135,7 @@ public class BugControllerTests extends BaseTest {
void testBugPageSuccess() throws Exception {
// 表头字段, 状态选项, 处理人选项
this.requestGetWithOk(BUG_HEADER_CUSTOM_FIELD + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_STATUS_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_COLUMNS_OPTION + "/default-project-for-bug");
BugPageRequest bugRequest = new BugPageRequest();
bugRequest.setCurrent(1);
bugRequest.setPageSize(10);
@ -539,7 +537,7 @@ public class BugControllerTests extends BaseTest {
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
this.requestPost(BUG_TEMPLATE_DETAIL, request, status().is5xxServerError());
// 获取处理人选项(Local)
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_COLUMNS_OPTION + "/default-project-for-bug");
// 开启插件集成
record.setEnable(true);
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
@ -563,8 +561,7 @@ public class BugControllerTests extends BaseTest {
this.requestGetWithOk(BUG_DETAIL + "/default-bug-id-jira-sync");
// 表头字段, 状态选项, 处理人选项 (非Local平台)
this.requestGetWithOk(BUG_HEADER_CUSTOM_FIELD + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_STATUS_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_COLUMNS_OPTION + "/default-project-for-bug");
// 同步并删除的两条缺陷
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
@ -698,7 +695,7 @@ public class BugControllerTests extends BaseTest {
// 获取禅道模板(删除默认项目模板)
bugService.attachTemplateStatusField(null, null, null, null);
// 获取处理人选项
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
this.requestGetWithOk(BUG_HEADER_COLUMNS_OPTION + "/default-project-for-bug");
// 批量删除
BugBatchRequest request = new BugBatchRequest();
request.setProjectId("default-project-for-bug");

View File

@ -2,8 +2,8 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.project.mapper.ExtProjectMemberMapper">
<select id="listMember" resultType="java.lang.String">
select distinct u.id
from user_role_relation urr
select distinct t.id FROM (
SELECT u.id AS id,urr.create_time from user_role_relation urr
join `user` u on urr.user_id = u.id
<where>
urr.source_id = #{request.projectId} and u.deleted = 0
@ -15,6 +15,7 @@
<include refid="filter"/>
</where>
order by urr.create_time desc
) t
</select>
<select id="getMemberByOrg" resultType="io.metersphere.system.dto.user.UserExtendDTO">

View File

@ -2,8 +2,9 @@ import { CommentParams } from '@/components/business/ms-comment/types';
import MSR from '@/api/http/index';
import * as bugURL from '@/api/requrls/bug-management';
import { getCustomOptionHeaderUrl } from '@/api/requrls/bug-management';
import { BugEditFormObject, BugListItem } from '@/models/bug-management';
import { BugEditFormObject, BugListItem, BugOptionListItem } from '@/models/bug-management';
import { AssociatedList, DemandItem, OperationFile } from '@/models/caseManagement/featureCase';
import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
@ -15,6 +16,14 @@ import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
export function getBugList(data: TableQueryParams) {
return MSR.post<CommonList<BugListItem>>({ url: bugURL.postTableListUrl, data });
}
/**
*
* @param data
*/
export function getCustomOptionHeader(projectId: string) {
return MSR.get<BugOptionListItem>({ url: `${bugURL.getCustomOptionHeaderUrl}${projectId}` });
}
/**
* Bug
* @param data

View File

@ -25,6 +25,7 @@ export const postCreateCommentUrl = '/bug/comment/add';
export const getCommentListUrl = '/bug/comment/get/';
export const getDeleteCommentUrl = '/bug/comment/delete/';
export const getCustomFieldHeaderUrl = '/bug/header/custom-field/';
export const getCustomOptionHeaderUrl = '/bug/header/columns-option/';
// 上传or关联文件
export const uploadOrAssociationFileUrl = '/bug/attachment/upload';
// 转存文件

View File

@ -19,6 +19,17 @@ export interface BugListItem {
deleted: boolean; // 删除标志
}
export interface BugOptionItem {
value: string; // 缺陷id
text: string; // 缺陷编号
}
export interface BugOptionListItem {
userOption: BugOptionItem[]; // 处理人下拉选项
handleUserOption: BugOptionItem[]; // 处理人下拉选项
statusOption: BugOptionItem[]; // 缺陷状态下拉选项
}
export interface BugExportColumn {
key: string; // 字段key
text?: string; // 字段名称

View File

@ -50,6 +50,82 @@
/>
</div>
</template>
<template #createUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="createUserFilterVisible"
v-model:status-filters="createUserFilterValue"
:title="(columnConfig.title as string)"
:list="createUserFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #updateUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="updateUserFilterVisible"
v-model:status-filters="updateUserFilterValue"
:title="(columnConfig.title as string)"
:list="updateUserFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #handleUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="handleUserFilterVisible"
v-model:status-filters="handleUserFilterValue"
:title="(columnConfig.title as string)"
:list="handleUserFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #statusFilter="{ columnConfig }">
<TableFilter
v-model:visible="statusFilterVisible"
v-model:status-filters="statusFilterValue"
:title="(columnConfig.title as string)"
:list="statusFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #severityFilter="{ columnConfig }">
<TableFilter
v-model:visible="severityFilterVisible"
v-model:status-filters="severityFilterValue"
:title="(columnConfig.title as string)"
:list="severityFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #empty> </template>
</MsBaseTable>
</MsCard>
@ -126,6 +202,7 @@
</template>
<script lang="ts" async setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useIntervalFn } from '@vueuse/core';
import { Message, TableData } from '@arco-design/web-vue';
@ -144,6 +221,7 @@
import BatchEditModal from './components/batchEditModal.vue';
import BugDetailDrawer from './components/bug-detail-drawer.vue';
import DeleteModal from './components/deleteModal.vue';
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
import {
deleteBatchBug,
@ -151,6 +229,7 @@
exportBug,
getBugList,
getCustomFieldHeader,
getCustomOptionHeader,
getExportConfig,
getSyncStatus,
syncBugEnterprise,
@ -168,7 +247,7 @@
tableParamsToRequestParams,
} from '@/utils';
import { BugEditCustomField, BugListItem } from '@/models/bug-management';
import { BugEditCustomField, BugListItem, BugOptionItem, BugOptionListItem } from '@/models/bug-management';
import { RouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
@ -194,6 +273,23 @@
const isXpack = computed(() => licenseStore.hasLicense());
const { openDeleteModal } = useModal();
const route = useRoute();
const createUserFilterOptions = ref<BugOptionItem[]>([]);
const createUserFilterVisible = ref(false);
const createUserFilterValue = ref<string[]>([]);
const updateUserFilterOptions = ref<BugOptionItem[]>([]);
const updateUserFilterVisible = ref(false);
const updateUserFilterValue = ref<string[]>([]);
const handleUserFilterOptions = ref<BugOptionItem[]>([]);
const handleUserFilterVisible = ref(false);
const handleUserFilterValue = ref<string[]>([]);
const statusFilterOptions = ref<BugOptionItem[]>([]);
const statusFilterVisible = ref(false);
const statusFilterValue = ref<string[]>([]);
const severityFilterOptions = ref<BugOptionItem[]>([]);
const severityFilterVisible = ref(false);
const severityFilterValue = ref<string[]>([]);
const severityColumnId = ref('');
//
const isComplete = ref(false);
//
@ -236,6 +332,21 @@
const getCustomFieldColumns = async () => {
const res = await getCustomFieldHeader(projectId.value);
customFields.value = res;
// filters
customFields.value.forEach((item) => {
if ((item.fieldName === '严重程度' || item.fieldName === 'Bug Degree') && item.options) {
severityFilterOptions.value = [];
severityColumnId.value = `custom_single_${item.fieldId}`;
item.options.forEach((option) => {
severityFilterOptions.value.push({
value: option.value,
text: option.text,
});
});
}
});
return customFieldToColumns(res);
};
@ -269,13 +380,16 @@
dataIndex: 'statusName',
width: 84,
slotName: 'status',
titleSlotName: 'statusFilter',
showDrag: true,
showInTable: true,
},
{
title: 'bugManagement.handleMan',
dataIndex: 'handleUser',
slotName: 'handleUser',
showTooltip: true,
titleSlotName: 'handleUserFilter',
width: 75,
showDrag: true,
showInTable: true,
@ -299,16 +413,18 @@
title: 'bugManagement.tag',
showDrag: true,
isStringTag: true,
width: 200,
width: 456,
dataIndex: 'tags',
showInTable: true,
},
{
title: 'bugManagement.creator',
dataIndex: 'createUser',
slotName: 'createUser',
width: 112,
showTooltip: true,
showDrag: true,
titleSlotName: 'createUserFilter',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
@ -332,6 +448,7 @@
width: 112,
showTooltip: true,
showDrag: true,
titleSlotName: 'updateUserFilter',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
@ -358,8 +475,15 @@
},
];
const customColumns = await getCustomFieldColumns();
customColumns.forEach((item) => {
item.showInTable = item.title === '严重程度';
if (item.title === '严重程度' || item.title === 'Bug Degree') {
item.showInTable = true;
item.titleSlotName = 'severityFilter';
item.slotName = 'severity';
} else {
item.showInTable = false;
}
});
await tableStore.initColumn(TableKeyEnum.BUG_MANAGEMENT, columns.concat(customColumns), 'drawer');
@ -622,10 +746,42 @@
}
}
async function initFilterOptions() {
const res = await getCustomOptionHeader(appStore.currentProjectId);
createUserFilterOptions.value = res.userOption;
updateUserFilterOptions.value = res.userOption;
handleUserFilterOptions.value = res.handleUserOption;
statusFilterOptions.value = res.statusOption;
}
function saveSort(sortObj: { [key: string]: string }) {
sort.value = sortObj;
}
async function searchData() {
// eslint-disable-next-line no-use-before-define
setLoadListParams(initTableParams());
await loadList();
}
function initTableParams() {
const filterParams = {
status: statusFilterValue.value,
handleUser: handleUserFilterValue.value,
updateUser: updateUserFilterValue.value,
createUser: createUserFilterValue.value,
};
filterParams[severityColumnId.value] = severityFilterValue.value;
return {
keyword: keyword.value,
projectId: projectId.value,
filter: { ...filterParams },
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
},
};
}
watchEffect(() => {
setProps({ heightUsed: heightUsed.value });
});
@ -638,6 +794,7 @@
onMounted(() => {
setLoadListParams({ projectId: projectId.value });
setExportOptionData();
initFilterOptions();
fetchData();
if (route.query.id) {
//