feat(测试计划): 测试计划新增批量开启/关闭定时任务功能

This commit is contained in:
limin-fit2 2022-02-11 16:32:33 +08:00 committed by jianxing
parent 89fd47bace
commit 9289719449
13 changed files with 565 additions and 262 deletions

View File

@ -3,6 +3,8 @@ package io.metersphere.api.dto.datacount.request;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
/** /**
* @author song.tianyang * @author song.tianyang
* @Date 2020/12/17 5:04 下午 * @Date 2020/12/17 5:04 下午
@ -13,4 +15,5 @@ import lombok.Setter;
public class ScheduleInfoRequest { public class ScheduleInfoRequest {
private String taskID; private String taskID;
private boolean enable; private boolean enable;
private List<String> taskIds;
} }

View File

@ -176,14 +176,16 @@
<when test="key=='status'"> <when test="key=='status'">
<choose> <choose>
<when test="request.executorOrPrincipal != null"> <when test="request.executorOrPrincipal != null">
AND (( test_plan_principal.principal_id = '${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and AND (( test_plan_principal.principal_id =
'${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and
test_plan.status in test_plan.status in
<foreach collection="values" item="value" separator="," open="(" close=")"> <foreach collection="values" item="value" separator="," open="(" close=")">
#{value} #{value}
</foreach> </foreach>
) )
or or
(test_plan_test_case.executor = '${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and (test_plan_test_case.executor =
'${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and
test_plan_test_case.status in test_plan_test_case.status in
<foreach collection="values" item="value" separator="," open="(" close=")"> <foreach collection="values" item="value" separator="," open="(" close=")">
#{value} #{value}
@ -198,6 +200,20 @@
</otherwise> </otherwise>
</choose> </choose>
</when> </when>
<when test="key=='schedule_status'">
and
<foreach collection="values" item="value" separator="or" open="(" close=")">
<if test="value == 'OPEN'">
schedule.`enable` = 1
</if>
<if test="value == 'SHUT'">
schedule.`enable` = 0
</if>
<if test="value == 'NOTSET' ">
schedule.id is null
</if>
</foreach>
</when>
</choose> </choose>
</if> </if>
</foreach> </foreach>
@ -277,14 +293,17 @@
<foreach collection="projectIds" item="id" separator="," open="(" close=")"> <foreach collection="projectIds" item="id" separator="," open="(" close=")">
#{id} #{id}
</foreach> </foreach>
</if>) as temp </if>
) as temp
</select> </select>
<select id="selectTestPlanByRelevancy" resultMap="BaseResultMap" parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest"> <select id="selectTestPlanByRelevancy" resultMap="BaseResultMap"
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
SELECT * FROM test_plan p SELECT * FROM test_plan p
<where> <where>
<if test="request.scenarioId != null"> <if test="request.scenarioId != null">
AND p.id IN (SELECT test_plan_id FROM test_plan_api_scenario WHERE api_scenario_id = #{request.scenarioId} ) AND p.id IN (SELECT test_plan_id FROM test_plan_api_scenario WHERE api_scenario_id =
#{request.scenarioId} )
</if> </if>
<if test="request.apiId != null"> <if test="request.apiId != null">
AND p.id IN (SELECT test_plan_id FROM test_plan_api_case WHERE api_case_id = #{request.apiId}) AND p.id IN (SELECT test_plan_id FROM test_plan_api_case WHERE api_case_id = #{request.apiId})
@ -295,17 +314,23 @@
</where> </where>
</select> </select>
<select id="findTestProjectNameByTestPlanID" resultType="java.lang.String"> <select id="findTestProjectNameByTestPlanID" resultType="java.lang.String">
SELECT p.name FROM test_plan tp INNER JOIN project p ON p.id =tp.project_id SELECT p.name
WHERE tp.id = #{0} limit 1; FROM test_plan tp
INNER JOIN project p ON p.id = tp.project_id
WHERE tp.id = #{0}
limit 1;
</select> </select>
<select id="findScheduleCreateUserById" resultType="java.lang.String"> <select id="findScheduleCreateUserById" resultType="java.lang.String">
SELECT user_id FROM `schedule` SELECT user_id
FROM `schedule`
WHERE resource_id = #{0} WHERE resource_id = #{0}
limit 1; limit 1;
</select> </select>
<select id="findIdByPerformanceReportId" resultType="java.lang.String"> <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 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}, '%')
AND report.is_performance_executing = true; AND report.is_performance_executing = true;
</select> </select>

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum ScheduleStatus {
OPEN,SHUT,NOTSET
}

View File

@ -277,4 +277,9 @@ public class TestPlanController {
public List<User> getPlanFollow(@PathVariable String planId) { public List<User> getPlanFollow(@PathVariable String planId) {
return testPlanService.getPlanFollow(planId); return testPlanService.getPlanFollow(planId);
} }
@PostMapping(value = "/schedule/Batch/updateEnable")
public void updateBatchScheduleEnable(@RequestBody ScheduleInfoRequest request) {
testPlanService.batchUpdateScheduleEnable(request);
}
} }

View File

@ -22,4 +22,16 @@ public class TestPlanDTO extends TestPlanWithBLOBs {
* 定时任务是否开启 * 定时任务是否开启
*/ */
private boolean scheduleOpen; private boolean scheduleOpen;
/**
* 定时任务状态
*/
private String scheduleStatus;
/**
* 定时任务规则
*/
private String scheduleCorn;
/**
* 定时任务下一次执行时间
*/
private Long scheduleExecuteTime;
} }

