From 91e792d6533ccbee62663d00153ad7622853effa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Bo=CC=88hm?= Date: Tue, 12 Dec 2023 10:17:27 +0100 Subject: [PATCH 01/17] Database scheme for schedule subscriber --- .../cit/ase/domain/ScheduleSubscriber.java | 54 +++++++++++++++++++ .../cit/ase/domain/SimulationSchedule.java | 13 +++++ .../ScheduleSubscriberRepository.java | 8 +++ .../simulation/SimulationScheduleService.java | 20 ++++++- .../resources/config/application-prod.yml | 2 +- ...00000000000012_add_schedule_subscriber.xml | 25 +++++++++ .../resources/config/liquibase/master.xml | 1 + .../mail/scheduleSubscriptionEmail.html | 19 +++++++ 8 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java create mode 100644 src/main/java/de/tum/cit/ase/repository/ScheduleSubscriberRepository.java create mode 100644 src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml create mode 100644 src/main/resources/templates/mail/scheduleSubscriptionEmail.html diff --git a/src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java b/src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java new file mode 100644 index 00000000..43b1252e --- /dev/null +++ b/src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java @@ -0,0 +1,54 @@ +package de.tum.cit.ase.domain; + +import jakarta.persistence.*; + +@Entity +@Table(name = "simulation_schedule") +public class ScheduleSubscriber { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "schedule_id", nullable = false) + private SimulationSchedule schedule; + + @Column(name = "email", nullable = false) + private String email; + + @Column(name = "key", nullable = false, length = 20) + private String key; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public SimulationSchedule getSchedule() { + return schedule; + } + + public void setSchedule(SimulationSchedule schedule) { + this.schedule = schedule; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/src/main/java/de/tum/cit/ase/domain/SimulationSchedule.java b/src/main/java/de/tum/cit/ase/domain/SimulationSchedule.java index 6b9db13a..4d15c622 100644 --- a/src/main/java/de/tum/cit/ase/domain/SimulationSchedule.java +++ b/src/main/java/de/tum/cit/ase/domain/SimulationSchedule.java @@ -4,6 +4,7 @@ import jakarta.persistence.*; import java.time.DayOfWeek; import java.time.ZonedDateTime; +import java.util.Set; @Entity @Table(name = "simulation_schedule") @@ -39,6 +40,10 @@ public class SimulationSchedule { @Column(name = "day_of_week") private DayOfWeek dayOfWeek; + @OneToMany(mappedBy = "schedule", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE) + @JsonIgnore + private Set subscribers; + public Long getId() { return id; } @@ -103,6 +108,14 @@ public void setDayOfWeek(DayOfWeek dayOfWeek) { this.dayOfWeek = dayOfWeek; } + public Set getSubscribers() { + return subscribers; + } + + public void setSubscribers(Set subscribers) { + this.subscribers = subscribers; + } + public enum Cycle { DAILY, WEEKLY, diff --git a/src/main/java/de/tum/cit/ase/repository/ScheduleSubscriberRepository.java b/src/main/java/de/tum/cit/ase/repository/ScheduleSubscriberRepository.java new file mode 100644 index 00000000..4f68f09c --- /dev/null +++ b/src/main/java/de/tum/cit/ase/repository/ScheduleSubscriberRepository.java @@ -0,0 +1,8 @@ +package de.tum.cit.ase.repository; + +import de.tum.cit.ase.domain.ScheduleSubscriber; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ScheduleSubscriberRepository extends JpaRepository {} diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java index 4dc07b6d..859a9604 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java @@ -2,7 +2,9 @@ import static java.time.ZonedDateTime.now; +import de.tum.cit.ase.domain.ScheduleSubscriber; import de.tum.cit.ase.domain.SimulationSchedule; +import de.tum.cit.ase.repository.ScheduleSubscriberRepository; import de.tum.cit.ase.repository.SimulationScheduleRepository; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -12,6 +14,7 @@ import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import tech.jhipster.security.RandomUtil; @Service public class SimulationScheduleService { @@ -20,13 +23,16 @@ public class SimulationScheduleService { private final SimulationScheduleRepository simulationScheduleRepository; private final SimulationDataService simulationDataService; + private final ScheduleSubscriberRepository scheduleSubscriberRepository; public SimulationScheduleService( SimulationScheduleRepository simulationScheduleRepository, - SimulationDataService simulationDataService + SimulationDataService simulationDataService, + ScheduleSubscriberRepository scheduleSubscriberRepository ) { this.simulationScheduleRepository = simulationScheduleRepository; this.simulationDataService = simulationDataService; + this.scheduleSubscriberRepository = scheduleSubscriberRepository; } public SimulationSchedule createSimulationSchedule(long simulationId, SimulationSchedule simulationSchedule) { @@ -68,6 +74,18 @@ public List getSimulationSchedules(long simulationId) { return simulationScheduleRepository.findAllBySimulationId(simulationId); } + public ScheduleSubscriber subscribeToSchedule(long scheduleId, String email) { + var schedule = simulationScheduleRepository.findById(scheduleId).orElseThrow(); + if (schedule.getSubscribers().stream().anyMatch(subscriber -> subscriber.getEmail().equals(email))) { + throw new IllegalArgumentException("Already subscribed to this schedule"); + } + var subscriber = new ScheduleSubscriber(); + subscriber.setSchedule(schedule); + subscriber.setEmail(email); + subscriber.setKey(RandomUtil.generateActivationKey()); + return scheduleSubscriberRepository.save(subscriber); + } + @Scheduled(fixedRate = 1000 * 60, initialDelay = 0) void executeScheduledSimulations() { log.info("Executing scheduled simulation runs"); diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml index ced8d4a3..9be34662 100644 --- a/src/main/resources/config/application-prod.yml +++ b/src/main/resources/config/application-prod.yml @@ -109,7 +109,7 @@ jhipster: token-validity-in-seconds: 86400 token-validity-in-seconds-for-remember-me: 2592000 mail: # specific JHipster mail property, for standard properties see MailProperties - base-url: http://my-server-url-to-change # Modify according to your server's URL + base-url: https://artemis-performance-test0.artemis.cit.tum.de/ # Modify according to your server's URL logging: use-json-format: false # By default, logs are not in Json format logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration diff --git a/src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml b/src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml new file mode 100644 index 00000000..b2a18e8e --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 34f3bf7b..40c4412b 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -20,6 +20,7 @@ + diff --git a/src/main/resources/templates/mail/scheduleSubscriptionEmail.html b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html new file mode 100644 index 00000000..362211ca --- /dev/null +++ b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html @@ -0,0 +1,19 @@ + + + + Subscribed to Artemis-Benchmarking schedule + + + +

Hello,

+

+

+ Click here to unsubscribe. +

+

+ Regards, +
+ Artemis-Benchmarking +

+ + From 74d1ee20428a2aefb336ca37c30516e2c3621247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Bo=CC=88hm?= Date: Tue, 12 Dec 2023 11:43:21 +0100 Subject: [PATCH 02/17] Send confirmation email --- .../tum/cit/ase/domain/ScheduleSubscriber.java | 6 ++++-- .../de/tum/cit/ase/service/MailService.java | 18 ++++++++++++++++++ .../simulation/SimulationScheduleService.java | 13 ++++++++++--- .../cit/ase/web/rest/SimulationResource.java | 6 ++++++ .../00000000000012_add_schedule_subscriber.xml | 2 +- .../mail/scheduleSubscriptionEmail.html | 4 ++-- 6 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java b/src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java index 43b1252e..20710272 100644 --- a/src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java +++ b/src/main/java/de/tum/cit/ase/domain/ScheduleSubscriber.java @@ -1,9 +1,10 @@ package de.tum.cit.ase.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; @Entity -@Table(name = "simulation_schedule") +@Table(name = "schedule_subscriber") public class ScheduleSubscriber { @Id @@ -12,12 +13,13 @@ public class ScheduleSubscriber { @ManyToOne @JoinColumn(name = "schedule_id", nullable = false) + @JsonIgnore private SimulationSchedule schedule; @Column(name = "email", nullable = false) private String email; - @Column(name = "key", nullable = false, length = 20) + @Column(name = "subscription_key", nullable = false, length = 20) private String key; public Long getId() { diff --git a/src/main/java/de/tum/cit/ase/service/MailService.java b/src/main/java/de/tum/cit/ase/service/MailService.java index a33b673f..e601a545 100644 --- a/src/main/java/de/tum/cit/ase/service/MailService.java +++ b/src/main/java/de/tum/cit/ase/service/MailService.java @@ -1,5 +1,6 @@ package de.tum.cit.ase.service; +import de.tum.cit.ase.domain.ScheduleSubscriber; import de.tum.cit.ase.domain.User; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; @@ -33,6 +34,10 @@ public class MailService { private static final String BASE_URL = "baseUrl"; + private static final String SUBSCRIPTION_KEY = "subscriptionKey"; + + private static final String SIMULATION_NAME = "simulationName"; + private final JHipsterProperties jHipsterProperties; private final JavaMailSender javaMailSender; @@ -115,4 +120,17 @@ public void sendPasswordResetMail(User user) { log.debug("Sending password reset email to '{}'", user.getEmail()); self.sendEmailFromTemplate(user, "mail/passwordResetEmail", "email.reset.title"); } + + @Async + public void sendSubscribedMail(ScheduleSubscriber subscriber) { + log.debug("Sending subscription confirmation email to '{}'", subscriber.getEmail()); + Locale locale = Locale.forLanguageTag("en"); + Context context = new Context(locale); + context.setVariable(SIMULATION_NAME, subscriber.getSchedule().getSimulation().getName()); + context.setVariable(SUBSCRIPTION_KEY, subscriber.getKey()); + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String content = templateEngine.process("mail/scheduleSubscriptionEmail", context); + String subject = "Artemis-Benchmarking - Subscription to simulation schedule"; + self.sendEmail(subscriber.getEmail(), subject, content, false, true); + } } diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java index 859a9604..fbea2eb3 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java @@ -6,6 +6,7 @@ import de.tum.cit.ase.domain.SimulationSchedule; import de.tum.cit.ase.repository.ScheduleSubscriberRepository; import de.tum.cit.ase.repository.SimulationScheduleRepository; +import de.tum.cit.ase.service.MailService; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.TemporalAdjusters; @@ -24,15 +25,18 @@ public class SimulationScheduleService { private final SimulationScheduleRepository simulationScheduleRepository; private final SimulationDataService simulationDataService; private final ScheduleSubscriberRepository scheduleSubscriberRepository; + private final MailService mailService; public SimulationScheduleService( SimulationScheduleRepository simulationScheduleRepository, SimulationDataService simulationDataService, - ScheduleSubscriberRepository scheduleSubscriberRepository + ScheduleSubscriberRepository scheduleSubscriberRepository, + MailService mailService ) { this.simulationScheduleRepository = simulationScheduleRepository; this.simulationDataService = simulationDataService; this.scheduleSubscriberRepository = scheduleSubscriberRepository; + this.mailService = mailService; } public SimulationSchedule createSimulationSchedule(long simulationId, SimulationSchedule simulationSchedule) { @@ -75,15 +79,18 @@ public List getSimulationSchedules(long simulationId) { } public ScheduleSubscriber subscribeToSchedule(long scheduleId, String email) { + log.debug("Subscribing {} to schedule {}", email, scheduleId); var schedule = simulationScheduleRepository.findById(scheduleId).orElseThrow(); if (schedule.getSubscribers().stream().anyMatch(subscriber -> subscriber.getEmail().equals(email))) { throw new IllegalArgumentException("Already subscribed to this schedule"); } var subscriber = new ScheduleSubscriber(); subscriber.setSchedule(schedule); - subscriber.setEmail(email); + subscriber.setEmail(email.toLowerCase()); subscriber.setKey(RandomUtil.generateActivationKey()); - return scheduleSubscriberRepository.save(subscriber); + var savedSubscriber = scheduleSubscriberRepository.save(subscriber); + mailService.sendSubscribedMail(savedSubscriber); + return savedSubscriber; } @Scheduled(fixedRate = 1000 * 60, initialDelay = 0) diff --git a/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java b/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java index fa79c9e3..afa73ddf 100644 --- a/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java +++ b/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java @@ -174,4 +174,10 @@ public ResponseEntity deleteSchedule(@PathVariable long scheduleId) { public ResponseEntity> getSchedules(@PathVariable long simulationId) { return new ResponseEntity<>(simulationScheduleService.getSimulationSchedules(simulationId), HttpStatus.OK); } + + @PostMapping("/schedules/{scheduleId}/subscribe") + public ResponseEntity subscribeToSchedule(@PathVariable long scheduleId, @RequestBody String email) { + simulationScheduleService.subscribeToSchedule(scheduleId, email); + return new ResponseEntity<>(HttpStatus.OK); + } } diff --git a/src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml b/src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml index b2a18e8e..9b29190b 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000012_add_schedule_subscriber.xml @@ -17,7 +17,7 @@ - + diff --git a/src/main/resources/templates/mail/scheduleSubscriptionEmail.html b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html index 362211ca..415154bd 100644 --- a/src/main/resources/templates/mail/scheduleSubscriptionEmail.html +++ b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html @@ -6,9 +6,9 @@

Hello,

-

+

- Click here to unsubscribe. + Click here to unsubscribe.

Regards, From 683791748a43a8a3382cf4e2c9f90a9ac9053743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Bo=CC=88hm?= Date: Tue, 12 Dec 2023 15:04:46 +0100 Subject: [PATCH 03/17] Result summary email for scheduled runs --- .../domain/SimulationResultForSummary.java | 100 ++++++++++++++++++ .../de/tum/cit/ase/domain/SimulationRun.java | 11 ++ .../de/tum/cit/ase/service/MailService.java | 25 ++++- .../simulation/SimulationDataService.java | 3 +- .../simulation/SimulationResultService.java | 1 + .../SimulationRunExecutionService.java | 12 ++- .../simulation/SimulationScheduleService.java | 2 +- .../cit/ase/web/rest/SimulationResource.java | 2 +- .../mail/scheduleSubscriptionEmail.html | 15 ++- .../mail/subscriptionResultEmail.html | 41 +++++++ .../simulations-overview.component.ts | 12 +++ src/main/webapp/manifest.webapp | 22 ---- .../ase/service/SimulationDataServiceIT.java | 4 +- .../service/SimulationScheduleServiceIT.java | 2 +- 14 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 src/main/java/de/tum/cit/ase/domain/SimulationResultForSummary.java create mode 100644 src/main/resources/templates/mail/subscriptionResultEmail.html diff --git a/src/main/java/de/tum/cit/ase/domain/SimulationResultForSummary.java b/src/main/java/de/tum/cit/ase/domain/SimulationResultForSummary.java new file mode 100644 index 00000000..a00a8a5a --- /dev/null +++ b/src/main/java/de/tum/cit/ase/domain/SimulationResultForSummary.java @@ -0,0 +1,100 @@ +package de.tum.cit.ase.domain; + +import de.tum.cit.ase.util.TimeLogUtil; + +public class SimulationResultForSummary { + + public String avgTotal; + public String avgAuthentication; + public String avgGetStudentExam; + public String avgStartStudentExam; + public String avgSubmitExercise; + public String avgSubmitStudentExam; + public String avgClone; + public String avgPush; + + public static SimulationResultForSummary from(SimulationRun run) { + SimulationResultForSummary result = new SimulationResultForSummary(); + result.avgTotal = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.TOTAL)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + result.avgAuthentication = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.AUTHENTICATION)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + result.avgGetStudentExam = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.GET_STUDENT_EXAM)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + result.avgStartStudentExam = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.START_STUDENT_EXAM)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + result.avgSubmitExercise = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.SUBMIT_EXERCISE)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + result.avgSubmitStudentExam = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.SUBMIT_STUDENT_EXAM)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + result.avgClone = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.CLONE)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + result.avgPush = + TimeLogUtil.formatDuration( + run + .getStats() + .stream() + .filter((SimulationStats stats) -> stats.getRequestType().equals(RequestType.PUSH)) + .findFirst() + .orElseThrow() + .getAvgResponseTime() + ); + return result; + } +} diff --git a/src/main/java/de/tum/cit/ase/domain/SimulationRun.java b/src/main/java/de/tum/cit/ase/domain/SimulationRun.java index 2867bed5..e5803839 100644 --- a/src/main/java/de/tum/cit/ase/domain/SimulationRun.java +++ b/src/main/java/de/tum/cit/ase/domain/SimulationRun.java @@ -35,6 +35,9 @@ public class SimulationRun { @Transient private ArtemisAccountDTO adminAccount; + @Transient + private SimulationSchedule schedule; + public Long getId() { return id; } @@ -91,6 +94,14 @@ public void setAdminAccount(ArtemisAccountDTO adminAccount) { this.adminAccount = adminAccount; } + public SimulationSchedule getSchedule() { + return schedule; + } + + public void setSchedule(SimulationSchedule schedule) { + this.schedule = schedule; + } + public enum Status { QUEUED, RUNNING, diff --git a/src/main/java/de/tum/cit/ase/service/MailService.java b/src/main/java/de/tum/cit/ase/service/MailService.java index e601a545..1b414677 100644 --- a/src/main/java/de/tum/cit/ase/service/MailService.java +++ b/src/main/java/de/tum/cit/ase/service/MailService.java @@ -1,6 +1,9 @@ package de.tum.cit.ase.service; import de.tum.cit.ase.domain.ScheduleSubscriber; +import de.tum.cit.ase.domain.SimulationResultForSummary; +import de.tum.cit.ase.domain.SimulationRun; +import de.tum.cit.ase.domain.SimulationSchedule; import de.tum.cit.ase.domain.User; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; @@ -126,11 +129,29 @@ public void sendSubscribedMail(ScheduleSubscriber subscriber) { log.debug("Sending subscription confirmation email to '{}'", subscriber.getEmail()); Locale locale = Locale.forLanguageTag("en"); Context context = new Context(locale); - context.setVariable(SIMULATION_NAME, subscriber.getSchedule().getSimulation().getName()); - context.setVariable(SUBSCRIPTION_KEY, subscriber.getKey()); + context.setVariable("subscriber", subscriber); context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); String content = templateEngine.process("mail/scheduleSubscriptionEmail", context); String subject = "Artemis-Benchmarking - Subscription to simulation schedule"; self.sendEmail(subscriber.getEmail(), subject, content, false, true); } + + @Async + public void sendRunResultMail(SimulationRun run, SimulationSchedule schedule) { + SimulationResultForSummary result = SimulationResultForSummary.from(run); + Locale locale = Locale.forLanguageTag("en"); + Context context = new Context(locale); + context.setVariable("run", run); + context.setVariable("result", result); + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String subject = "Artemis-Benchmarking - Result for scheduled run"; + schedule + .getSubscribers() + .forEach(subscriber -> { + context.setVariable("subscriber", subscriber); + String content = templateEngine.process("mail/subscriptionResultEmail", context); + log.debug("Sending result email to '{}'", subscriber.getEmail()); + self.sendEmail(subscriber.getEmail(), subject, content, false, true); + }); + } } diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java index ccfbf591..07acb58a 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java @@ -85,7 +85,7 @@ public void deleteSimulationRun(long runId) { simulationRunRepository.deleteById(runId); } - public SimulationRun createAndQueueSimulationRun(long simulationId, ArtemisAccountDTO accountDTO) { + public SimulationRun createAndQueueSimulationRun(long simulationId, ArtemisAccountDTO accountDTO, SimulationSchedule schedule) { Simulation simulation = simulationRepository.findById(simulationId).orElseThrow(); if ( @@ -105,6 +105,7 @@ public SimulationRun createAndQueueSimulationRun(long simulationId, ArtemisAccou SimulationRun savedSimulationRun = simulationRunRepository.save(simulationRun); savedSimulationRun.setAdminAccount(accountDTO); + savedSimulationRun.setSchedule(schedule); simulationRunQueueService.queueSimulationRun(savedSimulationRun); simulationWebsocketService.sendNewRun(savedSimulationRun); return savedSimulationRun; diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationResultService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationResultService.java index 0e3a6f8f..5387326a 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationResultService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationResultService.java @@ -1,6 +1,7 @@ package de.tum.cit.ase.service.simulation; import de.tum.cit.ase.domain.*; +import de.tum.cit.ase.domain.RequestType; import de.tum.cit.ase.repository.SimulationStatsRepository; import de.tum.cit.ase.repository.StatsByMinuteRepository; import java.time.ZonedDateTime; diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java index 7fc57428..3191323d 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java @@ -7,6 +7,7 @@ import de.tum.cit.ase.domain.*; import de.tum.cit.ase.repository.LogMessageRepository; import de.tum.cit.ase.repository.SimulationRunRepository; +import de.tum.cit.ase.service.MailService; import de.tum.cit.ase.service.artemis.ArtemisConfiguration; import de.tum.cit.ase.service.artemis.ArtemisUserService; import de.tum.cit.ase.service.artemis.interaction.SimulatedArtemisAdmin; @@ -38,6 +39,7 @@ public class SimulationRunExecutionService { private final SimulationRunRepository simulationRunRepository; private final SimulationResultService simulationResultService; private final LogMessageRepository logMessageRepository; + private final MailService mailService; private boolean doNotSleep = false; public SimulationRunExecutionService( @@ -46,7 +48,8 @@ public SimulationRunExecutionService( ArtemisUserService artemisUserService, SimulationRunRepository simulationRunRepository, SimulationResultService simulationResultService, - LogMessageRepository logMessageRepository + LogMessageRepository logMessageRepository, + MailService mailService ) { this.simulationWebsocketService = simulationWebsocketService; this.artemisConfiguration = artemisConfiguration; @@ -54,6 +57,7 @@ public SimulationRunExecutionService( this.simulationResultService = simulationResultService; this.logMessageRepository = logMessageRepository; this.artemisUserService = artemisUserService; + this.mailService = mailService; } /** @@ -65,6 +69,8 @@ public SimulationRunExecutionService( */ public synchronized void simulateExam(SimulationRun simulationRun) { ArtemisAccountDTO accountDTO = simulationRun.getAdminAccount(); + SimulationSchedule schedule = simulationRun.getSchedule(); + simulationRun.setStatus(SimulationRun.Status.RUNNING); simulationRun = simulationRunRepository.save(simulationRun); simulationWebsocketService.sendRunStatusUpdate(simulationRun); @@ -263,6 +269,7 @@ public synchronized void simulateExam(SimulationRun simulationRun) { cleanupAsync(admin, simulationRun, courseId, examId); SimulationRun runWithResult = simulationResultService.calculateAndSaveResult(simulationRun, requestStats); finishSimulationRun(runWithResult); + runWithResult.setSchedule(schedule); sendRunResult(runWithResult); } @@ -472,5 +479,8 @@ private void finishSimulationRun(SimulationRun simulationRun) { private void sendRunResult(SimulationRun simulationRun) { simulationWebsocketService.sendSimulationResult(simulationRun); + if (simulationRun.getSchedule() != null) { + mailService.sendRunResultMail(simulationRun, simulationRun.getSchedule()); + } } } diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java index fbea2eb3..0e945774 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java @@ -103,7 +103,7 @@ void executeScheduledSimulations() { .forEach(simulationSchedule -> { log.info("Executing scheduled simulation run for simulation {}", simulationSchedule.getSimulation().getId()); var simulation = simulationSchedule.getSimulation(); - simulationDataService.createAndQueueSimulationRun(simulation.getId(), null); + simulationDataService.createAndQueueSimulationRun(simulation.getId(), null, simulationSchedule); updateNextRun(simulationSchedule); }); } diff --git a/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java b/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java index afa73ddf..971dab3c 100644 --- a/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java +++ b/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java @@ -93,7 +93,7 @@ public ResponseEntity runSimulation( @PathVariable long simulationId, @RequestBody(required = false) ArtemisAccountDTO accountDTO ) { - var run = simulationDataService.createAndQueueSimulationRun(simulationId, accountDTO); + var run = simulationDataService.createAndQueueSimulationRun(simulationId, accountDTO, null); return new ResponseEntity<>(run, HttpStatus.OK); } diff --git a/src/main/resources/templates/mail/scheduleSubscriptionEmail.html b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html index 415154bd..da7ebb4e 100644 --- a/src/main/resources/templates/mail/scheduleSubscriptionEmail.html +++ b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html @@ -5,15 +5,20 @@ -

Hello,

-

- Click here to unsubscribe. + Hello,
+ You have been subscribed to a schedule for the simulation
+ Simulation.

+

You will receive a summary of the simulation results after every scheduled run.

- Regards, + If you subscribed by mistake, you can unsubscribe here: + Unsubscribe +

+

+ Best regards,
- Artemis-Benchmarking + The Artemis-Benchmarking Team

diff --git a/src/main/resources/templates/mail/subscriptionResultEmail.html b/src/main/resources/templates/mail/subscriptionResultEmail.html new file mode 100644 index 00000000..a4027a3d --- /dev/null +++ b/src/main/resources/templates/mail/subscriptionResultEmail.html @@ -0,0 +1,41 @@ + + + + Result for scheduled run + + + +

+ Hello,
+ A scheduled run for the simulation + Simulation was executed.
+ Here are the results: +

+

+ Users: 0
+ Server: STAGING
+ Mode: CREATE_COURSE_AND_EXAM
+

+

+ Avg. response times:
+ - total: 0 ms
+ - authentication: 0 ms
+ - get student exam: 0 ms
+ - start student exam: 0 ms
+ - submit exercises: 0 ms
+ - submit student exam: 0 ms
+ - clone: 0 ms
+ - push: 0 ms
+

+ To see more details, please visit the run page. +

+ You are receiving this email because you subscribed to the schedule.
+ Click here to unsubscribe: Unsubscribe +

+

+ Best regards, +
+ The Artemis-Benchmarking Team +

+ + diff --git a/src/main/webapp/app/simulations/simulations-overview/simulations-overview.component.ts b/src/main/webapp/app/simulations/simulations-overview/simulations-overview.component.ts index 3da09f49..dfd04ae5 100644 --- a/src/main/webapp/app/simulations/simulations-overview/simulations-overview.component.ts +++ b/src/main/webapp/app/simulations/simulations-overview/simulations-overview.component.ts @@ -5,6 +5,7 @@ import { SimulationRun, Status } from '../../entities/simulation/simulationRun'; import { getOrder } from '../../entities/simulation/simulationStats'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'jhi-simulations-overview', @@ -24,15 +25,25 @@ export class SimulationsOverviewComponent implements OnInit { constructor( private simulationsService: SimulationsService, private modalService: NgbModal, + private route: ActivatedRoute, + private router: Router, ) {} ngOnInit(): void { + const selectedRunString = this.route.snapshot.queryParamMap.get('runId'); + let selectedRunId = -1; + if (selectedRunString) { + selectedRunId = parseInt(selectedRunString, 10); + } this.simulationsService.getSimulations().subscribe(simulations => { this.simulations = simulations.sort((a, b) => new Date(b.creationDate).getTime() - new Date(a.creationDate).getTime()); this.simulations.forEach(simulation => { simulation.runs.forEach(run => { this.subscribeToRunStatus(run); + if (run.id === selectedRunId) { + this.selectRun(run); + } }); }); }); @@ -58,6 +69,7 @@ export class SimulationsOverviewComponent implements OnInit { run.logMessages = updatedRun.logMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); this.selectedRun = run; this.subscribeToSelectedRun(run); + this.router.navigate([], { queryParams: { runId: run.id } }); }); } diff --git a/src/main/webapp/manifest.webapp b/src/main/webapp/manifest.webapp index 18f25768..612d2e69 100644 --- a/src/main/webapp/manifest.webapp +++ b/src/main/webapp/manifest.webapp @@ -1,28 +1,6 @@ { "name": "ArtemisBenchmarking", "short_name": "ArtemisBenchmarking", - "icons": [ - { - "src": "./content/images/jhipster_family_member_3_head-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "./content/images/jhipster_family_member_3_head-256.png", - "sizes": "256x256", - "type": "image/png" - }, - { - "src": "./content/images/jhipster_family_member_3_head-384.png", - "sizes": "384x384", - "type": "image/png" - }, - { - "src": "./content/images/jhipster_family_member_3_head-512.png", - "sizes": "512x512", - "type": "image/png" - } - ], "theme_color": "#000000", "background_color": "#e0e0e0", "start_url": ".", diff --git a/src/test/java/de/tum/cit/ase/service/SimulationDataServiceIT.java b/src/test/java/de/tum/cit/ase/service/SimulationDataServiceIT.java index dc0cfa4e..eb21b7c6 100644 --- a/src/test/java/de/tum/cit/ase/service/SimulationDataServiceIT.java +++ b/src/test/java/de/tum/cit/ase/service/SimulationDataServiceIT.java @@ -161,7 +161,7 @@ public void createAndQueueSimulationRun_success() { return runArg; }); - var queuedRun = simulationDataService.createAndQueueSimulationRun(1L, null); + var queuedRun = simulationDataService.createAndQueueSimulationRun(1L, null, null); assertEquals(1L, queuedRun.getId()); verify(simulationRunRepository).save(queuedRun); @@ -181,7 +181,7 @@ public void createAndQueueSimulationRun_fail_prodWithoutAccount() { runArg.setId(1L); return runArg; }); - assertThrows(IllegalArgumentException.class, () -> simulationDataService.createAndQueueSimulationRun(1L, null)); + assertThrows(IllegalArgumentException.class, () -> simulationDataService.createAndQueueSimulationRun(1L, null, null)); verify(simulationRunRepository, times(0)).save(any()); verify(simulationRunQueueService, times(0)).queueSimulationRun(any()); diff --git a/src/test/java/de/tum/cit/ase/service/SimulationScheduleServiceIT.java b/src/test/java/de/tum/cit/ase/service/SimulationScheduleServiceIT.java index b97ca19f..4c23587a 100644 --- a/src/test/java/de/tum/cit/ase/service/SimulationScheduleServiceIT.java +++ b/src/test/java/de/tum/cit/ase/service/SimulationScheduleServiceIT.java @@ -53,7 +53,7 @@ public void setUp() { when(simulationDataService.getSimulation(1L)).thenReturn(simulation); - when(simulationDataService.createAndQueueSimulationRun(anyLong(), any())).thenReturn(null); + when(simulationDataService.createAndQueueSimulationRun(anyLong(), any(), any())).thenReturn(null); } @Test From ee10e4e9e9e66d86c82b0c9ce2ac4a718d8f3e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Bo=CC=88hm?= Date: Tue, 12 Dec 2023 16:23:29 +0100 Subject: [PATCH 04/17] Email in case of failure --- .../ase/repository/LogMessageRepository.java | 10 +++++- .../de/tum/cit/ase/service/MailService.java | 28 +++++++++++++--- .../SimulationRunExecutionService.java | 10 +++++- .../mail/subscriptionFailureEmail.html | 33 +++++++++++++++++++ 4 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/templates/mail/subscriptionFailureEmail.html diff --git a/src/main/java/de/tum/cit/ase/repository/LogMessageRepository.java b/src/main/java/de/tum/cit/ase/repository/LogMessageRepository.java index a4b4e768..386f5767 100644 --- a/src/main/java/de/tum/cit/ase/repository/LogMessageRepository.java +++ b/src/main/java/de/tum/cit/ase/repository/LogMessageRepository.java @@ -1,6 +1,14 @@ package de.tum.cit.ase.repository; import de.tum.cit.ase.domain.LogMessage; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; -public interface LogMessageRepository extends JpaRepository {} +@Repository +public interface LogMessageRepository extends JpaRepository { + @Query(value = "select log from LogMessage log where log.simulationRun.id = :#{#simulationRunId} and log.isError = true") + List findBySimulationRunIdAndErrorIsTrue(@Param("simulationRunId") long simulationRunId); +} diff --git a/src/main/java/de/tum/cit/ase/service/MailService.java b/src/main/java/de/tum/cit/ase/service/MailService.java index 1b414677..5c1531dc 100644 --- a/src/main/java/de/tum/cit/ase/service/MailService.java +++ b/src/main/java/de/tum/cit/ase/service/MailService.java @@ -1,10 +1,6 @@ package de.tum.cit.ase.service; -import de.tum.cit.ase.domain.ScheduleSubscriber; -import de.tum.cit.ase.domain.SimulationResultForSummary; -import de.tum.cit.ase.domain.SimulationRun; -import de.tum.cit.ase.domain.SimulationSchedule; -import de.tum.cit.ase.domain.User; +import de.tum.cit.ase.domain.*; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import java.nio.charset.StandardCharsets; @@ -154,4 +150,26 @@ public void sendRunResultMail(SimulationRun run, SimulationSchedule schedule) { self.sendEmail(subscriber.getEmail(), subject, content, false, true); }); } + + @Async + public void sendRunFailureMail(SimulationRun run, SimulationSchedule schedule, LogMessage errorLogMessage) { + Locale locale = Locale.forLanguageTag("en"); + Context context = new Context(locale); + context.setVariable("run", run); + if (errorLogMessage != null) { + context.setVariable("error", errorLogMessage.getMessage()); + } else { + context.setVariable("error", "No error message found."); + } + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String subject = "Artemis-Benchmarking - Scheduled run failed"; + schedule + .getSubscribers() + .forEach(subscriber -> { + context.setVariable("subscriber", subscriber); + String content = templateEngine.process("mail/subscriptionFailureEmail", context); + log.debug("Sending result email to '{}'", subscriber.getEmail()); + self.sendEmail(subscriber.getEmail(), subject, content, false, true); + }); + } } diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java index 3191323d..f700fc6a 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationRunExecutionService.java @@ -73,6 +73,7 @@ public synchronized void simulateExam(SimulationRun simulationRun) { simulationRun.setStatus(SimulationRun.Status.RUNNING); simulationRun = simulationRunRepository.save(simulationRun); + simulationRun.setSchedule(schedule); simulationWebsocketService.sendRunStatusUpdate(simulationRun); var simulation = simulationRun.getSimulation(); @@ -269,7 +270,6 @@ public synchronized void simulateExam(SimulationRun simulationRun) { cleanupAsync(admin, simulationRun, courseId, examId); SimulationRun runWithResult = simulationResultService.calculateAndSaveResult(simulationRun, requestStats); finishSimulationRun(runWithResult); - runWithResult.setSchedule(schedule); sendRunResult(runWithResult); } @@ -466,6 +466,14 @@ private void failSimulationRun(SimulationRun simulationRun) { if (Thread.currentThread().isInterrupted()) { return; } + if (simulationRun.getSchedule() != null) { + LogMessage errorLogMessage = logMessageRepository + .findBySimulationRunIdAndErrorIsTrue(simulationRun.getId()) + .stream() + .max(Comparator.comparing(LogMessage::getTimestamp)) + .orElse(null); + mailService.sendRunFailureMail(simulationRun, simulationRun.getSchedule(), errorLogMessage); + } simulationRun.setStatus(SimulationRun.Status.FAILED); SimulationRun savedSimulationRun = simulationRunRepository.save(simulationRun); simulationWebsocketService.sendRunStatusUpdate(savedSimulationRun); diff --git a/src/main/resources/templates/mail/subscriptionFailureEmail.html b/src/main/resources/templates/mail/subscriptionFailureEmail.html new file mode 100644 index 00000000..30af6b00 --- /dev/null +++ b/src/main/resources/templates/mail/subscriptionFailureEmail.html @@ -0,0 +1,33 @@ + + + + Scheduled run failed + + + +

+ Hello,
+ A scheduled run for the simulation + Simulation was executed.
+

+

+ The run failed with the following error message:
+ Error message +

+

+ Users: 0
+ Server: STAGING
+ Mode: CREATE_COURSE_AND_EXAM
+

+ To see more details, please visit the run page. +

+ You are receiving this email because you subscribed to the schedule.
+ Click here to unsubscribe: Unsubscribe +

+

+ Best regards, +
+ The Artemis-Benchmarking Team +

+ + From 808f298afb56a963f7592544a97b7b6c8dbc6797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Bo=CC=88hm?= Date: Tue, 12 Dec 2023 16:29:58 +0100 Subject: [PATCH 05/17] Email improvements --- .../resources/templates/mail/subscriptionFailureEmail.html | 6 +++--- .../resources/templates/mail/subscriptionResultEmail.html | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/templates/mail/subscriptionFailureEmail.html b/src/main/resources/templates/mail/subscriptionFailureEmail.html index 30af6b00..08d93c55 100644 --- a/src/main/resources/templates/mail/subscriptionFailureEmail.html +++ b/src/main/resources/templates/mail/subscriptionFailureEmail.html @@ -15,9 +15,9 @@ Error message

- Users: 0
- Server: STAGING
- Mode: CREATE_COURSE_AND_EXAM
+ Users: 0
+ Server: STAGING
+ Mode: CREATE_COURSE_AND_EXAM

To see more details, please visit the run page.

diff --git a/src/main/resources/templates/mail/subscriptionResultEmail.html b/src/main/resources/templates/mail/subscriptionResultEmail.html index a4027a3d..2c7a5d0b 100644 --- a/src/main/resources/templates/mail/subscriptionResultEmail.html +++ b/src/main/resources/templates/mail/subscriptionResultEmail.html @@ -12,9 +12,9 @@ Here are the results:

- Users: 0
- Server: STAGING
- Mode: CREATE_COURSE_AND_EXAM
+ Users: 0
+ Server: STAGING
+ Mode: CREATE_COURSE_AND_EXAM

Avg. response times:
From 47cc15025c07611ea76d3d48f93ac27f37be5a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Bo=CC=88hm?= Date: Tue, 12 Dec 2023 17:44:47 +0100 Subject: [PATCH 06/17] UI for subscribing --- .../simulation/SimulationScheduleService.java | 3 +- .../mail/scheduleSubscriptionEmail.html | 4 +- .../mail/subscriptionFailureEmail.html | 2 +- .../mail/subscriptionResultEmail.html | 26 ++++----- .../simulation-card.component.ts | 2 +- .../simulation-schedule-dialog.component.html | 35 ++++++++++-- .../simulation-schedule-dialog.component.scss | 0 .../simulation-schedule-dialog.component.ts | 54 +++++++++++++++++-- .../app/simulations/simulations.service.ts | 5 ++ 9 files changed, 106 insertions(+), 25 deletions(-) rename src/main/webapp/app/layouts/{simulation-card => }/simulation-schedule-dialog/simulation-schedule-dialog.component.html (81%) rename src/main/webapp/app/layouts/{simulation-card => }/simulation-schedule-dialog/simulation-schedule-dialog.component.scss (100%) rename src/main/webapp/app/layouts/{simulation-card => }/simulation-schedule-dialog/simulation-schedule-dialog.component.ts (75%) diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java index 0e945774..afc4e07a 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationScheduleService.java @@ -82,7 +82,8 @@ public ScheduleSubscriber subscribeToSchedule(long scheduleId, String email) { log.debug("Subscribing {} to schedule {}", email, scheduleId); var schedule = simulationScheduleRepository.findById(scheduleId).orElseThrow(); if (schedule.getSubscribers().stream().anyMatch(subscriber -> subscriber.getEmail().equals(email))) { - throw new IllegalArgumentException("Already subscribed to this schedule"); + log.debug("Subscriber {} already subscribed to schedule {}", email, scheduleId); + return null; } var subscriber = new ScheduleSubscriber(); subscriber.setSchedule(schedule); diff --git a/src/main/resources/templates/mail/scheduleSubscriptionEmail.html b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html index da7ebb4e..8a40f86c 100644 --- a/src/main/resources/templates/mail/scheduleSubscriptionEmail.html +++ b/src/main/resources/templates/mail/scheduleSubscriptionEmail.html @@ -12,8 +12,8 @@

You will receive a summary of the simulation results after every scheduled run.

- If you subscribed by mistake, you can unsubscribe here: - Unsubscribe + If you subscribed by mistake, + click here to unsubscribe

Best regards, diff --git a/src/main/resources/templates/mail/subscriptionFailureEmail.html b/src/main/resources/templates/mail/subscriptionFailureEmail.html index 08d93c55..865f6f07 100644 --- a/src/main/resources/templates/mail/subscriptionFailureEmail.html +++ b/src/main/resources/templates/mail/subscriptionFailureEmail.html @@ -22,7 +22,7 @@ To see more details, please visit the run page.

You are receiving this email because you subscribed to the schedule.
- Click here to unsubscribe: Unsubscribe + Click here to unsubscribe

Best regards, diff --git a/src/main/resources/templates/mail/subscriptionResultEmail.html b/src/main/resources/templates/mail/subscriptionResultEmail.html index 2c7a5d0b..44ff84b5 100644 --- a/src/main/resources/templates/mail/subscriptionResultEmail.html +++ b/src/main/resources/templates/mail/subscriptionResultEmail.html @@ -16,21 +16,23 @@ Server: STAGING
Mode: CREATE_COURSE_AND_EXAM

-

- Avg. response times:
- - total: 0 ms
- - authentication: 0 ms
- - get student exam: 0 ms
- - start student exam: 0 ms
- - submit exercises: 0 ms
- - submit student exam: 0 ms
- - clone: 0 ms
- - push: 0 ms
-

+
+

Avg. response times:

+

+ - total: 0 ms
+ - authentication: 0 ms
+ - get student exam: 0 ms
+ - start student exam: 0 ms
+ - submit exercises: 0 ms
+ - submit student exam: 0 ms
+ - clone: 0 ms
+ - push: 0 ms
+

+
To see more details, please visit the run page.

You are receiving this email because you subscribed to the schedule.
- Click here to unsubscribe: Unsubscribe + Click here to unsubscribe

Best regards, diff --git a/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts b/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts index 4e90a397..92477593 100644 --- a/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts +++ b/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts @@ -6,7 +6,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ArtemisServer } from '../../core/util/artemisServer'; import { ArtemisAccountDTO } from '../../simulations/artemisAccountDTO'; import { faCalendarDays, faChevronRight, faClock, faTrashCan } from '@fortawesome/free-solid-svg-icons'; -import { SimulationScheduleDialogComponent } from './simulation-schedule-dialog/simulation-schedule-dialog.component'; +import { SimulationScheduleDialogComponent } from '../simulation-schedule-dialog/simulation-schedule-dialog.component'; @Component({ selector: 'jhi-simulation-card', diff --git a/src/main/webapp/app/layouts/simulation-card/simulation-schedule-dialog/simulation-schedule-dialog.component.html b/src/main/webapp/app/layouts/simulation-schedule-dialog/simulation-schedule-dialog.component.html similarity index 81% rename from src/main/webapp/app/layouts/simulation-card/simulation-schedule-dialog/simulation-schedule-dialog.component.html rename to src/main/webapp/app/layouts/simulation-schedule-dialog/simulation-schedule-dialog.component.html index 94fe1166..84fa29c8 100644 --- a/src/main/webapp/app/layouts/simulation-card/simulation-schedule-dialog/simulation-schedule-dialog.component.html +++ b/src/main/webapp/app/layouts/simulation-schedule-dialog/simulation-schedule-dialog.component.html @@ -3,10 +3,15 @@

+ + + + + + + diff --git a/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts b/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts index 9f65688a..2c3acd52 100644 --- a/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts +++ b/src/main/webapp/app/layouts/simulation-card/simulation-card.component.ts @@ -5,7 +5,7 @@ import { SimulationsService } from '../../simulations/simulations.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ArtemisServer } from '../../core/util/artemisServer'; import { ArtemisAccountDTO } from '../../simulations/artemisAccountDTO'; -import { faCalendarDays, faChevronRight, faClock, faTrashCan } from '@fortawesome/free-solid-svg-icons'; +import { faCalendarDays, faChevronRight, faClock, faEye, faEyeSlash, faTrashCan, faUserTie } from '@fortawesome/free-solid-svg-icons'; import { SimulationScheduleDialogComponent } from '../simulation-schedule-dialog/simulation-schedule-dialog.component'; @Component({ @@ -18,6 +18,9 @@ export class SimulationCardComponent implements OnInit { faChevronRight = faChevronRight; faCalendarDays = faCalendarDays; faClock = faClock; + faUserTie = faUserTie; + faEye = faEye; + faEyeSlash = faEyeSlash; @Input() simulation!: Simulation; @@ -27,9 +30,11 @@ export class SimulationCardComponent implements OnInit { numberOfDisplayedRuns = 3; numberOfActiveSchedules = 0; credentialsRequired = false; + instructorAccountAvailable = false; adminPassword = ''; adminUsername = ''; + showAdminPassword = false; @Output() clickedRunEvent = new EventEmitter(); @Output() delete = new EventEmitter(); @@ -56,6 +61,7 @@ export class SimulationCardComponent implements OnInit { this.simulation.server === ArtemisServer.PRODUCTION && this.simulation.mode !== Mode.EXISTING_COURSE_PREPARED_EXAM && !instructorCredentialsProvided(this.simulation); + this.instructorAccountAvailable = instructorCredentialsProvided(this.simulation); } startRun(content: any): void { @@ -69,8 +75,15 @@ export class SimulationCardComponent implements OnInit { this.simulationService.runSimulation(this.simulation.id!, account).subscribe(newRun => { this.addNewRun(newRun); }); + this.adminPassword = ''; + this.adminUsername = ''; + this.showAdminPassword = false; + }, + () => { + this.adminPassword = ''; + this.adminUsername = ''; + this.showAdminPassword = false; }, - () => {}, ); } else { this.simulationService.runSimulation(this.simulation.id!).subscribe(newRun => { @@ -79,6 +92,22 @@ export class SimulationCardComponent implements OnInit { } } + patchInstructorAccount(content: any): void { + this.modalService.open(content, { ariaLabelledBy: 'instructor-modal-title' }).result.then( + () => { + this.patchSimulationInstructorAccount(); + this.adminPassword = ''; + this.adminUsername = ''; + this.showAdminPassword = false; + }, + () => { + this.adminPassword = ''; + this.adminUsername = ''; + this.showAdminPassword = false; + }, + ); + } + sortRuns(): void { this.simulation.runs.sort((a, b) => new Date(b.startDateTime).getTime() - new Date(a.startDateTime).getTime()); this.displayedRuns = this.simulation.runs.slice(0, 3); @@ -147,4 +176,13 @@ export class SimulationCardComponent implements OnInit { this.sortRuns(); this.updateDisplayRuns(); } + + patchSimulationInstructorAccount(): void { + const account = new ArtemisAccountDTO(this.adminUsername, this.adminPassword); + this.simulationService.patchSimulationInstructorAccount(this.simulation.id!, account).subscribe(updatedSimulation => { + this.simulation.instructorUsername = updatedSimulation.instructorUsername; + this.simulation.instructorPassword = updatedSimulation.instructorPassword; + this.instructorAccountAvailable = instructorCredentialsProvided(this.simulation); + }); + } } diff --git a/src/main/webapp/app/simulations/simulations.service.ts b/src/main/webapp/app/simulations/simulations.service.ts index 8b293d67..c6b0b1ff 100644 --- a/src/main/webapp/app/simulations/simulations.service.ts +++ b/src/main/webapp/app/simulations/simulations.service.ts @@ -72,6 +72,11 @@ export class SimulationsService { return this.httpClient.delete(endpoint).pipe(map(() => {})); } + patchSimulationInstructorAccount(simulationId: number, account: ArtemisAccountDTO): Observable { + const endpoint = this.applicationConfigService.getEndpointFor('/api/simulations/' + simulationId + '/instructor-account'); + return this.httpClient.patch(endpoint, account).pipe(map((res: any) => res as Simulation)); + } + deleteSimulationRun(runId: number): Observable { const endpoint = this.applicationConfigService.getEndpointFor('/api/simulations/runs/' + runId); return this.httpClient.delete(endpoint).pipe(map(() => {})); diff --git a/src/main/webapp/content/scss/global.scss b/src/main/webapp/content/scss/global.scss index f65ed0d7..76ca0aaf 100644 --- a/src/main/webapp/content/scss/global.scss +++ b/src/main/webapp/content/scss/global.scss @@ -97,6 +97,10 @@ Generic styles width: fit-content !important; } +.h-fit-content { + height: fit-content !important; +} + .h-90 { height: 90% !important; } From cb76b1b04e509c12058091299e1c11d5868fcf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Bo=CC=88hm?= Date: Fri, 15 Dec 2023 13:45:55 +0100 Subject: [PATCH 14/17] Delete instructor account --- .../simulation/SimulationDataService.java | 8 +++++++ .../cit/ase/web/rest/SimulationResource.java | 10 +++++++++ .../simulation-card.component.html | 21 +++++++++++++++++-- .../simulation-card.component.ts | 16 ++++++++++++-- .../app/simulations/simulations.service.ts | 5 +++++ 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java b/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java index c018c12c..ad7270c1 100644 --- a/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java +++ b/src/main/java/de/tum/cit/ase/service/simulation/SimulationDataService.java @@ -216,4 +216,12 @@ public Simulation updateInstructorAccount(long simulationId, ArtemisAccountDTO a simulation.setInstructorPassword(account.getPassword()); return simulationRepository.save(simulation); } + + public Simulation removeInstructorAccount(long simulationId) { + var simulation = simulationRepository.findById(simulationId).orElseThrow(); + + simulation.setInstructorUsername(null); + simulation.setInstructorPassword(null); + return simulationRepository.save(simulation); + } } diff --git a/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java b/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java index 127ed6b4..e9cb61db 100644 --- a/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java +++ b/src/main/java/de/tum/cit/ase/web/rest/SimulationResource.java @@ -212,4 +212,14 @@ public ResponseEntity updateInstructorAccount(@PathVariable long sim } return new ResponseEntity<>(simulation, HttpStatus.OK); } + + @DeleteMapping("/{simulationId}/instructor-account") + public ResponseEntity removeInstructorAccount(@PathVariable long simulationId) { + var simulation = simulationDataService.removeInstructorAccount(simulationId); + if (simulation.getInstructorUsername() != null || simulation.getInstructorPassword() != null) { + simulation.setInstructorUsername(""); + simulation.setInstructorPassword(""); + } + return new ResponseEntity<>(simulation, HttpStatus.OK); + } } diff --git a/src/main/webapp/app/layouts/simulation-card/simulation-card.component.html b/src/main/webapp/app/layouts/simulation-card/simulation-card.component.html index 275dc8be..bc741efc 100644 --- a/src/main/webapp/app/layouts/simulation-card/simulation-card.component.html +++ b/src/main/webapp/app/layouts/simulation-card/simulation-card.component.html @@ -204,6 +204,9 @@