diff --git a/pom-dependency-tree.txt b/pom-dependency-tree.txt index 34effcf2f..9a86a2079 100644 --- a/pom-dependency-tree.txt +++ b/pom-dependency-tree.txt @@ -1,4 +1,4 @@ -ai.elimu:webapp:war:2.6.17-SNAPSHOT +ai.elimu:webapp:war:2.6.20-SNAPSHOT +- ai.elimu:model:jar:model-2.0.97:compile | \- com.google.code.gson:gson:jar:2.13.0:compile | \- com.google.errorprone:error_prone_annotations:jar:2.37.0:compile diff --git a/src/main/java/ai/elimu/web/analytics/MainAnalyticsController.java b/src/main/java/ai/elimu/web/analytics/MainAnalyticsController.java index 181f8b1c5..4f7446287 100644 --- a/src/main/java/ai/elimu/web/analytics/MainAnalyticsController.java +++ b/src/main/java/ai/elimu/web/analytics/MainAnalyticsController.java @@ -1,9 +1,12 @@ package ai.elimu.web.analytics; +import ai.elimu.dao.LetterSoundAssessmentEventDao; import ai.elimu.dao.LetterSoundLearningEventDao; import ai.elimu.dao.StoryBookLearningEventDao; +import ai.elimu.dao.StudentDao; import ai.elimu.dao.VideoLearningEventDao; import ai.elimu.dao.WordLearningEventDao; +import ai.elimu.entity.analytics.LetterSoundLearningEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; @@ -17,10 +20,16 @@ @Slf4j public class MainAnalyticsController { + private final StudentDao studentDao; + + private final LetterSoundAssessmentEventDao letterSoundAssessmentEventDao; private final LetterSoundLearningEventDao letterSoundLearningEventDao; + // private final WordAssessmentEventDao wordAssessmentEventDao; private final WordLearningEventDao wordLearningEventDao; + // TODO: Numbers + private final StoryBookLearningEventDao storyBookLearningEventDao; private final VideoLearningEventDao videoLearningEventDao; @@ -29,9 +38,16 @@ public class MainAnalyticsController { public String handleRequest(Model model) { log.info("handleRequest"); + model.addAttribute("studentCount", studentDao.readCount()); + + model.addAttribute("letterSoundAssessmentEventCount", letterSoundAssessmentEventDao.readCount()); model.addAttribute("letterSoundLearningEventCount", letterSoundLearningEventDao.readCount()); + + // model.addAttribute("wordAssessmentEventCount", wordAssessmentEventDao.readCount()); model.addAttribute("wordLearningEventCount", wordLearningEventDao.readCount()); + model.addAttribute("storyBookLearningEventCount", storyBookLearningEventDao.readCount()); + model.addAttribute("videoLearningEventCount", videoLearningEventDao.readCount()); return "analytics/main"; diff --git a/src/main/java/ai/elimu/web/analytics/students/LetterSoundAssessmentEventCsvExportController.java b/src/main/java/ai/elimu/web/analytics/students/LetterSoundAssessmentEventsCsvExportController.java similarity index 98% rename from src/main/java/ai/elimu/web/analytics/students/LetterSoundAssessmentEventCsvExportController.java rename to src/main/java/ai/elimu/web/analytics/students/LetterSoundAssessmentEventsCsvExportController.java index fe8421b6c..0da480bc2 100644 --- a/src/main/java/ai/elimu/web/analytics/students/LetterSoundAssessmentEventCsvExportController.java +++ b/src/main/java/ai/elimu/web/analytics/students/LetterSoundAssessmentEventsCsvExportController.java @@ -22,7 +22,7 @@ @RequestMapping("/analytics/students/{studentId}/letter-sound-assessment-events.csv") @RequiredArgsConstructor @Slf4j -public class LetterSoundAssessmentEventCsvExportController { +public class LetterSoundAssessmentEventsCsvExportController { private final StudentDao studentDao; diff --git a/src/main/java/ai/elimu/web/analytics/LetterSoundLearningEventCsvExportController.java b/src/main/java/ai/elimu/web/analytics/students/LetterSoundLearningEventsCsvExportController.java similarity index 73% rename from src/main/java/ai/elimu/web/analytics/LetterSoundLearningEventCsvExportController.java rename to src/main/java/ai/elimu/web/analytics/students/LetterSoundLearningEventsCsvExportController.java index 3aa75feea..3a0c12f1c 100644 --- a/src/main/java/ai/elimu/web/analytics/LetterSoundLearningEventCsvExportController.java +++ b/src/main/java/ai/elimu/web/analytics/students/LetterSoundLearningEventsCsvExportController.java @@ -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 letterSoundLearningEvents = letterSoundLearningEventDao.readAll(); + Student student = studentDao.read(studentId); + log.info("student.getAndroidId(): " + student.getAndroidId()); + + List letterSoundLearningEvents = letterSoundLearningEventDao.readAll(student.getAndroidId()); 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() ); } diff --git a/src/main/java/ai/elimu/web/analytics/students/StudentController.java b/src/main/java/ai/elimu/web/analytics/students/StudentController.java index 2c35d13d2..b314cbb91 100644 --- a/src/main/java/ai/elimu/web/analytics/students/StudentController.java +++ b/src/main/java/ai/elimu/web/analytics/students/StudentController.java @@ -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 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); + + List 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 wordLearningEvents = wordLearningEventDao.readAll(student.getAndroidId()); - List wordMonthList = new ArrayList<>(); List wordEventCountList = new ArrayList<>(); if (!wordLearningEvents.isEmpty()) { - // Group event count by month (e.g. "Aug-2024", "Sep-2024") - Map eventCountByMonthMap = new HashMap<>(); - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM-yyyy"); + // Group event count by week (e.g. "2024-09", "2024-26") + Map 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); } } - model.addAttribute("wordMonthList", wordMonthList); model.addAttribute("wordEventCountList", wordEventCountList); model.addAttribute("wordLearningEvents", wordLearningEvents); // Prepare chart data - StoryBookLearningEvents List storyBookLearningEvents = storyBookLearningEventDao.readAll(student.getAndroidId()); - List storyBookMonthList = new ArrayList<>(); List storyBookEventCountList = new ArrayList<>(); if (!storyBookLearningEvents.isEmpty()) { - // Group event count by month (e.g. "Aug-2024", "Sep-2024") - Map eventCountByMonthMap = new HashMap<>(); - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM-yyyy"); + // Group event count by week (e.g. "2024-09", "2024-26") + Map 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 videoLearningEvents = videoLearningEventDao.readAll(student.getAndroidId()); - List videoMonthList = new ArrayList<>(); List videoEventCountList = new ArrayList<>(); if (!videoLearningEvents.isEmpty()) { - // Group event count by month (e.g. "Aug-2024", "Sep-2024") - Map eventCountByMonthMap = new HashMap<>(); - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM-yyyy"); + // Group event count by week (e.g. "2024-09", "2024-26") + Map 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); } } - model.addAttribute("videoMonthList", videoMonthList); model.addAttribute("videoEventCountList", videoEventCountList); model.addAttribute("videoLearningEvents", videoLearningEvents); diff --git a/src/main/java/ai/elimu/web/analytics/students/StudentListController.java b/src/main/java/ai/elimu/web/analytics/students/StudentListController.java new file mode 100644 index 000000000..868eca0ff --- /dev/null +++ b/src/main/java/ai/elimu/web/analytics/students/StudentListController.java @@ -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 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"; + } +} diff --git a/src/main/java/ai/elimu/web/analytics/students/StudentsCsvExportController.java b/src/main/java/ai/elimu/web/analytics/students/StudentsCsvExportController.java new file mode 100644 index 000000000..8d78d52e0 --- /dev/null +++ b/src/main/java/ai/elimu/web/analytics/students/StudentsCsvExportController.java @@ -0,0 +1,74 @@ +package ai.elimu.web.analytics.students; + +import ai.elimu.dao.StudentDao; +import ai.elimu.entity.analytics.students.Student; +import ai.elimu.util.AnalyticsHelper; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/analytics/students/students.csv") +@RequiredArgsConstructor +@Slf4j +public class StudentsCsvExportController { + + private final StudentDao studentDao; + + @GetMapping + public void handleRequest( + HttpServletResponse response, + OutputStream outputStream + ) throws IOException { + log.info("handleRequest"); + + List students = studentDao.readAll(); + log.info("students.size(): " + students.size()); + for (Student student : students) { + student.setAndroidId(AnalyticsHelper.redactAndroidId(student.getAndroidId())); + } + + CSVFormat csvFormat = CSVFormat.DEFAULT.builder() + .setHeader( + "id", + "android_id" + ) + .build(); + + StringWriter stringWriter = new StringWriter(); + CSVPrinter csvPrinter = new CSVPrinter(stringWriter, csvFormat); + + for (Student student : students) { + log.info("student.getId(): " + student.getId()); + + csvPrinter.printRecord( + student.getId(), + student.getAndroidId() + ); + } + csvPrinter.flush(); + csvPrinter.close(); + + String csvFileContent = stringWriter.toString(); + + response.setContentType("text/csv"); + byte[] bytes = csvFileContent.getBytes(); + response.setContentLength(bytes.length); + try { + outputStream.write(bytes); + outputStream.flush(); + outputStream.close(); + } catch (IOException ex) { + log.error(ex.getMessage()); + } + } +} diff --git a/src/main/java/ai/elimu/web/analytics/students/StudentsListController.java b/src/main/java/ai/elimu/web/analytics/students/StudentsListController.java deleted file mode 100644 index cbe8a5424..000000000 --- a/src/main/java/ai/elimu/web/analytics/students/StudentsListController.java +++ /dev/null @@ -1,66 +0,0 @@ -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.StoryBookLearningEvent; -import ai.elimu.entity.analytics.WordLearningEvent; -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 StudentsListController { - - private final StudentDao studentDao; - - private final WordLearningEventDao wordLearningEventDao; - - private final StoryBookLearningEventDao storyBookLearningEventDao; - - @GetMapping - public String handleRequest(Model model) { - log.info("handleRequest"); - - // Generate Student IDs for pre-existing learning events - for (WordLearningEvent wordLearningEvent : wordLearningEventDao.readAll()) { - log.info("wordLearningEvent.getAndroidId(): " + wordLearningEvent.getAndroidId()); - Student existingStudent = studentDao.read(wordLearningEvent.getAndroidId()); - if (existingStudent == null) { - Student student = new Student(); - student.setAndroidId(wordLearningEvent.getAndroidId()); - studentDao.create(student); - log.info("Stored Student in database with ID " + student.getId()); - } - } - for (StoryBookLearningEvent storyBookLearningEvent : storyBookLearningEventDao.readAll()) { - log.info("storyBookLearningEvent.getAndroidId(): " + storyBookLearningEvent.getAndroidId()); - Student existingStudent = studentDao.read(storyBookLearningEvent.getAndroidId()); - if (existingStudent == null) { - Student student = new Student(); - student.setAndroidId(storyBookLearningEvent.getAndroidId()); - studentDao.create(student); - log.info("Stored Student in database with ID " + student.getId()); - } - } - - List students = studentDao.readAll(); - for (Student student : students) { - student.setAndroidId(AnalyticsHelper.redactAndroidId(student.getAndroidId())); - } - model.addAttribute("students", students); - - return "analytics/students/list"; - } -} diff --git a/src/main/java/ai/elimu/web/analytics/students/WordAssessmentEventsCsvExportController.java b/src/main/java/ai/elimu/web/analytics/students/WordAssessmentEventsCsvExportController.java new file mode 100644 index 000000000..61b897c54 --- /dev/null +++ b/src/main/java/ai/elimu/web/analytics/students/WordAssessmentEventsCsvExportController.java @@ -0,0 +1,95 @@ +package ai.elimu.web.analytics.students; + +// import ai.elimu.dao.WordAssessmentEventDao; +import ai.elimu.dao.StudentDao; +// import ai.elimu.entity.analytics.WordAssessmentEvent; +import ai.elimu.entity.analytics.students.Student; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.lang.NotImplementedException; +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/students/{studentId}/word-assessment-events.csv") +@RequiredArgsConstructor +@Slf4j +public class WordAssessmentEventsCsvExportController { + + private final StudentDao studentDao; + + // private final WordAssessmentEventDao wordAssessmentEventDao; + + @GetMapping + public void handleRequest( + @PathVariable Long studentId, + HttpServletResponse response, + OutputStream outputStream + ) throws IOException { + log.info("handleRequest"); + + Student student = studentDao.read(studentId); + log.info("student.getAndroidId(): " + student.getAndroidId()); + + throw new NotImplementedException("https://github.com/elimu-ai/webapp/issues/921"); + // List wordAssessmentEvents = wordAssessmentEventDao.readAll(student.getAndroidId()); + // log.info("wordAssessmentEvents.size(): " + wordAssessmentEvents.size()); + + // CSVFormat csvFormat = CSVFormat.DEFAULT.builder() + // .setHeader( + // "id", + // "timestamp", + // "package_name", + // "letter_sound_letters", + // "letter_sound_sounds", + // "letter_sound_id", + // "mastery_score", + // "time_spent_ms", + // "additional_data" + // ) + // .build(); + + // StringWriter stringWriter = new StringWriter(); + // CSVPrinter csvPrinter = new CSVPrinter(stringWriter, csvFormat); + + // for (WordAssessmentEvent wordAssessmentEvent : wordAssessmentEvents) { + // log.info("wordAssessmentEvent.getId(): " + wordAssessmentEvent.getId()); + + // csvPrinter.printRecord( + // wordAssessmentEvent.getId(), + // wordAssessmentEvent.getTimestamp().getTimeInMillis(), + // wordAssessmentEvent.getPackageName(), + // wordAssessmentEvent.getWordLetters(), + // wordAssessmentEvent.getWordSounds(), + // wordAssessmentEvent.getWordId(), + // wordAssessmentEvent.getMasteryScore(), + // wordAssessmentEvent.getTimeSpentMs(), + // wordAssessmentEvent.getAdditionalData() + // ); + // } + // csvPrinter.flush(); + // csvPrinter.close(); + + // String csvFileContent = stringWriter.toString(); + + // response.setContentType("text/csv"); + // byte[] bytes = csvFileContent.getBytes(); + // response.setContentLength(bytes.length); + // try { + // outputStream.write(bytes); + // outputStream.flush(); + // outputStream.close(); + // } catch (IOException ex) { + // log.error(ex.getMessage()); + // } + } +} diff --git a/src/main/java/ai/elimu/web/analytics/WordLearningEventCsvExportController.java b/src/main/java/ai/elimu/web/analytics/students/WordLearningEventsCsvExportController.java similarity index 74% rename from src/main/java/ai/elimu/web/analytics/WordLearningEventCsvExportController.java rename to src/main/java/ai/elimu/web/analytics/students/WordLearningEventsCsvExportController.java index ba011754a..14400b8f0 100644 --- a/src/main/java/ai/elimu/web/analytics/WordLearningEventCsvExportController.java +++ b/src/main/java/ai/elimu/web/analytics/students/WordLearningEventsCsvExportController.java @@ -1,8 +1,9 @@ -package ai.elimu.web.analytics; +package ai.elimu.web.analytics.students; import ai.elimu.dao.WordLearningEventDao; +import ai.elimu.dao.StudentDao; import ai.elimu.entity.analytics.WordLearningEvent; -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,37 +15,40 @@ 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/word-learning-event/list/word-learning-events.csv") +@RequestMapping("/analytics/students/{studentId}/word-learning-events.csv") @RequiredArgsConstructor @Slf4j -public class WordLearningEventCsvExportController { +public class WordLearningEventsCsvExportController { + + private final StudentDao studentDao; private final WordLearningEventDao wordLearningEventDao; @GetMapping public void handleRequest( + @PathVariable Long studentId, HttpServletResponse response, OutputStream outputStream ) throws IOException { log.info("handleRequest"); - List wordLearningEvents = wordLearningEventDao.readAll(); + Student student = studentDao.read(studentId); + log.info("student.getAndroidId(): " + student.getAndroidId()); + + List wordLearningEvents = wordLearningEventDao.readAll(student.getAndroidId()); log.info("wordLearningEvents.size(): " + wordLearningEvents.size()); - for (WordLearningEvent wordLearningEvent : wordLearningEvents) { - wordLearningEvent.setAndroidId(AnalyticsHelper.redactAndroidId(wordLearningEvent.getAndroidId())); - } CSVFormat csvFormat = CSVFormat.DEFAULT.builder() .setHeader( - "id", // The Room database ID + "id", "timestamp", - "android_id", "package_name", - "word_id", "word_text", + "word_id", "learning_event_type", "additional_data" ) @@ -59,15 +63,15 @@ public void handleRequest( csvPrinter.printRecord( wordLearningEvent.getId(), wordLearningEvent.getTimestamp().getTimeInMillis(), - wordLearningEvent.getAndroidId(), wordLearningEvent.getPackageName(), - (wordLearningEvent.getWord() == null) ? null : wordLearningEvent.getWord().getId(), wordLearningEvent.getWordText(), + (wordLearningEvent.getWord() != null) ? wordLearningEvent.getWord().getId() : 0, + // wordLearningEvent.getWordId(), https://github.com/elimu-ai/webapp/issues/2113 wordLearningEvent.getLearningEventType(), wordLearningEvent.getAdditionalData() ); - csvPrinter.flush(); } + csvPrinter.flush(); csvPrinter.close(); String csvFileContent = stringWriter.toString(); diff --git a/src/main/webapp/WEB-INF/jsp/analytics/main.jsp b/src/main/webapp/WEB-INF/jsp/analytics/main.jsp index 90c1aef2c..cde28240b 100644 --- a/src/main/webapp/WEB-INF/jsp/analytics/main.jsp +++ b/src/main/webapp/WEB-INF/jsp/analytics/main.jsp @@ -3,79 +3,65 @@ -
-
-
Learning events
-
- -
-
-
- text_format Letter-sound correspondences -
- -
-
- -
-
-
- sms Words -
- -
-
- -
-
-
- book Storybooks -
- -
-
+

+
- movie Videos -
- -
-
- -
-
Assessment events
-
- -
-
-
- text_format Letters -
- -
-
- -
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Data TypeTotal Count
🎼 Letter-sound assessment events${letterSoundAssessmentEventCount}
🎼 Letter-sound learning events${letterSoundLearningEventCount}
🔤 Word assessment events${wordAssessmentEventCount}
🔤 Word learning events${wordLearningEventCount}
🔢 Number assessment events${numberAssessmentEventCount}
🔢 Number learning events${numberLearningEventCount}
📚 Storybook learning events${storyBookLearningEventCount}
🎬 Video learning events${videoLearningEventCount}
+
diff --git a/src/main/webapp/WEB-INF/jsp/analytics/students/id.jsp b/src/main/webapp/WEB-INF/jsp/analytics/students/id.jsp index 92dd8a66a..85477dd00 100644 --- a/src/main/webapp/WEB-INF/jsp/analytics/students/id.jsp +++ b/src/main/webapp/WEB-INF/jsp/analytics/students/id.jsp @@ -14,8 +14,8 @@

Student ID

#${student.id}
-
-

Android ID

+ +

Android ID

${student.androidId}
@@ -128,28 +128,64 @@ }); }); -
Assessment events (${fn:length(letterSoundAssessmentEvents)})
+
Letter-sound assessment events (${fn:length(letterSoundAssessmentEvents)})
...
-
Learning events (${fn:length(letterSoundLearningEvents)})
+ + Export to CSVvertical_align_bottom + + +
Letter-sound learning events (${fn:length(letterSoundLearningEvents)})
...
🔤 Words
-
Assessment events (${fn:length(wordAssessmentEvents)})
+ + Export to CSVvertical_align_bottom + + +
Word assessment events (${fn:length(wordAssessmentEvents)})
...
-
Learning events (${fn:length(wordLearningEvents)})
+ + Export to CSVvertical_align_bottom + + +
Word learning events (${fn:length(wordLearningEvents)})
+
Students (${fn:length(students)})
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/analytics/word-learning-event/list.jsp b/src/main/webapp/WEB-INF/jsp/analytics/word-learning-event/list.jsp deleted file mode 100644 index e17e39859..000000000 --- a/src/main/webapp/WEB-INF/jsp/analytics/word-learning-event/list.jsp +++ /dev/null @@ -1,89 +0,0 @@ - - WordLearningEvents (${fn:length(wordLearningEvents)}) - - - -
-
- - - -
-
- -
- - Export to CSVvertical_align_bottom - - - -
Student ID
- - - - - - - - - - - - - - - - - - - - -
timestampandroid_idpackage_nameword_idword_textlearning_event_type
- - - ${wordLearningEvent.androidId} - - - ${wordLearningEvent.application.packageName} - - - - ${wordLearningEvent.word.text} - - - "" - - ${wordLearningEvent.learningEventType} -
-
-
diff --git a/src/test/java/selenium/analytics/MainAnalyticsPage.java b/src/test/java/selenium/analytics/MainAnalyticsPage.java index 1ab21dd7b..c92463eef 100644 --- a/src/test/java/selenium/analytics/MainAnalyticsPage.java +++ b/src/test/java/selenium/analytics/MainAnalyticsPage.java @@ -18,11 +18,6 @@ public MainAnalyticsPage(WebDriver driver) { ErrorHelper.verifyNoScriptOrMarkupError(driver); } - public void pressWordLearningEventsLink() { - WebElement link = driver.findElement(By.id("wordLearningEventsLink")); - link.click(); - } - public void pressStoryBookLearningEventsLink() { WebElement link = driver.findElement(By.id("storyBookLearningEventsLink")); link.click(); diff --git a/src/test/java/selenium/analytics/WordLearningEventsPage.java b/src/test/java/selenium/analytics/WordLearningEventsPage.java deleted file mode 100644 index 3059751d0..000000000 --- a/src/test/java/selenium/analytics/WordLearningEventsPage.java +++ /dev/null @@ -1,19 +0,0 @@ -package selenium.analytics; - -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; - -import selenium.util.ErrorHelper; - -public class WordLearningEventsPage { - - private WebDriver driver; - - public WordLearningEventsPage(WebDriver driver) { - this.driver = driver; - - driver.findElement(By.id("wordLearningEventsPage")); - - ErrorHelper.verifyNoScriptOrMarkupError(driver); - } -} diff --git a/src/test/java/selenium/analytics/WordLearningEventsPageTest.java b/src/test/java/selenium/analytics/WordLearningEventsPageTest.java deleted file mode 100644 index 2fed90c8b..000000000 --- a/src/test/java/selenium/analytics/WordLearningEventsPageTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package selenium.analytics; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import lombok.extern.slf4j.Slf4j; -import selenium.util.DomainHelper; - -@Slf4j -public class WordLearningEventsPageTest { - - private WebDriver driver; - - @BeforeEach - public void setUp() { - log.info("setUp"); - - ChromeOptions chromeOptions = new ChromeOptions(); - - // Read "headless" property set on the command line: - // mvn clean verify -P regression-test-ui -D headless=true - String headlessSystemProperty = System.getProperty("headless"); - log.info("headlessSystemProperty: \"" + headlessSystemProperty + "\""); - if ("true".equals(headlessSystemProperty)) { - chromeOptions.addArguments("headless"); - } - - driver = new ChromeDriver(chromeOptions); - - driver.get(DomainHelper.getBaseUrl() + "/analytics"); - log.info("driver.getCurrentUrl(): " + driver.getCurrentUrl()); - } - - @AfterEach - public void tearDown() { - log.info("tearDown"); - - driver.quit(); - } - - @Test - public void testWordLearningEventsPage() { - log.info("testWordLearningEventsPage"); - - MainAnalyticsPage mainAnalyticsPage = new MainAnalyticsPage(driver); - mainAnalyticsPage.pressWordLearningEventsLink(); - - WordLearningEventsPage wordLearningEventsPage = new WordLearningEventsPage(driver); - } -}