View File

@ -8,6 +8,7 @@ import com.google.gson.Gson;
import io.metersphere.api.dto.APIReportResult; import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.EnvironmentType; import io.metersphere.api.dto.EnvironmentType;
import io.metersphere.api.dto.automation.*; import io.metersphere.api.dto.automation.*;
import io.metersphere.api.dto.datacount.request.ScheduleInfoRequest;
import io.metersphere.api.dto.definition.ApiTestCaseRequest; import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.BatchRunDefinitionRequest; import io.metersphere.api.dto.definition.BatchRunDefinitionRequest;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO; import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
@ -55,6 +56,10 @@ import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils; import org.mybatis.spring.SqlSessionUtils;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.TriggerBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -431,6 +436,18 @@ public class TestPlanService {
TestPlanReportExample example = new TestPlanReportExample(); TestPlanReportExample example = new TestPlanReportExample();
example.createCriteria().andTestPlanIdEqualTo(item.getId()); example.createCriteria().andTestPlanIdEqualTo(item.getId());
item.setExecutionTimes((int) testPlanReportMapper.countByExample(example)); item.setExecutionTimes((int) testPlanReportMapper.countByExample(example));
if (StringUtils.isNotBlank(item.getScheduleId())) {
if (item.isScheduleOpen()) {
item.setScheduleStatus(ScheduleStatus.OPEN.name());
Schedule schedule = scheduleService.getScheduleByResource(item.getId(), ScheduleGroup.TEST_PLAN_TEST.name());
item.setScheduleCorn(schedule.getValue());
item.setScheduleExecuteTime(getNextTriggerTime(schedule.getValue()));
} else {
item.setScheduleStatus(ScheduleStatus.SHUT.name());
}
} else {
item.setScheduleStatus(ScheduleStatus.NOTSET.name());
}
}); });
calcTestPlanRate(testPlans); calcTestPlanRate(testPlans);
return testPlans; return testPlans;
@ -1983,4 +2000,23 @@ public class TestPlanService {
} }
return JSONArray.parseArray(customFields.get(0).getOptions()); return JSONArray.parseArray(customFields.get(0).getOptions());
} }
public void batchUpdateScheduleEnable(ScheduleInfoRequest request) {
for (String id : request.getTaskIds()) {
Schedule schedule = scheduleService.getSchedule(id);
schedule.setEnable(request.isEnable());
apiAutomationService.updateSchedule(schedule);
}
}
//获取下次执行时间getFireTimeAfter也可以下下次...
private static long getNextTriggerTime(String cron) {
if (!CronExpression.isValidExpression(cron)) {
return 0;
}
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("Caclulate Date").withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
Date time0 = trigger.getStartTime();
Date time1 = trigger.getFireTimeAfter(time0);
return time1.getTime();
}
} }

View File

@ -0,0 +1,82 @@
<template>
<el-dialog :visible.sync="dialogFormVisible"
:before-close="close"
width="14%">
<div>
<el-switch
v-model="schedule.enable"
:inactive-text="$t('test_track.plan.batch_update_schedule_enable', [size])"
active-color="#783887">
</el-switch>
<p style="font-size: 10px;color: gray;margin-bottom: -10px">
{{ $t('test_track.plan.batch_update_schedule_enable_alert') }}</p>
</div>
<template v-slot:footer>
<ms-dialog-footer
@cancel="close"
@confirm="saveTest"/>
</template>
</el-dialog>
</template>
<script>
import MsDialogFooter from "../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
export default {
components: {MsDialogFooter},
data() {
return {
name: "MsTestPlanScheduleBatchSwitch",
schedule: {
enable: true,
taskIds: []
},
size: 0,
selectRows: new Set(),
allDataRows: new Set(),
dialogFormVisible: false,
test: {}
}
},
props: {
treeNodes: {
type: Array
},
currentProject: {
type: Object
}
},
methods: {
open(ids, size) {
if (size) {
this.size = size;
} else {
this.size = this.$parent.selectDataCounts;
}
this.schedule.taskIds = ids;
listenGoBack(this.close);
this.dialogFormVisible = true;
},
saveTest() {
this.$post("/test/plan/schedule/Batch/updateEnable", this.schedule, () => {
this.$success(this.$t('commons.modify_success'));
this.dialogFormVisible = false;
this.$emit("refreshTable");
});
},
close() {
removeGoBackListener(this.close);
this.dialogFormVisible = false;
}
}
}
</script>
<style scoped>
</style>

