From 8659c67c02357b4a4f588193c9c7d0b00d2775f2 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 22 Dec 2020 18:36:05 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?firefox=E6=97=A0=E6=B3=95=E5=B1=95=E7=A4=BA=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/business/index.html | 2 +- frontend/src/login/login.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/business/index.html b/frontend/src/business/index.html index b677562844..2ac2681e5d 100644 --- a/frontend/src/business/index.html +++ b/frontend/src/business/index.html @@ -4,7 +4,7 @@ - + MeterSphere diff --git a/frontend/src/login/login.html b/frontend/src/login/login.html index 909af4db2c..852ca19a90 100644 --- a/frontend/src/login/login.html +++ b/frontend/src/login/login.html @@ -4,7 +4,7 @@ - + MeterSphere From 5180210910a6e7b588312c3ef814ac5c8ee99863 Mon Sep 17 00:00:00 2001 From: "song.tianyang" Date: Tue, 22 Dec 2020 20:02:07 +0800 Subject: [PATCH 2/3] =?UTF-8?q?style:=20=E6=9B=B4=E6=94=B9=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E7=BB=9F=E8=AE=A1=E8=AF=A6=E6=83=85=E5=AD=97=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更改首页统计卡牌右下角的统计详情字体 --- .../api/homepage/components/ApiInfoCard.vue | 7 ++++--- .../api/homepage/components/SceneInfoCard.vue | 19 ++++++++++++------- .../components/ScheduleTaskInfoCard.vue | 11 +++++++---- .../homepage/components/TestCaseInfoCard.vue | 7 ++++--- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue b/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue index 010a21a5fa..1c9eabb172 100644 --- a/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue @@ -83,14 +83,14 @@ {{apiCountData.runningCount}} - + {{$t('api_test.home_page.detail_card.not_started')}} {{"\xa0\xa0"}} {{apiCountData.notStartedCount}} - + {{$t('api_test.home_page.detail_card.finished')}} {{"\xa0\xa0"}} @@ -157,10 +157,11 @@ export default { box-shadow: 0 0px 0px 0 rgba(0,0,0,.1); } .default-property{ - + font-size: 12px } .main-property{ color: #F39021; + font-size: 12px } .el-card /deep/ .el-card__header { diff --git a/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue b/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue index 7da19aaa67..1683a9121c 100644 --- a/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue @@ -11,7 +11,9 @@ {{sceneCountData.allApiDataCountNumber}} - {{$t('api_test.home_page.unit_of_measurement')}} + + {{$t('api_test.home_page.unit_of_measurement')}} + @@ -65,12 +67,14 @@ {{sceneCountData.unexecuteCount}} - - {{$t('api_test.home_page.detail_card.execution_failed')}} - {{"\xa0\xa0"}} - {{sceneCountData.executionFailedCount}} + + + {{$t('api_test.home_page.detail_card.execution_failed')}} + {{"\xa0\xa0"}} + {{sceneCountData.executionFailedCount}} + - + {{$t('api_test.home_page.detail_card.execution_pass')}} {{"\xa0\xa0"}} @@ -141,10 +145,11 @@ export default { box-shadow: 0 0px 0px 0 rgba(0,0,0,.1); } .defaultProperty{ - + font-size: 12px } .main-property{ color: #F39021; + font-size: 12px } .el-card /deep/ .el-card__header { diff --git a/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue b/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue index 8d57c6ddcf..6f9d952ab6 100644 --- a/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue @@ -11,7 +11,9 @@ {{scheduleTaskCountData.allApiDataCountNumber}} - {{$t('api_test.home_page.unit_of_measurement')}} + + {{$t('api_test.home_page.unit_of_measurement')}} + @@ -66,9 +68,9 @@ {{scheduleTaskCountData.failedCount}} - + - + {{$t('api_test.home_page.detail_card.success')}} {{"\xa0\xa0"}} @@ -146,10 +148,11 @@ export default { box-shadow: 0 0px 0px 0 rgba(0,0,0,.1); } .default-property{ - + font-size: 12px } .main-property{ color: #F39021; + font-size: 12px; } .el-card /deep/ .el-card__header { diff --git a/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue b/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue index 8495305479..2b77654801 100644 --- a/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue @@ -99,9 +99,9 @@ {{testCaseCountData.uncoverageCount}} - + - + {{$t('api_test.home_page.detail_card.coverage')}} {{"\xa0\xa0"}} @@ -176,10 +176,11 @@ export default { box-shadow: 0 0px 0px 0 rgba(0,0,0,.1); } .default-property{ - + font-size: 12px } .main-property{ color: #F39021; + font-size: 12px } .el-card /deep/ .el-card__header { From 4552b58b0cc5b3275490eaa008cf218dc93188cb Mon Sep 17 00:00:00 2001 From: "song.tianyang" Date: Tue, 22 Dec 2020 19:03:52 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96):=20=E5=A2=9E=E5=8A=A0=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为场景增加定时任务功能,功能位置放于场景表格每一行都"操作"处 --- .../controller/ApiAutomationController.java | 18 ++ .../dto/automation/RunScenarioRequest.java | 2 + .../api/service/ApiAutomationService.java | 57 +++- .../commons/constants/ScheduleGroup.java | 2 +- .../job/sechedule/ApiScenarioTestJob.java | 74 ++++++ .../metersphere/job/sechedule/ApiTestJob.java | 2 +- .../job/sechedule/ScheduleManager.java | 5 + .../metersphere/service/ScheduleService.java | 2 + .../scenario/ScenarioExtendBtns.vue | 8 +- .../automation/schedule/ScheduleMaintain.vue | 244 +++++++++++++++++ .../schedule/ScheduleNotification.vue | 251 ++++++++++++++++++ 11 files changed, 648 insertions(+), 17 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/job/sechedule/ApiScenarioTestJob.java create mode 100644 frontend/src/business/components/api/automation/schedule/ScheduleMaintain.vue create mode 100644 frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java index 844d6138b6..d7b0559772 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java @@ -2,15 +2,21 @@ package io.metersphere.api.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import io.metersphere.api.dto.APITestResult; import io.metersphere.api.dto.automation.*; import io.metersphere.api.dto.definition.RunDefinitionRequest; import io.metersphere.api.service.ApiAutomationService; import io.metersphere.base.domain.ApiScenario; import io.metersphere.base.domain.ApiScenarioWithBLOBs; +import io.metersphere.base.domain.ApiTest; +import io.metersphere.base.domain.Schedule; import io.metersphere.commons.constants.RoleConstants; +import io.metersphere.commons.constants.ScheduleGroup; +import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.service.ScheduleService; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.web.bind.annotation.*; @@ -27,6 +33,7 @@ public class ApiAutomationController { @Resource ApiAutomationService apiAutomationService; + @PostMapping("/list/{goPage}/{pageSize}") public Pager> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiScenarioRequest request) { Page page = PageHelper.startPage(goPage, pageSize, true); @@ -96,5 +103,16 @@ public class ApiAutomationController { return apiAutomationService.addScenarioToPlan(request); } + + @PostMapping(value = "/schedule/update") + public void updateSchedule(@RequestBody Schedule request) { + apiAutomationService.updateSchedule(request); + } + + @PostMapping(value = "/schedule/create") + public void createSchedule(@RequestBody Schedule request) { + apiAutomationService.createSchedule(request); + } + } diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/RunScenarioRequest.java b/backend/src/main/java/io/metersphere/api/dto/automation/RunScenarioRequest.java index 088b05d1b9..78f5a847c0 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/RunScenarioRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/RunScenarioRequest.java @@ -21,5 +21,7 @@ public class RunScenarioRequest { private String executeType; + private String reportUserID; + private List scenarioIds; } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 792ba9d87b..c2be111297 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; import io.metersphere.api.dto.automation.*; import io.metersphere.api.dto.datacount.ApiDataCountResult; import io.metersphere.api.dto.definition.RunDefinitionRequest; @@ -12,21 +13,19 @@ import io.metersphere.api.dto.definition.request.*; import io.metersphere.api.dto.scenario.KeyValue; import io.metersphere.api.dto.scenario.environment.EnvironmentConfig; import io.metersphere.api.jmeter.JMeterService; -import io.metersphere.base.domain.ApiScenario; -import io.metersphere.base.domain.ApiScenarioExample; -import io.metersphere.base.domain.ApiScenarioWithBLOBs; -import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs; +import io.metersphere.base.domain.*; import io.metersphere.base.mapper.ApiScenarioMapper; import io.metersphere.base.mapper.ext.ExtApiScenarioMapper; import io.metersphere.base.mapper.ext.ExtTestPlanMapper; -import io.metersphere.commons.constants.APITestStatus; -import io.metersphere.commons.constants.ApiRunMode; -import io.metersphere.commons.constants.ReportTriggerMode; +import io.metersphere.commons.constants.*; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.DateUtils; import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.i18n.Translator; +import io.metersphere.job.sechedule.ApiScenarioTestJob; +import io.metersphere.job.sechedule.ApiTestJob; +import io.metersphere.service.ScheduleService; import io.metersphere.track.dto.TestPlanDTO; import io.metersphere.track.request.testcase.QueryTestPlanRequest; import org.apache.commons.collections.CollectionUtils; @@ -54,11 +53,15 @@ public class ApiAutomationService { private ApiDefinitionService apiDefinitionService; @Resource private ExtApiScenarioMapper extApiScenarioMapper; +// @Resource +// private ApiTagMapper apiTagMapper; @Resource private JMeterService jMeterService; @Resource private ApiTestEnvironmentService environmentService; @Resource + private ScheduleService scheduleService; + @Resource private ApiScenarioReportService apiReportService; @Resource private ExtTestPlanMapper extTestPlanMapper; @@ -184,14 +187,19 @@ public class ApiAutomationService { return new ArrayList<>(); } - private void createAPIScenarioReportResult(String id, String triggerMode, String execType, String projectId) { + private void createAPIScenarioReportResult(String id, String triggerMode, String execType, String projectId,String userID) { APIScenarioReportResult report = new APIScenarioReportResult(); report.setId(id); report.setName("测试执行结果"); report.setCreateTime(System.currentTimeMillis()); report.setUpdateTime(System.currentTimeMillis()); report.setStatus(APITestStatus.Running.name()); - report.setUserId(SessionUtils.getUserId()); + if(StringUtils.isNotEmpty(userID)){ + report.setUserId(userID); + }else { + report.setUserId(SessionUtils.getUserId()); + } + report.setTriggerMode(triggerMode); report.setExecuteType(execType); report.setProjectId(projectId); @@ -210,10 +218,12 @@ public class ApiAutomationService { MsTestPlan testPlan = new MsTestPlan(); testPlan.setHashTree(new LinkedList<>()); HashTree jmeterTestPlanHashTree = new ListedHashTree(); + String projectID = request.getProjectId(); for (ApiScenarioWithBLOBs item : apiScenarios) { MsThreadGroup group = new MsThreadGroup(); group.setLabel(item.getName()); group.setName(item.getName()); + projectID = item.getProjectId(); try { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -245,7 +255,7 @@ public class ApiAutomationService { jMeterService.runDefinition(request.getId(), jmeterTestPlanHashTree, request.getReportId(), ApiRunMode.SCENARIO.name()); createAPIScenarioReportResult(request.getId(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(), - request.getExecuteType(), request.getProjectId()); + request.getExecuteType(), projectID,request.getReportUserID()); return request.getId(); } @@ -271,7 +281,8 @@ public class ApiAutomationService { request.getTestElement().getJmx(hashTree); // 调用执行方法 jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.SCENARIO.name()); - createAPIScenarioReportResult(request.getId(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId()); + createAPIScenarioReportResult(request.getId(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(), + SessionUtils.getUserId()); return request.getId(); } @@ -347,4 +358,28 @@ public class ApiAutomationService { public List countRunResultByProjectID(String projectId) { return extApiScenarioMapper.countRunResultByProjectID(projectId); } + + public void createSchedule(Schedule request) { + + Schedule schedule = scheduleService.buildApiTestSchedule(request); + schedule.setJob(ApiScenarioTestJob.class.getName()); + schedule.setGroup(ScheduleGroup.API_SCENARIO_TEST.name()); + schedule.setType(ScheduleType.CRON.name()); + + scheduleService.addSchedule(schedule); + this.addOrUpdateApiScenarioCronJob(request); + + } + + public void updateSchedule(Schedule request) { + scheduleService.editSchedule(request); + this.addOrUpdateApiScenarioCronJob(request); + } + + private void addOrUpdateApiScenarioCronJob(Schedule request) { + scheduleService.addOrUpdateCronJob( + request, ApiScenarioTestJob.getJobKey(request.getResourceId()), ApiScenarioTestJob.getTriggerKey(request.getResourceId()), ApiScenarioTestJob.class); + } + + } diff --git a/backend/src/main/java/io/metersphere/commons/constants/ScheduleGroup.java b/backend/src/main/java/io/metersphere/commons/constants/ScheduleGroup.java index 09bbf02589..4a21f86850 100644 --- a/backend/src/main/java/io/metersphere/commons/constants/ScheduleGroup.java +++ b/backend/src/main/java/io/metersphere/commons/constants/ScheduleGroup.java @@ -1,5 +1,5 @@ package io.metersphere.commons.constants; public enum ScheduleGroup { - API_TEST, PERFORMANCE_TEST + API_TEST, PERFORMANCE_TEST, API_SCENARIO_TEST } diff --git a/backend/src/main/java/io/metersphere/job/sechedule/ApiScenarioTestJob.java b/backend/src/main/java/io/metersphere/job/sechedule/ApiScenarioTestJob.java new file mode 100644 index 0000000000..42e8b70f81 --- /dev/null +++ b/backend/src/main/java/io/metersphere/job/sechedule/ApiScenarioTestJob.java @@ -0,0 +1,74 @@ +package io.metersphere.job.sechedule; + +import io.metersphere.api.dto.SaveAPITestRequest; +import io.metersphere.api.dto.automation.ExecuteType; +import io.metersphere.api.dto.automation.RunScenarioRequest; +import io.metersphere.api.service.APITestService; +import io.metersphere.api.service.ApiAutomationService; +import io.metersphere.commons.constants.ReportTriggerMode; +import io.metersphere.commons.constants.ScheduleGroup; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; +import org.quartz.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * 情景测试Job + * @author song.tianyang + * @Date 2020/12/22 2:59 下午 + * @Description + */ +public class ApiScenarioTestJob extends MsScheduleJob { + private String projectID; + private List scenarioIds; + + private ApiAutomationService apiAutomationService; + public ApiScenarioTestJob() { + apiAutomationService = (ApiAutomationService) CommonBeanFactory.getBean(ApiAutomationService.class); + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + JobKey jobKey = context.getTrigger().getJobKey(); + JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); + String resourceId = jobDataMap.getString("resourceId"); + this.userId = jobDataMap.getString("userId"); + this.expression = jobDataMap.getString("expression"); + this.projectID = jobDataMap.getString("projectId"); + + if(resourceId!=null){ + scenarioIds = new ArrayList<>(); + scenarioIds.add(resourceId); + } + + LogUtil.info(jobKey.getGroup() + " Running: " + resourceId); + LogUtil.info("CronExpression: " + expression); + businessExecute(context); + } + + @Override + void businessExecute(JobExecutionContext context) { + RunScenarioRequest request = new RunScenarioRequest(); + String id = UUID.randomUUID().toString(); + request.setId(id); + request.setReportId(id); + request.setProjectId(projectID); + request.setTriggerMode(ReportTriggerMode.MANUAL.name()); + request.setExecuteType(ExecuteType.Completed.name()); + request.setScenarioIds(this.scenarioIds); + request.setReportUserID(this.userId); + + apiAutomationService.run(request); + } + + public static JobKey getJobKey(String testId) { + return new JobKey(testId, ScheduleGroup.API_SCENARIO_TEST.name()); + } + + public static TriggerKey getTriggerKey(String testId) { + return new TriggerKey(testId, ScheduleGroup.API_SCENARIO_TEST.name()); + } +} diff --git a/backend/src/main/java/io/metersphere/job/sechedule/ApiTestJob.java b/backend/src/main/java/io/metersphere/job/sechedule/ApiTestJob.java index 825f0de177..05e1f14a80 100644 --- a/backend/src/main/java/io/metersphere/job/sechedule/ApiTestJob.java +++ b/backend/src/main/java/io/metersphere/job/sechedule/ApiTestJob.java @@ -9,7 +9,6 @@ import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.TriggerKey; - public class ApiTestJob extends MsScheduleJob { private APITestService apiTestService; @@ -34,3 +33,4 @@ public class ApiTestJob extends MsScheduleJob { return new TriggerKey(testId, ScheduleGroup.API_TEST.name()); } } + diff --git a/backend/src/main/java/io/metersphere/job/sechedule/ScheduleManager.java b/backend/src/main/java/io/metersphere/job/sechedule/ScheduleManager.java index c32dedca52..d37956b7a9 100644 --- a/backend/src/main/java/io/metersphere/job/sechedule/ScheduleManager.java +++ b/backend/src/main/java/io/metersphere/job/sechedule/ScheduleManager.java @@ -1,10 +1,14 @@ package io.metersphere.job.sechedule; +import io.metersphere.commons.constants.ScheduleGroup; import io.metersphere.commons.utils.LogUtil; +import org.python.antlr.ast.Str; import org.quartz.*; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.List; +import java.util.Set; @Component public class ScheduleManager { @@ -291,4 +295,5 @@ public class ScheduleManager { jobDataMap.put("userId", userId); return jobDataMap; } + } diff --git a/backend/src/main/java/io/metersphere/service/ScheduleService.java b/backend/src/main/java/io/metersphere/service/ScheduleService.java index 36f5ea1530..2568462767 100644 --- a/backend/src/main/java/io/metersphere/service/ScheduleService.java +++ b/backend/src/main/java/io/metersphere/service/ScheduleService.java @@ -20,6 +20,7 @@ import io.metersphere.dto.ScheduleDao; import io.metersphere.job.sechedule.ApiTestJob; import io.metersphere.job.sechedule.ScheduleManager; import org.apache.commons.lang3.StringUtils; +import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.SchedulerException; import org.quartz.TriggerKey; @@ -64,6 +65,7 @@ public class ScheduleService { } public Schedule getScheduleByResource(String resourceId, String group) { + ScheduleExample example = new ScheduleExample(); example.createCriteria().andResourceIdEqualTo(resourceId).andGroupEqualTo(group); List schedules = scheduleMapper.selectByExample(example); diff --git a/frontend/src/business/components/api/automation/scenario/ScenarioExtendBtns.vue b/frontend/src/business/components/api/automation/scenario/ScenarioExtendBtns.vue index daa5481201..c055c7c657 100644 --- a/frontend/src/business/components/api/automation/scenario/ScenarioExtendBtns.vue +++ b/frontend/src/business/components/api/automation/scenario/ScenarioExtendBtns.vue @@ -5,20 +5,20 @@ {{ $t('api_test.automation.view_ref') }} - + {{ $t('api_test.automation.schedule') }} - + + + diff --git a/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue b/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue new file mode 100644 index 0000000000..09da062150 --- /dev/null +++ b/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue @@ -0,0 +1,251 @@ + + + + + +