Merge branch 'master' into local-api-delimit

# Conflicts:
#	backend/src/main/resources/i18n/messages_en_US.properties
#	backend/src/main/resources/i18n/messages_zh_CN.properties
#	backend/src/main/resources/i18n/messages_zh_TW.properties
#	frontend/src/business/components/api/test/ApiTestConfig.vue
This commit is contained in:
fit2-zhao 2020-11-23 12:31:48 +08:00
commit e008baaf2e
69 changed files with 1207 additions and 335 deletions

3
.gitignore vendored
View File

@ -30,4 +30,5 @@ target
.settings .settings
.project .project
.classpath .classpath
.jython_cache .jython_cache
qywx.json

View File

@ -6,9 +6,9 @@ ARG MS_VERSION=dev
RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter
ADD backend/target/backend-1.4.jar /opt/apps COPY backend/target/backend-1.4.jar /opt/apps
ADD backend/target/classes/jmeter/ /opt/jmeter/ COPY backend/target/classes/jmeter/ /opt/jmeter/
ENV JAVA_APP_JAR=/opt/apps/backend-1.4.jar ENV JAVA_APP_JAR=/opt/apps/backend-1.4.jar

33
README_EN.md → README-EN.md Executable file → Normal file
View File

@ -7,11 +7,10 @@
MeterSphere is a one-stop open-source enterprise-class continuous testing platform. It covers functions such as tests tracking, interface testing, performance testing, team collaboration and is compatible with open-source standards such as JMeter. It helps development and testing teams to conduct highly scalable automated testing, making full use of elasticity of the cloud, and accelerating the delivery process of high-quality software. MeterSphere is a one-stop open-source enterprise-class continuous testing platform. It covers functions such as tests tracking, interface testing, performance testing, team collaboration and is compatible with open-source standards such as JMeter. It helps development and testing teams to conduct highly scalable automated testing, making full use of elasticity of the cloud, and accelerating the delivery process of high-quality software.
- Test Tracking: Far beyond the user experience of TestLink. - Test Tracking: Far beyond the user experience of TestLink.
- API Testing: Similar to Postman's experience. - API Testing: Similar to Postman's experience.
- Performance Testing: Compatible with JMeter. Support Kubernetes and Cloud Environment. High concurrency, distributed performance testing with ease. - Performance Testing: Compatible with JMeter. Support Kubernetes and Cloud Environment. High concurrency, distributed performance testing with ease.
- Team Collaboration: duo-levels tenants system, naturally support team co-op. - Team Collaboration: duo-levels tenants system, naturally support team co-op.
## Quick Start ## Quick Start
@ -26,11 +25,11 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
``` ```
## Technical advantages ## Technical advantages
- Full Life Cycle: Full coverage over all STLC phases. Starting from the Test Plan to the Report Creation phase. - Full Life Cycle: Full coverage over all STLC phases. Starting from the Test Plan to the Report Creation phase.
- Automation & Scalable: Automation for interface and performance testings. Fully adopt the elasticity of Cloud to produce a large scale of performance testing. - Automation & Scalable: Automation for interface and performance testings. Fully adopt the elasticity of Cloud to produce a large scale of performance testing.
- Continuous Testing: Seamlessly integrated with the CI tools. Supporting enterprises for "Shift left" testing. - Continuous Testing: Seamlessly integrated with the CI tools. Supporting enterprises for "Shift left" testing.
- Team Collaboration: Support different proportions of teams. Capable from a group of five to a testing center of several hundred people. - Team Collaboration: Support different proportions of teams. Capable from a group of five to a testing center of several hundred people.
## Features List ## Features List
@ -149,15 +148,13 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
</tbody> </tbody>
</table> </table>
## Technology stack ## Technology stack
- Backend: [Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_introduction.htm) - Backend: [Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_introduction.htm)
- Frontend: [Vue.js](https://vuejs.org/) - Frontend: [Vue.js](https://vuejs.org/)
- Middleware: [MySQL](https://www.mysql.com/), [Kafka](https://kafka.apache.org/) - Middleware: [MySQL](https://www.mysql.com/), [Kafka](https://kafka.apache.org/)
- Basic infrastructure: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/) - Basic infrastructure: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/)
- Test engine: [JMeter](https://jmeter.apache.org/) - Test engine: [JMeter](https://jmeter.apache.org/)
## License & Copyright ## License & Copyright
@ -165,6 +162,6 @@ Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://www.gnu.org/licenses/gpl-2.0.html <https://www.gnu.org/licenses/gpl-2.0.html>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

View File

@ -5,41 +5,41 @@
[![GitHub All Releases](https://img.shields.io/github/downloads/metersphere/metersphere/total)](https://github.com/metersphere/metersphere/releases) [![GitHub All Releases](https://img.shields.io/github/downloads/metersphere/metersphere/total)](https://github.com/metersphere/metersphere/releases)
[![TesterHome](https://img.shields.io/badge/TTF-TesterHome-2955C5.svg)](https://testerhome.com/github_statistics) [![TesterHome](https://img.shields.io/badge/TTF-TesterHome-2955C5.svg)](https://testerhome.com/github_statistics)
> [English](README_EN.md) | 中文 > [English](README-EN.md) | 中文
|Developer Wanted| | Developer Wanted |
|----------------| | ------------------------------------------------------------------------------------------------------------ |
|我们正在寻找开发者,欢迎加入我们共同打造更好用、更强大的 MeterSphere。联系我们 <metersphere@fit2cloud.com>| | 我们正在寻找开发者,欢迎加入我们共同打造更好用、更强大的 MeterSphere。联系我们 [metersphere@fit2cloud.com](mailto:metersphere@fit2cloud.com) |
MeterSphere 是一站式的开源企业级持续测试平台涵盖测试跟踪、接口测试、性能测试、团队协作等功能兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。 MeterSphere 是一站式的开源企业级持续测试平台涵盖测试跟踪、接口测试、性能测试、团队协作等功能兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。
- 测试跟踪: 远超 TestLink 的使用体验; - 测试跟踪: 远超 TestLink 的使用体验;
- 接口测试: 类似 Postman 的体验; - 接口测试: 类似 Postman 的体验;
- 性能测试: 兼容 JMeter支持 Kubernetes 和云环境,轻松支持高并发、分布式的性能测试; - 性能测试: 兼容 JMeter支持 Kubernetes 和云环境,轻松支持高并发、分布式的性能测试;
- 团队协作: 两级租户体系,天然支持团队协作。 - 团队协作: 两级租户体系,天然支持团队协作。
![产品定位](https://metersphere.oss-cn-hangzhou.aliyuncs.com/img/ct-devops.png) ![产品定位](https://metersphere.oss-cn-hangzhou.aliyuncs.com/img/ct-devops.png)
> 如需进一步了解 MeterSphere 开源项目,推荐阅读 [MeterSphere 的初心和使命](https://mp.weixin.qq.com/s/DpCt3BNgBTlV3sJ5qtPmZw) > 如需进一步了解 MeterSphere 开源项目,推荐阅读 [MeterSphere 的初心和使命](https://mp.weixin.qq.com/s/DpCt3BNgBTlV3sJ5qtPmZw)
## 在线体验 ## 在线体验
- 环境地址https://demo.metersphere.com/
- 用户名demo
- 密码P@ssw0rd123..
| :warning: 注意 | - 环境地址:<https://demo.metersphere.com/>
|:---------------------------| - 用户名demo
| 该环境仅作体验目的使用,我们会定时清理、重置数据! | - 密码P@ssw0rd123..
| 请勿修改体验环境用户的密码! |
| :warning: 注意 |
| :--------------------------- |
| 该环境仅作体验目的使用,我们会定时清理、重置数据! |
| 请勿修改体验环境用户的密码! |
| 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! | | 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! |
## 快速开始 ## 快速开始
仅需两步快速安装 MeterSphere 仅需两步快速安装 MeterSphere
1. 准备一台不小于 8 G内存的 64位 Linux 主机; 1. 准备一台不小于 8 G内存的 64位 Linux 主机;
2. 以 root 用户执行如下命令一键安装 MeterSphere。 2. 以 root 用户执行如下命令一键安装 MeterSphere。
```sh ```sh
curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/quick_start.sh | sh curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/quick_start.sh | sh
@ -47,34 +47,37 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
文档和演示视频: 文档和演示视频:
- [完整文档](https://metersphere.io/docs/) - [完整文档](https://metersphere.io/docs/)
- [演示视频](http://video.fit2cloud.com/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91202006%20MeterSphere%20v1.0%20%E5%8A%9F%E8%83%BD%E6%BC%94%E7%A4%BA.mp4) - [演示视频](http://video.fit2cloud.com/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91202006%20MeterSphere%20v1.0%20%E5%8A%9F%E8%83%BD%E6%BC%94%E7%A4%BA.mp4)
## MeterSphere 企业版 ## MeterSphere 企业版
[申请企业版使用](https://jinshuju.net/f/CzzAOe) [申请企业版使用](https://jinshuju.net/f/CzzAOe)
> 注: 企业版支持离线安装,申请通过后会提供高速下载链接 > 注: 企业版支持离线安装,申请通过后会提供高速下载链接
## 相关工具 ## 相关工具
- [Jenkins 插件](https://github.com/metersphere/jenkins-plugin) - [Jenkins 插件](https://github.com/metersphere/jenkins-plugin)
- [浏览器插件](https://github.com/metersphere/chrome-extensions) - [浏览器插件](https://github.com/metersphere/chrome-extensions)
## 版本说明 ## 版本说明
MeterSphere 版本号命名规则为v大版本.功能版本.Bug修复版本。比如 MeterSphere 版本号命名规则为v大版本.功能版本.Bug修复版本。比如
``` ```text
v1.0.1 是 v1.0.0 之后的Bug修复版本 v1.0.1 是 v1.0.0 之后的Bug修复版本
v1.1.0 是 v1.0.0 之后的功能版本。 v1.1.0 是 v1.0.0 之后的功能版本。
``` ```
像其它优秀开源项目一样MeterSphere 将每月发布一个功能版本。 像其它优秀开源项目一样MeterSphere 将每月发布一个功能版本。
## 技术优势 ## 技术优势
- 全生命周期: 能够覆盖从测试计划到测试执行、测试报告分析的不同阶段; - 全生命周期: 能够覆盖从测试计划到测试执行、测试报告分析的不同阶段;
- 自动化 & 扩展性: 支持接口和性能的自动化测试,可以充分利用云弹性实现超大规模的性能测试; - 自动化 & 扩展性: 支持接口和性能的自动化测试,可以充分利用云弹性实现超大规模的性能测试;
- 持续测试: 能够与持续集成工具无缝集成,支撑企业实现测试左移; - 持续测试: 能够与持续集成工具无缝集成,支撑企业实现测试左移;
- 团队协作: 支持不同规模的测试团队,小到几个人的测试团队、大到数百人的测试中心。 - 团队协作: 支持不同规模的测试团队,小到几个人的测试团队、大到数百人的测试中心。
## 功能列表 ## 功能列表
@ -276,17 +279,17 @@ v1.1.0 是 v1.0.0 之后的功能版本。
## 技术栈 ## 技术栈
- 后端: [Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_introduction.htm) - 后端: [Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_introduction.htm)
- 前端: [Vue.js](https://vuejs.org/) - 前端: [Vue.js](https://vuejs.org/)
- 中间件: [MySQL](https://www.mysql.com/), [Kafka](https://kafka.apache.org/) - 中间件: [MySQL](https://www.mysql.com/), [Kafka](https://kafka.apache.org/)
- 基础设施: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/) - 基础设施: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/)
- 测试引擎: [JMeter](https://jmeter.apache.org/) - 测试引擎: [JMeter](https://jmeter.apache.org/)
## 致谢 ## 致谢
- [BlazeMeter](https://www.blazemeter.com/):感谢 BlazeMeter 提供的设计思路 - [BlazeMeter](https://www.blazemeter.com/):感谢 BlazeMeter 提供的设计思路
- [JMeter](https://jmeter.apache.org/)MeterSphere 使用了 JMeter 作为测试引擎 - [JMeter](https://jmeter.apache.org/)MeterSphere 使用了 JMeter 作为测试引擎
- [Element](https://element.eleme.cn/#/):感谢 Element 提供的优秀组件库 - [Element](https://element.eleme.cn/#/):感谢 Element 提供的优秀组件库
## 加入 MeterSphere 团队 ## 加入 MeterSphere 团队
@ -304,6 +307,6 @@ Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://www.gnu.org/licenses/gpl-2.0.html <https://www.gnu.org/licenses/gpl-2.0.html>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

View File

@ -1,61 +1,65 @@
## v1.1 (已发布) ## v1.1 (已发布)
- [x] 浏览器插件支持编辑录制后的内容 - [x] 浏览器插件支持编辑录制后的内容
- [x] 插件录制的脚本支持用作接口测试 - [x] 插件录制的脚本支持用作接口测试
- [x] 动态展示性能测试报告 - [x] 动态展示性能测试报告
- [x] 优化性能测试稳定性及资源使用效率 - [x] 优化性能测试稳定性及资源使用效率
- [x] 提供 Jenkins 插件 - [x] 提供 Jenkins 插件
- [x] 提供 Swagger API 文档 - [x] 提供 Swagger API 文档
- [x] 增加针对项目的环境配置、全局变量管理功能 - [x] 增加针对项目的环境配置、全局变量管理功能
- [x] 支持 JMX/Swagger 等方式导入接口测试 - [x] 支持 JMX/Swagger 等方式导入接口测试
- [x] 支持 LDAP 登录 - [x] 支持 LDAP 登录
- [ ] 推出在线体验环境 - [ ] 推出在线体验环境
## v1.2 (已发布) ## v1.2 (已发布)
- [x] 接口测试支持前后置脚本 - [x] 接口测试支持前后置脚本
- [x] 接口测试支持常用函数 - [x] 接口测试支持常用函数
- [x] 接口测试批量执行 - [x] 接口测试批量执行
- [x] 接口测试单接口调试功能 - [x] 接口测试单接口调试功能
- [ ] 测试用例增加用例评审功能 - [ ] 测试用例增加用例评审功能
- [x] 测试用例与缺陷管理工具的集成 - [x] 测试用例与缺陷管理工具的集成
- [x] 测试用例增加批量操作类型 - [x] 测试用例增加批量操作类型
- [ ] 增加消息通知 - [ ] 增加消息通知
- [ ] 测试报告导出 - [ ] 测试报告导出
- [ ] 优化性能测试压力配置模式 - [ ] 优化性能测试压力配置模式
## v1.3 (已发布) ## v1.3 (已发布)
- [x] 测试跟踪:用例评审机制
- [x] 测试跟踪:测试计划关联用例支持跨项目 - [x] 测试跟踪:用例评审机制
- [ ] 测试跟踪:测试用例支持贴图 - [x] 测试跟踪:测试计划关联用例支持跨项目
- [x] 测试跟踪:支持思维导图格式导入用例 - [ ] 测试跟踪:测试用例支持贴图
- [x] 接口测试:增加逻辑控制环节 - [x] 测试跟踪:支持思维导图格式导入用例
- [x] 接口测试:支持场景拼接 - [x] 接口测试:增加逻辑控制环节
- [x] 接口测试:支持 SQL 类型的请求 - [x] 接口测试:支持场景拼接
- [x] 接口测试:支持自定义 Hosts - [x] 接口测试:支持 SQL 类型的请求
- [x] 接口测试:参数增加启用禁用 - [x] 接口测试:支持自定义 Hosts
- [ ] 性能测试:增加压测模式 - [x] 接口测试:参数增加启用禁用
- [x] 其他:消息通知 - [ ] 性能测试:增加压测模式
- [x] 其他:报告导出 - [x] 其他:消息通知
- [x] 其他:报告导出
## v1.4 (已发布) ## v1.4 (已发布)
- [x] 测试跟踪模块编辑测试用例支持上传附件
- [x] 支持上传并引用自定义Jar包 - [x] 测试跟踪模块编辑测试用例支持上传附件
- [x] 接口测试支持TCP协议请求 - [x] 支持上传并引用自定义Jar包
- [x] 全新的消息通知设置,支持企业微信、钉钉机器人通知 - [x] 接口测试支持TCP协议请求
- [x] 全新的消息通知设置,支持企业微信、钉钉机器人通知
## v1.5 (开发中) ## v1.5 (开发中)
- [ ] 性能测试:优化并发数、持续时间等压力配置方式
- [ ] 性能测试:支持使用了额外插件的 JMX 文件 - [ ] 性能测试:优化并发数、持续时间等压力配置方式
- [ ] 性能测试:自动修改 csv 等数据文件引用路径 - [ ] 性能测试:支持使用了额外插件的 JMX 文件
- [ ] 性能测试:优化性能测试报告展示 - [ ] 性能测试:自动修改 csv 等数据文件引用路径
- [ ] 测试跟踪:支持对接禅道同步缺陷 - [ ] 性能测试:优化性能测试报告展示
- [ ] 其他Jenkins 插件支持 pipeline 方式调用 - [ ] 测试跟踪:支持对接禅道同步缺陷
- [ ] 其他Jenkins 插件支持 pipeline 方式调用
## 规划中 ## 规划中
- [ ] 接口测试支持添加 WebSocket 协议请求
- [ ] 接口管理功能 - [ ] 接口测试支持添加 WebSocket 协议请求
- [ ] 集成云平台动态管理测试资源池 - [ ] 接口管理功能
- [ ] 支持 K8s 集群作为测试资源池 - [ ] 集成云平台动态管理测试资源池
- [ ] 移动端测试支持 - [ ] 支持 K8s 集群作为测试资源池
- [ ] UI 功能测试支持 - [ ] 移动端测试支持
- [ ] UI 功能测试支持

View File

@ -156,7 +156,7 @@
<dependency> <dependency>
<groupId>org.python</groupId> <groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId> <artifactId>jython-standalone</artifactId>
<version>2.7.0</version> <version>2.7.2</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -22,5 +22,7 @@ public class LoadTestReport implements Serializable {
private String triggerMode; private String triggerMode;
private String fileId;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }

View File

@ -643,6 +643,76 @@ public class LoadTestReportExample {
addCriterion("trigger_mode not between", value1, value2, "triggerMode"); addCriterion("trigger_mode not between", value1, value2, "triggerMode");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andFileIdIsNull() {
addCriterion("file_id is null");
return (Criteria) this;
}
public Criteria andFileIdIsNotNull() {
addCriterion("file_id is not null");
return (Criteria) this;
}
public Criteria andFileIdEqualTo(String value) {
addCriterion("file_id =", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdNotEqualTo(String value) {
addCriterion("file_id <>", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdGreaterThan(String value) {
addCriterion("file_id >", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdGreaterThanOrEqualTo(String value) {
addCriterion("file_id >=", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdLessThan(String value) {
addCriterion("file_id <", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdLessThanOrEqualTo(String value) {
addCriterion("file_id <=", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdLike(String value) {
addCriterion("file_id like", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdNotLike(String value) {
addCriterion("file_id not like", value, "fileId");
return (Criteria) this;
}
public Criteria andFileIdIn(List<String> values) {
addCriterion("file_id in", values, "fileId");
return (Criteria) this;
}
public Criteria andFileIdNotIn(List<String> values) {
addCriterion("file_id not in", values, "fileId");
return (Criteria) this;
}
public Criteria andFileIdBetween(String value1, String value2) {
addCriterion("file_id between", value1, value2, "fileId");
return (Criteria) this;
}
public Criteria andFileIdNotBetween(String value1, String value2) {
addCriterion("file_id not between", value1, value2, "fileId");
return (Criteria) this;
}
} }
public static class Criteria extends GeneratedCriteria { public static class Criteria extends GeneratedCriteria {

View File

@ -21,5 +21,7 @@ public class Project implements Serializable {
private String jiraKey; private String jiraKey;
private String zentaoId;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }

View File

@ -643,6 +643,76 @@ public class ProjectExample {
addCriterion("jira_key not between", value1, value2, "jiraKey"); addCriterion("jira_key not between", value1, value2, "jiraKey");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andZentaoIdIsNull() {
addCriterion("zentao_id is null");
return (Criteria) this;
}
public Criteria andZentaoIdIsNotNull() {
addCriterion("zentao_id is not null");
return (Criteria) this;
}
public Criteria andZentaoIdEqualTo(String value) {
addCriterion("zentao_id =", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotEqualTo(String value) {
addCriterion("zentao_id <>", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdGreaterThan(String value) {
addCriterion("zentao_id >", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdGreaterThanOrEqualTo(String value) {
addCriterion("zentao_id >=", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdLessThan(String value) {
addCriterion("zentao_id <", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdLessThanOrEqualTo(String value) {
addCriterion("zentao_id <=", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdLike(String value) {
addCriterion("zentao_id like", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotLike(String value) {
addCriterion("zentao_id not like", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdIn(List<String> values) {
addCriterion("zentao_id in", values, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotIn(List<String> values) {
addCriterion("zentao_id not in", values, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdBetween(String value1, String value2) {
addCriterion("zentao_id between", value1, value2, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotBetween(String value1, String value2) {
addCriterion("zentao_id not between", value1, value2, "zentaoId");
return (Criteria) this;
}
} }
public static class Criteria extends GeneratedCriteria { public static class Criteria extends GeneratedCriteria {

View File

@ -10,6 +10,7 @@
<result column="status" jdbcType="VARCHAR" property="status" /> <result column="status" jdbcType="VARCHAR" property="status" />
<result column="user_id" jdbcType="VARCHAR" property="userId" /> <result column="user_id" jdbcType="VARCHAR" property="userId" />
<result column="trigger_mode" jdbcType="VARCHAR" property="triggerMode" /> <result column="trigger_mode" jdbcType="VARCHAR" property="triggerMode" />
<result column="file_id" jdbcType="VARCHAR" property="fileId" />
</resultMap> </resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.LoadTestReportWithBLOBs"> <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.LoadTestReportWithBLOBs">
<result column="description" jdbcType="LONGVARCHAR" property="description" /> <result column="description" jdbcType="LONGVARCHAR" property="description" />
@ -74,7 +75,7 @@
</where> </where>
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, test_id, `name`, create_time, update_time, `status`, user_id, trigger_mode id, test_id, `name`, create_time, update_time, `status`, user_id, trigger_mode, file_id
</sql> </sql>
<sql id="Blob_Column_List"> <sql id="Blob_Column_List">
description, load_configuration description, load_configuration
@ -130,12 +131,12 @@
<insert id="insert" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs"> <insert id="insert" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
INSERT INTO load_test_report (id, test_id, `name`, INSERT INTO load_test_report (id, test_id, `name`,
create_time, update_time, `status`, create_time, update_time, `status`,
user_id, trigger_mode, description, user_id, trigger_mode, file_id,
load_configuration) description, load_configuration)
VALUES (#{id,jdbcType=VARCHAR}, #{testId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, VALUES (#{id,jdbcType=VARCHAR}, #{testId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{status,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{status,jdbcType=VARCHAR},
#{userId,jdbcType=VARCHAR}, #{triggerMode,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR}, #{userId,jdbcType=VARCHAR}, #{triggerMode,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR},
#{loadConfiguration,jdbcType=LONGVARCHAR}) #{description,jdbcType=LONGVARCHAR}, #{loadConfiguration,jdbcType=LONGVARCHAR})
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs"> <insert id="insertSelective" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
insert into load_test_report insert into load_test_report
@ -164,6 +165,9 @@
<if test="triggerMode != null"> <if test="triggerMode != null">
trigger_mode, trigger_mode,
</if> </if>
<if test="fileId != null">
file_id,
</if>
<if test="description != null"> <if test="description != null">
description, description,
</if> </if>
@ -196,6 +200,9 @@
<if test="triggerMode != null"> <if test="triggerMode != null">
#{triggerMode,jdbcType=VARCHAR}, #{triggerMode,jdbcType=VARCHAR},
</if> </if>
<if test="fileId != null">
#{fileId,jdbcType=VARCHAR},
</if>
<if test="description != null"> <if test="description != null">
#{description,jdbcType=LONGVARCHAR}, #{description,jdbcType=LONGVARCHAR},
</if> </if>
@ -237,6 +244,9 @@
<if test="record.triggerMode != null"> <if test="record.triggerMode != null">
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR}, trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
</if> </if>
<if test="record.fileId != null">
file_id = #{record.fileId,jdbcType=VARCHAR},
</if>
<if test="record.description != null"> <if test="record.description != null">
description = #{record.description,jdbcType=LONGVARCHAR}, description = #{record.description,jdbcType=LONGVARCHAR},
</if> </if>
@ -258,6 +268,7 @@
`status` = #{record.status,jdbcType=VARCHAR}, `status` = #{record.status,jdbcType=VARCHAR},
user_id = #{record.userId,jdbcType=VARCHAR}, user_id = #{record.userId,jdbcType=VARCHAR},
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR}, trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
file_id = #{record.fileId,jdbcType=VARCHAR},
description = #{record.description,jdbcType=LONGVARCHAR}, description = #{record.description,jdbcType=LONGVARCHAR},
load_configuration = #{record.loadConfiguration,jdbcType=LONGVARCHAR} load_configuration = #{record.loadConfiguration,jdbcType=LONGVARCHAR}
<if test="_parameter != null"> <if test="_parameter != null">
@ -273,7 +284,8 @@
update_time = #{record.updateTime,jdbcType=BIGINT}, update_time = #{record.updateTime,jdbcType=BIGINT},
`status` = #{record.status,jdbcType=VARCHAR}, `status` = #{record.status,jdbcType=VARCHAR},
user_id = #{record.userId,jdbcType=VARCHAR}, user_id = #{record.userId,jdbcType=VARCHAR},
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR} trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
file_id = #{record.fileId,jdbcType=VARCHAR}
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
</if> </if>
@ -302,6 +314,9 @@
<if test="triggerMode != null"> <if test="triggerMode != null">
trigger_mode = #{triggerMode,jdbcType=VARCHAR}, trigger_mode = #{triggerMode,jdbcType=VARCHAR},
</if> </if>
<if test="fileId != null">
file_id = #{fileId,jdbcType=VARCHAR},
</if>
<if test="description != null"> <if test="description != null">
description = #{description,jdbcType=LONGVARCHAR}, description = #{description,jdbcType=LONGVARCHAR},
</if> </if>
@ -312,27 +327,29 @@
where id = #{id,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR}
</update> </update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs"> <update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
update load_test_report UPDATE load_test_report
set test_id = #{testId,jdbcType=VARCHAR}, SET test_id = #{testId,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR}, `name` = #{name,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}, update_time = #{updateTime,jdbcType=BIGINT},
`status` = #{status,jdbcType=VARCHAR}, `status` = #{status,jdbcType=VARCHAR},
user_id = #{userId,jdbcType=VARCHAR}, user_id = #{userId,jdbcType=VARCHAR},
trigger_mode = #{triggerMode,jdbcType=VARCHAR}, trigger_mode = #{triggerMode,jdbcType=VARCHAR},
file_id = #{fileId,jdbcType=VARCHAR},
description = #{description,jdbcType=LONGVARCHAR}, description = #{description,jdbcType=LONGVARCHAR},
load_configuration = #{loadConfiguration,jdbcType=LONGVARCHAR} load_configuration = #{loadConfiguration,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR} WHERE id = #{id,jdbcType=VARCHAR}
</update> </update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.LoadTestReport"> <update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.LoadTestReport">
update load_test_report UPDATE load_test_report
set test_id = #{testId,jdbcType=VARCHAR}, SET test_id = #{testId,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR}, `name` = #{name,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}, update_time = #{updateTime,jdbcType=BIGINT},
`status` = #{status,jdbcType=VARCHAR}, `status` = #{status,jdbcType=VARCHAR},
user_id = #{userId,jdbcType=VARCHAR}, user_id = #{userId,jdbcType=VARCHAR},
trigger_mode = #{triggerMode,jdbcType=VARCHAR} trigger_mode = #{triggerMode,jdbcType=VARCHAR},
where id = #{id,jdbcType=VARCHAR} file_id = #{fileId,jdbcType=VARCHAR}
WHERE id = #{id,jdbcType=VARCHAR}
</update> </update>
</mapper> </mapper>

View File

@ -10,6 +10,7 @@
<result column="update_time" jdbcType="BIGINT" property="updateTime" /> <result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="tapd_id" jdbcType="VARCHAR" property="tapdId" /> <result column="tapd_id" jdbcType="VARCHAR" property="tapdId" />
<result column="jira_key" jdbcType="VARCHAR" property="jiraKey" /> <result column="jira_key" jdbcType="VARCHAR" property="jiraKey" />
<result column="zentao_id" jdbcType="VARCHAR" property="zentaoId" />
</resultMap> </resultMap>
<sql id="Example_Where_Clause"> <sql id="Example_Where_Clause">
<where> <where>
@ -70,7 +71,8 @@
</where> </where>
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, workspace_id, `name`, description, create_time, update_time, tapd_id, jira_key id, workspace_id, `name`, description, create_time, update_time, tapd_id, jira_key,
zentao_id
</sql> </sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultMap="BaseResultMap"> <select id="selectByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultMap="BaseResultMap">
select select
@ -105,10 +107,12 @@
<insert id="insert" parameterType="io.metersphere.base.domain.Project"> <insert id="insert" parameterType="io.metersphere.base.domain.Project">
insert into project (id, workspace_id, `name`, insert into project (id, workspace_id, `name`,
description, create_time, update_time, description, create_time, update_time,
tapd_id, jira_key) tapd_id, jira_key, zentao_id
)
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR}) #{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR}, #{zentaoId,jdbcType=VARCHAR}
)
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.Project"> <insert id="insertSelective" parameterType="io.metersphere.base.domain.Project">
insert into project insert into project
@ -137,6 +141,9 @@
<if test="jiraKey != null"> <if test="jiraKey != null">
jira_key, jira_key,
</if> </if>
<if test="zentaoId != null">
zentao_id,
</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null"> <if test="id != null">
@ -163,6 +170,9 @@
<if test="jiraKey != null"> <if test="jiraKey != null">
#{jiraKey,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR},
</if> </if>
<if test="zentaoId != null">
#{zentaoId,jdbcType=VARCHAR},
</if>
</trim> </trim>
</insert> </insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultType="java.lang.Long"> <select id="countByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultType="java.lang.Long">
@ -198,6 +208,9 @@
<if test="record.jiraKey != null"> <if test="record.jiraKey != null">
jira_key = #{record.jiraKey,jdbcType=VARCHAR}, jira_key = #{record.jiraKey,jdbcType=VARCHAR},
</if> </if>
<if test="record.zentaoId != null">
zentao_id = #{record.zentaoId,jdbcType=VARCHAR},
</if>
</set> </set>
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
@ -212,7 +225,8 @@
create_time = #{record.createTime,jdbcType=BIGINT}, create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT}, update_time = #{record.updateTime,jdbcType=BIGINT},
tapd_id = #{record.tapdId,jdbcType=VARCHAR}, tapd_id = #{record.tapdId,jdbcType=VARCHAR},
jira_key = #{record.jiraKey,jdbcType=VARCHAR} jira_key = #{record.jiraKey,jdbcType=VARCHAR},
zentao_id = #{record.zentaoId,jdbcType=VARCHAR}
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
</if> </if>
@ -241,6 +255,9 @@
<if test="jiraKey != null"> <if test="jiraKey != null">
jira_key = #{jiraKey,jdbcType=VARCHAR}, jira_key = #{jiraKey,jdbcType=VARCHAR},
</if> </if>
<if test="zentaoId != null">
zentao_id = #{zentaoId,jdbcType=VARCHAR},
</if>
</set> </set>
where id = #{id,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR}
</update> </update>
@ -252,7 +269,8 @@
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}, update_time = #{updateTime,jdbcType=BIGINT},
tapd_id = #{tapdId,jdbcType=VARCHAR}, tapd_id = #{tapdId,jdbcType=VARCHAR},
jira_key = #{jiraKey,jdbcType=VARCHAR} jira_key = #{jiraKey,jdbcType=VARCHAR},
zentao_id = #{zentaoId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR}
</update> </update>
</mapper> </mapper>

View File

@ -4,7 +4,7 @@
<select id="getProjectWithWorkspace" resultType="io.metersphere.dto.ProjectDTO"> <select id="getProjectWithWorkspace" resultType="io.metersphere.dto.ProjectDTO">
select p.id, p.workspace_id, p.name, p.description, p.update_time, select p.id, p.workspace_id, p.name, p.description, p.update_time,
p.create_time, w.id as workspaceId, w.name as workspaceName, p.tapd_id, p.jira_key p.create_time, w.id as workspaceId, w.name as workspaceName, p.tapd_id, p.jira_key, p.zentao_id
from project p from project p
join workspace w on p.workspace_id = w.id join workspace w on p.workspace_id = w.id
<where> <where>
@ -37,6 +37,9 @@
<if test="platform == 'Tapd'"> <if test="platform == 'Tapd'">
tapd_id = null tapd_id = null
</if> </if>
<if test="platform == 'Zentao'">
zentao_id = null
</if>
</set> </set>
where project.id in (select id from (select id where project.id in (select id from (select id
from project from project

View File

@ -49,8 +49,16 @@
<select id="getOrgMemberList" resultType="io.metersphere.base.domain.User"> <select id="getOrgMemberList" resultType="io.metersphere.base.domain.User">
SELECT DISTINCT * FROM ( SELECT DISTINCT * FROM (
SELECT `user`.* FROM user_role JOIN `user` ON user_role.user_id = `user`.id SELECT `user`.*
WHERE user_role.source_id = #{orgMember.organizationId} FROM user_role JOIN `user`
ON user_role.user_id = `user`.id
WHERE user_role.source_id in
(
SELECT id FROM workspace w
WHERE w.organization_id = #{orgMember.organizationId}
UNION
SELECT #{orgMember.organizationId} AS id FROM dual
)
<if test="orgMember.name != null"> <if test="orgMember.name != null">
AND `user`.name like CONCAT('%', #{orgMember.name},'%') AND `user`.name like CONCAT('%', #{orgMember.name},'%')
</if> </if>

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants; package io.metersphere.commons.constants;
public enum IssuesManagePlatform { public enum IssuesManagePlatform {
Tapd, Jira, Local Tapd, Jira, Local, Zentao
} }

View File

@ -3,6 +3,7 @@ package io.metersphere.commons.constants;
public class RoleConstants { public class RoleConstants {
public final static String ADMIN = "admin"; public final static String ADMIN = "admin";
public final static String ORG_ADMIN = "org_admin"; public final static String ORG_ADMIN = "org_admin";
public final static String ORG_MEMBER = "org_member";
public final static String TEST_VIEWER = "test_viewer"; public final static String TEST_VIEWER = "test_viewer";
public final static String TEST_MANAGER = "test_manager"; public final static String TEST_MANAGER = "test_manager";
public final static String TEST_USER = "test_user"; public final static String TEST_USER = "test_user";

View File

@ -16,5 +16,5 @@ public class ProjectDTO {
private Long updateTime; private Long updateTime;
private String tapdId; private String tapdId;
private String jiraKey; private String jiraKey;
private String zentaoId;
} }

View File

@ -24,6 +24,7 @@ import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -99,10 +100,10 @@ public class MailService {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom(javaMailSender.getUsername()); helper.setFrom(javaMailSender.getUsername());
if (StringUtils.equals(type, NoticeConstants.API)) { if (StringUtils.equals(type, NoticeConstants.API)) {
helper.setSubject("MeterSphere平台" + Translator.get("task_notification")); helper.setSubject("MeterSphere平台" + Translator.get("task_notification_jenkins"));
} }
if (StringUtils.equals(type, NoticeConstants.SCHEDULE)) { if (StringUtils.equals(type, NoticeConstants.SCHEDULE)) {
helper.setSubject("MeterSphere平台" + Translator.get("task_notification_")); helper.setSubject("MeterSphere平台" + Translator.get("task_notification"));
} }
String[] users; String[] users;
List<String> emails = new ArrayList<>(); List<String> emails = new ArrayList<>();
@ -113,7 +114,11 @@ public class MailService {
users = emails.toArray(new String[0]); users = emails.toArray(new String[0]);
helper.setText(getContent(Template, context), true); helper.setText(getContent(Template, context), true);
helper.setTo(users); helper.setTo(users);
javaMailSender.send(mimeMessage); try {
javaMailSender.send(mimeMessage);
} catch (MailException e) {
LogUtil.error(e);
}
} }
//测试评审 //测试评审

View File

@ -16,6 +16,9 @@ import io.metersphere.performance.controller.request.ReportRequest;
import io.metersphere.performance.service.ReportService; import io.metersphere.performance.service.ReportService;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -130,4 +133,13 @@ public class PerformanceReportController {
public void deleteReportBatch(@RequestBody DeleteReportRequest reportRequest) { public void deleteReportBatch(@RequestBody DeleteReportRequest reportRequest) {
reportService.deleteReportBatch(reportRequest); reportService.deleteReportBatch(reportRequest);
} }
@GetMapping("/jtl/download/{reportId}")
public ResponseEntity<byte[]> downloadJtl(@PathVariable String reportId) {
byte[] bytes = reportService.downloadJtl(reportId);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + reportId + ".jtl\"")
.body(bytes);
}
} }

View File

@ -39,34 +39,36 @@ public class PerformanceNoticeTask {
private LoadTestReportMapper loadTestReportMapper; private LoadTestReportMapper loadTestReportMapper;
private final ExecutorService executorService = Executors.newFixedThreadPool(20); private final ExecutorService executorService = Executors.newFixedThreadPool(20);
private boolean isRunning = true;
@PreDestroy private boolean isRunning=false;
/*@PreDestroy
public void preDestroy() { public void preDestroy() {
isRunning = false; isRunning = false;
} }*/
public void registerNoticeTask(LoadTestReportWithBLOBs loadTestReport) { public void registerNoticeTask(LoadTestReportWithBLOBs loadTestReport) {
int count = 20; isRunning=true;
while (count-- > 0) { executorService.submit(() -> {
LoadTestReportWithBLOBs loadTestReportFromDatabase = loadTestReportMapper.selectByPrimaryKey(loadTestReport.getId()); LogUtil.info("性能测试定时任务");
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Completed.name())) { while (isRunning) {
isRunning = false; LoadTestReportWithBLOBs loadTestReportFromDatabase = loadTestReportMapper.selectByPrimaryKey(loadTestReport.getId());
sendSuccessNotice(loadTestReportFromDatabase); if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Completed.name())) {
return; sendSuccessNotice(loadTestReportFromDatabase);
isRunning=false;
}
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Error.name())) {
sendFailNotice(loadTestReportFromDatabase);
isRunning=false;
}
try {
//查询定时任务是否关闭
Thread.sleep(1000 * 30);// 每分钟检查 loadtest 的状态
} catch (InterruptedException e) {
LogUtil.error(e);
}
} }
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Error.name())) { });
isRunning = false;
sendFailNotice(loadTestReportFromDatabase);
return;
}
count--;
try {
Thread.sleep(1000 * 4L);// 每分钟检查 loadtest 的状态
} catch (InterruptedException e) {
LogUtil.error(e);
}
}
} }
public void sendSuccessNotice(LoadTestReportWithBLOBs loadTestReport) { public void sendSuccessNotice(LoadTestReportWithBLOBs loadTestReport) {

View File

@ -785,7 +785,9 @@ public class JmeterDocumentParser implements DocumentParser {
threadGroup.appendChild(createStringProp(document, "LogFilename", "")); threadGroup.appendChild(createStringProp(document, "LogFilename", ""));
// bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空 // bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空
// threadGroup.appendChild(createStringProp(document, "Iterations", "1")); // threadGroup.appendChild(createStringProp(document, "Iterations", "1"));
threadGroup.appendChild(createStringProp(document, "Unit", "M")); // threadGroup.appendChild(createStringProp(document, "Unit", "M"));
// 单位改成秒
threadGroup.appendChild(createStringProp(document, "Unit", "S"));
} }
private void processCheckoutTimer(Element element) { private void processCheckoutTimer(Element element) {
@ -878,6 +880,24 @@ public class JmeterDocumentParser implements DocumentParser {
} }
private void processVariableThroughputTimer(Element variableThroughputTimer) { private void processVariableThroughputTimer(Element variableThroughputTimer) {
Object durations = context.getProperty("duration");
Integer duration;
if (durations instanceof List) {
Object o = ((List<?>) durations).get(0);
duration = (Integer) o;
((List<?>) durations).remove(0);
} else {
duration = (Integer) durations;
}
Object rpsLimits = context.getProperty("rpsLimit");
String rpsLimit;
if (rpsLimits instanceof List) {
Object o = ((List<?>) rpsLimits).get(0);
((List<?>) rpsLimits).remove(0);
rpsLimit = o.toString();
} else {
rpsLimit = rpsLimits.toString();
}
if (variableThroughputTimer.getChildNodes().getLength() > 0) { if (variableThroughputTimer.getChildNodes().getLength() > 0) {
final NodeList childNodes = variableThroughputTimer.getChildNodes(); final NodeList childNodes = variableThroughputTimer.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) { for (int i = 0; i < childNodes.getLength(); i++) {
@ -903,27 +923,9 @@ public class JmeterDocumentParser implements DocumentParser {
stringPropCount++; stringPropCount++;
} else { } else {
stringPropCount = 0; stringPropCount = 0;
Object durations = context.getProperty("duration");// 传入的是分钟数, 需要转化成秒数 prop.getFirstChild().setNodeValue(String.valueOf(duration));
Integer duration;
if (durations instanceof List) {
Object o = ((List<?>) durations).get(0);
duration = (Integer) o;
((List<?>) durations).remove(0);
} else {
duration = (Integer) durations;
}
prop.getFirstChild().setNodeValue(String.valueOf(duration * 60));
continue; continue;
} }
Object rpsLimits = context.getProperty("rpsLimit");
String rpsLimit;
if (rpsLimits instanceof List) {
Object o = ((List<?>) rpsLimits).get(0);
((List<?>) rpsLimits).remove(0);
rpsLimit = o.toString();
} else {
rpsLimit = rpsLimits.toString();
}
prop.getFirstChild().setNodeValue(rpsLimit); prop.getFirstChild().setNodeValue(rpsLimit);
} }
} }

View File

@ -207,6 +207,7 @@ public class PerformanceTestService {
@Transactional(noRollbackFor = MSException.class)// 保存失败的信息 @Transactional(noRollbackFor = MSException.class)// 保存失败的信息
public String run(RunTestPlanRequest request) { public String run(RunTestPlanRequest request) {
LogUtil.info("性能测试run测试");
final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId()); final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
if (request.getUserId() != null) { if (request.getUserId() != null) {
loadTest.setUserId(request.getUserId()); loadTest.setUserId(request.getUserId());

View File

@ -13,11 +13,13 @@ import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.controller.request.OrderRequest; import io.metersphere.controller.request.OrderRequest;
import io.metersphere.dto.LogDetailDTO; import io.metersphere.dto.LogDetailDTO;
import io.metersphere.dto.ReportDTO; import io.metersphere.dto.ReportDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.performance.base.*; import io.metersphere.performance.base.*;
import io.metersphere.performance.controller.request.DeleteReportRequest; import io.metersphere.performance.controller.request.DeleteReportRequest;
import io.metersphere.performance.controller.request.ReportRequest; import io.metersphere.performance.controller.request.ReportRequest;
import io.metersphere.performance.engine.Engine; import io.metersphere.performance.engine.Engine;
import io.metersphere.performance.engine.EngineFactory; import io.metersphere.performance.engine.EngineFactory;
import io.metersphere.service.FileService;
import io.metersphere.service.TestResourceService; import io.metersphere.service.TestResourceService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -47,6 +49,8 @@ public class ReportService {
private TestResourceService testResourceService; private TestResourceService testResourceService;
@Resource @Resource
private LoadTestReportDetailMapper loadTestReportDetailMapper; private LoadTestReportDetailMapper loadTestReportDetailMapper;
@Resource
private FileService fileService;
public List<ReportDTO> getRecentReportList(ReportRequest request) { public List<ReportDTO> getRecentReportList(ReportRequest request) {
List<OrderRequest> orders = new ArrayList<>(); List<OrderRequest> orders = new ArrayList<>();
@ -168,7 +172,10 @@ public class ReportService {
public void checkReportStatus(String reportId) { public void checkReportStatus(String reportId) {
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId); LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
String reportStatus = loadTestReport.getStatus(); String reportStatus = "";
if (loadTestReport != null) {
reportStatus = loadTestReport.getStatus();
}
if (StringUtils.equals(PerformanceTestStatus.Error.name(), reportStatus)) { if (StringUtils.equals(PerformanceTestStatus.Error.name(), reportStatus)) {
MSException.throwException("Report generation error!"); MSException.throwException("Report generation error!");
} }
@ -268,4 +275,12 @@ public class ReportService {
String content = getContent(id, ReportKeys.ResponseCodeChart); String content = getContent(id, ReportKeys.ResponseCodeChart);
return JSON.parseArray(content, ChartsData.class); return JSON.parseArray(content, ChartsData.class);
} }
public byte[] downloadJtl(String reportId) {
LoadTestReportWithBLOBs report = getReport(reportId);
if (StringUtils.isBlank(report.getFileId())) {
throw new RuntimeException(Translator.get("load_test_report_file_not_exist"));
}
return fileService.loadFileAsBytes(report.getFileId());
}
} }

View File

@ -171,12 +171,11 @@ public class OrganizationService {
SessionUser sessionUser = SessionUtils.getUser(); SessionUser sessionUser = SessionUtils.getUser();
UserDTO user = userService.getUserDTO(sessionUser.getId()); UserDTO user = userService.getUserDTO(sessionUser.getId());
List<String> collect = user.getUserRoles().stream() List<String> collect = user.getUserRoles().stream()
.filter(ur -> RoleConstants.ORG_ADMIN.equals(ur.getRoleId())) .filter(ur -> RoleConstants.ORG_ADMIN.equals(ur.getRoleId()) || RoleConstants.ORG_MEMBER.equals(ur.getRoleId()))
.map(UserRole::getSourceId) .map(UserRole::getSourceId)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!collect.contains(organizationId)) { if (!collect.contains(organizationId)) {
MSException.throwException(Translator.get("organization_does_not_belong_to_user")); MSException.throwException(Translator.get("organization_does_not_belong_to_user"));
} }
} }
} }

View File

@ -2,6 +2,7 @@ package io.metersphere.track.controller;
import io.metersphere.base.domain.Issues; import io.metersphere.base.domain.Issues;
import io.metersphere.track.issue.PlatformUser; import io.metersphere.track.issue.PlatformUser;
import io.metersphere.track.issue.ZentaoBuild;
import io.metersphere.track.service.IssuesService; import io.metersphere.track.service.IssuesService;
import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesRequest;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -42,8 +43,19 @@ public class TestCaseIssuesController {
} }
@GetMapping("/tapd/user/{caseId}") @GetMapping("/tapd/user/{caseId}")
public List<PlatformUser> getPlatformUsers(@PathVariable String caseId) { public List<PlatformUser> getTapdUsers(@PathVariable String caseId) {
return issuesService.getTapdProjectUsers(caseId); return issuesService.getTapdProjectUsers(caseId);
} }
@GetMapping("/zentao/user/{caseId}")
public List<PlatformUser> getZentaoUsers(@PathVariable String caseId) {
return issuesService.getZentaoUsers(caseId);
}
@GetMapping("/zentao/builds/{caseId}")
public List<ZentaoBuild> getZentaoBuilds(@PathVariable String caseId) {
return issuesService.getZentaoBuilds(caseId);
}
} }

View File

@ -10,11 +10,13 @@ import java.util.List;
public class IssueFactory { public class IssueFactory {
public static AbstractIssuePlatform createPlatform(String platform, IssuesRequest addIssueRequest) { public static AbstractIssuePlatform createPlatform(String platform, IssuesRequest addIssueRequest) {
if (StringUtils.equals(IssuesManagePlatform.Tapd.toString(), platform)) { if (StringUtils.equals(IssuesManagePlatform.Tapd.toString(), platform)) {
return new TapdIssue(addIssueRequest); return new TapdPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.Jira.toString(), platform)) { } else if (StringUtils.equals(IssuesManagePlatform.Jira.toString(), platform)) {
return new JiraIssue(addIssueRequest); return new JiraPlatform(addIssueRequest);
} else if (StringUtils.equals("LOCAL", platform)) { } else if (StringUtils.equals(IssuesManagePlatform.Zentao.toString(), platform)) {
return new LocalIssue(addIssueRequest); return new ZentaoPlatform(addIssueRequest);
} else if (StringUtils.equals("LOCAL", platform)) {
return new LocalPlatform(addIssueRequest);
} }
return null; return null;
} }

View File

@ -28,10 +28,10 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class JiraIssue extends AbstractIssuePlatform { public class JiraPlatform extends AbstractIssuePlatform {
public JiraIssue(IssuesRequest issuesRequest) { public JiraPlatform(IssuesRequest issuesRequest) {
super(issuesRequest); super(issuesRequest);
} }

View File

@ -10,9 +10,9 @@ import io.metersphere.track.request.testcase.IssuesRequest;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public class LocalIssue extends AbstractIssuePlatform { public class LocalPlatform extends AbstractIssuePlatform {
public LocalIssue(IssuesRequest issuesRequest) { public LocalPlatform(IssuesRequest issuesRequest) {
super(issuesRequest); super(issuesRequest);
} }

View File

@ -24,10 +24,10 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class TapdIssue extends AbstractIssuePlatform { public class TapdPlatform extends AbstractIssuePlatform {
public TapdIssue(IssuesRequest issueRequest) { public TapdPlatform(IssuesRequest issueRequest) {
super(issueRequest); super(issueRequest);
} }

View File

@ -0,0 +1,9 @@
package io.metersphere.track.issue;
import lombok.Data;
@Data
public class ZentaoBuild {
private String id;
private String name;
}

View File

@ -0,0 +1,300 @@
package io.metersphere.track.issue;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.request.testcase.IssuesRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.*;
import java.util.stream.Collectors;
public class ZentaoPlatform extends AbstractIssuePlatform {
/**
* zentao account
*/
private final String account;
/**
* zentao password
*/
private final String password;
/**
* zentao url eg:http://x.x.x.x/zentao
*/
private final String url;
public ZentaoPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
String config = getPlatformConfig(IssuesManagePlatform.Zentao.toString());
JSONObject object = JSON.parseObject(config);
this.account = object.getString("account");
this.password = object.getString("password");
this.url = object.getString("url");
}
@Override
String getProjectId() {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project.getZentaoId();
}
@Override
public List<Issues> getIssue() {
List<Issues> list = new ArrayList<>();
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andTestCaseIdEqualTo(testCaseId);
List<Issues> issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Zentao.toString());
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
issuesIds.forEach(issuesId -> {
Issues dto = getZentaoIssues(issuesId);
if (StringUtils.isBlank(dto.getId())) {
// 缺陷不存在解除用例和缺陷的关联
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
issuesExample.createCriteria()
.andTestCaseIdEqualTo(testCaseId)
.andIssuesIdEqualTo(issuesId);
testCaseIssuesMapper.deleteByExample(issuesExample);
issuesMapper.deleteByPrimaryKey(issuesId);
} else {
dto.setPlatform(IssuesManagePlatform.Zentao.toString());
// 缺陷状态为 关闭则不显示
if (!StringUtils.equals("closed", dto.getStatus())) {
list.add(dto);
}
}
});
return list;
}
private Issues getZentaoIssues(String bugId) {
String session = login();
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getModel-bug-getById-bugID={bugId}?zentaosid=" + session,
HttpMethod.POST, requestEntity, String.class, bugId);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
LogUtil.info("bug id is " + bugId + obj);
if (obj != null) {
JSONObject bug = obj.getJSONObject("data");
String id = bug.getString("id");
String title = bug.getString("title");
String description = bug.getString("steps");
Long createTime = bug.getLong("openedDate");
String status = bug.getString("status");
String reporter = bug.getString("openedBy");
int deleted = bug.getInteger("deleted");
if (deleted == 1) {
return new Issues();
}
Issues issues = new Issues();
issues.setId(id);
issues.setTitle(title);
issues.setDescription(description);
issues.setCreateTime(createTime);
issues.setStatus(status);
issues.setReporter(reporter);
return issues;
}
} catch (Exception e) {
LogUtil.error("get zentao bug fail " + e.getMessage());
}
return new Issues();
}
@Override
public void addIssue(IssuesRequest issuesRequest) {
String session = login();
String projectId = getProjectId();
if (StringUtils.isBlank(projectId)) {
MSException.throwException("add zentao bug fail, project zentao id is null");
}
if (StringUtils.isBlank(session)) {
MSException.throwException("session is null");
}
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("product", projectId);
paramMap.add("title", issuesRequest.getTitle());
paramMap.add("steps", issuesRequest.getContent());
if (!CollectionUtils.isEmpty(issuesRequest.getZentaoBuilds())) {
List<String> builds = issuesRequest.getZentaoBuilds();
builds.forEach(build -> {
paramMap.add("openedBuild[]", build);
});
} else {
paramMap.add("openedBuild", "trunk");
}
if (StringUtils.isNotBlank(issuesRequest.getZentaoUser())) {
paramMap.add("assignedTo", issuesRequest.getZentaoUser());
}
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(paramMap, new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getModel-bug-create.json?zentaosid=" + session, HttpMethod.POST, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
LogUtil.info("add zentao bug " + obj);
if (obj != null) {
JSONObject data = obj.getJSONObject("data");
String id = data.getString("id");
if (StringUtils.isNotBlank(id)) {
// 用例与第三方缺陷平台中的缺陷关联
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(id);
testCaseIssues.setTestCaseId(testCaseId);
testCaseIssuesMapper.insert(testCaseIssues);
IssuesExample issuesExample = new IssuesExample();
issuesExample.createCriteria().andIdEqualTo(id)
.andPlatformEqualTo(IssuesManagePlatform.Zentao.toString());
if (issuesMapper.selectByExample(issuesExample).size() <= 0) {
// 插入缺陷表
Issues issues = new Issues();
issues.setId(id);
issues.setPlatform(IssuesManagePlatform.Zentao.toString());
issuesMapper.insert(issues);
}
}
}
}
@Override
public void deleteIssue(String id) {
}
@Override
public void testAuth() {
try {
login();
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException("验证失败!");
}
}
private String login() {
String session = getSession();
String loginUrl = url + "user-login.json?zentaosid=" + session;
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("account", account);
paramMap.add("password", password);
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(paramMap, new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(loginUrl, HttpMethod.POST, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
JSONObject user = obj.getJSONObject("user");
if (user == null) {
LogUtil.error("login fail");
LogUtil.error(obj);
// 登录失败获取的session无效置空session
MSException.throwException("zentao login fail");
}
String username = user.getString("account");
if (!StringUtils.equals(username, account)) {
LogUtil.error("login failinconsistent users");
MSException.throwException("zentao login fail");
}
return session;
}
private String getSession() {
RestTemplate restTemplate = new RestTemplate();
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(new HttpHeaders());
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getsessionid.json", HttpMethod.GET, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
JSONObject data = obj.getJSONObject("data");
String session = data.getString("sessionID");
return session;
}
@Override
public List<PlatformUser> getPlatformUser() {
String session = login();
HttpHeaders httpHeaders = new HttpHeaders();
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(httpHeaders);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getModel-user-getList?zentaosid=" + session,
HttpMethod.GET, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
LogUtil.info("zentao user " + obj);
JSONArray data = obj.getJSONArray("data");
List<PlatformUser> users = new ArrayList<>();
for (int i = 0; i < data.size(); i++) {
JSONObject o = data.getJSONObject(i);
PlatformUser platformUser = new PlatformUser();
String account = o.getString("account");
String username = o.getString("realname");
platformUser.setName(username);
platformUser.setUser(account);
users.add(platformUser);
}
return users;
}
public List<ZentaoBuild> getBuilds() {
String session = login();
String projectId = getProjectId();
HttpHeaders httpHeaders = new HttpHeaders();
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(httpHeaders);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getModel-build-getProductBuildPairs-productID={projectId}?zentaosid=" + session,
HttpMethod.GET, requestEntity, String.class, projectId);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
LogUtil.info("zentao builds" + obj);
JSONObject data = obj.getJSONObject("data");
Map<String,Object> maps = data.getInnerMap();
List<ZentaoBuild> list = new ArrayList<>();
for (Map.Entry map : maps.entrySet()) {
ZentaoBuild build = new ZentaoBuild();
String id = (String) map.getKey();
if (StringUtils.isNotBlank(id)) {
build.setId((String) map.getKey());
build.setName((String) map.getValue());
list.add(build);
}
}
return list;
}
}

View File

@ -0,0 +1,26 @@
package io.metersphere.track.issue;
import io.metersphere.commons.utils.EncryptUtils;
public class ZentaoUtils {
/**
* @param code Zentao 应用代号
* @param key Zentao 密钥
* @return token
*/
public static String getToken(String code, String key, String time) {
return (String) EncryptUtils.md5Encrypt(code + key + time);
}
/**
* @param url Zentao url
* @param code Zentao 应用代号
* @param key Zentao 密钥
* @return url
*/
public static String getUrl(String url, String code, String key) {
String time = String.valueOf(System.currentTimeMillis());;
return url + "api.php?" + "code=" + code + "&time=" + time + "&token=" + getToken(code, key, time);
}
}

View File

@ -13,4 +13,12 @@ public class IssuesRequest {
private String projectId; private String projectId;
private String testCaseId; private String testCaseId;
private List<String> tapdUsers; private List<String> tapdUsers;
/**
* zentao bug 处理人
*/
private String zentaoUser;
/**
* zentao bug 影响版本
*/
private List<String> zentaoBuilds;
} }

View File

@ -19,9 +19,7 @@ import io.metersphere.notice.service.NoticeService;
import io.metersphere.notice.service.WxChatTaskService; import io.metersphere.notice.service.WxChatTaskService;
import io.metersphere.service.IntegrationService; import io.metersphere.service.IntegrationService;
import io.metersphere.service.ProjectService; import io.metersphere.service.ProjectService;
import io.metersphere.track.issue.AbstractIssuePlatform; import io.metersphere.track.issue.*;
import io.metersphere.track.issue.IssueFactory;
import io.metersphere.track.issue.PlatformUser;
import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesRequest;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -63,9 +61,11 @@ public class IssuesService {
boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString()); boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString());
boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString()); boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString());
boolean zentao = isIntegratedPlatform(orgId, IssuesManagePlatform.Zentao.toString());
String tapdId = getTapdProjectId(issuesRequest.getTestCaseId()); String tapdId = getTapdProjectId(issuesRequest.getTestCaseId());
String jiraKey = getJiraProjectKey(issuesRequest.getTestCaseId()); String jiraKey = getJiraProjectKey(issuesRequest.getTestCaseId());
String zentaoId = getZentaoProjectId(issuesRequest.getTestCaseId());
List<String> platforms = new ArrayList<>(); List<String> platforms = new ArrayList<>();
@ -82,7 +82,13 @@ public class IssuesService {
} }
} }
if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey)) { if (zentao) {
if (StringUtils.isNotBlank(zentaoId)) {
platforms.add(IssuesManagePlatform.Zentao.name());
}
}
if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey) && StringUtils.isBlank(zentaoId)) {
platforms.add("LOCAL"); platforms.add("LOCAL");
} }
@ -122,6 +128,7 @@ public class IssuesService {
boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString()); boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString());
boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString()); boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString());
boolean zentao = isIntegratedPlatform(orgId, IssuesManagePlatform.Zentao.toString());
List<String> platforms = new ArrayList<>(); List<String> platforms = new ArrayList<>();
if (tapd) { if (tapd) {
@ -140,6 +147,13 @@ public class IssuesService {
} }
} }
if (zentao) {
String zentaoId = getZentaoProjectId(caseId);
if (StringUtils.isNotBlank(zentaoId)) {
platforms.add(IssuesManagePlatform.Zentao.name());
}
}
platforms.add("LOCAL"); platforms.add("LOCAL");
IssuesRequest issueRequest = new IssuesRequest(); IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setTestCaseId(caseId); issueRequest.setTestCaseId(caseId);
@ -152,18 +166,24 @@ public class IssuesService {
return list; return list;
} }
public String getTapdProjectId(String testCaseId) { private String getTapdProjectId(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId); TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId()); Project project = projectService.getProjectById(testCase.getProjectId());
return project.getTapdId(); return project.getTapdId();
} }
public String getJiraProjectKey(String testCaseId) { private String getJiraProjectKey(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId); TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId()); Project project = projectService.getProjectById(testCase.getProjectId());
return project.getJiraKey(); return project.getJiraKey();
} }
private String getZentaoProjectId(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project.getZentaoId();
}
/** /**
* 是否关联平台 * 是否关联平台
*/ */
@ -189,6 +209,13 @@ public class IssuesService {
return platform.getPlatformUser(); return platform.getPlatformUser();
} }
public List<PlatformUser> getZentaoUsers(String caseId) {
IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setTestCaseId(caseId);
AbstractIssuePlatform platform = IssueFactory.createPlatform(IssuesManagePlatform.Zentao.name(), issueRequest);
return platform.getPlatformUser();
}
public void deleteIssue(String id) { public void deleteIssue(String id) {
issuesMapper.deleteByPrimaryKey(id); issuesMapper.deleteByPrimaryKey(id);
} }
@ -200,4 +227,11 @@ public class IssuesService {
} }
return context; return context;
} }
public List<ZentaoBuild> getZentaoBuilds(String caseId) {
IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setTestCaseId(caseId);
ZentaoPlatform platform = (ZentaoPlatform) IssueFactory.createPlatform(IssuesManagePlatform.Zentao.name(), issueRequest);
return platform.getBuilds();
}
} }

View File

@ -457,12 +457,19 @@ public class TestCaseService {
if (t.getMethod().equals("manual")) { if (t.getMethod().equals("manual")) {
String steps = t.getSteps(); String steps = t.getSteps();
String setp = ""; String setp = "";
if (steps.contains("null") && !steps.contains("\"null\"")) { setp = steps;
setp = steps.replace("null", "\"\""); JSONArray jsonArray = null;
} else {
setp = steps; //解决旧版本保存用例导出报错
try {
jsonArray = JSON.parseArray(setp);
} catch (Exception e) {
if (steps.contains("null") && !steps.contains("\"null\"")) {
setp = steps.replace("null", "\"\"");
jsonArray = JSON.parseArray(setp);
}
} }
JSONArray jsonArray = JSON.parseArray(setp);
for (int j = 0; j < jsonArray.size(); j++) { for (int j = 0; j < jsonArray.size(); j++) {
int num = j + 1; int num = j + 1;
step.append(num + "." + jsonArray.getJSONObject(j).getString("desc") + "\n"); step.append(num + "." + jsonArray.getJSONObject(j).getString("desc") + "\n");

View File

@ -0,0 +1,2 @@
ALTER TABLE load_test_report
ADD file_id VARCHAR(50) NULL;

View File

@ -0,0 +1,2 @@
INSERT INTO role (id, name, description, type, create_time, update_time)
VALUES ('org_member', '组织成员', NULL, NULL, unix_timestamp() * 1000, unix_timestamp() * 1000);

View File

@ -0,0 +1 @@
alter table project add zentao_id varchar(50) null;

View File

@ -0,0 +1,4 @@
alter table issues drop primary key;
alter table issues
add constraint issues_pk
primary key (id, platform);

View File

@ -170,6 +170,8 @@ task_defect_notification=Task defect notification
task_notification=Jenkins Task notification task_notification=Jenkins Task notification
task_notification_=Timing task result notification task_notification_=Timing task result notification
api_definition_url_not_repeating=The interface request address already exists api_definition_url_not_repeating=The interface request address already exists
task_notification_jenkins=Jenkins Task notification
task_notification=Result notification

View File

@ -170,4 +170,6 @@ test_plan_notification=测试计划通知
task_defect_notification=缺陷任务通知 task_defect_notification=缺陷任务通知
task_notification=jenkins任务通知 task_notification=jenkins任务通知
task_notification_=定时任务结果通知 task_notification_=定时任务结果通知
api_definition_url_not_repeating=接口请求地址已经存在 api_definition_url_not_repeating=接口请求地址已经存在
task_notification_jenkins=jenkins任务通知
task_notification=任务通知

View File

@ -169,6 +169,9 @@ check_owner_comment=當前用戶沒有操作此評論的權限
upload_content_is_null=導入內容為空 upload_content_is_null=導入內容為空
test_plan_notification=測試計畫通知 test_plan_notification=測試計畫通知
task_defect_notification=缺陷任務通知 task_defect_notification=缺陷任務通知
task_notification_jenkins=jenkins任務通知
task_notification=任務通知
task_notification=jenkins任務通知 task_notification=jenkins任務通知
task_notification_=定時任務通知 task_notification_=定時任務通知
api_definition_url_not_repeating=接口請求地址已經存在 api_definition_url_not_repeating=接口請求地址已經存在

View File

@ -57,9 +57,9 @@ export default {
if (header.default !== undefined) { if (header.default !== undefined) {
this.licenseHeader = "LicenseMessage"; this.licenseHeader = "LicenseMessage";
} }
//
if (display.default !== undefined) { if (display.default !== undefined) {
display.default.valid(this); display.default.showHome(this);
} }
} else { } else {
window.location.href = "/login" window.location.href = "/login"

View File

@ -1,5 +1,5 @@
<template> <template>
<ms-container v-loading="loading" :element-loading-text="$t('api_report.running')"> <ms-container v-loading="loading">
<ms-main-container> <ms-main-container>
<el-card> <el-card>
<section class="report-container" v-if="this.report.testId"> <section class="report-container" v-if="this.report.testId">
@ -23,10 +23,12 @@
</el-tabs> </el-tabs>
</el-col> </el-col>
<el-col :span="16" style="margin-top: 40px;"> <el-col :span="16" style="margin-top: 40px;">
<ms-request-result-tail v-if="isRequestResult" :request-type="requestType" :request="request" :scenario-name="scenarioName"/> <ms-request-result-tail v-if="isRequestResult" :request-type="requestType" :request="request"
:scenario-name="scenarioName"/>
</el-col> </el-col>
</el-row> </el-row>
<ms-api-report-export v-if="reportExportVisible" id="apiTestReport" :title="report.testName" :content="content" :total-time="totalTime"/> <ms-api-report-export v-if="reportExportVisible" id="apiTestReport" :title="report.testName"
:content="content" :total-time="totalTime"/>
</main> </main>
</section> </section>
</el-card> </el-card>
@ -44,7 +46,7 @@ import MsScenarioResults from "./components/ScenarioResults";
import MsContainer from "@/business/components/common/components/MsContainer"; import MsContainer from "@/business/components/common/components/MsContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer"; import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import MsApiReportExport from "./ApiReportExport"; import MsApiReportExport from "./ApiReportExport";
import {exportPdf} from "../../../../common/js/utils"; import {exportPdf} from "@/common/js/utils";
import html2canvas from "html2canvas"; import html2canvas from "html2canvas";
import MsApiReportViewHeader from "./ApiReportViewHeader"; import MsApiReportViewHeader from "./ApiReportViewHeader";
import {RequestFactory} from "../test/model/ScenarioModel"; import {RequestFactory} from "../test/model/ScenarioModel";
@ -104,7 +106,7 @@ export default {
try { try {
this.content = JSON.parse(this.report.content); this.content = JSON.parse(this.report.content);
} catch (e) { } catch (e) {
console.log(this.report.content) // console.log(this.report.content)
throw e; throw e;
} }
this.getFails(); this.getFails();
@ -158,14 +160,12 @@ export default {
let reset = this.exportReportReset; let reset = this.exportReportReset;
this.$nextTick(function () { this.$nextTick(function () {
setTimeout(() => { html2canvas(document.getElementById('apiTestReport'), {
html2canvas(document.getElementById('apiTestReport'), { // scale: 2,
scale: 2 }).then(function (canvas) {
}).then(function(canvas) { exportPdf(name, [canvas]);
exportPdf(name, [canvas]); reset();
reset(); });
});
}, 1000);
}); });
}, },
exportReportReset() { exportReportReset() {
@ -196,35 +196,35 @@ export default {
<style scoped> <style scoped>
.report-container { .report-container {
height: calc(100vh - 155px); height: calc(100vh - 155px);
min-height: 600px; min-height: 600px;
overflow-y: auto; overflow-y: auto;
} }
.report-header { .report-header {
font-size: 15px; font-size: 15px;
} }
.report-header a { .report-header a {
text-decoration: none; text-decoration: none;
} }
.report-header .time { .report-header .time {
color: #909399; color: #909399;
margin-left: 10px; margin-left: 10px;
} }
.report-container .fail { .report-container .fail {
color: #F56C6C; color: #F56C6C;
} }
.report-container .is-active .fail { .report-container .is-active .fail {
color: inherit; color: inherit;
} }
.export-button { .export-button {
float: right; float: right;
} }
</style> </style>

View File

@ -118,7 +118,7 @@
validateDomain(domain) { validateDomain(domain) {
let strRegex = "^(?=^.{3,255}$)(http(s)?:\\/\\/)?(www\\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\\d+)*(\\/\\w+\\.\\w+)*$"; let strRegex = "^(?=^.{3,255}$)(http(s)?:\\/\\/)?(www\\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\\d+)*(\\/\\w+\\.\\w+)*$";
const re = new RegExp(strRegex); const re = new RegExp(strRegex);
if (re.test(domain) && domain.length < 26) { if (re.test(domain) && domain.length < 67) {
return true; return true;
} }
this.$warning(this.$t('load_test.input_domain')); this.$warning(this.$t('load_test.input_domain'));

View File

@ -2,7 +2,7 @@
<div> <div>
<el-row :gutter="10" type="flex" justify="space-between" align="middle"> <el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col> <el-col>
<el-input :disabled="isReadOnly" :value="value" v-bind="$attrs" step="100" size="small" type="number" @change="change" @input="input" <el-input :disabled="isReadOnly" :value="value" v-bind="$attrs" step="100" size="small" type="number" @change="change" @input="input" :min="0"
:placeholder="$t('api_test.request.assertions.response_in_time')"/> :placeholder="$t('api_test.request.assertions.response_in_time')"/>
</el-col> </el-col>
<el-col class="assertion-btn"> <el-col class="assertion-btn">
@ -35,17 +35,28 @@
methods: { methods: {
add() { add() {
this.duration.value = this.value; if (this.validate()) {
this.callback(); this.duration.value = this.value;
this.callback();
}
}, },
remove() { remove() {
this.duration.value = undefined; this.duration.value = undefined;
}, },
change(value) { change(value) {
this.$emit('change', value); if (this.validate()) {
this.$emit('change', value);
}
}, },
input(value) { input(value) {
this.$emit('input', value); this.$emit('input', value);
},
validate() {
if (Number(this.value) < 0 || this.value=='') {
this.$error(this.$t('commons.formatErr'));
return false;
}
return true;
} }
} }
} }

View File

@ -20,7 +20,7 @@
}, },
methods: { methods: {
initWebSocket() { initWebSocket() {
window.console.log("init WebSocket"); // window.console.log("init WebSocket");
const uri = "ws://" + window.location.host + "/socket"; const uri = "ws://" + window.location.host + "/socket";
this.websocket = new WebSocket(uri); this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage; this.websocket.onmessage = this.onMessage;
@ -36,10 +36,10 @@
window.console.error(e) window.console.error(e)
}, },
onMessage(e) { onMessage(e) {
window.console.log(e.data) // window.console.log(e.data)
}, },
onClose(e) { onClose(e) {
window.console.log('断开连接', e); // window.console.log('', e);
}, },
send(Data) { send(Data) {
this.websocket.send(Data); this.websocket.send(Data);

View File

@ -25,6 +25,9 @@
<el-button :disabled="isReadOnly" type="info" plain size="mini" @click="handleExport(reportName)"> <el-button :disabled="isReadOnly" type="info" plain size="mini" @click="handleExport(reportName)">
{{ $t('test_track.plan_view.export_report') }} {{ $t('test_track.plan_view.export_report') }}
</el-button> </el-button>
<el-button :disabled="isReadOnly" type="warning" plain size="mini" @click="downloadJtl()">
{{ $t('report.downloadJtl') }}
</el-button>
<!--<el-button :disabled="isReadOnly" type="warning" plain size="mini">--> <!--<el-button :disabled="isReadOnly" type="warning" plain size="mini">-->
<!--{{$t('report.compare')}}--> <!--{{$t('report.compare')}}-->
@ -95,6 +98,7 @@ import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser, exportPdf} from "@/common/js/utils"; import {checkoutTestManagerOrTestUser, exportPdf} from "@/common/js/utils";
import html2canvas from 'html2canvas'; import html2canvas from 'html2canvas';
import MsPerformanceReportExport from "./PerformanceReportExport"; import MsPerformanceReportExport from "./PerformanceReportExport";
import {Message} from "element-ui";
export default { export default {
@ -234,10 +238,10 @@ export default {
}); });
}, },
onOpen() { onOpen() {
window.console.log("socket opening."); // window.console.log("socket opening.");
}, },
onError(e) { onError(e) {
window.console.error(e) // window.console.error(e)
}, },
onMessage(e) { onMessage(e) {
this.$set(this.report, "refresh", e.data); // this.$set(this.report, "refresh", e.data); //
@ -249,7 +253,7 @@ export default {
this.$set(this.report, "status", 'Running'); this.$set(this.report, "status", 'Running');
this.status = 'Running'; this.status = 'Running';
this.initReportTimeInfo(); this.initReportTimeInfo();
window.console.log('receive a message:', e.data); // window.console.log('receive a message:', e.data);
}, },
onClose(e) { onClose(e) {
if (e.code === 1005) { if (e.code === 1005) {
@ -259,7 +263,7 @@ export default {
this.$set(this.report, "refresh", Math.random()); // this.$set(this.report, "refresh", Math.random()); //
this.$set(this.report, "status", 'Completed'); this.$set(this.report, "status", 'Completed');
this.initReportTimeInfo(); this.initReportTimeInfo();
window.console.log("socket closed."); // window.console.log("socket closed.");
}, },
handleExport(name) { handleExport(name) {
this.result.loading = true; this.result.loading = true;
@ -267,20 +271,46 @@ export default {
let reset = this.exportReportReset; let reset = this.exportReportReset;
this.$nextTick(function () { this.$nextTick(function () {
setTimeout(() => { html2canvas(document.getElementById('performanceReportExport'), {
html2canvas(document.getElementById('performanceReportExport'), { // scale: 2
scale: 2 }).then(function (canvas) {
}).then(function (canvas) { exportPdf(name, [canvas]);
exportPdf(name, [canvas]); reset();
reset(); });
});
}, 1000);
}); });
}, },
exportReportReset() { exportReportReset() {
this.reportExportVisible = false; this.reportExportVisible = false;
this.result.loading = false; this.result.loading = false;
}, },
downloadJtl() {
let config = {
url: "/performance/report/jtl/download/" + this.reportId,
method: 'get',
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 = this.reportId + ".jtl";
aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(aTag.href)
} else {
// IE10+
navigator.msSaveBlob(blob, this.filename)
}
}).catch(e => {
let text = e.response.data.text();
text.then((data) => {
Message.error({message: JSON.parse(data).message || e.message, showClose: true});
});
});
}
}, },
created() { created() {
this.isReadOnly = false; this.isReadOnly = false;
@ -328,7 +358,7 @@ export default {
}); });
this.initWebSocket(); this.initWebSocket();
} else { } else {
console.log("close socket."); // console.log("close socket.");
this.websocket.close() //websocket this.websocket.close() //websocket
} }
} }

View File

@ -129,7 +129,11 @@ export default {
this.threadGroups[i].rampUpTime = item.value; this.threadGroups[i].rampUpTime = item.value;
break; break;
case DURATION: case DURATION:
this.threadGroups[i].duration = item.value; if (item.unit) {
this.threadGroups[i].duration = item.value;
} else {
this.threadGroups[i].duration = item.value * 60;
}
break; break;
case STEPS: case STEPS:
this.threadGroups[i].step = item.value; this.threadGroups[i].step = item.value;
@ -154,7 +158,11 @@ export default {
this.threadGroups[0].rampUpTime = d.value; this.threadGroups[0].rampUpTime = d.value;
break; break;
case DURATION: case DURATION:
this.threadGroups[0].duration = d.value; if (d.unit) {
this.threadGroups[0].duration = d.value;
} else {
this.threadGroups[0].duration = d.value * 60;
}
break; break;
case STEPS: case STEPS:
this.threadGroups[0].step = d.value; this.threadGroups[0].step = d.value;
@ -196,7 +204,7 @@ export default {
}); });
}, },
getJmxContent() { getJmxContent() {
console.log(this.report.testId); // console.log(this.report.testId);
if (!this.report.testId) { if (!this.report.testId) {
return; return;
} }

View File

@ -191,7 +191,11 @@ export default {
this.threadGroups[i].rampUpTime = item.value; this.threadGroups[i].rampUpTime = item.value;
break; break;
case DURATION: case DURATION:
this.threadGroups[i].duration = item.value; if (item.unit) {
this.threadGroups[i].duration = item.value;
} else {
this.threadGroups[i].duration = item.value * 60;
}
break; break;
case STEPS: case STEPS:
this.threadGroups[i].step = item.value; this.threadGroups[i].step = item.value;
@ -216,7 +220,11 @@ export default {
this.threadGroups[0].rampUpTime = d.value; this.threadGroups[0].rampUpTime = d.value;
break; break;
case DURATION: case DURATION:
this.threadGroups[0].duration = d.value; if (d.unit) {
this.threadGroups[0].duration = d.value;
} else {
this.threadGroups[0].duration = d.value * 60;
}
break; break;
case STEPS: case STEPS:
this.threadGroups[0].step = d.value; this.threadGroups[0].step = d.value;
@ -468,7 +476,7 @@ export default {
{key: TARGET_LEVEL, value: this.threadGroups[i].threadNumber}, {key: TARGET_LEVEL, value: this.threadGroups[i].threadNumber},
{key: RAMP_UP, value: this.threadGroups[i].rampUpTime}, {key: RAMP_UP, value: this.threadGroups[i].rampUpTime},
{key: STEPS, value: this.threadGroups[i].step}, {key: STEPS, value: this.threadGroups[i].step},
{key: DURATION, value: this.threadGroups[i].duration}, {key: DURATION, value: this.threadGroups[i].duration, unit: 'S'},
{key: RPS_LIMIT, value: this.threadGroups[i].rpsLimit}, {key: RPS_LIMIT, value: this.threadGroups[i].rpsLimit},
{key: RPS_LIMIT_ENABLE, value: this.threadGroups[i].rpsLimitEnable}, {key: RPS_LIMIT_ENABLE, value: this.threadGroups[i].rpsLimitEnable},
{key: HOLD, value: this.threadGroups[i].duration - this.threadGroups[i].rampUpTime}, {key: HOLD, value: this.threadGroups[i].duration - this.threadGroups[i].rampUpTime},

View File

@ -63,6 +63,9 @@
<el-form-item :label="$t('project.jira_key')" v-if="jira"> <el-form-item :label="$t('project.jira_key')" v-if="jira">
<el-input v-model="form.jiraKey" autocomplete="off"></el-input> <el-input v-model="form.jiraKey" autocomplete="off"></el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('project.zentao_id')" v-if="zentao">
<el-input v-model="form.zentaoId" autocomplete="off"></el-input>
</el-form-item>
</el-form> </el-form>
<template v-slot:footer> <template v-slot:footer>
<div class="dialog-footer"> <div class="dialog-footer">
@ -116,6 +119,7 @@ export default {
items: [], items: [],
tapd: false, tapd: false,
jira: false, jira: false,
zentao: false,
form: {}, form: {},
currentPage: 1, currentPage: 1,
pageSize: 5, pageSize: 5,
@ -194,6 +198,9 @@ export default {
if (platforms.indexOf("Jira") !== -1) { if (platforms.indexOf("Jira") !== -1) {
this.jira = true; this.jira = true;
} }
if (platforms.indexOf("Zentao") !== -1) {
this.zentao = true;
}
}); });
} }
}, },

View File

@ -165,6 +165,11 @@
for (let i = 0; i < this.tableData.length; i++) { for (let i = 0; i < this.tableData.length; i++) {
this.$get(url + "/" + encodeURIComponent(this.tableData[i].id), response => { this.$get(url + "/" + encodeURIComponent(this.tableData[i].id), response => {
let roles = response.data; let roles = response.data;
if (roles.length < 1) {
roles.push({
id : "org_member",
});
}
this.$set(this.tableData[i], "roles", roles); this.$set(this.tableData[i], "roles", roles);
}) })
} }

View File

@ -145,10 +145,17 @@ export default {
data.isReadOnly = true; data.isReadOnly = true;
if (data.type === 'EMAIL') { if (data.type === 'EMAIL') {
data.isReadOnly = !data.isReadOnly data.isReadOnly = !data.isReadOnly
data.webhook = ""
} }
}, },
handleEditTask(index,data){ handleEditTask(index,data) {
data.isSet = true data.isSet = true
if (data.type === 'EMAIL') {
data.isReadOnly = false
data.webhook = ""
} else {
data.isReadOnly = true
}
}, },
handleAddTaskModel(type) { handleAddTaskModel(type) {
let Task = {}; let Task = {};
@ -178,7 +185,7 @@ export default {
handleAddTask(index, data) { handleAddTask(index, data) {
if (data.event && data.userIds.length > 0 && data.type) { if (data.event && data.userIds.length > 0 && data.type) {
console.log(data.type) // console.log(data.type)
if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') { if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') {
if (!data.webhook) { if (!data.webhook) {
this.$warning(this.$t('organization.message.message_webhook')); this.$warning(this.$t('organization.message.message_webhook'));

View File

@ -147,6 +147,7 @@ export default {
data.isReadOnly = true; data.isReadOnly = true;
if (data.type === 'EMAIL') { if (data.type === 'EMAIL') {
data.isReadOnly = !data.isReadOnly data.isReadOnly = !data.isReadOnly
data.webhook = ""
} }
}, },
handleAddTaskModel(type) { handleAddTaskModel(type) {
@ -176,7 +177,7 @@ export default {
}, },
handleAddTask(index, data) { handleAddTask(index, data) {
if (data.event && data.userIds.length > 0 && data.type) { if (data.event && data.userIds.length > 0 && data.type) {
console.log(data.type) // console.log(data.type)
if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') { if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') {
if (!data.webhook) { if (!data.webhook) {
this.$warning(this.$t('organization.message.message_webhook')); this.$warning(this.$t('organization.message.message_webhook'));
@ -190,8 +191,15 @@ export default {
this.$warning(this.$t('organization.message.message')); this.$warning(this.$t('organization.message.message'));
} }
}, },
handleEditTask(index,data){ handleEditTask(index,data) {
data.isSet = true data.isSet = true
if (data.type === 'EMAIL') {
data.isReadOnly = false
data.webhook = ""
} else {
data.isReadOnly = true
}
}, },
addTask(data) { addTask(data) {
let list = [] let list = []

View File

@ -140,7 +140,7 @@ export default {
methods: { methods: {
initForm(){ initForm(){
this.result = this.$get('/notice/search/message/'+this.testId, response => { this.result = this.$get('/notice/search/message/'+this.testId, response => {
console.log(response.data); // console.log(response.data);
this.form.scheduleTask = response.data; this.form.scheduleTask = response.data;
}) })
}, },
@ -148,6 +148,7 @@ export default {
data.isReadOnly = true; data.isReadOnly = true;
if (data.type === 'EMAIL') { if (data.type === 'EMAIL') {
data.isReadOnly = !data.isReadOnly data.isReadOnly = !data.isReadOnly
data.webhook = ""
} }
}, },
handleAddTaskModel(type) { handleAddTaskModel(type) {
@ -164,13 +165,19 @@ export default {
this.form.scheduleTask.push(Task) this.form.scheduleTask.push(Task)
} }
}, },
handleEditTask(index,data){ handleEditTask(index,data) {
data.isSet = true data.isSet = true
data.testId=this.testId data.testId = this.testId
if (data.type === 'EMAIL') {
data.isReadOnly = false
data.webhook = ""
} else {
data.isReadOnly = true
}
}, },
handleAddTask(index, data) { handleAddTask(index, data) {
if (data.event && data.userIds.length > 0 && data.type) { if (data.event && data.userIds.length > 0 && data.type) {
console.log(data.type) // console.log(data.type)
if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') { if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') {
if (!data.webhook) { if (!data.webhook) {
this.$warning(this.$t('organization.message.message_webhook')); this.$warning(this.$t('organization.message.message_webhook'));

View File

@ -151,10 +151,17 @@ export default {
data.isReadOnly = true; data.isReadOnly = true;
if (data.type === 'EMAIL') { if (data.type === 'EMAIL') {
data.isReadOnly = !data.isReadOnly data.isReadOnly = !data.isReadOnly
data.webhook = ""
} }
}, },
handleEditTask(index,data){ handleEditTask(index,data) {
data.isSet = true data.isSet = true
if (data.type === 'EMAIL') {
data.isReadOnly = false
data.webhook = ""
} else {
data.isReadOnly = true
}
}, },
handleAddTaskModel(type) { handleAddTaskModel(type) {
let Task = {}; let Task = {};
@ -184,7 +191,7 @@ export default {
handleAddTask(index, data) { handleAddTask(index, data) {
if (data.event && data.userIds.length > 0 && data.type) { if (data.event && data.userIds.length > 0 && data.type) {
console.log(data.type) // console.log(data.type)
if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') { if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') {
if (!data.webhook) { if (!data.webhook) {
this.$warning(this.$t('organization.message.message_webhook')); this.$warning(this.$t('organization.message.message_webhook'));

View File

@ -152,10 +152,17 @@ export default {
data.isReadOnly = true; data.isReadOnly = true;
if (data.type === 'EMAIL') { if (data.type === 'EMAIL') {
data.isReadOnly = !data.isReadOnly data.isReadOnly = !data.isReadOnly
data.webhook = ""
} }
}, },
handleEditTask(index,data){ handleEditTask(index,data) {
data.isSet = true data.isSet = true
if (data.type === 'EMAIL') {
data.isReadOnly = false
data.webhook = ""
} else {
data.isReadOnly = true
}
}, },
handleAddTaskModel(type) { handleAddTaskModel(type) {
let Task = {}; let Task = {};
@ -185,7 +192,7 @@ export default {
handleAddTask(index, data) { handleAddTask(index, data) {
if (data.event && data.userIds.length > 0 && data.type) { if (data.event && data.userIds.length > 0 && data.type) {
console.log(data.type) // console.log(data.type)
if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') { if (data.type === 'NAIL_ROBOT' || data.type === 'WECHAT_ROBOT') {
if (!data.webhook) { if (!data.webhook) {
this.$warning(this.$t('organization.message.message_webhook')); this.$warning(this.$t('organization.message.message_webhook'));

View File

@ -3,12 +3,15 @@
<div style="width: 500px"> <div style="width: 500px">
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div> <div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules"> <el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
<el-form-item :label="$t('organization.integration.app_name')" prop="account"> <el-form-item :label="$t('organization.integration.account')" prop="account">
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_app_name')"/> <el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('organization.integration.app_key')" prop="password"> <el-form-item :label="$t('organization.integration.password')" prop="password">
<el-input v-model="form.password" auto-complete="new-password" <el-input v-model="form.password" auto-complete="new-password"
:placeholder="$t('organization.integration.input_app_key')" show-password/> :placeholder="$t('organization.integration.input_api_password')" show-password/>
</el-form-item>
<el-form-item :label="$t('organization.integration.zentao_url')" prop="url">
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_zentao_url')"/>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@ -56,14 +59,19 @@ export default {
rules: { rules: {
account: { account: {
required: true, required: true,
message: this.$t('organization.integration.input_app_name'), message: this.$t('organization.integration.input_api_account'),
trigger: ['change', 'blur'] trigger: ['change', 'blur']
}, },
password: { password: {
required: true, required: true,
message: this.$t('organization.integration.input_app_key'), message: this.$t('organization.integration.input_api_password'),
trigger: ['change', 'blur'] trigger: ['change', 'blur']
} },
url: {
required: true,
message: this.$t('organization.integration.input_zentao_url'),
trigger: ['change', 'blur']
},
}, },
} }
}, },
@ -71,12 +79,16 @@ export default {
save() { save() {
this.$refs['form'].validate(valid => { this.$refs['form'].validate(valid => {
if (valid) { if (valid) {
let formatUrl = this.form.url.trim();
if (!formatUrl.endsWith('/')) {
formatUrl = formatUrl + '/';
}
const {lastOrganizationId} = getCurrentUser(); const {lastOrganizationId} = getCurrentUser();
let param = {}; let param = {};
let auth = { let auth = {
account: this.form.account, account: this.form.account,
password: this.form.password, password: this.form.password,
url: formatUrl,
}; };
param.organizationId = lastOrganizationId; param.organizationId = lastOrganizationId;
param.platform = ZEN_TAO; param.platform = ZEN_TAO;
@ -106,6 +118,7 @@ export default {
let config = JSON.parse(data.configuration); let config = JSON.parse(data.configuration);
this.$set(this.form, 'account', config.account); this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password); this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
} else { } else {
this.clear(); this.clear();
} }
@ -114,19 +127,26 @@ export default {
clear() { clear() {
this.$set(this.form, 'account', ''); this.$set(this.form, 'account', '');
this.$set(this.form, 'password', ''); this.$set(this.form, 'password', '');
this.$set(this.form, 'url', '');
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.form.clearValidate(); this.$refs.form.clearValidate();
}); });
}, },
testConnection() { testConnection() {
if (this.form.account && this.form.password) { this.$refs['form'].validate(valid => {
this.$parent.result = this.$get("issues/auth/" + ZEN_TAO, () => { if (valid) {
this.$success(this.$t('organization.integration.verified')); if (this.form.account && this.form.password) {
}); this.$parent.result = this.$get("issues/auth/" + ZEN_TAO, () => {
} else { this.$success(this.$t('organization.integration.verified'));
this.$warning(this.$t('organization.integration.not_integrated')); });
return false; } else {
} this.$warning(this.$t('organization.integration.not_integrated'));
return false;
}
} else {
return false;
}
})
}, },
cancelIntegration() { cancelIntegration() {
if (this.form.account && this.form.password) { if (this.form.account && this.form.password) {

View File

@ -32,7 +32,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="source" :label="$t('user.source')"/> <el-table-column prop="source" :label="$t('user.source')"/>
<el-table-column :label="$t('commons.operating')"> <el-table-column :label="$t('commons.operating')" min-width="120px">
<template v-slot:default="scope"> <template v-slot:default="scope">
<ms-table-operator @editClick="edit(scope.row)" @deleteClick="del(scope.row)"> <ms-table-operator @editClick="edit(scope.row)" @deleteClick="del(scope.row)">
<template v-slot:behind> <template v-slot:behind>
@ -103,6 +103,21 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</div> </div>
<div v-if="role.id === 'org_member'">
<el-form-item :label="$t('organization.select_organization')"
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('organization.select_organization'), trigger: 'change'}"
>
<el-select filterable v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
<el-option
v-for="item in form.orgList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</div>
<div v-if="role.id === 'test_manager'"> <div v-if="role.id === 'test_manager'">
<el-form-item :label="$t('workspace.select')" <el-form-item :label="$t('workspace.select')"
:prop="'roles.' + index + '.ids'" :prop="'roles.' + index + '.ids'"
@ -214,6 +229,21 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</div> </div>
<div v-if="role.id === 'org_member'">
<el-form-item :label="$t('organization.select_organization')"
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('organization.select_organization'), trigger: 'change'}"
>
<el-select filterable v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
<el-option
v-for="item in form.orgList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</div>
<div v-if="role.id === 'test_manager'"> <div v-if="role.id === 'test_manager'">
<el-form-item :label="$t('workspace.select')" <el-form-item :label="$t('workspace.select')"
:prop="'roles.' + index + '.ids'" :prop="'roles.' + index + '.ids'"

View File

@ -139,7 +139,7 @@
size="mini" size="mini"
:default-sort="{prop: 'num', order: 'ascending'}" :default-sort="{prop: 'num', order: 'ascending'}"
highlight-current-row> highlight-current-row>
<el-table-column :label="$t('test_track.case.number')" prop="num" min-width="15%"></el-table-column> <el-table-column :label="$t('test_track.case.number')" prop="num" min-width="10%"></el-table-column>
<el-table-column :label="$t('test_track.case.step_desc')" prop="desc" min-width="35%"> <el-table-column :label="$t('test_track.case.step_desc')" prop="desc" min-width="35%">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-input <el-input
@ -168,7 +168,7 @@
clearable/> clearable/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.input_content')" min-width="15%"> <el-table-column :label="$t('commons.input_content')" min-width="20%">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-button <el-button
type="primary" type="primary"

View File

@ -8,7 +8,7 @@
:tip="$t('commons.search_by_name_or_id')" :tip="$t('commons.search_by_name_or_id')"
:create-tip="$t('test_track.case.create')" @create="testCaseCreate"> :create-tip="$t('test_track.case.create')" @create="testCaseCreate">
<template v-slot:title> <template v-slot:title>
<node-breadcrumb class="table-title" :nodes="selectParentNodes" @refresh="refresh"/> <node-breadcrumb class="table-title" :nodes="selectParentNodes" @refresh="showAll"/>
</template> </template>
<template v-slot:button> <template v-slot:button>
<ms-table-button :is-tester-permission="true" icon="el-icon-download" <ms-table-button :is-tester-permission="true" icon="el-icon-download"
@ -298,6 +298,9 @@ export default {
// param.nodeIds = this.selectNodeIds; // param.nodeIds = this.selectNodeIds;
this.condition.nodeIds = this.selectNodeIds; this.condition.nodeIds = this.selectNodeIds;
} }
this.getData();
},
getData() {
if (this.currentProject) { if (this.currentProject) {
this.condition.projectId = this.currentProject.id; this.condition.projectId = this.currentProject.id;
this.result = this.$post(this.buildPagePath('/test/case/list'), this.condition, response => { this.result = this.$post(this.buildPagePath('/test/case/list'), this.condition, response => {
@ -366,6 +369,10 @@ export default {
this.selectRows.clear(); this.selectRows.clear();
this.$emit('refresh'); this.$emit('refresh');
}, },
showAll() {
this.condition = {components: TEST_CASE_CONFIGS};
this.getData();
},
showDetail(row, event, column) { showDetail(row, event, column) {
this.$emit('testCaseDetail', row); this.$emit('testCaseDetail', row);
}, },

View File

@ -106,6 +106,9 @@
nodeChange(nodeIds, pNodes) { nodeChange(nodeIds, pNodes) {
this.selectNodeIds = nodeIds; this.selectNodeIds = nodeIds;
this.selectParentNodes = pNodes; this.selectParentNodes = pNodes;
// node
this.$refs.testPlanTestCaseList.currentPage = 1;
this.$refs.testPlanTestCaseList.pageSize = 10;
}, },
changePlan(plan) { changePlan(plan) {
this.currentPlan = plan; this.currentPlan = plan;

View File

@ -93,6 +93,13 @@
</el-col> </el-col>
</el-row> </el-row>
<el-row>
<el-col :span="4" :offset="1" v-if="testCase.testId == 'other'">
<span class="cast_label">{{ $t('test_track.case.test_name') }}</span>
<span class="cast_item">{{ testCase.otherTestName }}</span>
</el-col>
</el-row>
<el-row> <el-row>
<el-col :offset="1"> <el-col :offset="1">
<span class="cast_label">{{ $t('test_track.case.prerequisite') }}</span> <span class="cast_label">{{ $t('test_track.case.prerequisite') }}</span>
@ -100,7 +107,7 @@
</el-col> </el-col>
</el-row> </el-row>
<el-row v-if="testCase.method === 'auto' && testCase.testId"> <el-row v-if="testCase.method === 'auto' && testCase.testId && testCase.testId != 'other'">
<el-col class="test-detail" :span="20" :offset="1"> <el-col class="test-detail" :span="20" :offset="1">
<el-tabs v-model="activeTab" type="border-card" @tab-click="testTabChange"> <el-tabs v-model="activeTab" type="border-card" @tab-click="testTabChange">
<el-tab-pane name="detail" :label="$t('test_track.plan_view.test_detail')"> <el-tab-pane name="detail" :label="$t('test_track.plan_view.test_detail')">
@ -220,7 +227,7 @@
<ckeditor :editor="editor" :disabled="isReadOnly" :config="editorConfig" <ckeditor :editor="editor" :disabled="isReadOnly" :config="editorConfig"
v-model="testCase.issues.content"/> v-model="testCase.issues.content"/>
<el-row v-if="hasTapdId"> <el-row v-if="hasTapdId">
{{ $t('test_track.issue.please_choose_current_owner') }} {{ $t('test_track.issue.tapd_current_owner') }}
<el-select v-model="testCase.tapdUsers" <el-select v-model="testCase.tapdUsers"
multiple multiple
filterable filterable
@ -231,6 +238,27 @@
:value="userInfo.user"/> :value="userInfo.user"/>
</el-select> </el-select>
</el-row> </el-row>
<el-row v-if="hasZentaoId">
{{ $t('test_track.issue.zentao_bug_build') }}
<el-select v-model="testCase.zentaoBuilds"
multiple
filterable
style="width: 20%"
:placeholder="$t('test_track.issue.zentao_bug_build')"
collapse-tags size="small">
<el-option v-for="(build, index) in Builds" :key="index" :label="build.name"
:value="build.id"/>
</el-select>
{{ $t('test_track.issue.zentao_bug_assigned') }}
<el-select v-model="testCase.zentaoAssigned"
filterable
style="width: 20%"
:placeholder="$t('test_track.issue.please_choose_current_owner')"
collapse-tags size="small">
<el-option v-for="(userInfo, index) in zentaoUsers" :key="index" :label="userInfo.name"
:value="userInfo.user"/>
</el-select>
</el-row>
<el-button type="primary" size="small" @click="saveIssues">{{ $t('commons.save') }}</el-button> <el-button type="primary" size="small" @click="saveIssues">{{ $t('commons.save') }}</el-button>
<el-button size="small" @click="issuesSwitch=false">{{ $t('commons.cancel') }}</el-button> <el-button size="small" @click="issuesSwitch=false">{{ $t('commons.cancel') }}</el-button>
</el-col> </el-col>
@ -360,7 +388,12 @@ export default {
activeTab: 'detail', activeTab: 'detail',
isFailure: true, isFailure: true,
users: [], users: [],
Builds: [],
zentaoBuilds: [],
zentaoUsers: [],
zentaoAssigned: "",
hasTapdId: false, hasTapdId: false,
hasZentaoId: false,
tableData: [], tableData: [],
}; };
}, },
@ -481,6 +514,8 @@ export default {
this.showDialog = true; this.showDialog = true;
this.issuesSwitch = false; this.issuesSwitch = false;
this.activeTab = 'detail'; this.activeTab = 'detail';
this.hasTapdId = false;
this.hasZentaoId = false;
listenGoBack(this.handleClose); listenGoBack(this.handleClose);
this.initData(testCase); this.initData(testCase);
}, },
@ -558,6 +593,15 @@ export default {
this.users = response.data; this.users = response.data;
}) })
} }
if (project.zentaoId) {
this.hasZentaoId = true;
this.result = this.$get("/issues/zentao/builds/" + this.testCase.caseId, response => {
this.Builds = response.data;
})
this.result = this.$get("/issues/zentao/user/" + this.testCase.caseId, response => {
this.zentaoUsers = response.data;
})
}
}) })
} }
}, },
@ -587,6 +631,8 @@ export default {
param.content = this.testCase.issues.content; param.content = this.testCase.issues.content;
param.testCaseId = this.testCase.caseId; param.testCaseId = this.testCase.caseId;
param.tapdUsers = this.testCase.tapdUsers; param.tapdUsers = this.testCase.tapdUsers;
param.zentaoBuilds = this.testCase.zentaoBuilds;
param.zentaoUser = this.testCase.zentaoAssigned;
this.result = this.$post("/issues/add", param, () => { this.result = this.$post("/issues/add", param, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
@ -597,6 +643,8 @@ export default {
this.testCase.issues.title = ""; this.testCase.issues.title = "";
this.testCase.issues.content = ""; this.testCase.issues.content = "";
this.testCase.tapdUsers = []; this.testCase.tapdUsers = [];
this.testCase.zentaoBuilds = [];
this.testCase.zentaoAssigned = "";
}, },
getIssues(caseId) { getIssues(caseId) {
this.result = this.$get("/issues/get/" + caseId, response => { this.result = this.$get("/issues/get/" + caseId, response => {

View File

@ -210,14 +210,12 @@
let reset = this.exportReportReset; let reset = this.exportReportReset;
this.$nextTick(function () { this.$nextTick(function () {
setTimeout(() => { html2canvas(document.getElementById('testCaseReportExport'), {
html2canvas(document.getElementById('testCaseReportExport'), { // scale: 2
scale: 2 }).then(function(canvas) {
}).then(function(canvas) { exportPdf(name, [canvas]);
exportPdf(name, [canvas]); reset();
reset(); });
});
}, 1000);
}); });
}, },

View File

@ -301,7 +301,7 @@ export default {
return path + "/" + this.currentPage + "/" + this.pageSize; return path + "/" + this.currentPage + "/" + this.pageSize;
}, },
handleEdit(testCase, index) { handleEdit(testCase, index) {
console.log(testCase) // console.log(testCase)
this.isReadOnly = false; this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) { if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true; this.isReadOnly = true;

View File

@ -26,14 +26,14 @@ body {
white-space: nowrap; white-space: nowrap;
} }
/*解决高度塌陷和边距重叠*/ /* 解决高度塌陷和边距重叠 */
.clearfix:before, .clearfix:after { .clearfix:before, .clearfix:after {
content: ""; content: "";
display: table; display: table;
clear: both; clear: both;
} }
/*解决富文本框中link显示问题*/ /* 解决富文本框中link显示问题 */
.ck-rounded-corners .ck.ck-balloon-panel, .ck.ck-balloon-panel.ck-rounded-corners { .ck-rounded-corners .ck.ck-balloon-panel, .ck.ck-balloon-panel.ck-rounded-corners {
z-index: 10055 !important; z-index: 10055 !important;
} }
@ -48,7 +48,7 @@ body {
table-layout: fixed !important; table-layout: fixed !important;
} }
/* <-- 表格拖拽表头调整宽度,在 t-bable 上添加 border 属性,并添加 adjust-table 类名*/ /* <-- 表格拖拽表头调整宽度,在 t-bable 上添加 border 属性,并添加 adjust-table 类名 */
.adjust-table td { .adjust-table td {
border-right: 0; border-right: 0;
} }
@ -65,7 +65,7 @@ body {
background-color: white; background-color: white;
} }
.adjust-table th:not([class*='el-table-column--selection']):hover:after { .adjust-table th:not([class*="el-table-column--selection"]):hover:after {
content: ''; content: '';
position: absolute; position: absolute;
top: 25%; top: 25%;
@ -81,7 +81,7 @@ body {
/* 表格拖拽表头调整宽度 --> */ /* 表格拖拽表头调整宽度 --> */
/* <-- 表格 input 编辑效果*/ /* <-- 表格 input 编辑效果 */
.table-edit-input .el-textarea__inner { .table-edit-input .el-textarea__inner {
border-style: hidden; border-style: hidden;
} }

View File

@ -27,7 +27,7 @@ export default {
operating: 'Operating', operating: 'Operating',
input_limit: 'Within {0} and {1} characters', input_limit: 'Within {0} and {1} characters',
login: 'Sign In', login: 'Sign In',
welcome: 'Welcome back, please enter username and password to log in to MeterSphere', welcome: 'Welcome back, please enter username and password to log in',
username: 'Username', username: 'Username',
password: 'Password', password: 'Password',
input_username: 'Please enter username', input_username: 'Please enter username',
@ -258,14 +258,14 @@ export default {
jira_issuetype: 'JIRA issuetype', jira_issuetype: 'JIRA issuetype',
input_api_account: 'please enter account', input_api_account: 'please enter account',
input_api_password: 'Please enter password', input_api_password: 'Please enter password',
input_app_name: 'Please enter the application code',
input_app_key: 'Please enter the key',
input_jira_url: 'Please enter Jira address, for example: https://metersphere.atlassian.net/', input_jira_url: 'Please enter Jira address, for example: https://metersphere.atlassian.net/',
input_jira_issuetype: 'Please enter the question type', input_jira_issuetype: 'Please enter the question type',
zentao_url: 'Zentao url',
input_zentao_url: 'Please enter Zentao address, for example: http://xx.xx.xx.xx/zentao/',
use_tip: 'Usage guidelines:', use_tip: 'Usage guidelines:',
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"', use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)', use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
use_tip_zentao: 'Log in to ZenTao as a super administrator user, enter the background-secondary development-application, click [Add Application] to add an application', use_tip_zentao: 'The account password is a Zentao account with corresponding permissions, and the account needs to have super model calling interface permissions',
use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the Metersphere project', use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the Metersphere project',
link_the_project_now: 'Link the project now', link_the_project_now: 'Link the project now',
cancel_edit: 'Cancel edit', cancel_edit: 'Cancel edit',
@ -292,6 +292,7 @@ export default {
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')', special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
tapd_id: 'TAPD Project ID', tapd_id: 'TAPD Project ID',
jira_key: 'JIRA Project key', jira_key: 'JIRA Project key',
zentao_id: 'Zentao Project ID',
}, },
member: { member: {
create: 'Create', create: 'Create',
@ -336,6 +337,7 @@ export default {
please_choose_role: 'Please Choose Role', please_choose_role: 'Please Choose Role',
admin: 'Admin', admin: 'Admin',
org_admin: 'Org_Admin', org_admin: 'Org_Admin',
org_member: 'Org Member',
test_manager: 'Test Manager', test_manager: 'Test Manager',
test_user: 'Test User', test_user: 'Test User',
test_viewer: 'Read-only User', test_viewer: 'Read-only User',
@ -360,6 +362,7 @@ export default {
test_stop_now_confirm: 'Are you sure you want to stop the current test immediately?', test_stop_now_confirm: 'Are you sure you want to stop the current test immediately?',
test_rerun_confirm: 'Are you sure you want to rerun the current test immediately?', test_rerun_confirm: 'Are you sure you want to rerun the current test immediately?',
test_stop_success: 'Test stop successfully', test_stop_success: 'Test stop successfully',
downloadJtl: 'Download JTL',
test_execute_again: 'Test Execute Again', test_execute_again: 'Test Execute Again',
export: 'Export', export: 'Export',
compare: 'Compare', compare: 'Compare',
@ -411,12 +414,12 @@ export default {
delete_file: "The file already exists, please delete the file with the same name first!", delete_file: "The file already exists, please delete the file with the same name first!",
thread_num: 'Concurrent users:', thread_num: 'Concurrent users:',
input_thread_num: 'Please enter the number of threads', input_thread_num: 'Please enter the number of threads',
duration: 'Duration time (minutes):', duration: 'Duration time (seconds):',
input_duration: 'Please enter a duration', input_duration: 'Please enter a duration',
rps_limit: 'RPS Limit:', rps_limit: 'RPS Limit:',
input_rps_limit: 'Please enter a limit', input_rps_limit: 'Please enter a limit',
ramp_up_time_within: 'In', ramp_up_time_within: 'In',
ramp_up_time_minutes: 'minutes, separate', ramp_up_time_minutes: 'seconds, separate',
ramp_up_time_times: 'add concurrent users', ramp_up_time_times: 'add concurrent users',
advanced_config_error: 'Advanced configuration verification failed', advanced_config_error: 'Advanced configuration verification failed',
domain_bind: 'Domain bind', domain_bind: 'Domain bind',
@ -748,7 +751,7 @@ export default {
detail: "Report detail", detail: "Report detail",
delete: "Delete report", delete: "Delete report",
batch_delete: "Delete reports in bulk", batch_delete: "Delete reports in bulk",
running: "The test is running", running: "The test is reporting",
not_exist: "Test report does not exist", not_exist: "Test report does not exist",
}, },
test_track: { test_track: {
@ -1016,6 +1019,8 @@ export default {
preview: "Preview", preview: "Preview",
please_choose_current_owner: "Please choose current owner", please_choose_current_owner: "Please choose current owner",
tapd_current_owner: "Tapd Current Owner", tapd_current_owner: "Tapd Current Owner",
zentao_bug_build: "Zentao bug Impact version",
zentao_bug_assigned: "Zentao bug handler",
} }
}, },
test_resource_pool: { test_resource_pool: {
@ -1148,7 +1153,7 @@ export default {
performance: "Number of performance tests", performance: "Number of performance tests",
resource_pool: "Available test resource pool", resource_pool: "Available test resource pool",
max_threads: "Maximum Concurrency", max_threads: "Maximum Concurrency",
duration: "Stress test duration(minutes)", duration: "Stress test duration(seconds)",
use_default: "Use default quota", use_default: "Use default quota",
yes: "Yes", yes: "Yes",
no: "No", no: "No",

View File

@ -27,7 +27,7 @@ export default {
operating: '操作', operating: '操作',
input_limit: '长度在 {0} 到 {1} 个字符', input_limit: '长度在 {0} 到 {1} 个字符',
login: '登录', login: '登录',
welcome: '欢迎回来,请输入用户名和密码登录MeterSphere', welcome: '欢迎回来,请输入用户名和密码登录',
username: '姓名', username: '姓名',
password: '密码', password: '密码',
input_username: '请输入用户姓名', input_username: '请输入用户姓名',
@ -257,15 +257,15 @@ export default {
jira_url: 'JIRA 地址', jira_url: 'JIRA 地址',
jira_issuetype: '问题类型', jira_issuetype: '问题类型',
input_api_account: '请输入账号', input_api_account: '请输入账号',
input_api_password: '请输入口令', input_api_password: '请输入密码',
input_app_name: '请输入应用代号',
input_app_key: '请输入密钥',
input_jira_url: '请输入Jira地址https://metersphere.atlassian.net/', input_jira_url: '请输入Jira地址https://metersphere.atlassian.net/',
input_jira_issuetype: '请输入问题类型', input_jira_issuetype: '请输入问题类型',
zentao_url: 'Zentao 地址',
input_zentao_url: '请输入Zentao地址http://xx.xx.xx.xx/zentao/',
use_tip: '使用指引:', use_tip: '使用指引:',
use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询', use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
use_tip_jira: 'Jira software server 认证信息为 账号密码Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)', use_tip_jira: 'Jira software server 认证信息为 账号密码Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)',
use_tip_zentao: '用超级管理员用户登录禅道,进入后台-二次开发-应用,点击【添加应用】新增一个应用', use_tip_zentao: '账号密码为具有相应权限的Zentao账号账号需要具有 超级model调用接口权限',
use_tip_two: '保存 Basic Auth 账号信息后,需要在 Metersphere 项目中手动关联 ID/key', use_tip_two: '保存 Basic Auth 账号信息后,需要在 Metersphere 项目中手动关联 ID/key',
link_the_project_now: '马上关联项目', link_the_project_now: '马上关联项目',
cancel_edit: '取消编辑', cancel_edit: '取消编辑',
@ -291,6 +291,7 @@ export default {
special_characters_are_not_supported: '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)', special_characters_are_not_supported: '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
tapd_id: 'TAPD项目ID', tapd_id: 'TAPD项目ID',
jira_key: 'JIRA项目key', jira_key: 'JIRA项目key',
zentao_id: 'Zentao项目ID',
}, },
member: { member: {
create: '添加成员', create: '添加成员',
@ -335,6 +336,7 @@ export default {
please_choose_role: '请选择角色', please_choose_role: '请选择角色',
admin: '系统管理员', admin: '系统管理员',
org_admin: '组织管理员', org_admin: '组织管理员',
org_member: '组织成员',
test_manager: '测试经理', test_manager: '测试经理',
test_user: '测试人员', test_user: '测试人员',
test_viewer: '只读用户', test_viewer: '只读用户',
@ -360,6 +362,7 @@ export default {
test_rerun_confirm: '确定要再次执行当前测试吗?', test_rerun_confirm: '确定要再次执行当前测试吗?',
test_stop_success: '停止成功', test_stop_success: '停止成功',
test_execute_again: '再次执行', test_execute_again: '再次执行',
downloadJtl: '下载JTL',
export: '导出', export: '导出',
compare: '比较', compare: '比较',
generation_error: '报告生成错误, 无法查看, 请检查日志详情!', generation_error: '报告生成错误, 无法查看, 请检查日志详情!',
@ -409,12 +412,12 @@ export default {
delete_file: "文件已存在,请先删除同名文件!", delete_file: "文件已存在,请先删除同名文件!",
thread_num: '并发用户数:', thread_num: '并发用户数:',
input_thread_num: '请输入线程数', input_thread_num: '请输入线程数',
duration: '压测时长(分钟', duration: '压测时长(',
input_duration: '请输入时长', input_duration: '请输入时长',
rps_limit: 'RPS上限', rps_limit: 'RPS上限',
input_rps_limit: '请输入限制', input_rps_limit: '请输入限制',
ramp_up_time_within: '在', ramp_up_time_within: '在',
ramp_up_time_minutes: '分钟内,分', ramp_up_time_minutes: '内,分',
ramp_up_time_times: '次增加并发用户', ramp_up_time_times: '次增加并发用户',
advanced_config_error: '高级配置校验失败', advanced_config_error: '高级配置校验失败',
domain_bind: '域名绑定', domain_bind: '域名绑定',
@ -752,7 +755,7 @@ export default {
detail: "报告详情", detail: "报告详情",
delete: "删除报告", delete: "删除报告",
batch_delete: "批量删除报告", batch_delete: "批量删除报告",
running: "测试执行中", running: "测试报告导出中",
not_exist: "测试报告不存在", not_exist: "测试报告不存在",
}, },
test_track: { test_track: {
@ -1019,7 +1022,9 @@ export default {
close_success: "关闭成功", close_success: "关闭成功",
preview: "预览", preview: "预览",
please_choose_current_owner: "请选择处理人", please_choose_current_owner: "请选择处理人",
tapd_current_owner: "Tapd平台处理人", tapd_current_owner: "Tapd bug 处理人:",
zentao_bug_build: "禅道 bug 影响版本",
zentao_bug_assigned: "禅道 bug 处理人",
} }
}, },
test_resource_pool: { test_resource_pool: {
@ -1151,7 +1156,7 @@ export default {
performance: "性能测试数量", performance: "性能测试数量",
resource_pool: "可用测试资源池", resource_pool: "可用测试资源池",
max_threads: "最大并发数", max_threads: "最大并发数",
duration: "压测时长(分钟", duration: "压测时长(",
use_default: "使用默认配额", use_default: "使用默认配额",
yes: "是", yes: "是",
no: "否", no: "否",

View File

@ -27,7 +27,7 @@ export default {
operating: '操作', operating: '操作',
input_limit: '長度在 {0} 到 {1} 個字符', input_limit: '長度在 {0} 到 {1} 個字符',
login: '登錄', login: '登錄',
welcome: '歡迎回來,請輸入用戶名和密碼登錄MeterSphere', welcome: '歡迎回來,請輸入用戶名和密碼登錄',
username: '姓名', username: '姓名',
password: '密碼', password: '密碼',
input_username: '請輸入用戶姓名', input_username: '請輸入用戶姓名',
@ -257,15 +257,15 @@ export default {
jira_url: 'JIRA 地址', jira_url: 'JIRA 地址',
jira_issuetype: '問題類型', jira_issuetype: '問題類型',
input_api_account: '請輸入賬號', input_api_account: '請輸入賬號',
input_api_password: '請輸入口令', input_api_password: '請輸入密碼',
input_app_name: '請輸入應用代號',
input_app_key: '請輸入密鑰',
input_jira_url: '請輸入Jira地址https://metersphere.atlassian.net/', input_jira_url: '請輸入Jira地址https://metersphere.atlassian.net/',
input_jira_issuetype: '請輸入問題類型', input_jira_issuetype: '請輸入問題類型',
zentao_url: 'Zentao 地址',
input_zentao_url: '請輸入Zentao地址http://xx.xx.xx.xx/zentao/',
use_tip: '使用指引:', use_tip: '使用指引:',
use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢', use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢',
use_tip_jira: 'Jira software server 認證信息為 賬號密碼Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)', use_tip_jira: 'Jira software server 認證信息為 賬號密碼Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
use_tip_zentao: '用超級管理員用戶登錄禪道,進入後台-二次開發-應用,點擊【添加應用】添加一個應用', use_tip_zentao: '賬號密碼為具有相應權限的Zentao賬號賬號需要具有 超級model調用接口權限',
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key', use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key',
link_the_project_now: '馬上關聯項目', link_the_project_now: '馬上關聯項目',
cancel_edit: '取消編輯', cancel_edit: '取消編輯',
@ -291,6 +291,7 @@ export default {
special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)', special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
tapd_id: 'TAPD項目ID', tapd_id: 'TAPD項目ID',
jira_key: 'JIRA項目key', jira_key: 'JIRA項目key',
zentao_id: 'Zentao項目ID',
}, },
member: { member: {
create: '添加成員', create: '添加成員',
@ -335,6 +336,7 @@ export default {
please_choose_role: '請選擇角色', please_choose_role: '請選擇角色',
admin: '系統管理員', admin: '系統管理員',
org_admin: '組織管理員', org_admin: '組織管理員',
org_member: '組織成員',
test_manager: '測試經理', test_manager: '測試經理',
test_user: '測試人員', test_user: '測試人員',
test_viewer: '只讀用戶', test_viewer: '只讀用戶',
@ -358,6 +360,7 @@ export default {
test_stop_now: '立即停止', test_stop_now: '立即停止',
test_stop_now_confirm: '確定要立即停止當前測試嗎?', test_stop_now_confirm: '確定要立即停止當前測試嗎?',
test_rerun_confirm: '確定要再次執行當前測試嗎?', test_rerun_confirm: '確定要再次執行當前測試嗎?',
downloadJtl: '下載JTL',
test_stop_success: '停止成功', test_stop_success: '停止成功',
test_execute_again: '再次執行', test_execute_again: '再次執行',
export: '導出', export: '導出',
@ -409,12 +412,12 @@ export default {
delete_file: "文件已存在,請先刪除同名文件!", delete_file: "文件已存在,請先刪除同名文件!",
thread_num: '並發用戶數:', thread_num: '並發用戶數:',
input_thread_num: '請輸入線程數', input_thread_num: '請輸入線程數',
duration: '壓測時長(分鐘', duration: '壓測時長(',
input_duration: '請輸入時長', input_duration: '請輸入時長',
rps_limit: 'RPS上限', rps_limit: 'RPS上限',
input_rps_limit: '請輸入限制', input_rps_limit: '請輸入限制',
ramp_up_time_within: '在', ramp_up_time_within: '在',
ramp_up_time_minutes: '分鐘內,分', ramp_up_time_minutes: '內,分',
ramp_up_time_times: '次增加並發用戶', ramp_up_time_times: '次增加並發用戶',
advanced_config_error: '高級配置校驗失敗', advanced_config_error: '高級配置校驗失敗',
domain_bind: '域名綁定', domain_bind: '域名綁定',
@ -751,7 +754,7 @@ export default {
detail: "報告詳情", detail: "報告詳情",
delete: "刪除報告", delete: "刪除報告",
batch_delete: "批量刪除報告", batch_delete: "批量刪除報告",
running: "測試執行中", running: "測試報告導出中",
not_exist: "測試報告不存在", not_exist: "測試報告不存在",
}, },
test_track: { test_track: {
@ -1018,7 +1021,9 @@ export default {
close_success: "關閉成功", close_success: "關閉成功",
preview: "預覽", preview: "預覽",
please_choose_current_owner: "請選擇處理人", please_choose_current_owner: "請選擇處理人",
tapd_current_owner: "Tapd平臺處理人", tapd_current_owner: "Tapd bug 處理人:",
zentao_bug_build: "禪道 bug 影響版本",
zentao_bug_assigned: "禪道 bug 處理人",
} }
}, },
test_resource_pool: { test_resource_pool: {