feat(变更历史): 完成变更历史基础功能

This commit is contained in:
fit2-zhao 2021-05-20 18:57:02 +08:00 committed by fit2-zhao
parent 5fb95c1f93
commit 9484471f96
20 changed files with 941 additions and 639 deletions

View File

@ -9,4 +9,7 @@ import java.util.List;
public interface ExtOperatingLogMapper {
List<OperatingLogDTO> list(@Param("request") OperatingLogRequest request);
List<OperatingLogDTO> findBySourceId(@Param("request") OperatingLogRequest request);
}

View File

@ -41,4 +41,31 @@
order by t.oper_time desc
</select>
<select id="findBySourceId" resultType="io.metersphere.log.vo.OperatingLogDTO">
SELECT
t.id,
t.project_id,
t.oper_user,
t.source_id,
t.oper_type,
t.oper_module,
t.oper_title,
t.oper_time,
t.oper_content,
t1.NAME userName,
t2.`name` projectName
FROM
operating_log t
LEFT JOIN USER t1 ON t.oper_user = t1.id
LEFT JOIN project t2 ON t.project_id = t2.id
LEFT JOIN workspace w on t2.workspace_id = w.id
<where>
t.oper_type in ('UPDATE','BATCH_UPDATE')
<if test="request.sourceId != null and request.sourceId != ''">
and t.source_id like #{request.sourceId, jdbcType=VARCHAR}
</if>
</where>
order by t.oper_time desc
</select>
</mapper>

View File

@ -35,4 +35,10 @@ public class OperatingLogController {
return operatingLogService.get(id);
}
@GetMapping("/get/source/{id}")
public List<OperatingLogDTO> findBySourceId(@PathVariable String id) {
return operatingLogService.findBySourceId(id);
}
}

View File

@ -45,4 +45,18 @@ public class OperatingLogService {
}
return dto;
}
public List<OperatingLogDTO> findBySourceId(String id) {
OperatingLogRequest request = new OperatingLogRequest();
request.setSourceId("%" + id + "%");
List<OperatingLogDTO> logWithBLOBs = extOperatingLogMapper.findBySourceId(request);
if (CollectionUtils.isNotEmpty(logWithBLOBs)) {
for (OperatingLogDTO logWithBLOB : logWithBLOBs) {
if (StringUtils.isNotEmpty(logWithBLOB.getOperContent())) {
logWithBLOB.setDetails(JSON.parseObject(logWithBLOB.getOperContent(), OperatingLogDetails.class));
}
}
}
return logWithBLOBs;
}
}

View File

@ -28,5 +28,7 @@ public class OperatingLogDTO implements Serializable {
private Long operTime;
private String operContent;
private OperatingLogDetails details;
}

View File

@ -15,6 +15,7 @@ public class PerformanceReference {
performanceColumns.put("loadConfiguration", "压力配置");
performanceColumns.put("advancedConfiguration", "高级配置");
performanceColumns.put("description", "描述");
performanceColumns.put("ms-dff-col", "loadConfiguration,advancedConfiguration");
reportColumns.put("name","报告名称");
}

View File

@ -16,7 +16,7 @@ public class TestCaseReference {
testCaseColumns.put("priority", "用例等级");
testCaseColumns.put("method", "请求类型");
testCaseColumns.put("prerequisite", "前置条件");
testCaseColumns.put("remark", "评论");
testCaseColumns.put("remark", "备注");
testCaseColumns.put("steps", "用例步骤");
testCaseColumns.put("other_test_name", "其他名称");
testCaseColumns.put("review_status", "评审状态");
@ -24,7 +24,8 @@ public class TestCaseReference {
testCaseColumns.put("demand_name", "需求名称");
testCaseColumns.put("follow_people", "关注人");
testCaseColumns.put("status", "用例状态");
testCaseColumns.put("step_description", "步骤描述");
testCaseColumns.put("expected_result", "预期结果");
testCaseColumns.put("stepDescription", "步骤描述");
testCaseColumns.put("expectedResult", "预期结果");
testCaseColumns.put("comment", "评论");
}
}

View File

