feat(性能测试): 性能测试保存时根据配置来发送脚本审核的通知
--story=1012024 --user=宋天阳 接口脚本增加预警审核机制 https://www.tapd.cn/55049933/s/1371891
This commit is contained in:
parent
402ce846cf
commit
3222ea22b2
|
@ -3,41 +3,72 @@
|
|||
<div class="ms-header-menu align-right">
|
||||
<el-tooltip effect="light">
|
||||
<template v-slot:content>
|
||||
<span>{{ $t('commons.notice_center') }}</span>
|
||||
<span>{{ $t("commons.notice_center") }}</span>
|
||||
</template>
|
||||
<div @click="showNoticeCenter" v-if="noticeCount > 0 || noticeShow">
|
||||
<el-badge is-dot class="item" type="danger">
|
||||
<font-awesome-icon class="icon global focusing" :icon="['fas', 'bell']"/>
|
||||
<font-awesome-icon
|
||||
class="icon global focusing"
|
||||
:icon="['fas', 'bell']"
|
||||
/>
|
||||
</el-badge>
|
||||
</div>
|
||||
<font-awesome-icon @click="showNoticeCenter" class="icon global focusing" :icon="['fas', 'bell']" v-else/>
|
||||
<font-awesome-icon
|
||||
@click="showNoticeCenter"
|
||||
class="icon global focusing"
|
||||
:icon="['fas', 'bell']"
|
||||
v-else
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-drawer :visible.sync="taskVisible" :destroy-on-close="true" direction="rtl"
|
||||
:withHeader="true" :modal="false" :title="$t('commons.notice_center')" size="550px"
|
||||
custom-class="ms-drawer-task">
|
||||
<el-drawer
|
||||
:visible.sync="taskVisible"
|
||||
:destroy-on-close="true"
|
||||
direction="rtl"
|
||||
:withHeader="true"
|
||||
:modal="false"
|
||||
:title="$t('commons.notice_center')"
|
||||
size="550px"
|
||||
custom-class="ms-drawer-task"
|
||||
>
|
||||
<div style="margin: 0 20px 0">
|
||||
<el-tabs :active-name="activeName">
|
||||
<el-tab-pane :label="$t('commons.mentioned_me_notice')" name="mentionedMe">
|
||||
<notification-data ref="mentionedMe" :user-list="userList" type="MENTIONED_ME"/>
|
||||
<el-tab-pane
|
||||
:label="$t('commons.mentioned_me_notice')"
|
||||
name="mentionedMe"
|
||||
>
|
||||
<notification-data
|
||||
ref="mentionedMe"
|
||||
:user-list="userList"
|
||||
type="MENTIONED_ME"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('commons.system_notice')" name="systemNotice">
|
||||
<notification-data ref="systemNotice" :user-list="userList" type="SYSTEM_NOTICE"/>
|
||||
<notification-data
|
||||
ref="systemNotice"
|
||||
:user-list="userList"
|
||||
type="SYSTEM_NOTICE"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsDrawer from "../MsDrawer";
|
||||
import MsTipButton from "../MsTipButton";
|
||||
import {getOperation, getResource} from "./util";
|
||||
import { getOperation, getResource } from "./util";
|
||||
import NotificationData from "./components/NotificationData";
|
||||
import {getWsMemberList, initNoticeSocket, read, readAll, searchNotifications} from "../../api/notification";
|
||||
import {
|
||||
getWsMemberList,
|
||||
initNoticeSocket,
|
||||
read,
|
||||
readAll,
|
||||
searchNotifications,
|
||||
} from "../../api/notification";
|
||||
|
||||
export default {
|
||||
name: "MsNotification",
|
||||
|
@ -46,9 +77,7 @@ export default {
|
|||
MsTipButton,
|
||||
MsDrawer,
|
||||
},
|
||||
inject: [
|
||||
'reload'
|
||||
],
|
||||
inject: ["reload"],
|
||||
data() {
|
||||
return {
|
||||
noticeCount: 0,
|
||||
|
@ -63,7 +92,7 @@ export default {
|
|||
userList: [],
|
||||
userMap: {},
|
||||
websocket: Object,
|
||||
activeName: 'mentionedMe',
|
||||
activeName: "mentionedMe",
|
||||
pageSize: 20,
|
||||
goPage: 1,
|
||||
totalPage: 0,
|
||||
|
@ -83,18 +112,17 @@ export default {
|
|||
if (!v) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getUserList() {
|
||||
getWsMemberList()
|
||||
.then(response => {
|
||||
this.userList = response.data;
|
||||
this.userMap = this.userList.reduce((r, c) => {
|
||||
r[c.id] = c;
|
||||
return r;
|
||||
}, {});
|
||||
});
|
||||
getWsMemberList().then((response) => {
|
||||
this.userList = response.data;
|
||||
this.userMap = this.userList.reduce((r, c) => {
|
||||
r[c.id] = c;
|
||||
return r;
|
||||
}, {});
|
||||
});
|
||||
},
|
||||
initWebSocket() {
|
||||
this.websocket = initNoticeSocket();
|
||||
|
@ -103,10 +131,9 @@ export default {
|
|||
this.websocket.onerror = this.onError;
|
||||
this.websocket.onclose = this.onClose;
|
||||
},
|
||||
onOpen() {
|
||||
},
|
||||
onOpen() {},
|
||||
onError(e) {
|
||||
console.warn('socket error: ', e)
|
||||
console.warn("socket error: ", e);
|
||||
},
|
||||
onMessage(e) {
|
||||
let m = JSON.parse(e.data);
|
||||
|
@ -121,8 +148,7 @@ export default {
|
|||
this.$refs.mentionedMe.init();
|
||||
}
|
||||
},
|
||||
onClose(e) {
|
||||
},
|
||||
onClose(e) {},
|
||||
showNoticeCenter() {
|
||||
this.noticeCount = 0;
|
||||
this.readAll();
|
||||
|
@ -138,43 +164,61 @@ export default {
|
|||
this.initIndex = 0;
|
||||
},
|
||||
readAll() {
|
||||
readAll()
|
||||
readAll();
|
||||
this.noticeShow = false;
|
||||
},
|
||||
getNotifications() {
|
||||
this.initWebSocket();
|
||||
},
|
||||
showNotification() {
|
||||
searchNotifications({}, 1, 10)
|
||||
.then(response => {
|
||||
let data = response.data.listObject;
|
||||
let now = this.serverTime;
|
||||
data.filter(d => d.status === 'UNREAD').forEach(d => {
|
||||
searchNotifications({}, 1, 10).then((response) => {
|
||||
let data = response.data.listObject;
|
||||
let now = this.serverTime;
|
||||
data
|
||||
.filter((d) => d.status === "UNREAD")
|
||||
.forEach((d) => {
|
||||
if (now - d.createTime > 10 * 1000) {
|
||||
return;
|
||||
}
|
||||
d.user = this.userMap[d.operator] || {name: 'MS'};
|
||||
let message = d.user.name + getOperation(d.operation) + getResource(d) + ": " + d.resourceName;
|
||||
let title = d.type === 'MENTIONED_ME' ? this.$t('commons.mentioned_me_notice') : this.$t('commons.system_notice');
|
||||
d.user = this.userMap[d.operator] || { name: "MS" };
|
||||
let message = "";
|
||||
if (d.operation === "REVIEW") {
|
||||
message =
|
||||
getResource(d) +
|
||||
":" +
|
||||
d.resourceName +
|
||||
" " +
|
||||
this.$t("commons.contains_script_review");
|
||||
} else {
|
||||
message =
|
||||
d.user.name +
|
||||
getOperation(d.operation) +
|
||||
getResource(d) +
|
||||
": " +
|
||||
d.resourceName;
|
||||
}
|
||||
let title =
|
||||
d.type === "MENTIONED_ME"
|
||||
? this.$t("commons.mentioned_me_notice")
|
||||
: this.$t("commons.system_notice");
|
||||
setTimeout(() => {
|
||||
this.$notify({
|
||||
title: title,
|
||||
type: 'info',
|
||||
type: "info",
|
||||
message: message,
|
||||
});
|
||||
// 弹出之后标记成已读
|
||||
read(d.id)
|
||||
read(d.id);
|
||||
this.noticeShow = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-icon-check {
|
||||
color: #44b349;
|
||||
margin-left: 10px;
|
||||
|
@ -197,8 +241,8 @@ export default {
|
|||
:deep(.el-drawer__header) {
|
||||
font-size: 18px;
|
||||
color: #0a0a0a;
|
||||
border-bottom: 1px solid #E6E6E6;
|
||||
background-color: #FFF;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
@ -238,7 +282,6 @@ export default {
|
|||
border-color: #783887;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-menu-item) {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
@ -259,11 +302,11 @@ export default {
|
|||
}
|
||||
|
||||
.ms-task-error {
|
||||
color: #F56C6C;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.ms-task-success {
|
||||
color: #67C23A;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.ms-header-menu {
|
||||
|
@ -275,5 +318,4 @@ export default {
|
|||
cursor: pointer;
|
||||
border-color: var(--color);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,31 +1,73 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="padding-bottom: 5px; width: 100%; height: 50px;">
|
||||
<div style="float:right;">
|
||||
<span style="color: gray; padding-right: 10px">({{ totalCount }} {{ $t('commons.notice_count') }})</span>
|
||||
<div style="padding-bottom: 5px; width: 100%; height: 50px">
|
||||
<div style="float: right">
|
||||
<span style="color: gray; padding-right: 10px"
|
||||
>({{ totalCount }} {{ $t("commons.notice_count") }})</span
|
||||
>
|
||||
<el-dropdown @command="handleCommand" style="padding-right: 10px">
|
||||
<span class="el-dropdown-link" v-if="totalPage > 0">
|
||||
{{ goPage }}/{{ totalPage }}<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<span class="el-dropdown-link" v-if="totalPage > 0">
|
||||
{{ goPage }}/{{ totalPage
|
||||
}}<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<span v-else class="el-dropdown-link">0/0</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<div class="dropdown-content">
|
||||
<el-dropdown-item v-for="i in totalPage" :key="i" :command="i">{{ i }}</el-dropdown-item>
|
||||
<el-dropdown-item v-for="i in totalPage" :key="i" :command="i"
|
||||
>{{ i }}
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-button icon="el-icon-arrow-left" size="mini" :disabled="goPage === 1" @click="prevPage"/>
|
||||
<el-button icon="el-icon-arrow-right" size="mini" :disabled="goPage === totalPage" @click="nextPage"/>
|
||||
<el-button
|
||||
icon="el-icon-arrow-left"
|
||||
size="mini"
|
||||
:disabled="goPage === 1"
|
||||
@click="prevPage"
|
||||
/>
|
||||
<el-button
|
||||
icon="el-icon-arrow-right"
|
||||
size="mini"
|
||||
:disabled="goPage === totalPage"
|
||||
@click="nextPage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-container">
|
||||
<el-table :data="systemNoticeData"
|
||||
:show-header="false"
|
||||
:highlight-current-row="true"
|
||||
style="width: 100%">
|
||||
<el-table
|
||||
:data="systemNoticeData"
|
||||
:show-header="false"
|
||||
:highlight-current-row="true"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="content" :label="$t('commons.name')">
|
||||
<template v-slot="{row}">
|
||||
<el-row type="flex" align="start" class="current-user">
|
||||
<template v-slot="{ row }">
|
||||
<!--评审信息的格式与其余的不一样-->
|
||||
<el-row
|
||||
v-if="isReviewNotice(row)"
|
||||
type="flex"
|
||||
align="start"
|
||||
class="current-user"
|
||||
>
|
||||
<el-col :span="2">
|
||||
<div class="icon-title">
|
||||
{{ row.resourceName.substring(0, 1) }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="22">
|
||||
<span class="operation">
|
||||
<span>{{ getResource(row) }}:</span>
|
||||
<span
|
||||
@click="clickResource(row)"
|
||||
style="color: #783887; cursor: pointer"
|
||||
>
|
||||
{{ row.resourceName }}
|
||||
</span>
|
||||
<span> {{ $t("commons.contains_script_review") }} </span>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-else type="flex" align="start" class="current-user">
|
||||
<el-col :span="2">
|
||||
<div class="icon-title">
|
||||
{{ row.user.name.substring(0, 1) }}
|
||||
|
@ -33,9 +75,13 @@
|
|||
</el-col>
|
||||
<el-col :span="22">
|
||||
<span class="username">{{ row.user.name }}</span>
|
||||
<span class="operation">{{ getOperation(row.operation) }}{{ getResource(row) }}:
|
||||
<span v-if="row.resourceId && row.operation.indexOf('DELETE') < 0" @click="clickResource(row)"
|
||||
style="color: #783887; cursor: pointer;">
|
||||
<span class="operation"
|
||||
>{{ getOperation(row.operation) }}{{ getResource(row) }}:
|
||||
<span
|
||||
v-if="row.resourceId && row.operation.indexOf('DELETE') < 0"
|
||||
@click="clickResource(row)"
|
||||
style="color: #783887; cursor: pointer"
|
||||
>
|
||||
{{ row.resourceName }}
|
||||
</span>
|
||||
<span v-else>{{ row.resourceName }}</span>
|
||||
|
@ -43,26 +89,29 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="12">
|
||||
|
||||
</el-col>
|
||||
<el-col :span="12"></el-col>
|
||||
<el-col :span="6">
|
||||
<span class="time-style">{{ row.createTime | datetimeFormat }}</span>
|
||||
<span class="time-style">{{
|
||||
row.createTime | datetimeFormat
|
||||
}}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div style="color: gray; padding-top:20px; text-align: center">
|
||||
- {{ $t('commons.notice_tips') }} -
|
||||
<div style="color: gray; padding-top: 20px; text-align: center">
|
||||
- {{ $t("commons.notice_tips") }} -
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getOperation, getResource, getUrl} from "../util";
|
||||
import {searchNotifications, updateUserByResourceId} from "../../../api/notification";
|
||||
import { getOperation, getResource, getUrl } from "../util";
|
||||
import {
|
||||
searchNotifications,
|
||||
updateUserByResourceId,
|
||||
} from "../../../api/notification";
|
||||
|
||||
export default {
|
||||
name: "NotificationData",
|
||||
|
@ -73,7 +122,7 @@ export default {
|
|||
goPage: 1,
|
||||
totalPage: 0,
|
||||
totalCount: 0,
|
||||
userMap: {}
|
||||
userMap: {},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
@ -95,17 +144,18 @@ export default {
|
|||
this.init();
|
||||
},
|
||||
init() {
|
||||
let param = {type: this.type};
|
||||
this.result = searchNotifications(param, this.goPage, this.pageSize)
|
||||
.then(response => {
|
||||
let param = { type: this.type };
|
||||
this.result = searchNotifications(param, this.goPage, this.pageSize).then(
|
||||
(response) => {
|
||||
this.systemNoticeData = response.data.listObject;
|
||||
this.totalPage = response.data.pageCount;
|
||||
this.totalCount = response.data.itemCount;
|
||||
|
||||
this.systemNoticeData.forEach(n => {
|
||||
n.user = this.userMap[n.operator] || {name: "MS"};
|
||||
this.systemNoticeData.forEach((n) => {
|
||||
n.user = this.userMap[n.operator] || { name: "MS" };
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
prevPage() {
|
||||
if (this.goPage < 1) {
|
||||
|
@ -130,10 +180,9 @@ export default {
|
|||
}
|
||||
let uri = getUrl(resource);
|
||||
|
||||
updateUserByResourceId(resourceId)
|
||||
.then(() => {
|
||||
this.toPage(uri);
|
||||
});
|
||||
updateUserByResourceId(resourceId).then(() => {
|
||||
this.toPage(uri);
|
||||
});
|
||||
},
|
||||
toPage(uri) {
|
||||
let id = "new_a";
|
||||
|
@ -146,8 +195,11 @@ export default {
|
|||
|
||||
let element = document.getElementById(id);
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
isReviewNotice(row) {
|
||||
return row.operation === "REVIEW";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -395,6 +395,7 @@ const message = {
|
|||
},
|
||||
reviewer: "Reviewer",
|
||||
append_reviewer: "Append Reviewer",
|
||||
contains_script_review: "has script step,review it please.",
|
||||
report_statistics: {
|
||||
reserved: "Reserved",
|
||||
menu: {
|
||||
|
@ -520,10 +521,10 @@ const message = {
|
|||
ui_module: "default",
|
||||
},
|
||||
other: "Other",
|
||||
function_introduction: 'Function introduction',
|
||||
page_guidance: 'Page guidance',
|
||||
novice_journey: 'Novice Journey',
|
||||
minder_operation: 'Minder Operation'
|
||||
function_introduction: "Function introduction",
|
||||
page_guidance: "Page guidance",
|
||||
novice_journey: "Novice Journey",
|
||||
minder_operation: "Minder Operation",
|
||||
},
|
||||
login: {
|
||||
normal_Login: "Normal Login",
|
||||
|
@ -1381,7 +1382,8 @@ const message = {
|
|||
title: "Upload jar package",
|
||||
jar_file: "Jar Package",
|
||||
jar_manage: "JAR package management",
|
||||
delete_tip: "Deleting the plug-in requires restarting the service to take effect",
|
||||
delete_tip:
|
||||
"Deleting the plug-in requires restarting the service to take effect",
|
||||
delete_confirm: "Confirm to delete the plugin",
|
||||
file_exist: "The name already exists in the project",
|
||||
upload_limit_size: "Upload file size cannot exceed 30MB!",
|
||||
|
@ -3539,56 +3541,56 @@ const message = {
|
|||
},
|
||||
shepherd: {
|
||||
step1: {
|
||||
title: 'A Workspaces and Projects',
|
||||
text: 'MeterSphere uses [workspace] and [project] to isolate test data, and you can switch between workspace and project in the top menu.'
|
||||
title: "A Workspaces and Projects",
|
||||
text: "MeterSphere uses [workspace] and [project] to isolate test data, and you can switch between workspace and project in the top menu.",
|
||||
},
|
||||
step2: {
|
||||
title: 'Side navigation menu',
|
||||
text: 'The navigation menu shows which function module you are in.'
|
||||
title: "Side navigation menu",
|
||||
text: "The navigation menu shows which function module you are in.",
|
||||
},
|
||||
step3: {
|
||||
title: 'One workspace holds multiple projects',
|
||||
text: 'A "project" is a collection of use cases and members. Various types of tests on MeterSphere are viewed and managed through projects.'
|
||||
title: "One workspace holds multiple projects",
|
||||
text: 'A "project" is a collection of use cases and members. Various types of tests on MeterSphere are viewed and managed through projects.',
|
||||
},
|
||||
step4: {
|
||||
title: 'Top function menu',
|
||||
text: 'The topl function menu supports switching subdivision functions under the current first-class module.'
|
||||
title: "Top function menu",
|
||||
text: "The topl function menu supports switching subdivision functions under the current first-class module.",
|
||||
},
|
||||
step5: {
|
||||
title: "Where are you?",
|
||||
text: "Now, that you have joined a workspace and become a member of the current project, start your testing journey from here."
|
||||
text: "Now, that you have joined a workspace and become a member of the current project, start your testing journey from here.",
|
||||
},
|
||||
exit:'skip',
|
||||
next: 'Next',
|
||||
know:'know',
|
||||
exit: "skip",
|
||||
next: "Next",
|
||||
know: "know",
|
||||
},
|
||||
guide: {
|
||||
home: {
|
||||
title: 'Welcome to MeterSphere!',
|
||||
desc: 'A quickstart guide to see what MeterSphere can do for you.',
|
||||
button: 'Lets get started'
|
||||
title: "Welcome to MeterSphere!",
|
||||
desc: "A quickstart guide to see what MeterSphere can do for you.",
|
||||
button: "Lets get started",
|
||||
},
|
||||
test: {
|
||||
title: 'Test cases are the cornerstone of testing',
|
||||
desc: '<span>Maintain your test cases through online editing/file import/URL synchronization/multi-person review,</span><br><span>add them to your test plan,quantitatively manage test progress, record results, synchronize issues, </span><br><span>retain/share test reports, and cover the entire software testing life cycle.</span>',
|
||||
button: 'Next: Interface Test'
|
||||
title: "Test cases are the cornerstone of testing",
|
||||
desc: "<span>Maintain your test cases through online editing/file import/URL synchronization/multi-person review,</span><br><span>add them to your test plan,quantitatively manage test progress, record results, synchronize issues, </span><br><span>retain/share test reports, and cover the entire software testing life cycle.</span>",
|
||||
button: "Next: Interface Test",
|
||||
},
|
||||
api: {
|
||||
title: 'Simulate real scenarios to automate API testing',
|
||||
desc: '<span>API testing is triggered by manual/scheduled tasks/plug-ins, supporting multiple communication protocols; </span><br><span>scenario case-sets are arranged based on real business processes, </span><br><span>and multi-type controllers/custom scripts/assertions are supported to meet various user needs.</span>',
|
||||
button: 'Next: UI Test'
|
||||
title: "Simulate real scenarios to automate API testing",
|
||||
desc: "<span>API testing is triggered by manual/scheduled tasks/plug-ins, supporting multiple communication protocols; </span><br><span>scenario case-sets are arranged based on real business processes, </span><br><span>and multi-type controllers/custom scripts/assertions are supported to meet various user needs.</span>",
|
||||
button: "Next: UI Test",
|
||||
},
|
||||
ui: {
|
||||
title: 'Portable UI element library and command set',
|
||||
title: "Portable UI element library and command set",
|
||||
desc: '<span>Arrange scenario cases based on reusable element library and commands;</span><br><span>combine your commonly used test steps into new command, which can be flexibly called in automation scenarios.</span><br><span style="background-color: #ffffff;color:#ffffff">/</span>',
|
||||
button: 'Next: Performance Test'
|
||||
button: "Next: Performance Test",
|
||||
},
|
||||
performance: {
|
||||
title: 'One-click launch performance testing',
|
||||
desc: '<span>Provides a distributed performance testing solution, supporting multiple types of testing resource pools </span><br><span>such as physical machines/virtual machines/k8s container clusters; </span><br><span>One-click to initiate API scenario case to performance testing, and view real-time reports;</span><br><span>Provides report comparison under different configurations to control performance bottlenecks and optimize them.</span>',
|
||||
button: 'To create your first test case'
|
||||
title: "One-click launch performance testing",
|
||||
desc: "<span>Provides a distributed performance testing solution, supporting multiple types of testing resource pools </span><br><span>such as physical machines/virtual machines/k8s container clusters; </span><br><span>One-click to initiate API scenario case to performance testing, and view real-time reports;</span><br><span>Provides report comparison under different configurations to control performance bottlenecks and optimize them.</span>",
|
||||
button: "To create your first test case",
|
||||
},
|
||||
go_prev: 'Return to previous'
|
||||
go_prev: "Return to previous",
|
||||
},
|
||||
side_task: {
|
||||
test_tracking: {
|
||||
|
@ -3638,7 +3640,7 @@ const message = {
|
|||
subtitle: "You have completed all the novice journey, full of energy",
|
||||
desc: "If you want to continue to learn about advanced tutorials, please follow our technical blog and live channel",
|
||||
blog_url: "Technical Blog",
|
||||
live_url: "Live Channel"
|
||||
live_url: "Live Channel",
|
||||
},
|
||||
close: {
|
||||
title: "Close the novice journey",
|
||||
|
@ -3649,8 +3651,8 @@ const message = {
|
|||
},
|
||||
save_success: "Closed successfully",
|
||||
to_try: "Go to try it",
|
||||
view_video: "View video tutorial"
|
||||
}
|
||||
view_video: "View video tutorial",
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -387,6 +387,7 @@ const message = {
|
|||
},
|
||||
reviewer: "评审人",
|
||||
append_reviewer: "追加评审人",
|
||||
contains_script_review: "包含脚本步骤,请审核",
|
||||
report_statistics: {
|
||||
reserved: "预留模块敬请期待",
|
||||
menu: {
|
||||
|
@ -512,10 +513,10 @@ const message = {
|
|||
template_delete: "模版删除",
|
||||
scope: "应用场景",
|
||||
other: "其他",
|
||||
function_introduction: '功能介绍',
|
||||
page_guidance: '页面指引',
|
||||
novice_journey: '新手旅程',
|
||||
minder_operation: '脑图操作'
|
||||
function_introduction: "功能介绍",
|
||||
page_guidance: "页面指引",
|
||||
novice_journey: "新手旅程",
|
||||
minder_operation: "脑图操作",
|
||||
},
|
||||
login: {
|
||||
normal_Login: "普通登录",
|
||||
|
@ -3413,56 +3414,56 @@ const message = {
|
|||
},
|
||||
shepherd: {
|
||||
step1: {
|
||||
title: '工作空间和项目',
|
||||
text: 'MeterSphere 使用 [工作空间] 和 [项目] 来隔离测试数据, 你可以在顶部菜单进行工作空间和项目切换。'
|
||||
title: "工作空间和项目",
|
||||
text: "MeterSphere 使用 [工作空间] 和 [项目] 来隔离测试数据, 你可以在顶部菜单进行工作空间和项目切换。",
|
||||
},
|
||||
step2: {
|
||||
title: '功能主菜单',
|
||||
text: '主菜单显示你所在的功能模块。'
|
||||
title: "功能主菜单",
|
||||
text: "主菜单显示你所在的功能模块。",
|
||||
},
|
||||
step3: {
|
||||
title: '一个空间可以创建多个项目',
|
||||
text: '「项目」是一组用例和成员的集合。MeterSphere 上各种类型的测试均通过项目进行分权分域查看和管理。'
|
||||
title: "一个空间可以创建多个项目",
|
||||
text: "「项目」是一组用例和成员的集合。MeterSphere 上各种类型的测试均通过项目进行分权分域查看和管理。",
|
||||
},
|
||||
step4: {
|
||||
title: '一级功能菜单',
|
||||
text: '顶部一级功能菜单栏,支持在当前功能模块下切换细分功能。'
|
||||
title: "一级功能菜单",
|
||||
text: "顶部一级功能菜单栏,支持在当前功能模块下切换细分功能。",
|
||||
},
|
||||
step5: {
|
||||
title: "你在哪?",
|
||||
text: "现在,你已加入了一个工作空间并成为当前项目的一员,就从这里开始你的测试之旅吧。"
|
||||
text: "现在,你已加入了一个工作空间并成为当前项目的一员,就从这里开始你的测试之旅吧。",
|
||||
},
|
||||
exit:'跳过',
|
||||
next:'下一步',
|
||||
know:'知道啦',
|
||||
exit: "跳过",
|
||||
next: "下一步",
|
||||
know: "知道啦",
|
||||
},
|
||||
guide: {
|
||||
home: {
|
||||
title: '欢迎来到 MeterSphere!',
|
||||
desc: '通过一个快捷指引来了解 MeterSphere 究竟能为你做哪些事。',
|
||||
button: '让我们开始吧'
|
||||
title: "欢迎来到 MeterSphere!",
|
||||
desc: "通过一个快捷指引来了解 MeterSphere 究竟能为你做哪些事。",
|
||||
button: "让我们开始吧",
|
||||
},
|
||||
test: {
|
||||
title: '测试用例是测试任务的基石',
|
||||
desc: '<span>使用在线编辑/文件导入/URL同步/多人评审的方式维护你的用例,</span><br><span>将它们加入你的测试计划中,量化管理测试进度,记录结果,同步缺陷,</span><br><span>留存/分享测试报告,覆盖整个测试生命周期。</span>',
|
||||
button: '下一个:接口测试'
|
||||
title: "测试用例是测试任务的基石",
|
||||
desc: "<span>使用在线编辑/文件导入/URL同步/多人评审的方式维护你的用例,</span><br><span>将它们加入你的测试计划中,量化管理测试进度,记录结果,同步缺陷,</span><br><span>留存/分享测试报告,覆盖整个测试生命周期。</span>",
|
||||
button: "下一个:接口测试",
|
||||
},
|
||||
api: {
|
||||
title: '模拟真实场景 让接口自动化',
|
||||
desc: '<span>通过手动/定时任务/插件触发接口测试,支持多种通信协议;</span><br><span>基于真实业务流程编排场景化用例集,支持添加多类型控制器/自定义脚本/断言,</span><br><span>满足各种复杂场景所需。</span>',
|
||||
button: '下一个:UI测试'
|
||||
title: "模拟真实场景 让接口自动化",
|
||||
desc: "<span>通过手动/定时任务/插件触发接口测试,支持多种通信协议;</span><br><span>基于真实业务流程编排场景化用例集,支持添加多类型控制器/自定义脚本/断言,</span><br><span>满足各种复杂场景所需。</span>",
|
||||
button: "下一个:UI测试",
|
||||
},
|
||||
ui: {
|
||||
title: '可移植的 UI 元素库与指令集',
|
||||
title: "可移植的 UI 元素库与指令集",
|
||||
desc: '<span>基于可复用的元素库及指令快速编排场景化用例;</span><br><span>将你常用的测试步骤组合成新的自定义指令,在自动化场景中灵活调用。</span><br><span style="background-color: #ffffff;color:#ffffff">/</span>',
|
||||
button: '下一个:性能测试'
|
||||
button: "下一个:性能测试",
|
||||
},
|
||||
performance: {
|
||||
title: '性能测试 一键就可以',
|
||||
desc: '<span>提供分布式压测解决方案,支持物理机/虚拟机/k8s容器集群等多类型压测资源池;</span><br><span>使用接口测试转性能一键发起,实时查看报告;</span><br><span>提供差异配置下的报告对比,掌控性能瓶颈及调优。</span>',
|
||||
button: '完成!去创建你的第 1 条测试用例'
|
||||
title: "性能测试 一键就可以",
|
||||
desc: "<span>提供分布式压测解决方案,支持物理机/虚拟机/k8s容器集群等多类型压测资源池;</span><br><span>使用接口测试转性能一键发起,实时查看报告;</span><br><span>提供差异配置下的报告对比,掌控性能瓶颈及调优。</span>",
|
||||
button: "完成!去创建你的第 1 条测试用例",
|
||||
},
|
||||
go_prev: '返回上一个'
|
||||
go_prev: "返回上一个",
|
||||
},
|
||||
side_task: {
|
||||
test_tracking: {
|
||||
|
@ -3512,7 +3513,7 @@ const message = {
|
|||
subtitle: "你已完成全部新手旅程 能量满载~",
|
||||
desc: "想继续了解进阶教程,请关注我们的技术博客和直播",
|
||||
blog_url: "博客地址",
|
||||
live_url: "直播间地址"
|
||||
live_url: "直播间地址",
|
||||
},
|
||||
close: {
|
||||
title: "关闭新手旅程",
|
||||
|
@ -3523,8 +3524,8 @@ const message = {
|
|||
},
|
||||
save_success: "关闭成功",
|
||||
to_try: "去完成",
|
||||
view_video: "观看视频教程"
|
||||
}
|
||||
view_video: "观看视频教程",
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -387,6 +387,7 @@ const message = {
|
|||
},
|
||||
reviewer: "評審人",
|
||||
append_reviewer: "追加評審人",
|
||||
contains_script_review: "包含腳本步驟,請審核",
|
||||
report_statistics: {
|
||||
reserved: "預留模塊敬請期待",
|
||||
menu: {
|
||||
|
@ -511,10 +512,10 @@ const message = {
|
|||
},
|
||||
template_delete: "模版刪除",
|
||||
other: "其他",
|
||||
function_introduction: '功能介紹',
|
||||
page_guidance: '頁面指引',
|
||||
novice_journey: '新手旅程',
|
||||
minder_operation: '腦圖操作'
|
||||
function_introduction: "功能介紹",
|
||||
page_guidance: "頁面指引",
|
||||
novice_journey: "新手旅程",
|
||||
minder_operation: "腦圖操作",
|
||||
},
|
||||
login: {
|
||||
normal_Login: "普通登錄",
|
||||
|
@ -3410,56 +3411,56 @@ const message = {
|
|||
},
|
||||
shepherd: {
|
||||
step1: {
|
||||
title: '工作空間和項目',
|
||||
text: 'MeterSphere 使用 [工作空間] 和 [項目] 來隔離測試數據, 你可以在頂部菜單進行工作空間和項目切換。'
|
||||
title: "工作空間和項目",
|
||||
text: "MeterSphere 使用 [工作空間] 和 [項目] 來隔離測試數據, 你可以在頂部菜單進行工作空間和項目切換。",
|
||||
},
|
||||
step2: {
|
||||
title: '功能主菜單',
|
||||
text: '主菜單顯示您所在的功能模塊。'
|
||||
title: "功能主菜單",
|
||||
text: "主菜單顯示您所在的功能模塊。",
|
||||
},
|
||||
step3: {
|
||||
title: '一個空間可以創建多個項目',
|
||||
text: '「項目」是一組用例和成員的集合。 MeterSphere 上各種類型的測試均通過項目進行分權分域查看和管理。'
|
||||
title: "一個空間可以創建多個項目",
|
||||
text: "「項目」是一組用例和成員的集合。 MeterSphere 上各種類型的測試均通過項目進行分權分域查看和管理。",
|
||||
},
|
||||
step4: {
|
||||
title: '一級功能菜單',
|
||||
text: '頂部一級功能菜單欄,支持在當前功能模塊下切換細分功能。'
|
||||
title: "一級功能菜單",
|
||||
text: "頂部一級功能菜單欄,支持在當前功能模塊下切換細分功能。",
|
||||
},
|
||||
step5: {
|
||||
title: "你在哪?",
|
||||
text: "現在,你已加入了一個工作空間並成為當前項目的一員,就從這裡開始你的測試之旅吧。"
|
||||
text: "現在,你已加入了一個工作空間並成為當前項目的一員,就從這裡開始你的測試之旅吧。",
|
||||
},
|
||||
exit:'跳過',
|
||||
next:'下一步',
|
||||
know:'知道啦',
|
||||
exit: "跳過",
|
||||
next: "下一步",
|
||||
know: "知道啦",
|
||||
},
|
||||
guide: {
|
||||
home: {
|
||||
title: '歡迎來到 MeterSphere!',
|
||||
desc: '通過一個快捷指引來了解 MeterSphere 究竟能為你做哪些事。',
|
||||
button: '讓我們開始吧'
|
||||
title: "歡迎來到 MeterSphere!",
|
||||
desc: "通過一個快捷指引來了解 MeterSphere 究竟能為你做哪些事。",
|
||||
button: "讓我們開始吧",
|
||||
},
|
||||
test: {
|
||||
title: '測試用例是測試任務的基石',
|
||||
desc: '<span>使用在線編輯/文件導入/URL同步/多人評審的方式維護你的用例,</span><br><span>將它們加入你的測試計劃中,量化管理測試進度,記錄結果,同步缺陷,</span><br><span>留存/分享測試報告,覆蓋整個測試生命週期。</span>',
|
||||
button: '下一個:接口測試'
|
||||
title: "測試用例是測試任務的基石",
|
||||
desc: "<span>使用在線編輯/文件導入/URL同步/多人評審的方式維護你的用例,</span><br><span>將它們加入你的測試計劃中,量化管理測試進度,記錄結果,同步缺陷,</span><br><span>留存/分享測試報告,覆蓋整個測試生命週期。</span>",
|
||||
button: "下一個:接口測試",
|
||||
},
|
||||
api: {
|
||||
title: '模擬真實場景 讓接口自動化',
|
||||
desc: '<span>通過手動/定時任務/插件觸發接口測試,支持多種通信協議;</span><br><span> 基於真實業務流程編排場景化用例集,支持添加多類型控制器/自定義腳本/斷言,</span><br><span>滿足各種複雜場景所需。</span>',
|
||||
button: '下一個:UI測試'
|
||||
title: "模擬真實場景 讓接口自動化",
|
||||
desc: "<span>通過手動/定時任務/插件觸發接口測試,支持多種通信協議;</span><br><span> 基於真實業務流程編排場景化用例集,支持添加多類型控制器/自定義腳本/斷言,</span><br><span>滿足各種複雜場景所需。</span>",
|
||||
button: "下一個:UI測試",
|
||||
},
|
||||
ui: {
|
||||
title: '可移植的 UI 元素庫與指令集',
|
||||
title: "可移植的 UI 元素庫與指令集",
|
||||
desc: '<span>基於可複用的元素庫及指令快速編排場景化用例;</span><br><span>將你常用的測試步驟組合成新的自定義指令,在自動化場景中靈活調用。</span><br><span style="background-color: #ffffff;color:#ffffff">/</span>',
|
||||
button: '下一個:性能測試'
|
||||
button: "下一個:性能測試",
|
||||
},
|
||||
performance: {
|
||||
title: '性能測試 一鍵就可以',
|
||||
desc: '<span>提供分佈式壓測解決方案,支持物理機/虛擬機/k8s容器集群等多類型壓測資源池;</span><br><span>使用接口測試轉性能一鍵發起,實時查看報告;</span><br><span>提供差異配置下的報告對比,掌控性能瓶頸及調優。</span>',
|
||||
button: '完成!去創建你的第 1 條測試用例'
|
||||
title: "性能測試 一鍵就可以",
|
||||
desc: "<span>提供分佈式壓測解決方案,支持物理機/虛擬機/k8s容器集群等多類型壓測資源池;</span><br><span>使用接口測試轉性能一鍵發起,實時查看報告;</span><br><span>提供差異配置下的報告對比,掌控性能瓶頸及調優。</span>",
|
||||
button: "完成!去創建你的第 1 條測試用例",
|
||||
},
|
||||
go_prev: '返回上一個'
|
||||
go_prev: "返回上一個",
|
||||
},
|
||||
side_task: {
|
||||
test_tracking: {
|
||||
|
@ -3509,7 +3510,7 @@ const message = {
|
|||
subtitle: "您已完成全部新手旅程 能量滿載~",
|
||||
desc: "想繼續了解進階教程,請關注我們的技術博客和直播",
|
||||
blog_url: "博客地址",
|
||||
live_url: "直播間地址"
|
||||
live_url: "直播間地址",
|
||||
},
|
||||
close: {
|
||||
title: "關閉新手旅程",
|
||||
|
@ -3520,8 +3521,8 @@ const message = {
|
|||
},
|
||||
save_success: "關閉成功",
|
||||
to_try: "去完成",
|
||||
view_video: "觀看視頻教程"
|
||||
}
|
||||
view_video: "觀看視頻教程",
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -46,6 +46,7 @@ public interface NoticeConstants {
|
|||
String UPDATE = "UPDATE";
|
||||
String DELETE = "DELETE";
|
||||
String COMPLETE = "COMPLETE";
|
||||
String REVIEW = "REVIEW";
|
||||
|
||||
String CASE_CREATE = "CASE_CREATE";
|
||||
String CASE_UPDATE = "CASE_UPDATE";
|
||||
|
|
|
@ -107,4 +107,13 @@ public enum ProjectApplicationType {
|
|||
* 资源池ID
|
||||
*/
|
||||
RESOURCE_POOL_ID,
|
||||
|
||||
/**
|
||||
* 性能测试是否评审脚本
|
||||
*/
|
||||
PERFORMANCE_REVIEW_LOAD_TEST_SCRIPT,
|
||||
/**
|
||||
* 性能测试脚本评审人
|
||||
*/
|
||||
PERFORMANCE_SCRIPT_REVIEWER,
|
||||
}
|
||||
|
|
|
@ -36,4 +36,6 @@ public class ProjectConfig {
|
|||
private String resourcePoolId;
|
||||
private Boolean poolEnable = false;
|
||||
private Boolean reReview = false;
|
||||
private String performanceScriptReviewer;
|
||||
private Boolean performanceReviewLoadTestScript = false;
|
||||
}
|
||||
|
|
|
@ -784,4 +784,10 @@ public class FileMetadataService {
|
|||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public List<FileMetadataWithBLOBs> selectByIdAndType(List<String> idList, String jmx) {
|
||||
FileMetadataExample fileMetadataExample = new FileMetadataExample();
|
||||
fileMetadataExample.createCriteria().andIdIn(idList).andTypeEqualTo("JMX");
|
||||
return fileMetadataMapper.selectByExampleWithBLOBs(fileMetadataExample);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.metersphere.request.*;
|
|||
import io.metersphere.service.BaseCheckPermissionService;
|
||||
import io.metersphere.service.PerformanceTestService;
|
||||
import io.metersphere.task.dto.TaskRequestDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -29,7 +30,7 @@ import org.springframework.http.ResponseEntity;
|
|||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -96,12 +97,12 @@ public class PerformanceTestController {
|
|||
@RequestPart(value = "file", required = false) List<MultipartFile> files
|
||||
) {
|
||||
request.setId(UUID.randomUUID().toString());
|
||||
// checkPermissionService.checkProjectOwner(request.getProjectId());
|
||||
LoadTest loadTest = performanceTestService.save(request, files);
|
||||
|
||||
List<ApiLoadTest> apiList = request.getApiList();
|
||||
apiPerformanceService.add(apiList, loadTest.getId());
|
||||
|
||||
//检查并发送审核脚本的通知
|
||||
performanceTestService.checkAndSendReviewMessage(new ArrayList<>(request.getUpdatedFileList()), files, request.getId(), request.getName(), request.getProjectId());
|
||||
return loadTest;
|
||||
}
|
||||
|
||||
|
@ -114,8 +115,10 @@ public class PerformanceTestController {
|
|||
@RequestPart("request") EditTestPlanRequest request,
|
||||
@RequestPart(value = "file", required = false) List<MultipartFile> files
|
||||
) {
|
||||
// // checkPermissionService.checkPerformanceTestOwner(request.getId());
|
||||
return performanceTestService.edit(request, files);
|
||||
LoadTest returnModel = performanceTestService.edit(request, files);
|
||||
//检查并发送审核脚本的通知
|
||||
performanceTestService.checkAndSendReviewMessage(new ArrayList<>(request.getUpdatedFileList()), files, request.getId(), request.getName(), request.getProjectId());
|
||||
return returnModel;
|
||||
}
|
||||
|
||||
|
||||
|
@ -160,7 +163,7 @@ public class PerformanceTestController {
|
|||
public Pager<List<FileMetadata>> getProjectFiles(@PathVariable String projectId, @PathVariable String loadType,
|
||||
@PathVariable int goPage, @PathVariable int pageSize,
|
||||
@RequestBody QueryProjectFileRequest request) {
|
||||
// // checkPermissionService.checkProjectOwner(projectId);
|
||||
// // checkPermissionService.checkProjectOwner(projectId);
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
return PageUtils.setPageInfo(page, performanceTestService.getProjectFiles(projectId, loadType, request));
|
||||
}
|
||||
|
@ -283,6 +286,7 @@ public class PerformanceTestController {
|
|||
public LoadTestDTO getLoadTestByVersion(@PathVariable String version, @PathVariable String refId) {
|
||||
return performanceTestService.getLoadTestByVersion(version, refId);
|
||||
}
|
||||
|
||||
@GetMapping("check-file-is-related/{fileId}")
|
||||
public void checkFileIsRelated(@PathVariable String fileId) {
|
||||
performanceTestService.checkFileIsRelated(fileId);
|
||||
|
|
|
@ -24,12 +24,16 @@ import io.metersphere.log.vo.DetailColumn;
|
|||
import io.metersphere.log.vo.OperatingLogDetails;
|
||||
import io.metersphere.log.vo.performance.PerformanceReference;
|
||||
import io.metersphere.metadata.service.FileMetadataService;
|
||||
import io.metersphere.notice.service.NotificationService;
|
||||
import io.metersphere.quota.service.BaseQuotaService;
|
||||
import io.metersphere.request.*;
|
||||
import io.metersphere.task.dto.TaskRequestDTO;
|
||||
import io.metersphere.utils.JmxParseUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.api.RLock;
|
||||
|
@ -38,7 +42,6 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
@ -94,6 +97,10 @@ public class PerformanceTestService {
|
|||
private TestCaseTestMapper testCaseTestMapper;
|
||||
@Resource
|
||||
private BaseQuotaService baseQuotaService;
|
||||
@Resource
|
||||
private BaseProjectApplicationService baseProjectApplicationService;
|
||||
@Resource
|
||||
private NotificationService notificationService;
|
||||
|
||||
public List<LoadTestDTO> list(QueryTestPlanRequest request) {
|
||||
request.setOrders(ServiceUtils.getDefaultSortOrder(request.getOrders()));
|
||||
|
@ -794,29 +801,6 @@ public class PerformanceTestService {
|
|||
return null;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 初始化场景与性能测试的关联关系
|
||||
// */
|
||||
// public void initScenarioLoadTest() {
|
||||
// LoadTestExample example = new LoadTestExample();
|
||||
// example.createCriteria().andScenarioIdIsNotNull();
|
||||
// List<LoadTest> loadTests = loadTestMapper.selectByExample(example);
|
||||
// SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
|
||||
// ApiLoadTestMapper mapper = sqlSession.getMapper(ApiLoadTestMapper.class);
|
||||
// loadTests.forEach(item -> {
|
||||
// ApiLoadTest scenarioLoadTest = new ApiLoadTest();
|
||||
// scenarioLoadTest.setType(ApiLoadType.SCENARIO.name());
|
||||
// scenarioLoadTest.setApiId(item.getScenarioId());
|
||||
// scenarioLoadTest.setApiVersion(item.getScenarioVersion() == null ? 0 : item.getScenarioVersion());
|
||||
// scenarioLoadTest.setLoadTestId(item.getId());
|
||||
// scenarioLoadTest.setId(UUID.randomUUID().toString());
|
||||
// mapper.insert(scenarioLoadTest);
|
||||
// });
|
||||
// sqlSession.flushStatements();
|
||||
// if (sqlSession != null && sqlSessionFactory != null) {
|
||||
// SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
|
||||
// }
|
||||
// }
|
||||
|
||||
public Integer getGranularity(String reportId) {
|
||||
Integer granularity = CommonBeanFactory.getBean(JmeterProperties.class).getReport().getGranularity();
|
||||
|
@ -1017,4 +1001,40 @@ public class PerformanceTestService {
|
|||
public List<BaseCase> getBaseCaseByProjectId(String projectId) {
|
||||
return extLoadTestMapper.selectBaseCaseByProjectId(projectId);
|
||||
}
|
||||
|
||||
//检查并发送脚本审核的通知
|
||||
public void checkAndSendReviewMessage(List<FileMetadata> fileMetadataList, List<MultipartFile> files, String loadTestId, String loadTestName, String projectId) {
|
||||
ProjectApplication reviewLoadTestScript = baseProjectApplicationService.getProjectApplication(
|
||||
projectId, ProjectApplicationType.PERFORMANCE_REVIEW_LOAD_TEST_SCRIPT.name());
|
||||
if (BooleanUtils.toBoolean(reviewLoadTestScript.getTypeValue())) {
|
||||
ProjectApplication loadTestScriptReviewerConfig = baseProjectApplicationService.getProjectApplication(
|
||||
projectId, ProjectApplicationType.PERFORMANCE_SCRIPT_REVIEWER.name());
|
||||
if (StringUtils.isNotEmpty(loadTestScriptReviewerConfig.getTypeValue())) {
|
||||
boolean isSend = this.isSendScriptReviewMessage(fileMetadataList, files);
|
||||
if (isSend) {
|
||||
Notification notification = new Notification();
|
||||
notification.setTitle("性能测试通知");
|
||||
notification.setOperator(SessionUtils.getUserId());
|
||||
notification.setOperation(NoticeConstants.Event.REVIEW);
|
||||
notification.setResourceId(loadTestId);
|
||||
notification.setResourceName(loadTestName);
|
||||
notification.setResourceType(NoticeConstants.TaskType.PERFORMANCE_TEST_TASK);
|
||||
notification.setType(NotificationConstants.Type.SYSTEM_NOTICE.name());
|
||||
notification.setStatus(NotificationConstants.Status.UNREAD.name());
|
||||
notification.setCreateTime(System.currentTimeMillis());
|
||||
notification.setReceiver(loadTestScriptReviewerConfig.getTypeValue());
|
||||
notificationService.sendAnnouncement(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSendScriptReviewMessage(List<FileMetadata> fileMetadataList, List<MultipartFile> files) {
|
||||
List<FileMetadataWithBLOBs> fileMetadataWithBLOBsList = new ArrayList<>();
|
||||
if (CollectionUtils.isNotEmpty(fileMetadataList)) {
|
||||
List<String> idList = fileMetadataList.stream().map(FileMetadata::getId).toList();
|
||||
fileMetadataWithBLOBsList = fileMetadataService.selectByIdAndType(idList, "JMX");
|
||||
}
|
||||
return JmxParseUtil.isJmxHasScriptByFiles(files) || JmxParseUtil.isJmxHasScriptByStorage(fileMetadataWithBLOBsList);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import io.metersphere.base.domain.FileMetadataWithBLOBs;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.environment.utils.XMLUtils;
|
||||
import io.metersphere.metadata.service.FileCenter;
|
||||
import io.metersphere.metadata.vo.FileRequest;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
|
||||
//Jmx解析工具类
|
||||
public class JmxParseUtil {
|
||||
public static boolean isJmxHasScriptByFiles(List<MultipartFile> files) {
|
||||
if (CollectionUtils.isNotEmpty(files)) {
|
||||
for (MultipartFile file : files) {
|
||||
if (jmxMultipartFileHasScript(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isJmxHasScriptByStorage(List<FileMetadataWithBLOBs> fileMetadataWithBLOBs) {
|
||||
for (FileMetadataWithBLOBs fileMetadata : fileMetadataWithBLOBs) {
|
||||
if (StringUtils.equalsIgnoreCase(fileMetadata.getType(), "jmx")) {
|
||||
FileRequest fileRequest = new FileRequest();
|
||||
fileRequest.setFileName(fileMetadata.getName());
|
||||
fileRequest.setProjectId(fileMetadata.getProjectId());
|
||||
fileRequest.setResourceId(fileMetadata.getId());
|
||||
fileRequest.setStorage(fileMetadata.getStorage());
|
||||
fileRequest.setResourceType(fileMetadata.getResourceType());
|
||||
fileRequest.setType(fileMetadata.getType());
|
||||
fileRequest.setPath(fileMetadata.getPath());
|
||||
fileRequest.setFileAttachInfoByString(fileMetadata.getAttachInfo());
|
||||
|
||||
if (jmxStorageFileHasScript(fileRequest)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean jmxMultipartFileHasScript(MultipartFile file) {
|
||||
if (file != null) {
|
||||
try {
|
||||
String fileName = file.getOriginalFilename();
|
||||
if (StringUtils.endsWithIgnoreCase(fileName, ".jmx")) {
|
||||
return jmxBytesHasScript(file.getBytes());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("检查上传的jmx文件是否含有脚本数据失败", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean jmxStorageFileHasScript(FileRequest fileRequest) {
|
||||
try {
|
||||
return jmxBytesHasScript(FileCenter.getRepository(fileRequest.getStorage()).getFile(fileRequest));
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("下载jmx文件解析是否含有脚本数据失败", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean jmxBytesHasScript(byte[] jmxFileByte) throws Exception {
|
||||
if (jmxFileByte != null) {
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
XMLUtils.setExpandEntityReferencesFalse(documentBuilderFactory);
|
||||
DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
|
||||
final Document document = builder.parse(new InputSource(new ByteArrayInputStream(jmxFileByte)));
|
||||
final Element jmxDoc = document.getDocumentElement();
|
||||
NodeList childNodes = jmxDoc.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
Node node = childNodes.item(i);
|
||||
if (node instanceof Element) {
|
||||
if (hashTreeNodeHasScript((Element) node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hashTreeNodeHasScript(Element hashTree) {
|
||||
if (invalid(hashTree)) {
|
||||
return false;
|
||||
}
|
||||
if (hashTree.getChildNodes().getLength() > 0) {
|
||||
final NodeList childNodes = hashTree.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
Node node = childNodes.item(i);
|
||||
if (node instanceof Element elementItem) {
|
||||
if (invalid(elementItem)) {
|
||||
continue;
|
||||
}
|
||||
if (nodeIsScript(elementItem)) {
|
||||
return true;
|
||||
} else {
|
||||
//递归子节点是否有脚本
|
||||
if (hashTreeNodeHasScript(elementItem)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean invalid(Element ele) {
|
||||
return !StringUtils.isBlank(ele.getAttribute("enabled")) && !Boolean.parseBoolean(ele.getAttribute("enabled"));
|
||||
}
|
||||
|
||||
private static boolean nodeIsScript(Node node) {
|
||||
return StringUtils.containsAnyIgnoreCase(node.getNodeName(), "JSR223", "Processor");
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<app-manage-item
|
||||
:title="name"
|
||||
:description="popTitle"
|
||||
:append-span="3"
|
||||
:middle-span="12"
|
||||
:prepend-span="9"
|
||||
>
|
||||
<template #prepend>
|
||||
<span style="margin-left: 10px; line-height: 35px">{{ name }}</span>
|
||||
<el-tooltip
|
||||
v-show="popTitle"
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="popTitle"
|
||||
placement="right-start"
|
||||
>
|
||||
<i class="el-icon-info" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template #middle>
|
||||
<span>{{ $t("pj.reviewers") }}</span>
|
||||
<el-select
|
||||
v-model="config.performanceScriptReviewer"
|
||||
@change="reviewerChange"
|
||||
size="mini"
|
||||
style="margin-left: 5px"
|
||||
filterable
|
||||
:placeholder="$t('api_test.definition.api_principal')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reviewers"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-switch
|
||||
v-model="config.performanceReviewLoadTestScript"
|
||||
@change="switchChange"
|
||||
></el-switch>
|
||||
</template>
|
||||
</app-manage-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppManageItem from "@/business/menu/appmanage/AppManageItem.vue";
|
||||
|
||||
export default {
|
||||
name: "ReviewerConfig",
|
||||
components: { AppManageItem },
|
||||
props: {
|
||||
name: String,
|
||||
popTitle: String,
|
||||
reviewers: Array,
|
||||
config: Object,
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
created() {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
switchChange() {
|
||||
this.$emit("chooseChange");
|
||||
},
|
||||
reviewerChange() {
|
||||
this.$emit("reviewerChange");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -8,6 +8,10 @@ const message = {
|
|||
"(Environment configuration with the same name filtered {0})",
|
||||
check_third_project_success: "inspection passed",
|
||||
api_run_pool_title: "Interface execution resource pool",
|
||||
reviewers: "Reviewers",
|
||||
load_test_script_review: "Performance test script review",
|
||||
load_test_script_review_detail:
|
||||
"Performance test script file upload must specify user review",
|
||||
},
|
||||
file_manage: {
|
||||
my_file: "My File",
|
||||
|
|
|
@ -7,6 +7,9 @@ const message = {
|
|||
environment_import_repeat_tip: "(已过滤同名称的环境配置 {0})",
|
||||
check_third_project_success: "检查通过",
|
||||
api_run_pool_title: "接口执行资源池",
|
||||
reviewers: "审核人",
|
||||
load_test_script_review: "性能脚本审核",
|
||||
load_test_script_review_detail: "上传性能测试脚本文件须指定用户审核",
|
||||
},
|
||||
file_manage: {
|
||||
my_file: "我的文件",
|
||||
|
|
|
@ -7,6 +7,9 @@ const message = {
|
|||
environment_import_repeat_tip: "(已過濾同名稱的環境配置 {0})",
|
||||
check_third_project_success: "檢查通過",
|
||||
api_run_pool_title: "接口執行資源池",
|
||||
reviewers: "審核人",
|
||||
load_test_script_review: "性能腳本審核",
|
||||
load_test_script_review_detail: "上傳性能測試腳本文件須指定用戶審核",
|
||||
},
|
||||
file_manage: {
|
||||
my_file: "我的文件",
|
||||
|
|
Loading…
Reference in New Issue