diff --git a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java index af89aaf7eddc82a6f5165107b22dcf4d2195853a..d425a2da081883f9f91acf59f2c2e3a3f7b900e4 100644 --- a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java +++ b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java @@ -97,7 +97,7 @@ public class LiveStreamCourseNode extends AbstractAccessableCourseNode { LiveStreamSecurityCallback secCallback = LiveStreamSecurityCallbackFactory .createSecurityCallback(userCourseEnv, this.getModuleConfiguration()); OLATResource courseOres = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseResource(); - runCtrl = new LiveStreamRunController(ureq, wControl, this.getModuleConfiguration(), courseOres, secCallback, calendars); + runCtrl = new LiveStreamRunController(ureq, wControl, this, courseOres, secCallback, calendars); } Controller ctrl = TitledWrapperHelper.getWrapper(ureq, wControl, runCtrl, this, "o_livestream_icon"); return new NodeRunConstructionResult(ctrl); diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallback.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallback.java index f44ed7e63cfdaab24cb5eca363578675fe6efdc3..e1088254c90f98c2fa28bf4587c795278c2d1e06 100644 --- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallback.java +++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallback.java @@ -29,6 +29,8 @@ public interface LiveStreamSecurityCallback { boolean canViewStreams(); - boolean canEditStreams(); + boolean canViewStatistic(); + boolean canEditStreams(); + } diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallbackFactory.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallbackFactory.java index 583dbeb1bb345f503f4b4991d9ab62c5b059e576..0218a5421fa9ba85eba02d2b8f0cbd9b8a6be250 100644 --- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallbackFactory.java +++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamSecurityCallbackFactory.java @@ -34,15 +34,18 @@ public class LiveStreamSecurityCallbackFactory { public static LiveStreamSecurityCallback createSecurityCallback(UserCourseEnvironment userCourseEnv, ModuleConfiguration config) { boolean canViewStreams = true; + boolean canViewStatistic = userCourseEnv.isAdmin() || userCourseEnv.isCoach(); boolean canEditStreams = userCourseEnv.isAdmin() || (userCourseEnv.isCoach() && config.getBooleanSafe(LiveStreamCourseNode.CONFIG_COACH_CAN_EDIT)); - return createSecurityCallback(canViewStreams, canEditStreams); + return createSecurityCallback(canViewStreams, canViewStatistic, canEditStreams); } - public static LiveStreamSecurityCallback createSecurityCallback(boolean canViewStreams, boolean canEditStreams) { + public static LiveStreamSecurityCallback createSecurityCallback(boolean canViewStreams, boolean canViewStatistic, + boolean canEditStreams) { LiveStreamSecurityCallbackImpl secCallback = new LiveStreamSecurityCallbackImpl(); secCallback.setCanViewStreams(canViewStreams); + secCallback.setCanViewStatistic(canViewStatistic); secCallback.setCanEditStreams(canEditStreams); return secCallback; } @@ -50,6 +53,7 @@ public class LiveStreamSecurityCallbackFactory { private static class LiveStreamSecurityCallbackImpl implements LiveStreamSecurityCallback { private boolean canViewStreams; + private boolean canViewStatistic; private boolean canEditStreams; @Override @@ -61,6 +65,15 @@ public class LiveStreamSecurityCallbackFactory { this.canViewStreams = canViewStreams; } + @Override + public boolean canViewStatistic() { + return canViewStatistic; + } + + private void setCanViewStatistic(boolean canViewStatistic) { + this.canViewStatistic = canViewStatistic; + } + @Override public boolean canEditStreams() { return canEditStreams; diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java index e642a0eb71f691a395e37d7c07959eb56fd23eaa..6a534b4ea1bd7f3db37ac1ff25d9efcbdab073db 100644 --- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java +++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java @@ -19,6 +19,7 @@ */ package org.olat.course.nodes.livestream; +import java.util.Date; import java.util.List; import java.util.concurrent.ScheduledExecutorService; @@ -36,7 +37,20 @@ public interface LiveStreamService { List<? extends LiveStreamEvent> getRunningEvents(CourseCalendars calendars, int bufferBeforeMin, int bufferAfterMin); + + List<? extends LiveStreamEvent> getRunningAndPastEvents(CourseCalendars calendars, int bufferBeforeMin); List<? extends LiveStreamEvent> getUpcomingEvents(CourseCalendars calendars, int bufferBeforeMin); + + /** + * Get the number of unique viewers of the live stream event. + * + * @param courseResId + * @param courseNodeIdent + * @param from + * @param to + * @return + */ + Long getViewers(String courseResId, String courseNodeIdent, Date from, Date to); } diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java index 5b7a52b92f5d4a1ea2b5aab3f4867366e4557bca..6dde2be5ca42f56d26c6b69f1b4e04f51e71e488 100644 --- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java +++ b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java @@ -22,6 +22,7 @@ package org.olat.course.nodes.livestream.manager; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -54,6 +55,8 @@ public class LiveStreamServiceImpl implements LiveStreamService { @Autowired private CalendarManager calendarManager; + @Autowired + private LiveStreamStatisticDAO statisticDao; @Override public ScheduledExecutorService getScheduler() { @@ -81,6 +84,20 @@ public class LiveStreamServiceImpl implements LiveStreamService { return getLiveStreamEvents(calendars, from, to); } + + @Override + public List<? extends LiveStreamEvent> getRunningAndPastEvents(CourseCalendars calendars, int bufferBeforeMin) { + Date now = new Date(); + + Date from = new GregorianCalendar(2000, 1, 1).getTime(); + + Calendar cTo = Calendar.getInstance(); + cTo.setTime(now); + cTo.add(Calendar.MINUTE, bufferBeforeMin); + Date to = cTo.getTime(); + + return getLiveStreamEvents(calendars, from, to); + } @Override public List<? extends LiveStreamEvent> getUpcomingEvents(CourseCalendars calendars, int bufferBeforeMin) { @@ -147,4 +164,9 @@ public class LiveStreamServiceImpl implements LiveStreamService { } return liveStreamEvent; } + + @Override + public Long getViewers(String courseResId, String nodeIdent, Date from, Date to) { + return statisticDao.getViewers(courseResId, nodeIdent, from, to); + } } diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAO.java b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAO.java new file mode 100644 index 0000000000000000000000000000000000000000..52d933eb2f7395ba288531fa899f9fae09809645 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAO.java @@ -0,0 +1,62 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.manager; + +import java.util.Date; +import java.util.List; + +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.persistence.QueryBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * Initial date: 17 Dec 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Component +public class LiveStreamStatisticDAO { + + @Autowired + private DB dbInstance; + + public Long getViewers(String courseResId, String nodeIdent, Date from, Date to) { + QueryBuilder sb = new QueryBuilder(); + sb.append("select count(distinct log.userId)"); + sb.append(" from loggingobject log"); + sb.and().append("log.actionVerb = 'launch'"); + sb.and().append("log.targetResType = 'livestream'"); + sb.and().append("log.targetResId = :targetResId"); + sb.and().append("log.parentResId = :parentResId"); + sb.and().append("log.creationDate >= :from"); + sb.and().append("log.creationDate <= :to"); + + List<Long> counts = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("targetResId", nodeIdent) + .setParameter("parentResId", courseResId) + .setParameter("from", from) + .setParameter("to", to) + .getResultList(); + return !counts.isEmpty()? counts.get(0): null; + } +} diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventDataModel.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventDataModel.java index 9fe89373acbd37155d93f57e0ae9a12ee8969dcc..7b4daec2c73188122ca45c28595a1003d6f85200 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventDataModel.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventDataModel.java @@ -53,6 +53,7 @@ public class LiveStreamEventDataModel extends DefaultFlexiTableDataModel<LiveStr case subject: return row.getEvent().getSubject(); case description: return row.getEvent().getDescription(); case location: return row.getEvent().getLocation(); + case viewers: return row.getViewers(); default: return null; } } @@ -67,7 +68,8 @@ public class LiveStreamEventDataModel extends DefaultFlexiTableDataModel<LiveStr begin("table.header.begin"), end("table.header.end"), location("table.header.location"), - description("table.header.description"); + description("table.header.description"), + viewers("table.header.viewers"); private final String i18nKey; diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventRow.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventRow.java index 8f293bdc9116c6e45adfb2cf964e440e5b79e996..b874d5587bfe06a0d07dc0a8176d3b209796e8bb 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventRow.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamEventRow.java @@ -30,6 +30,7 @@ import org.olat.course.nodes.livestream.LiveStreamEvent; public class LiveStreamEventRow { private final LiveStreamEvent event; + private Long viewers; public LiveStreamEventRow(LiveStreamEvent event) { this.event = event; @@ -39,4 +40,12 @@ public class LiveStreamEventRow { return event; } + public Long getViewers() { + return viewers; + } + + public void setViewers(Long viewers) { + this.viewers = viewers; + } + } diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java index 50c5d5c50ee74340e13cc4acf88186443316bfcd..7c9e09b0d303bb06901ec67596d37d057f2790e8 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java @@ -32,6 +32,7 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.util.resource.OresHelper; +import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.cal.CourseCalendars; import org.olat.course.nodes.livestream.LiveStreamSecurityCallback; import org.olat.modules.ModuleConfiguration; @@ -46,24 +47,29 @@ import org.olat.resource.OLATResource; public class LiveStreamRunController extends BasicController { private static final String PLAY_RES_TYPE = "streams"; + private static final String STATISTIC_RES_TYPE = "statistic"; private static final String EDIT_RES_TYPE = "edit"; private VelocityContainer mainVC; private SegmentViewComponent segmentView; private Link streamsLink; + private Link statisticLink; private Link editLink; private LiveStreamsController streamsCtrl; + private LiveStreamStatisticController statisticCtrl; private WeeklyCalendarController editCtrl; private final ModuleConfiguration moduleConfiguration; + private final String courseNodeIdent; private final OLATResource courseOres; private final CourseCalendars calendars; - public LiveStreamRunController(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfiguration, + public LiveStreamRunController(UserRequest ureq, WindowControl wControl, CourseNode coureNode, OLATResource courseOres, LiveStreamSecurityCallback secCallback, CourseCalendars calendars) { super(ureq, wControl); - this.moduleConfiguration = moduleConfiguration; + this.moduleConfiguration = coureNode.getModuleConfiguration(); + this.courseNodeIdent = coureNode.getIdent(); this.courseOres = courseOres; this.calendars = calendars; @@ -74,6 +80,10 @@ public class LiveStreamRunController extends BasicController { streamsLink = LinkFactory.createLink("run.streams", mainVC, this); segmentView.addSegment(streamsLink, true); } + if (secCallback.canViewStatistic()) { + statisticLink = LinkFactory.createLink("run.statistic", mainVC, this); + segmentView.addSegment(statisticLink, true); + } if (secCallback.canEditStreams()) { editLink = LinkFactory.createLink("run.edit.events", mainVC, this); segmentView.addSegment(editLink, false); @@ -95,6 +105,8 @@ public class LiveStreamRunController extends BasicController { Component clickedLink = mainVC.getComponent(segmentCName); if (clickedLink == streamsLink) { doOpenStreams(ureq); + } else if (clickedLink == statisticLink){ + doOpenStatistic(ureq); } else if (clickedLink == editLink){ doOpenEdit(ureq); } @@ -115,6 +127,20 @@ public class LiveStreamRunController extends BasicController { mainVC.put("segmentCmp", streamsCtrl.getInitialComponent()); } + private void doOpenStatistic(UserRequest ureq) { + if (statisticCtrl == null) { + WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(STATISTIC_RES_TYPE), null); + statisticCtrl = new LiveStreamStatisticController(ureq, swControl, courseOres, courseNodeIdent, + moduleConfiguration, calendars); + listenTo(statisticCtrl); + } else { + statisticCtrl.refreshData(); + addToHistory(ureq, statisticCtrl); + } + segmentView.select(statisticLink); + mainVC.put("segmentCmp", statisticCtrl.getInitialComponent()); + } + private void doOpenEdit(UserRequest ureq) { if (editCtrl == null) { WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(EDIT_RES_TYPE), null); diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamStatisticController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamStatisticController.java new file mode 100644 index 0000000000000000000000000000000000000000..86b4262278968108c4ff8d386af28e0f7f508b1f --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamStatisticController.java @@ -0,0 +1,124 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.nodes.LiveStreamCourseNode; +import org.olat.course.nodes.cal.CourseCalendars; +import org.olat.course.nodes.livestream.LiveStreamEvent; +import org.olat.course.nodes.livestream.LiveStreamService; +import org.olat.course.nodes.livestream.ui.LiveStreamEventDataModel.EventCols; +import org.olat.modules.ModuleConfiguration; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 17 Dec 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class LiveStreamStatisticController extends FormBasicController { + + private FlexiTableElement tableEl; + private LiveStreamEventDataModel dataModel; + + private String courseResId; + private String courseNodeIdent; + private final CourseCalendars calendars; + private final int bufferBeforeMin; + + @Autowired + private LiveStreamService liveStreamService; + + public LiveStreamStatisticController(UserRequest ureq, WindowControl wControl, OLATResource courseOres, + String courseNodeIdent, ModuleConfiguration moduleConfiguration, CourseCalendars calendars) { + super(ureq, wControl, LAYOUT_VERTICAL); + this.courseResId = courseOres.getResourceableId().toString(); + this.courseNodeIdent = courseNodeIdent; + this.calendars = calendars; + + bufferBeforeMin = moduleConfiguration.getIntegerSafe(LiveStreamCourseNode.CONFIG_BUFFER_BEFORE_MIN, 0); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("statistic.title"); + + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(EventCols.subject)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(EventCols.begin)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(EventCols.end)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(EventCols.viewers)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(EventCols.location)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(EventCols.description)); + + dataModel = new LiveStreamEventDataModel(columnsModel, getLocale()); + tableEl = uifactory.addTableElement(getWindowControl(), "table", dataModel, 20, false, getTranslator(), formLayout); + tableEl.setAndLoadPersistedPreferences(ureq, "livestream-statistic"); + tableEl.setEmtpyTableMessageKey("statistic.table.empty"); + loadModel(); + } + + void refreshData() { + loadModel(); + } + + private void loadModel() { + List<? extends LiveStreamEvent> upcomingEvents = liveStreamService.getRunningAndPastEvents(calendars, + bufferBeforeMin); + List<LiveStreamEventRow> rows = new ArrayList<>(upcomingEvents.size()); + for (LiveStreamEvent liveStreamEvent : upcomingEvents) { + LiveStreamEventRow row = new LiveStreamEventRow(liveStreamEvent); + Long viewers = liveStreamService.getViewers(courseResId, courseNodeIdent, liveStreamEvent.getBegin(), + liveStreamEvent.getEnd()); + row.setViewers(viewers); + rows.add(row); + } + Collections.sort(rows, (e1, e2) -> e1.getEvent().getBegin().compareTo(e2.getEvent().getBegin())); + dataModel.setObjects(rows); + tableEl.reset(false, false, true); + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties index a63505df2ee05616236168c449d799fc3e979f82..bc2a803cfa8f8322d54f38d4b214a9e639dbe1a9 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties @@ -30,12 +30,16 @@ player.profile.both=Beide Streams anzeigen player.profile.stream1=Stream 1 anzeigen player.profile.stream2=Stream 2 anzeigen run.edit.events=Termine bearbeiten +run.statistic=Statistik run.streams=Live Streams +statistic.table.empty=Es wurden noch keine Livestreams ausgestrahlt. +statistic.title=Ausgestrahle Livestreams table.empty=Es sind keine Livestreams anstehend. table.header.begin=$org.olat.commons.calendar\:cal.form.begin table.header.description=$org.olat.commons.calendar\:cal.form.description table.header.end=$org.olat.commons.calendar\:cal.form.end table.header.location=$org.olat.commons.calendar\:cal.form.location table.header.subject=$org.olat.commons.calendar\:cal.form.subject +table.header.viewers=Zuschauer viewer.error.browser=Der Livestream kann in diesem Browser nicht angezeigt werden. Bitte verwenden Sie einen anderen Browser. viewer.no.stream=Aktuell wird kein Livestream ausgestrahlt. diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties index dcccc5ff9fa344a5022005fcb2171e02a8cca979..a4e187294fb0f37b8552a6c62e5ab17e602de562 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties @@ -30,12 +30,16 @@ player.profile.both=Show both streams player.profile.stream1=Show stream 1 player.profile.stream2=Show stream 2 run.edit.events=Edit events +run.statistic=Statistic run.streams=Livestreams +statistic.table.empty=No live streams have been broadcast yet. +statistic.title=Broadcast live streams table.empty=No upcoming live streams are available. table.header.begin=$org.olat.commons.calendar\:cal.form.begin table.header.description=$org.olat.commons.calendar\:cal.form.description table.header.end=$org.olat.commons.calendar\:cal.form.end table.header.location=$org.olat.commons.calendar\:cal.form.location table.header.subject=$org.olat.commons.calendar\:cal.form.subject +table.header.viewers=Viewers viewer.error.browser=The livestream cannot be displayed in this browser. Please use a different browser. viewer.no.stream=Currently no live stream is broadcasted. diff --git a/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql b/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql index 5c46c36430a22890ae2bfd05a046f79497690818..caca9df4e8a7fa131724139cce39c41dc90b5bc4 100644 --- a/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql +++ b/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql @@ -15,4 +15,7 @@ create table o_gta_task_revision ( alter table o_gta_task_revision add constraint task_rev_to_task_idx foreign key (fk_task) references o_gta_task (id); create index idx_task_rev_to_task_idx on o_gta_task_revision (fk_task); alter table o_gta_task_revision add constraint task_rev_to_ident_idx foreign key (fk_comment_author) references o_bs_identity (id); -create index idx_task_rev_to_ident_idx on o_gta_task_revision (fk_comment_author); \ No newline at end of file +create index idx_task_rev_to_ident_idx on o_gta_task_revision (fk_comment_author); + +-- livestream +create index idx_log_livestream_idx on o_loggingtable(targetresid, creationdate, parentresid, user_id) where actionverb = 'launch' and targetrestype = 'livestream'; diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 82085e77bcd7c51e6c1f487ed0349ce0e733ec03..89ef00db7126f57b7b11c0069e38fa0445c2740b 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -4104,6 +4104,8 @@ create index log_ptarget_resid_idx on o_loggingtable(parentresid); create index log_gptarget_resid_idx on o_loggingtable(grandparentresid); create index log_ggptarget_resid_idx on o_loggingtable(greatgrandparentresid); create index log_creationdate_idx on o_loggingtable(creationdate); +create index idx_log_livestream_idx on o_loggingtable(targetresid, creationdate, parentresid, user_id) where actionverb = 'launch' and targetrestype = 'livestream'; + insert into hibernate_unique_key values ( 0 ); diff --git a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAOTest.java b/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAOTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ce47808ec6d166fdbe8d69be77b26e5096a08640 --- /dev/null +++ b/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAOTest.java @@ -0,0 +1,89 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.manager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.olat.test.JunitTestHelper.random; + +import java.util.Date; +import java.util.GregorianCalendar; + +import org.junit.Test; +import org.olat.core.commons.persistence.DB; +import org.olat.core.logging.activity.LoggingObject; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 17 Dec 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class LiveStreamStatisticDAOTest extends OlatTestCase { + + @Autowired + private LiveStreamStatisticDAO sut; + @Autowired + private DB dbInstance; + + @Test + public void shouldGetViewers() { + Long userKey1 = 132L; + Long userKey2 = 1324L; + Long userKeyOther = 13245L; + String courseResId = "courseResId"; + String nodeIdent = "nodeIdent"; + Date before = new GregorianCalendar(2010, 2, 8).getTime(); + Date from = new GregorianCalendar(2010, 2, 9).getTime(); + Date inside = new GregorianCalendar(2010, 2, 10).getTime(); + Date to = new GregorianCalendar(2010, 2, 11).getTime(); + Date after = new GregorianCalendar(2010, 2, 12).getTime(); + createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey1, inside); + createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey1, inside); + createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey1, inside); + createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey2, inside); + // These log entries should have all wrong parameters. So userKeyOther should not be a viewer. + createLoggingObject("OTHER", courseResId, "livestream", nodeIdent, userKeyOther, inside); + createLoggingObject("launch", "OTHER", "livestream", nodeIdent, userKeyOther, inside); + createLoggingObject("launch", courseResId, "OTHER", nodeIdent, userKeyOther, inside); + createLoggingObject("launch", courseResId, "livestream", "OTHER", userKeyOther, inside); + createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKeyOther, before); + createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKeyOther, after); + dbInstance.commitAndCloseSession(); + + Long viewers = sut.getViewers(courseResId, nodeIdent, from, to); + + assertThat(viewers).isEqualTo(2); + } + + private void createLoggingObject(String actionVerb, String parentResId, String targetResType, String targetResId, + Long identityKey, Date creationDate) { + LoggingObject logObj = new LoggingObject(random(), identityKey, "r", actionVerb, "node"); + logObj.setCreationDate(creationDate); + logObj.setParentResId(parentResId); + logObj.setTargetResType(targetResType); + logObj.setTargetResId(targetResId); + logObj.setResourceAdminAction(Boolean.TRUE); + dbInstance.saveObject(logObj); + } + + +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index e36fe09fe70cfc8027d7d6a003dedc909108e4b0..568b8972e06009dcfc13ea1f09b7bae5ddd4f791 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -179,6 +179,7 @@ import org.junit.runners.Suite; org.olat.course.nodes.gta.manager.GTATaskRevisionDAOTest.class, org.olat.course.nodes.gta.manager.GTAIdentityMarkDAOTest.class, org.olat.course.nodes.gta.rule.GTAReminderRuleTest.class, + org.olat.course.nodes.livestream.manager.LiveStreamStatisticDAOTest.class, org.olat.course.nodes.pf.manager.PFManagerTest.class, org.olat.course.assessment.AssessmentManagerTest.class, org.olat.course.assessment.manager.UserCourseInformationsManagerTest.class,