From f244b9b94e7f6e11e3c1aa7358d1cd44c0c107a2 Mon Sep 17 00:00:00 2001
From: Dominik Seeger <dominik.seeger@gmx.net>
Date: Mon, 15 Feb 2021 15:53:39 +0100
Subject: [PATCH] added exam date to export as `exam_start_timestamp`

---
 src/input_types/ilias.rs   |  2 ++
 src/output_types/exam.rs   |  1 +
 src/transform/mod.rs       | 36 +++++++++++++++++++++++++++++++-----
 src/transform/sub_types.rs | 22 +++++++++++-----------
 4 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/src/input_types/ilias.rs b/src/input_types/ilias.rs
index d8b3784..5e9b395 100644
--- a/src/input_types/ilias.rs
+++ b/src/input_types/ilias.rs
@@ -134,6 +134,8 @@ pub mod qti_file {
     #[derive(Deserialize, Debug)]
     pub struct Assessment {
         pub section: Section,
+        #[serde(rename = "qtimetadata")]
+        pub qti_meta_data: QtiMetaData
     }
 
     #[derive(Deserialize, Debug)]
diff --git a/src/output_types/exam.rs b/src/output_types/exam.rs
index 55ec18d..6adcc13 100644
--- a/src/output_types/exam.rs
+++ b/src/output_types/exam.rs
@@ -7,6 +7,7 @@ use crate::output_types::submission_type::SubmissionType;
 #[derive(Serialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
 pub struct Exam {
     pub module: Module,
+    pub exam_start_timestamp: u32,
     pub submission_types: Vec<SubmissionType>,
     pub students: Vec<Student>,
 }
diff --git a/src/transform/mod.rs b/src/transform/mod.rs
index d36676c..a965c09 100644
--- a/src/transform/mod.rs
+++ b/src/transform/mod.rs
@@ -29,7 +29,7 @@ pub struct TransformContext {
     pub enable_latex_rendering: bool,
     pub sub_types_by_name: Option<HashMap<String, SubmissionType>>,
     // Use default values in place of user input
-    pub no_user_input: bool
+    pub no_user_input: bool,
 }
 
 impl Default for TransformContext {
@@ -40,7 +40,7 @@ impl Default for TransformContext {
             parse_cloze_questions: None,
             enable_latex_rendering: true,
             sub_types_by_name: None,
-            no_user_input: true
+            no_user_input: true,
         }
     }
 }
