Skip to content

Commit

Permalink
add courseId and courseName to course and exam metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
b-fein committed Sep 22, 2023
1 parent 92fbaf3 commit 7500934
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 35 deletions.
74 changes: 49 additions & 25 deletions src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@
import de.tum.in.www1.artemis.repository.StudentExamRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MultiGauge;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.*;

Check warning on line 44 in src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java

View check run for this annotation

Teamscale / teamscale-findings

src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java#L44

[New] Star import of `io.micrometer.core.instrument.*` should not be used https://teamscale.io/findings.html#details/GitHub-ls1intum-Artemis?t=metrics-add-course-id-tags%3AHEAD&id=87C64433F1FC336E0088CA8BD1134DF3

@Component
public class MetricsBean {
Expand All @@ -57,6 +54,8 @@ public class MetricsBean {

private static final String ARTEMIS_HEALTH_TAG = "healthindicator";

private static final String NO_SEMESTER_TAG = "No semester";

private static final int LOGGING_DELAY_SECONDS = 10;

/**
Expand Down Expand Up @@ -331,7 +330,7 @@ interface NowEndDateActiveUserNamesMetricsEntryFunction {
* - the current date
* - an end date (which is the current date + one of the values of MINUTE_RANGES_LOOKAHEAD), and
* - a list of active users (that is, users that created a submission within the last 14 days).
*
* <p>
* The implementing method may decide to ignore certain arguments.
* The method returns a list of ExerciseTypeMetricsEntries, that each correspond to an exercise type and a value.
*
Expand All @@ -350,7 +349,7 @@ interface NowEndDateActiveUserNamesIntegerFunction {
* This interface is used to calculate and expose metrics that are based on
* - the current date, and
* - an end date (which is the current date + one of the values of MINUTE_RANGES_LOOKAHEAD).
*
* <p>
* The implementing method may decide to ignore certain arguments.
* The method returns an integer, representing the corresponding value.
*
Expand Down Expand Up @@ -471,32 +470,57 @@ public void updatePublicArtemisMetrics() {
}

private void updateActiveUserMultiGauge(ZonedDateTime now) {
var activeUserPeriodsInDays = new Integer[] { 1, 7, 14, 30 };
activeUserMultiGauge.register(Stream.of(activeUserPeriodsInDays)
.map(periodInDays -> MultiGauge.Row.of(Tags.of("period", periodInDays.toString()), statisticsRepository.countActiveUsers(now.minusDays(periodInDays), now)))
// A mutable list is required here because otherwise the values can not be updated correctly
.collect(Collectors.toCollection(ArrayList::new)), true);
final Integer[] activeUserPeriodsInDays = new Integer[] { 1, 7, 14, 30 };

// A mutable list is required here because otherwise the values can not be updated correctly
final List<MultiGauge.Row<?>> gauges = Stream.of(activeUserPeriodsInDays).map(periodInDays -> {
final Tags tags = Tags.of("period", periodInDays.toString());
final long activeUsers = statisticsRepository.countActiveUsers(now.minusDays(periodInDays), now);
return MultiGauge.Row.of(tags, activeUsers);
}).collect(Collectors.toCollection(ArrayList::new));

activeUserMultiGauge.register(gauges, true);
}

private void updateStudentsCourseMultiGauge(List<Course> activeCourses) {
studentsCourseGauge.register(
activeCourses.stream().map(course -> MultiGauge.Row.of(Tags.of("courseName", course.getTitle(), "semester", course.getSemester()), course.getNumberOfStudents()))
// A mutable list is required here because otherwise the values can not be updated correctly
.collect(Collectors.toCollection(ArrayList::new)),
true);
// A mutable list is required here because otherwise the values can not be updated correctly
final List<MultiGauge.Row<?>> gauges = activeCourses.stream().map(course -> {
final Tags tags = Tags.of("courseId", Long.toString(course.getId()), "courseName", course.getTitle(), "semester", course.getSemester());
final long studentCount = course.getNumberOfStudents();
return MultiGauge.Row.of(tags, studentCount);
}).collect(Collectors.toCollection(ArrayList::new));

studentsCourseGauge.register(gauges, true);
}

private void updateStudentsExamMultiGauge(List<Exam> examsInActiveCourses, List<Course> courses) {
studentsExamGauge.register(examsInActiveCourses.stream()
.map(exam -> MultiGauge.Row.of(Tags.of("examName", exam.getTitle(), "semester", getExamSemester(courses, exam)),
studentExamRepository.findByExamId(exam.getId()).size()))
// A mutable list is required here because otherwise the values can not be updated correctly
.collect(Collectors.toCollection(ArrayList::new)), true);
// A mutable list is required here because otherwise the values can not be updated correctly
final List<MultiGauge.Row<?>> gauges = examsInActiveCourses.stream().map(exam -> {
final Tags tags = getExamMetricTags(courses, exam);
final long studentCount = studentExamRepository.countByExamId(exam.getId());
return MultiGauge.Row.of(tags, studentCount);
}).collect(Collectors.toCollection(ArrayList::new));

studentsExamGauge.register(gauges, true);
}

private Tags getExamMetricTags(final List<Course> courses, final Exam exam) {
final Optional<Course> examCourse = findExamCourse(courses, exam);

final List<Tag> tags = new ArrayList<>();
examCourse.ifPresent(course -> {
tags.add(Tag.of("courseId", Long.toString(course.getId())));
tags.add(Tag.of("courseName", course.getTitle()));
});
tags.add(Tag.of("examId", Long.toString(exam.getId())));
tags.add(Tag.of("examName", exam.getTitle()));
tags.add(Tag.of("semester", examCourse.map(Course::getSemester).orElse(NO_SEMESTER_TAG)));

return Tags.of(tags);
}

private String getExamSemester(final List<Course> courses, final Exam exam) {
// The exam.getCourse() is not populated (the semester property is not set) -> Use course from the courses list, which contains the semester
return courses.stream().filter(course -> Objects.equals(course.getId(), exam.getCourse().getId())).findAny().map(Course::getSemester).orElse("No semester");
private Optional<Course> findExamCourse(final List<Course> courses, final Exam exam) {
return courses.stream().filter(course -> Objects.equals(course.getId(), exam.getCourse().getId())).findAny();
}

private void updateActiveExerciseMultiGauge() {
Expand Down Expand Up @@ -530,7 +554,7 @@ private void extractExerciseTypeMetricsAndAddToMetricsResults(List<ExerciseTypeM
private void ensureCourseInformationIsSet(List<Course> courses) {
courses.forEach(course -> {
if (course.getSemester() == null) {
course.setSemester("No semester");
course.setSemester(NO_SEMESTER_TAG);
}
if (course.getTitle() == null) {
if (course.getShortName() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,18 @@ public interface StudentExamRepository extends JpaRepository<StudentExam, Long>
SELECT se
FROM StudentExam se
WHERE se.exam.id = :examId
AND se.testRun = FALSE
AND se.testRun = FALSE
""")
Set<StudentExam> findByExamId(@Param("examId") long examId);

@Query("""
SELECT COUNT(DISTINCT se)
FROM StudentExam se
WHERE se.exam.id = :examId
AND se.testRun = FALSE
""")
long countByExamId(@Param("examId") long examId);

Check warning on line 84 in src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java

View check run for this annotation

Teamscale / teamscale-findings

src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java#L84

[New] In this file 30 interface comments are missing. Consider adding explanatory comments or restricting the visibility. View in Teamscale: https://teamscale.io/activity.html#details/GitHub-ls1intum-Artemis?t=metrics-add-course-id-tags%3A1695421246000

@Query("""
SELECT se
FROM StudentExam se
Expand Down
24 changes: 15 additions & 9 deletions src/test/java/de/tum/in/www1/artemis/config/MetricsBeanTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@ void testPublicMetricsExams() {

metricsBean.updatePublicArtemisMetrics();

var totalNumberOfExams = examRepository.count();
var numberOfActiveExams = examRepository.countAllActiveExams(ZonedDateTime.now());
long totalNumberOfExams = examRepository.count();
long numberOfActiveExams = examRepository.countAllActiveExams(ZonedDateTime.now());

// Assert that there is at least one non-active exam in the database so that the values returned from the metrics are different
assertThat(numberOfActiveExams.intValue()).isNotEqualTo(totalNumberOfExams);
assertThat(numberOfActiveExams).isNotEqualTo(totalNumberOfExams);

assertMetricEquals(totalNumberOfExams, "artemis.statistics.public.exams");
assertMetricEquals(numberOfActiveExams, "artemis.statistics.public.active_exams");
Expand All @@ -240,9 +240,10 @@ void testPublicMetricsCourseAndExamStudents() {
var users = userUtilService.addUsers(TEST_PREFIX, 3, 0, 0, 0);

var course1 = courseUtilService.createCourse();
course1.setTitle("Course 1");
course1.setSemester(null);
course1.setStudentGroupName(TEST_PREFIX + "course1Students");
courseRepository.save(course1);
course1 = courseRepository.save(course1);
var exam1 = examUtilService.addExamWithModellingAndTextAndFileUploadAndQuizAndEmptyGroup(course1);
exam1.setStartDate(ZonedDateTime.now().minusMinutes(1));
exam1.setTitle("exam" + UUID.randomUUID());
Expand All @@ -253,7 +254,7 @@ void testPublicMetricsCourseAndExamStudents() {
var course2 = courseUtilService.createCourse();
course2.setSemester("WS 2023/24");
course2.setStudentGroupName(TEST_PREFIX + "course2Students");
courseRepository.save(course2);
course2 = courseRepository.save(course2);
var exam2 = examUtilService.addExamWithModellingAndTextAndFileUploadAndQuizAndEmptyGroup(course2);
exam2.setTitle("exam" + UUID.randomUUID());
exam2.setStartDate(ZonedDateTime.now().minusMinutes(1));
Expand All @@ -269,11 +270,16 @@ void testPublicMetricsCourseAndExamStudents() {

metricsBean.updatePublicArtemisMetrics();

assertMetricEquals(3, "artemis.statistics.public.course_students", "courseName", course1.getTitle(), "semester", "No semester");
assertMetricEquals(1, "artemis.statistics.public.course_students", "courseName", course2.getTitle(), "semester", "WS 2023/24");
String course1Id = Long.toString(course1.getId());
String course2Id = Long.toString(course2.getId());

assertMetricEquals(3, "artemis.statistics.public.course_students", "courseId", course1Id, "courseName", course1.getTitle(), "semester", "No semester");
assertMetricEquals(1, "artemis.statistics.public.course_students", "courseId", course2Id, "courseName", course2.getTitle(), "semester", "WS 2023/24");

assertMetricEquals(2, "artemis.statistics.public.exam_students", "examName", exam1.getTitle(), "semester", "No semester");
assertMetricEquals(1, "artemis.statistics.public.exam_students", "examName", exam2.getTitle(), "semester", "WS 2023/24");
assertMetricEquals(2, "artemis.statistics.public.exam_students", "courseId", course1Id, "courseName", course1.getTitle(), "examId", Long.toString(exam1.getId()),
"examName", exam1.getTitle(), "semester", "No semester");
assertMetricEquals(1, "artemis.statistics.public.exam_students", "courseId", course2Id, "examId", Long.toString(exam2.getId()), "examName", exam2.getTitle(), "semester",
"WS 2023/24");
}

@Test
Expand Down

0 comments on commit 7500934

Please sign in to comment.