@@ -39,6 +39,7 @@
+ {{$t('校验')}}
{{$t('commons.update')}}
{{$t('commons.add')}}
@@ -81,6 +82,7 @@
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
+ result: {},
currentConfig: new DatabaseConfig(),
rules: {
name: [
@@ -124,6 +126,11 @@
return false;
}
});
+ },
+ validate() {
+ this.result = this.$post('/api/database/validate', this.currentConfig, () => {
+ this.$success(this.$t('commons.connection_successful'));
+ })
}
}
}
diff --git a/frontend/src/business/components/api/test/model/JMX.js b/frontend/src/business/components/api/test/model/JMX.js
index 0d02ecff8c..6c57e47486 100644
--- a/frontend/src/business/components/api/test/model/JMX.js
+++ b/frontend/src/business/components/api/test/model/JMX.js
@@ -337,6 +337,7 @@ export class HTTPSamplerProxy extends DefaultTestElement {
}
this.boolProp("HTTPSampler.use_keepalive", options.keepalive, true);
+ this.boolProp("HTTPSampler.DO_MULTIPART_POST", options.doMultipartPost, false);
}
}
diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js
index e1c8d79763..099dabbebb 100644
--- a/frontend/src/business/components/api/test/model/ScenarioModel.js
+++ b/frontend/src/business/components/api/test/model/ScenarioModel.js
@@ -346,6 +346,7 @@ export class HttpRequest extends Request {
this.environment = options.environment;
this.useEnvironment = options.useEnvironment;
this.debugReport = undefined;
+ this.doMultipartPost = options.doMultipartPost;
this.connectTimeout = options.connectTimeout || 60 * 1000;
this.responseTimeout = options.responseTimeout;
this.followRedirects = options.followRedirects === undefined ? true : options.followRedirects;
@@ -476,13 +477,14 @@ export class SqlRequest extends Request {
this.useEnvironment = options.useEnvironment;
this.resultVariable = options.resultVariable;
this.variableNames = options.variableNames;
+ this.variables = [];
this.debugReport = undefined;
this.dataSource = options.dataSource;
this.query = options.query;
// this.queryType = options.queryType;
this.queryTimeout = options.queryTimeout || 60000;
- this.sets({args: KeyValue, attachmentArgs: KeyValue}, options);
+ this.sets({args: KeyValue, attachmentArgs: KeyValue, variables: KeyValue}, options);
}
isValid() {
@@ -987,7 +989,7 @@ class JMXHttpRequest {
this.connectTimeout = request.connectTimeout;
this.responseTimeout = request.responseTimeout;
this.followRedirects = request.followRedirects;
-
+ this.doMultipartPost = request.doMultipartPost;
}
}
@@ -1115,7 +1117,6 @@ class JMXGenerator {
if (request.enable) {
if (!request.isValid()) return;
let sampler;
-
if (request instanceof DubboRequest) {
sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig));
} else if (request instanceof HttpRequest) {
@@ -1126,6 +1127,7 @@ class JMXGenerator {
} else if (request instanceof SqlRequest) {
request.dataSource = scenario.databaseConfigMap.get(request.dataSource);
sampler = new JDBCSampler(request.name || "", request);
+ this.addRequestVariables(sampler, request);
} else if (request instanceof TCPRequest) {
sampler = new TCPSampler(request.name || "", new JMXTCPRequest(request, scenario));
}
@@ -1187,6 +1189,14 @@ class JMXGenerator {
}
}
+ addRequestVariables(httpSamplerProxy, request) {
+ let name = request.name + " Variables";
+ let variables = this.filterKV(request.variables);
+ if (variables && variables.length > 0) {
+ httpSamplerProxy.put(new Arguments(name, variables));
+ }
+ }
+
addScenarioCookieManager(threadGroup, scenario) {
if (scenario.enableCookieShare) {
threadGroup.put(new CookieManager(scenario.name));
diff --git a/frontend/src/business/components/track/case/components/ReviewStatus.vue b/frontend/src/business/components/track/case/components/ReviewStatus.vue
new file mode 100644
index 0000000000..87882ffe74
--- /dev/null
+++ b/frontend/src/business/components/track/case/components/ReviewStatus.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/track/case/components/TestCaseEdit.vue b/frontend/src/business/components/track/case/components/TestCaseEdit.vue
index cff54cc231..169bd88a16 100644
--- a/frontend/src/business/components/track/case/components/TestCaseEdit.vue
+++ b/frontend/src/business/components/track/case/components/TestCaseEdit.vue
@@ -209,6 +209,59 @@
+
+
+ 附件:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.updateTime | timestampFormatDate }}
+
+
+
+
+
+
+
+
+
+
+
@@ -234,6 +287,7 @@ import {TokenKey, WORKSPACE_ID} from '../../../../../common/js/constants';
import MsDialogFooter from '../../../common/components/MsDialogFooter'
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
+import {Message} from "element-ui";
export default {
name: "TestCaseEdit",
@@ -263,6 +317,9 @@ export default {
maintainerOptions: [],
testOptions: [],
workspaceId: '',
+ fileList: [],
+ tableData: [],
+ uploadList: [],
rules: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
@@ -339,6 +396,7 @@ export default {
tmp.steps = JSON.parse(testCase.steps);
Object.assign(this.form, tmp);
this.form.module = testCase.nodeId;
+ this.getFileMetaData(testCase);
} else {
if (this.selectNode.data) {
this.form.module = this.selectNode.data.id;
@@ -358,6 +416,24 @@ export default {
this.reload();
this.dialogFormVisible = true;
},
+ getFileMetaData(testCase) {
+ this.fileList = [];
+ this.tableData = [];
+ this.uploadList = [];
+ this.result = this.$get("test/case/file/metadata/" + testCase.id, response => {
+ let files = response.data;
+
+ if (!files) {
+ return;
+ }
+ // deep copy
+ this.fileList = JSON.parse(JSON.stringify(files));
+ this.tableData = JSON.parse(JSON.stringify(files));
+ this.tableData.map(f => {
+ f.size = f.size + ' Bytes';
+ });
+ })
+ },
handleAddStep(index, data) {
let step = {};
step.num = data.num + 1;
@@ -400,7 +476,8 @@ export default {
if (valid) {
let param = this.buildParam();
if (this.validate(param)) {
- this.result = this.$post('/test/case/' + this.operationType, param, () => {
+ let option = this.getOption(param);
+ this.result = this.$request(option, () => {
this.$success(this.$t('commons.save_success'));
if (this.operationType == 'add' && this.isCreateContinue) {
this.form.name = '';
@@ -411,6 +488,7 @@ export default {
result: ''
}];
this.form.remark = '';
+ this.form.uploadList = [];
this.$emit("refresh");
return;
}
@@ -444,6 +522,32 @@ export default {
}
return param;
},
+ getOption(param) {
+ let formData = new FormData();
+ let url = '/test/case/' + this.operationType;
+ this.uploadList.forEach(f => {
+ formData.append("file", f);
+ });
+
+ param.updatedFileList = this.fileList;
+
+ let requestJson = JSON.stringify(param, function (key, value) {
+ return key === "file" ? undefined : value
+ });
+
+ formData.append('request', new Blob([requestJson], {
+ type: "application/json"
+ }));
+
+ return {
+ method: 'POST',
+ url: url,
+ data: formData,
+ headers: {
+ 'Content-Type': undefined
+ }
+ };
+ },
validate(param) {
for (let i = 0; i < param.steps.length; i++) {
if ((param.steps[i].desc && param.steps[i].desc.length > 300) ||
@@ -527,7 +631,88 @@ export default {
return true;
});
}
- }
+ },
+ handleExceed() {
+ this.$error(this.$t('load_test.file_size_limit'));
+ },
+ beforeUpload(file) {
+ if (!this.fileValidator(file)) {
+ /// todo: 显示错误信息
+ return false;
+ }
+
+ if (this.tableData.filter(f => f.name === file.name).length > 0) {
+ this.$error(this.$t('load_test.delete_file'));
+ return false;
+ }
+
+ let type = file.name.substring(file.name.lastIndexOf(".") + 1);
+
+ this.tableData.push({
+ name: file.name,
+ size: file.size + ' Bytes', /// todo: 按照大小显示Byte、KB、MB等
+ type: type.toUpperCase(),
+ updateTime: file.lastModified,
+ });
+
+ return true;
+ },
+ handleUpload(uploadResources) {
+ this.uploadList.push(uploadResources.file);
+ },
+ handleDownload(file) {
+ let data = {
+ name: file.name,
+ id: file.id,
+ };
+ let config = {
+ url: '/test/case/file/download',
+ method: 'post',
+ data: data,
+ responseType: 'blob'
+ };
+ this.result = this.$request(config).then(response => {
+ const content = response.data;
+ const blob = new Blob([content]);
+ if ("download" in document.createElement("a")) {
+ // 非IE下载
+ // chrome/firefox
+ let aTag = document.createElement('a');
+ aTag.download = file.name;
+ aTag.href = URL.createObjectURL(blob);
+ aTag.click();
+ URL.revokeObjectURL(aTag.href)
+ } else {
+ // IE10+下载
+ navigator.msSaveBlob(blob, this.filename)
+ }
+ }).catch(e => {
+ Message.error({message: e.message, showClose: true});
+ });
+ },
+ handleDelete(file, index) {
+ this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "?", '', {
+ confirmButtonText: this.$t('commons.confirm'),
+ callback: (action) => {
+ if (action === 'confirm') {
+ this._handleDelete(file, index);
+ }
+ }
+ });
+ },
+ _handleDelete(file, index) {
+ this.fileList.splice(index, 1);
+ this.tableData.splice(index, 1);
+ let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
+ if (i > -1) {
+ this.uploadList.splice(i, 1);
+ }
+ },
+ fileValidator(file) {
+ /// todo: 是否需要对文件内容和大小做限制
+ return file.size > 0;
+ },
+
}
}
diff --git a/frontend/src/business/components/track/case/components/TestCaseList.vue b/frontend/src/business/components/track/case/components/TestCaseList.vue
index 8b9fb910ec..b37550b99d 100644
--- a/frontend/src/business/components/track/case/components/TestCaseList.vue
+++ b/frontend/src/business/components/track/case/components/TestCaseList.vue
@@ -66,7 +66,7 @@
trigger="hover"
>
- {{ scope.row.name }}
+ {{ scope.row.name }}
@@ -107,7 +107,7 @@
:label="$t('test_track.case.status')">
-
+
@@ -174,6 +174,7 @@
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
import TestCaseDetail from "./TestCaseDetail";
+ import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
export default {
name: "TestCaseList",
components: {
@@ -192,7 +193,8 @@
ShowMoreBtn,
BatchEdit,
StatusTableItem,
- TestCaseDetail
+ TestCaseDetail,
+ ReviewStatus
},
data() {
return {
@@ -222,9 +224,9 @@
{text: this.$t('commons.api'), value: 'api'}
],
statusFilters: [
- {text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
- {text: this.$t('test_track.plan_view.pass'), value: 'Pass'},
- {text: '未通过', value: 'UnPass'},
+ {text: this.$t('test_track.case.status_prepare'), value: 'Prepare'},
+ {text: this.$t('test_track.case.status_pass'), value: 'Pass'},
+ {text: this.$t('test_track.case.status_un_pass'), value: 'UnPass'},
],
showMore: false,
buttons: [
diff --git a/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseList.vue b/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseList.vue
index 0cc7b95ead..d04dc6ab9a 100644
--- a/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseList.vue
+++ b/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseList.vue
@@ -347,11 +347,7 @@ export default {
});
this.refreshTableAndPlan();
this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser();
- this.result = this.$get('user/list', response => {
- this.executorFilters = response.data.map(u => {
- return {text: u.name, value: u.id}
- });
- });
+ this.getMaintainerOptions();
},
beforeDestroy() {
hub.$off("openFailureTestCase");
@@ -583,6 +579,9 @@ export default {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.valueArr.executor = response.data;
+ this.executorFilters = response.data.map(u => {
+ return {text: u.name, value: u.id}
+ });
});
}
}
diff --git a/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue b/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue
index e4acd96f0a..6d0da9dfb0 100644
--- a/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue
+++ b/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue
@@ -109,7 +109,7 @@
:label="$t('test_track.review_view.execute_result')">
-
+
@@ -169,6 +169,7 @@ import {_filter, _sort, checkoutTestManagerOrTestUser, hasRoles} from "../../../
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
import {ROLE_TEST_MANAGER, ROLE_TEST_USER} from "../../../../../../common/js/constants";
import TestReviewTestCaseEdit from "./TestReviewTestCaseEdit";
+import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
export default {
name: "TestReviewTestCaseList",
@@ -176,7 +177,7 @@ export default {
MsTableOperatorButton, MsTableOperator, MethodTableItem, TypeTableItem,
StatusTableItem, PriorityTableItem, StatusEdit,
ExecutorEdit, MsTipButton, TestReviewTestCaseEdit, MsTableHeader,
- NodeBreadcrumb, MsTableButton, ShowMoreBtn, BatchEdit, MsTablePagination
+ NodeBreadcrumb, MsTableButton, ShowMoreBtn, BatchEdit, MsTablePagination, ReviewStatus
},
data() {
return {
diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js
index c7c1a020b7..a8c17c0b13 100644
--- a/frontend/src/i18n/en-US.js
+++ b/frontend/src/i18n/en-US.js
@@ -504,6 +504,7 @@ export default {
connect_timeout: "Connect Timeout",
response_timeout: "Response Timeout",
follow_redirects: "Follow Redirects",
+ do_multipart_post: "Use multipart/form-data for POST",
body_upload_limit_size: "The file size does not exceed 500 MB",
condition: "condition",
condition_variable: "Variable, e.g: ${var}",
@@ -713,8 +714,8 @@ export default {
batch_delete_case: 'Batch delete',
batch_unlink: 'Batch Unlink',
project_name: "Project",
- status: 'Status',
- status_prepare: 'Prepare',
+ status: 'Review Status',
+ status_prepare: 'Not reviewed',
status_pass: 'Pass',
status_un_pass: 'UnPass',
cancel_relevance_project: "Disassociating the project will also cancel the associated test cases under the project",
diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js
index 45e25d5246..5d955b9f7c 100644
--- a/frontend/src/i18n/zh-CN.js
+++ b/frontend/src/i18n/zh-CN.js
@@ -505,6 +505,7 @@ export default {
connect_timeout: "连接超时",
response_timeout: "响应超时",
follow_redirects: "跟随重定向",
+ do_multipart_post: "对 POST 使用 multipart/form-data",
body_upload_limit_size: "上传文件大小不能超过 500 MB!",
condition: "条件",
condition_variable: "变量,例如: ${var}",
@@ -715,8 +716,8 @@ export default {
batch_delete_case: '批量删除用例',
batch_unlink: '批量取消关联',
project_name: '所属项目',
- status: '状态',
- status_prepare: '未开始',
+ status: '评审状态',
+ status_prepare: '未评审',
status_pass: '通过',
status_un_pass: '未通过',
cancel_relevance_project: "取消项目关联会同时取消该项目下已关联的测试用例",
diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js
index 468a7e7c61..30e9e04a09 100644
--- a/frontend/src/i18n/zh-TW.js
+++ b/frontend/src/i18n/zh-TW.js
@@ -505,6 +505,7 @@ export default {
connect_timeout: "連接超時",
response_timeout: "響應超時",
follow_redirects: "跟隨重定向",
+ do_multipart_post: "對 POST 使用 multipart/form-data",
body_upload_limit_size: "上傳文件大小不能超過 500 MB!",
condition: "條件",
condition_variable: "變量,例如: ${var}",
@@ -715,8 +716,8 @@ export default {
batch_delete_case: '批量刪除用例',
batch_unlink: '批量取消關聯',
project_name: '所屬項目',
- status: '狀態',
- status_prepare: '未開始',
+ status: '評審狀態',
+ status_prepare: '未評審',
status_pass: '通過',
status_un_pass: '未通過',
cancel_relevance_project: "取消項目關聯會同時取消該項目下已關聯的測試用例",