This commit is contained in:
chenjianxing 2020-08-19 15:41:24 +08:00
commit 83c21a33b5
33 changed files with 1148 additions and 945 deletions

2
backend/.gitignore vendored
View File

@ -32,4 +32,4 @@ target
.project .project
.classpath .classpath
.factorypath .factorypath
*.jar src/main/resources/jmeter/lib/

View File

@ -1,6 +1,7 @@
package io.metersphere.base.domain; package io.metersphere.base.domain;
import java.io.Serializable; import java.io.Serializable;
import lombok.Data; import lombok.Data;
@Data @Data
@ -23,5 +24,7 @@ public class Issues implements Serializable {
private String description; private String description;
private String model;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }

View File

@ -115,9 +115,10 @@
</select> </select>
<select id="list" resultType="io.metersphere.track.dto.TestPlanCaseDTO"> <select id="list" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
select test_plan_test_case.*, test_case.* select test_plan_test_case.*, test_case.*,test_case_node.name as model
from test_plan_test_case from test_plan_test_case
inner join test_case on test_plan_test_case.case_id = test_case.id inner join test_case on test_plan_test_case.case_id = test_case.id left join test_case_node on
test_case_node.id=test_case.node_id
<where> <where>
<if test="request.combine != null"> <if test="request.combine != null">
<include refid="combine"> <include refid="combine">

View File

@ -1,21 +1,14 @@
package io.metersphere.config; package io.metersphere.config;
import io.metersphere.interceptor.TestInterceptor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor());
}
@Bean @Bean
public RestTemplate restTemplate() { public RestTemplate restTemplate() {
return new RestTemplate(); return new RestTemplate();

View File

@ -1,17 +0,0 @@
package io.metersphere.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// response.setHeader("Authentication-Status", "invalid");
return true;
}
}

View File

@ -1,6 +1,7 @@
package io.metersphere.track.domain; package io.metersphere.track.domain;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.TestCaseNode; import io.metersphere.base.domain.TestCaseNode;
import io.metersphere.base.domain.TestCaseNodeExample; import io.metersphere.base.domain.TestCaseNodeExample;
import io.metersphere.base.mapper.TestCaseNodeMapper; import io.metersphere.base.mapper.TestCaseNodeMapper;
@ -8,6 +9,7 @@ import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.MathUtils; import io.metersphere.commons.utils.MathUtils;
import io.metersphere.track.dto.*; import io.metersphere.track.dto.*;
import io.metersphere.track.service.IssuesService;
import io.metersphere.track.service.TestCaseNodeService; import io.metersphere.track.service.TestCaseNodeService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -78,7 +80,9 @@ public class ReportResultComponent extends ReportComponent {
} }
private void getModuleResultMap(Map<String, Set<String>> childIdMap, Map<String, TestCaseReportModuleResultDTO> moduleResultMap, TestPlanCaseDTO testCase, List<TestCaseNodeDTO> nodeTrees) { private void getModuleResultMap(Map<String, Set<String>> childIdMap, Map<String, TestCaseReportModuleResultDTO> moduleResultMap, TestPlanCaseDTO testCase, List<TestCaseNodeDTO> nodeTrees) {
IssuesService issuesService = (IssuesService) CommonBeanFactory.getBean("issuesService");
childIdMap.forEach((rootNodeId, childIds) -> { childIdMap.forEach((rootNodeId, childIds) -> {
if (childIds.contains(testCase.getNodeId())) { if (childIds.contains(testCase.getNodeId())) {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId); TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId);
if (moduleResult == null) { if (moduleResult == null) {
@ -112,15 +116,11 @@ public class ReportResultComponent extends ReportComponent {
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Blocking.name())) { if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Blocking.name())) {
moduleResult.setBlockingCount(moduleResult.getBlockingCount() + 1); moduleResult.setBlockingCount(moduleResult.getBlockingCount() + 1);
} }
if (StringUtils.isNotBlank(testCase.getIssues())) { moduleResult.setIssuesCount(moduleResult.getIssuesCount() + issuesService.getIssues(testCase.getCaseId()).size());
if (JSON.parseObject(testCase.getIssues()).getBoolean("hasIssues")) {
moduleResult.setIssuesCount(moduleResult.getIssuesCount() + 1);
}
;
}
moduleResultMap.put(rootNodeId, moduleResult); moduleResultMap.put(rootNodeId, moduleResult);
return; return;
} }
}); });
} }
} }

View File

