feat(接口测试): 接口报告分享

--user=郭雨琦 接口报告分享
This commit is contained in:
guoyuqi 2022-03-01 20:16:07 +08:00 committed by xiaomeinvG
parent 265893012f
commit ea5a4ca0ae
20 changed files with 248 additions and 13 deletions

View File

@ -3,9 +3,11 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.automation.APIScenarioReportResult;
import io.metersphere.api.dto.share.*; import io.metersphere.api.dto.share.*;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper;
import io.metersphere.base.mapper.ext.ExtShareInfoMapper; import io.metersphere.base.mapper.ext.ExtShareInfoMapper;
import io.metersphere.commons.constants.ProjectApplicationType; import io.metersphere.commons.constants.ProjectApplicationType;
import io.metersphere.commons.constants.ShareType; import io.metersphere.commons.constants.ShareType;
@ -61,6 +63,9 @@ public class ShareInfoService {
@Lazy @Lazy
@Resource @Resource
TestPlanMapper testPlanMapper; TestPlanMapper testPlanMapper;
@Resource
private ExtApiScenarioReportMapper extApiScenarioReportMapper;
public List<ApiDocumentInfoDTO> findApiDocumentSimpleInfoByRequest(ApiDocumentRequest request) { public List<ApiDocumentInfoDTO> findApiDocumentSimpleInfoByRequest(ApiDocumentRequest request) {
if (this.isParamLegitimacy(request)) { if (this.isParamLegitimacy(request)) {
@ -547,6 +552,14 @@ public class ShareInfoService {
projectId = testPlan.getProjectId(); projectId = testPlan.getProjectId();
}; };
}
if(shareInfo.getShareType().equals("API_REPORT")){
type = ProjectApplicationType.API_SHARE_REPORT_TIME.toString();
APIScenarioReportResult reportResult = extApiScenarioReportMapper.get(shareInfo.getCustomData());
if (reportResult != null){
projectId = reportResult.getProjectId();
};
} }
if(StringUtils.isBlank(type)|| Strings.isBlank(projectId)){ if(StringUtils.isBlank(type)|| Strings.isBlank(projectId)){
millisCheck(System.currentTimeMillis() - shareInfo.getUpdateTime() ,1000 * 60 * 60 * 24,shareInfo.getId()); millisCheck(System.currentTimeMillis() - shareInfo.getUpdateTime() ,1000 * 60 * 60 * 24,shareInfo.getId());

View File

@ -2,5 +2,6 @@ package io.metersphere.commons.constants;
public enum ProjectApplicationType { public enum ProjectApplicationType {
TRACK_SHARE_REPORT_TIME, TRACK_SHARE_REPORT_TIME,
PERFORMANCE_SHARE_REPORT_TIME PERFORMANCE_SHARE_REPORT_TIME,
API_SHARE_REPORT_TIME
} }

View File

@ -61,6 +61,7 @@ public class ShiroUtils {
filterChainDefinitionMap.put("/share/**", "anon"); filterChainDefinitionMap.put("/share/**", "anon");
filterChainDefinitionMap.put("/sharePlanReport", "anon"); filterChainDefinitionMap.put("/sharePlanReport", "anon");
filterChainDefinitionMap.put("/sharePerformanceReport", "anon"); filterChainDefinitionMap.put("/sharePerformanceReport", "anon");
filterChainDefinitionMap.put("/shareApiReport", "anon");
filterChainDefinitionMap.put("/system/theme", "anon"); filterChainDefinitionMap.put("/system/theme", "anon");
filterChainDefinitionMap.put("/system/save/baseurl/**", "anon"); filterChainDefinitionMap.put("/system/save/baseurl/**", "anon");

View File

@ -46,4 +46,9 @@ public class IndexController {
public String sharePerformanceRedirect() { public String sharePerformanceRedirect() {
return "share-performance-report.html"; return "share-performance-report.html";
} }
@GetMapping(value = "/shareApiReport")
public String shareApiRedirect() {
return "share-api-report.html";
}
} }

View File

@ -157,7 +157,7 @@ public class ShareController {
@GetMapping("/api/scenario/report/get/{shareId}/{reportId}") @GetMapping("/api/scenario/report/get/{shareId}/{reportId}")
public APIScenarioReportResult get(@PathVariable String shareId, @PathVariable String reportId) { public APIScenarioReportResult get(@PathVariable String shareId, @PathVariable String reportId) {
// shareInfoService.scenarioReportValidate(shareId, reportId); shareInfoService.validate(shareId, reportId);
return apiScenarioReportService.get(reportId); return apiScenarioReportService.get(reportId);
} }

View File

@ -0,0 +1,37 @@
DROP PROCEDURE IF EXISTS project_api_appl;
DELIMITER //
CREATE PROCEDURE project_api_appl()
BEGIN
#声明结束标识
DECLARE end_flag int DEFAULT 0;
DECLARE projectId varchar(64);
#声明游标 group_curosr
DECLARE project_curosr CURSOR FOR SELECT DISTINCT id FROM project;
#设置终止标志
DECLARE CONTINUE HANDLER FOR NOT FOUND SET end_flag=1;
#打开游标
OPEN project_curosr;
#获取当前游标指针记录,取出值赋给自定义的变量
FETCH project_curosr INTO projectId;
#遍历游标
REPEAT
#利用取到的值进行数据库的操作
INSERT INTO project_application (project_id, type, type_value)
VALUES (projectId, 'API_SHARE_REPORT_TIME', '24H');
# 将游标中的值再赋值给变量,供下次循环使用
FETCH project_curosr INTO projectId;
UNTIL end_flag END REPEAT;
#关闭游标
close project_curosr;
END
//
DELIMITER ;
CALL project_api_appl();
DROP PROCEDURE IF EXISTS project_api_appl;

View File

@ -1,5 +1,5 @@
<template> <template>
<ms-api-report :report-id="reportId"></ms-api-report> <ms-api-report :report-id="reportIdValue" :share-id="shareId" :is-share="isShare" :is-plan="isPlanReport" :is-template="false"></ms-api-report>
</template> </template>
<script> <script>
@ -7,13 +7,30 @@ import MsApiReport from "@/business/components/api/automation/report/ApiReportDe
export default { export default {
name: "ApiReportView", name: "ApiReportView",
components: {MsApiReport}, components: {MsApiReport},
props: {
reportId:String,
isShare: Boolean,
shareId: String,
isPlanReport: Boolean,
},
computed: { computed: {
reportId:function (){ reportIdByPath:function (){
if(this.$route){
return this.$route.params.reportId return this.$route.params.reportId
}else{
return null;
}
}
},
data(){
return {
reportIdValue:'',
} }
}, },
created() { created() {
console.log(this.reportId) if(!this.reportIdValue){
this.reportIdValue = this.reportIdByPath ? this.reportIdByPath:this.reportId;
}
} }
} }

View File

@ -23,9 +23,30 @@
<el-button v-if="!isPlan && (!debug || exportFlag) && !isTemplate" v-permission="['PROJECT_API_REPORT:READ+EXPORT']" :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px"> <el-button v-if="!isPlan && (!debug || exportFlag) && !isTemplate" v-permission="['PROJECT_API_REPORT:READ+EXPORT']" :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px">
{{ $t('test_track.plan_view.export_report') }} {{ $t('test_track.plan_view.export_report') }}
</el-button> </el-button>
<el-popover
v-if="!isPlan && (!debug || exportFlag) && !isTemplate"
v-permission="['PROJECT_PERFORMANCE_REPORT:READ+EXPORT']"
style="margin-right: 10px;float: right;"
placement="bottom"
width="300">
<p>{{ shareUrl }}</p>
<span style="color: red;float: left;margin-left: 10px;" v-if="application.typeValue">{{
$t('commons.validity_period')+application.typeValue
}}</span>
<div style="text-align: right; margin: 0">
<el-button type="primary" size="mini" :disabled="!shareUrl"
v-clipboard:copy="shareUrl">{{ $t("commons.copy") }}
</el-button>
</div>
<el-button slot="reference" :disabled="isReadOnly" type="danger" plain size="mini"
@click="handleShare(report)">
{{ $t('test_track.plan_view.share_report') }}
</el-button>
</el-popover>
<el-button v-if="showCancelButton" class="export-button" plain size="mini" @click="returnView()" > <el-button v-if="showCancelButton" class="export-button" plain size="mini" @click="returnView()" >
{{$t('commons.cancel')}} {{$t('commons.cancel')}}
</el-button> </el-button>
</el-col> </el-col>
</el-row> </el-row>
</header> </header>
@ -33,6 +54,9 @@
<script> <script>
import {generateShareInfoWithExpired} from "@/network/share";
import {getCurrentProjectID} from "@/common/js/utils";
export default { export default {
name: "MsApiReportViewHeader", name: "MsApiReportViewHeader",
props: { props: {
@ -73,6 +97,8 @@ export default {
return { return {
isReadOnly: false, isReadOnly: false,
nameIsEdit: false, nameIsEdit: false,
shareUrl: "",
application:{}
} }
}, },
created() { created() {
@ -91,7 +117,38 @@ export default {
}, },
returnView(){ returnView(){
this.$router.push('/api/automation/report'); this.$router.push('/api/automation/report');
},
handleShare(report) {
this.getProjectApplication();
let pram = {};
pram.customData = report.id;
pram.shareType = 'API_REPORT';
generateShareInfoWithExpired(pram, (data) => {
let thisHost = window.location.host;
this.shareUrl = thisHost + "/shareApiReport" + data.shareUrl;
});
},
getProjectApplication(){
this.$get('/project_application/get/' + getCurrentProjectID()+"/API_SHARE_REPORT_TIME", res => {
if(res.data){
let quantity = res.data.typeValue.substring(0, res.data.typeValue.length - 1);
let unit = res.data.typeValue.substring(res.data.typeValue.length - 1);
if(unit==='H'){
res.data.typeValue = quantity+this.$t('commons.date_unit.hour');
}else
if(unit==='D'){
res.data.typeValue = quantity+this.$t('commons.date_unit.day');
}else
if(unit==='M'){
res.data.typeValue = quantity+this.$t('commons.date_unit.month');
}else
if(unit==='Y'){
res.data.typeValue = quantity+this.$t('commons.date_unit.year');
} }
this.application = res.data;
}
});
},
} }
} }
</script> </script>

View File

@ -156,7 +156,6 @@ export default {
}; };
}, },
created() { created() {
console.log("看看是不是这页面")
this.$get('/api/definition/follow/' + this.basisData.id, response => { this.$get('/api/definition/follow/' + this.basisData.id, response => {
this.basisData.follows = response.data; this.basisData.follows = response.data;
for (let i = 0; i < response.data.length; i++) { for (let i = 0; i < response.data.length; i++) {

View File

@ -46,7 +46,7 @@
</div> </div>
<el-button slot="reference" :disabled="isReadOnly" type="danger" plain size="mini" <el-button slot="reference" :disabled="isReadOnly" type="danger" plain size="mini"
@click="handleShare(report)"> @click="handleShare(report)">
{{ $t('分享报告') }} {{ $t('test_track.plan_view.share_report') }}
</el-button> </el-button>
</el-popover> </el-popover>
<el-button :disabled="report.status !== 'Completed'" type="default" plain <el-button :disabled="report.status !== 'Completed'" type="default" plain

View File

@ -62,6 +62,8 @@
</app-manage-item> </app-manage-item>
<timing-item ref="apiTimingItem" :choose.sync="form.cleanApiReport" :expr.sync="form.cleanApiReportExpr" <timing-item ref="apiTimingItem" :choose.sync="form.cleanApiReport" :expr.sync="form.cleanApiReportExpr"
@chooseChange="chooseChange" :title="$t('project.timing_clean_api_report')"/> @chooseChange="chooseChange" :title="$t('project.timing_clean_api_report')"/>
<timing-item ref="trackTimingItem" :choose.sync="application.shareReport" :expr.sync="application.typeValue" :share-link="true" :unit-options="applyUnitOptions"
@chooseChange="chooseChangeApply" :title="$t('report.report_sharing_link')"/>
</el-row> </el-row>
</el-col> </el-col>
<el-col :span="8" :offset="4"> <el-col :span="8" :offset="4">

View File

@ -204,7 +204,6 @@ export default {
this.currentRow = row; this.currentRow = row;
}, },
handleDelete(row) { handleDelete(row) {
console.log(row);
this.$confirm(this.$t('project.file_delete_tip', [row.name]), '', { this.$confirm(this.$t('project.file_delete_tip', [row.name]), '', {
confirmButtonText: this.$t('commons.confirm'), confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'), cancelButtonText: this.$t('commons.cancel'),

View File

@ -2301,6 +2301,7 @@ export default {
test_detail: "Test detail", test_detail: "Test detail",
failure_case: "Failure case", failure_case: "Failure case",
export_report: "Export Report", export_report: "Export Report",
share_report: "Share Report",
no_case_relevance: "No related use cases", no_case_relevance: "No related use cases",
automatically_update_status: "Auto update status", automatically_update_status: "Auto update status",
allow_associated_repetitive_cases: "Repetitive Case", allow_associated_repetitive_cases: "Repetitive Case",

View File

@ -2306,6 +2306,7 @@ export default {
test_detail: "测试详情", test_detail: "测试详情",
failure_case: "失败用例", failure_case: "失败用例",
export_report: "导出报告", export_report: "导出报告",
share_report: "分享报告",
no_case_relevance: "没有关联用例", no_case_relevance: "没有关联用例",
automatically_update_status: "自动更新状态", automatically_update_status: "自动更新状态",
automatically_update_status_tip: "当功能用例关联的接口或性能用例在测试计划执行后,自动更新功能用例的状态", automatically_update_status_tip: "当功能用例关联的接口或性能用例在测试计划执行后,自动更新功能用例的状态",

View File

@ -2305,6 +2305,7 @@ export default {
test_detail: "測試詳情", test_detail: "測試詳情",
failure_case: "失敗用例", failure_case: "失敗用例",
export_report: "導出報告", export_report: "導出報告",
share_report: "分享報告",
no_case_relevance: "沒有關聯用例", no_case_relevance: "沒有關聯用例",
automatically_update_status: "自動更新狀態", automatically_update_status: "自動更新狀態",
automatically_update_status_tip: "當功能用例關聯的接口或性能用例在測試計劃執行後,自動更新功能用例的狀態", automatically_update_status_tip: "當功能用例關聯的接口或性能用例在測試計劃執行後,自動更新功能用例的狀態",

View File

@ -0,0 +1,43 @@
import Vue from 'vue';
import ElementUI, {Button, Card, Col, Form, FormItem, Input, Main, Popover, Row, Table, TableColumn} from 'element-ui';
import '@/assets/theme/index.css';
import '@/common/css/menu-header.css';
import '@/common/css/main.css';
import i18n from "@/i18n/i18n";
import chart from "@/common/js/chart";
import filters from "@/common/js/filter";
import icon from "@/common/js/icon";
import message from "@/common/js/message";
import ajax from "@/common/js/ajax";
function apiReportUse(id, template) {
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value)
});
Vue.use(Row);
Vue.use(Col);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Input);
Vue.use(Button);
Vue.use(chart);
Vue.use(Main);
Vue.use(Card);
Vue.use(TableColumn);
Vue.use(Table);
Vue.use(filters);
Vue.use(icon);
Vue.use(message);
Vue.use(ajax);
Vue.use(Popover);
new Vue({
el: id,
i18n,
render: h => h(template)
});
}
export default apiReportUse;

View File

@ -0,0 +1,36 @@
<template>
<ms-api-report :report-id="reportId" :share-id="shareId" :is-share="isShare" :is-plan="true" ></ms-api-report>
</template>
<script>
import {getShareId} from "@/common/js/utils";
import {getShareInfo} from "@/network/share";
import MsApiReport from "@/business/components/api/automation/report/ApiReportDetail";
export default {
name: "ShareApiReportTemplate",
components: {MsApiReport},
data() {
return {
reportId: '',
shareId: '',
isShare: true,
};
},
created() {
this.shareId = getShareId();
getShareInfo(this.shareId, (data) => {
if (!data) {
this.$error('连接已失效,请重新获取!');
return;
}
if (data.shareType === 'API_REPORT') {
this.reportId = data.customData;
}
});
},
};
</script>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="shortcut icon" href="<%= BASE_URL %>favicon.ico">
<title>Api Report</title>
</head>
<body>
<div id="shareApiReport"></div>
</body>
</html>

View File

@ -0,0 +1,4 @@
import ShareApiReportTemplate from "@/template/report/api/share/ShareApiReportTemplate";
import apiReportUse from "@/template/report/api/apiReportUse";
apiReportUse('#shareApiReport', ShareApiReportTemplate);

View File

@ -44,6 +44,11 @@ module.exports = {
template: "src/template/report/performance/share/share-performance-report.html", template: "src/template/report/performance/share/share-performance-report.html",
filename: "share-performance-report.html", filename: "share-performance-report.html",
}, },
shareApiReport: {
entry: "src/template/report/api/share/share-api-report.js",
template: "src/template/report/api/share/share-api-report.html",
filename: "share-api-report.html",
},
enterpriseReport: { enterpriseReport: {
entry: "src/template/enterprise/share/share-enterprise-report.js", entry: "src/template/enterprise/share/share-enterprise-report.js",
template: "src/template/enterprise/share/share-enterprise-report.html", template: "src/template/enterprise/share/share-enterprise-report.html",