diff --git a/src/input_types/ilias.rs b/src/input_types/ilias.rs index d8b3784af397f588ab6aacbc4ea99032efed14df..5e9b395ba487040ca5eea26ed7032a13af0825c6 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 55ec18dd2c7a73271ced127957e7b23765f6c3a2..6adcc13c64284696b3e4a92388cad9a4312c489c 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 d36676cf98896f02a07b2280307d4a37c2489309..a965c096613255fb3ca43e26f9682022941ccae2 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 a006b7031c7f274a574f97fdf1bcb23f92435c0d..7a6e4b47bab5d226e3d3ca26feeb153dc1434e85 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,