From e68cfc62e9861c75997ba11ead33a4a59e066cab Mon Sep 17 00:00:00 2001 From: song-tianyang Date: Mon, 10 Apr 2023 16:12:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95):=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E5=88=97=E8=A1=A8=E5=A2=9E=E5=8A=A0=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=BB=BB=E5=8A=A1=E5=B1=95=E7=A4=BA=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --story=1011684 --user=宋天阳 接口场景列表、UI场景列表 展示定时任务列 https://www.tapd.cn/55049933/s/1360916 --- .../io/metersphere/api/dto/ScheduleDTO.java | 14 + .../base/mapper/ext/ExtScheduleMapper.java | 13 +- .../base/mapper/ext/ExtScheduleMapper.xml | 67 ++-- .../scenario/ApiScenarioController.java | 8 +- .../service/ext/ExtApiScheduleService.java | 13 +- .../service/scenario/ApiScenarioService.java | 27 +- api-test/frontend/src/api/scenario.js | 5 + .../automation/scenario/ApiScenarioList.vue | 238 ++++++++----- .../schedule/ScheduleInfoInTable.vue | 78 +++++ .../src/utils/default-table-header.js | 314 ++++++++++-------- .../plan/dto/TestPlanReportDataStruct.java | 5 - .../plan/service/TestPlanService.java | 4 +- 12 files changed, 516 insertions(+), 270 deletions(-) create mode 100644 api-test/backend/src/main/java/io/metersphere/api/dto/ScheduleDTO.java create mode 100644 api-test/frontend/src/business/automation/schedule/ScheduleInfoInTable.vue diff --git a/api-test/backend/src/main/java/io/metersphere/api/dto/ScheduleDTO.java b/api-test/backend/src/main/java/io/metersphere/api/dto/ScheduleDTO.java new file mode 100644 index 0000000000..49404fe6fb --- /dev/null +++ b/api-test/backend/src/main/java/io/metersphere/api/dto/ScheduleDTO.java @@ -0,0 +1,14 @@ +package io.metersphere.api.dto; + +import io.metersphere.base.domain.Schedule; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ScheduleDTO extends Schedule { + /** + * 定时任务下一次执行时间 + */ + private Long scheduleExecuteTime; +} diff --git a/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.java b/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.java index 6498fb7101..a5dff4515c 100644 --- a/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.java +++ b/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.java @@ -1,9 +1,8 @@ package io.metersphere.base.mapper.ext; +import io.metersphere.api.dto.ScheduleDTO; import io.metersphere.api.dto.definition.ApiSwaggerUrlDTO; import io.metersphere.dto.ScheduleDao; -import io.metersphere.dto.TaskInfoResult; -import io.metersphere.request.BaseQueryRequest; import io.metersphere.request.QueryScheduleRequest; import org.apache.ibatis.annotations.Param; @@ -12,16 +11,12 @@ import java.util.List; public interface ExtScheduleMapper { List list(@Param("request") QueryScheduleRequest request); - long countTaskByProjectId(String workspaceId); - - long countTaskByProjectIdAndCreateTimeRange(@Param("projectId")String projectId, @Param("startTime") long startTime, @Param("endTime") long endTime); - - List findRunningTaskInfoByProjectID(@Param("projectId") String workspaceID, @Param("request") BaseQueryRequest request); - void insert(@Param("apiSwaggerUrlDTO") ApiSwaggerUrlDTO apiSwaggerUrlDTO); - ApiSwaggerUrlDTO select(String id); + ApiSwaggerUrlDTO select(String id); int updateNameByResourceID(@Param("resourceId") String resourceId, @Param("name") String name); + List selectByResourceIds(@Param("ids") List resourceIDs); + } diff --git a/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.xml b/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.xml index 87fa24a38d..21c75621b8 100644 --- a/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.xml +++ b/api-test/backend/src/main/java/io/metersphere/base/mapper/ext/ExtScheduleMapper.xml @@ -4,7 +4,8 @@ insert into swagger_url_project (id, `project_id`, `swagger_url`, `schedule_id`) - values (#{apiSwaggerUrlDTO.id,jdbcType=VARCHAR}, #{apiSwaggerUrlDTO.projectId,jdbcType=VARCHAR}, #{apiSwaggerUrlDTO.swaggerUrl,jdbcType=VARCHAR}, + values (#{apiSwaggerUrlDTO.id,jdbcType=VARCHAR}, #{apiSwaggerUrlDTO.projectId,jdbcType=VARCHAR}, + #{apiSwaggerUrlDTO.swaggerUrl,jdbcType=VARCHAR}, #{apiSwaggerUrlDTO.scheduleId,jdbcType=VARCHAR}) SELECT COUNT(id) AS countNumber FROM `schedule` - WHERE resource_id IN ( - SELECT id FROM api_scenario WHERE project_id = #{0,jdbcType=VARCHAR} AND status != 'Trash' AND latest = 1 - ) + WHERE resource_id IN (SELECT id + FROM api_scenario + WHERE project_id = #{0,jdbcType=VARCHAR} + AND status != 'Trash' + AND latest = 1) + - update schedule set name = #{name} where resource_id = #{resourceId} + update schedule + set name = #{name} + where resource_id = #{resourceId} diff --git a/api-test/backend/src/main/java/io/metersphere/controller/scenario/ApiScenarioController.java b/api-test/backend/src/main/java/io/metersphere/controller/scenario/ApiScenarioController.java index bde9bb972f..302451a6d5 100644 --- a/api-test/backend/src/main/java/io/metersphere/controller/scenario/ApiScenarioController.java +++ b/api-test/backend/src/main/java/io/metersphere/controller/scenario/ApiScenarioController.java @@ -24,6 +24,7 @@ import io.metersphere.request.ResetOrderRequest; import io.metersphere.service.ext.ExtApiTaskService; import io.metersphere.service.scenario.ApiScenarioService; import io.metersphere.task.dto.TaskRequestDTO; +import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.Logical; @@ -34,7 +35,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import jakarta.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -57,6 +57,12 @@ public class ApiScenarioController { return PageUtils.setPageInfo(page, apiAutomationService.list(request)); } + @PostMapping("/scenario/schedule") + @RequiresPermissions("PROJECT_API_SCENARIO:READ") + public Map scenarioScheduleInfo(@RequestBody List scenarioIds) { + return apiAutomationService.selectScheduleInfo(scenarioIds); + } + @PostMapping("/list") @RequiresPermissions("PROJECT_API_SCENARIO:READ") public List listAll(@RequestBody ApiScenarioRequest request) { diff --git a/api-test/backend/src/main/java/io/metersphere/service/ext/ExtApiScheduleService.java b/api-test/backend/src/main/java/io/metersphere/service/ext/ExtApiScheduleService.java index 813a8397e6..9986eb8830 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/ext/ExtApiScheduleService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/ext/ExtApiScheduleService.java @@ -1,5 +1,6 @@ package io.metersphere.service.ext; +import io.metersphere.api.dto.ScheduleDTO; import io.metersphere.api.dto.ScheduleRequest; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.ScheduleMapper; @@ -22,6 +23,8 @@ import io.metersphere.sechedule.ApiScenarioTestJob; import io.metersphere.sechedule.ScheduleManager; import io.metersphere.sechedule.SwaggerUrlImportJob; import io.metersphere.service.ServiceUtils; +import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.quartz.JobKey; import org.quartz.SchedulerException; @@ -29,10 +32,10 @@ import org.quartz.TriggerKey; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; -import jakarta.annotation.Resource; import java.util.stream.Collectors; @Service @@ -233,4 +236,12 @@ public class ExtApiScheduleService { } } + + public List selectByResourceIds(List scenarioIds) { + if (CollectionUtils.isNotEmpty(scenarioIds)) { + return extScheduleMapper.selectByResourceIds(scenarioIds); + } else { + return new ArrayList<>(); + } + } } diff --git a/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioService.java b/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioService.java index 00b4960fe8..a36c559f5f 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioService.java @@ -9,7 +9,6 @@ import io.metersphere.api.dto.definition.ApiTestCaseInfo; import io.metersphere.api.dto.definition.RunDefinitionRequest; import io.metersphere.api.dto.definition.request.*; import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; -import io.metersphere.api.dto.definition.request.unknown.MsJmeterElement; import io.metersphere.api.dto.export.ScenarioToPerformanceInfoDTO; import io.metersphere.api.dto.scenario.ApiScenarioParamDTO; import io.metersphere.api.exec.scenario.ApiScenarioEnvService; @@ -71,6 +70,10 @@ import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONObject; import org.mybatis.spring.SqlSessionUtils; +import org.quartz.CronExpression; +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.TriggerBuilder; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -2325,4 +2328,26 @@ public class ApiScenarioService { } + public Map selectScheduleInfo(List scenarioIds) { + if (CollectionUtils.isNotEmpty(scenarioIds)) { + List scheduleInfoList = scheduleService.selectByResourceIds(scenarioIds); + for (ScheduleDTO schedule : scheduleInfoList) { + schedule.setScheduleExecuteTime(this.getNextTriggerTime(schedule.getValue())); + } + return scheduleInfoList.stream().collect(Collectors.toMap(Schedule::getResourceId, item -> item)); + } else { + return new HashMap<>(); + } + } + + //获取下次执行时间(getFireTimeAfter,也可以下下次...) + private long getNextTriggerTime(String cron) { + if (!CronExpression.isValidExpression(cron)) { + return 0; + } + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("Calculate Date").withSchedule(CronScheduleBuilder.cronSchedule(cron)).build(); + Date time0 = trigger.getStartTime(); + Date time1 = trigger.getFireTimeAfter(time0); + return time1 == null ? 0 : time1.getTime(); + } } diff --git a/api-test/frontend/src/api/scenario.js b/api-test/frontend/src/api/scenario.js index 4d202809e7..2d3f186190 100644 --- a/api-test/frontend/src/api/scenario.js +++ b/api-test/frontend/src/api/scenario.js @@ -14,6 +14,11 @@ export function getScenarioList(currentPage, pageSize, condition) { return post(url, condition); } +export function getScheduleDetail(scenarioIds) { + let url = '/api/automation/scenario/schedule'; + return post(url, scenarioIds); +} + export function getScenarioByTrash(condition) { return post('/api/automation/list/all/trash', condition); } diff --git a/api-test/frontend/src/business/automation/scenario/ApiScenarioList.vue b/api-test/frontend/src/business/automation/scenario/ApiScenarioList.vue index 15a46f6f11..1f227acca8 100644 --- a/api-test/frontend/src/business/automation/scenario/ApiScenarioList.vue +++ b/api-test/frontend/src/business/automation/scenario/ApiScenarioList.vue @@ -40,7 +40,7 @@ :fields-width="fieldsWidth" v-if="this.trashEnable" :label="$t('commons.delete_user')" - min-width="120"/> + min-width="120" /> + sortable /> @@ -105,7 +105,7 @@ prop="status" min-width="120px"> @@ -117,20 +117,20 @@ min-width="120px" prop="tags"> + +
+
+ +
+
+ + sortable /> + sortable="custom" /> {{ k }} -
+
{{ v }}: - - {{ k }} - - -
+ + {{ k }} + +
- +
- + + + + min-width="80px" /> @@ -244,7 +260,7 @@ :fields-width="fieldsWidth" :label="$t('api_test.automation.passing_rate')" prop="passRate" - min-width="120px"/> + min-width="120px" /> - +
@@ -301,7 +317,7 @@ :scenarioId="scenarioId" :infoDb="infoDb" :report-id="reportId" - :currentProjectId="projectId"/> + :currentProjectId="projectId" /> + :currentProjectId="projectId" /> + :row="selectRows" />
@@ -342,14 +358,14 @@ @batchEdit="batchEdit" :typeArr="typeArr" :value-arr="valueArr" - :dialog-title="$t('test_track.case.batch_edit_case')"/> - + :dialog-title="$t('test_track.case.batch_edit_case')" /> + + ref="apiBatchRun" /> - - + ref="runTest" /> + + - + + ref="apiDeleteConfirm" /> - + @@ -395,6 +411,7 @@ import { getScenarioList, getScenarioVersions, getScenarioWithBLOBsById, + getScheduleDetail, listWithIds, removeScenarioToGcByBatch, runBatch, @@ -404,13 +421,13 @@ import { scenarioRun, updateScenarioEnv, } from '@/api/scenario'; -import {getMaintainer, getProject} from '@/api/project'; -import {getProjectVersions, versionEnableByProjectId} from '@/api/xpack'; -import {getCurrentProjectID, getCurrentUserId} from 'metersphere-frontend/src/utils/token'; -import {downloadFile, getUUID, objToStrMap, strMapToObj} from 'metersphere-frontend/src/utils'; -import {hasLicense, hasPermission} from 'metersphere-frontend/src/utils/permission'; -import {API_SCENARIO_CONFIGS} from 'metersphere-frontend/src/components/search/search-components'; -import {API_SCENARIO_LIST} from 'metersphere-frontend/src/utils/constants'; +import { getMaintainer, getProject } from '@/api/project'; +import { getProjectVersions, versionEnableByProjectId } from '@/api/xpack'; +import { getCurrentProjectID, getCurrentUserId } from 'metersphere-frontend/src/utils/token'; +import { downloadFile, getUUID, objToStrMap, strMapToObj } from 'metersphere-frontend/src/utils'; +import { hasLicense, hasPermission } from 'metersphere-frontend/src/utils/permission'; +import { API_SCENARIO_CONFIGS } from 'metersphere-frontend/src/components/search/search-components'; +import { API_SCENARIO_LIST } from 'metersphere-frontend/src/utils/constants'; import { buildBatchParam, getCustomTableHeader, @@ -418,27 +435,29 @@ import { getLastTableSortField, getSelectDataCounts, } from 'metersphere-frontend/src/utils/tableUtils'; -import {API_SCENARIO_FILTERS} from 'metersphere-frontend/src/utils/table-constants'; +import { API_SCENARIO_FILTERS } from 'metersphere-frontend/src/utils/table-constants'; import MsTable from 'metersphere-frontend/src/components/table/MsTable'; import MsTableColumn from 'metersphere-frontend/src/components/table/MsTableColumn'; import HeaderLabelOperate from 'metersphere-frontend/src/components/head/HeaderLabelOperate'; -import {getGraphByCondition} from '@/api/graph'; -import {API_SCENARIO_CONFIGS_TRASH, TYPE_TO_C} from '@/business/automation/scenario/Setting'; +import { getGraphByCondition } from '@/api/graph'; +import { API_SCENARIO_CONFIGS_TRASH, TYPE_TO_C } from '@/business/automation/scenario/Setting'; import MsTableSearchBar from 'metersphere-frontend/src/components/MsTableSearchBar'; import MsTableAdvSearchBar from 'metersphere-frontend/src/components/search/MsTableAdvSearchBar'; import ListItemDeleteConfirm from 'metersphere-frontend/src/components/ListItemDeleteConfirm'; import ScenarioDeleteConfirm from '@/business/automation/scenario/ScenarioDeleteConfirm'; -import {$error} from 'metersphere-frontend/src/plugins/message'; +import { $error } from 'metersphere-frontend/src/plugins/message'; import MsSearch from 'metersphere-frontend/src/components/search/MsSearch'; -import {buildNodePath} from 'metersphere-frontend/src/model/NodeTree'; -import {getEnvironmentByProjectId} from 'metersphere-frontend/src/api/environment'; -import {REPORT_STATUS} from '@/business/commons/js/commons'; -import {usePerformanceStore} from '@/store'; -import {request} from 'metersphere-frontend/src/plugins/request'; -import {parseEnvironment} from '@/business/environment/model/EnvironmentModel'; +import { buildNodePath } from 'metersphere-frontend/src/model/NodeTree'; +import { getEnvironmentByProjectId } from 'metersphere-frontend/src/api/environment'; +import { REPORT_STATUS } from '@/business/commons/js/commons'; +import { usePerformanceStore } from '@/store'; +import { request } from 'metersphere-frontend/src/plugins/request'; +import { parseEnvironment } from '@/business/environment/model/EnvironmentModel'; import MsApiRunMode from '@/business/automation/scenario/common/ApiRunMode'; import ApiDeleteConfirm from '@/business/definition/components/list/ApiDeleteConfirm'; import MsShowReference from '@/business/definition/components/reference/ShowReference'; +import scheduleInfoInTable from '@/business/automation/schedule/ScheduleInfoInTable.vue'; +import { scheduleUpdate } from '@/api/schedule'; const performanceStore = usePerformanceStore(); export default { @@ -455,6 +474,7 @@ export default { ApiDeleteConfirm, MsShowReference, ScenarioDeleteConfirm, + scheduleInfoInTable, MsApiReportStatus: () => import('../report/ApiReportStatus'), HeaderCustom: () => import('metersphere-frontend/src/components/head/HeaderCustom'), BatchMove: () => import('@/business/commons/BatchMove'), @@ -657,8 +677,8 @@ export default { }, ], typeArr: [ - {id: 'level', name: this.$t('test_track.case.priority')}, - {id: 'status', name: this.$t('test_track.plan.plan_status')}, + { id: 'level', name: this.$t('test_track.case.priority') }, + { id: 'status', name: this.$t('test_track.plan.plan_status') }, { id: 'principal', name: this.$t('api_test.definition.request.responsible'), @@ -669,14 +689,14 @@ export default { id: 'projectEnv', name: this.$t('api_test.definition.request.run_env'), }, - {id: 'tags', name: this.$t('commons.tag')}, + { id: 'tags', name: this.$t('commons.tag') }, ], valueArr: { level: [ - {name: 'P0', id: 'P0'}, - {name: 'P1', id: 'P1'}, - {name: 'P2', id: 'P2'}, - {name: 'P3', id: 'P3'}, + { name: 'P0', id: 'P0' }, + { name: 'P1', id: 'P1' }, + { name: 'P2', id: 'P2' }, + { name: 'P3', id: 'P3' }, ], status: [ { @@ -717,10 +737,10 @@ export default { if (!this.projectName || this.projectName === '') { this.getProjectName(); } - this.condition.filters = {status: ['Prepare', 'Underway', 'Completed']}; + this.condition.filters = { status: ['Prepare', 'Underway', 'Completed'] }; this.initEnvironment(); if (this.trashEnable) { - this.condition.filters = {status: ['Trash']}; + this.condition.filters = { status: ['Trash'] }; this.condition.moduleIds = []; this.operators = this.trashOperators; this.buttons = this.trashButtons; @@ -735,7 +755,7 @@ export default { } if (this.trashEnable) { - this.condition.orders = [{name: 'delete_time', type: 'desc'}]; + this.condition.orders = [{ name: 'delete_time', type: 'desc' }]; } else { this.condition.orders = getLastTableSortField(this.tableHeaderKey); } @@ -768,7 +788,7 @@ export default { }, trashEnable() { if (this.trashEnable) { - this.condition.filters = {status: ['Trash']}; + this.condition.filters = { status: ['Trash'] }; this.condition.moduleIds = []; this.operators = this.trashOperators; this.buttons = this.trashButtons; @@ -797,7 +817,7 @@ export default { moduleOptionsNew() { let moduleOptions = []; this.moduleOptions.forEach((node) => { - buildNodePath(node, {path: ''}, moduleOptions); + buildNodePath(node, { path: '' }, moduleOptions); }); return moduleOptions; }, @@ -829,12 +849,12 @@ export default { parseEnvironment(environment); }); this.environmentsFilters = response.data.map((u) => { - return {text: u.name, value: u.id}; + return { text: u.name, value: u.id }; }); }); } }, - search(projectId){ + search(projectId) { this.$EventBus.$emit('scenarioConditionBus', this.condition); this.nodeChange(projectId); }, @@ -915,7 +935,9 @@ export default { let data = response.data; this.total = data.itemCount; this.tableData = data.listObject; + let ids = []; this.tableData.forEach((item) => { + ids.push(item.id); if (item.tags && item.tags.length > 0) { item.tags = JSON.parse(item.tags); } @@ -924,9 +946,59 @@ export default { if (this.$refs.scenarioTable) { this.$refs.scenarioTable.clearSelection(); } + this.selectSchedule(ids); }); } }, + selectSchedule(ids) { + if (ids.length > 0) { + getScheduleDetail(ids).then((response) => { + if (response.data) { + let scheduleData = response.data; + this.tableData.forEach((scenario) => { + let scheduleInfo = this.getScheduleObject(scheduleData[scenario.id], scenario.id); + this.$set(scenario, 'scheduleObj', scheduleInfo); + }); + } + }); + } + }, + scheduleStatusChange(schedule) { + let scheduleRequest = { + taskID: schedule.id, + enable: schedule.enable, + }; + this.result = scheduleUpdate(scheduleRequest) + .then(() => { + schedule.scheduleStatus = schedule.enable ? 'OPEN' : 'SHUT'; + this.$success(this.$t('commons.save_success')); + }) + .catch(() => { + this.$success(this.$t('commons.save_failed')); + schedule.enable = !schedule.enable; + }); + }, + getScheduleObject(schedule, resourceId) { + if (schedule) { + return { + scheduleStatus: schedule.enable ? 'OPEN' : 'SHUT', + scheduleCorn: schedule.value, + scheduleExecuteTime: schedule.scheduleExecuteTime, + enable: schedule.enable, + id: schedule.id, + resourceId: schedule.resourceId, + }; + } else { + return { + scheduleStatus: '', + scheduleCorn: '', + scheduleExecuteTime: '', + enable: false, + id: '', + resourceId: resourceId, + }; + } + }, handleCommand(cmd) { let table = this.$refs.scenarioTable; switch (cmd) { @@ -1019,7 +1091,7 @@ export default { getMaintainer().then((response) => { option.push(...response.data); this.userFilters = response.data.map((u) => { - return {text: u.name, value: u.id}; + return { text: u.name, value: u.id }; }); }); }, @@ -1030,11 +1102,11 @@ export default { this.versionFilters = response.data .filter((u) => u.id === currentVersion) .map((u) => { - return {text: u.name, value: u.id}; + return { text: u.name, value: u.id }; }); } else { this.versionFilters = response.data.map((u) => { - return {text: u.name, value: u.id}; + return { text: u.name, value: u.id }; }); } }); @@ -1125,7 +1197,7 @@ export default { handleRunBatch(config) { this.infoDb = false; - let run = {config: config}; + let run = { config: config }; run.id = getUUID(); //按照列表排序 let ids = this.orderBySelectRows(); @@ -1421,7 +1493,7 @@ export default { this.search(); }); } else { - let param = {ids: [api.id]}; + let param = { ids: [api.id] }; removeScenarioToGcByBatch(param).then(() => { this.$success(this.$t('commons.delete_success')); this.$refs.apiDeleteConfirmVersion.close(); @@ -1460,7 +1532,7 @@ export default { method: 'post', data: param, responseType: 'blob', - headers: {'Content-Type': 'application/json; charset=utf-8'}, + headers: { 'Content-Type': 'application/json; charset=utf-8' }, }; request(config).then( (response) => { diff --git a/api-test/frontend/src/business/automation/schedule/ScheduleInfoInTable.vue b/api-test/frontend/src/business/automation/schedule/ScheduleInfoInTable.vue new file mode 100644 index 0000000000..9572a2b7ef --- /dev/null +++ b/api-test/frontend/src/business/automation/schedule/ScheduleInfoInTable.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/framework/sdk-parent/frontend/src/utils/default-table-header.js b/framework/sdk-parent/frontend/src/utils/default-table-header.js index 0e22612676..6facd4cc7d 100644 --- a/framework/sdk-parent/frontend/src/utils/default-table-header.js +++ b/framework/sdk-parent/frontend/src/utils/default-table-header.js @@ -1,190 +1,220 @@ -export const TEST_CASE_LIST = 'test_case_list'; +export const TEST_CASE_LIST = "test_case_list"; export const CUSTOM_FIELD_LIST = new Set([ - 'name', - 'scene', - 'type', - 'remark', - 'system', - 'createTime', - 'updateTime' + "name", + "scene", + "type", + "remark", + "system", + "createTime", + "updateTime", ]); - export const ISSUE_TEMPLATE_LIST = new Set([ - 'name', - 'platform', - 'description', - 'createTime', - 'updateTime' + "name", + "platform", + "description", + "createTime", + "updateTime", ]); export let CUSTOM_TABLE_HEADER = { - CUSTOM_FIELD: [ - {id: 'name', key: '1', label: 'commons.name'}, - {id: 'scene', key: '2', label: 'custom_field.scene'}, - {id: 'type', key: '3', label: 'custom_field.attribute_type'}, - {id: 'remark', key: '4', label: 'commons.remark'}, - {id: 'system', key: '5', label: 'custom_field.system_field'}, - {id: 'createTime', key: '6', label: 'commons.create_time'}, - {id: 'updateTime', key: '7', label: 'commons.update_time'}, + { id: "name", key: "1", label: "commons.name" }, + { id: "scene", key: "2", label: "custom_field.scene" }, + { id: "type", key: "3", label: "custom_field.attribute_type" }, + { id: "remark", key: "4", label: "commons.remark" }, + { id: "system", key: "5", label: "custom_field.system_field" }, + { id: "createTime", key: "6", label: "commons.create_time" }, + { id: "updateTime", key: "7", label: "commons.update_time" }, ], //接口定义 API_DEFINITION: [ - {id: 'num', key: '1', label: "ID"}, - {id: 'name', key: '2', label: 'api_test.definition.api_name'}, - {id: 'method', key: '3', label: 'api_test.definition.api_type'}, - {id: 'userName', key: '4', label: 'api_test.definition.api_principal'}, - {id: 'path', key: '5', label: 'api_test.definition.api_path'}, - {id: 'tags', key: '6', label: 'commons.tag'}, - {id: 'versionId', key: 'f', label: 'project.version.name', xpack: true}, - {id: 'updateTime', key: '7', label: 'api_test.definition.api_last_time'}, - {id: 'caseTotal', key: '8', label: 'api_test.definition.api_case_number'}, - {id: 'caseStatus', key: '9', label: 'api_test.definition.api_case_status'}, - {id: 'casePassingRate', key: 'a', label: 'api_test.definition.api_case_passing_rate'}, - {id: 'status', key: 'b', label: 'api_test.definition.api_status'}, - {id: 'createTime', key: 'c', label: 'commons.create_time'}, - {id: 'description', key: 'e', label: 'commons.description'}, + { id: "num", key: "1", label: "ID" }, + { id: "name", key: "2", label: "api_test.definition.api_name" }, + { id: "method", key: "3", label: "api_test.definition.api_type" }, + { id: "userName", key: "4", label: "api_test.definition.api_principal" }, + { id: "path", key: "5", label: "api_test.definition.api_path" }, + { id: "tags", key: "6", label: "commons.tag" }, + { id: "versionId", key: "f", label: "project.version.name", xpack: true }, + { id: "updateTime", key: "7", label: "api_test.definition.api_last_time" }, + { id: "caseTotal", key: "8", label: "api_test.definition.api_case_number" }, + { + id: "caseStatus", + key: "9", + label: "api_test.definition.api_case_status", + }, + { + id: "casePassingRate", + key: "a", + label: "api_test.definition.api_case_passing_rate", + }, + { id: "status", key: "b", label: "api_test.definition.api_status" }, + { id: "createTime", key: "c", label: "commons.create_time" }, + { id: "description", key: "e", label: "commons.description" }, ], //接口用例 API_CASE: [ - {id: 'num', key: '1', label: "ID"}, - {id: 'name', key: '2', label: 'test_track.case.name'}, - {id: 'priority', key: '3', label: 'test_track.case.priority'}, - {id: 'path', key: '4', label: 'api_test.definition.api_definition_path'}, - {id: 'execResult', key: '5', label: 'test_track.plan_view.execute_result'}, - {id: 'caseStatus', key: '6', label: 'commons.status'}, - {id: 'tags', key: '7', label: 'commons.tag'}, - {id: 'versionId', key: 'f', label: 'project.version.name', xpack: true}, - {id: 'createUser', key: '8', label: 'api_test.creator'}, - {id: 'updateTime', key: '9', label: 'api_test.definition.api_last_time'}, - {id: 'createTime', key: 'a', label: 'commons.create_time'}, - {id: 'passRate', key: 'b', label: 'commons.pass_rate'}, - {id: 'environment', key: 'e', label: 'commons.environment'}, + { id: "num", key: "1", label: "ID" }, + { id: "name", key: "2", label: "test_track.case.name" }, + { id: "priority", key: "3", label: "test_track.case.priority" }, + { id: "path", key: "4", label: "api_test.definition.api_definition_path" }, + { + id: "execResult", + key: "5", + label: "test_track.plan_view.execute_result", + }, + { id: "caseStatus", key: "6", label: "commons.status" }, + { id: "tags", key: "7", label: "commons.tag" }, + { id: "versionId", key: "f", label: "project.version.name", xpack: true }, + { id: "createUser", key: "8", label: "api_test.creator" }, + { id: "updateTime", key: "9", label: "api_test.definition.api_last_time" }, + { id: "createTime", key: "a", label: "commons.create_time" }, + { id: "passRate", key: "b", label: "commons.pass_rate" }, + { id: "environment", key: "e", label: "commons.environment" }, ], //场景测试 API_SCENARIO: [ - {id: 'num', key: '1', label: "ID"}, - {id: 'name', key: '2', label: 'api_report.scenario_name'}, - {id: 'level', key: '3', label: 'api_test.automation.case_level'}, - {id: 'status', key: '4', label: 'test_track.plan.plan_status'}, - {id: 'tags', key: '5', label: 'commons.tag'}, - {id: 'versionId', key: 'f', label: 'project.version.name', xpack: true}, - {id: 'creatorName', key: 'd', label: 'api_test.automation.creator'}, - {id: 'principalName', key: '6', label: 'api_test.definition.api_principal'}, - {id: 'environmentMap', key: 'e', label: 'commons.environment'}, - {id: 'updateTime', key: '7', label: 'api_test.definition.api_last_time'}, - {id: 'stepTotal', key: '8', label: 'api_test.automation.step'}, - {id: 'lastResult', key: 'a', label: 'api_test.automation.last_result'}, - {id: 'passRate', key: 'b', label: 'api_test.automation.passing_rate'}, - {id: 'createTime', key: 'c', label: 'commons.create_time'}, + { id: "num", key: "1", label: "ID" }, + { id: "name", key: "2", label: "api_report.scenario_name" }, + { id: "level", key: "3", label: "api_test.automation.case_level" }, + { id: "status", key: "4", label: "test_track.plan.plan_status" }, + { id: "tags", key: "5", label: "commons.tag" }, + { id: "versionId", key: "f", label: "project.version.name", xpack: true }, + { id: "creatorName", key: "d", label: "api_test.automation.creator" }, + { + id: "principalName", + key: "6", + label: "api_test.definition.api_principal", + }, + { id: "environmentMap", key: "e", label: "commons.environment" }, + { id: "updateTime", key: "7", label: "api_test.definition.api_last_time" }, + { id: "stepTotal", key: "8", label: "api_test.automation.step" }, + { id: "lastResult", key: "a", label: "api_test.automation.last_result" }, + { id: "passRate", key: "b", label: "api_test.automation.passing_rate" }, + { id: "createTime", key: "c", label: "commons.create_time" }, + { id: "schedule", key: "g", label: "commons.trigger_mode.schedule" }, ], //场景测试 UI_SCENARIO: [ - {id: 'num', key: '1', label: "ID"}, - {id: 'name', key: '2', label: 'api_report.scenario_name'}, - {id: 'level', key: '3', label: 'api_test.automation.case_level'}, - {id: 'status', key: '4', label: 'test_track.plan.plan_status'}, - {id: 'tags', key: '5', label: 'commons.tag'}, + { id: "num", key: "1", label: "ID" }, + { id: "name", key: "2", label: "api_report.scenario_name" }, + { id: "level", key: "3", label: "api_test.automation.case_level" }, + { id: "status", key: "4", label: "test_track.plan.plan_status" }, + { id: "tags", key: "5", label: "commons.tag" }, // {id: 'versionId', key: 'f', label: 'project.version.name', xpack: true}, - {id: 'creatorName', key: 'd', label: 'api_test.automation.creator'}, - {id: 'principalName', key: '6', label: 'api_test.definition.api_principal'}, - {id: 'environmentMap', key: 'e', label: 'commons.environment'}, - {id: 'updateTime', key: '7', label: 'api_test.definition.api_last_time'}, - {id: 'stepTotal', key: '8', label: 'api_test.automation.step'}, - {id: 'lastResult', key: 'a', label: 'api_test.automation.last_result'}, - {id: 'passRate', key: 'b', label: 'api_test.automation.passing_rate'}, - {id: 'createTime', key: 'c', label: 'commons.create_time'}, + { id: "creatorName", key: "d", label: "api_test.automation.creator" }, + { + id: "principalName", + key: "6", + label: "api_test.definition.api_principal", + }, + { id: "environmentMap", key: "e", label: "commons.environment" }, + { id: "updateTime", key: "7", label: "api_test.definition.api_last_time" }, + { id: "stepTotal", key: "8", label: "api_test.automation.step" }, + { id: "lastResult", key: "a", label: "api_test.automation.last_result" }, + { id: "passRate", key: "b", label: "api_test.automation.passing_rate" }, + { id: "createTime", key: "c", label: "commons.create_time" }, ], //自定义指令 UI_CUSTOM_COMMAND: [ - {id: 'num', key: '1', label: "ID"}, - {id: 'name', key: '2', label: 'ui.command_name_label'}, - {id: 'tags', key: '5', label: 'commons.tag'}, - {id: 'creatorName', key: 'd', label: 'api_test.automation.creator'}, - {id: 'principalName', key: '6', label: 'api_test.definition.api_principal'}, - {id: 'environmentMap', key: '8', label: 'commons.environment'}, - {id: 'updateTime', key: '7', label: 'api_test.definition.api_last_time'}, - {id: 'lastResult', key: 'a', label: 'ui.debug_result_label'}, - {id: 'createTime', key: 'c', label: 'commons.create_time'}, + { id: "num", key: "1", label: "ID" }, + { id: "name", key: "2", label: "ui.command_name_label" }, + { id: "tags", key: "5", label: "commons.tag" }, + { id: "creatorName", key: "d", label: "api_test.automation.creator" }, + { + id: "principalName", + key: "6", + label: "api_test.definition.api_principal", + }, + { id: "environmentMap", key: "8", label: "commons.environment" }, + { id: "updateTime", key: "7", label: "api_test.definition.api_last_time" }, + { id: "lastResult", key: "a", label: "ui.debug_result_label" }, + { id: "createTime", key: "c", label: "commons.create_time" }, ], // 测试报告 TRACK_REPORT_TABLE: [ - {id: 'name', key: '1', label: 'test_track.report.list.name'}, - {id: 'testPlanName', key: '2', label: 'test_track.report.list.test_plan'}, - {id: 'creator', key: '3', label: 'test_track.report.list.creator'}, - {id: 'createTime', key: '4', label: 'test_track.report.list.create_time'}, - {id: 'triggerMode', key: '5', label: 'test_track.report.list.trigger_mode'}, - {id: 'status', key: '6', label: 'commons.status'}, - {id: 'runTime', key: '7', label: 'test_track.report.list.run_time'}, - {id: 'passRate', key: '8', label: 'test_track.report.list.pass_rate'}, + { id: "name", key: "1", label: "test_track.report.list.name" }, + { id: "testPlanName", key: "2", label: "test_track.report.list.test_plan" }, + { id: "creator", key: "3", label: "test_track.report.list.creator" }, + { id: "createTime", key: "4", label: "test_track.report.list.create_time" }, + { + id: "triggerMode", + key: "5", + label: "test_track.report.list.trigger_mode", + }, + { id: "status", key: "6", label: "commons.status" }, + { id: "runTime", key: "7", label: "test_track.report.list.run_time" }, + { id: "passRate", key: "8", label: "test_track.report.list.pass_rate" }, ], // 场景变量 VARIABLE_LIST_TABLE: [ - {id: 'num', key: '1', label: "ID"}, - {id: 'name', key: '2', label: 'api_test.variable_name'}, - {id: 'type', key: '3', label: 'test_track.case.type'}, - {id: 'value', key: '4', label: 'api_test.value'}, - {id: 'description', key: '5', label: 'commons.description'}, + { id: "num", key: "1", label: "ID" }, + { id: "name", key: "2", label: "api_test.variable_name" }, + { id: "type", key: "3", label: "test_track.case.type" }, + { id: "value", key: "4", label: "api_test.value" }, + { id: "description", key: "5", label: "commons.description" }, ], //缺陷列表 ELEMENT_LIST: [ - {id: 'num', key: '1', label: 'ID'}, - {id: 'name', key: '2', label: '元素名称'}, - {id: 'locationType', key: '3', label: '定位类型'}, - {id: 'location', key: '4', label: '元素定位'}, - {id: 'createUser', key: '5', label: '创建人'}, - {id: 'createTime', key: '6', label: 'commons.create_time'}, - {id: 'updateUser', key: '7', label: '更新人'}, - {id: 'updateTime', key: '8', label: 'commons.update_time'}, + { id: "num", key: "1", label: "ID" }, + { id: "name", key: "2", label: "元素名称" }, + { id: "locationType", key: "3", label: "定位类型" }, + { id: "location", key: "4", label: "元素定位" }, + { id: "createUser", key: "5", label: "创建人" }, + { id: "createTime", key: "6", label: "commons.create_time" }, + { id: "updateUser", key: "7", label: "更新人" }, + { id: "updateTime", key: "8", label: "commons.update_time" }, ], //空间配额 QUOTA_WS_LIST: [ - {id: 'workspaceName', key: 'a', label: 'commons.workspace'}, - {id: 'api', key: 'b', label: 'quota.api'}, - {id: 'performance', key: 'c', label: 'quota.performance'}, - {id: 'maxThreads', key: 'd', label: 'quota.max_threads'}, - {id: 'duration', key: 'e', label: 'quota.duration'}, - {id: 'resourcePool', key: 'f', label: 'quota.resource_pool'}, - {id: 'useDefault', key: 'j', label: 'quota.use_default'}, - {id: 'vumTotal', key: 'h', label: 'quota.vum_total'}, - {id: 'vumUsed', key: 'i', label: 'quota.vum_used'}, - {id: 'member', key: 'g', label: 'quota.member'}, - {id: 'project', key: 'k', label: 'quota.project'}, + { id: "workspaceName", key: "a", label: "commons.workspace" }, + { id: "api", key: "b", label: "quota.api" }, + { id: "performance", key: "c", label: "quota.performance" }, + { id: "maxThreads", key: "d", label: "quota.max_threads" }, + { id: "duration", key: "e", label: "quota.duration" }, + { id: "resourcePool", key: "f", label: "quota.resource_pool" }, + { id: "useDefault", key: "j", label: "quota.use_default" }, + { id: "vumTotal", key: "h", label: "quota.vum_total" }, + { id: "vumUsed", key: "i", label: "quota.vum_used" }, + { id: "member", key: "g", label: "quota.member" }, + { id: "project", key: "k", label: "quota.project" }, ], //项目配额 QUOTA_PJ_LIST: [ - {id: 'projectName', key: 'a', label: 'commons.project'}, - {id: 'api', key: 'b', label: 'quota.api'}, - {id: 'performance', key: 'c', label: 'quota.performance'}, - {id: 'maxThreads', key: 'd', label: 'quota.max_threads'}, - {id: 'duration', key: 'e', label: 'quota.duration'}, - {id: 'resourcePool', key: 'f', label: 'quota.resource_pool'}, - {id: 'useDefault', key: 'j', label: 'quota.use_default'}, - {id: 'vumTotal', key: 'h', label: 'quota.vum_total'}, - {id: 'vumUsed', key: 'i', label: 'quota.vum_used'}, - {id: 'member', key: 'g', label: 'quota.member'}, + { id: "projectName", key: "a", label: "commons.project" }, + { id: "api", key: "b", label: "quota.api" }, + { id: "performance", key: "c", label: "quota.performance" }, + { id: "maxThreads", key: "d", label: "quota.max_threads" }, + { id: "duration", key: "e", label: "quota.duration" }, + { id: "resourcePool", key: "f", label: "quota.resource_pool" }, + { id: "useDefault", key: "j", label: "quota.use_default" }, + { id: "vumTotal", key: "h", label: "quota.vum_total" }, + { id: "vumUsed", key: "i", label: "quota.vum_used" }, + { id: "member", key: "g", label: "quota.member" }, ], // 测试报告列表 PERFORMANCE_REPORT_TABLE: [ - {id: 'testName', key: 'a', label: 'report.test_name'}, - {id: 'name', key: 'b', label: 'commons.name'}, - {id: 'versionId', key: 'c', label: 'project.version.name'}, - {id: 'userName', key: 'd', label: 'report.user_name'}, - {id: 'maxUsers', key: 'e', label: 'report.max_users'}, - {id: 'avgResponseTime', key: 'f', label: 'report.response_time'}, - {id: 'tps', key: 'g', label: 'TPS'}, - {id: 'testStartTime', key: 'h', label: 'report.test_start_time'}, - {id: 'testEndTime', key: 'i', label: 'report.test_end_time'}, - {id: 'testDuration', key: 'j', label: 'report.test_execute_time'}, - {id: 'triggerMode', key: 'k', label: 'test_track.report.list.trigger_mode'}, - {id: 'status', key: 'l', label: 'commons.status'}, - ] - -} + { id: "testName", key: "a", label: "report.test_name" }, + { id: "name", key: "b", label: "commons.name" }, + { id: "versionId", key: "c", label: "project.version.name" }, + { id: "userName", key: "d", label: "report.user_name" }, + { id: "maxUsers", key: "e", label: "report.max_users" }, + { id: "avgResponseTime", key: "f", label: "report.response_time" }, + { id: "tps", key: "g", label: "TPS" }, + { id: "testStartTime", key: "h", label: "report.test_start_time" }, + { id: "testEndTime", key: "i", label: "report.test_end_time" }, + { id: "testDuration", key: "j", label: "report.test_execute_time" }, + { + id: "triggerMode", + key: "k", + label: "test_track.report.list.trigger_mode", + }, + { id: "status", key: "l", label: "commons.status" }, + ], +}; diff --git a/test-track/backend/src/main/java/io/metersphere/plan/dto/TestPlanReportDataStruct.java b/test-track/backend/src/main/java/io/metersphere/plan/dto/TestPlanReportDataStruct.java index 467a3ac67a..dd5cecc301 100644 --- a/test-track/backend/src/main/java/io/metersphere/plan/dto/TestPlanReportDataStruct.java +++ b/test-track/backend/src/main/java/io/metersphere/plan/dto/TestPlanReportDataStruct.java @@ -73,7 +73,6 @@ public class TestPlanReportDataStruct extends TestPlanReportContent { List runningCaseList = apiAllCases.stream().filter( dto -> StringUtils.equalsAnyIgnoreCase(dto.getExecResult(), - ApiReportStatus.PENDING.name(), ApiReportStatus.RERUNNING.name(), ApiReportStatus.RUNNING.name())).toList(); if (runningCaseList.size() > 0) { @@ -84,7 +83,6 @@ public class TestPlanReportDataStruct extends TestPlanReportContent { List runningCaseList = scenarioAllCases.stream().filter( dto -> StringUtils.equalsAnyIgnoreCase(dto.getLastResult(), - ApiReportStatus.PENDING.name(), ApiReportStatus.RERUNNING.name(), ApiReportStatus.RUNNING.name())).toList(); if (runningCaseList.size() > 0) { @@ -95,8 +93,6 @@ public class TestPlanReportDataStruct extends TestPlanReportContent { List runningCaseList = loadAllCases.stream().filter( dto -> StringUtils.equalsAnyIgnoreCase(dto.getStatus(), - PerformanceTestStatus.Starting.name(), - PerformanceTestStatus.Running.name(), PerformanceTestStatus.Reporting.name())).toList(); if (runningCaseList.size() > 0) { return true; @@ -106,7 +102,6 @@ public class TestPlanReportDataStruct extends TestPlanReportContent { List runningCaseList = uiAllCases.stream().filter( dto -> StringUtils.equalsAnyIgnoreCase(dto.getLastResult(), - ApiReportStatus.PENDING.name(), ApiReportStatus.RERUNNING.name(), ApiReportStatus.RUNNING.name())).toList(); if (runningCaseList.size() > 0) { diff --git a/test-track/backend/src/main/java/io/metersphere/plan/service/TestPlanService.java b/test-track/backend/src/main/java/io/metersphere/plan/service/TestPlanService.java index b052e3788d..8d26e0b957 100644 --- a/test-track/backend/src/main/java/io/metersphere/plan/service/TestPlanService.java +++ b/test-track/backend/src/main/java/io/metersphere/plan/service/TestPlanService.java @@ -2408,7 +2408,9 @@ public class TestPlanService { TestPlanReportDataStruct testPlanReportDataStruct = new TestPlanReportDataStruct(); try { testPlanReportDataStruct = this.generateReportStruct(testPlanWithBLOBs, testPlanReport, testPlanReportContent, false); - if (StringUtils.isBlank(testPlanReportContent.getApiBaseCount()) && !testPlanReportDataStruct.hasRunningCase()) { + if (StringUtils.isBlank(testPlanReportContent.getApiBaseCount()) + && !testPlanReportDataStruct.hasRunningCase() + && StringUtils.equalsAnyIgnoreCase(testPlanReport.getStatus(), TestPlanReportStatus.FAILED.name(), TestPlanReportStatus.COMPLETED.name(), TestPlanReportStatus.SUCCESS.name())) { //旧版本的测试计划报告,没有重新统计过测试计划报告时,且当不存在运行中的用例,会将结果保存下来 testPlanReportService.updateReportStructInfo(testPlanReportContent, testPlanReportDataStruct); }