-
-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(analytics): add video learning events to dashboard (#1923)
- Loading branch information
Showing
10 changed files
with
292 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
src/main/java/ai/elimu/web/analytics/VideoLearningEventCsvExportController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package ai.elimu.web.analytics; | ||
|
||
import ai.elimu.dao.VideoLearningEventDao; | ||
import ai.elimu.model.analytics.VideoLearningEvent; | ||
import ai.elimu.util.AnalyticsHelper; | ||
|
||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.io.StringWriter; | ||
import java.util.List; | ||
|
||
import org.apache.logging.log4j.Logger; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
import javax.servlet.http.HttpServletResponse; | ||
import org.apache.commons.csv.CSVFormat; | ||
import org.apache.commons.csv.CSVPrinter; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.springframework.web.bind.annotation.RequestMethod; | ||
|
||
@Controller | ||
@RequestMapping("/analytics/video-learning-event/list") | ||
public class VideoLearningEventCsvExportController { | ||
|
||
private final Logger logger = LogManager.getLogger(); | ||
|
||
@Autowired | ||
private VideoLearningEventDao videoLearningEventDao; | ||
|
||
@RequestMapping(value="/video-learning-events.csv", method = RequestMethod.GET) | ||
public void handleRequest( | ||
HttpServletResponse response, | ||
OutputStream outputStream | ||
) throws IOException { | ||
logger.info("handleRequest"); | ||
|
||
List<VideoLearningEvent> videoLearningEvents = videoLearningEventDao.readAll(); | ||
logger.info("videoLearningEvents.size(): " + videoLearningEvents.size()); | ||
|
||
CSVFormat csvFormat = CSVFormat.DEFAULT.builder() | ||
.setHeader( | ||
"id", // The Room database ID | ||
"timestamp", | ||
"android_id", | ||
"package_name", | ||
"video_id", | ||
"video_title", | ||
"learning_event_type", | ||
"additional_data" | ||
) | ||
.build(); | ||
|
||
StringWriter stringWriter = new StringWriter(); | ||
CSVPrinter csvPrinter = new CSVPrinter(stringWriter, csvFormat); | ||
|
||
for (VideoLearningEvent videoLearningEvent : videoLearningEvents) { | ||
logger.info("videoLearningEvent.getId(): " + videoLearningEvent.getId()); | ||
|
||
videoLearningEvent.setAndroidId(AnalyticsHelper.redactAndroidId(videoLearningEvent.getAndroidId())); | ||
|
||
csvPrinter.printRecord( | ||
videoLearningEvent.getId(), | ||
videoLearningEvent.getTimestamp().getTimeInMillis(), | ||
videoLearningEvent.getAndroidId(), | ||
videoLearningEvent.getPackageName(), | ||
videoLearningEvent.getVideoId(), | ||
videoLearningEvent.getVideoTitle(), | ||
videoLearningEvent.getLearningEventType(), | ||
videoLearningEvent.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) { | ||
logger.error(ex); | ||
} | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
src/main/java/ai/elimu/web/analytics/VideoLearningEventListController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package ai.elimu.web.analytics; | ||
|
||
import ai.elimu.dao.VideoLearningEventDao; | ||
import ai.elimu.model.analytics.VideoLearningEvent; | ||
import ai.elimu.util.AnalyticsHelper; | ||
|
||
import java.text.SimpleDateFormat; | ||
import java.util.ArrayList; | ||
import java.util.Calendar; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestMethod; | ||
|
||
@Controller | ||
@RequestMapping("/analytics/video-learning-event/list") | ||
public class VideoLearningEventListController { | ||
|
||
private final Logger logger = LogManager.getLogger(); | ||
|
||
@Autowired | ||
private VideoLearningEventDao videoLearningEventDao; | ||
|
||
@RequestMapping(method = RequestMethod.GET) | ||
public String handleRequest(Model model) { | ||
logger.info("handleRequest"); | ||
|
||
List<VideoLearningEvent> videoLearningEvents = videoLearningEventDao.readAll(); | ||
for (VideoLearningEvent videoLearningEvent : videoLearningEvents) { | ||
videoLearningEvent.setAndroidId(AnalyticsHelper.redactAndroidId(videoLearningEvent.getAndroidId())); | ||
} | ||
model.addAttribute("videoLearningEvents", videoLearningEvents); | ||
|
||
// Prepare chart data | ||
List<String> monthList = new ArrayList<>(); | ||
List<Integer> eventCountList = 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"); | ||
for (VideoLearningEvent event : videoLearningEvents) { | ||
String eventMonth = simpleDateFormat.format(event.getTimestamp().getTime()); | ||
eventCountByMonthMap.put(eventMonth, eventCountByMonthMap.getOrDefault(eventMonth, 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()); | ||
monthList.add(monthAsString); | ||
|
||
eventCountList.add(eventCountByMonthMap.getOrDefault(monthAsString, 0)); | ||
|
||
// Increase the date by 1 month | ||
month.add(Calendar.MONTH, 1); | ||
} | ||
} | ||
model.addAttribute("monthList", monthList); | ||
model.addAttribute("eventCountList", eventCountList); | ||
|
||
return "analytics/video-learning-event/list"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
src/main/webapp/WEB-INF/jsp/analytics/video-learning-event/list.jsp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<content:title> | ||
VideoLearningEvents (${fn:length(videoLearningEvents)}) | ||
</content:title> | ||
|
||
<content:section cssId="videoLearningEventsPage"> | ||
<div class="section row"> | ||
<div class="card-panel"> | ||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script> | ||
<canvas id="chart"></canvas> | ||
<script> | ||
const labels = [ | ||
<c:forEach var="month" items="${monthList}">'${month}',</c:forEach> | ||
]; | ||
const data = { | ||
labels: labels, | ||
datasets: [{ | ||
data: <c:out value="${eventCountList}" />, | ||
label: 'Learning events', | ||
backgroundColor: 'rgba(149,117,205, 0.5)', // #9575cd deep-purple lighten-2 | ||
borderColor: 'rgba(149,117,205, 0.5)', // #9575cd deep-purple lighten-2 | ||
tension: 0.5, | ||
fill: true | ||
}] | ||
}; | ||
const config = { | ||
type: 'line', | ||
data: data, | ||
options: {} | ||
}; | ||
var ctx = document.getElementById('chart'); | ||
new Chart(ctx, config); | ||
</script> | ||
</div> | ||
</div> | ||
|
||
<div class="section row"> | ||
<a id="exportToCsvButton" class="right btn waves-effect waves-light grey-text white" | ||
href="<spring:url value='/analytics/video-learning-event/list/video-learning-events.csv' />"> | ||
<fmt:message key="export.to.csv" /><i class="material-icons right">vertical_align_bottom</i> | ||
</a> | ||
<script> | ||
$(function() { | ||
$('#exportToCsvButton').click(function() { | ||
console.info('#exportToCsvButton click'); | ||
Materialize.toast('Preparing CSV file. Please wait...', 4000, 'rounded'); | ||
}); | ||
}); | ||
</script> | ||
|
||
<table class="bordered highlight"> | ||
<thead> | ||
<th><code>timestamp</code></th> | ||
<th><code>android_id</code></th> | ||
<th><code>package_name</code></th> | ||
<th><code>video_title</code></th> | ||
<th><code>learning_event_type</code></th> | ||
</thead> | ||
<tbody> | ||
<c:forEach var="videoLearningEvent" items="${videoLearningEvents}"> | ||
<tr class="videoLearningEvent"> | ||
<td> | ||
<fmt:formatDate value="${videoLearningEvent.timestamp.time}" pattern="yyyy-MM-dd HH:mm:ss" /> | ||
</td> | ||
<td> | ||
${videoLearningEvent.androidId} | ||
</td> | ||
<td> | ||
${videoLearningEvent.packageName} | ||
</td> | ||
<td> | ||
<c:choose> | ||
<c:when test="${not empty videoLearningEvent.video}"> | ||
"<a href="<spring:url value='/content/video/edit/${videoLearningEvent.video.id}' />">${videoLearningEvent.video.title}</a>" | ||
</c:when> | ||
<c:otherwise> | ||
"${videoLearningEvent.videoTitle}" | ||
</c:otherwise> | ||
</c:choose> | ||
</td> | ||
<td> | ||
${videoLearningEvent.learningEventType} | ||
</td> | ||
</tr> | ||
</c:forEach> | ||
</tbody> | ||
</table> | ||
</div> | ||
</content:section> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters