Skip to content

Commit 4f98d6f

Browse files
committed
Rework scheduling fixes elan-ev#1300,
Enhance Opencast scheduling functionality with improved event validation and modal management for editing - Added checks to ensure events exist before every deletion or updating scheduled event in opencast to avoid search index conflicts. - Introduced a new SchedulingEditModal for editing resources with confirmation prompts. - Updated SchedulingOptions to handle unsaved changes and resource editing. - Enhanced state management in Vuex for scheduling-related modals and unsaved changes.
1 parent c567da9 commit 4f98d6f

File tree

8 files changed

+571
-94
lines changed

8 files changed

+571
-94
lines changed

cronjobs/opencast_refresh_scheduling.php

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ public function execute($last_result, $parameters = array())
9191
$sop_scheduled_events = ScheduledRecordings::findBySql('start >= :start', [':start' => time()]);
9292
echo 'In SOP geplante Events: ' . sizeof($sop_scheduled_events) . "\n";
9393

94-
$time = time();
9594
// 3. Loop through every SOP records, to validate the record.
9695
foreach ($sop_scheduled_events as $scheduled_events) {
9796
try {
@@ -104,12 +103,14 @@ public function execute($last_result, $parameters = array())
104103
// In case validation fails, we try to remove the record on both sides!
105104
echo '-----------------------------------------------------' . "\n";
106105

107-
if (empty($cd) || empty($course) || empty($course_config_id) || empty($resource_obj) // Any requirement fails
108-
|| !ScheduleHelper::validateCourseAndResource($scheduled_events['seminar_id'], $resource_obj['config_id']) // The server config id of the course and the oc_resource does not match
109-
|| $cd->room_booking->resource_id != $scheduled_events['resource_id'] // The resource of the record and course date does not match
110-
|| $cd->room_booking->begin != $scheduled_events['coursedate_start'] // Start or Enddate are different
106+
if (empty($cd) || empty($course) || empty($course_config_id) || empty($resource_obj) // Any requirement fails
107+
// The server config id of the course and the oc_resource does not match
108+
|| !ScheduleHelper::validateCourseAndResource($scheduled_events['seminar_id'], $resource_obj['config_id'])
109+
// The resource of the record and course date does not match
110+
|| $cd->room_booking->resource_id != $scheduled_events['resource_id']
111+
// Start or Enddate are different
112+
|| $cd->room_booking->begin != $scheduled_events['coursedate_start']
111113
|| $cd->room_booking->end != $scheduled_events['coursedate_end']
112-
/* || intval($cd->end_time) < $time */ // TODO: decide whether to remove those records that are expired!
113114
) {
114115

115116
// Try to delete the record because it couldn't pass the validation!
@@ -156,10 +157,18 @@ public function execute($last_result, $parameters = array())
156157
}
157158

158159
if (!empty($oc_event_to_delete)) {
159-
$scheduler_client = SchedulerClient::getInstance($oc_config_id);
160-
$result = $scheduler_client->deleteEvent($oc_event_id);
161-
if ($result) {
162-
unset($oc_scheduled_events[$oc_set_index]['scheduled_events'][$oc_event_id]);
160+
// We double check the event existence before removing it in OC, in order to avoid search index conflict!
161+
$api_event_client = ApiEventsClient::getInstance($oc_config_id);
162+
$oc_event_to_delete_obj = $api_event_client->getEpisode($oc_event_id);
163+
$oc_event_to_delete_exists = !empty($oc_event_to_delete_obj)
164+
&& $oc_event_to_delete_obj->status == 'EVENTS.EVENTS.STATUS.SCHEDULED';
165+
166+
if ($oc_event_to_delete_exists) {
167+
$scheduler_client = SchedulerClient::getInstance($oc_config_id);
168+
$result = $scheduler_client->deleteEvent($oc_event_id);
169+
if ($result) {
170+
unset($oc_scheduled_events[$oc_set_index]['scheduled_events'][$oc_event_id]);
171+
}
163172
}
164173
}
165174

@@ -205,6 +214,7 @@ public function execute($last_result, $parameters = array())
205214

206215
// 4. Those scheduled events that are not stored in SOP, will appear in this block.
207216
// We try to delete them if the global config "OPENCAST_MANAGE_ALL_OC_EVENTS" is enabled!
217+
echo '-----------------------------------------------------' . "\n";
208218
if (StudipConfig::get()->OPENCAST_MANAGE_ALL_OC_EVENTS) {
209219
echo _('Lösche nicht über Stud.IP Termine geplante Events:') . "\n";
210220
} else {
@@ -221,8 +231,16 @@ public function execute($last_result, $parameters = array())
221231
if (strtotime($scheduled_event->start) > time()) {
222232
echo $scheduled_event->identifier . ' ' . $scheduled_event->title . "\n";
223233
if (StudipConfig::get()->OPENCAST_MANAGE_ALL_OC_EVENTS) {
224-
$scheduler_client = SchedulerClient::getInstance($oc_set['config_id']);
225-
$scheduler_client->deleteEvent($scheduled_event->identifier);
234+
// We double check the event existence before removing it in OC, in order to avoid search index conflict!
235+
$api_event_client = ApiEventsClient::getInstance($oc_set['config_id']);
236+
$oc_event_to_delete_obj = $api_event_client->getEpisode($scheduled_event->identifier);
237+
$oc_event_to_delete_exists = !empty($oc_event_to_delete_obj)
238+
&& $oc_event_to_delete_obj->status == 'EVENTS.EVENTS.STATUS.SCHEDULED';
239+
240+
if ($oc_event_to_delete_exists) {
241+
$scheduler_client = SchedulerClient::getInstance($oc_set['config_id']);
242+
$scheduler_client->deleteEvent($scheduled_event->identifier);
243+
}
226244
}
227245
}
228246
}

lib/Models/ScheduleHelper.php

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -599,11 +599,21 @@ public static function deleteEventForSeminar($course_id, $termin_id, $external_c
599599
return false;
600600
}
601601

602-
$scheduler_client = SchedulerClient::getInstance($config_id);
603-
604-
$result = $scheduler_client->deleteEvent($event_id);
602+
// In order to delete the scheduled recording from Opencast, we ensure that the event already exists!
603+
// this is important to avoid breaking search index.
604+
$api_event_client = ApiEventsClient::getInstance($config_id);
605+
$oc_event = $api_event_client->getEpisode($event_id);
606+
$event_exists = !empty($oc_event) && $oc_event->status == 'EVENTS.EVENTS.STATUS.SCHEDULED';
607+
608+
$oc_deleted_result = false;
609+
if ($event_exists) {
610+
$scheduler_client = SchedulerClient::getInstance($config_id);
611+
$oc_deleted_result = $scheduler_client->deleteEvent($event_id);
612+
}
605613

606-
if ($result) {
614+
// In case of successfully deleted the event in opencast or the event could not be found,
615+
// we need to remove the records in SOCP as well!
616+
if ($oc_deleted_result || (!$oc_deleted_result && !$event_exists)) {
607617
ScheduledRecordings::unscheduleRecording($event_id, $resource_id, $termin_id);
608618
// Remove the livestream video here!
609619
if ($is_livestream) {
@@ -705,6 +715,16 @@ public static function updateEventForSeminar($course_id, $termin_id, $start = nu
705715
$scheduled_recording_obj->store();
706716
}
707717

718+
// In order to update the scheduled recording in Opencast, we ensure that the event already exists!
719+
// this is important to avoid breaking search index.
720+
$api_event_client = ApiEventsClient::getInstance($resource_obj['config_id']);
721+
$oc_event = $api_event_client->getEpisode($event_id);
722+
$event_exists = !empty($oc_event) && $oc_event->status == 'EVENTS.EVENTS.STATUS.SCHEDULED';
723+
724+
if (!$event_exists) {
725+
return false;
726+
}
727+
708728
$metadata = self::createEventMetadata($course_id, $resource_id, $resource_obj['config_id'], $termin_id, $event_id, $is_livestream);
709729

710730
$start = $scheduled_recording_obj->start * 1000;
@@ -979,24 +999,40 @@ public static function addUpdateResource($resource_id, $config_id, $capture_agen
979999
// We take the current resource obj to use its data for deleteEventForSeminar,
9801000
// to make sure that server config id is correct!
9811001
$ex_resource_obj = Resources::findByResource_id($resource_id);
1002+
1003+
// We record the config (server) change.
1004+
$server_ca_has_been_changed = (int) $ex_resource_obj['config_id'] !== (int) $config_id;
1005+
9821006
// We Update the resource first.
9831007
$success = Resources::setResource($resource_id, $config_id, $capture_agent, $workflow_id, $livestream_workflow_id);
9841008
// If is updated.
985-
/*
9861009
if ($success) {
987-
// We update current scheduled events that uses this resource.
1010+
// We loop through current recorded with this recourse to update or delete them!
9881011
if ($scheduled_recordings = ScheduledRecordings::getScheduleRecordingList($resource_id)) {
9891012
foreach ($scheduled_recordings as $recording) {
990-
// We try to update the those record as well.
991-
$updated = self::updateEventForSeminar($recording['seminar_id'], $recording['date_id'], null, null, true);
992-
// If update fails, we remove them!
993-
if (!$updated && $ex_resource_obj) {
994-
self::deleteEventForSeminar($recording['seminar_id'], $recording['date_id'], $ex_resource_obj['config_id']);
1013+
// In case the config (sever) has been changed, we remove the recording.
1014+
if ($server_ca_has_been_changed) {
1015+
self::deleteEventForSeminar(
1016+
$recording['seminar_id'],
1017+
$recording['date_id'],
1018+
$ex_resource_obj['config_id']
1019+
);
1020+
} else {
1021+
// Otherwise, we try to update the recording.
1022+
$updated = self::updateEventForSeminar($recording['seminar_id'], $recording['date_id'], null, null, true);
1023+
// If update fails, we remove them!
1024+
if (!$updated && $ex_resource_obj) {
1025+
self::deleteEventForSeminar(
1026+
$recording['seminar_id'],
1027+
$recording['date_id'],
1028+
$ex_resource_obj['config_id']
1029+
);
1030+
}
9951031
}
1032+
9961033
}
9971034
}
9981035
}
999-
*/
10001036
return $success;
10011037
}
10021038

@@ -1014,7 +1050,7 @@ public static function deleteResource($resource_id)
10141050
$ex_resource_obj = Resources::findByResource_id($resource_id);
10151051
// Then we delete the resource.
10161052
$success = Resources::removeResource($resource_id);
1017-
// If the deletion succeed, then we perform the deleting of the scheduled recordings.
1053+
// If the deletion succeed, then we perform the deleting of the scheduled recordings in the future!
10181054
if ($success) {
10191055
$scheduled_recordings = ScheduledRecordings::getScheduleRecordingList($resource_id);
10201056
if (!empty($scheduled_recordings) && !empty($ex_resource_obj)) {
@@ -1119,8 +1155,8 @@ public static function createOrUpdateLivestreamVideo($config_id, $course_id, $ev
11191155
'description' => $event->description,
11201156
'duration' => $event->duration,
11211157
'state' => 'running',
1122-
'created' => date('Y-m-d H:i:s', strtotime($event->created)),
1123-
'presenters' => $event->creator,
1158+
'created' => date('Y-m-d H:i:s', strtotime($event->created)),
1159+
'presenters' => $event->creator,
11241160
'available' => true,
11251161
'publication' => $publication,
11261162
'is_livestream' => true,

lib/Models/ScheduledRecordings.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,23 @@ public static function getScheduleRecording($seminar_id, $resource_id, $date_id,
101101

102102
/**
103103
* Gets the list of schedule recording based on the parameters passed
104+
* It will by default get the records in the future, unless $include_old_records is set!
104105
*
105106
* @param string $resource_id id of resource
106107
* @param string $seminar_id id of course
107108
* @param string $status the status of the record
108109
* @param bool|null $is_livestream the livestream flag of the record
110+
* @param bool $include_old_records If true, includes all records regardless of their date;
111+
* if false, only future (upcoming) records are returned.
109112
*
110113
* @return object|bool
111114
*/
112-
public static function getScheduleRecordingList($resource_id = null, $seminar_id = null, $status = '', $is_livestream = null)
115+
public static function getScheduleRecordingList(
116+
$resource_id = null,
117+
$seminar_id = null,
118+
$status = '',
119+
$is_livestream = null,
120+
$include_old_records = false)
113121
{
114122
$where_array = [];
115123
$params = [];
@@ -130,6 +138,11 @@ public static function getScheduleRecordingList($resource_id = null, $seminar_id
130138
$where_array[] = "is_livestream = ?";
131139
$params[] = $is_livestream;
132140
}
141+
// Always get the records in future, unless intentionally requested otherwise via $include_old_records
142+
if (!$include_old_records) {
143+
$where_array[] = "start >= ?";
144+
$params[] = time();
145+
}
133146
if (!empty($where_array)) {
134147
return self::findBySQL(implode(' AND ', $where_array), $params);
135148
}

lib/Routes/Config/ConfigUpdate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function __invoke(Request $request, Response $response, $args)
4848
];
4949
}
5050
} else {
51-
// ScheduleHelper::deleteResource($resource['id']);
51+
ScheduleHelper::deleteResource($resource['id']);
5252
}
5353
}
5454
}

vueapp/components/Config/AdminConfigs.vue

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,56 @@
11
<template>
2-
<div>
3-
<form class="default">
4-
<MessageBox type="warning" v-if="is_scheduling_configured && !is_scheduling_enabled">
5-
{{ $gettext('Es wurden bisher keine Räume mit Aufzeichnungstechnik konfiguriert! Bitte konsultieren Sie die Hilfeseiten.') }}
6-
<a :href="$filters.helpurl('OpencastV3Administration#toc2')"
7-
target="_blank"
8-
>
9-
{{ $gettext('Aufzeichnungsplanung konfigurieren') }}
10-
</a>
11-
</MessageBox>
12-
13-
<MessageBox type="info" v-if="canMigratePlaylists">
14-
{{ $gettext('Sie verwenden Opencast 16 oder höher und können die Wiedergabelisten mit zu Opencast übertragen und die automatische Synchronisation einschalten.') }}
15-
<br>
16-
<a @click.stop="migratePlaylists" style="cursor: pointer">
17-
{{ $gettext('Synchronisierung aktivieren und Wiedergabelisten übertragen') }}
18-
</a>
19-
</MessageBox>
20-
21-
<GlobalOptions :config_list="config_list"/>
22-
23-
<SchedulingOptions v-if="is_scheduling_enabled" :config_list="config_list"/>
24-
25-
<footer>
26-
<StudipButton icon="accept" @click.prevent="storeAdminConfig($event)">
27-
<span>
28-
{{ $gettext('Einstellungen speichern') }}
29-
</span>
30-
</StudipButton>
31-
</footer>
32-
</form>
33-
</div>
2+
<div>
3+
<form class="default">
4+
<MessageBox type="warning" v-if="is_scheduling_configured && !is_scheduling_enabled">
5+
{{ $gettext('Es wurden bisher keine Räume mit Aufzeichnungstechnik konfiguriert! Bitte konsultieren Sie die Hilfeseiten.') }}
6+
<a :href="$filters.helpurl('OpencastV3Administration#toc2')"
7+
target="_blank"
8+
>
9+
{{ $gettext('Aufzeichnungsplanung konfigurieren') }}
10+
</a>
11+
</MessageBox>
12+
13+
<MessageBox type="info" v-if="canMigratePlaylists">
14+
{{ $gettext('Sie verwenden Opencast 16 oder höher und können die Wiedergabelisten mit zu Opencast übertragen und die automatische Synchronisation einschalten.') }}
15+
<br>
16+
<a @click.stop="migratePlaylists" style="cursor: pointer">
17+
{{ $gettext('Synchronisierung aktivieren und Wiedergabelisten übertragen') }}
18+
</a>
19+
</MessageBox>
20+
21+
<GlobalOptions :config_list="config_list"/>
22+
23+
<SchedulingOptions v-if="is_scheduling_enabled"
24+
:config_list="config_list"
25+
@openEditModalInParent="openSchedulingEditModal"
26+
/>
27+
28+
<footer>
29+
<StudipButton icon="accept" @click.prevent="storeAdminConfig($event)">
30+
<span>
31+
{{ $gettext('Einstellungen speichern') }}
32+
</span>
33+
</StudipButton>
34+
</footer>
35+
</form>
36+
37+
<!-- Modals outside of form -->
38+
<SchedulingEditModal v-if="shouldSchedulingEditModalBeVisible !== false" />
39+
40+
<StudipDialog
41+
v-if="shouldConfirmationModalBeVisible !== false"
42+
:title="confirmationModalObj.title"
43+
:confirmText="$gettext('Akzeptieren')"
44+
:confirmClass="'accept'"
45+
:closeText="$gettext('Abbrechen')"
46+
:closeClass="'cancel'"
47+
:height="confirmationModalObj?.height ?? '220'"
48+
:width="confirmationModalObj?.width ?? '500'"
49+
@close="closeAllModals"
50+
@confirm="confirmationModalObj.confirm"
51+
:alert="confirmationModalObj.text"
52+
/>
53+
</div>
3454
</template>
3555

3656
<script>
@@ -42,13 +62,16 @@ import MessageList from "@/components/MessageList";
4262
import GlobalOptions from "@/components/Config/GlobalOptions";
4363
import SchedulingOptions from "@/components/Config/SchedulingOptions";
4464
import MessageBox from "@/components/MessageBox";
65+
import SchedulingEditModal from "@/components/Config/SchedulingEditModal";
66+
import StudipDialog from '@studip/StudipDialog';
4567
4668
export default {
4769
name: "AdminConfigs",
4870
components: {
4971
StudipButton, StudipIcon,
5072
MessageList, MessageBox,
51-
GlobalOptions, SchedulingOptions
73+
GlobalOptions, SchedulingOptions,
74+
SchedulingEditModal, StudipDialog
5275
},
5376
5477
data() {
@@ -58,7 +81,13 @@ export default {
5881
},
5982
6083
computed: {
61-
...mapGetters(['config_list', 'simple_config_list']),
84+
...mapGetters([
85+
'config_list',
86+
'simple_config_list',
87+
'shouldConfirmationModalBeVisible',
88+
'confirmationModalObj',
89+
'shouldSchedulingEditModalBeVisible'
90+
]),
6291
6392
is_scheduling_enabled() {
6493
return this.config_list?.scheduling && this.is_scheduling_configured;
@@ -102,6 +131,7 @@ export default {
102131
view.$store.dispatch('addMessage', data.messages[i]);
103132
}
104133
}
134+
view.$store.dispatch('toggleSchedulingUnsavedChanges', false);
105135
}).catch(function (error) {
106136
view.$store.dispatch('addMessage', {
107137
type: 'error',
@@ -121,7 +151,12 @@ export default {
121151
122152
this.config_list.can_migrate_playlists = undefined;
123153
});
154+
},
155+
156+
closeAllModals() {
157+
this.$store.dispatch('closeSchedulingEditModal');
158+
this.$store.dispatch('closeConfirmationModal');
124159
}
125160
},
126161
}
127-
</script>
162+
</script>

0 commit comments

Comments
 (0)