- @events.timestamp_pretty@
+ @events.timestamp@
@@ -165,7 +165,16 @@
function createEvent(e) {
var event = template.cloneNode(true);
var title = event.querySelector("[name=title]");
- var timestamp = new Date(e.timestamp * 1000).toISOString().substring(0, 19).replace("T", " ");
+ var timestamp = new Date(e.timestamp * 1000);
+ // Compute the local ISO date. toISOString would
+ // return the UTC time...
+ timestamp =
+ (timestamp.getFullYear() + '').padStart(4, '0') + '-' +
+ ((timestamp.getMonth() + 1) + '').padStart(2, '0') + '-' +
+ (timestamp.getDate() + '').padStart(2, '0') + ' ' +
+ (timestamp.getHours() + '').padStart(2, '0') + ':' +
+ (timestamp.getMinutes() + '').padStart(2, '0') + ':' +
+ (timestamp.getSeconds() + '').padStart(2, '0');
title.textContent = timestamp;
eventList.appendChild(event);
return event;
Index: openacs-4/packages/proctoring-support/lib/proctoring-display.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/lib/proctoring-display.tcl,v
diff -u -r1.1.2.5 -r1.1.2.6
--- openacs-4/packages/proctoring-support/lib/proctoring-display.tcl 11 Jun 2021 14:53:15 -0000 1.1.2.5
+++ openacs-4/packages/proctoring-support/lib/proctoring-display.tcl 10 Feb 2022 09:32:58 -0000 1.1.2.6
@@ -73,6 +73,11 @@
if {$delete_p && [llength $user_id] >= 1} {
foreach u $user_id {
+ ::xo::dc dml -prepare {integer integer} delete_artifacts {
+ delete from proctoring_object_artifacts
+ where object_id = :object_id
+ and user_id = :u
+ }
set folder [::proctoring::folder \
-object_id $object_id -user_id $u]
file delete -force -- $folder
@@ -106,58 +111,64 @@
set portrait_url [export_vars -base "/shared/portrait-bits.tcl" {user_id {size x200}}]
set back_url $base_url
- set camera_pics [lsort -increasing -dictionary \
- [glob -nocomplain -directory $folder camera-image-*.*]]
- set desktop_pics [lsort -increasing -dictionary \
- [glob -nocomplain -directory $folder desktop-image-*.*]]
- set rows [list]
- foreach camera_pic $camera_pics desktop_pic $desktop_pics {
- set row [dict create \
- audio_url ""]
- if {$camera_pic ne ""} {
- set camera_pic [file tail $camera_pic]
- regexp {^camera-image-(\d+)\.\w+$} $camera_pic m camera_timestamp
- dict set row camera_url [export_vars -base $user_url {{file $camera_pic}}]
- } else {
- dict set row camera_url ""
- }
+ db_multirow events get_artifacts {
+ select camera.file as camera_url,
+ desktop.file as desktop_url,
+ coalesce(camera.timestamp,
+ desktop.timestamp) as timestamp,
+ null as audio_url
+ from (select timestamp,
+ file,
+ rank() over (
+ partition by object_id, user_id
+ order by timestamp asc
+ ) as order
+ from proctoring_object_artifacts
+ where object_id = :object_id
+ and user_id = :user_id
+ and type = 'image'
+ and name = 'camera') camera
+ join
+ (select timestamp,
+ file,
+ rank() over (
+ partition by object_id, user_id
+ order by timestamp asc
+ ) as order
+ from proctoring_object_artifacts
+ where object_id = :object_id
+ and user_id = :user_id
+ and type = 'image'
+ and name = 'desktop') desktop
+ on camera.order = desktop.order
- if {$desktop_pic ne ""} {
- set desktop_pic [file tail $desktop_pic]
- regexp {^desktop-image-(\d+)\.\w+$} $desktop_pic m desktop_timestamp
- dict set row desktop_url [export_vars -base $user_url {{file $desktop_pic}}]
- } else {
- dict set row desktop_url ""
- }
+ union
- if {[info exists camera_timestamp]} {
- set timestamp $camera_timestamp
- } else {
- set timestamp $desktop_timestamp
- }
- dict set row timestamp $timestamp
- dict set row timestamp_pretty [clock format $timestamp -format "%y-%m-%d %H:%M:%S"]
+ select null as camera_url,
+ null as desktop_url,
+ timestamp,
+ file as audio_url
+ from proctoring_object_artifacts
+ where object_id = :object_id
+ and user_id = :user_id
+ and type = 'audio'
- lappend rows $row
+ order by timestamp asc
+ } {
+ if {$camera_url ne ""} {
+ set camera_url [file tail $camera_url]
+ set camera_url [export_vars -base $user_url {{file $camera_url}}]
+ }
+ if {$desktop_url ne ""} {
+ set desktop_url [file tail $desktop_url]
+ set desktop_url [export_vars -base $user_url {{file $desktop_url}}]
+ }
+ if {$audio_url ne ""} {
+ set audio_url [file tail $audio_url]
+ set audio_url [export_vars -base $user_url {{file $audio_url}}]
+ }
}
-
- set audios [glob -nocomplain -directory $folder *-audio-*.*]
- foreach audio $audios {
- set row [dict create]
- set audio [file tail $audio]
- regexp {^\w+-audio-(\d+)\.\w+$} $audio m timestamp
- dict set row audio_url [export_vars -base $user_url {{file $audio}}]
- dict set row camera_url ""
- dict set row desktop_url ""
- dict set row timestamp $timestamp
- dict set row timestamp_pretty [clock format $timestamp -format "%y-%m-%d %H:%M:%S"]
-
- lappend rows $row
- }
-
- template::util::list_to_multirow events $rows
- template::multirow sort events timestamp
}
} else {
# List of proctored users
@@ -169,32 +180,34 @@
set delete_confirm [_ xowiki.delete_all_confirm]
if {$delete_p} {
+ ::xo::dc dml -prepare integer delete_artifacts {
+ delete from proctoring_object_artifacts
+ where object_id = :object_id
+ }
file delete -force -- $folder
ad_returnredirect $base_url
ad_script_abort
}
- set rows [list]
- foreach user_folder [glob -type d -nocomplain -directory $folder *] {
- set row [dict create]
- set proctored_user_id [file tail $user_folder]
- dict set row user_id $proctored_user_id
+ db_multirow -extend {
+ student_id
+ proctoring_url
+ portrait_url
+ filter
+ } -unclobber users get_users {
+ select distinct a.user_id,
+ p.first_names,
+ p.last_name
+ from proctoring_object_artifacts a,
+ persons p
+ where object_id = :object_id
+ and a.user_id = p.person_id
+ order by last_name asc, first_names asc
+ } {
+ set student_id [::party::email -party_id $user_id]
- set user [acs_user::get -user_id $proctored_user_id]
- if {$user eq ""} {continue}
-
- set first_names [dict get $user first_names]
- set last_name [dict get $user last_name]
-
- dict set row user_id $proctored_user_id
- dict set row first_names $first_names
- dict set row last_name $last_name
- dict set row proctoring_url [export_vars -no_base_encode -base $base_url {{user_id $proctored_user_id} {object_id $object_id}}]
- dict set row portrait_url /shared/portrait-bits.tcl?user_id=$proctored_user_id
- dict set row filter [string tolower "$last_name $first_names"]
- lappend rows $row
+ set proctoring_url [export_vars -no_base_encode -base $base_url { user_id object_id }]
+ set portrait_url /shared/portrait-bits.tcl?user_id=$user_id
+ set filter [string tolower "$last_name $first_names $student_id"]
}
- template::util::list_to_multirow users $rows
- template::multirow sort users first_names
- template::multirow sort users last_name
}
Index: openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl,v
diff -u -r1.1.2.12 -r1.1.2.13
--- openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl 9 Feb 2022 15:09:51 -0000 1.1.2.12
+++ openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl 10 Feb 2022 09:32:58 -0000 1.1.2.13
@@ -80,6 +80,16 @@
file mkdir -- $proctoring_dir
file rename -force -- ${file.tmpfile} $file_path
+ # Create an entry in the database for the file we have just
+ # collected, so that we can further enrich it with metadata in
+ # later postprocessing phases.
+ ::xo::dc dml -prepare {integer integer integer text text text} init_artifact {
+ insert into proctoring_object_artifacts
+ (object_id, user_id, timestamp, name, type, file)
+ values
+ (:object_id, :user_id, to_timestamp(:timestamp), :name, :type, :file_path)
+ }
+
# Notify a websocket about the upload so that e.g. a UI can be updated
# in real time.
if {$notify_p} {
Index: openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql,v
diff -u -r1.1.2.6 -r1.1.2.7
--- openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql 6 Aug 2021 11:31:12 -0000 1.1.2.6
+++ openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql 10 Feb 2022 09:32:58 -0000 1.1.2.7
@@ -66,4 +66,49 @@
-- access to this exam
);
+--
+-- The proctoring_object_artifacts table is meant to store information
+-- for each file collected during proctoring and provide a technical
+-- space to store additional metadata coming from e.g. postprocessing
+-- happening at a later phase.
+-- The metadata column is a JSON so that the particular type of
+-- information we store is flexible and can depend on the different
+-- kind of file or even be extended by downstream integrations.
+--
+create table proctoring_object_artifacts (
+ artifact_id serial primary key,
+ -- we might have referenced the proctoring_objects table rather
+ -- than acs_objects, but this makes the data model more
+ -- flexible for those integrations that do not store the
+ -- proctoring configuration in proctoring_objects (e.g. xowf)
+ object_id integer references acs_objects(object_id) on delete cascade,
+ user_id integer references users(user_id) on delete cascade,
+ timestamp timestamp not null default current_timestamp,
+ name text not null,
+ type text not null,
+ file text not null,
+ metadata jsonb
+);
+
+create index proctoring_object_artifacts_object_id_idx on
+ proctoring_object_artifacts(object_id);
+
+create index proctoring_object_artifacts_user_id_idx on
+ proctoring_object_artifacts(user_id);
+
+create index proctoring_object_artifacts_timestamp_idx on
+ proctoring_object_artifacts(timestamp);
+
+create index proctoring_object_artifacts_name_idx on
+ proctoring_object_artifacts(name);
+
+create index proctoring_object_artifacts_type_idx on
+ proctoring_object_artifacts(type);
+
+create index proctoring_object_artifacts_file_idx on
+ proctoring_object_artifacts(file);
+
+create unique index proctoring_object_artifacts_un_idx on
+ proctoring_object_artifacts(object_id, user_id, timestamp, name, type);
+
end;
Index: openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql,v
diff -u -r1.1.2.2 -r1.1.2.3
--- openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql 9 Jun 2021 15:55:54 -0000 1.1.2.2
+++ openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql 10 Feb 2022 09:32:58 -0000 1.1.2.3
@@ -2,3 +2,4 @@
drop table proctoring_objects;
drop table proctoring_examination_statement_acceptance;
drop table proctoring_safe_exam_browser_conf;
+drop table proctoring_object_artifacts;
Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/proctoring-support/sql/postgresql/upgrade/upgrade-3.0.0-3.1.0.sql'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/proctoring-support/tcl/apm-callback-procs.tcl'.
Fisheye: No comparison available. Pass `N' to diff?
Index: openacs-4/packages/proctoring-support/www/upload.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/www/upload.tcl,v
diff -u -r1.1.2.4 -r1.1.2.5
--- openacs-4/packages/proctoring-support/www/upload.tcl 21 Nov 2021 20:26:07 -0000 1.1.2.4
+++ openacs-4/packages/proctoring-support/www/upload.tcl 10 Feb 2022 09:32:59 -0000 1.1.2.5
@@ -7,7 +7,7 @@
file
file.tmpfile
{check_active_p:boolean true}
- {notify_p:boolean false}
+ {notify_p:boolean true}
{record_p:boolean true}
}
#ns_log notice "UPLOAD called with notify_p $notify_p record_p $record_p"