From 3eeeb998a16ca4044ac27553a27c281521b65f9e Mon Sep 17 00:00:00 2001 From: isabella Date: Mon, 13 Jan 2025 15:54:59 +0100 Subject: [PATCH 01/30] WIP --- package.json | 2 +- .../service/pyris/PyrisConnectorService.java | 22 ++++ .../iris/service/pyris/PyrisJobService.java | 16 +++ .../service/pyris/PyrisWebhookService.java | 35 ++++++ ...PyrisTranscriptionIngestionWebhookDTO.java | 15 +++ ...ookTranscriptionIngestionExecutionDTO.java | 13 ++ .../iris/web/IrisTranscriptionResource.java | 111 ++++++++++++++++++ .../aet/artemis/lecture/domain/Lecture.java | 18 +++ .../artemis/lecture/domain/Transcription.java | 61 ++++++++++ .../lecture/domain/TranscriptionSegment.java | 41 +++++++ .../repository/TranscriptionRepository.java | 28 +++++ 11 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java diff --git a/package.json b/package.json index d392de262aa4..914f2fe2b9f2 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "prepare": "husky", "prettier:check": "prettier --check \"src/{main/webapp,test}/**/*.{json,ts,js,css,scss,html}\"", "prettier:write": "prettier --write \"src/{main/webapp,test}/**/*.{json,ts,js,css,scss,html}\"", - "start": "npm run prebuild -- --develop && ng serve --hmr", + "start": "npm run prebuild -- --develop && ng serve --hmr --host 0.0.0.0", "test-diff:ci": "git fetch origin develop && npm run prebuild && ng test --log-heap-usage -w=4 --ci --reporters=default --reporters=jest-junit --pass-with-no-tests --changed-since=origin/develop", "test-diff": "npm run prebuild && ng test --log-heap-usage -w=4 --pass-with-no-tests --changed-since=origin/develop --coverage", "test:ci": "npm run prebuild && ng test --coverage --log-heap-usage -w=4 --ci --reporters=default --reporters=jest-junit", diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java index e29999073eb9..aea4af4ade58 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java @@ -31,6 +31,7 @@ import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisVariantDTO; import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisWebhookLectureDeletionExecutionDTO; import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisWebhookLectureIngestionExecutionDTO; +import de.tum.cit.aet.artemis.iris.service.pyris.dto.transcriptionIngestion.PyrisWebhookTranscriptionIngestionExecutionDTO; import de.tum.cit.aet.artemis.iris.service.pyris.job.IngestionWebhookJob; import de.tum.cit.aet.artemis.iris.web.open.PublicPyrisStatusUpdateResource; @@ -108,6 +109,27 @@ public void executePipeline(String feature, String variant, Object executionDTO, } } + /** + * Executes a webhook and send transcription to the webhook with the given variant + * + * @param variant The variant of the feature to execute + * @param executionDTO The DTO sent as a body for the execution + */ + public void executeTranscriptionAddtionWebhook(String variant, PyrisWebhookTranscriptionIngestionExecutionDTO executionDTO) { + var endpoint = "/api/v1/webhooks/transcriptions/" + variant; + try { + restTemplate.postForEntity(pyrisUrl + endpoint, objectMapper.valueToTree(executionDTO), Void.class); + } + catch (HttpStatusCodeException e) { + log.error("Failed to send lecture unit {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); + throw toIrisException(e); + } + catch (RestClientException | IllegalArgumentException e) { + log.error("Failed to send lecture unit {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); + throw new PyrisConnectorException("Could not fetch response from Pyris"); + } + } + /** * Executes a webhook and send lectures to the webhook with the given variant * diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java index 46c0dd7547cd..f42ee8f00382 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java @@ -108,6 +108,22 @@ public String addIngestionWebhookJob(long courseId, long lectureId, long lecture return token; } + /** + * Adds a new transcription ingestion webhook job to the job map with a timeout. + * + * @param courseId the ID of the course associated with the webhook job + * @param lectureId the ID of the lecture associated with the webhook job + * @return a unique token identifying the created webhook job + */ + public String addTranscriptionIngestionWebhookJob(long courseId, long lectureId) { + var token = generateJobIdToken(); + var job = new IngestionWebhookJob(token, courseId, lectureId, 0); // TODO: check whether this job can be reused + long timeoutWebhookJob = 60; + TimeUnit unitWebhookJob = TimeUnit.MINUTES; + jobMap.put(token, job, timeoutWebhookJob, unitWebhookJob); + return token; + } + /** * Remove a job from the job map. * diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java index 395932a7157a..59e1dd1e1b19 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java @@ -30,11 +30,14 @@ import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisLectureUnitWebhookDTO; import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisWebhookLectureDeletionExecutionDTO; import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisWebhookLectureIngestionExecutionDTO; +import de.tum.cit.aet.artemis.iris.service.pyris.dto.transcriptionIngestion.PyrisTranscriptionIngestionWebhookDTO; +import de.tum.cit.aet.artemis.iris.service.pyris.dto.transcriptionIngestion.PyrisWebhookTranscriptionIngestionExecutionDTO; import de.tum.cit.aet.artemis.iris.service.settings.IrisSettingsService; import de.tum.cit.aet.artemis.lecture.domain.AttachmentType; import de.tum.cit.aet.artemis.lecture.domain.AttachmentUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; +import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; @@ -69,6 +72,38 @@ public PyrisWebhookService(PyrisConnectorService pyrisConnectorService, PyrisJob this.lectureRepository = lectureRepository; } + private boolean transcriptionIngestionEnabled(Course course) { + // WIP + return true; + } + + /** + * adds the transcription to the vector database in Pyris + * + * @param transcription The transcription that got Updated + * @return jobToken if the job was created else null + */ + public String addTranscriptionToPyrisDB(Transcription transcription) { + if (lectureIngestionEnabled(transcription.getLecture().getCourse())) { + return executeTranscriptionAdditionWebhook(new PyrisTranscriptionIngestionWebhookDTO(transcription, 0, "", 0, "", "")); + } + return null; + } + + /** + * executes executeTranscriptionAdditionWebhook add transcription from to the vector database on pyris + * + * @param toUpdateTranscription The transcription that are going to be Updated + * @return jobToken if the job was created + */ + private String executeTranscriptionAdditionWebhook(PyrisTranscriptionIngestionWebhookDTO toUpdateTranscription) { + String jobToken = pyrisJobService.addTranscriptionIngestionWebhookJob(toUpdateTranscription.courseId(), toUpdateTranscription.lectureId()); + PyrisPipelineExecutionSettingsDTO settingsDTO = new PyrisPipelineExecutionSettingsDTO(jobToken, List.of(), artemisBaseUrl); + PyrisWebhookTranscriptionIngestionExecutionDTO executionDTO = new PyrisWebhookTranscriptionIngestionExecutionDTO(toUpdateTranscription, settingsDTO, List.of()); + pyrisConnectorService.executeTranscriptionAddtionWebhook("fullIngestion", executionDTO); + return jobToken; + } + private boolean lectureIngestionEnabled(Course course) { return irisSettingsService.getRawIrisSettingsFor(course).getIrisLectureIngestionSettings() != null && irisSettingsService.getRawIrisSettingsFor(course).getIrisLectureIngestionSettings().isEnabled(); diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java new file mode 100644 index 000000000000..8e4237f8660d --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java @@ -0,0 +1,15 @@ +package de.tum.cit.aet.artemis.iris.service.pyris.dto.transcriptionIngestion; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.cit.aet.artemis.lecture.domain.Transcription; + +/** + * Represents a webhook data transfer object for lecture units in the Pyris system. + * This DTO is used to encapsulate the information related to updates of lecture units, + * providing necessary details such as lecture and course identifiers, names, and descriptions. + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) + +public record PyrisTranscriptionIngestionWebhookDTO(Transcription transcription, long lectureId, String lectureName, long courseId, String courseName, String courseDescription) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java new file mode 100644 index 000000000000..97cc55069051 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.iris.service.pyris.dto.transcriptionIngestion; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisPipelineExecutionSettingsDTO; +import de.tum.cit.aet.artemis.iris.service.pyris.dto.status.PyrisStageDTO; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record PyrisWebhookTranscriptionIngestionExecutionDTO(PyrisTranscriptionIngestionWebhookDTO transcription, PyrisPipelineExecutionSettingsDTO settings, + List initialStages) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java new file mode 100644 index 000000000000..15e3844aa93c --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java @@ -0,0 +1,111 @@ +package de.tum.cit.aet.artemis.iris.web; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.tum.cit.aet.artemis.core.domain.Course; +import de.tum.cit.aet.artemis.core.repository.CourseRepository; +import de.tum.cit.aet.artemis.core.repository.UserRepository; +import de.tum.cit.aet.artemis.core.security.Role; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastInstructorInCourse; +import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; +import de.tum.cit.aet.artemis.iris.dto.IngestionState; +import de.tum.cit.aet.artemis.iris.service.IrisRateLimitService; +import de.tum.cit.aet.artemis.iris.service.pyris.PyrisConnectorException; +import de.tum.cit.aet.artemis.iris.service.pyris.PyrisHealthIndicator; +import de.tum.cit.aet.artemis.iris.service.pyris.PyrisWebhookService; + +@Profile(PROFILE_IRIS) +@RestController +@RequestMapping("api/iris/transcription-ingestion") +public class IrisTranscriptionResource { + + private static final Logger log = LoggerFactory.getLogger(IrisResource.class); + + protected final UserRepository userRepository; + + protected final IrisRateLimitService irisRateLimitService; + + protected final PyrisHealthIndicator pyrisHealthIndicator; + + private final AuthorizationCheckService authorizationCheckService; + + private final CourseRepository courseRepository; + + private final PyrisWebhookService pyrisWebhookService; + + public IrisTranscriptionResource(UserRepository userRepository, PyrisHealthIndicator pyrisHealthIndicator, IrisRateLimitService irisRateLimitService, + AuthorizationCheckService authorizationCheckService, CourseRepository courseRepository, PyrisWebhookService pyrisWebhookService) { + this.userRepository = userRepository; + this.pyrisHealthIndicator = pyrisHealthIndicator; + this.irisRateLimitService = irisRateLimitService; + this.authorizationCheckService = authorizationCheckService; + this.courseRepository = courseRepository; + this.pyrisWebhookService = pyrisWebhookService; + } + + /** + * Retrieves the overall ingestion state of a Transcription by communicating with Pyris. + * + *

+ * This method sends a GET request to the external Pyris service to fetch the current ingestion + * state of all Transcription in a course, identified by its `lectureId`. The ingestion state can be aggregated from + * multiple lecture units or can reflect the overall status of the lecture ingestion process. + *

+ * + * @param courseId the ID of the lecture for which the ingestion state is being requested + * @return a {@link ResponseEntity} containing the {@link IngestionState} of the lecture, + */ + @GetMapping("courses/{courseId}/lectures/transcription-ingestion-state") + @EnforceAtLeastInstructorInCourse + public ResponseEntity> getStatusOfTranscriptionIngestion(@PathVariable long courseId) { + try { + Course course = courseRepository.findByIdElseThrow(courseId); + authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); + return ResponseEntity.ok(pyrisWebhookService.getLecturesIngestionState(courseId)); // update to new webhook service for transcription + } + catch (PyrisConnectorException e) { + log.error("Error fetching ingestion state for course {}", courseId, e); + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); + } + } + + /** + * Retrieves the ingestion state of all transcription unit in a lecture by communicating with Pyris. + * + *

+ * This method sends a GET request to the external Pyris service to fetch the current ingestion + * state of a lecture unit, identified by its ID. It constructs a request using the provided + * `lectureId` and `lectureUnitId` and returns the state of the ingestion process (e.g., NOT_STARTED, + * IN_PROGRESS, DONE, ERROR). + *

+ * + * @param courseId the ID of the lecture the unit belongs to + * @param lectureId the ID of the lecture the unit belongs to + * @return a {@link ResponseEntity} containing the {@link IngestionState} of the lecture unit, + */ + @GetMapping("courses/{courseId}/lectures/{lectureId}/lecture-units/transcription-ingestion-state") + @EnforceAtLeastInstructorInCourse + public ResponseEntity> getStatusOfLectureUnitsTranscriptionIngestion(@PathVariable long courseId, @PathVariable long lectureId) { + try { + Course course = courseRepository.findByIdElseThrow(courseId); + authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); + return ResponseEntity.ok(pyrisWebhookService.getLectureUnitsIngestionState(courseId, lectureId)); // update to new webhook service for transcription + } + catch (PyrisConnectorException e) { + log.error("Error fetching ingestion state for lecture {}", lectureId, e); + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); + } + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java index 0163d67ca23d..d75dc3a989e7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java @@ -60,6 +60,11 @@ public class Lecture extends DomainObject { @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private List lectureUnits = new ArrayList<>(); + @OneToMany(mappedBy = "lecture", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnoreProperties("lecture") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + private List transcriptions = new ArrayList<>(); + @OneToMany(mappedBy = "lecture", cascade = CascadeType.REMOVE, orphanRemoval = true) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @JsonIncludeProperties({ "id" }) @@ -141,6 +146,19 @@ public void addLectureUnit(LectureUnit lectureUnit) { lectureUnit.setLecture(this); } + public List getTranscriptions() { + return transcriptions; + } + + public void setTranscriptions(List transcriptions) { + this.transcriptions = transcriptions; + } + + public void addTranscription(Transcription transcription) { + this.transcriptions.add(transcription); + transcription.setLecture(this); + } + public Set getPosts() { return posts; } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java new file mode 100644 index 000000000000..0200c0dd1927 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java @@ -0,0 +1,61 @@ +package de.tum.cit.aet.artemis.lecture.domain; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +import de.tum.cit.aet.artemis.core.domain.DomainObject; + +@Entity +@Table(name = "transcription") +public class Transcription extends DomainObject { + + private String language; + + @ManyToOne + @JoinColumn(name = "lecture_id") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + private Lecture lecture; + + @OneToMany(cascade = CascadeType.ALL) + @OrderColumn(name = "start_time") + @JoinColumn(name = "transcrition_id") + private List segments = new ArrayList<>(); + + public Transcription() { + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Lecture getLecture() { + return lecture; + } + + public void setLecture(Lecture lecture) { + this.lecture = lecture; + } + + public List getSegments() { + return segments; + } + + public void setSegments(List segments) { + this.segments = segments; + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java new file mode 100644 index 000000000000..5b2c41c86bca --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java @@ -0,0 +1,41 @@ +package de.tum.cit.aet.artemis.lecture.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +import de.tum.cit.aet.artemis.core.domain.DomainObject; + +@Entity +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +@Table(name = "transcription_segments") +public class TranscriptionSegment extends DomainObject { + + @Column(name = "start_time") + private Double startTime; + + @Column(name = "end_time") + private Double endTime; + + private String text; + + @ManyToOne + @JoinColumn(name = "lecture_unit_id") + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + private LectureUnit lectureUnit; + + public TranscriptionSegment() { + } + + public TranscriptionSegment(Double startTime, Double endTime, String text, LectureUnit lectureUnit) { + this.startTime = startTime; + this.endTime = endTime; + this.text = text; + this.lectureUnit = lectureUnit; + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java new file mode 100644 index 000000000000..cec75ada3059 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java @@ -0,0 +1,28 @@ +package de.tum.cit.aet.artemis.lecture.repository; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import java.util.Optional; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import de.tum.cit.aet.artemis.lecture.domain.Transcription; + +/** + * Spring Data JPA repository for the Transcription of a lecture video entity. + */ +@Profile(PROFILE_CORE) +@Repository +public interface TranscriptionRepository { + + @Query(""" + SELECT t + FROM Transcription t + WHERE t.id = :textUnitId + """) + Optional findById(@Param("lectureId") Long lectureId); + +} From 6335ee16ad7ee50c4a1a90a1aa47a74398f92e40 Mon Sep 17 00:00:00 2001 From: isabella Date: Mon, 13 Jan 2025 16:10:32 +0100 Subject: [PATCH 02/30] WIP --- .../de/tum/cit/aet/artemis/lecture/domain/Transcription.java | 2 +- .../cit/aet/artemis/lecture/domain/TranscriptionSegment.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java index 0200c0dd1927..1c05126bb5b4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java @@ -29,7 +29,7 @@ public class Transcription extends DomainObject { @OneToMany(cascade = CascadeType.ALL) @OrderColumn(name = "start_time") - @JoinColumn(name = "transcrition_id") + @JoinColumn(name = "transcription_id") private List segments = new ArrayList<>(); public Transcription() { diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java index 5b2c41c86bca..05d11a38fc3d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java @@ -3,6 +3,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; +import jakarta.persistence.Lob; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; @@ -22,6 +23,7 @@ public class TranscriptionSegment extends DomainObject { @Column(name = "end_time") private Double endTime; + @Lob private String text; @ManyToOne From a357749b3e97382fa97d43e341bdf4c82b9733ac Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 13 Jan 2025 16:10:32 +0100 Subject: [PATCH 03/30] WIP --- .../artemis/lecture/domain/Transcription.java | 2 +- .../lecture/domain/TranscriptionSegment.java | 2 + .../changelog/20250113154600_changelog.xml | 59 +++++++++++++++++++ .../resources/config/liquibase/master.xml | 1 + 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java index 0200c0dd1927..1c05126bb5b4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java @@ -29,7 +29,7 @@ public class Transcription extends DomainObject { @OneToMany(cascade = CascadeType.ALL) @OrderColumn(name = "start_time") - @JoinColumn(name = "transcrition_id") + @JoinColumn(name = "transcription_id") private List segments = new ArrayList<>(); public Transcription() { diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java index 5b2c41c86bca..05d11a38fc3d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java @@ -3,6 +3,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; +import jakarta.persistence.Lob; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; @@ -22,6 +23,7 @@ public class TranscriptionSegment extends DomainObject { @Column(name = "end_time") private Double endTime; + @Lob private String text; @ManyToOne diff --git a/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml new file mode 100644 index 000000000000..fec68d59e5c6 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index c25b722955e3..d25b9ee6b6d2 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -43,6 +43,7 @@ + From 3f9a5571d510922ceada59660141cac0527d974b Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 13 Jan 2025 17:17:24 +0100 Subject: [PATCH 04/30] WIP --- .../app/iris/iris-transcription.service.ts | 22 +++++++++++++++ src/main/webapp/app/iris/iris.module.ts | 2 ++ ...ure-transcription-ingestion.component.html | 12 +++++++++ ...ure-transcription-ingestion.component.scss | 3 +++ ...cture-transcription-ingestion.component.ts | 27 +++++++++++++++++++ ...iris-course-settings-update.component.html | 5 ++++ 6 files changed, 71 insertions(+) create mode 100644 src/main/webapp/app/iris/iris-transcription.service.ts create mode 100644 src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html create mode 100644 src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss create mode 100644 src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts diff --git a/src/main/webapp/app/iris/iris-transcription.service.ts b/src/main/webapp/app/iris/iris-transcription.service.ts new file mode 100644 index 000000000000..5b8ea0209bd1 --- /dev/null +++ b/src/main/webapp/app/iris/iris-transcription.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class IrisTranscriptionService { + private resourceURL = 'api'; + + constructor(private httpClient: HttpClient) {} + + ingestTranscription(courseId: number, lectureId: number): Observable { + console.log('inserting123'); + console.log(`${this.resourceURL}/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`); + return this.httpClient.put( + `${this.resourceURL}/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`, + {}, + { + responseType: 'text', + }, + ); + } +} diff --git a/src/main/webapp/app/iris/iris.module.ts b/src/main/webapp/app/iris/iris.module.ts index 844b5d97c3af..59d6b7f32ea7 100644 --- a/src/main/webapp/app/iris/iris.module.ts +++ b/src/main/webapp/app/iris/iris.module.ts @@ -21,6 +21,7 @@ import { IrisBaseChatbotComponent } from 'app/iris/base-chatbot/iris-base-chatbo import { ChatStatusBarComponent } from 'app/iris/base-chatbot/chat-status-bar/chat-status-bar.component'; import { CourseChatbotComponent } from 'app/iris/course-chatbot/course-chatbot.component'; import { IrisLogoComponent } from 'app/iris/iris-logo/iris-logo.component'; +import { LectureTranscriptionIngestionComponent } from 'app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component'; @NgModule({ declarations: [ @@ -36,6 +37,7 @@ import { IrisLogoComponent } from 'app/iris/iris-logo/iris-logo.component'; ChatStatusBarComponent, IrisLogoButtonComponent, CourseChatbotComponent, + LectureTranscriptionIngestionComponent, ], imports: [ CommonModule, diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html new file mode 100644 index 000000000000..4d0a5ad37831 --- /dev/null +++ b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html @@ -0,0 +1,12 @@ +
+
+

Ingest transcription by lecture id

+ + +
+
+

Insert transcription into DB

+ + +
+
diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss new file mode 100644 index 000000000000..fad257e7d2fe --- /dev/null +++ b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss @@ -0,0 +1,3 @@ +.insert-db-container { + margin-top: 1rem; +} diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts new file mode 100644 index 000000000000..d6a830b33928 --- /dev/null +++ b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts @@ -0,0 +1,27 @@ +import { Component, Input } from '@angular/core'; +import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import { IrisTranscriptionService } from 'app/iris/iris-transcription.service'; + +@Component({ + selector: 'jhi-lecture-transcription-ingestion', + templateUrl: './lecture-transcription-ingestion.component.html', + styleUrl: './lecture-transcription-ingestion.component.scss', +}) +export class LectureTranscriptionIngestionComponent { + @Input() + public courseId: number; + + lectureIdInput = ''; + + isIngestionLoading = false; + + faCheck = faCheck; + + constructor(private irisTranscriptionService: IrisTranscriptionService) {} + + ingestTranscription(): void { + this.irisTranscriptionService.ingestTranscription(this.courseId, Number(this.lectureIdInput)).subscribe((response) => { + console.log('Response received:', response); + }); + } +} diff --git a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html index 43df077bdd28..0d4839cd92d3 100644 --- a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html @@ -3,4 +3,9 @@

@if (courseId) { } + +
+ +

Lecture Transcription

+ From 151abde2589ca01cc5dec88e4c8db003800d0ee2 Mon Sep 17 00:00:00 2001 From: isabella Date: Mon, 13 Jan 2025 17:17:49 +0100 Subject: [PATCH 05/30] WIP --- .../lecture/service/LectureService.java | 14 ++++++++++ .../artemis/lecture/web/LectureResource.java | 28 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java index 9bf8edba5bea..b229e396e0cd 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java @@ -30,6 +30,7 @@ import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; +import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; @Profile(PROFILE_CORE) @@ -186,4 +187,17 @@ public void ingestLecturesInPyris(Set lectures) { } } } + + /** + * Ingest the transcriptions when triggered by the ingest transcription button + * + * @param transcriptions set of transcriptions to be ingested + */ + public void ingestTranscriptionInPyris(Set transcriptions) { + if (pyrisWebhookService.isPresent()) { + for (Transcription transcription : transcriptions) { + pyrisWebhookService.get().addTranscriptionToPyrisDB(transcription); + } + } + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index deeb3ec3861c..741914400076 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -51,6 +51,7 @@ import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; +import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; import de.tum.cit.aet.artemis.lecture.service.LectureImportService; import de.tum.cit.aet.artemis.lecture.service.LectureService; @@ -293,6 +294,33 @@ public ResponseEntity ingestLectures(@PathVariable Long courseId, @Request return ResponseEntity.ok().build(); } + /** + * POST /courses/{courseId}/ingest-transcription + * This endpoint is for starting the ingestion of all lectures or only one lecture when triggered in Artemis. + * + * @param courseId the ID of the course for which all lectures should be ingested in pyris + * @param lectureId If this id is present then only ingest this one lecture of the respective course + * @return the ResponseEntity with status 200 (OK) and a message success or null if the operation failed + */ + @Profile(PROFILE_IRIS) + @PutMapping("courses/{courseId}/ingest-transcription") + @EnforceAtLeastInstructorInCourse + public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @RequestParam(required = false) Optional lectureId) { + Course course = courseRepository.findByIdWithLecturesAndLectureUnitsElseThrow(courseId); + authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); + if (lectureId.isPresent()) { + Optional lectureToIngest = course.getLectures().stream().filter(lecture -> lecture.getId().equals(lectureId.get())).findFirst(); + if (lectureToIngest.isPresent()) { + Set lecturesToIngest = new HashSet<>(lectureToIngest.get().getTranscriptions()); + lectureService.ingestTranscriptionInPyris(lecturesToIngest); + return ResponseEntity.ok().build(); + } + return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.allLecturesError", "idExists")).body(null); + } + lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); + return ResponseEntity.ok().build(); + } + /** * GET /lectures/:lectureId/details : get the "lectureId" lecture. * From 750a1791d305673840654d9eea846d4848a8c844 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 13 Jan 2025 17:36:30 +0100 Subject: [PATCH 06/30] WIP --- .../tum/cit/aet/artemis/lecture/web/LectureResource.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index 741914400076..f993ec6253f6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -51,7 +51,6 @@ import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; import de.tum.cit.aet.artemis.lecture.service.LectureImportService; import de.tum.cit.aet.artemis.lecture.service.LectureService; @@ -311,13 +310,13 @@ public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @R if (lectureId.isPresent()) { Optional lectureToIngest = course.getLectures().stream().filter(lecture -> lecture.getId().equals(lectureId.get())).findFirst(); if (lectureToIngest.isPresent()) { - Set lecturesToIngest = new HashSet<>(lectureToIngest.get().getTranscriptions()); - lectureService.ingestTranscriptionInPyris(lecturesToIngest); + // Set lecturesToIngest = new HashSet<>(lectureToIngest.get().getTranscriptions()); + // lectureService.ingestTranscriptionInPyris(lecturesToIngest); return ResponseEntity.ok().build(); } return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.allLecturesError", "idExists")).body(null); } - lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); + // lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); return ResponseEntity.ok().build(); } From cad2db6c2b00a7d91d7829bc13f16aba3c88f867 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 14 Jan 2025 15:59:41 +0100 Subject: [PATCH 07/30] Add transcription resource --- .../artemis/lecture/domain/Transcription.java | 32 ++++++--- .../lecture/domain/TranscriptionSegment.java | 54 +++++++++++++- .../artemis/lecture/dto/TranscriptionDTO.java | 9 +++ .../lecture/dto/TranscriptionSegmentDTO.java | 7 ++ .../repository/TranscriptionRepository.java | 19 ++++- .../artemis/lecture/web/LectureResource.java | 7 +- .../lecture/web/TranscriptionResource.java | 72 +++++++++++++++++++ .../changelog/20250113154600_changelog.xml | 2 + .../app/iris/iris-transcription.service.ts | 12 ++-- ...ure-transcription-ingestion.component.html | 6 +- ...cture-transcription-ingestion.component.ts | 11 +-- 11 files changed, 202 insertions(+), 29 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionDTO.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionSegmentDTO.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java index 1c05126bb5b4..f9b90ae6b3a4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java @@ -8,39 +8,40 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderColumn; +import jakarta.persistence.OrderBy; import jakarta.persistence.Table; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; +import com.fasterxml.jackson.annotation.JsonIgnore; + import de.tum.cit.aet.artemis.core.domain.DomainObject; @Entity @Table(name = "transcription") public class Transcription extends DomainObject { - private String language; - @ManyToOne @JoinColumn(name = "lecture_id") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @JsonIgnore private Lecture lecture; + private String language; + @OneToMany(cascade = CascadeType.ALL) - @OrderColumn(name = "start_time") + @OrderBy("startTime asc") @JoinColumn(name = "transcription_id") private List segments = new ArrayList<>(); public Transcription() { } - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { + public Transcription(Lecture lecture, String language, List segments) { + this.lecture = lecture; this.language = language; + this.segments = segments; } public Lecture getLecture() { @@ -51,6 +52,14 @@ public void setLecture(Lecture lecture) { this.lecture = lecture; } + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + public List getSegments() { return segments; } @@ -58,4 +67,9 @@ public List getSegments() { public void setSegments(List segments) { this.segments = segments; } + + @Override + public String toString() { + return "Transcription [language=" + language + ", segments=" + segments + "]"; + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java index 05d11a38fc3d..cc88106efa4e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java @@ -10,6 +10,8 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; +import com.fasterxml.jackson.annotation.JsonIgnore; + import de.tum.cit.aet.artemis.core.domain.DomainObject; @Entity @@ -29,15 +31,65 @@ public class TranscriptionSegment extends DomainObject { @ManyToOne @JoinColumn(name = "lecture_unit_id") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @JsonIgnore private LectureUnit lectureUnit; + @Column(name = "slide_number") + private int slideNumber; + public TranscriptionSegment() { } - public TranscriptionSegment(Double startTime, Double endTime, String text, LectureUnit lectureUnit) { + public TranscriptionSegment(Double startTime, Double endTime, String text, LectureUnit lectureUnit, int slideNumber) { + this.startTime = startTime; + this.endTime = endTime; + this.text = text; + this.lectureUnit = lectureUnit; + this.slideNumber = slideNumber; + } + + public Double getStartTime() { + return startTime; + } + + public void setStartTime(Double startTime) { this.startTime = startTime; + } + + public Double getEndTime() { + return endTime; + } + + public void setEndTime(Double endTime) { this.endTime = endTime; + } + + public String getText() { + return text; + } + + public void setText(String text) { this.text = text; + } + + public LectureUnit getLectureUnit() { + return lectureUnit; + } + + public void setLectureUnit(LectureUnit lectureUnit) { this.lectureUnit = lectureUnit; } + + public int getSlideNumber() { + return slideNumber; + } + + public void setSlideNumber(int slideNumber) { + this.slideNumber = slideNumber; + } + + @Override + public String toString() { + return "TranscriptionSegment [startTime = " + startTime + ", endTime = " + endTime + ", text = " + text + "]"; + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionDTO.java b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionDTO.java new file mode 100644 index 000000000000..dd41a0f4f468 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionDTO.java @@ -0,0 +1,9 @@ +package de.tum.cit.aet.artemis.lecture.dto; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record TranscriptionDTO(Long lectureId, String language, List segments) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionSegmentDTO.java b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionSegmentDTO.java new file mode 100644 index 000000000000..fb7cc442a266 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionSegmentDTO.java @@ -0,0 +1,7 @@ +package de.tum.cit.aet.artemis.lecture.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record TranscriptionSegmentDTO(Double startTime, Double endTime, String text, int slideNumber, Long lectureUnitId) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java index cec75ada3059..b1428ed1b21d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java @@ -2,6 +2,7 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import java.util.List; import java.util.Optional; import org.springframework.context.annotation.Profile; @@ -9,6 +10,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; import de.tum.cit.aet.artemis.lecture.domain.Transcription; /** @@ -16,13 +18,24 @@ */ @Profile(PROFILE_CORE) @Repository -public interface TranscriptionRepository { +public interface TranscriptionRepository extends ArtemisJpaRepository { @Query(""" SELECT t FROM Transcription t - WHERE t.id = :textUnitId + WHERE t.lecture.id = :lectureId """) - Optional findById(@Param("lectureId") Long lectureId); + Optional findByLectureId(@Param("lectureId") Long lectureId); + + @Query(""" + SELECT t + FROM Transcription t + WHERE t.lecture.id = :lectureId + """) + List findAllByLectureId(@Param("lectureId") Long lectureId); + + default Transcription findByIdOrElseThrow(Long id) { + return getValueElseThrow(findById(id), id); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index f993ec6253f6..741914400076 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -51,6 +51,7 @@ import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; +import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; import de.tum.cit.aet.artemis.lecture.service.LectureImportService; import de.tum.cit.aet.artemis.lecture.service.LectureService; @@ -310,13 +311,13 @@ public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @R if (lectureId.isPresent()) { Optional lectureToIngest = course.getLectures().stream().filter(lecture -> lecture.getId().equals(lectureId.get())).findFirst(); if (lectureToIngest.isPresent()) { - // Set lecturesToIngest = new HashSet<>(lectureToIngest.get().getTranscriptions()); - // lectureService.ingestTranscriptionInPyris(lecturesToIngest); + Set lecturesToIngest = new HashSet<>(lectureToIngest.get().getTranscriptions()); + lectureService.ingestTranscriptionInPyris(lecturesToIngest); return ResponseEntity.ok().build(); } return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.allLecturesError", "idExists")).body(null); } - // lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); + lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); return ResponseEntity.ok().build(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java new file mode 100644 index 000000000000..074cccb0a49d --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java @@ -0,0 +1,72 @@ +package de.tum.cit.aet.artemis.lecture.web; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; +import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; +import de.tum.cit.aet.artemis.lecture.domain.Lecture; +import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; +import de.tum.cit.aet.artemis.lecture.domain.Transcription; +import de.tum.cit.aet.artemis.lecture.domain.TranscriptionSegment; +import de.tum.cit.aet.artemis.lecture.dto.TranscriptionDTO; +import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; +import de.tum.cit.aet.artemis.lecture.repository.TranscriptionRepository; + +@Profile(PROFILE_CORE) +@RestController +@RequestMapping("api/") +public class TranscriptionResource { + + private static final Logger log = LoggerFactory.getLogger(TranscriptionResource.class); + + private final TranscriptionRepository transcriptionRepository; + + private final LectureRepository lectureRepository; + + private final LectureUnitRepository lectureUnitRepository; + + public TranscriptionResource(TranscriptionRepository transcriptionRepository, LectureRepository lectureRepository, LectureUnitRepository lectureUnitRepository) { + this.transcriptionRepository = transcriptionRepository; + this.lectureRepository = lectureRepository; + this.lectureUnitRepository = lectureUnitRepository; + } + + /** + * POST /transcription : Create a new transcription. + * + * @param transcriptionDTO the transcription object to create + * @return the ResponseEntity with status 201 (Created) and with body the new transcription, or with status 400 (Bad Request) if the transcription has already an ID + * @throws URISyntaxException if the Location URI syntax is incorrect + */ + @PostMapping(value = "transcription") + @EnforceAtLeastEditor + public ResponseEntity createTranscription(@RequestBody TranscriptionDTO transcriptionDTO) throws URISyntaxException { + Lecture lecture = lectureRepository.findById(transcriptionDTO.lectureId()).orElseThrow(() -> new EntityNotFoundException("no lecture found for this id")); + + List segments = transcriptionDTO.segments().stream().map(segment -> { + LectureUnit lectureUnit = lectureUnitRepository.findById(segment.lectureUnitId()).orElseThrow(() -> new EntityNotFoundException("no lecture unit found for this id")); + return new TranscriptionSegment(segment.startTime(), segment.endTime(), segment.text(), lectureUnit, segment.slideNumber()); + }).toList(); + + Transcription transcription = new Transcription(lecture, transcriptionDTO.language(), segments); + transcription.setId(null); + + Transcription result = transcriptionRepository.save(transcription); + + return ResponseEntity.created(new URI("/api/transcriptions/" + result.getId())).body(result); + } +} diff --git a/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml index fec68d59e5c6..de5ce1a1ea2f 100644 --- a/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml @@ -32,6 +32,8 @@ type="DOUBLE"/> + { - console.log('inserting123'); - console.log(`${this.resourceURL}/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`); return this.httpClient.put( - `${this.resourceURL}/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`, + `api/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`, {}, { responseType: 'text', }, ); } + + createTranscription(transcription: any): Observable { + return this.httpClient.post(`api/transcription`, transcription, { + responseType: 'text', + }); + } } diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html index 4d0a5ad37831..607ad74ec987 100644 --- a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html +++ b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html @@ -2,11 +2,11 @@