@@ -49,7 +49,7 @@ pub fn transform(
     ilias_export: IliasExportData,
     context: &TransformContext,
 ) -> Result<HektorOutput> {
-    let submission_types = transform_sub_types(ilias_export.qti_data, context)?;
+    let submission_types = transform_sub_types(&ilias_export.qti_data, context)?;
     let students = transform_students(ilias_export.results_data.tst_active.users, context)?;
     let submissions = transform_submissions(
         ilias_export.results_data.tst_solutions.solutions,
@@ -68,8 +68,18 @@ pub fn transform(
         .into_iter()
         .map(|(_key, val)| val)
         .collect();
+    let start_timestamp = match ilias_export.qti_data.assessment.qti_meta_data.meta_data_fields
+        .into_iter()
+        .find(|f| f.field_label == "activation_start_time") {
+        Some(t) => t.field_entry.parse::<u32>()?,
+        _ => {
+            log::warn!("Could not get exam date. Using 0 as default value");
+            0
+        }
+    };
     let exam = Exam {
         module,
+        exam_start_timestamp: start_timestamp,
         submission_types,
         students,
     };
@@ -90,7 +100,7 @@ pub fn merge_transform(
         "merge_transform must not be called with sub_types in context"
     );
     let last = ilias_exports.pop().unwrap();
-    let submission_types = transform_sub_types(last.qti_data, context)?;
+    let submission_types = transform_sub_types(&last.qti_data, context)?;
     let mut students = transform_students(last.results_data.tst_active.users, context)?;
     let mut submissions = transform_submissions(
         last.results_data.tst_solutions.solutions,
@@ -98,6 +108,21 @@ pub fn merge_transform(
         context,
     )?;
 
+    // Since we only export one exam, we take the start date of the first exam
+    let first_export = match ilias_exports.iter().next() {
+        Some(t) => t,
+        _ => unreachable!("Need to provide at least one export file")
+    };
+    let start_timestamp = match first_export.qti_data.assessment.qti_meta_data.meta_data_fields
+        .iter()
+        .find(|f| f.field_label == "activation_start_time") {
+        Some(t) => t.field_entry.parse::<u32>()?,
+        _ => {
+            log::warn!("Could not get exam date. Using 0 as default value");
+            0
+        }
+    };
+
     let sub_types_set: HashSet<SubmissionType> = submission_types.values().cloned().collect();
     let sub_types_by_name = submission_types
         .values()
@@ -112,7 +137,7 @@ pub fn merge_transform(
         // and already has occurred, the program is not aborted and the first passed
         // submission types are included in the output. Should however the names of the submission
         // types differ this is likely due to passing the wrong file and an error is returned
-        let other_sub_types = transform_sub_types(other_export.qti_data, context)?;
+        let other_sub_types = transform_sub_types(&other_export.qti_data, context)?;
         let other_sub_types_set = other_sub_types.values().cloned().collect();
         if sub_types_set != other_sub_types_set {
             let symmetric_diff: Vec<_> = sub_types_set
@@ -185,6 +210,7 @@ pub fn merge_transform(
         .collect();
     let exam = Exam {
         module,
+        exam_start_timestamp: start_timestamp,
         submission_types,
         students,
     };
diff --git a/src/transform/sub_types.rs b/src/transform/sub_types.rs
index a006b70..7a6e4b4 100644
--- a/src/transform/sub_types.rs
+++ b/src/transform/sub_types.rs
@@ -16,7 +16,7 @@ use crate::output_types::submission_type::{ProgrammingLang, SubmissionType};
 use crate::transform::TransformContext;
 
 pub fn transform_sub_types(
-    ilias_qti_data: QtiData,
+    ilias_qti_data: &QtiData,
     context: &TransformContext,
 ) -> Result<HashMap<String, SubmissionType>> {
     let allowed_question_types = if context.parse_cloze_questions.is_some() {
@@ -27,7 +27,7 @@ pub fn transform_sub_types(
 
     let sub_types = {
         let mut map = HashMap::new();
-        for item in ilias_qti_data.assessment.section.items {
+        for item in &ilias_qti_data.assessment.section.items {
             if let Some((id, sub_type)) =
                 transform_sub_type(item, &allowed_question_types, context)?
             {
@@ -40,7 +40,7 @@ pub fn transform_sub_types(
 }
 
 fn transform_sub_type(
-    qti_item: Item,
+    qti_item: &Item,
     allowed_question_types: &[&str],
     context: &TransformContext,
 ) -> Result<Option<(String, SubmissionType)>> {
@@ -51,14 +51,14 @@ fn transform_sub_type(
         .context("Unable to parse question id from qti data")?
         .as_str()
         .to_owned();
-    let name = qti_item.title;
+    let name = &qti_item.title;
 
     let meta_fields: HashMap<String, String> = qti_item
         .item_meta_data
         .qti_meta_data
         .meta_data_fields
-        .into_iter()
-        .map(|field| (field.field_label, field.field_entry))
+        .iter()
+        .map(|field| (field.field_label.clone(), field.field_entry.clone()))
         .collect();
     // skip transformation for this item if question type is not parsed
     match meta_fields.get(MetaField::QuestionType.map_index()) {
@@ -71,7 +71,7 @@ fn transform_sub_type(
     // Otherwise the data is taken from the xml data/input
     let (solution, full_score, programming_language) =
         if let Some(sub_types) = context.sub_types_by_name.as_ref() {
-            let sub_type = sub_types.get(&name).with_context(|| {
+            let sub_type = sub_types.get(name).with_context(|| {
                 format!(
                     "Passed sub types as context but \"{}\" is not contained",
                     &name
@@ -114,7 +114,7 @@ fn transform_sub_type(
         .presentation
         .flow
         .materials
-        .into_iter()
+        .iter()
         .filter_map(|mat| match mat {
             Material::Material { mat_text } => Some(mat_text),
             Material::Other => None,
@@ -126,16 +126,16 @@ fn transform_sub_type(
             text: Some(text),
         }) => {
             if text_type == "text/xhtml" && context.enable_latex_rendering {
-                preprocess_material_text_latex(text)?
+                preprocess_material_text_latex(text.clone())?
             } else {
-                text
+                text.clone()
             }
         }
         _ => "No description available.".into(),
     };
 
     let sub_type = SubmissionType {
-        name,
+        name: String::from(name),
         full_score,
         description,
         solution,
-- 
GitLab