-
-
Notifications
You must be signed in to change notification settings - Fork 70
feat: export csvs #2204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: export csvs #2204
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,9 @@ | ||||||||||||||||||||||||||||||
| package ai.elimu.web.analytics; | ||||||||||||||||||||||||||||||
| package ai.elimu.web.analytics.students; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import ai.elimu.dao.LetterSoundLearningEventDao; | ||||||||||||||||||||||||||||||
| import ai.elimu.dao.StudentDao; | ||||||||||||||||||||||||||||||
| import ai.elimu.entity.analytics.LetterSoundLearningEvent; | ||||||||||||||||||||||||||||||
| import ai.elimu.util.AnalyticsHelper; | ||||||||||||||||||||||||||||||
| import ai.elimu.entity.analytics.students.Student; | ||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||
| import java.io.OutputStream; | ||||||||||||||||||||||||||||||
|
|
@@ -14,39 +15,41 @@ | |||||||||||||||||||||||||||||
| import org.apache.commons.csv.CSVPrinter; | ||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Controller; | ||||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @Controller | ||||||||||||||||||||||||||||||
| @RequestMapping("/analytics/letter-sound-learning-event/list/letter-sound-learning-events.csv") | ||||||||||||||||||||||||||||||
| @RequestMapping("/analytics/students/{studentId}/letter-sound-learning-events.csv") | ||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||
| public class LetterSoundLearningEventCsvExportController { | ||||||||||||||||||||||||||||||
| public class LetterSoundLearningEventsCsvExportController { | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private final StudentDao studentDao; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private final LetterSoundLearningEventDao letterSoundLearningEventDao; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @GetMapping | ||||||||||||||||||||||||||||||
| public void handleRequest( | ||||||||||||||||||||||||||||||
| @PathVariable Long studentId, | ||||||||||||||||||||||||||||||
| HttpServletResponse response, | ||||||||||||||||||||||||||||||
| OutputStream outputStream | ||||||||||||||||||||||||||||||
| ) throws IOException { | ||||||||||||||||||||||||||||||
| log.info("handleRequest"); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| List<LetterSoundLearningEvent> letterSoundLearningEvents = letterSoundLearningEventDao.readAll(); | ||||||||||||||||||||||||||||||
| Student student = studentDao.read(studentId); | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add null check for student entity. The controller should handle the case where a student with the given ID doesn't exist. Student student = studentDao.read(studentId);
+ if (student == null) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents🛠️ Refactor suggestion Add null check for student. The code doesn't handle the case where Student student = studentDao.read(studentId);
+if (student == null) {
+ log.warn("Student not found with ID: " + studentId);
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "Student not found");
+ return;
+}
log.info("student.getAndroidId(): " + student.getAndroidId());📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| log.info("student.getAndroidId(): " + student.getAndroidId()); | ||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+40
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix privacy issue: Android ID logged without redaction. The Android ID is being logged directly without redaction, which could compromise user privacy. - log.info("student.getAndroidId(): " + student.getAndroidId());
+ log.info("student.getAndroidId(): " + AnalyticsHelper.redactAndroidId(student.getAndroidId()));📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSecurity concern: Avoid logging unredacted Android IDs. The code logs the actual Android ID without redaction, which could expose sensitive user data in log files. Consider redacting the Android ID before logging or removing this log statement entirely. -log.info("student.getAndroidId(): " + student.getAndroidId());
+log.info("student.getAndroidId(): " + AnalyticsHelper.redactAndroidId(student.getAndroidId()));Or simply remove the log statement if it's not essential for debugging. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| List<LetterSoundLearningEvent> letterSoundLearningEvents = letterSoundLearningEventDao.readAll(student.getAndroidId()); | ||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null check for student lookup. The code doesn't handle the case where a student with the given ID doesn't exist, which could result in a NullPointerException. Student student = studentDao.read(studentId);
+if (student == null) {
+ log.warn("Student not found with ID: " + studentId);
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "Student not found");
+ return;
+}
log.info("student.getAndroidId(): " + student.getAndroidId());📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| log.info("letterSoundLearningEvents.size(): " + letterSoundLearningEvents.size()); | ||||||||||||||||||||||||||||||
| for (LetterSoundLearningEvent letterSoundLearningEvent : letterSoundLearningEvents) { | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.setAndroidId(AnalyticsHelper.redactAndroidId(letterSoundLearningEvent.getAndroidId())); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| CSVFormat csvFormat = CSVFormat.DEFAULT.builder() | ||||||||||||||||||||||||||||||
| .setHeader( | ||||||||||||||||||||||||||||||
| "id", // The Room database ID | ||||||||||||||||||||||||||||||
| "id", | ||||||||||||||||||||||||||||||
| "timestamp", | ||||||||||||||||||||||||||||||
| "android_id", | ||||||||||||||||||||||||||||||
| "package_name", | ||||||||||||||||||||||||||||||
| // "letter_sound_letters", | ||||||||||||||||||||||||||||||
| // "letter_sound_sounds", | ||||||||||||||||||||||||||||||
| "letter_sound_id", | ||||||||||||||||||||||||||||||
| "letter_sound_letters", | ||||||||||||||||||||||||||||||
| "letter_sound_sounds", | ||||||||||||||||||||||||||||||
| "learning_event_type", | ||||||||||||||||||||||||||||||
| "additional_data" | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||
|
|
@@ -60,12 +63,10 @@ public void handleRequest( | |||||||||||||||||||||||||||||
| csvPrinter.printRecord( | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.getId(), | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.getTimestamp().getTimeInMillis(), | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.getAndroidId(), | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.getPackageName(), | ||||||||||||||||||||||||||||||
| // letterSoundLearningEvent.getLetterSoundLetters(), | ||||||||||||||||||||||||||||||
| // letterSoundLearningEvent.getLetterSoundSounds(), | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.getLetterSoundId(), | ||||||||||||||||||||||||||||||
| new String[] {}, // TODO | ||||||||||||||||||||||||||||||
| new String[] {}, // TODO | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.getLearningEventType(), | ||||||||||||||||||||||||||||||
| letterSoundLearningEvent.getAdditionalData() | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -60,6 +60,21 @@ public String handleRequest(@PathVariable Long studentId, Model model) { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("numeracySkills", NumeracySkill.values()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Generate a list of weeks from 6 months ago until now | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> weekList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-ww"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendar6MonthsAgo = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| calendar6MonthsAgo.add(Calendar.MONTH, -6); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendarNow = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar week = calendar6MonthsAgo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!week.after(calendarNow)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String weekAsString = simpleDateFormat.format(week.getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| weekList.add(weekAsString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| week.add(Calendar.WEEK_OF_YEAR, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("weekList", weekList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+76
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Address SimpleDateFormat pattern and time zone issues. The
Consider using - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-ww");
+ DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("yyyy-ww", Locale.US);
+ ZoneId serverTimeZone = ZoneId.systemDefault();Or better yet, use - List<String> weekList = new ArrayList<>();
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-ww");
- Calendar calendar6MonthsAgo = Calendar.getInstance();
- calendar6MonthsAgo.add(Calendar.MONTH, -6);
- Calendar calendarNow = Calendar.getInstance();
- Calendar week = calendar6MonthsAgo;
- while (!week.after(calendarNow)) {
- String weekAsString = simpleDateFormat.format(week.getTime());
- weekList.add(weekAsString);
- week.add(Calendar.WEEK_OF_YEAR, 1);
- }
+ List<String> weekList = new ArrayList<>();
+ LocalDate sixMonthsAgo = LocalDate.now().minusMonths(6);
+ LocalDate now = LocalDate.now();
+ LocalDate current = sixMonthsAgo.with(DayOfWeek.MONDAY); // Start from Monday of the week
+
+ while (!current.isAfter(now)) {
+ String weekAsString = current.format(DateTimeFormatter.ofPattern("yyyy-ww"));
+ weekList.add(weekAsString);
+ current = current.plusWeeks(1);
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<LetterSoundAssessmentEvent> letterSoundAssessmentEvents = letterSoundAssessmentEventDao.readAll(student.getAndroidId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("letterSoundAssessmentEvents", letterSoundAssessmentEvents); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -69,99 +84,69 @@ public String handleRequest(@PathVariable Long studentId, Model model) { | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Prepare chart data - WordLearningEvents | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<WordLearningEvent> wordLearningEvents = wordLearningEventDao.readAll(student.getAndroidId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> wordMonthList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Integer> wordEventCountList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!wordLearningEvents.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Group event count by month (e.g. "Aug-2024", "Sep-2024") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Integer> eventCountByMonthMap = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM-yyyy"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Group event count by week (e.g. "2024-09", "2024-26") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Integer> eventCountByWeekMap = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (WordLearningEvent event : wordLearningEvents) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String eventMonth = simpleDateFormat.format(event.getTimestamp().getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventCountByMonthMap.put(eventMonth, eventCountByMonthMap.getOrDefault(eventMonth, 0) + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String eventWeek = simpleDateFormat.format(event.getTimestamp().getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventCountByWeekMap.put(eventWeek, eventCountByWeekMap.getOrDefault(eventWeek, 0) + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Iterate each month from 4 years ago until now | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendar4YearsAgo = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| calendar4YearsAgo.add(Calendar.YEAR, -4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendarNow = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar month = calendar4YearsAgo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!month.after(calendarNow)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String monthAsString = simpleDateFormat.format(month.getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| wordMonthList.add(monthAsString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| wordEventCountList.add(eventCountByMonthMap.getOrDefault(monthAsString, 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Increase the date by 1 month | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| month.add(Calendar.MONTH, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Iterate each week from 6 months ago until now | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| week = calendar6MonthsAgo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!week.after(calendarNow)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String weekAsString = simpleDateFormat.format(week.getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| wordEventCountList.add(eventCountByWeekMap.getOrDefault(weekAsString, 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| week.add(Calendar.WEEK_OF_YEAR, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+97
to
102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix potential Calendar mutation issue. The code reuses the same Calendar instances ( Apply this fix to ensure Calendar independence: - // Iterate each week from 6 months ago until now
- week = calendar6MonthsAgo;
- while (!week.after(calendarNow)) {
- String weekAsString = simpleDateFormat.format(week.getTime());
- wordEventCountList.add(eventCountByWeekMap.getOrDefault(weekAsString, 0));
- week.add(Calendar.WEEK_OF_YEAR, 1);
- }
+ // Iterate each week from 6 months ago until now
+ week = (Calendar) calendar6MonthsAgo.clone();
+ while (!week.after(calendarNow)) {
+ String weekAsString = simpleDateFormat.format(week.getTime());
+ wordEventCountList.add(eventCountByWeekMap.getOrDefault(weekAsString, 0));
+ week.add(Calendar.WEEK_OF_YEAR, 1);
+ }The same fix should be applied to lines 120-125 and 143-148 for the other event types. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("wordMonthList", wordMonthList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("wordEventCountList", wordEventCountList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("wordLearningEvents", wordLearningEvents); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Prepare chart data - StoryBookLearningEvents | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<StoryBookLearningEvent> storyBookLearningEvents = storyBookLearningEventDao.readAll(student.getAndroidId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> storyBookMonthList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Integer> storyBookEventCountList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!storyBookLearningEvents.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Group event count by month (e.g. "Aug-2024", "Sep-2024") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Integer> eventCountByMonthMap = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM-yyyy"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Group event count by week (e.g. "2024-09", "2024-26") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Integer> eventCountByWeekMap = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (StoryBookLearningEvent event : storyBookLearningEvents) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String eventMonth = simpleDateFormat.format(event.getTimestamp().getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventCountByMonthMap.put(eventMonth, eventCountByMonthMap.getOrDefault(eventMonth, 0) + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String eventWeek = simpleDateFormat.format(event.getTimestamp().getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventCountByWeekMap.put(eventWeek, eventCountByWeekMap.getOrDefault(eventWeek, 0) + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Iterate each month from 4 years ago until now | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendar4YearsAgo = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| calendar4YearsAgo.add(Calendar.YEAR, -4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendarNow = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar month = calendar4YearsAgo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!month.after(calendarNow)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String monthAsString = simpleDateFormat.format(month.getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| storyBookMonthList.add(monthAsString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| storyBookEventCountList.add(eventCountByMonthMap.getOrDefault(monthAsString, 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Increase the date by 1 month | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| month.add(Calendar.MONTH, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Iterate each week from 6 months ago until now | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| week = calendar6MonthsAgo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!week.after(calendarNow)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String weekAsString = simpleDateFormat.format(week.getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| storyBookEventCountList.add(eventCountByWeekMap.getOrDefault(weekAsString, 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| week.add(Calendar.WEEK_OF_YEAR, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("storyBookMonthList", storyBookMonthList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("storyBookEventCountList", storyBookEventCountList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("storyBookLearningEvents", storyBookLearningEvents); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Prepare chart data - VideoLearningEvents | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<VideoLearningEvent> videoLearningEvents = videoLearningEventDao.readAll(student.getAndroidId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> videoMonthList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Integer> videoEventCountList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!videoLearningEvents.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Group event count by month (e.g. "Aug-2024", "Sep-2024") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Integer> eventCountByMonthMap = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM-yyyy"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Group event count by week (e.g. "2024-09", "2024-26") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Integer> eventCountByWeekMap = new HashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (VideoLearningEvent event : videoLearningEvents) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String eventMonth = simpleDateFormat.format(event.getTimestamp().getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventCountByMonthMap.put(eventMonth, eventCountByMonthMap.getOrDefault(eventMonth, 0) + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String eventWeek = simpleDateFormat.format(event.getTimestamp().getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventCountByWeekMap.put(eventWeek, eventCountByWeekMap.getOrDefault(eventWeek, 0) + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Iterate each month from 4 years ago until now | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendar4YearsAgo = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| calendar4YearsAgo.add(Calendar.YEAR, -4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar calendarNow = Calendar.getInstance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Calendar month = calendar4YearsAgo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!month.after(calendarNow)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String monthAsString = simpleDateFormat.format(month.getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| videoMonthList.add(monthAsString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| videoEventCountList.add(eventCountByMonthMap.getOrDefault(monthAsString, 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Increase the date by 1 month | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| month.add(Calendar.MONTH, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Iterate each week from 6 months ago until now | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| week = calendar6MonthsAgo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!week.after(calendarNow)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| String weekAsString = simpleDateFormat.format(week.getTime()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| videoEventCountList.add(eventCountByWeekMap.getOrDefault(weekAsString, 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| week.add(Calendar.WEEK_OF_YEAR, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+89
to
148
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Eliminate code duplication and improve maintainability. The same event processing logic is repeated three times for different event types, violating the DRY principle. This makes the code harder to maintain and increases the risk of inconsistencies. Extract the common logic into a reusable method: + private <T> List<Integer> processLearningEvents(List<T> events,
+ Function<T, Calendar> timestampExtractor,
+ SimpleDateFormat dateFormat,
+ Calendar startCalendar,
+ Calendar endCalendar) {
+ List<Integer> eventCountList = new ArrayList<>();
+ if (!events.isEmpty()) {
+ Map<String, Integer> eventCountByWeekMap = new HashMap<>();
+ for (T event : events) {
+ String eventWeek = dateFormat.format(timestampExtractor.apply(event).getTime());
+ eventCountByWeekMap.put(eventWeek, eventCountByWeekMap.getOrDefault(eventWeek, 0) + 1);
+ }
+
+ Calendar week = (Calendar) startCalendar.clone();
+ while (!week.after(endCalendar)) {
+ String weekAsString = dateFormat.format(week.getTime());
+ eventCountList.add(eventCountByWeekMap.getOrDefault(weekAsString, 0));
+ week.add(Calendar.WEEK_OF_YEAR, 1);
+ }
+ }
+ return eventCountList;
+ }Then replace the repeated code blocks: - // Prepare chart data - WordLearningEvents
- List<WordLearningEvent> wordLearningEvents = wordLearningEventDao.readAll(student.getAndroidId());
- List<Integer> wordEventCountList = new ArrayList<>();
- if (!wordLearningEvents.isEmpty()) {
- // Group event count by week (e.g. "2024-09", "2024-26")
- Map<String, Integer> eventCountByWeekMap = new HashMap<>();
- for (WordLearningEvent event : wordLearningEvents) {
- String eventWeek = simpleDateFormat.format(event.getTimestamp().getTime());
- eventCountByWeekMap.put(eventWeek, eventCountByWeekMap.getOrDefault(eventWeek, 0) + 1);
- }
-
- // Iterate each week from 6 months ago until now
- week = calendar6MonthsAgo;
- while (!week.after(calendarNow)) {
- String weekAsString = simpleDateFormat.format(week.getTime());
- wordEventCountList.add(eventCountByWeekMap.getOrDefault(weekAsString, 0));
- week.add(Calendar.WEEK_OF_YEAR, 1);
- }
- }
+ List<WordLearningEvent> wordLearningEvents = wordLearningEventDao.readAll(student.getAndroidId());
+ List<Integer> wordEventCountList = processLearningEvents(
+ wordLearningEvents,
+ WordLearningEvent::getTimestamp,
+ simpleDateFormat,
+ calendar6MonthsAgo,
+ calendarNow
+ );🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("videoMonthList", videoMonthList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("videoEventCountList", videoEventCountList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.addAttribute("videoLearningEvents", videoLearningEvents); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package ai.elimu.web.analytics.students; | ||
|
|
||
| import ai.elimu.dao.StoryBookLearningEventDao; | ||
| import ai.elimu.dao.StudentDao; | ||
| import ai.elimu.dao.WordLearningEventDao; | ||
| import ai.elimu.entity.analytics.students.Student; | ||
| import ai.elimu.util.AnalyticsHelper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
|
|
||
| @Controller | ||
| @RequestMapping("/analytics/students") | ||
| @RequiredArgsConstructor | ||
| @Slf4j | ||
| public class StudentListController { | ||
|
|
||
| private final StudentDao studentDao; | ||
|
|
||
| @GetMapping | ||
| public String handleRequest(Model model) { | ||
| log.info("handleRequest"); | ||
|
|
||
| List<Student> students = studentDao.readAll(); | ||
| log.info("students.size(): " + students.size()); | ||
| for (Student student : students) { | ||
| student.setAndroidId(AnalyticsHelper.redactAndroidId(student.getAndroidId())); | ||
| } | ||
| model.addAttribute("students", students); | ||
|
|
||
| return "analytics/students/list"; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical issue: Uncomment WordAssessmentEventDao or remove JSP reference.
The
WordAssessmentEventDaois commented out, but the JSP file expects${wordAssessmentEventCount}. This inconsistency will cause the JSP to display an empty value or error.Either uncomment and properly inject the DAO:
Or remove the corresponding row from the JSP table.
📝 Committable suggestion
🤖 Prompt for AI Agents