Ingest transcription by lecture id

- +

Insert transcription into DB

- - + +
diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts index d6a830b33928..0a3ac26e08b2 100644 --- a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts +++ b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts @@ -12,16 +12,17 @@ export class LectureTranscriptionIngestionComponent { public courseId: number; lectureIdInput = ''; - - isIngestionLoading = false; + transcriptionInput = ''; faCheck = faCheck; constructor(private irisTranscriptionService: IrisTranscriptionService) {} ingestTranscription(): void { - this.irisTranscriptionService.ingestTranscription(this.courseId, Number(this.lectureIdInput)).subscribe((response) => { - console.log('Response received:', response); - }); + this.irisTranscriptionService.ingestTranscription(this.courseId, Number(this.lectureIdInput)).subscribe(() => {}); + } + + createTranscription(): void { + this.irisTranscriptionService.createTranscription(JSON.parse(this.transcriptionInput)).subscribe(() => {}); } } From d6ee26ee171ee08be2633ca9d21feb4c39b7e2f4 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Thu, 16 Jan 2025 16:46:15 +0100 Subject: [PATCH 08/30] Create custom path for lecture transcription ingestion --- .../artemis/lecture/domain/Transcription.java | 3 ++- .../lecture/repository/LectureRepository.java | 9 +++++++ .../repository/TranscriptionRepository.java | 11 +++++++- .../artemis/lecture/web/LectureResource.java | 20 ++++++++------ src/main/webapp/app/admin/admin.route.ts | 8 ++++++ ...-transcription-ingestion-routing.module.ts | 27 +++++++++++++++++++ ...ure-transcription-ingestion.component.html | 17 ++++++++++++ ...ure-transcription-ingestion.component.scss | 14 ++++++++++ ...cture-transcription-ingestion.component.ts | 4 +++ src/main/webapp/app/iris/iris.module.ts | 2 -- ...ure-transcription-ingestion.component.html | 12 --------- ...ure-transcription-ingestion.component.scss | 3 --- ...iris-course-settings-update.component.html | 5 ---- 13 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts create mode 100644 src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html create mode 100644 src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss rename src/main/webapp/app/{iris => admin}/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts (81%) delete mode 100644 src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html delete mode 100644 src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java index f9b90ae6b3a4..b8dc379472fa 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java @@ -5,6 +5,7 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; @@ -30,7 +31,7 @@ public class Transcription extends DomainObject { private String language; - @OneToMany(cascade = CascadeType.ALL) + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OrderBy("startTime asc") @JoinColumn(name = "transcription_id") private List segments = new ArrayList<>(); diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java index e441c1633b23..213f9814c0b1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java @@ -120,6 +120,15 @@ public interface LectureRepository extends ArtemisJpaRepository { """) Optional findByIdWithLectureUnitsAndSlidesAndAttachments(@Param("lectureId") long lectureId); + @Query(""" + SELECT lecture + FROM Lecture lecture + LEFT JOIN FETCH lecture.transcriptions transcriptions + LEFT JOIN FETCH lecture.transcriptions.segments segments + WHERE lecture.id = :lectureId + """) + Optional findByIdWithTranscriptionsWithSegments(@Param("lectureId") long lectureId); + @Query(""" SELECT lecture FROM Lecture lecture diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java index b1428ed1b21d..d1ef215d4dd3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java @@ -23,9 +23,18 @@ public interface TranscriptionRepository extends ArtemisJpaRepository findByIdWithSegments(@Param("id") Long id); + + @Query(""" + SELECT t + FROM Transcription t + LEFT JOIN FETCH t.segments WHERE t.lecture.id = :lectureId """) - Optional findByLectureId(@Param("lectureId") Long lectureId); + List findAllByLectureIdWithSegments(@Param("lectureId") Long lectureId); @Query(""" SELECT t diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index 741914400076..2de05e508de5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -53,6 +53,7 @@ import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; +import de.tum.cit.aet.artemis.lecture.repository.TranscriptionRepository; import de.tum.cit.aet.artemis.lecture.service.LectureImportService; import de.tum.cit.aet.artemis.lecture.service.LectureService; @@ -70,6 +71,8 @@ public class LectureResource { private final CompetencyApi competencyApi; + private final TranscriptionRepository transcriptionRepository; + @Value("${jhipster.clientApp.name}") private String applicationName; @@ -93,7 +96,7 @@ public class LectureResource { public LectureResource(LectureRepository lectureRepository, LectureService lectureService, LectureImportService lectureImportService, CourseRepository courseRepository, UserRepository userRepository, AuthorizationCheckService authCheckService, ExerciseService exerciseService, ChannelService channelService, - ChannelRepository channelRepository, CompetencyApi competencyApi) { + ChannelRepository channelRepository, CompetencyApi competencyApi, TranscriptionRepository transcriptionRepository) { this.lectureRepository = lectureRepository; this.lectureService = lectureService; this.lectureImportService = lectureImportService; @@ -104,6 +107,7 @@ public LectureResource(LectureRepository lectureRepository, LectureService lectu this.channelService = channelService; this.channelRepository = channelRepository; this.competencyApi = competencyApi; + this.transcriptionRepository = transcriptionRepository; } /** @@ -309,15 +313,15 @@ public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @R Course course = courseRepository.findByIdWithLecturesAndLectureUnitsElseThrow(courseId); authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); if (lectureId.isPresent()) { - Optional lectureToIngest = course.getLectures().stream().filter(lecture -> lecture.getId().equals(lectureId.get())).findFirst(); - if (lectureToIngest.isPresent()) { - Set lecturesToIngest = new HashSet<>(lectureToIngest.get().getTranscriptions()); - lectureService.ingestTranscriptionInPyris(lecturesToIngest); - return ResponseEntity.ok().build(); + List transcriptions = transcriptionRepository.findAllByLectureIdWithSegments(lectureId.get()); + if (transcriptions.isEmpty()) { + return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.allLecturesError", "idExists")).body(null); } - return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.allLecturesError", "idExists")).body(null); + Set transcriptionsToIngest = new HashSet<>(transcriptions); + lectureService.ingestTranscriptionInPyris(transcriptionsToIngest); + return ResponseEntity.ok().build(); } - lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); + // lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); return ResponseEntity.ok().build(); } diff --git a/src/main/webapp/app/admin/admin.route.ts b/src/main/webapp/app/admin/admin.route.ts index 992b20b48c2b..364732856bee 100644 --- a/src/main/webapp/app/admin/admin.route.ts +++ b/src/main/webapp/app/admin/admin.route.ts @@ -22,6 +22,7 @@ import { BuildAgentDetailsComponent } from 'app/localci/build-agents/build-agent import { AdminImportStandardizedCompetenciesComponent } from 'app/admin/standardized-competencies/import/admin-import-standardized-competencies.component'; import { CleanupServiceComponent } from 'app/admin/cleanup-service/cleanup-service.component'; import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard'; +import { LectureTranscriptionIngestionComponent } from 'app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component'; export const adminState: Routes = [ { @@ -158,6 +159,13 @@ export const adminState: Routes = [ pageTitle: 'cleanupService.title', }, }, + { + path: 'lecture-transcription-ingestion', + component: LectureTranscriptionIngestionComponent, + data: { + pageTitle: 'cleanupService.title', + }, + }, ...organizationMgmtRoute, ...userManagementRoute, ...systemNotificationManagementRoute, diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts new file mode 100644 index 000000000000..4454f7c0ec49 --- /dev/null +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { Authority } from 'app/shared/constants/authority.constants'; +import { IrisModule } from 'app/iris/iris.module'; +import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard'; +import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; +import { IrisGuard } from 'app/iris/iris-guard.service'; +import { LectureTranscriptionIngestionComponent } from 'app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component'; + +const routes: Routes = [ + { + path: 'lecture-transcription-ingestion', + component: LectureTranscriptionIngestionComponent, + data: { + authorities: [Authority.INSTRUCTOR, Authority.ADMIN], + pageTitle: 'artemisApp.iris.settings.title.course', + }, + canActivate: [UserRouteAccessService, IrisGuard], + canDeactivate: [PendingChangesGuard], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes), IrisModule], + exports: [RouterModule], +}) +export class LectureTranscriptionIngestionRoutingModule {} diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html new file mode 100644 index 000000000000..a13e41dbdb49 --- /dev/null +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html @@ -0,0 +1,17 @@ +
+

Lecture Transcription

+
+

Ingest

+

Ingest transcription into Pyris Weaviate DB

+ + +
+
+

Create

+

Create transcription into Artemis DB

+
+ + +
+
+
diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss new file mode 100644 index 000000000000..af9f62445580 --- /dev/null +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss @@ -0,0 +1,14 @@ +.insert-db-container { + margin-top: 1rem; +} + +.insert-transcription-row { + display: flex; + align-items: center; +} + +.insert-transcription-area { + width: min(40rem, 100%); + height: 5rem; + margin-right: 1rem; +} diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts similarity index 81% rename from src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts rename to src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts index 0a3ac26e08b2..3d0466ce6c13 100644 --- a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts @@ -1,11 +1,15 @@ import { Component, Input } from '@angular/core'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; import { IrisTranscriptionService } from 'app/iris/iris-transcription.service'; +import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; +import { FormsModule } from '@angular/forms'; @Component({ selector: 'jhi-lecture-transcription-ingestion', templateUrl: './lecture-transcription-ingestion.component.html', styleUrl: './lecture-transcription-ingestion.component.scss', + standalone: true, + imports: [ArtemisSharedComponentModule, FormsModule], }) export class LectureTranscriptionIngestionComponent { @Input() diff --git a/src/main/webapp/app/iris/iris.module.ts b/src/main/webapp/app/iris/iris.module.ts index 59d6b7f32ea7..844b5d97c3af 100644 --- a/src/main/webapp/app/iris/iris.module.ts +++ b/src/main/webapp/app/iris/iris.module.ts @@ -21,7 +21,6 @@ import { IrisBaseChatbotComponent } from 'app/iris/base-chatbot/iris-base-chatbo import { ChatStatusBarComponent } from 'app/iris/base-chatbot/chat-status-bar/chat-status-bar.component'; import { CourseChatbotComponent } from 'app/iris/course-chatbot/course-chatbot.component'; import { IrisLogoComponent } from 'app/iris/iris-logo/iris-logo.component'; -import { LectureTranscriptionIngestionComponent } from 'app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component'; @NgModule({ declarations: [ @@ -37,7 +36,6 @@ import { LectureTranscriptionIngestionComponent } from 'app/iris/lecture-transcr ChatStatusBarComponent, IrisLogoButtonComponent, CourseChatbotComponent, - LectureTranscriptionIngestionComponent, ], imports: [ CommonModule, diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html deleted file mode 100644 index 607ad74ec987..000000000000 --- a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
-

Ingest transcription by lecture id

- - -
-
-

Insert transcription into DB

- - -
-
diff --git a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss b/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss deleted file mode 100644 index fad257e7d2fe..000000000000 --- a/src/main/webapp/app/iris/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.insert-db-container { - margin-top: 1rem; -} diff --git a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html index 0d4839cd92d3..43df077bdd28 100644 --- a/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-course-settings-update/iris-course-settings-update.component.html @@ -3,9 +3,4 @@

@if (courseId) { } - -
- -

Lecture Transcription

- From 4ed75e20cc4b9df1823f048332fd59274a0eb999 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 20 Jan 2025 11:33:26 +0100 Subject: [PATCH 09/30] Remove host from package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 914f2fe2b9f2..d392de262aa4 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "prepare": "husky", "prettier:check": "prettier --check \"src/{main/webapp,test}/**/*.{json,ts,js,css,scss,html}\"", "prettier:write": "prettier --write \"src/{main/webapp,test}/**/*.{json,ts,js,css,scss,html}\"", - "start": "npm run prebuild -- --develop && ng serve --hmr --host 0.0.0.0", + "start": "npm run prebuild -- --develop && ng serve --hmr", "test-diff:ci": "git fetch origin develop && npm run prebuild && ng test --log-heap-usage -w=4 --ci --reporters=default --reporters=jest-junit --pass-with-no-tests --changed-since=origin/develop", "test-diff": "npm run prebuild && ng test --log-heap-usage -w=4 --pass-with-no-tests --changed-since=origin/develop --coverage", "test:ci": "npm run prebuild && ng test --coverage --log-heap-usage -w=4 --ci --reporters=default --reporters=jest-junit", From ca477108a0736a55c460427e9b033b77e3d934c3 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 20 Jan 2025 12:00:05 +0100 Subject: [PATCH 10/30] Fix lecture transcription ingestion route --- src/main/webapp/app/admin/admin.routes.ts | 7 +++++ ...-transcription-ingestion-routing.module.ts | 27 ------------------- 2 files changed, 7 insertions(+), 27 deletions(-) delete mode 100644 src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts diff --git a/src/main/webapp/app/admin/admin.routes.ts b/src/main/webapp/app/admin/admin.routes.ts index a069869a089c..c206268bda62 100644 --- a/src/main/webapp/app/admin/admin.routes.ts +++ b/src/main/webapp/app/admin/admin.routes.ts @@ -153,6 +153,13 @@ const routes: Routes = [ pageTitle: 'cleanupService.title', }, }, + { + path: 'lecture-transcription-ingestion', + loadComponent: () => import('app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component').then((m) => m.LectureTranscriptionIngestionComponent), + data: { + authorities: [Authority.ADMIN], + }, + }, ...organizationMgmtRoute, ...userManagementRoute, ...systemNotificationManagementRoute, diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts deleted file mode 100644 index 4454f7c0ec49..000000000000 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion-routing.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { Authority } from 'app/shared/constants/authority.constants'; -import { IrisModule } from 'app/iris/iris.module'; -import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard'; -import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; -import { IrisGuard } from 'app/iris/iris-guard.service'; -import { LectureTranscriptionIngestionComponent } from 'app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component'; - -const routes: Routes = [ - { - path: 'lecture-transcription-ingestion', - component: LectureTranscriptionIngestionComponent, - data: { - authorities: [Authority.INSTRUCTOR, Authority.ADMIN], - pageTitle: 'artemisApp.iris.settings.title.course', - }, - canActivate: [UserRouteAccessService, IrisGuard], - canDeactivate: [PendingChangesGuard], - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes), IrisModule], - exports: [RouterModule], -}) -export class LectureTranscriptionIngestionRoutingModule {} From 7e84063e87f3bce8618c0f83f62408877b43c3d0 Mon Sep 17 00:00:00 2001 From: isabella Date: Mon, 20 Jan 2025 12:09:02 +0100 Subject: [PATCH 11/30] cleanup code backend --- .../service/pyris/PyrisConnectorService.java | 6 +++--- .../iris/service/pyris/PyrisJobService.java | 3 ++- .../service/pyris/PyrisWebhookService.java | 7 +------ .../job/TranscriptionIngestionWebhookJob.java | 21 +++++++++++++++++++ .../iris/web/IrisTranscriptionResource.java | 2 +- .../lecture/service/LectureService.java | 2 +- 6 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/job/TranscriptionIngestionWebhookJob.java diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java index aea4af4ade58..50f711e1f43e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java @@ -115,17 +115,17 @@ public void executePipeline(String feature, String variant, Object executionDTO, * @param variant The variant of the feature to execute * @param executionDTO The DTO sent as a body for the execution */ - public void executeTranscriptionAddtionWebhook(String variant, PyrisWebhookTranscriptionIngestionExecutionDTO executionDTO) { + public void executeTranscriptionAdditionWebhook(String variant, PyrisWebhookTranscriptionIngestionExecutionDTO executionDTO) { var endpoint = "/api/v1/webhooks/transcriptions/" + variant; try { restTemplate.postForEntity(pyrisUrl + endpoint, objectMapper.valueToTree(executionDTO), Void.class); } catch (HttpStatusCodeException e) { - log.error("Failed to send lecture unit {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); + log.error("Failed to send transcription {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); throw toIrisException(e); } catch (RestClientException | IllegalArgumentException e) { - log.error("Failed to send lecture unit {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); + log.error("Failed to send transcription {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); throw new PyrisConnectorException("Could not fetch response from Pyris"); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java index f42ee8f00382..c92a50a8e752 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisJobService.java @@ -24,6 +24,7 @@ import de.tum.cit.aet.artemis.iris.service.pyris.job.ExerciseChatJob; import de.tum.cit.aet.artemis.iris.service.pyris.job.IngestionWebhookJob; import de.tum.cit.aet.artemis.iris.service.pyris.job.PyrisJob; +import de.tum.cit.aet.artemis.iris.service.pyris.job.TranscriptionIngestionWebhookJob; /** * The PyrisJobService class is responsible for managing Pyris jobs in the Artemis system. @@ -117,7 +118,7 @@ public String addIngestionWebhookJob(long courseId, long lectureId, long lecture */ public String addTranscriptionIngestionWebhookJob(long courseId, long lectureId) { var token = generateJobIdToken(); - var job = new IngestionWebhookJob(token, courseId, lectureId, 0); // TODO: check whether this job can be reused + var job = new TranscriptionIngestionWebhookJob(token, courseId, lectureId); long timeoutWebhookJob = 60; TimeUnit unitWebhookJob = TimeUnit.MINUTES; jobMap.put(token, job, timeoutWebhookJob, unitWebhookJob); diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java index 59e1dd1e1b19..e420c8aad499 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java @@ -72,11 +72,6 @@ public PyrisWebhookService(PyrisConnectorService pyrisConnectorService, PyrisJob this.lectureRepository = lectureRepository; } - private boolean transcriptionIngestionEnabled(Course course) { - // WIP - return true; - } - /** * adds the transcription to the vector database in Pyris * @@ -100,7 +95,7 @@ private String executeTranscriptionAdditionWebhook(PyrisTranscriptionIngestionWe String jobToken = pyrisJobService.addTranscriptionIngestionWebhookJob(toUpdateTranscription.courseId(), toUpdateTranscription.lectureId()); PyrisPipelineExecutionSettingsDTO settingsDTO = new PyrisPipelineExecutionSettingsDTO(jobToken, List.of(), artemisBaseUrl); PyrisWebhookTranscriptionIngestionExecutionDTO executionDTO = new PyrisWebhookTranscriptionIngestionExecutionDTO(toUpdateTranscription, settingsDTO, List.of()); - pyrisConnectorService.executeTranscriptionAddtionWebhook("fullIngestion", executionDTO); + pyrisConnectorService.executeTranscriptionAdditionWebhook("fullIngestion", executionDTO); return jobToken; } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/job/TranscriptionIngestionWebhookJob.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/job/TranscriptionIngestionWebhookJob.java new file mode 100644 index 000000000000..ad96ae9bd416 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/job/TranscriptionIngestionWebhookJob.java @@ -0,0 +1,21 @@ +package de.tum.cit.aet.artemis.iris.service.pyris.job; + +import de.tum.cit.aet.artemis.core.domain.Course; +import de.tum.cit.aet.artemis.exercise.domain.Exercise; + +/** + * An implementation of a PyrisJob for Transcription Ingestion in Pyris. + * This job is used to reference the details of then Ingestion when Pyris sends a status update. + */ +public record TranscriptionIngestionWebhookJob(String jobId, long courseId, long lectureId) implements PyrisJob { + + @Override + public boolean canAccess(Course course) { + return false; + } + + @Override + public boolean canAccess(Exercise exercise) { + return false; + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java index 15e3844aa93c..a77572299f50 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java @@ -31,7 +31,7 @@ @RequestMapping("api/iris/transcription-ingestion") public class IrisTranscriptionResource { - private static final Logger log = LoggerFactory.getLogger(IrisResource.class); + private static final Logger log = LoggerFactory.getLogger(IrisTranscriptionResource.class); protected final UserRepository userRepository; diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java index b229e396e0cd..dc88a6be2b1d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java @@ -196,7 +196,7 @@ public void ingestLecturesInPyris(Set lectures) { public void ingestTranscriptionInPyris(Set transcriptions) { if (pyrisWebhookService.isPresent()) { for (Transcription transcription : transcriptions) { - pyrisWebhookService.get().addTranscriptionToPyrisDB(transcription); + String jobToken = pyrisWebhookService.get().addTranscriptionToPyrisDB(transcription); } } } From 1b24721105f1080082d47e2f15ca63bd496a9330 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 20 Jan 2025 13:29:17 +0100 Subject: [PATCH 12/30] Add client tests --- ...ure-transcription-ingestion.component.html | 7 ++- ...cture-transcription-ingestion.component.ts | 20 ++++--- .../lecture-transcription.service.ts} | 2 +- ...-transcription-ingestion.component.spec.ts | 57 +++++++++++++++++++ .../lecture-transcription.service.spec.ts | 42 ++++++++++++++ 5 files changed, 116 insertions(+), 12 deletions(-) rename src/main/webapp/app/{iris/iris-transcription.service.ts => admin/lecture-transcription-ingestion/lecture-transcription.service.ts} (94%) create mode 100644 src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts create mode 100644 src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html index a13e41dbdb49..a86083c300d5 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html @@ -3,15 +3,16 @@

Lecture Transcription

Ingest

Ingest transcription into Pyris Weaviate DB

- - + + +

Create

Create transcription into Artemis DB

- +
diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts index 3d0466ce6c13..4ebc394ce2ad 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts @@ -1,8 +1,8 @@ -import { Component, Input } from '@angular/core'; +import { Component } from '@angular/core'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; -import { IrisTranscriptionService } from 'app/iris/iris-transcription.service'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { FormsModule } from '@angular/forms'; +import { LectureTranscriptionService } from 'app/admin/lecture-transcription-ingestion/lecture-transcription.service'; @Component({ selector: 'jhi-lecture-transcription-ingestion', @@ -12,21 +12,25 @@ import { FormsModule } from '@angular/forms'; imports: [ArtemisSharedComponentModule, FormsModule], }) export class LectureTranscriptionIngestionComponent { - @Input() - public courseId: number; - + courseIdInput = ''; lectureIdInput = ''; + transcriptionInput = ''; faCheck = faCheck; - constructor(private irisTranscriptionService: IrisTranscriptionService) {} + constructor(private lectureTranscriptionService: LectureTranscriptionService) {} ingestTranscription(): void { - this.irisTranscriptionService.ingestTranscription(this.courseId, Number(this.lectureIdInput)).subscribe(() => {}); + this.lectureTranscriptionService.ingestTranscription(Number(this.courseIdInput), Number(this.lectureIdInput)).subscribe(() => { + this.courseIdInput = ''; + this.lectureIdInput = ''; + }); } createTranscription(): void { - this.irisTranscriptionService.createTranscription(JSON.parse(this.transcriptionInput)).subscribe(() => {}); + this.lectureTranscriptionService.createTranscription(JSON.parse(this.transcriptionInput)).subscribe(() => { + this.transcriptionInput = ''; + }); } } diff --git a/src/main/webapp/app/iris/iris-transcription.service.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts similarity index 94% rename from src/main/webapp/app/iris/iris-transcription.service.ts rename to src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts index 77475c280381..6fa779d497d5 100644 --- a/src/main/webapp/app/iris/iris-transcription.service.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts @@ -3,7 +3,7 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) -export class IrisTranscriptionService { +export class LectureTranscriptionService { constructor(private httpClient: HttpClient) {} ingestTranscription(courseId: number, lectureId: number): Observable { diff --git a/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts new file mode 100644 index 000000000000..e095658c81b7 --- /dev/null +++ b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts @@ -0,0 +1,57 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ArtemisTestModule } from '../../../test.module'; +import { LectureTranscriptionIngestionComponent } from 'app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component'; +import { LectureTranscriptionService } from 'app/admin/lecture-transcription-ingestion/lecture-transcription.service'; +import { By } from '@angular/platform-browser'; +import { of } from 'rxjs'; + +describe('LectureTranscriptionIngestionComponent', () => { + let comp: LectureTranscriptionIngestionComponent; + let fixture: ComponentFixture; + let lectureTranscriptionService: LectureTranscriptionService; + + beforeEach(() => { + const mockLectureTranscriptionService = { + ingestTranscription: jest.fn().mockReturnValue(of({})), + createTranscription: jest.fn().mockReturnValue(of({})), + }; + + TestBed.configureTestingModule({ + imports: [ArtemisTestModule, LectureTranscriptionIngestionComponent], + providers: [{ provide: LectureTranscriptionService, useValue: mockLectureTranscriptionService }], + }).compileComponents(); + + fixture = TestBed.createComponent(LectureTranscriptionIngestionComponent); + comp = fixture.componentInstance; + lectureTranscriptionService = TestBed.inject(LectureTranscriptionService); + }); + + it('should call ingestTranscription with correct parameters when ingest button is clicked', waitForAsync(() => { + const courseId = '1'; + comp.courseIdInput = courseId; + + const lectureId = '1'; + comp.lectureIdInput = lectureId; + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('#ingest-transcription-button')); + + button.triggerEventHandler('onClick', null); + fixture.detectChanges(); + + expect(lectureTranscriptionService.ingestTranscription).toHaveBeenCalledWith(Number(courseId), Number(lectureId)); + })); + + it('should call createTranscription with correct parameters when create transcription button is clicked', waitForAsync(() => { + const transcription = '{ "transcription": [] }'; + comp.transcriptionInput = transcription; + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('#create-transcription-button')); + + button.triggerEventHandler('onClick', null); + fixture.detectChanges(); + + expect(lectureTranscriptionService.createTranscription).toHaveBeenCalledWith(JSON.parse(transcription)); + })); +}); diff --git a/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts new file mode 100644 index 000000000000..1b0d9521c081 --- /dev/null +++ b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts @@ -0,0 +1,42 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { LectureTranscriptionService } from 'app/admin/lecture-transcription-ingestion/lecture-transcription.service'; + +describe('LectureTranscriptionService', () => { + let service: LectureTranscriptionService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [provideHttpClient(), provideHttpClientTesting()], + }); + + service = TestBed.inject(LectureTranscriptionService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + it('should send PUT request to ingest transcription', () => { + const courseId = 1; + const lectureId = 1; + service.ingestTranscription(courseId, lectureId).subscribe(() => {}); + + const req = httpMock.expectOne({ method: 'PUT', url: `api/courses/${courseId}/ingest-transcription?lectureId=${lectureId}` }); + req.flush({}); + }); + + it('should send POST request to create transcription', () => { + const transcription = { transcription: [] }; + service.createTranscription(transcription).subscribe(() => {}); + + const req = httpMock.expectOne({ method: 'POST', url: `api/transcription` }); + + expect(req.request.body).toBe(transcription); + req.flush({}); + }); +}); From a22949a192b4f333200718b950da78148cf820e7 Mon Sep 17 00:00:00 2001 From: isabella Date: Mon, 20 Jan 2025 14:43:35 +0100 Subject: [PATCH 13/30] add server test --- .../connector/IrisRequestMockProvider.java | 10 ++ .../iris/PyrisTranscriptionIngestionTest.java | 91 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java diff --git a/src/test/java/de/tum/cit/aet/artemis/core/connector/IrisRequestMockProvider.java b/src/test/java/de/tum/cit/aet/artemis/core/connector/IrisRequestMockProvider.java index db8e032aca21..fe6c150d1b7f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/connector/IrisRequestMockProvider.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/connector/IrisRequestMockProvider.java @@ -37,6 +37,7 @@ import de.tum.cit.aet.artemis.iris.service.pyris.dto.chat.textexercise.PyrisTextExerciseChatPipelineExecutionDTO; import de.tum.cit.aet.artemis.iris.service.pyris.dto.competency.PyrisCompetencyExtractionPipelineExecutionDTO; import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisWebhookLectureIngestionExecutionDTO; +import de.tum.cit.aet.artemis.iris.service.pyris.dto.transcriptionIngestion.PyrisWebhookTranscriptionIngestionExecutionDTO; @Component @Profile(PROFILE_IRIS) @@ -171,6 +172,15 @@ public void mockIngestionWebhookRunResponse(Consumer responseConsumer) { + mockServer.expect(ExpectedCount.once(), requestTo(webhooksApiURL + "/transcriptions/fullIngestion")).andExpect(method(HttpMethod.POST)).andRespond(request -> { + var mockRequest = (MockClientHttpRequest) request; + var dto = mapper.readValue(mockRequest.getBodyAsString(), PyrisWebhookTranscriptionIngestionExecutionDTO.class); + responseConsumer.accept(dto); + return MockRestResponseCreators.withRawStatus(HttpStatus.ACCEPTED.value()).createResponse(request); + }); + } + public void mockDeletionWebhookRunResponse(Consumer responseConsumer) { mockServer.expect(ExpectedCount.once(), requestTo(webhooksApiURL + "/lectures/delete")).andExpect(method(HttpMethod.POST)).andRespond(request -> { var mockRequest = (MockClientHttpRequest) request; diff --git a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java new file mode 100644 index 000000000000..8dde0c439c28 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java @@ -0,0 +1,91 @@ +package de.tum.cit.aet.artemis.iris; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.test.context.support.WithMockUser; + +import de.tum.cit.aet.artemis.core.domain.Course; +import de.tum.cit.aet.artemis.core.user.util.UserUtilService; +import de.tum.cit.aet.artemis.core.util.CourseUtilService; +import de.tum.cit.aet.artemis.lecture.domain.Lecture; +import de.tum.cit.aet.artemis.lecture.domain.Transcription; +import de.tum.cit.aet.artemis.lecture.domain.TranscriptionSegment; +import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; +import de.tum.cit.aet.artemis.lecture.repository.TranscriptionRepository; + +public class PyrisTranscriptionIngestionTest extends AbstractIrisIntegrationTest { + + private static final String TEST_PREFIX = "pyristranscriptioningestiontest"; + + @Autowired + private TranscriptionRepository transcriptionRepository; + + @Autowired + private LectureRepository lectureRepository; + + @Autowired + private UserUtilService userUtilService; + + @Autowired + private CourseUtilService courseUtilService; + + private Lecture lecture1; + + @BeforeEach + void initTestCase() throws Exception { + userUtilService.addUsers(TEST_PREFIX, 1, 1, 0, 1); + List courses = courseUtilService.createCoursesWithExercisesAndLectures(TEST_PREFIX, true, true, 1); + Course course1 = this.courseRepository.findByIdWithExercisesAndExerciseDetailsAndLecturesElseThrow(courses.getFirst().getId()); + this.lecture1 = course1.getLectures().stream().findFirst().orElseThrow(); + this.lecture1.setTitle("Lecture " + lecture1.getId()); // needed for search by title + this.lecture1 = lectureRepository.save(this.lecture1); + // Add users that are not in the course + userUtilService.createAndSaveUser(TEST_PREFIX + "student42"); + userUtilService.createAndSaveUser(TEST_PREFIX + "tutor42"); + userUtilService.createAndSaveUser(TEST_PREFIX + "instructor42"); + + TranscriptionSegment segment1 = new TranscriptionSegment(0.0, 12.0, "Welcome to today's lecture", null, 1); + TranscriptionSegment segment2 = new TranscriptionSegment(0.0, 12.0, "Today we will talk about Artemis", null, 1); + Transcription transcription = new Transcription(this.lecture1, "en", List.of(new TranscriptionSegment[] { segment1, segment2 })); + + transcriptionRepository.save(transcription); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testIngestTranscriptionInPyris() throws Exception { + activateIrisFor(lecture1.getCourse()); + irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { + assertThat(dto.settings().authenticationToken()).isNotNull(); + }); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription", Optional.empty(), HttpStatus.OK); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testIngestTranscriptionInPyrisWithLectureId() throws Exception { + activateIrisFor(lecture1.getCourse()); + irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { + assertThat(dto.settings().authenticationToken()).isNotNull(); + }); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription?lectureId=" + lecture1.getId(), Optional.empty(), HttpStatus.OK); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "STUDENT") + void testIngestTranscriptionInPyrisWithoutPermission() throws Exception { + activateIrisFor(lecture1.getCourse()); + irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { + assertThat(dto.settings().authenticationToken()).isNotNull(); + }); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription?lectureId=" + lecture1.getId(), Optional.empty(), HttpStatus.FORBIDDEN); + } + +} From 04b63e92a37d9aaac618b19e649736d2fa5defdf Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 20 Jan 2025 15:03:50 +0100 Subject: [PATCH 14/30] Add status messages --- src/main/webapp/app/admin/admin.routes.ts | 1 + ...cture-transcription-ingestion.component.ts | 22 ++++++++++---- .../lecture-transcription.service.ts | 29 +++++++++++-------- src/main/webapp/i18n/en/global.json | 3 +- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/main/webapp/app/admin/admin.routes.ts b/src/main/webapp/app/admin/admin.routes.ts index c206268bda62..a038967973ab 100644 --- a/src/main/webapp/app/admin/admin.routes.ts +++ b/src/main/webapp/app/admin/admin.routes.ts @@ -158,6 +158,7 @@ const routes: Routes = [ loadComponent: () => import('app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component').then((m) => m.LectureTranscriptionIngestionComponent), data: { authorities: [Authority.ADMIN], + pageTitle: 'global.menu.admin.lectureTranscription', }, }, ...organizationMgmtRoute, diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts index 4ebc394ce2ad..96ad05ff2b56 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts @@ -1,8 +1,9 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { FormsModule } from '@angular/forms'; import { LectureTranscriptionService } from 'app/admin/lecture-transcription-ingestion/lecture-transcription.service'; +import { AlertService } from 'app/core/util/alert.service'; @Component({ selector: 'jhi-lecture-transcription-ingestion', @@ -12,6 +13,9 @@ import { LectureTranscriptionService } from 'app/admin/lecture-transcription-ing imports: [ArtemisSharedComponentModule, FormsModule], }) export class LectureTranscriptionIngestionComponent { + private lectureTranscriptionService = inject(LectureTranscriptionService); + private alertService = inject(AlertService); + courseIdInput = ''; lectureIdInput = ''; @@ -19,17 +23,25 @@ export class LectureTranscriptionIngestionComponent { faCheck = faCheck; - constructor(private lectureTranscriptionService: LectureTranscriptionService) {} - ingestTranscription(): void { - this.lectureTranscriptionService.ingestTranscription(Number(this.courseIdInput), Number(this.lectureIdInput)).subscribe(() => { + this.lectureTranscriptionService.ingestTranscription(Number(this.courseIdInput), Number(this.lectureIdInput)).subscribe((successful) => { + if (successful) { + this.alertService.success('Ingested transcription'); + } else { + this.alertService.error('Unknown error while ingesting transcription'); + } this.courseIdInput = ''; this.lectureIdInput = ''; }); } createTranscription(): void { - this.lectureTranscriptionService.createTranscription(JSON.parse(this.transcriptionInput)).subscribe(() => { + this.lectureTranscriptionService.createTranscription(JSON.parse(this.transcriptionInput)).subscribe((successful) => { + if (successful) { + this.alertService.success('Created transcription'); + } else { + this.alertService.error('Unknown error while creating transcription'); + } this.transcriptionInput = ''; }); } diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts index 6fa779d497d5..d8382c5f1206 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts @@ -1,24 +1,29 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; +import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class LectureTranscriptionService { constructor(private httpClient: HttpClient) {} - ingestTranscription(courseId: number, lectureId: number): Observable { - return this.httpClient.put( - `api/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`, - {}, - { - responseType: 'text', - }, - ); + ingestTranscription(courseId: number, lectureId: number): Observable { + return this.httpClient + .put( + `api/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`, + {}, + { + observe: 'response', + }, + ) + .pipe(map((response) => response.status == 200)); } - createTranscription(transcription: any): Observable { - return this.httpClient.post(`api/transcription`, transcription, { - responseType: 'text', - }); + createTranscription(transcription: any): Observable { + return this.httpClient + .post(`api/transcription`, transcription, { + observe: 'response', + }) + .pipe(map((response) => response.status == 201)); } } diff --git a/src/main/webapp/i18n/en/global.json b/src/main/webapp/i18n/en/global.json index 72739d2d972d..a0850c0b3368 100644 --- a/src/main/webapp/i18n/en/global.json +++ b/src/main/webapp/i18n/en/global.json @@ -117,7 +117,8 @@ "jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)", "lti": "LTI Configuration", "standardizedCompetencies": "Standardized Competencies", - "cleanupService": "Cleanup Service" + "cleanupService": "Cleanup Service", + "lectureTranscription": "Lecture Transcription" }, "language": "Language", "settings": "Settings", From b7333706cadfde60db5b8e4a31253e0d9e3502fe Mon Sep 17 00:00:00 2001 From: isabella Date: Mon, 20 Jan 2025 15:16:39 +0100 Subject: [PATCH 15/30] fix server style --- .../cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java index 8dde0c439c28..e6737b403191 100644 --- a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java @@ -20,7 +20,7 @@ import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; import de.tum.cit.aet.artemis.lecture.repository.TranscriptionRepository; -public class PyrisTranscriptionIngestionTest extends AbstractIrisIntegrationTest { +class PyrisTranscriptionIngestionTest extends AbstractIrisIntegrationTest { private static final String TEST_PREFIX = "pyristranscriptioningestiontest"; From a31455f039da380e16d086cf266be6d9b1a7ee63 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 20 Jan 2025 15:21:22 +0100 Subject: [PATCH 16/30] Remove not used db query --- .../artemis/lecture/repository/LectureRepository.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java index 213f9814c0b1..e441c1633b23 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java @@ -120,15 +120,6 @@ public interface LectureRepository extends ArtemisJpaRepository { """) Optional findByIdWithLectureUnitsAndSlidesAndAttachments(@Param("lectureId") long lectureId); - @Query(""" - SELECT lecture - FROM Lecture lecture - LEFT JOIN FETCH lecture.transcriptions transcriptions - LEFT JOIN FETCH lecture.transcriptions.segments segments - WHERE lecture.id = :lectureId - """) - Optional findByIdWithTranscriptionsWithSegments(@Param("lectureId") long lectureId); - @Query(""" SELECT lecture FROM Lecture lecture From 6f3c8045dcd1c55a363951fe62bf417ed4084e85 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Mon, 20 Jan 2025 15:57:38 +0100 Subject: [PATCH 17/30] Minor changes --- .../service/pyris/PyrisWebhookService.java | 11 +- .../iris/web/IrisTranscriptionResource.java | 111 ------------------ .../lecture/domain/TranscriptionSegment.java | 9 ++ .../artemis/lecture/web/LectureResource.java | 9 +- .../iris/PyrisTranscriptionIngestionTest.java | 9 +- 5 files changed, 33 insertions(+), 116 deletions(-) delete mode 100644 src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java index e420c8aad499..ba72e71a95ad 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java @@ -79,8 +79,17 @@ public PyrisWebhookService(PyrisConnectorService pyrisConnectorService, PyrisJob * @return jobToken if the job was created else null */ public String addTranscriptionToPyrisDB(Transcription transcription) { + if (transcription == null) { + throw new IllegalArgumentException("Transcription cannot be null"); + } + if (transcription.getLecture() == null) { + throw new IllegalArgumentException("Transcription must be associated with a lecture"); + } if (lectureIngestionEnabled(transcription.getLecture().getCourse())) { - return executeTranscriptionAdditionWebhook(new PyrisTranscriptionIngestionWebhookDTO(transcription, 0, "", 0, "", "")); + Lecture lecture = transcription.getLecture(); + Course course = lecture.getCourse(); + return executeTranscriptionAdditionWebhook( + new PyrisTranscriptionIngestionWebhookDTO(transcription, lecture.getId(), lecture.getTitle(), course.getId(), course.getTitle(), course.getDescription())); } return null; } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java deleted file mode 100644 index a77572299f50..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisTranscriptionResource.java +++ /dev/null @@ -1,111 +0,0 @@ -package de.tum.cit.aet.artemis.iris.web; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; - -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import de.tum.cit.aet.artemis.core.domain.Course; -import de.tum.cit.aet.artemis.core.repository.CourseRepository; -import de.tum.cit.aet.artemis.core.repository.UserRepository; -import de.tum.cit.aet.artemis.core.security.Role; -import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastInstructorInCourse; -import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; -import de.tum.cit.aet.artemis.iris.dto.IngestionState; -import de.tum.cit.aet.artemis.iris.service.IrisRateLimitService; -import de.tum.cit.aet.artemis.iris.service.pyris.PyrisConnectorException; -import de.tum.cit.aet.artemis.iris.service.pyris.PyrisHealthIndicator; -import de.tum.cit.aet.artemis.iris.service.pyris.PyrisWebhookService; - -@Profile(PROFILE_IRIS) -@RestController -@RequestMapping("api/iris/transcription-ingestion") -public class IrisTranscriptionResource { - - private static final Logger log = LoggerFactory.getLogger(IrisTranscriptionResource.class); - - protected final UserRepository userRepository; - - protected final IrisRateLimitService irisRateLimitService; - - protected final PyrisHealthIndicator pyrisHealthIndicator; - - private final AuthorizationCheckService authorizationCheckService; - - private final CourseRepository courseRepository; - - private final PyrisWebhookService pyrisWebhookService; - - public IrisTranscriptionResource(UserRepository userRepository, PyrisHealthIndicator pyrisHealthIndicator, IrisRateLimitService irisRateLimitService, - AuthorizationCheckService authorizationCheckService, CourseRepository courseRepository, PyrisWebhookService pyrisWebhookService) { - this.userRepository = userRepository; - this.pyrisHealthIndicator = pyrisHealthIndicator; - this.irisRateLimitService = irisRateLimitService; - this.authorizationCheckService = authorizationCheckService; - this.courseRepository = courseRepository; - this.pyrisWebhookService = pyrisWebhookService; - } - - /** - * Retrieves the overall ingestion state of a Transcription by communicating with Pyris. - * - *

- * This method sends a GET request to the external Pyris service to fetch the current ingestion - * state of all Transcription in a course, identified by its `lectureId`. The ingestion state can be aggregated from - * multiple lecture units or can reflect the overall status of the lecture ingestion process. - *

- * - * @param courseId the ID of the lecture for which the ingestion state is being requested - * @return a {@link ResponseEntity} containing the {@link IngestionState} of the lecture, - */ - @GetMapping("courses/{courseId}/lectures/transcription-ingestion-state") - @EnforceAtLeastInstructorInCourse - public ResponseEntity> getStatusOfTranscriptionIngestion(@PathVariable long courseId) { - try { - Course course = courseRepository.findByIdElseThrow(courseId); - authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); - return ResponseEntity.ok(pyrisWebhookService.getLecturesIngestionState(courseId)); // update to new webhook service for transcription - } - catch (PyrisConnectorException e) { - log.error("Error fetching ingestion state for course {}", courseId, e); - return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); - } - } - - /** - * Retrieves the ingestion state of all transcription unit in a lecture by communicating with Pyris. - * - *

- * This method sends a GET request to the external Pyris service to fetch the current ingestion - * state of a lecture unit, identified by its ID. It constructs a request using the provided - * `lectureId` and `lectureUnitId` and returns the state of the ingestion process (e.g., NOT_STARTED, - * IN_PROGRESS, DONE, ERROR). - *

- * - * @param courseId the ID of the lecture the unit belongs to - * @param lectureId the ID of the lecture the unit belongs to - * @return a {@link ResponseEntity} containing the {@link IngestionState} of the lecture unit, - */ - @GetMapping("courses/{courseId}/lectures/{lectureId}/lecture-units/transcription-ingestion-state") - @EnforceAtLeastInstructorInCourse - public ResponseEntity> getStatusOfLectureUnitsTranscriptionIngestion(@PathVariable long courseId, @PathVariable long lectureId) { - try { - Course course = courseRepository.findByIdElseThrow(courseId); - authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); - return ResponseEntity.ok(pyrisWebhookService.getLectureUnitsIngestionState(courseId, lectureId)); // update to new webhook service for transcription - } - catch (PyrisConnectorException e) { - log.error("Error fetching ingestion state for lecture {}", lectureId, e); - return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build(); - } - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java index cc88106efa4e..479c3b9ca20c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java @@ -6,6 +6,8 @@ import jakarta.persistence.Lob; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -19,12 +21,19 @@ @Table(name = "transcription_segments") public class TranscriptionSegment extends DomainObject { + @NotNull @Column(name = "start_time") private Double startTime; + @NotNull @Column(name = "end_time") private Double endTime; + @AssertTrue(message = "End time must be greater than start time") + private boolean isTimeValid() { + return startTime == null || endTime == null || endTime > startTime; + } + @Lob private String text; diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index 2de05e508de5..9d8e3175d267 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -311,18 +311,21 @@ public ResponseEntity ingestLectures(@PathVariable Long courseId, @Request @EnforceAtLeastInstructorInCourse public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @RequestParam(required = false) Optional lectureId) { Course course = courseRepository.findByIdWithLecturesAndLectureUnitsElseThrow(courseId); + if (course == null) { + return ResponseEntity.notFound().build(); + } authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); if (lectureId.isPresent()) { List transcriptions = transcriptionRepository.findAllByLectureIdWithSegments(lectureId.get()); if (transcriptions.isEmpty()) { - return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.allLecturesError", "idExists")).body(null); + return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.noTranscriptionError", "noTranscription")) + .body(null); } Set transcriptionsToIngest = new HashSet<>(transcriptions); lectureService.ingestTranscriptionInPyris(transcriptionsToIngest); return ResponseEntity.ok().build(); } - // lectureService.ingestTranscriptionInPyris(course.getLectures().stream().map(Lecture::getTranscriptions).flatMap(List::stream).collect(Collectors.toSet())); - return ResponseEntity.ok().build(); + return ResponseEntity.badRequest().build(); } /** diff --git a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java index e6737b403191..7d152cba1b79 100644 --- a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java @@ -65,7 +65,7 @@ void testIngestTranscriptionInPyris() throws Exception { irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { assertThat(dto.settings().authenticationToken()).isNotNull(); }); - request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription", Optional.empty(), HttpStatus.OK); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription", Optional.empty(), HttpStatus.BAD_REQUEST); } @Test @@ -78,6 +78,13 @@ void testIngestTranscriptionInPyrisWithLectureId() throws Exception { request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription?lectureId=" + lecture1.getId(), Optional.empty(), HttpStatus.OK); } + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testIngestTranscriptionWithInvalidLectureId() throws Exception { + activateIrisFor(lecture1.getCourse()); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription?lectureId=" + 999999L, Optional.empty(), HttpStatus.BAD_REQUEST); + } + @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "STUDENT") void testIngestTranscriptionInPyrisWithoutPermission() throws Exception { From 26ba567580de605481f25283e0a9de8844f33a55 Mon Sep 17 00:00:00 2001 From: isabella Date: Mon, 20 Jan 2025 17:02:21 +0100 Subject: [PATCH 18/30] fix attachmentResourceIntegrationTEst test --- .../aet/artemis/lecture/AttachmentResourceIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/de/tum/cit/aet/artemis/lecture/AttachmentResourceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lecture/AttachmentResourceIntegrationTest.java index 1bae5e4927f6..3de1573acaa3 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lecture/AttachmentResourceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lecture/AttachmentResourceIntegrationTest.java @@ -103,7 +103,7 @@ void updateAttachment(boolean fileUpdate) throws Exception { assertThat(actualAttachment.getName()).isEqualTo("new name"); var ignoringFields = new String[] { "name", "fileService", "filePathService", "entityFileService", "prevLink", "lecture.lectureUnits", "lecture.posts", "lecture.course", - "lecture.attachments" }; + "lecture.attachments", "lecture.transcriptions" }; assertThat(actualAttachment).usingRecursiveComparison().ignoringFields(ignoringFields).isEqualTo(expectedAttachment); verify(groupNotificationService).notifyStudentGroupAboutAttachmentChange(actualAttachment, notificationText); } From 1a820ed156bd31562543033e0362440ebeea5b2a Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 15:09:46 +0100 Subject: [PATCH 19/30] Implement feedback --- .../service/pyris/PyrisWebhookService.java | 6 +- ...PyrisTranscriptionIngestionWebhookDTO.java | 5 +- .../aet/artemis/lecture/domain/Lecture.java | 16 +-- ...ription.java => LectureTranscription.java} | 16 +-- ....java => LectureTranscriptionSegment.java} | 21 ++-- ...nDTO.java => LectureTranscriptionDTO.java} | 2 +- ...va => LectureTranscriptionSegmentDTO.java} | 2 +- .../lecture/repository/LectureRepository.java | 1 - .../LectureTranscriptionRepository.java | 33 ++++++ .../repository/LectureUnitRepository.java | 2 + .../repository/TranscriptionRepository.java | 50 -------- .../lecture/service/LectureService.java | 6 +- .../artemis/lecture/web/LectureResource.java | 40 +++---- .../web/LectureTranscriptionResource.java | 108 ++++++++++++++++++ .../lecture/web/TranscriptionResource.java | 72 ------------ .../changelog/20250113154600_changelog.xml | 42 +++---- ...ure-transcription-ingestion.component.html | 6 +- ...ure-transcription-ingestion.component.scss | 4 +- ...cture-transcription-ingestion.component.ts | 33 +++--- .../lecture-transcription.service.ts | 6 +- .../iris/PyrisTranscriptionIngestionTest.java | 34 ++---- ...-transcription-ingestion.component.spec.ts | 12 +- .../lecture-transcription.service.spec.ts | 8 +- 23 files changed, 276 insertions(+), 249 deletions(-) rename src/main/java/de/tum/cit/aet/artemis/lecture/domain/{Transcription.java => LectureTranscription.java} (73%) rename src/main/java/de/tum/cit/aet/artemis/lecture/domain/{TranscriptionSegment.java => LectureTranscriptionSegment.java} (84%) rename src/main/java/de/tum/cit/aet/artemis/lecture/dto/{TranscriptionDTO.java => LectureTranscriptionDTO.java} (58%) rename src/main/java/de/tum/cit/aet/artemis/lecture/dto/{TranscriptionSegmentDTO.java => LectureTranscriptionSegmentDTO.java} (52%) create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java delete mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java delete mode 100644 src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java index ba72e71a95ad..48f59c21e9a0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java @@ -36,8 +36,8 @@ import de.tum.cit.aet.artemis.lecture.domain.AttachmentType; import de.tum.cit.aet.artemis.lecture.domain.AttachmentUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscription; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; @@ -78,7 +78,7 @@ public PyrisWebhookService(PyrisConnectorService pyrisConnectorService, PyrisJob * @param transcription The transcription that got Updated * @return jobToken if the job was created else null */ - public String addTranscriptionToPyrisDB(Transcription transcription) { + public String addTranscriptionToPyrisDB(LectureTranscription transcription) { if (transcription == null) { throw new IllegalArgumentException("Transcription cannot be null"); } @@ -95,7 +95,7 @@ public String addTranscriptionToPyrisDB(Transcription transcription) { } /** - * executes executeTranscriptionAdditionWebhook add transcription from to the vector database on pyris + * executes executeTranscriptionAdditionWebhook to insert the transcription into the vector database of Pyris * * @param toUpdateTranscription The transcription that are going to be Updated * @return jobToken if the job was created diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java index 8e4237f8660d..b05436743ce3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisTranscriptionIngestionWebhookDTO.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscription; /** * Represents a webhook data transfer object for lecture units in the Pyris system. @@ -11,5 +11,6 @@ */ @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record PyrisTranscriptionIngestionWebhookDTO(Transcription transcription, long lectureId, String lectureName, long courseId, String courseName, String courseDescription) { +public record PyrisTranscriptionIngestionWebhookDTO(LectureTranscription transcription, long lectureId, String lectureName, long courseId, String courseName, + String courseDescription) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java index d75dc3a989e7..6a9f6c89596a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Lecture.java @@ -63,7 +63,7 @@ public class Lecture extends DomainObject { @OneToMany(mappedBy = "lecture", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnoreProperties("lecture") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) - private List transcriptions = new ArrayList<>(); + private Set lectureTranscriptions = new HashSet<>(); @OneToMany(mappedBy = "lecture", cascade = CascadeType.REMOVE, orphanRemoval = true) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @@ -146,17 +146,17 @@ public void addLectureUnit(LectureUnit lectureUnit) { lectureUnit.setLecture(this); } - public List getTranscriptions() { - return transcriptions; + public Set getLectureTranscriptions() { + return lectureTranscriptions; } - public void setTranscriptions(List transcriptions) { - this.transcriptions = transcriptions; + public void setTranscriptions(Set lectureTranscriptions) { + this.lectureTranscriptions = lectureTranscriptions; } - public void addTranscription(Transcription transcription) { - this.transcriptions.add(transcription); - transcription.setLecture(this); + public void addTranscription(LectureTranscription lectureTranscription) { + this.lectureTranscriptions.add(lectureTranscription); + lectureTranscription.setLecture(this); } public Set getPosts() { diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/LectureTranscription.java similarity index 73% rename from src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java rename to src/main/java/de/tum/cit/aet/artemis/lecture/domain/LectureTranscription.java index b8dc379472fa..f5e5fc458bbd 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/Transcription.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/LectureTranscription.java @@ -11,6 +11,7 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; import jakarta.persistence.Table; +import jakarta.validation.constraints.Size; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -20,8 +21,8 @@ import de.tum.cit.aet.artemis.core.domain.DomainObject; @Entity -@Table(name = "transcription") -public class Transcription extends DomainObject { +@Table(name = "lecture_transcription") +public class LectureTranscription extends DomainObject { @ManyToOne @JoinColumn(name = "lecture_id") @@ -29,17 +30,18 @@ public class Transcription extends DomainObject { @JsonIgnore private Lecture lecture; + @Size(min = 2, max = 2, message = "Language must be exactly 2 characters long") private String language; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OrderBy("startTime asc") @JoinColumn(name = "transcription_id") - private List segments = new ArrayList<>(); + private List segments = new ArrayList<>(); - public Transcription() { + public LectureTranscription() { } - public Transcription(Lecture lecture, String language, List segments) { + public LectureTranscription(Lecture lecture, String language, List segments) { this.lecture = lecture; this.language = language; this.segments = segments; @@ -61,11 +63,11 @@ public void setLanguage(String language) { this.language = language; } - public List getSegments() { + public List getSegments() { return segments; } - public void setSegments(List segments) { + public void setSegments(List segments) { this.segments = segments; } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/LectureTranscriptionSegment.java similarity index 84% rename from src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java rename to src/main/java/de/tum/cit/aet/artemis/lecture/domain/LectureTranscriptionSegment.java index 479c3b9ca20c..4ef372ef08b3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/TranscriptionSegment.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/LectureTranscriptionSegment.java @@ -18,8 +18,8 @@ @Entity @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@Table(name = "transcription_segments") -public class TranscriptionSegment extends DomainObject { +@Table(name = "lecture_transcription_segments") +public class LectureTranscriptionSegment extends DomainObject { @NotNull @Column(name = "start_time") @@ -29,11 +29,6 @@ public class TranscriptionSegment extends DomainObject { @Column(name = "end_time") private Double endTime; - @AssertTrue(message = "End time must be greater than start time") - private boolean isTimeValid() { - return startTime == null || endTime == null || endTime > startTime; - } - @Lob private String text; @@ -46,10 +41,10 @@ private boolean isTimeValid() { @Column(name = "slide_number") private int slideNumber; - public TranscriptionSegment() { + public LectureTranscriptionSegment() { } - public TranscriptionSegment(Double startTime, Double endTime, String text, LectureUnit lectureUnit, int slideNumber) { + public LectureTranscriptionSegment(Double startTime, Double endTime, String text, LectureUnit lectureUnit, int slideNumber) { this.startTime = startTime; this.endTime = endTime; this.text = text; @@ -99,6 +94,12 @@ public void setSlideNumber(int slideNumber) { @Override public String toString() { - return "TranscriptionSegment [startTime = " + startTime + ", endTime = " + endTime + ", text = " + text + "]"; + return "LectureTranscriptionSegment [startTime = " + startTime + ", endTime = " + endTime + ", text = " + text + "]"; } + + @AssertTrue(message = "End time must be greater than start time") + private boolean isTimeValid() { + return startTime == null || endTime == null || endTime > startTime; + } + } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionDTO.java b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/LectureTranscriptionDTO.java similarity index 58% rename from src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionDTO.java rename to src/main/java/de/tum/cit/aet/artemis/lecture/dto/LectureTranscriptionDTO.java index dd41a0f4f468..b29fb9b8106e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/LectureTranscriptionDTO.java @@ -5,5 +5,5 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record TranscriptionDTO(Long lectureId, String language, List segments) { +public record LectureTranscriptionDTO(Long lectureId, String language, List segments) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionSegmentDTO.java b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/LectureTranscriptionSegmentDTO.java similarity index 52% rename from src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionSegmentDTO.java rename to src/main/java/de/tum/cit/aet/artemis/lecture/dto/LectureTranscriptionSegmentDTO.java index fb7cc442a266..23cd2eef59fe 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/dto/TranscriptionSegmentDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/dto/LectureTranscriptionSegmentDTO.java @@ -3,5 +3,5 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record TranscriptionSegmentDTO(Double startTime, Double endTime, String text, int slideNumber, Long lectureUnitId) { +public record LectureTranscriptionSegmentDTO(Double startTime, Double endTime, String text, int slideNumber, Long lectureUnitId) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java index e441c1633b23..5d33a984b3ec 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java @@ -36,7 +36,6 @@ public interface LectureRepository extends ArtemisJpaRepository { Set findAllByCourseId(@Param("courseId") Long courseId); @Query(""" - SELECT lecture FROM Lecture lecture LEFT JOIN FETCH lecture.attachments WHERE lecture.course.id = :courseId diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java new file mode 100644 index 000000000000..5c903565db4c --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java @@ -0,0 +1,33 @@ +package de.tum.cit.aet.artemis.lecture.repository; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD; + +import java.util.Optional; +import java.util.Set; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscription; + +/** + * Spring Data JPA repository for the Transcription of a lecture video entity. + */ +@Profile(PROFILE_CORE) +@Repository +public interface LectureTranscriptionRepository extends ArtemisJpaRepository { + + @EntityGraph(type = LOAD, attributePaths = { "segments" }) + Optional findOneWithTranscriptionSegmentsById(@Param("id") Long id); + + @EntityGraph(type = LOAD, attributePaths = { "segments" }) + Set findAllWithTranscriptionSegmentsByLectureId(@Param("lectureId") Long lectureId); + + @EntityGraph(type = LOAD, attributePaths = {}) + Set findAllByLectureId(@Param("lectureId") Long lectureId); + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java index 42807551d308..a0e816148a8e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java @@ -28,6 +28,8 @@ public interface LectureUnitRepository extends ArtemisJpaRepository findById(@Param("lectureUnitId") long lectureUnitId); + Set findAllByIdIn(@Param("lectureUnitIds") Set lectureUnitIds); + @Query(""" SELECT lu FROM LectureUnit lu diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java deleted file mode 100644 index d1ef215d4dd3..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/TranscriptionRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package de.tum.cit.aet.artemis.lecture.repository; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.List; -import java.util.Optional; - -import org.springframework.context.annotation.Profile; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; - -/** - * Spring Data JPA repository for the Transcription of a lecture video entity. - */ -@Profile(PROFILE_CORE) -@Repository -public interface TranscriptionRepository extends ArtemisJpaRepository { - - @Query(""" - SELECT t - FROM Transcription t - LEFT JOIN FETCH t.segments - WHERE t.id = :id - """) - Optional findByIdWithSegments(@Param("id") Long id); - - @Query(""" - SELECT t - FROM Transcription t - LEFT JOIN FETCH t.segments - WHERE t.lecture.id = :lectureId - """) - List findAllByLectureIdWithSegments(@Param("lectureId") Long lectureId); - - @Query(""" - SELECT t - FROM Transcription t - WHERE t.lecture.id = :lectureId - """) - List findAllByLectureId(@Param("lectureId") Long lectureId); - - default Transcription findByIdOrElseThrow(Long id) { - return getValueElseThrow(findById(id), id); - } - -} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java index dc88a6be2b1d..785cfe6181a3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java @@ -29,8 +29,8 @@ import de.tum.cit.aet.artemis.lecture.domain.AttachmentUnit; import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscription; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; @Profile(PROFILE_CORE) @@ -193,9 +193,9 @@ public void ingestLecturesInPyris(Set lectures) { * * @param transcriptions set of transcriptions to be ingested */ - public void ingestTranscriptionInPyris(Set transcriptions) { + public void ingestTranscriptionInPyris(Set transcriptions) { if (pyrisWebhookService.isPresent()) { - for (Transcription transcription : transcriptions) { + for (LectureTranscription transcription : transcriptions) { String jobToken = pyrisWebhookService.get().addTranscriptionToPyrisDB(transcription); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index 9d8e3175d267..3a1f8b4e648d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -50,10 +50,10 @@ import de.tum.cit.aet.artemis.lecture.domain.AttachmentUnit; import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit; import de.tum.cit.aet.artemis.lecture.domain.Lecture; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscription; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; -import de.tum.cit.aet.artemis.lecture.repository.TranscriptionRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureTranscriptionRepository; import de.tum.cit.aet.artemis.lecture.service.LectureImportService; import de.tum.cit.aet.artemis.lecture.service.LectureService; @@ -71,7 +71,7 @@ public class LectureResource { private final CompetencyApi competencyApi; - private final TranscriptionRepository transcriptionRepository; + private final LectureTranscriptionRepository lectureTranscriptionRepository; @Value("${jhipster.clientApp.name}") private String applicationName; @@ -96,7 +96,7 @@ public class LectureResource { public LectureResource(LectureRepository lectureRepository, LectureService lectureService, LectureImportService lectureImportService, CourseRepository courseRepository, UserRepository userRepository, AuthorizationCheckService authCheckService, ExerciseService exerciseService, ChannelService channelService, - ChannelRepository channelRepository, CompetencyApi competencyApi, TranscriptionRepository transcriptionRepository) { + ChannelRepository channelRepository, CompetencyApi competencyApi, LectureTranscriptionRepository lectureTranscriptionRepository) { this.lectureRepository = lectureRepository; this.lectureService = lectureService; this.lectureImportService = lectureImportService; @@ -107,7 +107,7 @@ public LectureResource(LectureRepository lectureRepository, LectureService lectu this.channelService = channelService; this.channelRepository = channelRepository; this.competencyApi = competencyApi; - this.transcriptionRepository = transcriptionRepository; + this.lectureTranscriptionRepository = lectureTranscriptionRepository; } /** @@ -299,33 +299,27 @@ public ResponseEntity ingestLectures(@PathVariable Long courseId, @Request } /** - * POST /courses/{courseId}/ingest-transcription + * POST /lectures/{lectureId}/ingest-transcription * This endpoint is for starting the ingestion of all lectures or only one lecture when triggered in Artemis. * - * @param courseId the ID of the course for which all lectures should be ingested in pyris * @param lectureId If this id is present then only ingest this one lecture of the respective course * @return the ResponseEntity with status 200 (OK) and a message success or null if the operation failed */ @Profile(PROFILE_IRIS) - @PutMapping("courses/{courseId}/ingest-transcription") + @PutMapping("courses/{courseId}/lectures/{lectureId}/ingest-transcription") @EnforceAtLeastInstructorInCourse - public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @RequestParam(required = false) Optional lectureId) { - Course course = courseRepository.findByIdWithLecturesAndLectureUnitsElseThrow(courseId); - if (course == null) { - return ResponseEntity.notFound().build(); - } + public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @PathVariable Long lectureId) { + Lecture lecture = lectureRepository.findById(lectureId).orElseThrow(); + Course course = lecture.getCourse(); authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); - if (lectureId.isPresent()) { - List transcriptions = transcriptionRepository.findAllByLectureIdWithSegments(lectureId.get()); - if (transcriptions.isEmpty()) { - return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.noTranscriptionError", "noTranscription")) - .body(null); - } - Set transcriptionsToIngest = new HashSet<>(transcriptions); - lectureService.ingestTranscriptionInPyris(transcriptionsToIngest); - return ResponseEntity.ok().build(); + Set transcriptions = lectureTranscriptionRepository.findAllWithTranscriptionSegmentsByLectureId(lectureId); + if (transcriptions.isEmpty()) { + return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.noTranscriptionError", "noTranscription")) + .body(null); } - return ResponseEntity.badRequest().build(); + Set transcriptionsToIngest = new HashSet<>(transcriptions); + lectureService.ingestTranscriptionInPyris(transcriptionsToIngest); + return ResponseEntity.ok().build(); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java new file mode 100644 index 000000000000..58ef5a66657f --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java @@ -0,0 +1,108 @@ +package de.tum.cit.aet.artemis.lecture.web; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; +import de.tum.cit.aet.artemis.core.repository.UserRepository; +import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastEditorInCourse; +import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; +import de.tum.cit.aet.artemis.lecture.domain.Lecture; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscription; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscriptionSegment; +import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; +import de.tum.cit.aet.artemis.lecture.dto.LectureTranscriptionDTO; +import de.tum.cit.aet.artemis.lecture.dto.LectureTranscriptionSegmentDTO; +import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureTranscriptionRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; + +@Profile(PROFILE_CORE) +@RestController +@RequestMapping("api/") +public class LectureTranscriptionResource { + + private static final Logger log = LoggerFactory.getLogger(LectureTranscriptionResource.class); + + private final LectureTranscriptionRepository lectureTranscriptionRepository; + + private final LectureRepository lectureRepository; + + private final LectureUnitRepository lectureUnitRepository; + + private final UserRepository userRepository; + + private final AuthorizationCheckService authCheckService; + + public LectureTranscriptionResource(LectureTranscriptionRepository transcriptionRepository, LectureRepository lectureRepository, LectureUnitRepository lectureUnitRepository, + AuthorizationCheckService authCheckService, UserRepository userRepository) { + this.lectureTranscriptionRepository = transcriptionRepository; + this.lectureRepository = lectureRepository; + this.lectureUnitRepository = lectureUnitRepository; + this.authCheckService = authCheckService; + this.userRepository = userRepository; + } + + /** + * POST /transcription : Create a new transcription. + * + * @param transcriptionDTO the transcription object to create + * @return the ResponseEntity with status 201 (Created) and with body the new transcription, or with status 400 (Bad Request) if the transcription has already an ID + * @throws URISyntaxException if the Location URI syntax is incorrect + */ + @PostMapping(value = "courses/{courseId}/lecture/{lectureId}/transcriptions") + @EnforceAtLeastEditorInCourse + public ResponseEntity createTranscription(@RequestBody LectureTranscriptionDTO transcriptionDTO, @PathVariable Long courseId, + @PathVariable Long lectureId) throws URISyntaxException { + Lecture lecture = lectureRepository.findById(transcriptionDTO.lectureId()).orElseThrow(() -> new EntityNotFoundException("no lecture found for this id")); + + Set lectureUnitIds = transcriptionDTO.segments().stream().map(LectureTranscriptionSegmentDTO::lectureUnitId).collect(Collectors.toSet()); + Set lectureUnits = lectureUnitRepository.findAllByIdIn(lectureUnitIds); + + List segments = transcriptionDTO.segments().stream().map(segment -> { + LectureUnit lectureUnit = lectureUnits.stream().filter(lu -> lu.getId().equals(segment.lectureUnitId())).findFirst().orElseThrow(); + return new LectureTranscriptionSegment(segment.startTime(), segment.endTime(), segment.text(), lectureUnit, segment.slideNumber()); + }).toList(); + + LectureTranscription lectureTranscription = new LectureTranscription(lecture, transcriptionDTO.language(), segments); + lectureTranscription.setId(null); + + LectureTranscription result = lectureTranscriptionRepository.save(lectureTranscription); + + return ResponseEntity.created(new URI("/api/lecture/" + lectureId + "/transcriptions/" + result.getId())).body(result); + } + + /** + * GET /lectures/:lectureId/transcriptions/:transcriptionId : get the transcription for the transcriptionId. + * + * @param lectureId the lectureId of the lecture from the transcription + * @param transcriptionId the transcriptionId of the transcription to retrieve + * @return the ResponseEntity with status 200 (OK) and with body the transcription, or with status 404 (Not Found) + */ + @GetMapping("lectures/{lectureId}/transcription/{transcriptionId}") + @EnforceAtLeastStudent + public ResponseEntity getLecture(@PathVariable Long lectureId, @PathVariable Long transcriptionId) { + log.debug("REST request to get transcription {}", transcriptionId); + LectureTranscription transcription = lectureTranscriptionRepository.findById(transcriptionId).orElseThrow(); + authCheckService.checkIsAllowedToSeeLectureElseThrow(transcription.getLecture(), userRepository.getUserWithGroupsAndAuthorities()); + + return ResponseEntity.ok(transcription); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java deleted file mode 100644 index 074cccb0a49d..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/TranscriptionResource.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.tum.cit.aet.artemis.lecture.web; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; -import de.tum.cit.aet.artemis.lecture.domain.Lecture; -import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; -import de.tum.cit.aet.artemis.lecture.domain.TranscriptionSegment; -import de.tum.cit.aet.artemis.lecture.dto.TranscriptionDTO; -import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; -import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; -import de.tum.cit.aet.artemis.lecture.repository.TranscriptionRepository; - -@Profile(PROFILE_CORE) -@RestController -@RequestMapping("api/") -public class TranscriptionResource { - - private static final Logger log = LoggerFactory.getLogger(TranscriptionResource.class); - - private final TranscriptionRepository transcriptionRepository; - - private final LectureRepository lectureRepository; - - private final LectureUnitRepository lectureUnitRepository; - - public TranscriptionResource(TranscriptionRepository transcriptionRepository, LectureRepository lectureRepository, LectureUnitRepository lectureUnitRepository) { - this.transcriptionRepository = transcriptionRepository; - this.lectureRepository = lectureRepository; - this.lectureUnitRepository = lectureUnitRepository; - } - - /** - * POST /transcription : Create a new transcription. - * - * @param transcriptionDTO the transcription object to create - * @return the ResponseEntity with status 201 (Created) and with body the new transcription, or with status 400 (Bad Request) if the transcription has already an ID - * @throws URISyntaxException if the Location URI syntax is incorrect - */ - @PostMapping(value = "transcription") - @EnforceAtLeastEditor - public ResponseEntity createTranscription(@RequestBody TranscriptionDTO transcriptionDTO) throws URISyntaxException { - Lecture lecture = lectureRepository.findById(transcriptionDTO.lectureId()).orElseThrow(() -> new EntityNotFoundException("no lecture found for this id")); - - List segments = transcriptionDTO.segments().stream().map(segment -> { - LectureUnit lectureUnit = lectureUnitRepository.findById(segment.lectureUnitId()).orElseThrow(() -> new EntityNotFoundException("no lecture unit found for this id")); - return new TranscriptionSegment(segment.startTime(), segment.endTime(), segment.text(), lectureUnit, segment.slideNumber()); - }).toList(); - - Transcription transcription = new Transcription(lecture, transcriptionDTO.language(), segments); - transcription.setId(null); - - Transcription result = transcriptionRepository.save(transcription); - - return ResponseEntity.created(new URI("/api/transcriptions/" + result.getId())).body(result); - } -} diff --git a/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml index de5ce1a1ea2f..0880fe500249 100644 --- a/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml @@ -4,58 +4,58 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"> - + + primaryKeyName="pk_lecture_transcription"/> - + - - + + primaryKeyName="pk_lecture_transcription_segments"/> + type="DOUBLE"> + + + type="DOUBLE"> + + - + - - - + referencedTableName="lecture_transcription"/> \ No newline at end of file diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html index a86083c300d5..842d92c835ea 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.html @@ -3,13 +3,15 @@

Lecture Transcription

Ingest

Ingest transcription into Pyris Weaviate DB

- - + +

Create

Create transcription into Artemis DB

+ +
diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss index af9f62445580..d3fe2ef36545 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.scss @@ -5,10 +5,12 @@ .insert-transcription-row { display: flex; align-items: center; + margin-top: 0.5rem; } .insert-transcription-area { width: min(40rem, 100%); height: 5rem; - margin-right: 1rem; + margin-right: 0.4rem; + border: 1px solid gray; } diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts index 96ad05ff2b56..0d3db81d739f 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts @@ -16,33 +16,40 @@ export class LectureTranscriptionIngestionComponent { private lectureTranscriptionService = inject(LectureTranscriptionService); private alertService = inject(AlertService); - courseIdInput = ''; - lectureIdInput = ''; + ingestCourseIdInput = ''; + ingestLectureIdInput = ''; + + createCourseIdInput = ''; + createLectureIdInput = ''; transcriptionInput = ''; faCheck = faCheck; ingestTranscription(): void { - this.lectureTranscriptionService.ingestTranscription(Number(this.courseIdInput), Number(this.lectureIdInput)).subscribe((successful) => { + this.lectureTranscriptionService.ingestTranscription(Number(this.ingestCourseIdInput), Number(this.ingestLectureIdInput)).subscribe((successful) => { if (successful) { this.alertService.success('Ingested transcription'); } else { this.alertService.error('Unknown error while ingesting transcription'); } - this.courseIdInput = ''; - this.lectureIdInput = ''; + this.ingestCourseIdInput = ''; + this.ingestLectureIdInput = ''; }); } createTranscription(): void { - this.lectureTranscriptionService.createTranscription(JSON.parse(this.transcriptionInput)).subscribe((successful) => { - if (successful) { - this.alertService.success('Created transcription'); - } else { - this.alertService.error('Unknown error while creating transcription'); - } - this.transcriptionInput = ''; - }); + this.lectureTranscriptionService + .createTranscription(Number(this.createLectureIdInput), Number(this.createLectureIdInput), JSON.parse(this.transcriptionInput)) + .subscribe((successful) => { + if (successful) { + this.alertService.success('Created transcription'); + } else { + this.alertService.error('Unknown error while creating transcription'); + } + this.transcriptionInput = ''; + this.createCourseIdInput = ''; + this.createLectureIdInput = ''; + }); } } diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts index d8382c5f1206..05f67c55bbc6 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts @@ -10,7 +10,7 @@ export class LectureTranscriptionService { ingestTranscription(courseId: number, lectureId: number): Observable { return this.httpClient .put( - `api/courses/${courseId}/ingest-transcription?lectureId=${lectureId}`, + `api/courses/${courseId}/lectures/${lectureId}/ingest-transcription`, {}, { observe: 'response', @@ -19,9 +19,9 @@ export class LectureTranscriptionService { .pipe(map((response) => response.status == 200)); } - createTranscription(transcription: any): Observable { + createTranscription(courseId: number, lectureId: number, transcription: any): Observable { return this.httpClient - .post(`api/transcription`, transcription, { + .post(`api/courses/${courseId}/lecture/${lectureId}/transcriptions`, transcription, { observe: 'response', }) .pipe(map((response) => response.status == 201)); diff --git a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java index 7d152cba1b79..9cc03540a251 100644 --- a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java @@ -15,17 +15,17 @@ import de.tum.cit.aet.artemis.core.user.util.UserUtilService; import de.tum.cit.aet.artemis.core.util.CourseUtilService; import de.tum.cit.aet.artemis.lecture.domain.Lecture; -import de.tum.cit.aet.artemis.lecture.domain.Transcription; -import de.tum.cit.aet.artemis.lecture.domain.TranscriptionSegment; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscription; +import de.tum.cit.aet.artemis.lecture.domain.LectureTranscriptionSegment; import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; -import de.tum.cit.aet.artemis.lecture.repository.TranscriptionRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureTranscriptionRepository; -class PyrisTranscriptionIngestionTest extends AbstractIrisIntegrationTest { +class PyrisLectureTranscriptionIngestionTest extends AbstractIrisIntegrationTest { private static final String TEST_PREFIX = "pyristranscriptioningestiontest"; @Autowired - private TranscriptionRepository transcriptionRepository; + private LectureTranscriptionRepository lecturetranscriptionRepository; @Autowired private LectureRepository lectureRepository; @@ -51,21 +51,11 @@ void initTestCase() throws Exception { userUtilService.createAndSaveUser(TEST_PREFIX + "tutor42"); userUtilService.createAndSaveUser(TEST_PREFIX + "instructor42"); - TranscriptionSegment segment1 = new TranscriptionSegment(0.0, 12.0, "Welcome to today's lecture", null, 1); - TranscriptionSegment segment2 = new TranscriptionSegment(0.0, 12.0, "Today we will talk about Artemis", null, 1); - Transcription transcription = new Transcription(this.lecture1, "en", List.of(new TranscriptionSegment[] { segment1, segment2 })); + LectureTranscriptionSegment segment1 = new LectureTranscriptionSegment(0.0, 12.0, "Welcome to today's lecture", null, 1); + LectureTranscriptionSegment segment2 = new LectureTranscriptionSegment(0.0, 12.0, "Today we will talk about Artemis", null, 1); + LectureTranscription transcription = new LectureTranscription(this.lecture1, "en", List.of(new LectureTranscriptionSegment[] { segment1, segment2 })); - transcriptionRepository.save(transcription); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testIngestTranscriptionInPyris() throws Exception { - activateIrisFor(lecture1.getCourse()); - irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { - assertThat(dto.settings().authenticationToken()).isNotNull(); - }); - request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription", Optional.empty(), HttpStatus.BAD_REQUEST); + lecturetranscriptionRepository.save(transcription); } @Test @@ -75,14 +65,14 @@ void testIngestTranscriptionInPyrisWithLectureId() throws Exception { irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { assertThat(dto.settings().authenticationToken()).isNotNull(); }); - request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription?lectureId=" + lecture1.getId(), Optional.empty(), HttpStatus.OK); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/lectures/" + lecture1.getId() + "/ingest-transcription", Optional.empty(), HttpStatus.OK); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testIngestTranscriptionWithInvalidLectureId() throws Exception { activateIrisFor(lecture1.getCourse()); - request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription?lectureId=" + 999999L, Optional.empty(), HttpStatus.BAD_REQUEST); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/lectures/" + 9999L + "/ingest-transcription", Optional.empty(), HttpStatus.INTERNAL_SERVER_ERROR); } @Test @@ -92,7 +82,7 @@ void testIngestTranscriptionInPyrisWithoutPermission() throws Exception { irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { assertThat(dto.settings().authenticationToken()).isNotNull(); }); - request.put("/api/courses/" + lecture1.getCourse().getId() + "/ingest-transcription?lectureId=" + lecture1.getId(), Optional.empty(), HttpStatus.FORBIDDEN); + request.put("/api/courses/" + lecture1.getCourse().getId() + "/lectures/" + lecture1.getId() + "/ingest-transcription", Optional.empty(), HttpStatus.FORBIDDEN); } } diff --git a/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts index e095658c81b7..9288dbf6805c 100644 --- a/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts +++ b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.spec.ts @@ -28,10 +28,10 @@ describe('LectureTranscriptionIngestionComponent', () => { it('should call ingestTranscription with correct parameters when ingest button is clicked', waitForAsync(() => { const courseId = '1'; - comp.courseIdInput = courseId; + comp.ingestCourseIdInput = courseId; const lectureId = '1'; - comp.lectureIdInput = lectureId; + comp.ingestLectureIdInput = lectureId; fixture.detectChanges(); const button = fixture.debugElement.query(By.css('#ingest-transcription-button')); @@ -44,7 +44,13 @@ describe('LectureTranscriptionIngestionComponent', () => { it('should call createTranscription with correct parameters when create transcription button is clicked', waitForAsync(() => { const transcription = '{ "transcription": [] }'; + const courseId = '1'; + const lectureId = '1'; + comp.transcriptionInput = transcription; + comp.createCourseIdInput = courseId; + comp.createLectureIdInput = lectureId; + fixture.detectChanges(); const button = fixture.debugElement.query(By.css('#create-transcription-button')); @@ -52,6 +58,6 @@ describe('LectureTranscriptionIngestionComponent', () => { button.triggerEventHandler('onClick', null); fixture.detectChanges(); - expect(lectureTranscriptionService.createTranscription).toHaveBeenCalledWith(JSON.parse(transcription)); + expect(lectureTranscriptionService.createTranscription).toHaveBeenCalledWith(Number(courseId), Number(lectureId), JSON.parse(transcription)); })); }); diff --git a/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts index 1b0d9521c081..475ad4b9b0b4 100644 --- a/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts +++ b/src/test/javascript/spec/component/admin/lecture-transcription-ingestion/lecture-transcription.service.spec.ts @@ -26,15 +26,17 @@ describe('LectureTranscriptionService', () => { const lectureId = 1; service.ingestTranscription(courseId, lectureId).subscribe(() => {}); - const req = httpMock.expectOne({ method: 'PUT', url: `api/courses/${courseId}/ingest-transcription?lectureId=${lectureId}` }); + const req = httpMock.expectOne({ method: 'PUT', url: `api/courses/${courseId}/lectures/${lectureId}/ingest-transcription` }); req.flush({}); }); it('should send POST request to create transcription', () => { const transcription = { transcription: [] }; - service.createTranscription(transcription).subscribe(() => {}); + const courseId = 1; + const lectureId = 1; + service.createTranscription(courseId, lectureId, transcription).subscribe(() => {}); - const req = httpMock.expectOne({ method: 'POST', url: `api/transcription` }); + const req = httpMock.expectOne({ method: 'POST', url: `api/courses/${courseId}/lecture/${lectureId}/transcriptions` }); expect(req.request.body).toBe(transcription); req.flush({}); From 01320d31167137f42e380220934b04127b00afb2 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 15:14:19 +0100 Subject: [PATCH 20/30] Fix data type in db --- .../config/liquibase/changelog/20250113154600_changelog.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml index 0880fe500249..42d1fa797c40 100644 --- a/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20250113154600_changelog.xml @@ -34,7 +34,7 @@ + type="TEXT"/> Date: Tue, 21 Jan 2025 15:38:29 +0100 Subject: [PATCH 21/30] fix server style4 --- .../service/pyris/PyrisConnectorService.java | 4 +- .../service/pyris/PyrisWebhookService.java | 45 ++++++++++++------- ...ookTranscriptionIngestionExecutionDTO.java | 4 +- .../lecture/repository/LectureRepository.java | 1 + .../lecture/service/LectureService.java | 8 +--- .../artemis/lecture/web/LectureResource.java | 2 +- 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java index 50f711e1f43e..483ba94e6f9e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java @@ -121,11 +121,11 @@ public void executeTranscriptionAdditionWebhook(String variant, PyrisWebhookTran restTemplate.postForEntity(pyrisUrl + endpoint, objectMapper.valueToTree(executionDTO), Void.class); } catch (HttpStatusCodeException e) { - log.error("Failed to send transcription {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); + log.error("Failed to send transcription {} to Pyris: {}", executionDTO.lectureName(), e.getMessage()); throw toIrisException(e); } catch (RestClientException | IllegalArgumentException e) { - log.error("Failed to send transcription {} to Pyris: {}", executionDTO.transcription().lectureName(), e.getMessage()); + log.error("Failed to send transcription {} to Pyris: {}", executionDTO.lectureName(), e.getMessage()); throw new PyrisConnectorException("Could not fetch response from Pyris"); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java index 48f59c21e9a0..354166787697 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java @@ -75,35 +75,46 @@ public PyrisWebhookService(PyrisConnectorService pyrisConnectorService, PyrisJob /** * adds the transcription to the vector database in Pyris * - * @param transcription The transcription that got Updated + * @param transcriptions The transcription that got Updated * @return jobToken if the job was created else null */ - public String addTranscriptionToPyrisDB(LectureTranscription transcription) { - if (transcription == null) { - throw new IllegalArgumentException("Transcription cannot be null"); + public String addTranscriptionsToPyrisDB(Set transcriptions, Course course, Lecture lecture) { + if (transcriptions == null || transcriptions.isEmpty()) { + throw new IllegalArgumentException("Transcriptions cannot be empty"); } - if (transcription.getLecture() == null) { - throw new IllegalArgumentException("Transcription must be associated with a lecture"); - } - if (lectureIngestionEnabled(transcription.getLecture().getCourse())) { - Lecture lecture = transcription.getLecture(); - Course course = lecture.getCourse(); - return executeTranscriptionAdditionWebhook( - new PyrisTranscriptionIngestionWebhookDTO(transcription, lecture.getId(), lecture.getTitle(), course.getId(), course.getTitle(), course.getDescription())); + + if (!lectureIngestionEnabled(course)) { + return null; } - return null; + + transcriptions.forEach(transcription -> { + if (transcription.getLecture() == null) { + throw new IllegalArgumentException("Transcription must be associated with a lecture"); + } + else if (!transcription.getLecture().equals(lecture)) { + throw new IllegalArgumentException("All transcriptions must be associated with the same lecture"); + } + }); + + List pyrisTranscriptionIngestionWebhookDTOs = transcriptions.stream() + .map(transcription -> new PyrisTranscriptionIngestionWebhookDTO(transcription, lecture.getId(), lecture.getTitle(), course.getId(), course.getTitle(), + course.getDescription())) + .toList(); + + return executeTranscriptionAdditionWebhook(pyrisTranscriptionIngestionWebhookDTOs, course, lecture); } /** * executes executeTranscriptionAdditionWebhook to insert the transcription into the vector database of Pyris * - * @param toUpdateTranscription The transcription that are going to be Updated + * @param toUpdateTranscriptions The transcription that are going to be Updated * @return jobToken if the job was created */ - private String executeTranscriptionAdditionWebhook(PyrisTranscriptionIngestionWebhookDTO toUpdateTranscription) { - String jobToken = pyrisJobService.addTranscriptionIngestionWebhookJob(toUpdateTranscription.courseId(), toUpdateTranscription.lectureId()); + private String executeTranscriptionAdditionWebhook(List toUpdateTranscriptions, Course course, Lecture lecture) { + String jobToken = pyrisJobService.addTranscriptionIngestionWebhookJob(course.getId(), lecture.getId()); PyrisPipelineExecutionSettingsDTO settingsDTO = new PyrisPipelineExecutionSettingsDTO(jobToken, List.of(), artemisBaseUrl); - PyrisWebhookTranscriptionIngestionExecutionDTO executionDTO = new PyrisWebhookTranscriptionIngestionExecutionDTO(toUpdateTranscription, settingsDTO, List.of()); + PyrisWebhookTranscriptionIngestionExecutionDTO executionDTO = new PyrisWebhookTranscriptionIngestionExecutionDTO(toUpdateTranscriptions, lecture.getTitle(), settingsDTO, + List.of()); pyrisConnectorService.executeTranscriptionAdditionWebhook("fullIngestion", executionDTO); return jobToken; } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java index 97cc55069051..87c8153f23b0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/transcriptionIngestion/PyrisWebhookTranscriptionIngestionExecutionDTO.java @@ -8,6 +8,6 @@ import de.tum.cit.aet.artemis.iris.service.pyris.dto.status.PyrisStageDTO; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record PyrisWebhookTranscriptionIngestionExecutionDTO(PyrisTranscriptionIngestionWebhookDTO transcription, PyrisPipelineExecutionSettingsDTO settings, - List initialStages) { +public record PyrisWebhookTranscriptionIngestionExecutionDTO(List transcriptions, String lectureName, + PyrisPipelineExecutionSettingsDTO settings, List initialStages) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java index 5d33a984b3ec..e441c1633b23 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java @@ -36,6 +36,7 @@ public interface LectureRepository extends ArtemisJpaRepository { Set findAllByCourseId(@Param("courseId") Long courseId); @Query(""" + SELECT lecture FROM Lecture lecture LEFT JOIN FETCH lecture.attachments WHERE lecture.course.id = :courseId diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java index 785cfe6181a3..88a7fb95d692 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java @@ -193,11 +193,7 @@ public void ingestLecturesInPyris(Set lectures) { * * @param transcriptions set of transcriptions to be ingested */ - public void ingestTranscriptionInPyris(Set transcriptions) { - if (pyrisWebhookService.isPresent()) { - for (LectureTranscription transcription : transcriptions) { - String jobToken = pyrisWebhookService.get().addTranscriptionToPyrisDB(transcription); - } - } + public void ingestTranscriptionInPyris(Set transcriptions, Course course, Lecture lecture) { + pyrisWebhookService.ifPresent(webhookService -> webhookService.addTranscriptionsToPyrisDB(transcriptions, course, lecture)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index 3a1f8b4e648d..58f1eb2c58b4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -318,7 +318,7 @@ public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @P .body(null); } Set transcriptionsToIngest = new HashSet<>(transcriptions); - lectureService.ingestTranscriptionInPyris(transcriptionsToIngest); + lectureService.ingestTranscriptionInPyris(transcriptionsToIngest, course, lecture); return ResponseEntity.ok().build(); } From 90344dfe2d86b48b1e724664fd835f9e54394688 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 15:40:13 +0100 Subject: [PATCH 22/30] improve method names --- .../aet/artemis/lecture/web/LectureTranscriptionResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java index 58ef5a66657f..c3c5cbe497e1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java @@ -69,7 +69,7 @@ public LectureTranscriptionResource(LectureTranscriptionRepository transcription */ @PostMapping(value = "courses/{courseId}/lecture/{lectureId}/transcriptions") @EnforceAtLeastEditorInCourse - public ResponseEntity createTranscription(@RequestBody LectureTranscriptionDTO transcriptionDTO, @PathVariable Long courseId, + public ResponseEntity createLectureTranscription(@RequestBody LectureTranscriptionDTO transcriptionDTO, @PathVariable Long courseId, @PathVariable Long lectureId) throws URISyntaxException { Lecture lecture = lectureRepository.findById(transcriptionDTO.lectureId()).orElseThrow(() -> new EntityNotFoundException("no lecture found for this id")); @@ -98,7 +98,7 @@ public ResponseEntity createTranscription(@RequestBody Lec */ @GetMapping("lectures/{lectureId}/transcription/{transcriptionId}") @EnforceAtLeastStudent - public ResponseEntity getLecture(@PathVariable Long lectureId, @PathVariable Long transcriptionId) { + public ResponseEntity getLectureTranscriptions(@PathVariable Long lectureId, @PathVariable Long transcriptionId) { log.debug("REST request to get transcription {}", transcriptionId); LectureTranscription transcription = lectureTranscriptionRepository.findById(transcriptionId).orElseThrow(); authCheckService.checkIsAllowedToSeeLectureElseThrow(transcription.getLecture(), userRepository.getUserWithGroupsAndAuthorities()); From 8e9d7765e10851d5e22bcbd141e6f6a1e9f880e5 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 15:43:49 +0100 Subject: [PATCH 23/30] Fix dependency injection --- .../lecture-transcription.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts index 05f67c55bbc6..74966ef7a11d 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription.service.ts @@ -1,11 +1,11 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class LectureTranscriptionService { - constructor(private httpClient: HttpClient) {} + private httpClient = inject(HttpClient); ingestTranscription(courseId: number, lectureId: number): Observable { return this.httpClient From 0be3c13484af99577874dcedd3b31727fdfaa6d5 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 15:45:44 +0100 Subject: [PATCH 24/30] Minor fix --- .../lecture-transcription-ingestion.component.ts | 2 +- .../cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts index 0d3db81d739f..aff6d5f277a1 100644 --- a/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts +++ b/src/main/webapp/app/admin/lecture-transcription-ingestion/lecture-transcription-ingestion.component.ts @@ -40,7 +40,7 @@ export class LectureTranscriptionIngestionComponent { createTranscription(): void { this.lectureTranscriptionService - .createTranscription(Number(this.createLectureIdInput), Number(this.createLectureIdInput), JSON.parse(this.transcriptionInput)) + .createTranscription(Number(this.createCourseIdInput), Number(this.createLectureIdInput), JSON.parse(this.transcriptionInput)) .subscribe((successful) => { if (successful) { this.alertService.success('Created transcription'); diff --git a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java index 9cc03540a251..c7770de4394c 100644 --- a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisTranscriptionIngestionTest.java @@ -76,7 +76,7 @@ void testIngestTranscriptionWithInvalidLectureId() throws Exception { } @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "STUDENT") + @WithMockUser(username = TEST_PREFIX + "student1", roles = "STUDENT") void testIngestTranscriptionInPyrisWithoutPermission() throws Exception { activateIrisFor(lecture1.getCourse()); irisRequestMockProvider.mockTranscriptionIngestionWebhookRunResponse(dto -> { From 3c65bf952022bd54abb0d5a0fbe9a522dae91201 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 15:48:55 +0100 Subject: [PATCH 25/30] lecture - course validation --- .../de/tum/cit/aet/artemis/lecture/web/LectureResource.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index 58f1eb2c58b4..c9dd12418552 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -311,6 +311,10 @@ public ResponseEntity ingestLectures(@PathVariable Long courseId, @Request public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @PathVariable Long lectureId) { Lecture lecture = lectureRepository.findById(lectureId).orElseThrow(); Course course = lecture.getCourse(); + if (!lecture.getCourse().getId().equals(courseId)) { + return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.wrongLectureError", "lectureDoesNotMatchCourse")) + .body(null); + } authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); Set transcriptions = lectureTranscriptionRepository.findAllWithTranscriptionSegmentsByLectureId(lectureId); if (transcriptions.isEmpty()) { From 1d3c6b28a7baa4892fb5ccd9e9a37cf168969d2b Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 16:05:02 +0100 Subject: [PATCH 26/30] Minor fix --- .../artemis/lecture/repository/LectureUnitRepository.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java index a0e816148a8e..cf5573b333a0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureUnitRepository.java @@ -28,6 +28,11 @@ public interface LectureUnitRepository extends ArtemisJpaRepository findById(@Param("lectureUnitId") long lectureUnitId); + @Query(""" + SELECT lu + FROM LectureUnit lu + WHERE lu.id IN :lectureUnitIds + """) Set findAllByIdIn(@Param("lectureUnitIds") Set lectureUnitIds); @Query(""" From 5191ef0e7d58188cc4b721aa7aca3da51938654a Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 16:11:00 +0100 Subject: [PATCH 27/30] Fix server style errors --- .../lecture/repository/LectureTranscriptionRepository.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java index 5c903565db4c..c42a9050b3a2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java @@ -8,7 +8,6 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; @@ -22,12 +21,12 @@ public interface LectureTranscriptionRepository extends ArtemisJpaRepository { @EntityGraph(type = LOAD, attributePaths = { "segments" }) - Optional findOneWithTranscriptionSegmentsById(@Param("id") Long id); + Optional findOneWithTranscriptionSegmentsById(Long id); @EntityGraph(type = LOAD, attributePaths = { "segments" }) - Set findAllWithTranscriptionSegmentsByLectureId(@Param("lectureId") Long lectureId); + Set findAllWithTranscriptionSegmentsByLectureId(Long lectureId); @EntityGraph(type = LOAD, attributePaths = {}) - Set findAllByLectureId(@Param("lectureId") Long lectureId); + Set findAllByLectureId(Long lectureId); } From 079d088d13ed8dc1f32a8e6b5e9a3b139c188e48 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 16:49:25 +0100 Subject: [PATCH 28/30] fix server style --- .../artemis/iris/service/pyris/PyrisWebhookService.java | 2 ++ .../lecture/repository/LectureTranscriptionRepository.java | 7 ++++--- .../cit/aet/artemis/lecture/service/LectureService.java | 2 ++ .../tum/cit/aet/artemis/lecture/web/LectureResource.java | 1 + .../artemis/lecture/web/LectureTranscriptionResource.java | 6 ++++-- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java index 354166787697..eb89a3d68f5c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisWebhookService.java @@ -76,6 +76,8 @@ public PyrisWebhookService(PyrisConnectorService pyrisConnectorService, PyrisJob * adds the transcription to the vector database in Pyris * * @param transcriptions The transcription that got Updated + * @param course The course of the transcriptions + * @param lecture The lecture of the transcriptions * @return jobToken if the job was created else null */ public String addTranscriptionsToPyrisDB(Set transcriptions, Course course, Lecture lecture) { diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java index c42a9050b3a2..5c903565db4c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; @@ -21,12 +22,12 @@ public interface LectureTranscriptionRepository extends ArtemisJpaRepository { @EntityGraph(type = LOAD, attributePaths = { "segments" }) - Optional findOneWithTranscriptionSegmentsById(Long id); + Optional findOneWithTranscriptionSegmentsById(@Param("id") Long id); @EntityGraph(type = LOAD, attributePaths = { "segments" }) - Set findAllWithTranscriptionSegmentsByLectureId(Long lectureId); + Set findAllWithTranscriptionSegmentsByLectureId(@Param("lectureId") Long lectureId); @EntityGraph(type = LOAD, attributePaths = {}) - Set findAllByLectureId(Long lectureId); + Set findAllByLectureId(@Param("lectureId") Long lectureId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java index 88a7fb95d692..967d3d5fb1b0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/service/LectureService.java @@ -192,6 +192,8 @@ public void ingestLecturesInPyris(Set lectures) { * Ingest the transcriptions when triggered by the ingest transcription button * * @param transcriptions set of transcriptions to be ingested + * @param course The course containing the transcription + * @param lecture The lecture containing the transcription */ public void ingestTranscriptionInPyris(Set transcriptions, Course course, Lecture lecture) { pyrisWebhookService.ifPresent(webhookService -> webhookService.addTranscriptionsToPyrisDB(transcriptions, course, lecture)); diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index c9dd12418552..25d78067e445 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -302,6 +302,7 @@ public ResponseEntity ingestLectures(@PathVariable Long courseId, @Request * POST /lectures/{lectureId}/ingest-transcription * This endpoint is for starting the ingestion of all lectures or only one lecture when triggered in Artemis. * + * @param courseId The id of the course of the lecture * @param lectureId If this id is present then only ingest this one lecture of the respective course * @return the ResponseEntity with status 200 (OK) and a message success or null if the operation failed */ diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java index c3c5cbe497e1..0a740a635c20 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureTranscriptionResource.java @@ -63,8 +63,10 @@ public LectureTranscriptionResource(LectureTranscriptionRepository transcription /** * POST /transcription : Create a new transcription. * - * @param transcriptionDTO the transcription object to create - * @return the ResponseEntity with status 201 (Created) and with body the new transcription, or with status 400 (Bad Request) if the transcription has already an ID + * @param courseId The id of the course + * @param lectureId The id of the lecture + * @param transcriptionDTO The transcription object to create + * @return The ResponseEntity with status 201 (Created) and with body the new transcription, or with status 400 (Bad Request) if the transcription has already an ID * @throws URISyntaxException if the Location URI syntax is incorrect */ @PostMapping(value = "courses/{courseId}/lecture/{lectureId}/transcriptions") From e2c552c4846bee1b3d0858ba0d9450411afca30d Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 16:50:58 +0100 Subject: [PATCH 29/30] remove not needed auth check --- .../java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index 25d78067e445..66225e3e6fdd 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -316,7 +316,6 @@ public ResponseEntity ingestTranscriptions(@PathVariable Long courseId, @P return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.wrongLectureError", "lectureDoesNotMatchCourse")) .body(null); } - authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); Set transcriptions = lectureTranscriptionRepository.findAllWithTranscriptionSegmentsByLectureId(lectureId); if (transcriptions.isEmpty()) { return ResponseEntity.badRequest().headers(HeaderUtil.createAlert(applicationName, "artemisApp.iris.ingestionAlert.noTranscriptionError", "noTranscription")) From 53dcba7cdf6a36d80902183ce706c9a55a038a06 Mon Sep 17 00:00:00 2001 From: Sebastian Loose Date: Tue, 21 Jan 2025 16:56:34 +0100 Subject: [PATCH 30/30] fix server style --- .../lecture/repository/LectureTranscriptionRepository.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java index 5c903565db4c..c42a9050b3a2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureTranscriptionRepository.java @@ -8,7 +8,6 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; @@ -22,12 +21,12 @@ public interface LectureTranscriptionRepository extends ArtemisJpaRepository { @EntityGraph(type = LOAD, attributePaths = { "segments" }) - Optional findOneWithTranscriptionSegmentsById(@Param("id") Long id); + Optional findOneWithTranscriptionSegmentsById(Long id); @EntityGraph(type = LOAD, attributePaths = { "segments" }) - Set findAllWithTranscriptionSegmentsByLectureId(@Param("lectureId") Long lectureId); + Set findAllWithTranscriptionSegmentsByLectureId(Long lectureId); @EntityGraph(type = LOAD, attributePaths = {}) - Set findAllByLectureId(@Param("lectureId") Long lectureId); + Set findAllByLectureId(Long lectureId); }