feat(测试计划): 新增批量取消关联用例的接口
This commit is contained in:
parent
472e7ae5eb
commit
eae3398f94
|
@ -1,10 +1,9 @@
|
|||
package io.metersphere.plan.controller;
|
||||
|
||||
import com.alibaba.excel.util.StringUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.functional.request.ReviewFunctionalCasePageRequest;
|
||||
import io.metersphere.plan.constants.TestPlanResourceConfig;
|
||||
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
|
||||
import io.metersphere.plan.dto.request.ResourceSortRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
|
||||
|
@ -86,4 +85,13 @@ public class TestPlanFunctionalCaseController {
|
|||
public Map<String, Long> moduleCount(@Validated @RequestBody TestPlanCaseRequest request) {
|
||||
return testPlanFunctionalCaseService.moduleCount(request);
|
||||
}
|
||||
@PostMapping("/batch/disassociate")
|
||||
@Operation(summary = "测试计划-计划详情-列表-批量取消关联用例")
|
||||
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_ASSOCIATION)
|
||||
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
|
||||
public TestPlanAssociationResponse batchDisassociate(@Validated @RequestBody BasePlanCaseBatchRequest request) {
|
||||
testPlanManagementService.checkModuleIsOpen(request.getTestPlanId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN_FUNCTIONAL_CASE));
|
||||
return testPlanFunctionalCaseService.disassociate(request, new LogInsertModule(SessionUtils.getUserId(), "/test-plan/functional/case/association", HttpMethodConstants.POST.name()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package io.metersphere.plan.dto.request;
|
||||
|
||||
import io.metersphere.system.dto.table.TableBatchProcessDTO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class BasePlanCaseBatchRequest extends TableBatchProcessDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "测试计划id", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{test_plan.id.not_blank}")
|
||||
private String testPlanId;
|
||||
|
||||
@Schema(description = "模块id")
|
||||
private List<String> moduleIds;
|
||||
}
|
|
@ -5,6 +5,7 @@ import io.metersphere.functional.dto.FunctionalCaseModuleDTO;
|
|||
import io.metersphere.functional.dto.ProjectOptionDTO;
|
||||
import io.metersphere.plan.dto.AssociationNode;
|
||||
import io.metersphere.plan.dto.ResourceSelectParam;
|
||||
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
|
||||
import io.metersphere.plan.dto.response.TestPlanCasePageResponse;
|
||||
import io.metersphere.project.dto.NodeSortQueryParam;
|
||||
|
@ -35,4 +36,6 @@ public interface ExtTestPlanFunctionalCaseMapper {
|
|||
List<FunctionalCaseModuleCountDTO> countModuleIdByRequest(@Param("request") TestPlanCaseRequest request, @Param("deleted") boolean deleted);
|
||||
|
||||
long caseCount(@Param("request") TestPlanCaseRequest request, @Param("deleted") boolean deleted);
|
||||
|
||||
List<String> getIds(@Param("request") BasePlanCaseBatchRequest request, @Param("deleted") boolean deleted);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,19 @@
|
|||
ORDER BY #{orderString}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="getIds" resultType="java.lang.String">
|
||||
SELECT
|
||||
tpfc.id as id
|
||||
FROM
|
||||
test_plan_functional_case tpfc
|
||||
LEFT JOIN functional_case ON tpfc.functional_case_id = functional_case.id
|
||||
WHERE
|
||||
tpfc.test_plan_id = #{request.testPlanId}
|
||||
AND functional_case.deleted = #{deleted}
|
||||
<include refid="queryWhereConditionByBatchQueryRequest"/>
|
||||
</select>
|
||||
|
||||
<select id="selectDragInfoById" resultType="io.metersphere.plan.dto.AssociationNode">
|
||||
SELECT id, pos
|
||||
FROM test_plan_functional_case
|
||||
|
@ -138,6 +151,35 @@
|
|||
</include>
|
||||
</sql>
|
||||
|
||||
<sql id="queryWhereConditionByBatchQueryRequest">
|
||||
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
|
||||
and functional_case.module_id in
|
||||
<foreach collection="request.moduleIds" item="moduleId" separator="," open="(" close=")">
|
||||
#{moduleId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="request.condition.keyword != null">
|
||||
and (
|
||||
functional_case.name like concat('%', #{request.keyword},'%')
|
||||
or functional_case.num like concat('%', #{request.keyword},'%')
|
||||
or functional_case.tags like concat('%', #{request.keyword},'%')
|
||||
)
|
||||
</if>
|
||||
<include refid="filters">
|
||||
<property name="filter" value="request.condition.filter"/>
|
||||
</include>
|
||||
<choose>
|
||||
<when test='request.condition.searchMode == "AND"'>
|
||||
AND <include refid="batchQueryCombine"/>
|
||||
</when>
|
||||
<when test='request.condition.searchMode == "OR"'>
|
||||
and (
|
||||
<include refid="batchQueryCombine"/>
|
||||
)
|
||||
</when>
|
||||
</choose>
|
||||
</sql>
|
||||
|
||||
<sql id="filters">
|
||||
<if test="${filter} != null and ${filter}.size() > 0">
|
||||
<foreach collection="${filter}.entrySet()" index="key" item="values">
|
||||
|
@ -202,6 +244,16 @@
|
|||
</if>
|
||||
</sql>
|
||||
|
||||
<sql id="batchQueryCombine">
|
||||
<if test="request.condition.combine != null">
|
||||
<include refid="combine">
|
||||
<property name="condition" value="request.condition.combine"/>
|
||||
<property name="searchMode" value="request.condition.searchMode"/>
|
||||
</include>
|
||||
</if>
|
||||
1=1
|
||||
</sql>
|
||||
|
||||
<sql id="queryCombine">
|
||||
<if test="request.combine != null">
|
||||
<include refid="combine">
|
||||
|
|
|
@ -2,10 +2,10 @@ package io.metersphere.plan.service;
|
|||
|
||||
import io.metersphere.bug.dto.CaseRelateBugDTO;
|
||||
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
|
||||
import io.metersphere.functional.domain.FunctionalCaseModule;
|
||||
import io.metersphere.functional.dto.FunctionalCaseCustomFieldDTO;
|
||||
import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO;
|
||||
import io.metersphere.functional.dto.FunctionalCaseModuleDTO;
|
||||
import io.metersphere.functional.domain.FunctionalCaseModule;
|
||||
import io.metersphere.functional.dto.ProjectOptionDTO;
|
||||
import io.metersphere.functional.service.FunctionalCaseService;
|
||||
import io.metersphere.plan.domain.TestPlan;
|
||||
|
@ -13,6 +13,8 @@ import io.metersphere.plan.domain.TestPlanFunctionalCase;
|
|||
import io.metersphere.plan.domain.TestPlanFunctionalCaseExample;
|
||||
import io.metersphere.plan.dto.AssociationNodeSortDTO;
|
||||
import io.metersphere.plan.dto.ResourceLogInsertModule;
|
||||
import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
|
||||
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
|
||||
import io.metersphere.plan.dto.request.ResourceSortRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
|
||||
|
@ -42,6 +44,7 @@ import org.apache.ibatis.session.SqlSessionFactory;
|
|||
import org.mybatis.spring.SqlSessionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -120,6 +123,13 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
|
|||
}
|
||||
|
||||
|
||||
public void deleteTestPlanResource(@Validated TestPlanResourceAssociationParam associationParam) {
|
||||
TestPlanFunctionalCaseExample testPlanFunctionalCaseExample = new TestPlanFunctionalCaseExample();
|
||||
testPlanFunctionalCaseExample.createCriteria().andIdIn(associationParam.getResourceIdList());
|
||||
testPlanFunctionalCaseMapper.deleteByExample(testPlanFunctionalCaseExample);
|
||||
//TODO:更新执行历史的删除状态为true
|
||||
}
|
||||
|
||||
public TestPlanResourceSortResponse sortNode(ResourceSortRequest request, LogInsertModule logInsertModule) {
|
||||
TestPlanFunctionalCase dragNode = testPlanFunctionalCaseMapper.selectByPrimaryKey(request.getDragNodeId());
|
||||
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestPlanId());
|
||||
|
@ -255,4 +265,26 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
|
|||
|
||||
|
||||
}
|
||||
|
||||
public TestPlanAssociationResponse disassociate(BasePlanCaseBatchRequest request, LogInsertModule logInsertModule) {
|
||||
List<String> selectIds = doSelectIds(request);
|
||||
return super.disassociate(
|
||||
TestPlanResourceConstants.RESOURCE_FUNCTIONAL_CASE,
|
||||
request,
|
||||
logInsertModule,
|
||||
selectIds,
|
||||
this::deleteTestPlanResource);
|
||||
}
|
||||
|
||||
private List<String> doSelectIds(BasePlanCaseBatchRequest request) {
|
||||
if (request.isSelectAll()) {
|
||||
List<String> ids = extTestPlanFunctionalCaseMapper.getIds(request, false);
|
||||
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
|
||||
ids.removeAll(request.getExcludeIds());
|
||||
}
|
||||
return ids;
|
||||
} else {
|
||||
return request.getSelectIds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.plan.service;
|
|||
import io.metersphere.plan.domain.TestPlan;
|
||||
import io.metersphere.plan.domain.TestPlanConfig;
|
||||
import io.metersphere.plan.dto.*;
|
||||
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
|
||||
import io.metersphere.plan.dto.request.ResourceSortRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
|
||||
import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
|
||||
|
@ -86,6 +87,29 @@ public abstract class TestPlanResourceService {
|
|||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消关联资源od
|
||||
*
|
||||
* @return TestPlanAssociationResponse
|
||||
*/
|
||||
public TestPlanAssociationResponse disassociate(
|
||||
String resourceType,
|
||||
BasePlanCaseBatchRequest request,
|
||||
@Validated LogInsertModule logInsertModule,
|
||||
List<String>associationIdList,
|
||||
Consumer<TestPlanResourceAssociationParam> disassociate) {
|
||||
TestPlanAssociationResponse response = new TestPlanAssociationResponse();
|
||||
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestPlanId());
|
||||
//获取有效ID
|
||||
if (CollectionUtils.isNotEmpty(associationIdList)) {
|
||||
TestPlanResourceAssociationParam associationParam = new TestPlanResourceAssociationParam(associationIdList, testPlan.getProjectId(), testPlan.getId(), testPlan.getNum(), logInsertModule.getOperator());
|
||||
disassociate.accept(associationParam);
|
||||
response.setAssociationCount(associationIdList.size());
|
||||
testPlanResourceLogService.saveDeleteLog(testPlan, new ResourceLogInsertModule(resourceType, logInsertModule));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建节点排序的参数
|
||||
*
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package io.metersphere.plan.controller;
|
||||
|
||||
import io.metersphere.plan.domain.TestPlanFunctionalCase;
|
||||
import io.metersphere.plan.domain.TestPlanFunctionalCaseExample;
|
||||
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
|
||||
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
|
||||
import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import io.metersphere.system.base.BaseTest;
|
||||
import io.metersphere.system.controller.handler.ResultHolder;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
@ -13,6 +18,7 @@ import org.springframework.test.web.servlet.MvcResult;
|
|||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
|
@ -23,6 +29,11 @@ public class TestPlanCaseControllerTests extends BaseTest {
|
|||
public static final String FUNCTIONAL_CASE_TREE_URL = "/test-plan/functional/case/tree/";
|
||||
public static final String FUNCTIONAL_CASE_TREE_COUNT_URL = "/test-plan/functional/case/module/count";
|
||||
|
||||
public static final String FUNCTIONAL_CASE_DISASSOCIATE_URL = "/test-plan/functional/case/batch/disassociate";
|
||||
|
||||
@Resource
|
||||
private TestPlanFunctionalCaseMapper testPlanFunctionalCaseMapper;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
@Sql(scripts = {"/dml/init_test_plan_case_relate_bug.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
|
||||
|
@ -69,4 +80,25 @@ public class TestPlanCaseControllerTests extends BaseTest {
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void disassociateBatch() throws Exception {
|
||||
BasePlanCaseBatchRequest request = new BasePlanCaseBatchRequest();
|
||||
request.setTestPlanId("gyq_disassociate_plan_1");
|
||||
request.setSelectAll(true);
|
||||
request.setExcludeIds(List.of("gyq_disassociate_case_2"));
|
||||
this.requestPostWithOk(FUNCTIONAL_CASE_DISASSOCIATE_URL, request);
|
||||
TestPlanFunctionalCaseExample testPlanFunctionalCaseExample = new TestPlanFunctionalCaseExample();
|
||||
testPlanFunctionalCaseExample.createCriteria().andTestPlanIdEqualTo("gyq_disassociate_plan_1");
|
||||
List<TestPlanFunctionalCase> testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(testPlanFunctionalCaseExample);
|
||||
Assertions.assertEquals(1,testPlanFunctionalCases.size());
|
||||
request = new BasePlanCaseBatchRequest();
|
||||
request.setTestPlanId("gyq_disassociate_plan_1");
|
||||
request.setSelectAll(false);
|
||||
request.setSelectIds(List.of("gyq_disassociate_case_2"));
|
||||
this.requestPostWithOk(FUNCTIONAL_CASE_DISASSOCIATE_URL, request);
|
||||
testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(testPlanFunctionalCaseExample);
|
||||
Assertions.assertEquals(0,testPlanFunctionalCases.size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,23 +9,33 @@ VALUES ('bug_1', 100001, 'oasis', 'admin', 'admin', 'admin', UNIX_TIMESTAMP() *
|
|||
INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`)
|
||||
VALUES
|
||||
('plan_1', 20000, '123', 'wx_test_plan_id_3', 't_1', '测试关联', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
|
||||
('plan_2', 25000, '1234', 'wx_test_plan_id_3', 'root', '测试关联1', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
|
||||
('plan_2', 25000, '1234', 'wx_test_plan_id_3', 'root', '测试关联1', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
|
||||
('gyq_disassociate_plan_1', 25000, 'gyq_disassociate', 'wx_test_plan_id_3', 'root', '测试取消关联1', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
|
||||
|
||||
INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`)
|
||||
VALUES
|
||||
('relate_case_1', 'plan_1', 'fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1),
|
||||
('relate_case_2', 'plan_1', 'fc_2', 1714980158000, 'admin', NULL, NULL, NULL, 1),
|
||||
('relate_case_3', 'plan_2', 'fc_3', 1714980158000, 'admin', NULL, NULL, NULL, 1);
|
||||
('relate_case_3', 'plan_2', 'fc_3', 1714980158000, 'admin', NULL, NULL, NULL, 1),
|
||||
('gyq_disassociate_case_1', 'gyq_disassociate_plan_1', 'gyq_disassociate_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1),
|
||||
('gyq_disassociate_case_2', 'gyq_disassociate_plan_1', 'gyq_disassociate_fc_2', 1714980158000, 'admin', NULL, NULL, NULL, 1);
|
||||
|
||||
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
|
||||
VALUES
|
||||
('fc_1', 1, 't_1', '123', '100001', '111', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
||||
('fc_2', 2, 't_1', '123', '100001', '222', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
||||
('fc_3', 3, 'root', '123', '100001', '333', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
('fc_3', 3, 'root', '123', '100001', '333', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
||||
('gyq_disassociate_fc_1', 4, 'root', 'gyq_disassociate', '100001', 'disassociate1', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'gyq_disassociate_fc_1', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
||||
('gyq_disassociate_fc_2', 5, 'root', 'gyq_disassociate', '100001', 'disassociate2', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'gyq_disassociate_fc_2', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
|
||||
INSERT INTO project_version(id, project_id, name, description, status, latest, publish_time, start_time, end_time, create_time, create_user)
|
||||
values ('v3.0.0','123','v3', null, 'open', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin');
|
||||
|
||||
INSERT INTO project(id, num, organization_id, name, description, create_time, update_time, update_user, create_user, delete_time, deleted, delete_user, enable, module_setting)
|
||||
VALUE ('gyq_disassociate', null, 'organization_plan_associate_case_project', 'test_plan_associate_case_name', null,
|
||||
UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'admin', null, false, null, true, '["bugManagement","caseManagement","apiTest","testPlan"]');
|
||||
|
||||
|
||||
INSERT INTO `test_plan_module`(`id`, `project_id`, `name`, `parent_id`, `pos`, `create_time`, `update_time`, `create_user`, `update_user`)
|
||||
VALUES
|
||||
|
|
Loading…
Reference in New Issue