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.Setter;
import java.util.List;
/**
* @author song.tianyang
* @Date 2020/12/17 5:04 下午
@ -13,4 +15,5 @@ import lombok.Setter;
public class ScheduleInfoRequest {
private String taskID;
private boolean enable;
private List<String> taskIds;
}

View File

@ -103,7 +103,7 @@
</include>
</if>
<if test="${condition}.principal != null">
and test_plan.id in (SELECT test_plan_id FROM test_plan_principal WHERE principal_id
and test_plan.id in (SELECT test_plan_id FROM test_plan_principal WHERE principal_id
<include refid="condition">
<property name="object" value="${condition}.principal"/>
</include>
@ -114,8 +114,8 @@
<select id="list" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric"
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
select DISTINCT test_plan.*, project.name as projectName,schedule.id as scheduleId,
(select name from user where user.id = test_plan.creator) as createUser,
IF(schedule.enable = true,true,false) as scheduleOpen,
(select name from user where user.id = test_plan.creator) as createUser,
IF(schedule.enable = true,true,false) as scheduleOpen,
(select COUNT(*) from test_plan_test_case t
inner join test_case on t.case_id = test_case.id
left join test_case_node on test_case_node.id = test_case.node_id
@ -176,21 +176,23 @@
<when test="key=='status'">
<choose>
<when test="request.executorOrPrincipal != null">
AND (( test_plan_principal.principal_id = '${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and
test_plan.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
)
or
(test_plan_test_case.executor = '${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and
test_plan_test_case.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
))
AND (( test_plan_principal.principal_id =
'${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and
test_plan.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
)
or
(test_plan_test_case.executor =
'${@io.metersphere.commons.utils.SessionUtils@getUserId()}' and
test_plan_test_case.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
))
</when>
<otherwise >
<otherwise>
and test_plan.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
@ -198,6 +200,20 @@
</otherwise>
</choose>
</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>
</if>
</foreach>
@ -277,14 +293,17 @@
<foreach collection="projectIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>) as temp
</if>
) as temp
</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
<where>
<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 test="request.apiId != null">
AND p.id IN (SELECT test_plan_id FROM test_plan_api_case WHERE api_case_id = #{request.apiId})
@ -295,18 +314,24 @@
</where>
</select>
<select id="findTestProjectNameByTestPlanID" resultType="java.lang.String">
SELECT p.name FROM test_plan tp INNER JOIN project p ON p.id =tp.project_id
WHERE tp.id = #{0} limit 1;
SELECT p.name
FROM test_plan tp
INNER JOIN project p ON p.id = tp.project_id
WHERE tp.id = #{0}
limit 1;
</select>
<select id="findScheduleCreateUserById" resultType="java.lang.String">
SELECT user_id FROM `schedule`
SELECT user_id
FROM `schedule`
WHERE resource_id = #{0}
limit 1;
</select>
<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},'%')
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}, '%')
AND report.is_performance_executing = true;
</select>
<select id="listRecent" resultType="io.metersphere.base.domain.TestPlan">
@ -316,7 +341,7 @@
<if test="projectId != null">
and test_plan.project_id = #{projectId}
</if>
and (test_plan.creator = #{userId} or test_plan.principal = #{userId})
and (test_plan.creator = #{userId} or test_plan.principal = #{userId})
</where>
order by test_plan.update_time desc
</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) {
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 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.EnvironmentType;
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.BatchRunDefinitionRequest;
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.SqlSessionFactory;
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.LoggerFactory;
import org.springframework.context.annotation.Lazy;
@ -431,6 +436,18 @@ public class TestPlanService {
TestPlanReportExample example = new TestPlanReportExample();
example.createCriteria().andTestPlanIdEqualTo(item.getId());
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);
return testPlans;
@ -1983,4 +2000,23 @@ public class TestPlanService {
}
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.findSchedule(paramTestId);
this.$emit("refreshTable");
});
},
initUserList() {

View File

@ -7,267 +7,297 @@
</template>
<el-table
border
class="adjust-table"
:data="tableData"
@filter-change="filter"
@sort-change="sort"
:height="screenHeight"
<ms-table
v-loading="result.loading"
@row-click="intoPlan">
<template v-for="(item, index) in tableLabel">
<el-table-column
v-if="item.id == 'name'"
operator-width="170px"
row-key="id"
:data="tableData"
: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"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('commons.name')"
show-overflow-tooltip
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'userName'"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="principalName"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.plan_principal')"
show-overflow-tooltip
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'createUser'"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="createUser"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('commons.create_user')"
show-overflow-tooltip
:key="index">
</el-table-column>
<el-table-column
v-if="item.id == 'status'"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="status"
column-key="status"
:filters="statusFilters"
:label="$t('test_track.plan.plan_status')"
show-overflow-tooltip
:min-width="100"
:key="index">
<template v-slot:default="scope">
<span @click.stop="clickt = 'stop'">
<el-dropdown class="test-case-status" @command="statusChange">
<span class="el-dropdown-link">
<plan-status-table-item :value="scope.row.status"/>
</span>
<el-dropdown-menu slot="dropdown" chang>
<el-dropdown-item :disabled="!hasEditPermission" :command="{item: scope.row, status: 'Prepare'}">
{{ $t('test_track.plan.plan_status_prepare') }}
</el-dropdown-item>
<el-dropdown-item :disabled="!hasEditPermission"
:command="{item: scope.row, status: 'Underway'}">
{{ $t('test_track.plan.plan_status_running') }}
</el-dropdown-item>
<el-dropdown-item :disabled="!hasEditPermission"
:command="{item: scope.row, status: 'Finished'}">
{{ $t('test_track.plan.plan_status_finished') }}
</el-dropdown-item>
<el-dropdown-item :disabled="!hasEditPermission"
:command="{item: scope.row, status: 'Completed'}">
{{ $t('test_track.plan.plan_status_completed') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
column-key="status"
:field="item"
:fields-width="fieldsWidth"
min-width="120px"
:label="$t('test_track.plan.plan_status')">
<template v-slot:default="scope">
<span @click.stop="clickt = 'stop'">
<el-dropdown class="test-case-status" @command="statusChange">
<span class="el-dropdown-link">
<plan-status-table-item :value="scope.row.status"/>
</span>
<el-dropdown-menu slot="dropdown" chang>
<el-dropdown-item :disabled="!hasEditPermission" :command="{item: scope.row, status: 'Prepare'}">
{{ $t('test_track.plan.plan_status_prepare') }}
</el-dropdown-item>
<el-dropdown-item :disabled="!hasEditPermission"
:command="{item: scope.row, status: 'Underway'}">
{{ $t('test_track.plan.plan_status_running') }}
</el-dropdown-item>
<el-dropdown-item :disabled="!hasEditPermission"
:command="{item: scope.row, status: 'Finished'}">
{{ $t('test_track.plan.plan_status_finished') }}
</el-dropdown-item>
<el-dropdown-item :disabled="!hasEditPermission"
:command="{item: scope.row, status: 'Completed'}">
{{ $t('test_track.plan.plan_status_completed') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</template>
</ms-table-column>
<ms-table-column
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>
</el-table-column>
<el-table-column
v-if="item.id=='follow'"
</ms-table-column>
<ms-table-column
prop="follow"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.follow_people')"
show-overflow-tooltip
:key="index"
>
</el-table-column>
<el-table-column
v-if="item.id == 'stage'"
min-width="200px">
</ms-table-column>
<ms-table-column
prop="stage"
column-key="stage"
:field="item"
:fields-width="fieldsWidth"
sortable
:filters="stageFilters"
:label="$t('test_track.plan.plan_stage')"
show-overflow-tooltip
:min-width="110"
:key="index">
min-width="120px">
<template v-slot:default="scope">
<plan-stage-table-item :option="stageOption" :stage="scope.row.stage"/>
</template>
</el-table-column>
<el-table-column
v-if="item.id == 'testRate'"
</ms-table-column>
<ms-table-column
prop="testRate"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.home.test_rate')"
min-width="100"
show-overflow-tooltip
:key="index">
min-width="120px">
<template v-slot:default="scope">
<el-progress :percentage="scope.row.testRate"></el-progress>
</template>
</el-table-column>
<el-table-column
v-if="item.id == 'projectName'"
</ms-table-column>
<ms-table-column
prop="projectName"
:field="item"
:fields-width="fieldsWidth"
:label="$t('test_track.plan.plan_project')"
show-overflow-tooltip
:key="index">
</el-table-column>
<el-table-column v-if="item.id == 'tags'" prop="tags"
:label="$t('api_test.automation.tag')" :key="index">
min-width="200px">
</ms-table-column>
<ms-table-column
prop="tags"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('api_test.automation.tag')"
min-width="200px">
<template v-slot:default="scope">
<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>
</template>
</el-table-column>
<el-table-column
v-if="item.id == 'executionTimes'"
</ms-table-column>
<ms-table-column
prop="executionTimes"
:label="$t('commons.execution_times')"
show-overflow-tooltip
: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'"
:field="item"
:fields-width="fieldsWidth"
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"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.planned_start_time')"
show-overflow-tooltip
:min-width="110"
:key="index">
min-width="200px">
<template v-slot:default="scope">
<span>{{ scope.row.plannedStartTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
v-if="item.id == 'plannedEndTime'"
sortable
</ms-table-column>
<ms-table-column
prop="plannedEndTime"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.planned_end_time')"
show-overflow-tooltip
:min-width="110"
:key="index">
min-width="160px">
<template v-slot:default="scope">
<span>{{ scope.row.plannedEndTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
v-if="item.id == 'actualStartTime'"
sortable
</ms-table-column>
<ms-table-column
prop="actualStartTime"
:min-width="110"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.actual_start_time')"
show-overflow-tooltip
:key="index">
min-width="200px">
<template v-slot:default="scope">
<span>{{ scope.row.actualStartTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
v-if="item.id == 'actualEndTime'"
sortable
:min-width="110"
</ms-table-column>
<ms-table-column
prop="actualEndTime"
:field="item"
:fields-width="fieldsWidth"
sortable
:label="$t('test_track.plan.actual_end_time')"
show-overflow-tooltip
:key="index">
min-width="200px">
<template v-slot:default="scope">
<span>{{ scope.row.actualEndTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</ms-table-column>
</span>
<template v-slot:opt-before="scope">
<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']"
style="margin-right: 10px;"/>
</template>
<template v-slot:opt-behind="scope">
<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>
</el-tooltip>
<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>
</el-tooltip>
<el-dropdown @command="handleCommand($event, scope.row)" class="scenario-ext-btn"
v-permission="['PROJECT_TRACK_PLAN:READ+DELETE','PROJECT_TRACK_PLAN:READ+SCHEDULE']">
<el-link type="primary" :underline="false">
<el-icon class="el-icon-more"></el-icon>
</el-link>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="delete" v-permission="['PROJECT_TRACK_PLAN:READ+DELETE']">
{{ $t('commons.delete') }}
</el-dropdown-item>
<el-dropdown-item command="schedule_task" v-permission="['PROJECT_TRACK_PLAN:READ+SCHEDULE']">
{{ $t('commons.trigger_mode.schedule') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<el-table-column
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"
@exec="handleRun(scope.row)" v-permission="['PROJECT_TRACK_PLAN:READ+RUN']"/>
</template>
<template v-slot:middle>
<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">
<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 :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>
</el-tooltip>
</template>
<el-dropdown @command="handleCommand($event, scope.row)" class="scenario-ext-btn" v-permission="['PROJECT_TRACK_PLAN:READ+DELETE','PROJECT_TRACK_PLAN:READ+SCHEDULE']">
<el-link type="primary" :underline="false">
<el-icon class="el-icon-more"></el-icon>
</el-link>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="delete" v-permission="['PROJECT_TRACK_PLAN:READ+DELETE']">
{{ $t('commons.delete') }}
</el-dropdown-item>
<el-dropdown-item command="schedule_task" v-permission="['PROJECT_TRACK_PLAN:READ+SCHEDULE']">
{{ $t('commons.trigger_mode.schedule') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</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"
:total="total"/>
<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') }}
</ms-delete-confirm>
<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"/>
<ms-task-center ref="taskCenter" :show-menu="false"/>
</el-card>
@ -294,13 +326,11 @@ import {TEST_PLAN_CONFIGS} from "../../../common/components/search/search-compon
import {
_filter,
_sort,
deepClone,
getLabel,
deepClone, getCustomTableHeader, getCustomTableWidth,
getLastTableSortField,
saveLastTableSortField
} from "@/common/js/tableUtils";
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 HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
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 MsTaskCenter from "@/business/components/task/TaskCenter";
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 {
name: "TestPlanList",
@ -329,14 +363,15 @@ export default {
MsTestPlanScheduleMaintain,
MsTableOperator, MsTableOperatorButton,
MsDialogFooter, MsTableHeader,
MsTablePagination, PlanRunModeWithEnv, MsTaskCenter
MsTablePagination, PlanRunModeWithEnv, MsTaskCenter,
MsTableColumn,
MsTable,
MsTestPlanScheduleBatchSwitch
},
data() {
return {
createUser: "",
type: TEST_PLAN_LIST,
headerItems: Test_Plan_List,
tableHeaderKey:"TEST_PLAN_LIST",
tableHeaderKey: "TEST_PLAN_LIST",
tableLabel: [],
result: {},
cardResult: {},
@ -351,6 +386,8 @@ export default {
hasEditPermission: false,
total: 0,
tableData: [],
fields: getCustomTableHeader('TEST_PLAN_LIST'),
fieldsWidth: getCustomTableWidth('TEST_PLAN_LIST'),
screenHeight: 'calc(100vh - 200px)',
statusFilters: [
{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.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: "",
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: {
@ -376,6 +446,8 @@ export default {
},
created() {
this.projectId = this.$route.params.projectId;
this.batchButtons = this.publicButtons;
this.operators = this.simpleOperators;
if (!this.projectId) {
this.projectId = getCurrentProjectID();
}
@ -455,7 +527,7 @@ export default {
} else {
follow = follow + d.name;
}
if(this.currentUser().id===d.id){
if (this.currentUser().id === d.id) {
showFollow = true;
}
})
@ -467,8 +539,6 @@ export default {
})
});
});
getLabel(this, TEST_PLAN_LIST);
},
copyData(status) {
return JSON.parse(JSON.stringify(this.dataMap.get(status)));
@ -486,6 +556,18 @@ export default {
handleEdit(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) {
if (!hasPermission('PROJECT_TRACK_PLAN:READ+EDIT')) {
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) {
this.enableDeleteTip = testPlan.status === 'Underway' ? true : false;
this.$refs.deleteConfirm.open(testPlan);
@ -525,6 +627,10 @@ export default {
this.$success(this.$t('commons.delete_success'));
});
},
refresh() {
this.$refs.table.clear();
this.$emit('refresh');
},
intoPlan(row, column, event) {
if (column.label !== this.$t('commons.operating')) {
this.$router.push('/track/plan/view/' + row.id);
@ -540,7 +646,7 @@ export default {
this.condition.orders = [];
}
_sort(column, this.condition);
this.saveSortField(this.tableHeaderKey,this.condition.orders);
this.saveSortField(this.tableHeaderKey, this.condition.orders);
this.initTableData();
},
openReport(plan) {
@ -550,8 +656,8 @@ export default {
row.redirectFrom = "testPlan";
this.$refs.scheduleMaintain.open(row);
},
saveSortField(key,orders){
saveLastTableSortField(key,JSON.stringify(orders));
saveSortField(key, orders) {
saveLastTableSortField(key, JSON.stringify(orders));
},
handleCommand(cmd, row) {
switch (cmd) {
@ -565,7 +671,7 @@ export default {
},
handleCopy(row) {
this.cardResult.loading = true;
this.$post('test/plan/copy/' + row.id, {},() => {
this.$post('test/plan/copy/' + row.id, {}, () => {
this.initTableData();
});
},
@ -581,7 +687,16 @@ export default {
})
},
_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};
param.testPlanId = this.currentPlanId;
param.projectId = getCurrentProjectID();
@ -591,33 +706,33 @@ export default {
param.environmentGroupId = environmentGroupId;
param.requestOriginator = "TEST_PLAN";
this.$refs.taskCenter.open();
this.result = this.$post('test/plan/run/', param,() => {
this.result = this.$post('test/plan/run/', param, () => {
this.$success(this.$t('commons.run_success'));
}, error => {
// this.$error(error.message);
});
},
saveFollow(row){
if(row.showFollow){
saveFollow(row) {
if (row.showFollow) {
row.showFollow = false;
for (let i = 0; i < row.follows.length; i++) {
if(row.follows[i]===this.currentUser().id){
row.follows.splice(i,1)
if (row.follows[i] === this.currentUser().id) {
row.follows.splice(i, 1)
break;
}
}
this.$post('/test/plan/edit/follows/' + row.id, row.follows,() => {
this.$post('/test/plan/edit/follows/' + row.id, row.follows, () => {
this.$success(this.$t('commons.cancel_follow_success'));
});
return
}
if(!row.showFollow){
if (!row.showFollow) {
row.showFollow = true;
if(!row.follows){
if (!row.follows) {
row.follows = [];
}
row.follows.push(this.currentUser().id);
this.$post('/test/plan/edit/follows/' + row.id, row.follows,() => {
this.$post('/test/plan/edit/follows/' + row.id, row.follows, () => {
this.$success(this.$t('commons.follow_success'));
});
return
@ -642,7 +757,7 @@ export default {
.schedule-btn >>> .el-button {
margin-left: 10px;
color:#85888E;
color: #85888E;
border-color: #85888E;
border-width: thin;
}

View File

@ -118,8 +118,9 @@ export let CUSTOM_TABLE_HEADER = {
{id: 'plannedStartTime', key: '7', label: 'test_track.plan.planned_start_time'},
{id: 'plannedEndTime', key: '8', label: 'test_track.plan.planned_end_time'},
{id: 'actualStartTime', key: '9', label: 'test_track.plan.actual_start_time'},
{id: 'actualEndTime', key: 'a', label: 'test_track.plan.actual_end_time'},
{id: 'tags', key: 'b', label: 'commons.tag'},
{id: 'actualEndTime', key: '10', label: 'test_track.plan.actual_end_time'},
{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: 'passRate', key: 'd', label: 'commons.pass_rate'},
{id: 'createUser', key: 'e', label: 'commons.create_user'},

View File

@ -1823,6 +1823,7 @@ export default {
swagger_schedule: "swagger",
confirm: {
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_component_case_count: "Component Case Count",
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: {
case: "Load Case",
execution_status: "Execution status",

View File

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

View File

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