This commit is contained in:
fit2-zhao 2021-01-19 15:54:42 +08:00
commit f6490cb5ed
38 changed files with 333 additions and 289 deletions

View File

@ -312,24 +312,6 @@
<scope>runtime</scope>
</dependency>
<!-- buji-pac4j -->
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-cas</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>io.buji</groupId>
<artifactId>buji-pac4j</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<artifactId>shiro-web</artifactId>
<groupId>org.apache.shiro</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>

View File

@ -27,5 +27,7 @@ public class TaskInfoResult {
private String creator;
//更新时间
private Long updateTime;
//定时任务类型 情景定时任务/范围计划任务
private String taskType;
}

View File

@ -299,7 +299,7 @@ public class ApiAutomationService {
return null;
}
private void createScenarioReport(String id, String scenarioId, String scenarioName, String triggerMode, String execType, String projectId, String userID) {
public void createScenarioReport(String id, String scenarioId, String scenarioName, String triggerMode, String execType, String projectId, String userID) {
APIScenarioReportResult report = new APIScenarioReportResult();
report.setId(id);
report.setTestId(id);
@ -417,88 +417,6 @@ public class ApiAutomationService {
return request.getId();
}
/**
* 测试计划的定时任务--执行场景案例
*
* @param request
* @return
*/
public String run(SchedulePlanScenarioExecuteRequest request) {
MsTestPlan testPlan = new MsTestPlan();
testPlan.setHashTree(new LinkedList<>());
HashTree jmeterHashTree = new ListedHashTree();
Map<String, Map<String, String>> testPlanScenarioIdMap = request.getTestPlanScenarioIDMap();
for (Map.Entry<String, Map<String, String>> entry : testPlanScenarioIdMap.entrySet()) {
String testPlanID = entry.getKey();
Map<String, String> planScenarioIdMap = entry.getValue();
List<ApiScenarioWithBLOBs> apiScenarios = extApiScenarioMapper.selectIds(new ArrayList<>(planScenarioIdMap.keySet()));
try {
boolean isFirst = true;
for (ApiScenarioWithBLOBs item : apiScenarios) {
String apiScenarioID = item.getId();
String planScenarioID = planScenarioIdMap.get(apiScenarioID);
if (StringUtils.isEmpty(planScenarioID)) {
continue;
}
if (item.getStepTotal() == 0) {
// 只有一个场景且没有测试步骤则提示
if (apiScenarios.size() == 1) {
MSException.throwException((item.getName() + "" + Translator.get("automation_exec_info")));
}
LogUtil.warn(item.getName() + "" + Translator.get("automation_exec_info"));
continue;
}
MsThreadGroup group = new MsThreadGroup();
group.setLabel(item.getName());
group.setName(UUID.randomUUID().toString());
// 批量执行的结果直接存储为报告
if (isFirst) {
group.setName(request.getId());
isFirst = false;
}
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JSONObject element = JSON.parseObject(item.getScenarioDefinition());
MsScenario scenario = JSONObject.parseObject(item.getScenarioDefinition(), MsScenario.class);
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (element != null && StringUtils.isNotEmpty(element.getString("hashTree"))) {
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {
});
scenario.setHashTree(elements);
}
if (StringUtils.isNotEmpty(element.getString("variables"))) {
LinkedList<ScenarioVariable> variables = mapper.readValue(element.getString("variables"),
new TypeReference<LinkedList<ScenarioVariable>>() {
});
scenario.setVariables(variables);
}
group.setEnableCookieShare(scenario.isEnableCookieShare());
LinkedList<MsTestElement> scenarios = new LinkedList<>();
scenarios.add(scenario);
// 创建场景报告
//不同的运行模式第二个参数入参不同
createScenarioReport(group.getName(),
planScenarioID + ":" + request.getTestPlanReportId(),
item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);
}
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
String runMode = ApiRunMode.SCHEDULE_SCENARIO_PLAN.name();
// 调用执行方法
jMeterService.runDefinition(request.getId(), jmeterHashTree, request.getReportId(), runMode);
return request.getId();
}
/**
* 获取前台查询条件查询的所有(未经分页筛选)数据ID
*

View File

@ -50,10 +50,18 @@
AND create_time BETWEEN #{startTime} and #{endTime}
</select>
<select id="findRunningTaskInfoByProjectID" resultType="io.metersphere.api.dto.datacount.response.TaskInfoResult">
SELECT apiScene.id AS scenarioId,apiScene.`name` AS scenario,sch.id AS taskID,sch.`value` AS rule,sch.`enable` AS `taskStatus`,u.`name` AS creator,sch.update_time AS updateTime
SELECT apiScene.id AS scenarioId,apiScene.`name` AS scenario,sch.id AS taskID,sch.`value` AS rule,sch.`enable` AS `taskStatus`,u.`name` AS creator,sch.update_time AS updateTime,
'scenario' AS taskType
FROM api_scenario apiScene
INNER JOIN `schedule` sch ON apiScene.id = sch.resource_id
INNER JOIN `user` u ON u.id = sch.user_id
WHERE sch.`enable` = true AND apiScene.project_id = #{0,jdbcType=VARCHAR}
UNION
SELECT testPlan.id AS scenarioId,testPlan.`name` AS scenario,sch.id AS taskID,sch.`value` AS rule,sch.`enable` AS `taskStatus`,u.`name` AS creator,sch.update_time AS updateTime,
'testPlan' AS taskType
FROM test_plan testPlan
INNER JOIN `schedule` sch ON testPlan.id = sch.resource_id
INNER JOIN `user` u ON u.id = sch.user_id
WHERE sch.`enable` = true AND testPlan.project_id = #{0,jdbcType=VARCHAR}
</select>
</mapper>

View File

@ -98,7 +98,9 @@
<select id="list" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric"
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
select DISTINCT test_plan.*, user.name as user_name, project.name as projectName,schedule.id as scheduleId from test_plan
select DISTINCT test_plan.*, user.name as user_name, project.name as projectName,schedule.id as scheduleId,
IF(schedule.enable = true,true,false) as scheduleOpen
from test_plan
LEFT JOIN user ON user.id = test_plan.principal
LEFT JOIN schedule ON schedule.resource_id = test_plan.id
JOIN project on project.id = test_plan.project_id
@ -235,7 +237,7 @@
<select id="findIdByPerformanceReportId" resultType="java.lang.String">
SELECT report.id FROM test_plan_report report INNER JOIN test_plan_report_data reportData ON report.id = reportData.test_plan_report_id
WHERE reportData.performance_info like CONCAT('%', #{0}'%')
WHERE reportData.performance_info like CONCAT('%', #{0},'%')
</select>
</mapper>

View File

@ -22,8 +22,6 @@ public class LoadTestConsumer {
LoadTestReport loadTestReport = JSON.parseObject(record.value(), LoadTestReport.class);
Reflections reflections = new Reflections(Application.class);
Set<Class<? extends LoadTestFinishEvent>> subTypes = reflections.getSubTypesOf(LoadTestFinishEvent.class);
LogUtil.info("Execute Over: LoadTestConsumer");
System.out.println("Execute Over: LoadTestConsumer");
subTypes.forEach(s -> {
try {
CommonBeanFactory.getBean(s).execute(loadTestReport);

View File

@ -21,7 +21,12 @@ public class ShiroUtils {
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/display/info", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/display/file/**", "anon");
filterChainDefinitionMap.put("/jmeter/download/**", "anon");
filterChainDefinitionMap.put("/authsource/list/allenable", "anon");
filterChainDefinitionMap.put("/sso/signin", "anon");
// for swagger
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-ui/**", "anon");

View File

@ -1,10 +1,10 @@
package io.metersphere.config;
import io.metersphere.commons.user.UserModularRealmAuthenticator;
import io.metersphere.commons.utils.ShiroUtils;
import io.metersphere.security.ApiKeyFilter;
import io.metersphere.security.LdapRealm;
import io.metersphere.security.ShiroDBRealm;
import io.metersphere.security.UserModularRealmAuthenticator;
import io.metersphere.security.realm.LdapRealm;
import io.metersphere.security.realm.ShiroDBRealm;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
@ -46,10 +46,7 @@ public class ShiroConfig implements EnvironmentAware {
shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
filterChainDefinitionMap.put("/display/info", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/display/file/**", "anon");
filterChainDefinitionMap.put("/jmeter/download/**", "anon");
filterChainDefinitionMap.put("/**", "apikey, authc");
return shiroFilterFactoryBean;
}

View File

@ -1,7 +1,6 @@
package io.metersphere.controller;
import io.metersphere.commons.utils.SessionUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -23,15 +22,4 @@ public class IndexController {
return "redirect:/";
}
}
@GetMapping(value = "/sso/login")
public String ossLogin() {
return "redirect:/";
}
@GetMapping(value = "/sso/logout")
public void ossLogout() {
SecurityUtils.getSubject().logout();
}
}

View File

@ -8,4 +8,5 @@ import lombok.Setter;
public class LoginRequest {
private String username;
private String password;
private String authenticate;
}

View File

@ -40,22 +40,24 @@ public class TestPlanTestJob extends MsScheduleJob {
private Map<String,String> apiTestCaseIdMap;
private Map<String,String> performanceIdMap;
private ApiAutomationService apiAutomationService;
private PerformanceTestService performanceTestService;
private TestPlanScenarioCaseService testPlanScenarioCaseService;
private TestPlanApiCaseService testPlanApiCaseService;
private ApiTestCaseService apiTestCaseService;
private TestPlanReportService testPlanReportService;
private TestPlanLoadCaseService testPlanLoadCaseService;
private TestPlanService testPlanService;
public TestPlanTestJob() {
this.apiAutomationService = CommonBeanFactory.getBean(ApiAutomationService.class);
this.performanceTestService = CommonBeanFactory.getBean(PerformanceTestService.class);
this.testPlanScenarioCaseService = CommonBeanFactory.getBean(TestPlanScenarioCaseService.class);
this.testPlanApiCaseService = CommonBeanFactory.getBean(TestPlanApiCaseService.class);
this.apiTestCaseService = CommonBeanFactory.getBean(ApiTestCaseService.class);
this.testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class);
this.testPlanLoadCaseService = CommonBeanFactory.getBean(TestPlanLoadCaseService.class);
this.testPlanService = CommonBeanFactory.getBean(TestPlanService.class);
}
/**
@ -128,7 +130,7 @@ public class TestPlanTestJob extends MsScheduleJob {
scenarioRequest.setTestPlanID(this.resourceId);
scenarioRequest.setRunMode(ApiRunMode.SCHEDULE_SCENARIO_PLAN.name());
scenarioRequest.setTestPlanReportId(testPlanReport.getId());
apiAutomationService.run(scenarioRequest);
testPlanService.runApiCase(scenarioRequest);
LogUtil.info("-------------- testplan schedule ---------- scenario case over -----------------");
//执行性能测试任务

View File

@ -1,6 +1,5 @@
package io.metersphere.security;
import io.metersphere.commons.user.MsUserToken;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;

View File

@ -1,4 +1,4 @@
package io.metersphere.commons.user;
package io.metersphere.security;
import org.apache.shiro.authc.UsernamePasswordToken;

View File

@ -1,4 +1,4 @@
package io.metersphere.commons.user;
package io.metersphere.security;
import io.metersphere.commons.exception.MSException;
import org.apache.shiro.authc.AuthenticationException;

View File

@ -1,4 +1,4 @@
package io.metersphere.security;
package io.metersphere.security.realm;
import io.metersphere.base.domain.Role;

View File

@ -1,4 +1,4 @@
package io.metersphere.security;
package io.metersphere.security.realm;
import io.metersphere.base.domain.Role;

View File

@ -8,7 +8,6 @@ import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.constants.UserSource;
import io.metersphere.commons.constants.UserStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.MsUserToken;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.CodingUtil;
import io.metersphere.commons.utils.SessionUtils;
@ -24,6 +23,7 @@ import io.metersphere.dto.UserDTO;
import io.metersphere.dto.UserRoleDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.notice.domain.UserDetail;
import io.metersphere.security.MsUserToken;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
@ -559,16 +559,14 @@ public class UserService {
String login = (String) SecurityUtils.getSubject().getSession().getAttribute("authenticate");
String username = StringUtils.trim(request.getUsername());
String password = "";
String loginType = UserSource.LDAP.name();
if (!StringUtils.equals(login, UserSource.LDAP.name())) {
loginType = UserSource.LOCAL.name();
password = StringUtils.trim(request.getPassword());
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return ResultHolder.error("user or password can't be null");
}
}
MsUserToken token = new MsUserToken(username, password, loginType);
MsUserToken token = new MsUserToken(username, password, login);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);

View File

@ -59,4 +59,12 @@ public class TestPlanReportController {
TestPlanReport report = testPlanReportService.genTestPlanReport(planId,userId,ReportTriggerMode.API.name());
testPlanReportService.countReportByTestPlanReportId(report.getId(),null, ReportTriggerMode.API.name());
}
@GetMapping("/saveTestPlanReport/{planId}/{triggerMode}")
public String saveTestPlanReport(@PathVariable String planId,@PathVariable String triggerMode) {
String userId = SessionUtils.getUser().getId();
TestPlanReport report = testPlanReportService.genTestPlanReport(planId,userId,triggerMode);
testPlanReportService.countReportByTestPlanReportId(report.getId(),null, triggerMode);
return "success";
}
}

View File

@ -12,5 +12,13 @@ public class TestPlanDTO extends TestPlan {
private String projectName;
private String userName;
private List<String> projectIds;
/**
* 定时任务ID
*/
private String scheduleId;
/**
* 定时任务是否开启
*/
private boolean scheduleOpen;
}

View File

@ -1,21 +1,29 @@
package io.metersphere.track.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.metersphere.api.dto.automation.ApiScenarioDTO;
import io.metersphere.api.dto.automation.ScenarioStatus;
import io.metersphere.api.dto.automation.SchedulePlanScenarioExecuteRequest;
import io.metersphere.api.dto.automation.TestPlanScenarioRequest;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
import io.metersphere.api.dto.definition.request.*;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.*;
@ -37,6 +45,8 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.ListedHashTree;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -61,6 +71,8 @@ public class TestPlanService {
@Resource
TestPlanTestCaseMapper testPlanTestCaseMapper;
@Resource
ExtApiScenarioMapper extApiScenarioMapper;
@Resource
SqlSessionFactory sqlSessionFactory;
@Lazy
@Resource
@ -87,6 +99,10 @@ public class TestPlanService {
private TestPlanScenarioCaseService testPlanScenarioCaseService;
@Resource
private TestPlanLoadCaseService testPlanLoadCaseService;
@Resource
private JMeterService jMeterService;
@Resource
private ApiAutomationService apiAutomationService;
public synchronized void addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
@ -728,4 +744,85 @@ public class TestPlanService {
public String findScheduleCreateUserById(String testPlanId) {
return extTestPlanMapper.findScheduleCreateUserById(testPlanId);
}
/**
* 测试计划的定时任务--执行场景案例
*
* @param request
* @return
*/
public String runApiCase(SchedulePlanScenarioExecuteRequest request) {
MsTestPlan testPlan = new MsTestPlan();
testPlan.setHashTree(new LinkedList<>());
HashTree jmeterHashTree = new ListedHashTree();
Map<String, Map<String, String>> testPlanScenarioIdMap = request.getTestPlanScenarioIDMap();
for (Map.Entry<String, Map<String, String>> entry : testPlanScenarioIdMap.entrySet()) {
String testPlanID = entry.getKey();
Map<String, String> planScenarioIdMap = entry.getValue();
List<ApiScenarioWithBLOBs> apiScenarios = extApiScenarioMapper.selectIds(new ArrayList<>(planScenarioIdMap.keySet()));
try {
boolean isFirst = true;
for (ApiScenarioWithBLOBs item : apiScenarios) {
String apiScenarioID = item.getId();
String planScenarioID = planScenarioIdMap.get(apiScenarioID);
if (StringUtils.isEmpty(planScenarioID)) {
continue;
}
if (item.getStepTotal() == 0) {
// 只有一个场景且没有测试步骤则提示
if (apiScenarios.size() == 1) {
MSException.throwException((item.getName() + "" + Translator.get("automation_exec_info")));
}
LogUtil.warn(item.getName() + "" + Translator.get("automation_exec_info"));
continue;
}
MsThreadGroup group = new MsThreadGroup();
group.setLabel(item.getName());
group.setName(UUID.randomUUID().toString());
// 批量执行的结果直接存储为报告
if (isFirst) {
group.setName(request.getId());
isFirst = false;
}
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JSONObject element = JSON.parseObject(item.getScenarioDefinition());
MsScenario scenario = JSONObject.parseObject(item.getScenarioDefinition(), MsScenario.class);
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (element != null && StringUtils.isNotEmpty(element.getString("hashTree"))) {
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {
});
scenario.setHashTree(elements);
}
if (StringUtils.isNotEmpty(element.getString("variables"))) {
LinkedList<ScenarioVariable> variables = mapper.readValue(element.getString("variables"),
new TypeReference<LinkedList<ScenarioVariable>>() {
});
scenario.setVariables(variables);
}
group.setEnableCookieShare(scenario.isEnableCookieShare());
LinkedList<MsTestElement> scenarios = new LinkedList<>();
scenarios.add(scenario);
// 创建场景报告
//不同的运行模式第二个参数入参不同
apiAutomationService.createScenarioReport(group.getName(),
planScenarioID + ":" + request.getTestPlanReportId(),
item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);
}
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
String runMode = ApiRunMode.SCHEDULE_SCENARIO_PLAN.name();
// 调用执行方法
jMeterService.runDefinition(request.getId(), jmeterHashTree, request.getReportId(), runMode);
return request.getId();
}
}

@ -1 +1 @@
Subproject commit 36116c1bff736377e6b8a3b828c5fa9bd8b2f2f8
Subproject commit c2ed883e9be6fc7e01589f81916bf4ddc62148c0

View File

@ -65,14 +65,24 @@
<el-table-column :label="$t('commons.operating')" width="200px" v-if="!referenced">
<template v-slot:default="{row}">
<div v-if="trashEnable">
<el-button type="text" @click="reductionApi(row)" v-tester>{{ $t('commons.reduction') }}</el-button>
<el-button type="text" @click="remove(row)" v-tester>{{ $t('api_test.automation.remove') }}</el-button>
<!-- <el-button type="text" @click="reductionApi(row)" v-tester>{{ $t('commons.reduction') }}</el-button>-->
<!-- <el-button type="text" @click="remove(row)" v-tester>{{ $t('api_test.automation.remove') }}</el-button>-->
<ms-table-operator-button :tip="$t('commons.reduction')" icon="el-icon-refresh-left" @exec="reductionApi(row)" v-tester/>
<ms-table-operator-button :tip="$t('api_test.automation.remove')" icon="el-icon-delete" @exec="remove(row)" type="danger" v-tester/>
</div>
<div v-else>
<el-button type="text" @click="edit(row)" v-tester>{{ $t('api_test.automation.edit') }}</el-button>
<el-button type="text" @click="execute(row)" v-tester>{{ $t('api_test.automation.execute') }}</el-button>
<el-button type="text" @click="copy(row)" v-tester>{{ $t('api_test.automation.copy') }}</el-button>
<el-button type="text" @click="remove(row)" v-tester>{{ $t('api_test.automation.remove') }}</el-button>
<ms-table-operator-button :tip="$t('api_test.automation.edit')" icon="el-icon-edit" @exec="edit(row)" v-tester/>
<ms-table-operator-button class="run-button" :is-tester-permission="true" :tip="$t('api_test.automation.execute')"
icon="el-icon-video-play"
@exec="execute(row)" v-tester/>
<ms-table-operator-button :tip="$t('api_test.automation.copy')" icon="el-icon-document"
@exec="copy(row)"/>
<ms-table-operator-button :tip="$t('api_test.automation.remove')" icon="el-icon-delete" @exec="remove(row)" type="danger" v-tester/>
<!-- <el-button type="text" @click="edit(row)" v-tester>{{ $t('api_test.automation.edit') }}</el-button>-->
<!-- <el-button type="text" @click="execute(row)" v-tester>{{ $t('api_test.automation.execute') }}</el-button>-->
<!-- <el-button type="text" @click="copy(row)" v-tester>{{ $t('api_test.automation.copy') }}</el-button>-->
<!-- <el-button type="text" @click="remove(row)" v-tester>{{ $t('api_test.automation.remove') }}</el-button>-->
<ms-scenario-extend-buttons :row="row"/>
</div>
</template>
@ -109,6 +119,7 @@
import MsTestPlanList from "./testplan/TestPlanList";
import MsTableSelectAll from "../../../common/components/table/MsTableSelectAll";
import {API_CASE_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
export default {
name: "MsApiScenarioList",
@ -121,7 +132,8 @@
MsTag,
MsApiReportDetail,
MsScenarioExtendButtons,
MsTestPlanList
MsTestPlanList,
MsTableOperatorButton
},
props: {
referenced: {
@ -426,5 +438,8 @@
/deep/ .el-drawer__header {
margin-bottom: 0px;
}
/deep/ .run-button {
background-color: #409EFF;
border-color: #409EFF;
}
</style>

View File

@ -9,7 +9,7 @@
<el-dropdown-item command="create_performance" v-tester>{{ $t('api_test.create_performance_test') }}</el-dropdown-item>
</el-dropdown-menu>
<ms-reference-view ref="viewRef"/>
<ms-schedule-maintain ref="scheduleMaintain" />
<ms-schedule-maintain ref="scheduleMaintain" @refreshTable="refreshTable" />
</el-dropdown>
</template>
@ -61,6 +61,9 @@
})
});
},
refreshTable(){
}
}
}
</script>

View File

@ -115,7 +115,6 @@
<span>{{ scope.row.actualEndTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"

View File

@ -203,6 +203,7 @@ export default {
this.$post(url, param, () => {
this.$success(this.$t('commons.save_success'));
this.$emit("refreshTable");
});
},
checkScheduleEdit() {

View File

@ -69,12 +69,13 @@
<el-table-column v-if="!isReadOnly" :label="$t('commons.operating')" min-width="130" align="center">
<template v-slot:default="scope">
<!--<el-button type="text" @click="reductionApi(scope.row)" v-if="trashEnable">{{$t('commons.reduction')}}</el-button>-->
<el-button type="text" @click="handleTestCase(scope.row)" v-if="!trashEnable">{{ $t('commons.edit') }}
</el-button>
<el-button type="text" @click="handleDelete(scope.row)" style="color: #F56C6C">{{ $t('commons.delete') }}
</el-button>
<!-- <el-button type="text" @click="handleTestCase(scope.row)" v-if="!trashEnable">{{ $t('commons.edit') }}-->
<!-- </el-button>-->
<!-- <el-button type="text" @click="handleDelete(scope.row)" style="color: #F56C6C">{{ $t('commons.delete') }}-->
<!-- </el-button>-->
<ms-table-operator-button :tip="$t('commons.edit')" icon="el-icon-edit" @exec="handleTestCase(scope.row)" v-tester/>
<ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete" @exec="handleDelete(scope.row)" type="danger" v-tester/>
<ms-api-case-table-extend-btns @showCaseRef="showCaseRef" @showEnvironment="showEnvironment" @createPerformance="createPerformance" :row="scope.row" v-tester/>
</template>
</el-table-column>

View File

@ -112,10 +112,20 @@
<el-table-column v-if="!isReadOnly" :label="$t('commons.operating')" min-width="130" align="center">
<template v-slot:default="scope">
<el-button type="text" @click="reductionApi(scope.row)" v-if="trashEnable" v-tester>{{ $t('commons.reduction') }}</el-button>
<el-button type="text" @click="editApi(scope.row)" v-else v-tester>{{ $t('commons.edit') }}</el-button>
<el-button type="text" @click="handleTestCase(scope.row)">{{ $t('api_test.definition.request.case') }}</el-button>
<el-button type="text" @click="handleDelete(scope.row)" style="color: #F56C6C" v-tester>{{ $t('commons.delete') }}</el-button>
<ms-table-operator-button :tip="$t('commons.reduction')" icon="el-icon-refresh-left" @exec="reductionApi(scope.row)" v-if="trashEnable" v-tester/>
<ms-table-operator-button :tip="$t('commons.edit')" icon="el-icon-edit" @exec="editApi(scope.row)" v-else v-tester/>
<el-tooltip :content="$t('api_test.definition.request.case')"
placement="bottom"
:enterable="false"
effect="dark" >
<el-button @click="handleTestCase(scope.row)"
@keydown.enter.native.prevent
circle
style="padding: 9px 1px;font-size: 1px"
size="mini" >Case
</el-button>
</el-tooltip>
<ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete" @exec="handleDelete(scope.row)" type="danger" v-tester/>
</template>
</el-table-column>
</el-table>
@ -153,6 +163,7 @@
import ApiStatus from "@/business/components/api/definition/components/list/ApiStatus";
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
import {API_DEFINITION_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTipButton from "@/business/components/common/components/MsTipButton";
export default {
name: "ApiList",
@ -171,6 +182,7 @@
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit,
MsTipButton,
MsTableAdvSearchBar
},
data() {

View File

@ -9,7 +9,7 @@
<el-table-column prop="index" :label="$t('api_test.home_page.running_task_list.table_coloum.index')" width="80" show-overflow-tooltip/>
<el-table-column prop="scenario" :label="$t('api_test.home_page.running_task_list.table_coloum.scenario')" width="200" >
<template v-slot:default="{row}">
<el-link type="info" @click="redirect(row.scenarioId)">
<el-link type="info" @click="redirect(row)">
{{ row.scenario }}
</el-link>
</template>
@ -88,7 +88,11 @@ export default {
});
},
redirect(param){
this.$emit('redirectPage','scenario','scenario', 'edit:'+param);
if(param.taskType === 'testPlan'){
this.$emit('redirectPage','testPlanEdit','', param.scenarioId);
}else{
this.$emit('redirectPage','scenario','scenario', 'edit:'+param.scenarioId);
}
}
},

View File

@ -1,7 +1,7 @@
<template>
<el-dropdown size="medium" @command="handleCommand" class="align-right">
<span class="dropdown-link">
{{currentUser.name}}<i class="el-icon-caret-bottom el-icon--right"/>
{{ currentUser.name }}<i class="el-icon-caret-bottom el-icon--right"/>
</span>
<template v-slot:dropdown>
<el-dropdown-menu>
@ -23,82 +23,77 @@
</template>
<script>
import {getCurrentUser} from "../../../../common/js/utils";
import AboutUs from "./AboutUs";
import axios from "axios";
import {getCurrentUser} from "@/common/js/utils";
import AboutUs from "./AboutUs";
import axios from "axios";
export default {
name: "MsUser",
components: {AboutUs},
data() {
return {
isReadOnly: this.$store.state.isReadOnly.flag
}
},
computed: {
currentUser: () => {
return getCurrentUser();
}
},
methods: {
handleCommand(command) {
switch (command) {
case "personal":
// TODO
this.$router.push('/setting/personsetting').catch(error => error);
break;
case "logout":
axios.get("/signout").then(response => {
if (response.data.success) {
localStorage.clear();
window.location.href = "/login";
} else {
if (response.data.message === 'sso') {
localStorage.clear();
window.location.href = "/sso/logout"
}
}
}).catch(error => {
export default {
name: "MsUser",
components: {AboutUs},
data() {
return {
isReadOnly: this.$store.state.isReadOnly.flag
}
},
computed: {
currentUser: () => {
return getCurrentUser();
}
},
methods: {
handleCommand(command) {
switch (command) {
case "personal":
// TODO
this.$router.push('/setting/personsetting').catch(error => error);
break;
case "logout":
axios.get("/signout").then(response => {
if (response.data.success) {
localStorage.clear();
window.location.href = "/login";
});
break;
case "about":
this.$refs.aboutUs.open();
break;
case "help":
window.location.href = "https://metersphere.io/docs/index.html";
break;
default:
break;
}
},
changeBar(item) {
this.isReadOnly = !this.isReadOnly
this.$store.commit('setFlag', this.isReadOnly);
this.$store.commit('setValue', item);
if(item=="old"){
window.location.href = "/#/api/home_obsolete";
}else {
window.location.href = "/#/api/home";
}
}
}).catch(error => {
localStorage.clear();
window.location.href = "/login";
});
break;
case "about":
this.$refs.aboutUs.open();
break;
case "help":
window.location.href = "https://metersphere.io/docs/index.html";
break;
default:
break;
}
},
changeBar(item) {
this.isReadOnly = !this.isReadOnly
this.$store.commit('setFlag', this.isReadOnly);
this.$store.commit('setValue', item);
if (item == "old") {
window.location.href = "/#/api/home_obsolete";
} else {
window.location.href = "/#/api/home";
}
}
}
}
</script>
<style scoped>
.dropdown-link {
cursor: pointer;
font-size: 12px;
color: rgb(245, 245, 245);
line-height: 40px;
}
.dropdown-link {
cursor: pointer;
font-size: 12px;
color: rgb(245, 245, 245);
line-height: 40px;
}
.align-right {
float: right;
}
.align-right {
float: right;
}
</style>

View File

@ -128,10 +128,10 @@
@exec="openReport(scope.row.id, scope.row.reportId)"/>
</template>
</ms-table-operator>
<ms-table-operator-button style="margin-left: 10px;color:#85888E;border-color: #85888E; border-width: thin;" v-if="!scope.row.scheduleId" type="text"
<ms-table-operator-button style="margin-left: 10px;color:#85888E;border-color: #85888E; border-width: thin;" v-if="!scope.row.scheduleOpen" type="text"
:tip="$t('commons.trigger_mode.schedule')" icon="el-icon-time"
@exec="scheduleTask(scope.row)"/>
<ms-table-operator-button style="margin-left: 10px;color:#6C317C; border-color: #6C317C; border-width: thin;" v-if="scope.row.scheduleId" type="text"
<ms-table-operator-button style="margin-left: 10px;color:#6C317C; border-color: #6C317C; border-width: thin;" v-if="scope.row.scheduleOpen" type="text"
:tip="$t('commons.trigger_mode.schedule')" icon="el-icon-time"
@exec="scheduleTask(scope.row)"/>
</template>
@ -145,7 +145,7 @@
<ms-delete-confirm :title="$t('test_track.plan.plan_delete')" @delete="_handleDelete" ref="deleteConfirm" :with-tip="enableDeleteTip">
{{$t('test_track.plan.plan_delete_tip')}}
</ms-delete-confirm>
<ms-schedule-maintain ref="scheduleMaintain" />
<ms-schedule-maintain ref="scheduleMaintain" @refreshTable="initTableData" />
</el-card>
</template>

View File

@ -18,10 +18,10 @@
@click="dialogFormVisible=true">
{{ $t('report.test_stop_now') }}
</el-button>
<el-button :disabled="isReadOnly || report.status !== 'Completed'" type="success" plain size="mini"
@click="rerun(testId)">
{{ $t('report.test_execute_again') }}
</el-button>
<!-- <el-button :disabled="isReadOnly || report.status !== 'Completed'" type="success" plain size="mini"-->
<!-- @click="rerun(testId)">-->
<!-- {{ $t('report.test_execute_again') }}-->
<!-- </el-button>-->
<el-button :disabled="isReadOnly" type="info" plain size="mini" @click="handleExport(reportName)">
{{ $t('test_track.plan_view.export_report') }}
</el-button>

View File

@ -193,19 +193,17 @@ export default {
}
this.$post("/test/plan/load/case/list/" + this.currentPage + "/" + this.pageSize, param, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
let {itemCount, listObject} = data;
this.total = itemCount;
this.tableData = listObject;
})
},
refreshStatus() {
this.refreshScheduler = setInterval(() => {
let arr = this.tableData.filter(data => data.status !== 'Completed' && data.status !== 'Error' && data.status !== "Saved");
if (arr.length > 0) {
this.initTable();
} else {
clearInterval(this.refreshScheduler);
}
}, 4000);
//
let arr = this.tableData.filter(data => data.status !== 'Completed' && data.status !== 'Error' && data.status !== 'Saved');
arr.length > 0 ? this.initTable() : clearInterval(this.refreshScheduler);
}, 8000);
},
handleSelectAll(selection) {
if (selection.length > 0) {
@ -263,20 +261,18 @@ export default {
testPlanLoadId: loadCase.id,
triggerMode: 'CASE'
}).then(() => {
this.$notify({
this.$notify.success({
title: loadCase.caseName,
message: this.$t('test_track.plan.load_case.exec'),
type: 'success'
message: this.$t('test_track.plan.load_case.exec').toString()
});
this.initTable();
}).catch(() => {
//todo
this.$post('/test/plan/load/case/update', {id: loadCase.id, status: "error"}, () => {
this.initTable();
});
this.$notify.error({
title: loadCase.caseName,
message: this.$t('test_track.plan.load_case.error')
message: this.$t('test_track.plan.load_case.error').toString()
});
})
},
@ -289,15 +285,15 @@ export default {
},
sort(column) {
//
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.initTableData();
// if (this.condition.orders) {
// this.condition.orders = [];
// }
// _sort(column, this.condition);
// this.initTable();
},
filter(filters) {
_filter(filters, this.condition);
this.initTableData();
// _filter(filters, this.condition);
// this.initTable();
},
getReport(data) {
const {loadReportId} = data;

View File

@ -150,9 +150,12 @@
handleSave() {
let param = {};
this.buildParam(param);
this.result = this.$post('/case/report/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.$get('/test/plan/report/saveTestPlanReport/'+this.planId+'/MANUAL', () => {
this.result = this.$post('/case/report/edit', param, () => {
this.$success(this.$t('commons.save_success'));
});
});
},
buildParam(param) {
let content = {};

View File

@ -139,8 +139,10 @@
handleSave() {
let param = {};
this.buildParam(param);
this.result = this.$post('/case/report/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.$get('/test/plan/report/saveTestPlanReport/'+this.planId+'/MANUAL', () => {
this.result = this.$post('/case/report/edit', param, () => {
this.$success(this.$t('commons.save_success'));
});
});
},
buildParam(param) {

View File

@ -19,12 +19,12 @@
</div>
</el-col>
<el-col :span="12" class="head-right">
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleSave">
{{$t('commons.save')}}
</el-button>
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleEdit">
{{$t('test_track.plan_view.edit_component')}}
</el-button>
<!-- <el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleSave">-->
<!-- {{$t('commons.save')}}-->
<!-- </el-button>-->
<!-- <el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleEdit">-->
<!-- {{$t('test_track.plan_view.edit_component')}}-->
<!-- </el-button>-->
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleExport(report.name)">
{{$t('test_track.plan_view.export_report')}}
</el-button>

View File

@ -226,7 +226,7 @@ export default {
if (response.success) {
this.projects = response.data.filter(da => da.id !== getCurrentProjectID());
} else {
this.$warning()(response.message);
this.$warning(response.message);
}
});
},

@ -1 +1 @@
Subproject commit 3d96d7c61bc50f32f18311d23f447663e02d7d44
Subproject commit cea763d974b213104f6d6fc30ab48434b72e10f4

View File

@ -18,6 +18,7 @@
<el-radio-group v-model="form.authenticate">
<el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</el-radio>
<el-radio label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio>
<el-radio :label="auth.id" size="mini" v-for="auth in authSources" :key="auth.id">{{ auth.type }} {{ auth.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="username">
@ -52,6 +53,7 @@ import {DEFAULT_LANGUAGE} from "@/common/js/constants";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {};
const auth = requireComponent.keys().length > 0 ? requireComponent("./auth/Auth.vue") : {};
export default {
name: "Login",
@ -84,7 +86,9 @@ export default {
msg: '',
ready: false,
openLdap: false,
loginTitle: this.$t("commons.login") + " MeterSphere"
loginTitle: this.$t("commons.login") + " MeterSphere",
authSources: [],
loginUrl: 'signin',
}
},
beforeCreate() {
@ -94,17 +98,17 @@ export default {
display.default.showLogin(this);
}
if (auth.default !== undefined) {
auth.default.getAuthSources(this);
}
if (!response.data.success) {
if (response.data.message === 'sso') {
window.location.href = "/sso/login"
} else {
this.ready = true;
}
this.ready = true;
} else {
let user = response.data.data;
saveLocalStorage(response.data);
this.getLanguage(user.language);
window.location.href = "/"
window.location.href = "/";
}
});
this.$get("/ldap/open", response => {
@ -135,28 +139,24 @@ export default {
if (valid) {
switch (this.form.authenticate) {
case "LOCAL":
this.normalLogin();
this.loginUrl = "/signin";
this.doLogin();
break;
case "LDAP":
this.ldapLogin();
this.loginUrl = "/ldap/signin";
this.doLogin();
break;
default:
this.normalLogin();
this.loginUrl = "/sso/signin";
this.doLogin();
}
} else {
return false;
}
});
},
normalLogin() {
this.result = this.$post("signin", this.form, response => {
saveLocalStorage(response);
sessionStorage.setItem('loginSuccess', 'true');
this.getLanguage(response.data.language);
});
},
ldapLogin() {
this.result = this.$post("ldap/signin", this.form, response => {
doLogin() {
this.result = this.$post(this.loginUrl, this.form, response => {
saveLocalStorage(response);
sessionStorage.setItem('loginSuccess', 'true');
this.getLanguage(response.data.language);