@ -1,5 +1,6 @@
package io.metersphere.track.dto; package io.metersphere.track.dto;
import io.metersphere.base.domain.Issues;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -12,6 +13,7 @@ public class TestCaseReportMetricDTO {
private List<TestCaseReportStatusResultDTO> executeResult; private List<TestCaseReportStatusResultDTO> executeResult;
private List<TestCaseReportModuleResultDTO> moduleExecuteResult; private List<TestCaseReportModuleResultDTO> moduleExecuteResult;
private List<TestPlanCaseDTO> failureTestCases; private List<TestPlanCaseDTO> failureTestCases;
private List<Issues> Issues;
private List<String> executors; private List<String> executors;
private String principal; private String principal;
private Long startTime; private Long startTime;

View File

@ -1,8 +1,11 @@
package io.metersphere.track.dto; package io.metersphere.track.dto;
import io.metersphere.base.domain.Issues;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
@Getter @Getter
@Setter @Setter
public class TestCaseReportModuleResultDTO { public class TestCaseReportModuleResultDTO {

View File

@ -16,4 +16,5 @@ public class TestPlanCaseDTO extends TestCaseWithBLOBs {
private String caseId; private String caseId;
private String issues; private String issues;
private String reportId; private String reportId;
private String model;
} }

View File

@ -15,6 +15,7 @@ import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.constants.TestPlanTestCaseStatus; import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.MathUtils; import io.metersphere.commons.utils.MathUtils;
import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
@ -43,7 +44,6 @@ import java.util.stream.Collectors;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public class TestPlanService { public class TestPlanService {
@Resource @Resource
TestPlanMapper testPlanMapper; TestPlanMapper testPlanMapper;
@ -252,7 +252,7 @@ public class TestPlanService {
} }
public TestCaseReportMetricDTO getMetric(String planId) { public TestCaseReportMetricDTO getMetric(String planId) {
IssuesService issuesService = (IssuesService) CommonBeanFactory.getBean("issuesService");
QueryTestPlanRequest queryTestPlanRequest = new QueryTestPlanRequest(); QueryTestPlanRequest queryTestPlanRequest = new QueryTestPlanRequest();
queryTestPlanRequest.setId(planId); queryTestPlanRequest.setId(planId);
@ -264,16 +264,25 @@ public class TestPlanService {
List<ReportComponent> components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan); List<ReportComponent> components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan);
List<TestPlanCaseDTO> testPlanTestCases = listTestCaseByPlanId(planId); List<TestPlanCaseDTO> testPlanTestCases = listTestCaseByPlanId(planId);
List<Issues> issues = new ArrayList<>();
for (TestPlanCaseDTO testCase : testPlanTestCases) { for (TestPlanCaseDTO testCase : testPlanTestCases) {
List<Issues> issue = issuesService.getIssues(testCase.getCaseId());
if (issue.size() > 0) {
for (Issues i : issue) {
i.setModel(testCase.getModel());
}
issues.addAll(issue);
}
components.forEach(component -> { components.forEach(component -> {
component.readRecord(testCase); component.readRecord(testCase);
}); });
} }
TestCaseReportMetricDTO testCaseReportMetricDTO = new TestCaseReportMetricDTO(); TestCaseReportMetricDTO testCaseReportMetricDTO = new TestCaseReportMetricDTO();
components.forEach(component -> { components.forEach(component -> {
component.afterBuild(testCaseReportMetricDTO); component.afterBuild(testCaseReportMetricDTO);
}); });
testCaseReportMetricDTO.setIssues(issues);
return testCaseReportMetricDTO; return testCaseReportMetricDTO;
} }

@ -1 +1 @@
Subproject commit b86032cbbda9a9e6028308aa95a887cff2192f1c Subproject commit 8eff343619df1572e1cded52f173257ef4b518a1

View File

@ -38,6 +38,7 @@
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0", "eslint-plugin-vue": "^5.0.0",
"file-writer": "^1.0.2",
"vue-template-compiler": "^2.6.10", "vue-template-compiler": "^2.6.10",
"vue2-ace-editor": "0.0.15" "vue2-ace-editor": "0.0.15"
}, },

View File

@ -4,9 +4,17 @@
<el-card> <el-card>
<section class="report-container" v-if="this.report.testId"> <section class="report-container" v-if="this.report.testId">
<header class="report-header"> <header class="report-header">
<el-row>
<el-col>
<span>{{ report.projectName }} / </span> <span>{{ report.projectName }} / </span>
<router-link :to="path">{{ report.testName }}</router-link> <router-link :to="path">{{ report.testName }}</router-link>
<span class="time">{{ report.createTime | timestampFormatDate }}</span> <span class="time">{{ report.createTime | timestampFormatDate }}</span>
<el-button plain type="primary" size="mini" @click="handleExport(report.name)"
style="margin-left: 1200px">
{{$t('test_track.plan_view.export_report')}}
</el-button>
</el-col>
</el-row>
</header> </header>
<main v-if="this.isNotRunning"> <main v-if="this.isNotRunning">
<ms-metric-chart :content="content" :totalTime="totalTime"/> <ms-metric-chart :content="content" :totalTime="totalTime"/>
@ -43,6 +51,8 @@
import MsMetricChart from "./components/MetricChart"; import MsMetricChart from "./components/MetricChart";
import MsScenarioResults from "./components/ScenarioResults"; import MsScenarioResults from "./components/ScenarioResults";
import {Scenario} from "../test/model/ScenarioModel"; import {Scenario} from "../test/model/ScenarioModel";
import writer from "file-writer";
import ResumeCss from "../../../../common/css/main.css";
export default { export default {
name: "MsApiReportView", name: "MsApiReportView",

View File

@ -43,7 +43,8 @@
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['40%', '70%'],
// roseType: 'angle', // roseType: 'angle',
data: this.data data: this.data,
animation: false
} }
] ]
}, },