@ -30,6 +30,7 @@ import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.track.TestCaseReference;
import io.metersphere.service.FileService;
import io.metersphere.service.ProjectService;
import io.metersphere.track.dto.TestCaseCommentDTO;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.testcase.EditTestCaseRequest;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
@ -1156,6 +1157,13 @@ public class TestCaseService {
TestCaseWithBLOBs bloBs = testCaseMapper.selectByPrimaryKey(id);
if (bloBs != null) {
List<DetailColumn> columns = ReflexObjectUtil.getColumns(bloBs, TestCaseReference.testCaseColumns);
// 增加评论内容
List<TestCaseCommentDTO> dtos = testCaseCommentService.getCaseComments(id);
if (CollectionUtils.isNotEmpty(dtos)) {
List<String> names = dtos.stream().map(TestCaseCommentDTO::getDescription).collect(Collectors.toList());
DetailColumn detailColumn = new DetailColumn("评论", "comment", String.join("\n", names), null);
columns.add(detailColumn);
}
OperatingLogDetails details = new OperatingLogDetails(JSON.toJSONString(id), bloBs.getProjectId(), bloBs.getName(), bloBs.getCreateUser(), columns);
return JSON.toJSONString(details);
}
@ -1168,6 +1176,13 @@ public class TestCaseService {
String testCaseId = testPlanTestCaseMapper.selectByPrimaryKey(id).getCaseId();
TestCaseWithBLOBs testCaseWithBLOBs = testCaseMapper.selectByPrimaryKey(testCaseId);
List<DetailColumn> columns = ReflexObjectUtil.getColumns(bloBs, TestCaseReference.testCaseColumns);
// 增加评论内容
List<TestCaseCommentDTO> dtos = testCaseCommentService.getCaseComments(id);
if (CollectionUtils.isNotEmpty(dtos)) {
List<String> names = dtos.stream().map(TestCaseCommentDTO::getDescription).collect(Collectors.toList());
DetailColumn detailColumn = new DetailColumn("评论", "comment", String.join("\n", names), null);
columns.add(detailColumn);
}
OperatingLogDetails details = new OperatingLogDetails(JSON.toJSONString(testCaseWithBLOBs.getId()), testCaseWithBLOBs.getProjectId(), testCaseWithBLOBs.getName(), testCaseWithBLOBs.getCreateUser(), columns);
return JSON.toJSONString(details);
}

View File

@ -5,6 +5,8 @@
<!--操作按钮-->
<div class="ms-opt-btn">
<el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="currentScenario.id">{{$t('operating_log.change_history')}}</el-link>
<el-button id="inputDelay" type="primary" size="small" v-prevent-re-click @click="editScenario" title="ctrl + s" v-tester>
{{ $t('commons.save') }}
</el-button>
@ -224,6 +226,7 @@
<maximize-scenario :scenario-definition="scenarioDefinition" :envMap="projectEnvMap" :moduleOptions="moduleOptions"
:currentScenario="currentScenario" :type="type" ref="maximizeScenario" @openScenario="openScenario"/>
</ms-drawer>
<ms-change-history ref="changeHistory"/>
</div>
</el-card>
@ -262,6 +265,7 @@
import MsDrawer from "../../../common/components/MsDrawer";
import MsSelectTree from "../../../common/select-tree/SelectTree";
import {saveScenario} from "@/business/components/api/automation/api-automation";
import MsChangeHistory from "../../../history/ChangeHistory";
let jsonPath = require('jsonpath');
export default {
@ -289,7 +293,8 @@
MaximizeScenario,
ScenarioHeader,
MsDrawer,
MsSelectTree
MsSelectTree,
MsChangeHistory
},
data() {
return {
@ -494,6 +499,9 @@
},
},
methods: {
openHis(){
this.$refs.changeHistory.open(this.currentScenario.id);
},
setModule(id, data) {
this.currentScenario.apiScenarioModuleId = id;
this.currentScenario.modulePath = data.path;

View File

@ -6,6 +6,7 @@
<el-col>
<!--操作按钮-->
<div style="float: right;margin-right: 20px;margin-top: 20px">
<el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="basisData.id">{{$t('operating_log.change_history')}}</el-link>
<el-button type="primary" size="small" @click="saveApi" title="ctrl + s" v-tester>{{ $t('commons.save') }}</el-button>
<el-button type="primary" size="small" @click="runTest" v-tester>{{ $t('commons.test') }}</el-button>
</div>
@ -24,18 +25,19 @@
<!-- 请求参数 -->
<p class="tip">{{ $t('api_test.definition.request.req_param') }} </p>
<ms-basis-parameters :showScript="false" :request="request"/>
<ms-change-history ref="changeHistory"/>
</div>
</template>
<script>
import MsBasisApi from "./BasisApi";
import MsBasisParameters from "../request/dubbo/BasisParameters";
import MsChangeHistory from "../../../../history/ChangeHistory";
export default {
name: "MsApiDubboRequestForm",
components: {
MsBasisApi, MsBasisParameters
MsBasisApi, MsBasisParameters,MsChangeHistory
},
props: {
request: {},
@ -72,6 +74,9 @@
return {validated: false}
},
methods: {
openHis(){
this.$refs.changeHistory.open(this.basisData.id);
},
callback() {
this.validated = true;
},

View File

@ -5,6 +5,7 @@
<el-form :model="httpForm" :rules="rule" ref="httpForm" label-width="80px" label-position="right">
<!-- 操作按钮 -->
<div style="float: right;margin-right: 20px">
<el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="httpForm.id">{{$t('operating_log.change_history')}}</el-link>
<el-button type="primary" size="small" @click="saveApi" title="ctrl + s" v-tester>{{ $t('commons.save') }}</el-button>
<el-button type="primary" size="small" @click="runTest" v-tester>{{ $t('commons.test') }}</el-button>
</div>
@ -107,6 +108,9 @@
<!-- 响应内容-->
<p class="tip">{{ $t('api_test.definition.request.res_param') }} </p>
<ms-response-text :response="response"></ms-response-text>
<ms-change-history ref="changeHistory"/>
</el-card>
</div>
</template>
@ -121,10 +125,11 @@
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import MsSelectTree from "../../../../common/select-tree/SelectTree";
import MsChangeHistory from "../../../../history/ChangeHistory";
export default {
name: "MsAddCompleteHttpApi",
components: {MsJsr233Processor, MsResponseText, MsApiRequestForm, MsInputTag, MsSelectTree},
components: {MsJsr233Processor, MsResponseText, MsApiRequestForm, MsInputTag, MsSelectTree,MsChangeHistory},
data() {
let validateURL = (rule, value, callback) => {
if (!this.httpForm.path.startsWith("/") || this.httpForm.path.match(/\s/) != null) {
@ -224,6 +229,9 @@
}
},
methods: {
openHis(){
this.$refs.changeHistory.open(this.httpForm.id);
},
runTest() {
this.$refs['httpForm'].validate((valid) => {
if (valid) {

View File

@ -5,6 +5,7 @@
<el-col>
<!--操作按钮-->
<div style="float: right;margin-right: 20px;margin-top: 20px">
<el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="basisData.id">{{$t('operating_log.change_history')}}</el-link>
<el-button type="primary" size="small" @click="saveApi" title="ctrl + s" v-tester>{{ $t('commons.save') }}</el-button>
<el-button type="primary" size="small" @click="runTest" v-tester>{{ $t('commons.test') }}</el-button>
</div>
@ -23,6 +24,7 @@
<!-- 请求参数 -->
<p class="tip">{{ $t('api_test.definition.request.req_param') }} </p>
<ms-basis-parameters :showScript="false" :request="request"/>
<ms-change-history ref="changeHistory"/>
</div>
</template>
@ -30,11 +32,12 @@
<script>
import MsBasisApi from "./BasisApi";
import MsBasisParameters from "../request/database/BasisParameters";
import MsChangeHistory from "../../../../history/ChangeHistory";
export default {
name: "MsApiSqlRequestForm",
components: {
MsBasisApi, MsBasisParameters
MsBasisApi, MsBasisParameters,MsChangeHistory
},
props: {
request: {},
@ -70,6 +73,9 @@ export default {
return {validated: false}
},
methods: {
openHis(){
this.$refs.changeHistory.open(this.basisData.id);
},
callback() {
this.validated = true;
},

View File

@ -5,6 +5,7 @@
<el-col>
<!--操作按钮-->
<div style="float: right;margin-right: 20px;margin-top: 20px">
<el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="basisData.id">{{$t('operating_log.change_history')}}</el-link>
<el-button type="primary" size="small" @click="saveApi" title="ctrl + s" v-tester>{{ $t('commons.save') }}</el-button>
<el-button type="primary" size="small" @click="runTest" v-tester>{{ $t('commons.test') }}</el-button>
</div>
@ -32,6 +33,7 @@
<!-- <api-response-component :currentProtocol="apiCase.request.protocol" :api-item="apiCase"/>-->
</div>
<ms-change-history ref="changeHistory"/>
</div>
@ -40,12 +42,14 @@
<script>
import MsTcpBasicApi from "./TCPBasicApi";
import MsBasisParameters from "../request/tcp/TcpBasisParameters";
import MsChangeHistory from "../../../../history/ChangeHistory";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const esbDefinition = (requireComponent!=null&&requireComponent.keys().length) > 0 ? requireComponent("./apidefinition/EsbDefinition.vue") : {};
const esbDefinitionResponse = (requireComponent!=null&&requireComponent.keys().length) > 0 ? requireComponent("./apidefinition/EsbDefinitionResponse.vue") : {};
export default {
name: "MsAddCompleteTcpApi",
components: {MsTcpBasicApi, MsBasisParameters,
components: {MsTcpBasicApi, MsBasisParameters,MsChangeHistory,
"esbDefinition": esbDefinition.default,
"esbDefinitionResponse": esbDefinitionResponse.default},
props: {
@ -102,6 +106,9 @@ export default {
},
},
methods: {
openHis(){
this.$refs.changeHistory.open(this.basisData.id);
},
callback() {
this.validated = true;
},

View File

@ -0,0 +1,104 @@
<template>
<el-dialog :close-on-click-modal="false" :title="$t('operating_log.change_history')" :visible.sync="infoVisible" width="900px" :destroy-on-close="true"
@close="handleClose">
<el-table :data="details">
<el-table-column prop="operTime" :label="$t('operating_log.time')">
<template v-slot:default="scope">
<span>{{ scope.row.operTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="userName" :label="$t('operating_log.user')"/>
<el-table-column prop="columnTitle" :label="$t('operating_log.change_field')">
<template v-slot:default="scope">
<div v-if="scope.row.details && scope.row.details.columns">
<div v-for="detail in scope.row.details.columns" :key="detail.id">{{ detail.columnTitle }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="originalValue" :label="$t('operating_log.before_change')">
<template v-slot:default="scope">
<div v-if="scope.row.details && scope.row.details.columns">
<div v-for="detail in scope.row.details.columns" :key="detail.id">
<div v-if="linkDatas.indexOf(detail.columnName)!== -1">
<el-link style="color: #409EFF" @click="openDetail(scope.row,detail)">{{$t('operating_log.info')}}</el-link>
</div>
<el-tooltip :content="detail.originalValue" v-else>
<div class="current-value">{{ detail.originalValue ?detail.originalValue :"空值"}}</div>
</el-tooltip>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="newValue" :label="$t('operating_log.after_change')">
<template v-slot:default="scope">
<div v-if="scope.row.details && scope.row.details.columns">
<div v-for="detail in scope.row.details.columns" :key="detail.id">
<div v-if="linkDatas.indexOf(detail.columnName)!== -1">
<el-link style="color: #409EFF" @click="openDetail(scope.row,detail)">{{$t('operating_log.info')}}</el-link>
</div>
<el-tooltip :content="detail.newValue" v-else>
<div class="current-value">{{ detail.newValue ? detail.newValue : "空值"}}</div>
</el-tooltip>
</div>
</div>
</template>
</el-table-column>
</el-table>
<ms-history-detail ref="historyDetail"></ms-history-detail>
</el-dialog>
</template>
<script>
import MsHistoryDetail from "./HistoryDetail";
export default {
name: "MsChangeHistory",
components: {MsHistoryDetail},
props: {
title: String,
},
data() {
return {
infoVisible: false,
details: [],
linkDatas: ["prerequisite", "steps", "remark", "request", "response","scenarioDefinition","loadConfiguration","advancedConfiguration"],
}
},
methods: {
handleClose() {
this.infoVisible = false;
},
getDetails(id) {
this.result = this.$get("/operating/log/get/source/" + id, response => {
let data = response.data;
if (data) {
this.details = data;
}
})
},
open(id) {
this.infoVisible = true;
this.getDetails(id);
},
openDetail(row, value) {
value.createUser = row.details.createUser;
value.operTime = row.operTime;
this.$refs.historyDetail.open(value);
},
}
}
</script>
<style scoped>
.current-value {
display: inline-block;
overflow-x: hidden;
padding-bottom: 0;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
width: 120px;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<el-dialog :close-on-click-modal="false" :title="$t('operating_log.info')" :visible.sync="infoVisible" width="900px" :destroy-on-close="true"
@close="handleClose" append-to-body>
<div style="height: 700px;overflow: auto">
<div v-if="detail.createUser">
<p class="tip">{{ this.$t('report.user_name') }} {{detail.createUser}}</p>
</div>
<div>
<p class="tip">{{ this.$t('operating_log.time') }} {{ detail.operTime | timestampFormatDate }}</p>
</div>
<div style="overflow: auto">
<p class="tip">{{ this.$t('report.test_log_details') }} </p>
<el-row style="background:#F8F8F8">
<el-col :span="12">
{{$t('operating_log.before_change')}}
</el-col>
<el-col :span="12">
{{$t('operating_log.after_change')}}
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<div style="width: 400px;overflow: auto">
<pre>{{ detail.originalValue }}</pre>
</div>
</el-col>
<el-col :span="12">
<div style="width: 400px;overflow: auto">
<pre>{{ detail.newValue }}</pre>
</div>
</el-col>
</el-row>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: "MsHistoryDetail",
components: {},
props: {
title: String,
},
data() {
return {
infoVisible: false,
detail: {},
}
},
methods: {
handleClose() {
this.infoVisible = false;
},
open(value) {
this.infoVisible = true;
this.detail = value;
},
getType(type) {
return this.LOG_TYPE_MAP.get(type);
},
}
}
</script>
<style scoped>
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
}
</style>

View File

@ -12,6 +12,7 @@
</el-input>
</el-col>
<el-col :span="12" :offset="2">
<el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="test.id">{{$t('operating_log.change_history')}}</el-link>
<el-button :disabled="isReadOnly" type="primary" plain @click="save">{{ $t('commons.save') }}</el-button>
<el-button :disabled="isReadOnly" type="primary" plain @click="saveAndRun">
{{ $t('load_test.save_and_run') }}
@ -41,6 +42,9 @@
</el-tab-pane>
</el-tabs>
</el-card>
<ms-change-history ref="changeHistory"/>
</ms-main-container>
</ms-container>
</template>
@ -54,6 +58,7 @@ import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser, getCurrentProjectID} from "@/common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
import MsChangeHistory from "../../history/ChangeHistory";
export default {
name: "EditPerformanceTest",
@ -63,7 +68,8 @@ export default {
PerformanceBasicConfig,
PerformanceAdvancedConfig,
MsContainer,
MsMainContainer
MsMainContainer,
MsChangeHistory
},
data() {
return {
@ -122,6 +128,9 @@ export default {
this.importAPITest();
},
methods: {
openHis(){
this.$refs.changeHistory.open(this.test.id);
},
importAPITest() {
let apiTest = this.$store.state.test;
if (apiTest && apiTest.name) {

View File

@ -1822,5 +1822,6 @@ export default {
before_change: "Before change",
after_change: "After change",
share: "Share",
change_history: "Change history",
}
};

View File

@ -1830,5 +1830,6 @@ export default {
before_change: "变更前",
after_change: "变更后",
share: "分享",
change_history: "变更历史",
}
};

View File

@ -1830,5 +1830,6 @@ export default {
before_change: "變更前",
after_change: "變更後",
share: "分享",
change_history: "變更歷史",
}
};