View File

@ -255,6 +255,7 @@ export default {
} }
this.taskID = paramTestId; this.taskID = paramTestId;
this.findSchedule(paramTestId); this.findSchedule(paramTestId);
this.$emit("refreshTable");
}); });
}, },
initUserList() { initUserList() {

View File

@ -7,46 +7,58 @@
</template> </template>
<el-table <ms-table
border
class="adjust-table"
:data="tableData"
@filter-change="filter"
@sort-change="sort"
:height="screenHeight"
v-loading="result.loading" v-loading="result.loading"
@row-click="intoPlan"> operator-width="170px"
<template v-for="(item, index) in tableLabel"> row-key="id"
<el-table-column :data="tableData"
v-if="item.id == 'name'" :condition="condition"
:total="total"
:page-size.sync="pageSize"
:operators="operators"
:screen-height="screenHeight"
:batch-operators="batchButtons"
:remember-order="true"
:fields.sync="fields"
:field-key="tableHeaderKey"
@handlePageChange="intoPlan"
@refresh="initTableData"
ref="testPlanLitTable">
<span v-for="item in fields" :key="item.key">
<ms-table-column
prop="name" prop="name"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('commons.name')" :label="$t('commons.name')"
show-overflow-tooltip min-width="200px">
:key="index"> </ms-table-column>
</el-table-column> <ms-table-column
<el-table-column
v-if="item.id == 'userName'"
prop="principalName" prop="principalName"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.plan_principal')" :label="$t('test_track.plan.plan_principal')"
show-overflow-tooltip min-width="200px">
:key="index"> </ms-table-column>
</el-table-column> <ms-table-column
<el-table-column
v-if="item.id == 'createUser'"
prop="createUser" prop="createUser"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('commons.create_user')" :label="$t('commons.create_user')"
show-overflow-tooltip min-width="200px">
:key="index"> </ms-table-column>
</el-table-column> <ms-table-column
<el-table-column
v-if="item.id == 'status'"
prop="status" prop="status"
column-key="status"
:filters="statusFilters" :filters="statusFilters"
:label="$t('test_track.plan.plan_status')" column-key="status"
show-overflow-tooltip :field="item"
:min-width="100" :fields-width="fieldsWidth"
:key="index"> min-width="120px"
:label="$t('test_track.plan.plan_status')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span @click.stop="clickt = 'stop'"> <span @click.stop="clickt = 'stop'">
<el-dropdown class="test-case-status" @command="statusChange"> <el-dropdown class="test-case-status" @command="statusChange">
@ -73,181 +85,204 @@
</el-dropdown> </el-dropdown>
</span> </span>
</template> </template>
</el-table-column> </ms-table-column>
<el-table-column <ms-table-column
v-if="item.id=='follow'" prop="scheduleStatus"
:filters="scheduleFilters"
column-key="scheduleStatus"
:field="item"
:fields-width="fieldsWidth"
min-width="200px"
:label="$t('commons.trigger_mode.schedule')">
<template v-slot="scope">
<span v-if="scope.row.scheduleStatus === 'OPEN'">
<el-tooltip placement="bottom-start" effect="light">
<div slot="content">
{{ $t('api_test.home_page.running_task_list.table_coloum.run_rule') }}: {{
scope.row.scheduleCorn
}}<br/>
{{ $t('test_track.plan.next_run_time') }}<span>{{
scope.row.scheduleExecuteTime | timestampFormatDate
}}</span>
</div>
<el-switch
v-model="scope.row.scheduleOpen"
inactive-color="#DCDFE6"
@change="scheduleChange(scope.row)">
</el-switch>
</el-tooltip>
</span>
<span v-else-if="scope.row.scheduleStatus === 'SHUT'">
<el-switch
v-model="scope.row.scheduleOpen"
inactive-color="#DCDFE6"
@change="scheduleChange(scope.row)">
</el-switch>
</span>
<span v-else>
<el-link @click.stop="scheduleTask(scope.row)" style="color:#783887;">{{
$t('schedule.not_set')
}}</el-link>
</span>
</template>
</ms-table-column>
<ms-table-column
prop="follow" prop="follow"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.follow_people')" :label="$t('test_track.plan.follow_people')"
show-overflow-tooltip min-width="200px">
:key="index" </ms-table-column>
> <ms-table-column
</el-table-column>
<el-table-column
v-if="item.id == 'stage'"
prop="stage" prop="stage"
column-key="stage" column-key="stage"
:field="item"
:fields-width="fieldsWidth"
sortable
:filters="stageFilters" :filters="stageFilters"
:label="$t('test_track.plan.plan_stage')" :label="$t('test_track.plan.plan_stage')"
show-overflow-tooltip min-width="120px">
:min-width="110"
:key="index">
<template v-slot:default="scope"> <template v-slot:default="scope">
<plan-stage-table-item :option="stageOption" :stage="scope.row.stage"/> <plan-stage-table-item :option="stageOption" :stage="scope.row.stage"/>
</template> </template>
</el-table-column> </ms-table-column>
<el-table-column <ms-table-column
v-if="item.id == 'testRate'"
prop="testRate" prop="testRate"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.home.test_rate')" :label="$t('test_track.home.test_rate')"
min-width="100" min-width="120px">
show-overflow-tooltip
:key="index">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-progress :percentage="scope.row.testRate"></el-progress> <el-progress :percentage="scope.row.testRate"></el-progress>
</template> </template>
</el-table-column> </ms-table-column>
<el-table-column <ms-table-column
v-if="item.id == 'projectName'"
prop="projectName" prop="projectName"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.plan.plan_project')" :label="$t('test_track.plan.plan_project')"
show-overflow-tooltip min-width="200px">
:key="index"> </ms-table-column>
</el-table-column> <ms-table-column
<el-table-column v-if="item.id == 'tags'" prop="tags" prop="tags"
:label="$t('api_test.automation.tag')" :key="index"> :field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('api_test.automation.tag')"
min-width="200px">
<template v-slot:default="scope"> <template v-slot:default="scope">
<ms-tag v-for="(itemName,index) in scope.row.tags" :key="index" type="success" effect="plain" <ms-tag v-for="(itemName,index) in scope.row.tags" :key="index" type="success" effect="plain"
:content="itemName" style="margin-left: 0px; margin-right: 2px"></ms-tag> :content="itemName" style="margin-left: 0px; margin-right: 2px"></ms-tag>
</template> </template>
</el-table-column> </ms-table-column>
<el-table-column <ms-table-column
v-if="item.id == 'executionTimes'"
prop="executionTimes" prop="executionTimes"
:label="$t('commons.execution_times')" :field="item"
show-overflow-tooltip :fields-width="fieldsWidth"
:min-width="100"
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'testPlanTestCaseCount'"
prop="testPlanTestCaseCount"
:label="$t('test_track.plan.test_plan_test_case_count')"
show-overflow-tooltip
:min-width="100"
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'testPlanApiCaseCount'"
prop="testPlanApiCaseCount"
:label="$t('test_track.plan.test_plan_api_case_count')"
show-overflow-tooltip
:min-width="100"
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'testPlanApiScenarioCount'"
prop="testPlanApiScenarioCount"
:label="$t('test_track.plan.test_plan_api_scenario_count')"
show-overflow-tooltip
:min-width="100"
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'testPlanLoadCaseCount'"
prop="testPlanLoadCaseCount"
:label="$t('test_track.plan.test_plan_load_case_count')"
show-overflow-tooltip
:min-width="100"
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'passRate'"
prop="passRate"
:label="$t('commons.pass_rate')"
show-overflow-tooltip
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'plannedStartTime'"
sortable sortable
:label="$t('commons.execution_times')"
min-width="160px">
</ms-table-column>
<ms-table-column
prop="testPlanTestCaseCount"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.plan.test_plan_test_case_count')"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="testPlanApiCaseCount"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.plan.test_plan_api_case_count')"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="testPlanApiScenarioCount"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.plan.test_plan_api_scenario_count')"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="testPlanLoadCaseCount"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.plan.test_plan_load_case_count')"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="passRate"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('commons.pass_rate')"
min-width="120px">
</ms-table-column>
<ms-table-column
prop="plannedStartTime" prop="plannedStartTime"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.planned_start_time')" :label="$t('test_track.plan.planned_start_time')"
show-overflow-tooltip min-width="200px">
:min-width="110"
:key="index">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span>{{ scope.row.plannedStartTime | timestampFormatDate }}</span> <span>{{ scope.row.plannedStartTime | timestampFormatDate }}</span>
</template> </template>
</el-table-column> </ms-table-column>
<el-table-column <ms-table-column
v-if="item.id == 'plannedEndTime'"
sortable
prop="plannedEndTime" prop="plannedEndTime"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.planned_end_time')" :label="$t('test_track.plan.planned_end_time')"
show-overflow-tooltip min-width="160px">
:min-width="110"
:key="index">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span>{{ scope.row.plannedEndTime | timestampFormatDate }}</span> <span>{{ scope.row.plannedEndTime | timestampFormatDate }}</span>
</template> </template>
</el-table-column> </ms-table-column>
<el-table-column <ms-table-column
v-if="item.id == 'actualStartTime'"
sortable
prop="actualStartTime" prop="actualStartTime"
:min-width="110" :field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.actual_start_time')" :label="$t('test_track.plan.actual_start_time')"
show-overflow-tooltip min-width="200px">
:key="index">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span>{{ scope.row.actualStartTime | timestampFormatDate }}</span> <span>{{ scope.row.actualStartTime | timestampFormatDate }}</span>
</template> </template>
</el-table-column> </ms-table-column>
<el-table-column <ms-table-column
v-if="item.id == 'actualEndTime'"
sortable
:min-width="110"
prop="actualEndTime" prop="actualEndTime"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.actual_end_time')" :label="$t('test_track.plan.actual_end_time')"
show-overflow-tooltip min-width="200px">
:key="index">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span>{{ scope.row.actualEndTime | timestampFormatDate }}</span> <span>{{ scope.row.actualEndTime | timestampFormatDate }}</span>
</template> </template>
</el-table-column> </ms-table-column>
</template> </span>
<el-table-column <template v-slot:opt-before="scope">
min-width="200"
:label="$t('commons.operating')">
<template slot="header">
<header-label-operate @exec="customHeader"/>
</template>
<template v-slot:default="scope">
<div>
<ms-table-operator :edit-permission="['PROJECT_TRACK_PLAN:READ+EDIT']"
:show-delete="false"
@editClick="handleEdit(scope.row)">
<template v-slot:front>
<ms-table-operator-button :tip="$t('api_test.run')" icon="el-icon-video-play" class="run-button" <ms-table-operator-button :tip="$t('api_test.run')" icon="el-icon-video-play" class="run-button"
@exec="handleRun(scope.row)" v-permission="['PROJECT_TRACK_PLAN:READ+RUN']"/> @exec="handleRun(scope.row)" v-permission="['PROJECT_TRACK_PLAN:READ+RUN']"
style="margin-right: 10px;"/>
</template> </template>
<template v-slot:middle> <template v-slot:opt-behind="scope">
<ms-table-operator-button :tip="$t('commons.copy')" icon="el-icon-copy-document"
@exec="handleCopy(scope.row)" v-permission="['PROJECT_TRACK_PLAN:READ+COPY']"/>
<ms-table-operator-button v-permission="['PROJECT_TRACK_PLAN:READ+EDIT']"
:tip="$t('test_track.plan_view.view_report')" icon="el-icon-s-data"
@exec="openReport(scope.row)"/>
</template>
</ms-table-operator>
<template>
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!scope.row.showFollow"> <el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!scope.row.showFollow">
<i class="el-icon-star-off" style="color: #783987; font-size: 25px; cursor: pointer;padding-left: 5px;width: 28px;height: 28px; top: 5px; position: relative" @click="saveFollow(scope.row)"></i> <i class="el-icon-star-off"
style="color: #783987; font-size: 25px; cursor: pointer;padding-left: 5px;width: 28px;height: 28px; top: 5px; position: relative"
@click="saveFollow(scope.row)"></i>
</el-tooltip> </el-tooltip>
<el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="scope.row.showFollow"> <el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="scope.row.showFollow">
<i class="el-icon-star-on" style="color: #783987; font-size: 30px; cursor: pointer;padding-left: 5px;width: 28px;height: 28px; top: 6px; position: relative" @click="saveFollow(scope.row)"></i> <i class="el-icon-star-on"
style="color: #783987; font-size: 30px; cursor: pointer;padding-left: 5px;width: 28px;height: 28px; top: 6px; position: relative"
@click="saveFollow(scope.row)"></i>
</el-tooltip> </el-tooltip>
</template> <el-dropdown @command="handleCommand($event, scope.row)" class="scenario-ext-btn"
<el-dropdown @command="handleCommand($event, scope.row)" class="scenario-ext-btn" v-permission="['PROJECT_TRACK_PLAN:READ+DELETE','PROJECT_TRACK_PLAN:READ+SCHEDULE']"> v-permission="['PROJECT_TRACK_PLAN:READ+DELETE','PROJECT_TRACK_PLAN:READ+SCHEDULE']">
<el-link type="primary" :underline="false"> <el-link type="primary" :underline="false">
<el-icon class="el-icon-more"></el-icon> <el-icon class="el-icon-more"></el-icon>
</el-link> </el-link>
@ -260,14 +295,9 @@
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</div>
</template> </template>
</el-table-column>
</el-table>
<header-custom ref="headerCustom" :initTableData="init" :optionalFields=headerItems
:type=type></header-custom>
</ms-table>
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize" <ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/> :total="total"/>
<ms-delete-confirm :title="$t('test_track.plan.plan_delete')" @delete="_handleDelete" ref="deleteConfirm" <ms-delete-confirm :title="$t('test_track.plan.plan_delete')" @delete="_handleDelete" ref="deleteConfirm"
@ -275,7 +305,9 @@
{{ $t('test_track.plan.plan_delete_tip') }} {{ $t('test_track.plan.plan_delete_tip') }}
</ms-delete-confirm> </ms-delete-confirm>
<ms-test-plan-schedule-maintain ref="scheduleMaintain" @refreshTable="initTableData"/> <ms-test-plan-schedule-maintain ref="scheduleMaintain" @refreshTable="initTableData"/>
<plan-run-mode-with-env @handleRunBatch="_handleRun" ref="runMode" :plan-case-ids="[]" :type="'plan'" :plan-id="currentPlanId"/> <ms-test-plan-schedule-batch-switch ref="scheduleBatchSwitch" @refreshTable="initTableData"/>
<plan-run-mode-with-env @handleRunBatch="_handleRun" ref="runMode" :plan-case-ids="[]" :type="'plan'"
:plan-id="currentPlanId"/>
<test-plan-report-review ref="testCaseReportView"/> <test-plan-report-review ref="testCaseReportView"/>
<ms-task-center ref="taskCenter" :show-menu="false"/> <ms-task-center ref="taskCenter" :show-menu="false"/>
</el-card> </el-card>
@ -294,13 +326,11 @@ import {TEST_PLAN_CONFIGS} from "../../../common/components/search/search-compon
import { import {
_filter, _filter,
_sort, _sort,
deepClone, deepClone, getCustomTableHeader, getCustomTableWidth,
getLabel,
getLastTableSortField, getLastTableSortField,
saveLastTableSortField saveLastTableSortField
} from "@/common/js/tableUtils"; } from "@/common/js/tableUtils";
import {TEST_PLAN_LIST} from "@/common/js/constants"; import {TEST_PLAN_LIST} from "@/common/js/constants";
import {Test_Plan_List} from "@/business/components/common/model/JsonData";
import HeaderCustom from "@/business/components/common/head/HeaderCustom"; import HeaderCustom from "@/business/components/common/head/HeaderCustom";
import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate"; import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
import MsTag from "@/business/components/common/components/MsTag"; import MsTag from "@/business/components/common/components/MsTag";
@ -315,6 +345,10 @@ import PlanRunModeWithEnv from "@/business/components/track/plan/common/PlanRunM
import TestPlanReportReview from "@/business/components/track/report/components/TestPlanReportReview"; import TestPlanReportReview from "@/business/components/track/report/components/TestPlanReportReview";
import MsTaskCenter from "@/business/components/task/TaskCenter"; import MsTaskCenter from "@/business/components/task/TaskCenter";
import {getPlanStageOption} from "@/network/test-plan"; import {getPlanStageOption} from "@/network/test-plan";
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTestPlanScheduleBatchSwitch from "@/business/components/track/plan/components/ScheduleBatchSwitch";
export default { export default {
name: "TestPlanList", name: "TestPlanList",
@ -329,13 +363,14 @@ export default {
MsTestPlanScheduleMaintain, MsTestPlanScheduleMaintain,
MsTableOperator, MsTableOperatorButton, MsTableOperator, MsTableOperatorButton,
MsDialogFooter, MsTableHeader, MsDialogFooter, MsTableHeader,
MsTablePagination, PlanRunModeWithEnv, MsTaskCenter MsTablePagination, PlanRunModeWithEnv, MsTaskCenter,
MsTableColumn,
MsTable,
MsTestPlanScheduleBatchSwitch
}, },
data() { data() {
return { return {
createUser: "", createUser: "",
type: TEST_PLAN_LIST,
headerItems: Test_Plan_List,
tableHeaderKey: "TEST_PLAN_LIST", tableHeaderKey: "TEST_PLAN_LIST",
tableLabel: [], tableLabel: [],
result: {}, result: {},
@ -351,6 +386,8 @@ export default {
hasEditPermission: false, hasEditPermission: false,
total: 0, total: 0,
tableData: [], tableData: [],
fields: getCustomTableHeader('TEST_PLAN_LIST'),
fieldsWidth: getCustomTableWidth('TEST_PLAN_LIST'),
screenHeight: 'calc(100vh - 200px)', screenHeight: 'calc(100vh - 200px)',
statusFilters: [ statusFilters: [
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'}, {text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
@ -363,8 +400,41 @@ export default {
{text: this.$t('test_track.plan.system_test'), value: 'system'}, {text: this.$t('test_track.plan.system_test'), value: 'system'},
{text: this.$t('test_track.plan.regression_test'), value: 'regression'}, {text: this.$t('test_track.plan.regression_test'), value: 'regression'},
], ],
scheduleFilters: [
{text: this.$t('test_track.plan.schedule_enabled'), value: 'OPEN'},
{text: this.$t('test_track.issue.status_closed'), value: 'SHUT'},
{text: this.$t('schedule.not_set'), value: 'NOTSET'}
],
currentPlanId: "", currentPlanId: "",
stageOption: [] stageOption: [],
operators: [],
batchButtons: [],
publicButtons: [
{
name: this.$t('test_track.plan.test_plan_batch_switch'),
handleClick: this.handleBatchSwitch
}
],
simpleOperators: [
{
tip: this.$t('commons.edit'),
icon: "el-icon-edit",
exec: this.handleEdit,
permissions: ['PROJECT_TRACK_PLAN:READ+EDIT']
},
{
tip: this.$t('commons.copy'),
icon: "el-icon-copy-document",
exec: this.handleCopy,
permission: ['PROJECT_TRACK_PLAN:READ+COPY']
},
{
tip: this.$t('test_track.plan_view.view_report'),
icon: "el-icon-s-data",
exec: this.openReport,
permission: ['PROJECT_TRACK_PLAN:READ+EDIT']
},
]
}; };
}, },
watch: { watch: {
@ -376,6 +446,8 @@ export default {
}, },
created() { created() {
this.projectId = this.$route.params.projectId; this.projectId = this.$route.params.projectId;
this.batchButtons = this.publicButtons;
this.operators = this.simpleOperators;
if (!this.projectId) { if (!this.projectId) {
this.projectId = getCurrentProjectID(); this.projectId = getCurrentProjectID();
} }
@ -467,8 +539,6 @@ export default {
}) })
}); });
}); });
getLabel(this, TEST_PLAN_LIST);
}, },
copyData(status) { copyData(status) {
return JSON.parse(JSON.stringify(this.dataMap.get(status))); return JSON.parse(JSON.stringify(this.dataMap.get(status)));
@ -486,6 +556,18 @@ export default {
handleEdit(testPlan) { handleEdit(testPlan) {
this.$emit('testPlanEdit', testPlan); this.$emit('testPlanEdit', testPlan);
}, },
handleBatchSwitch() {
let param = [];
let size = 0;
let row = this.$refs.testPlanLitTable.selectRows.size;
this.$refs.testPlanLitTable.selectRows.forEach((item) => {
if (item.scheduleStatus != null && item.scheduleStatus != 'NOTSET') {
param.push(item.scheduleId);
size++;
}
});
this.$refs.scheduleBatchSwitch.open(param, size);
},
statusChange(data) { statusChange(data) {
if (!hasPermission('PROJECT_TRACK_PLAN:READ+EDIT')) { if (!hasPermission('PROJECT_TRACK_PLAN:READ+EDIT')) {
return; return;
@ -514,6 +596,26 @@ export default {
} }
}); });
}, },
scheduleChange(row) {
let param = {};
let message = this.$t('api_test.home_page.running_task_list.confirm.close_title');
param.taskID = row.scheduleId;
param.enable = row.scheduleOpen;
if (row.scheduleOpen) {
message = this.$t('api_test.home_page.running_task_list.confirm.open_title');
}
this.$confirm(message, this.$t('commons.prompt'), {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning',
}).then(() => {
this.result = this.$post('/test/plan/schedule/updateEnableByPrimyKey', param, response => {
this.$success(this.$t('commons.save_success'));
});
}).catch(() => {
row.scheduleOpen = param.enable = !param.enable;
});
},
handleDelete(testPlan) { handleDelete(testPlan) {
this.enableDeleteTip = testPlan.status === 'Underway' ? true : false; this.enableDeleteTip = testPlan.status === 'Underway' ? true : false;
this.$refs.deleteConfirm.open(testPlan); this.$refs.deleteConfirm.open(testPlan);
@ -525,6 +627,10 @@ export default {
this.$success(this.$t('commons.delete_success')); this.$success(this.$t('commons.delete_success'));
}); });
}, },
refresh() {
this.$refs.table.clear();
this.$emit('refresh');
},
intoPlan(row, column, event) { intoPlan(row, column, event) {
if (column.label !== this.$t('commons.operating')) { if (column.label !== this.$t('commons.operating')) {
this.$router.push('/track/plan/view/' + row.id); this.$router.push('/track/plan/view/' + row.id);
@ -581,7 +687,16 @@ export default {
}) })
}, },
_handleRun(config) { _handleRun(config) {
let {mode, reportType, onSampleError, runWithinResourcePool, resourcePoolId, envMap, environmentType, environmentGroupId} = config; let {
mode,
reportType,
onSampleError,
runWithinResourcePool,
resourcePoolId,
envMap,
environmentType,
environmentGroupId
} = config;
let param = {mode, reportType, onSampleError, runWithinResourcePool, resourcePoolId, envMap}; let param = {mode, reportType, onSampleError, runWithinResourcePool, resourcePoolId, envMap};
param.testPlanId = this.currentPlanId; param.testPlanId = this.currentPlanId;
param.projectId = getCurrentProjectID(); param.projectId = getCurrentProjectID();

View File

@ -118,8 +118,9 @@ export let CUSTOM_TABLE_HEADER = {
{id: 'plannedStartTime', key: '7', label: 'test_track.plan.planned_start_time'}, {id: 'plannedStartTime', key: '7', label: 'test_track.plan.planned_start_time'},
{id: 'plannedEndTime', key: '8', label: 'test_track.plan.planned_end_time'}, {id: 'plannedEndTime', key: '8', label: 'test_track.plan.planned_end_time'},
{id: 'actualStartTime', key: '9', label: 'test_track.plan.actual_start_time'}, {id: 'actualStartTime', key: '9', label: 'test_track.plan.actual_start_time'},
{id: 'actualEndTime', key: 'a', label: 'test_track.plan.actual_end_time'}, {id: 'actualEndTime', key: '10', label: 'test_track.plan.actual_end_time'},
{id: 'tags', key: 'b', label: 'commons.tag'}, {id: 'tags', key: 'a', label: 'commons.tag'},
{id: 'scheduleStatus', key: 'b', label: 'commons.trigger_mode.schedule'},
{id: 'executionTimes', key: 'c', label: 'commons.execution_times'}, {id: 'executionTimes', key: 'c', label: 'commons.execution_times'},
{id: 'passRate', key: 'd', label: 'commons.pass_rate'}, {id: 'passRate', key: 'd', label: 'commons.pass_rate'},
{id: 'createUser', key: 'e', label: 'commons.create_user'}, {id: 'createUser', key: 'e', label: 'commons.create_user'},

View File

@ -1823,6 +1823,7 @@ export default {
swagger_schedule: "swagger", swagger_schedule: "swagger",
confirm: { confirm: {
close_title: "Do you want to close this scheduled task", close_title: "Do you want to close this scheduled task",
open_title: "Do you want to start this scheduled task?",
} }
} }
}, },
@ -2109,6 +2110,11 @@ export default {
test_plan_load_case_count: "Load case count", test_plan_load_case_count: "Load case count",
test_plan_component_case_count: "Component Case Count", test_plan_component_case_count: "Component Case Count",
data_name: "Data Name", data_name: "Data Name",
test_plan_batch_switch: "batch on/off scheduled tasks",
batch_update_schedule_enable: 'update the scheduled task status of {0} test plans',
batch_update_schedule_enable_alert: 'note: only test plans with scheduled tasks can be updated',
next_run_time: 'next running time',
schedule_enabled: 'enabled',
load_case: { load_case: {
case: "Load Case", case: "Load Case",
execution_status: "Execution status", execution_status: "Execution status",

View File

@ -1828,6 +1828,7 @@ export default {
swagger_schedule: "swagger", swagger_schedule: "swagger",
confirm: { confirm: {
close_title: "要关闭这条定时任务吗?", close_title: "要关闭这条定时任务吗?",
open_title: "要开启这条定时任务吗?",
} }
} }
}, },
@ -2109,6 +2110,11 @@ export default {
test_plan_load_case_count: "性能用例数", test_plan_load_case_count: "性能用例数",
test_plan_component_case_count: "步骤用例数", test_plan_component_case_count: "步骤用例数",
data_name: "数据名称", data_name: "数据名称",
test_plan_batch_switch: "批量开/关定时任务",
batch_update_schedule_enable: '更新{0}个测试计划的定时任务状态为',
batch_update_schedule_enable_alert: '注意:只能更新已设置了定时任务的测试计划',
next_run_time: '下次运行时间',
schedule_enabled: '已开启',
load_case: { load_case: {
case: "性能用例", case: "性能用例",
execution_status: "执行状态", execution_status: "执行状态",

View File

@ -1828,6 +1828,7 @@ export default {
swagger_schedule: "swagger", swagger_schedule: "swagger",
confirm: { confirm: {
close_title: "要關閉這條定時任務嗎?", close_title: "要關閉這條定時任務嗎?",
open_title: "要開啟這條定時任務嗎?",
} }
} }
}, },
@ -2108,6 +2109,11 @@ export default {
test_plan_load_case_count: "性能用例數", test_plan_load_case_count: "性能用例數",
test_plan_component_case_count: "步驟用例數", test_plan_component_case_count: "步驟用例數",
data_name: "數據名稱", data_name: "數據名稱",
test_plan_batch_switch: "批量開/關定時任務",
batch_update_schedule_enable: '更新{0}個測試計畫的定時任務狀態為',
batch_update_schedule_enable_alert: '注意:只能更新已設定了定時任務的測試計畫',
next_run_time: '下次運行時間',
schedule_enabled: '已開啟',
load_case: { load_case: {
case: "性能用例", case: "性能用例",
execution_status: "執行狀態", execution_status: "執行狀態",