View File

@ -22,9 +22,10 @@
@click="rerun(testId)"> @click="rerun(testId)">
{{ $t('report.test_execute_again') }} {{ $t('report.test_execute_again') }}
</el-button> </el-button>
<!--<el-button :disabled="isReadOnly" type="info" plain size="mini"> <el-button :disabled="isReadOnly" type="info" plain size="mini" @click="exports(reportName)">
{{$t('report.export')}} {{$t('report.export')}}
</el-button> </el-button>
<!--
<el-button :disabled="isReadOnly" type="warning" plain size="mini"> <el-button :disabled="isReadOnly" type="warning" plain size="mini">
{{$t('report.compare')}} {{$t('report.compare')}}
</el-button>--> </el-button>-->
@ -44,7 +45,7 @@
</el-row> </el-row>
<el-divider/> <el-divider/>
<div ref="resume">
<el-tabs v-model="active" type="border-card" :stretch="true"> <el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('load_test.pressure_config')"> <el-tab-pane :label="$t('load_test.pressure_config')">
<ms-performance-pressure-config :is-read-only="true" :report="report"/> <ms-performance-pressure-config :is-read-only="true" :report="report"/>
@ -62,6 +63,8 @@
<ms-report-log-details :report="report"/> <ms-report-log-details :report="report"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div>
</el-card> </el-card>
<el-dialog :title="$t('report.test_stop_now_confirm')" :visible.sync="dialogFormVisible" width="30%"> <el-dialog :title="$t('report.test_stop_now_confirm')" :visible.sync="dialogFormVisible" width="30%">
@ -88,6 +91,8 @@ import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer"; import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser} from "@/common/js/utils"; import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
import writer from "file-writer";
import ResumeCss from "../../../../common/css/main.css";
export default { export default {
name: "PerformanceReportView", name: "PerformanceReportView",

View File

@ -16,10 +16,22 @@
</template> </template>
<el-table border class="adjust-table" :data="tableData" style="width: 100%"> <el-table border class="adjust-table" :data="tableData" style="width: 100%">
<el-table-column prop="accessKey" label="Access Key"/> <el-table-column prop="accessKey" label="Access Key">
<template v-slot:default="scope">
<div class="variable-combine">
<div class="variable">{{ scope.row.accessKey }}</div>
<div>
<el-tooltip :content="$t('api_test.copied')" manual v-model="scope.row.visible" placement="top"
:visible-arrow="false">
<i class="el-icon-copy-document copy" @click="copy(scope.row, 'accessKey', 'visible')"/>
</el-tooltip>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="secretKey" label="Secret Key"> <el-table-column prop="secretKey" label="Secret Key">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-link type="info" @click="showSecretKey(scope.row)">{{$t('commons.show')}}</el-link> <el-link type="primary" @click="showSecretKey(scope.row)">{{ $t('commons.show') }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" :label="$t('commons.status')"> <el-table-column prop="status" :label="$t('commons.status')">
@ -46,7 +58,15 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-card> </el-card>
<el-dialog title="Secret Key" :visible.sync="apiKeysVisible">
<div class="variable">
{{ currentRow.secretKey }}
<el-tooltip :content="$t('api_test.copied')" manual v-model="currentRow.visible2" placement="top"
:visible-arrow="false">
<i class="el-icon-copy-document copy" @click="copy(currentRow, 'secretKey', 'visible2')"/>
</el-tooltip>
</div>
</el-dialog>
</div> </div>
</template> </template>
@ -67,6 +87,7 @@
apiKeysVisible: false, apiKeysVisible: false,
condition: {}, condition: {},
tableData: [], tableData: [],
currentRow: {},
} }
}, },
@ -121,12 +142,50 @@
} }
}, },
showSecretKey(row) { showSecretKey(row) {
this.$alert(row.secretKey, 'Secret Key'); this.apiKeysVisible = true;
this.currentRow = row;
},
copy(row, key, visible) {
let input = document.createElement("input");
document.body.appendChild(input);
input.value = row[key];
input.select();
if (input.setSelectionRange) {
input.setSelectionRange(0, input.value.length);
} }
document.execCommand("copy");
document.body.removeChild(input);
row[visible] = true;
setTimeout(() => {
row[visible] = false;
}, 1000);
},
} }
} }
</script> </script>
<style scoped> <style scoped>
.variable-combine {
color: #7F7F7F;
line-height: 32px;
position: absolute;
top: 10px;
margin-right: -20px;
display: flex;
align-items: center;
}
.variable {
display: inline-block;
margin-right: 10px;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.copy {
font-size: 14px;
cursor: pointer;
color: #1E90FF;
}
</style> </style>

View File

@ -1,6 +1,5 @@
<template> <template>
<div> <div v-loading="result.loading">
<el-card class="box-card" v-loading="result.loading">
<!--邮件表单--> <!--邮件表单-->
<el-form :model="formInline" :rules="rules" ref="formInline" class="demo-form-inline" <el-form :model="formInline" :rules="rules" ref="formInline" class="demo-form-inline"
:disabled="show" v-loading="loading" size="small"> :disabled="show" v-loading="loading" size="small">
@ -61,7 +60,6 @@
</el-button> </el-button>
<el-button @click="cancel" type="info" v-if="showCancel" size="small">{{ $t('commons.cancel') }}</el-button> <el-button @click="cancel" type="info" v-if="showCancel" size="small">{{ $t('commons.cancel') }}</el-button>
</div> </div>
</el-card>
</div> </div>
</template> </template>
@ -71,8 +69,7 @@
name: "EmailSetting", name: "EmailSetting",
data() { data() {
return { return {
formInline: { formInline: {},
},
input: '', input: '',
visible: true, visible: true,
result: {}, result: {},

View File

@ -1,6 +1,5 @@
<template> <template>
<div> <div v-loading="result.loading">
<el-card class="box-card" v-loading="result.loading">
<el-form :model="form" size="small" :rules="rules" :disabled="show" ref="form"> <el-form :model="form" size="small" :rules="rules" :disabled="show" ref="form">
<el-form-item :label="$t('ldap.url')" prop="url"> <el-form-item :label="$t('ldap.url')" prop="url">
<el-input v-model="form.url" :placeholder="$t('ldap.input_url_placeholder')"/> <el-input v-model="form.url" :placeholder="$t('ldap.input_url_placeholder')"/>
@ -33,7 +32,8 @@
{{ $t('ldap.test_login') }} {{ $t('ldap.test_login') }}
</el-button> </el-button>
<el-button v-if="showEdit" size="small" @click="edit">{{ $t('ldap.edit') }}</el-button> <el-button v-if="showEdit" size="small" @click="edit">{{ $t('ldap.edit') }}</el-button>
<el-button type="success" v-if="showSave" size="small" @click="save('form')">{{$t('commons.save')}}</el-button> <el-button type="success" v-if="showSave" size="small" @click="save('form')">{{ $t('commons.save') }}
</el-button>
<el-button type="info" v-if="showCancel" size="small" @click="cancel">{{ $t('commons.cancel') }}</el-button> <el-button type="info" v-if="showCancel" size="small" @click="cancel">{{ $t('commons.cancel') }}</el-button>
</div> </div>
@ -55,7 +55,6 @@
</span> </span>
</el-dialog> </el-dialog>
</el-card>
</div> </div>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <el-card>
<el-tabs class="system-setting" v-model="activeName"> <el-tabs class="system-setting" v-model="activeName">
<el-tab-pane :label="$t('system_parameter_setting.mailbox_service_settings')" name="email"> <el-tab-pane :label="$t('system_parameter_setting.mailbox_service_settings')" name="email">
<email-setting/> <email-setting/>
@ -8,12 +8,13 @@
<ldap-setting/> <ldap-setting/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </el-card>
</template> </template>
<script> <script>
import EmailSetting from "./EmailSetting"; import EmailSetting from "./EmailSetting";
import LdapSetting from "./LdapSetting"; import LdapSetting from "./LdapSetting";
export default { export default {
name: "SystemParameterSetting", name: "SystemParameterSetting",
components: { components: {

View File

@ -14,7 +14,7 @@
<el-option v-for="(type, index) in typeArr" :key="index" :value="type.id" :label="type.name"/> <el-option v-for="(type, index) in typeArr" :key="index" :value="type.id" :label="type.name"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="更新后属性值为" prop="value"> <el-form-item :label="$t('test_track.case.updated_attr_value')" prop="value">
<el-select v-model="form.value" style="width: 80%" :filterable="filterable"> <el-select v-model="form.value" style="width: 80%" :filterable="filterable">
<el-option v-for="(option, index) in options" :key="index" :value="option.id" :label="option.name"> <el-option v-for="(option, index) in options" :key="index" :value="option.id" :label="option.name">
<div v-if="option.email"> <div v-if="option.email">
@ -47,7 +47,9 @@
valueArr: Object, valueArr: Object,
dialogTitle: { dialogTitle: {
type: String, type: String,
default: "批量操作" default() {
return this.$t('test_track.case.batch_operate')
}
} }
}, },
data() { data() {
@ -56,8 +58,8 @@
form: {}, form: {},
size: 0, size: 0,
rules: { rules: {
type: {required: true, message: "请选择属性", trigger: ['blur','change']}, type: {required: true, message: this.$t('test_track.case.please_select_attr'), trigger: ['blur','change']},
value: {required: true, message: "请选择属性对应的值", trigger: ['blur','change']} value: {required: true, message: this.$t('test_track.case.please_select_attr_value'), trigger: ['blur','change']}
}, },
options: [], options: [],
filterable: false, filterable: false,

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-dialog title="选择用例目录" <el-dialog :title="this.$t('test_track.case.select_catalog')"
:visible.sync="dialogVisible" :visible.sync="dialogVisible"
:before-close="close" :before-close="close"
:destroy-on-close="true" :destroy-on-close="true"

View File

@ -121,7 +121,7 @@
</el-card> </el-card>
<batch-edit ref="batchEdit" @batchEdit="batchEdit" <batch-edit ref="batchEdit" @batchEdit="batchEdit"
:typeArr="typeArr" :value-arr="valueArr" dialog-title="批量编辑用例"/> :typeArr="typeArr" :value-arr="valueArr" :dialog-title="$t('test_track.case.batch_edit_case')"/>
</div> </div>
</template> </template>
@ -193,18 +193,18 @@
showMore: false, showMore: false,
buttons: [ buttons: [
{ {
name: '批量编辑用例', handleClick: this.handleBatchEdit name: this.$t('test_track.case.batch_edit_case'), handleClick: this.handleBatchEdit
}, { }, {
name: '批量移动用例', handleClick: this.handleBatchMove name: this.$t('test_track.case.batch_move_case'), handleClick: this.handleBatchMove
}, { }, {
name: '批量删除用例', handleClick: this.handleDeleteBatch name: this.$t('test_track.case.batch_delete_case'), handleClick: this.handleDeleteBatch
} }
], ],
typeArr: [ typeArr: [
{id: 'priority', name: '用例等级'}, {id: 'priority', name: this.$t('test_track.case.priority')},
{id: 'type', name: '类型'}, {id: 'type', name: this.$t('test_track.case.type')},
{id: 'method', name: '测试方式'}, {id: 'method', name: this.$t('test_track.case.method')},
{id: 'maintainer', name: '维护人'}, {id: 'maintainer', name: this.$t('test_track.case.maintainer')},
], ],
valueArr: { valueArr: {
priority: [ priority: [

View File

@ -181,7 +181,7 @@
:active-text="$t('test_track.plan_view.submit_issues')"> :active-text="$t('test_track.plan_view.submit_issues')">
</el-switch> </el-switch>
<el-tooltip class="item" effect="dark" <el-tooltip class="item" effect="dark"
content="在系统设置-组织-服务集成中集成缺陷管理平台可以自动提交缺陷到指定缺陷管理平台" :content="$t('test_track.issue.platform_tip')"
placement="right"> placement="right">
<i class="el-icon-info"/> <i class="el-icon-info"/>
</el-tooltip> </el-tooltip>
@ -192,7 +192,7 @@
<el-col :span="20" :offset="1" class="issues-edit"> <el-col :span="20" :offset="1" class="issues-edit">
<el-input <el-input
type="text" type="text"
placeholder="请输入标题" :placeholder="$t('test_track.issue.input_title')"
v-model="testCase.issues.title" v-model="testCase.issues.title"
maxlength="100" maxlength="100"
show-word-limit show-word-limit
@ -207,14 +207,14 @@
<el-row> <el-row>
<el-col :span="20" :offset="1" class="issues-edit"> <el-col :span="20" :offset="1" class="issues-edit">
<el-table border class="adjust-table" :data="issues" style="width: 100%"> <el-table border class="adjust-table" :data="issues" style="width: 100%">
<el-table-column prop="id" label="缺陷ID" show-overflow-tooltip/> <el-table-column prop="id" :label="$t('test_track.issue.id')" show-overflow-tooltip/>
<el-table-column prop="title" label="缺陷标题"/> <el-table-column prop="title" :label="$t('test_track.issue.title')"/>
<el-table-column prop="description" label="缺陷描述" show-overflow-tooltip/> <el-table-column prop="description" :label="$t('test_track.issue.description')" show-overflow-tooltip/>
<el-table-column prop="status" label="缺陷状态"/> <el-table-column prop="status" :label="$t('test_track.issue.status')"/>
<el-table-column prop="platform" label="平台"/> <el-table-column prop="platform" :label="$t('test_track.issue.platform')"/>
<el-table-column label="操作"> <el-table-column :label="$t('test_track.issue.operate')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-tooltip content="关闭缺陷" <el-tooltip :content="$t('test_track.issue.close')"
placement="right"> placement="right">
<el-button type="danger" icon="el-icon-circle-close" size="mini" <el-button type="danger" icon="el-icon-circle-close" size="mini"
circle v-if="scope.row.platform === 'Local'" circle v-if="scope.row.platform === 'Local'"
@ -473,7 +473,7 @@
}, },
saveIssues() { saveIssues() {
if (!this.testCase.issues.title || !this.testCase.issues.content) { if (!this.testCase.issues.title || !this.testCase.issues.content) {
this.$warning("标题和描述必填"); this.$warning(this.$t('test_track.issue.title_description_required'));
return; return;
} }
let param = {}; let param = {};
@ -494,7 +494,7 @@
closeIssue(row) { closeIssue(row) {
this.result = this.$get("/issues/close/" + row.id, () => { this.result = this.$get("/issues/close/" + row.id, () => {
this.getIssues(this.testCase.caseId); this.getIssues(this.testCase.caseId);
this.$success("关闭成功"); this.$success(this.$t('test_track.issue.close_success'));
}); });
} }
} }

View File

@ -104,7 +104,7 @@
<el-table-column <el-table-column
prop="nodePath" prop="nodePath"
label="缺陷" :label="$t('test_track.issue.issue')"
show-overflow-tooltip> show-overflow-tooltip>
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-popover <el-popover
@ -113,10 +113,10 @@
trigger="hover"> trigger="hover">
<el-table border class="adjust-table" :data="scope.row.issuesContent" style="width: 100%"> <el-table border class="adjust-table" :data="scope.row.issuesContent" style="width: 100%">
<!-- <el-table-column prop="id" label="缺陷ID" show-overflow-tooltip/>--> <!-- <el-table-column prop="id" label="缺陷ID" show-overflow-tooltip/>-->
<el-table-column prop="title" label="缺陷标题"/> <el-table-column prop="title" :label="$t('test_track.issue.title')"/>
<el-table-column prop="description" label="缺陷描述" show-overflow-tooltip/> <el-table-column prop="description" :label="$t('test_track.issue.description')" show-overflow-tooltip/>
<!-- <el-table-column prop="status" label="缺陷状态"/>--> <!-- <el-table-column prop="status" label="缺陷状态"/>-->
<el-table-column prop="platform" label="平台"/> <el-table-column prop="platform" :label="$t('test_track.issue.platform')"/>
</el-table> </el-table>
<el-button slot="reference" type="text">{{scope.row.issuesSize}}</el-button> <el-button slot="reference" type="text">{{scope.row.issuesSize}}</el-button>
</el-popover> </el-popover>
@ -195,7 +195,7 @@
<test-case-report-view @refresh="initTableData" ref="testCaseReportView"/> <test-case-report-view @refresh="initTableData" ref="testCaseReportView"/>
</el-card> </el-card>
<batch-edit ref="batchEdit" @batchEdit="batchEdit" <batch-edit ref="batchEdit" @batchEdit="batchEdit"
:type-arr="typeArr" :value-arr="valueArr" dialog-title="批量更改测试计划"/> :type-arr="typeArr" :value-arr="valueArr" :dialog-title="$t('test_track.case.batch_edit_plan')"/>
</div> </div>
</template> </template>
@ -280,15 +280,15 @@
showMore: false, showMore: false,
buttons: [ buttons: [
{ {
name: '批量更改测试计划', handleClick: this.handleBatchEdit name: this.$t('test_track.case.batch_edit_plan'), handleClick: this.handleBatchEdit
}, },
{ {
name: '批量取消用例关联', handleClick: this.handleDeleteBatch name: this.$t('test_track.case.batch_unlink'), handleClick: this.handleDeleteBatch
} }
], ],
typeArr: [ typeArr: [
{id: 'status', name: '执行结果'}, {id: 'status', name: this.$t('test_track.plan_view.execute_result')},
{id: 'executor', name: '执行人'}, {id: 'executor', name: this.$t('test_track.plan_view.executor')},
], ],
valueArr: { valueArr: {
executor: [], executor: [],

View File

@ -11,7 +11,7 @@
show-overflow-tooltip> show-overflow-tooltip>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="module" prop="model"
:label="$t('test_track.module.module')" :label="$t('test_track.module.module')"
show-overflow-tooltip> show-overflow-tooltip>
</el-table-column> </el-table-column>
@ -32,13 +32,13 @@
show-overflow-tooltip> show-overflow-tooltip>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="currentOwner" prop="reporter"
:label="$t('test_track.module.current_owner')" :label="$t('test_track.module.current_owner')"
show-overflow-tooltip> show-overflow-tooltip>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="created" prop="createTime"
:label="$t('test_track.module.creation_time')"> :label="$t('test_track.module.creation_time')">
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -67,8 +67,8 @@
title: 'testCase1', title: 'testCase1',
description: "第一个模块测试", description: "第一个模块测试",
status: "接受/处理", status: "接受/处理",
currentOwner: "Andy", reporter: "Andy",
created: "2010.3.3", createTime: "2010.3.3",
}, },
] ]
} }

View File

@ -0,0 +1,15 @@
<template>
<div>
<chart/>
</div>
</template>
<script>
export default {
name: "ExportTestCaseReport"
}
</script>
<style scoped>
</style>

View File

@ -17,7 +17,7 @@
<test-result-component :test-results="metric.moduleExecuteResult" v-if="preview.id == 2"/> <test-result-component :test-results="metric.moduleExecuteResult" v-if="preview.id == 2"/>
<test-result-chart-component :execute-result="metric.executeResult" v-if="preview.id == 3"/> <test-result-chart-component :execute-result="metric.executeResult" v-if="preview.id == 3"/>
<failure-result-component :failure-test-cases="metric.failureTestCases" v-if="preview.id == 4"/> <failure-result-component :failure-test-cases="metric.failureTestCases" v-if="preview.id == 4"/>
<defect-list-component :defect-list="metric.defectList" v-if="preview.id == 5"/> <defect-list-component :defect-list="metric.issues" v-if="preview.id == 5"/>
<rich-text-component :is-report-view="isReportView" :preview="preview" v-if="preview.type != 'system'"/> <rich-text-component :is-report-view="isReportView" :preview="preview" v-if="preview.type != 'system'"/>
</div> </div>

View File

@ -66,6 +66,8 @@
this.charData.push(data); this.charData.push(data);
}); });
this.reload(); this.reload();
}, },
reload() { reload() {
this.isShow = false; this.isShow = false;

View File

@ -13,18 +13,25 @@
<el-row type="flex" class="head-bar"> <el-row type="flex" class="head-bar">
<el-col :span="12"> <el-col :span="12">
<div class="name-edit"> <div class="name-edit">
<el-button plain size="mini" icon="el-icon-back" @click="handleClose">{{$t('test_track.return')}}</el-button> <el-button plain size="mini" icon="el-icon-back" @click="handleClose">{{$t('test_track.return')}}
&nbsp; </el-button>&nbsp;
<span class="title">{{report.name}}</span> <span class="title">{{report.name}}</span>
</div> </div>
</el-col> </el-col>
<el-col :span="12" class="head-right"> <el-col :span="12" class="head-right">
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleSave">{{$t('commons.save')}}</el-button> <el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleSave">
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleEdit">{{$t('test_track.plan_view.edit_component')}}</el-button> {{$t('commons.save')}}
</el-button>
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleEdit">
{{$t('test_track.plan_view.edit_component')}}
</el-button>
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleExport(report.name)">
{{$t('test_track.plan_view.export_report')}}
</el-button>
</el-col> </el-col>
</el-row> </el-row>
<div class="container"> <div class="container" ref="resume">
<el-main> <el-main>
<div class="preview" v-for="item in previews" :key="item.id"> <div class="preview" v-for="item in previews" :key="item.id">
<template-component :isReportView="true" :metric="metric" :preview="item"/> <template-component :isReportView="true" :metric="metric" :preview="item"/>
@ -47,16 +54,20 @@
import RichTextComponent from "./TemplateComponent/RichTextComponent"; import RichTextComponent from "./TemplateComponent/RichTextComponent";
import TestCaseReportTemplateEdit from "./TestCaseReportTemplateEdit"; import TestCaseReportTemplateEdit from "./TestCaseReportTemplateEdit";
import TemplateComponent from "./TemplateComponent/TemplateComponent"; import TemplateComponent from "./TemplateComponent/TemplateComponent";
import writer from 'file-writer'
import ResumeCss from "../../../../../../../common/css/main.css";
export default { export default {
name: "TestCaseReportView", name: "TestCaseReportView",
components: { components: {
TemplateComponent, TemplateComponent, ResumeCss,
TestCaseReportTemplateEdit, TestCaseReportTemplateEdit,
RichTextComponent, TestResultComponent, TestResultChartComponent, BaseInfoComponent}, RichTextComponent, TestResultComponent, TestResultChartComponent, BaseInfoComponent
},
data() { data() {
return { return {
result: {}, result: {},
imgUrl: "",
showDialog: false, showDialog: false,
previews: [], previews: [],
report: {}, report: {},
@ -134,6 +145,31 @@
handleEdit() { handleEdit() {
this.$refs.templateEdit.open(this.reportId, true); this.$refs.templateEdit.open(this.reportId, true);
}, },
/*导出报告*/
/*handleExport(name) {
let html = this.getHtml();
writer(`${name}.html`, html, 'utf-8');
},
getHtml() {
const template = this.$refs.resume.innerHTML
let html = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>html</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>${ResumeCss}</style>
</head>
<body>
<div style="margin:0 auto;width:1200px">
<img id ='div' :src = "imgUrl" :style=" {width: '1000px', height: '500px'}" />
${template}
</div>
</body>
</html>`
return html
},*/
handleSave() { handleSave() {
let param = {}; let param = {};
this.buildParam(param); this.buildParam(param);
@ -166,7 +202,8 @@
}, },
getMetric() { getMetric() {
this.result = this.$get('/test/plan/get/metric/' + this.planId, response => { this.result = this.$get('/test/plan/get/metric/' + this.planId, response => {
this.metric = response.data; this.metric = response.data
if (!this.metric.failureTestCases) { if (!this.metric.failureTestCases) {
this.metric.failureTestCases = []; this.metric.failureTestCases = [];
} }
@ -177,7 +214,10 @@
this.metric.moduleExecuteResult = []; this.metric.moduleExecuteResult = [];
} }
/*缺陷列表*/ /*缺陷列表*/
this.metric.defectList = []; if (!this.metric.issues) {
this.metric.issues = [];
}
if (this.report.startTime) { if (this.report.startTime) {
this.metric.startTime = new Date(this.report.startTime); this.metric.startTime = new Date(this.report.startTime);
@ -186,7 +226,8 @@
this.metric.endTime = new Date(this.report.endTime); this.metric.endTime = new Date(this.report.endTime);
} }
}); });
} },
} }
} }
</script> </script>

@ -1 +1 @@
Subproject commit 7e4d80cc2b870a8cac6dbb9fe6711ab6041faf6d Subproject commit 06fc0a321a9886419be5c607ddaa6b40efb5179b

View File

@ -589,6 +589,16 @@ export default {
relate_test_not_find: 'The associated test does not exist, please check the test case', relate_test_not_find: 'The associated test does not exist, please check the test case',
batch_handle: 'Batch processing (select {0} item)', batch_handle: 'Batch processing (select {0} item)',
batch_update: 'Update the attributes of {0} cases', batch_update: 'Update the attributes of {0} cases',
select_catalog: 'Please select use case catalog',
updated_attr_value: 'The updated attribute value',
batch_operate: 'Batch operation',
please_select_attr: 'Please select attributes',
please_select_attr_value: 'Please select the value corresponding to the attribute',
batch_edit_plan: 'Batch change test plan',
batch_edit_case: 'Batch editing test cases',
batch_move_case: 'Batch move',
batch_delete_case: 'Batch delete',
batch_unlink: 'Batch Unlink',
import: { import: {
import: "Import test case", import: "Import test case",
case_import: "Import test case", case_import: "Import test case",
@ -698,6 +708,21 @@ export default {
report_template: "Report template", report_template: "Report template",
test_detail: "Test detail", test_detail: "Test detail",
failure_case: "Failure case", failure_case: "Failure case",
export_report: "Export Report"
},
issue: {
issue: "Issue",
platform_tip: "Integrated defect management platform in the system setting-organization-service integration can automatically submit defects to the designated defect management platform",
input_title: "Please enter title",
id: "Issue ID",
title: "Issue Title",
description: "Issue Description",
status: "Issue Status",
platform: "Platform",
operate: "Operate",
close: "Close",
title_description_required: "Title and description are required",
close_success: "Closed successfully",
} }
}, },
test_resource_pool: { test_resource_pool: {

View File

@ -592,6 +592,16 @@ export default {
relate_test_not_find: '关联的测试不存在,请检查用例', relate_test_not_find: '关联的测试不存在,请检查用例',
batch_handle: '批量处理 (选中{0}项)', batch_handle: '批量处理 (选中{0}项)',
batch_update: '更新{0}个用例的属性', batch_update: '更新{0}个用例的属性',
select_catalog: '请选择用例目录',
updated_attr_value: '更新后属性值为',
batch_operate: '批量操作',
please_select_attr: '请选择属性',
please_select_attr_value: '请选择属性对应的值',
batch_edit_plan: '批量更改测试计划',
batch_edit_case: '批量编辑用例',
batch_move_case: '批量移动用例',
batch_delete_case: '批量删除用例',
batch_unlink: '批量取消用例关联',
import: { import: {
import: "导入用例", import: "导入用例",
case_import: "导入测试用例", case_import: "导入测试用例",
@ -701,6 +711,21 @@ export default {
report_template: "测试报告模版", report_template: "测试报告模版",
test_detail: "测试详情", test_detail: "测试详情",
failure_case: "失败用例", failure_case: "失败用例",
export_report: "导出报告"
},
issue: {
issue: "缺陷",
platform_tip: "在系统设置-组织-服务集成中集成缺陷管理平台可以自动提交缺陷到指定缺陷管理平台",
input_title: "请输入标题",
id: "缺陷ID",
title: "缺陷标题",
description: "缺陷描述",
status: "缺陷状态",
platform: "平台",
operate: "操作",
close: "关闭缺陷",
title_description_required: "标题和描述必填",
close_success: "关闭成功",
} }
}, },
test_resource_pool: { test_resource_pool: {

View File

@ -589,6 +589,16 @@ export default {
relate_test_not_find: '關聯的測試不存在,請檢查用例', relate_test_not_find: '關聯的測試不存在,請檢查用例',
batch_handle: '批量處理 (選中{0}項)', batch_handle: '批量處理 (選中{0}項)',
batch_update: '更新{0}個用例的屬性', batch_update: '更新{0}個用例的屬性',
select_catalog: '請選擇用例目錄',
updated_attr_value: '更新後屬性值為',
batch_operate: '批量操作',
please_select_attr: '請選擇屬性',
please_select_attr_value: '請選擇屬性對應的值',
batch_edit_plan: '批量更改測試計劃',
batch_edit_case: '批量編輯用例',
batch_move_case: '批量移動用例',
batch_delete_case: '批量刪除用例',
batch_unlink: '批量取消用例關聯',
import: { import: {
import: "導入用例", import: "導入用例",
case_import: "導入測試用例", case_import: "導入測試用例",
@ -698,6 +708,21 @@ export default {
report_template: "測試報告模版", report_template: "測試報告模版",
test_detail: "測試詳情", test_detail: "測試詳情",
failure_case: "失敗用例", failure_case: "失敗用例",
export_report: "匯出報告"
},
issue: {
issue: "缺陷",
platform_tip: "在系統設置-組織-服務集成中集成缺陷管理平台可以自動提交缺陷到指定缺陷管理平台",
input_title: "請輸入標題",
id: "缺陷ID",
title: "缺陷標題",
description: "缺陷描述",
status: "缺陷狀態",
platform: "平台",
operate: "操作",
close: "關閉缺陷",
title_description_required: "標題和描述必填",
close_success: "關閉成功",
} }
}, },
test_resource_pool: { test_resource_pool: {