Skip to content

Commit 218f9b9

Browse files
committed
basic functionality of reusing files based on xml_id.
Use xml_id_path to recreate proforma structure with tests and model_solutions containing multiple files
1 parent b30aaa2 commit 218f9b9

File tree

6 files changed

+153
-72
lines changed

6 files changed

+153
-72
lines changed

app/services/proforma_service/convert_exercise_to_task.rb

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -77,62 +77,68 @@ def uuid
7777
end
7878

7979
def model_solutions
80-
@exercise.files.filter {|file| file.role == 'reference_implementation' }.map do |file|
80+
@exercise.files.filter {|file| file.role == 'reference_implementation' }.group_by {|file| file.xml_id_path&.split('/')&.first || "co-ms-#{file.id}" }.map do |xml_id, files|
8181
ProformaXML::ModelSolution.new(
82-
id: "ms-#{file.id}",
83-
files: model_solution_file(file)
82+
id: xml_id || "ms-#{files.first.id}",
83+
files: files.map {|file| model_solution_file(file) }
8484
)
8585
end
8686
end
8787

8888
def model_solution_file(file)
89-
[
90-
task_file(file).tap do |ms_file|
91-
ms_file.used_by_grader = false
92-
ms_file.usage_by_lms = 'display'
93-
end,
94-
]
89+
task_file(file).tap do |ms_file|
90+
ms_file.used_by_grader = false
91+
ms_file.usage_by_lms = 'display'
92+
end
9593
end
9694

9795
def tests
98-
@exercise.files.filter(&:teacher_defined_assessment?).map do |file|
96+
@exercise.files.filter(&:teacher_defined_assessment?).group_by {|file| file.xml_id_path&.split('/')&.first || "co-test-#{file.id}" }.map do |xml_id, files|
9997
ProformaXML::Test.new(
100-
id: file.id,
101-
title: file.name,
102-
files: test_file(file),
103-
meta_data: test_meta_data(file)
98+
id: xml_id || files.first.id,
99+
title: files.first.name,
100+
files: files.map {|file| test_file(file) },
101+
meta_data: test_meta_data(files)
104102
)
105103
end
106104
end
107105

108-
def test_meta_data(file)
106+
def test_meta_data(files)
109107
{
110108
'@@order' => %w[test-meta-data],
111109
'test-meta-data' => {
112-
'@@order' => %w[CodeOcean:feedback-message CodeOcean:weight CodeOcean:hidden-feedback],
110+
'@@order' => %w[CodeOcean:test-file],
113111
'@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'},
114-
'CodeOcean:feedback-message' => {
115-
'@@order' => %w[$1],
116-
'$1' => file.feedback_message,
117-
},
118-
'CodeOcean:weight' => {
119-
'@@order' => %w[$1],
120-
'$1' => file.weight,
121-
},
122-
'CodeOcean:hidden-feedback' => {
123-
'@@order' => %w[$1],
124-
'$1' => file.hidden_feedback,
125-
},
112+
'CodeOcean:test-file' => files.to_h do |file|
113+
[
114+
"CodeOcean:#{file.xml_id_path&.split('/')&.last || file.id}", {
115+
'@@order' => %w[CodeOcean:feedback-message CodeOcean:weight CodeOcean:hidden-feedback],
116+
'@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'},
117+
'@id' => file.xml_id_path&.split('/')&.last || file.id,
118+
'@name' => file.name,
119+
'CodeOcean:feedback-message' => {
120+
'@@order' => %w[$1],
121+
'$1' => file.feedback_message,
122+
},
123+
'CodeOcean:weight' => {
124+
'@@order' => %w[$1],
125+
'$1' => file.weight,
126+
},
127+
'CodeOcean:hidden-feedback' => {
128+
'@@order' => %w[$1],
129+
'$1' => file.hidden_feedback,
130+
},
131+
}
132+
]
133+
end,
126134
},
127135
}
128136
end
129137

130138
def test_file(file)
131-
[
132-
task_file(file).tap do |t_file|
133-
t_file.used_by_grader = true
134-
end,
135-
]
139+
task_file(file).tap do |t_file|
140+
t_file.used_by_grader = true
141+
end
136142
end
137143

138144
def exercise_files
@@ -169,7 +175,7 @@ def task_files
169175

170176
def task_file(file)
171177
task_file = ProformaXML::TaskFile.new(
172-
id: file.id,
178+
id: file.xml_id_path&.split('/')&.last || file.id,
173179
filename: filename(file),
174180
usage_by_lms: file.read_only ? 'display' : 'edit',
175181
visible: file.hidden ? 'no' : 'yes'

app/services/proforma_service/convert_task_to_exercise.rb

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,44 +60,56 @@ def string_to_bool(str)
6060
end
6161

6262
def files
63-
model_solution_files + test_files + task_files.values.tap {|array| array.each {|file| file.role ||= 'regular_file' } }
63+
model_solution_files + test_files + task_files
6464
end
6565

6666
def test_files
67-
@task.tests.map do |test_object|
68-
task_files.delete(test_object.files.first.id).tap do |file|
69-
file.weight = extract_meta_data(test_object.meta_data&.dig('test-meta-data'), 'weight').presence || 1.0
70-
file.feedback_message = extract_meta_data(test_object.meta_data&.dig('test-meta-data'), 'feedback-message').presence || 'Feedback'
71-
file.hidden_feedback = extract_meta_data(test_object.meta_data&.dig('test-meta-data'), 'hidden-feedback').presence || false
72-
file.role ||= 'teacher_defined_test'
67+
@task.tests.flat_map do |test|
68+
test.files.map do |task_file|
69+
codeocean_file_from_task_file(task_file, test).tap do |file|
70+
file.weight = extract_meta_data(test.meta_data&.dig('test-meta-data'), 'test-file', task_file.id, 'weight').presence || 1.0
71+
file.feedback_message = extract_meta_data(test.meta_data&.dig('test-meta-data'), 'test-file', task_file.id, 'feedback-message').presence || 'Feedback'
72+
file.hidden_feedback = extract_meta_data(test.meta_data&.dig('test-meta-data'), 'test-file', task_file.id, 'hidden-feedback').presence || false
73+
file.role = 'teacher_defined_test' unless file.teacher_defined_assessment?
74+
end
7375
end
7476
end
7577
end
7678

7779
def model_solution_files
78-
@task.model_solutions.map do |model_solution_object|
79-
task_files.delete(model_solution_object.files.first.id).tap do |file|
80-
file.role ||= 'reference_implementation'
80+
@task.model_solutions.flat_map do |model_solution|
81+
model_solution.files.map do |task_file|
82+
codeocean_file_from_task_file(task_file, model_solution).tap do |file|
83+
file.role ||= 'reference_implementation'
84+
end
8185
end
8286
end
8387
end
8488

8589
def task_files
86-
@task_files ||= @task.all_files.reject {|file| file.id == 'ms-placeholder-file' }.to_h do |task_file|
87-
[task_file.id, codeocean_file_from_task_file(task_file)]
90+
@task.files.reject {|file| file.id == 'ms-placeholder-file' }.map do |task_file|
91+
codeocean_file_from_task_file(task_file).tap do |file|
92+
file.role ||= 'regular_file'
93+
end
8894
end
95+
# @task_files ||= @task.all_files.reject {|file| file.id == 'ms-placeholder-file' }.to_h do |task_file|
96+
# [task_file.id, codeocean_file_from_task_file(task_file)]
97+
# end
8998
end
9099

91-
def codeocean_file_from_task_file(file)
100+
def codeocean_file_from_task_file(file, parent_object = nil)
92101
extension = File.extname(file.filename)
93-
codeocean_file = CodeOcean::File.new(
102+
103+
codeocean_file = CodeOcean::File.where(context: @exercise).where('xml_id_path LIKE ?', "%#{file.id}").first_or_initialize(context: @exercise)
104+
codeocean_file.assign_attributes(
94105
context: @exercise,
95106
file_type: file_type(extension),
96107
hidden: file.visible != 'yes', # hides 'delayed' and 'no'
97108
name: File.basename(file.filename, '.*'),
98109
read_only: file.usage_by_lms != 'edit',
99110
role: extract_meta_data(@task.meta_data&.dig('meta-data'), 'files', "CO-#{file.id}", 'role'),
100-
path: File.dirname(file.filename).in?(['.', '']) ? nil : File.dirname(file.filename)
111+
path: File.dirname(file.filename).in?(['.', '']) ? nil : File.dirname(file.filename),
112+
xml_id_path: (parent_object.nil? ? '' : "#{parent_object.id}/") + file.id.to_s
101113
)
102114
if file.binary
103115
codeocean_file.native_file = FileIO.new(file.content.dup.force_encoding('UTF-8'), File.basename(file.filename))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class AddXmlPathToFiles < ActiveRecord::Migration[7.1]
4+
def change
5+
add_column :files, :xml_id_path, :string, null: true, default: nil
6+
end
7+
end

db/schema.rb

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[7.1].define(version: 2023_12_08_194632) do
13+
ActiveRecord::Schema[7.1].define(version: 2024_09_03_204319) do
1414
# These are extensions that must be enabled in order to support this database
1515
enable_extension "pg_trgm"
1616
enable_extension "pgcrypto"
1717
enable_extension "plpgsql"
1818

1919
create_table "anomaly_notifications", id: :serial, force: :cascade do |t|
20-
t.integer "contributor_id", null: false
2120
t.string "contributor_type", null: false
21+
t.integer "contributor_id", null: false
2222
t.integer "exercise_id", null: false
2323
t.integer "exercise_collection_id", null: false
2424
t.jsonb "reason"
@@ -46,10 +46,10 @@
4646
t.string "api_key"
4747
t.datetime "created_at"
4848
t.datetime "updated_at"
49+
t.string "user_type"
4950
t.integer "user_id"
5051
t.string "push_url"
5152
t.string "check_uuid_url"
52-
t.string "user_type"
5353
t.index ["user_type", "user_id"], name: "index_codeharbor_links_on_user_type_and_user_id"
5454
end
5555

@@ -137,8 +137,8 @@
137137
create_table "events", id: :serial, force: :cascade do |t|
138138
t.string "category"
139139
t.string "data"
140-
t.integer "user_id"
141140
t.string "user_type"
141+
t.integer "user_id"
142142
t.integer "exercise_id"
143143
t.integer "file_id"
144144
t.datetime "created_at", null: false
@@ -207,8 +207,8 @@
207207
t.datetime "created_at"
208208
t.datetime "updated_at"
209209
t.boolean "use_anomaly_detection", default: false
210-
t.integer "user_id"
211210
t.string "user_type"
211+
t.integer "user_id"
212212
t.index ["user_type", "user_id"], name: "index_exercise_collections_on_user_type_and_user_id"
213213
end
214214

@@ -298,8 +298,8 @@
298298

299299
create_table "files", id: :serial, force: :cascade do |t|
300300
t.text "content"
301-
t.integer "context_id"
302301
t.string "context_type"
302+
t.integer "context_id"
303303
t.integer "file_id"
304304
t.integer "file_type_id"
305305
t.boolean "hidden"
@@ -315,6 +315,7 @@
315315
t.string "path"
316316
t.integer "file_template_id"
317317
t.boolean "hidden_feedback", default: false, null: false
318+
t.string "xml_id_path"
318319
t.index ["context_id", "context_type"], name: "index_files_on_context_id_and_context_type"
319320
end
320321

@@ -492,8 +493,8 @@
492493

493494
create_table "searches", id: :serial, force: :cascade do |t|
494495
t.integer "exercise_id", null: false
495-
t.integer "user_id", null: false
496496
t.string "user_type", null: false
497+
t.integer "user_id", null: false
497498
t.string "search"
498499
t.datetime "created_at"
499500
t.datetime "updated_at"
@@ -523,7 +524,7 @@
523524
t.bigint "user_id"
524525
t.integer "role", limit: 2, default: 0, null: false, comment: "Used as enum in Rails"
525526
t.index ["study_group_id"], name: "index_study_group_memberships_on_study_group_id"
526-
t.index ["user_type", "user_id"], name: "index_study_group_memberships_on_user"
527+
t.index ["user_type", "user_id"], name: "index_study_group_memberships_on_user_type_and_user_id"
527528
end
528529

529530
create_table "study_groups", force: :cascade do |t|
@@ -551,8 +552,8 @@
551552
end
552553

553554
create_table "subscriptions", id: :serial, force: :cascade do |t|
554-
t.integer "user_id"
555555
t.string "user_type"
556+
t.integer "user_id"
556557
t.integer "request_for_comment_id"
557558
t.string "subscription_type"
558559
t.datetime "created_at", null: false
@@ -619,13 +620,13 @@
619620
t.datetime "created_at", null: false
620621
t.datetime "updated_at", null: false
621622
t.index ["file_type_id"], name: "index_tips_on_file_type_id"
622-
t.index ["user_type", "user_id"], name: "index_tips_on_user"
623+
t.index ["user_type", "user_id"], name: "index_tips_on_user_type_and_user_id"
623624
end
624625

625626
create_table "user_exercise_feedbacks", id: :serial, force: :cascade do |t|
626627
t.integer "exercise_id", null: false
627-
t.integer "user_id", null: false
628628
t.string "user_type", null: false
629+
t.integer "user_id", null: false
629630
t.integer "difficulty"
630631
t.integer "working_time_seconds"
631632
t.string "feedback_text"
@@ -638,8 +639,8 @@
638639
end
639640

640641
create_table "user_exercise_interventions", id: :serial, force: :cascade do |t|
641-
t.integer "contributor_id", null: false
642642
t.string "contributor_type", null: false
643+
t.integer "contributor_id", null: false
643644
t.integer "exercise_id", null: false
644645
t.integer "intervention_id", null: false
645646
t.integer "accumulated_worktime_s"
@@ -652,16 +653,16 @@
652653
end
653654

654655
create_table "user_proxy_exercise_exercises", id: :serial, force: :cascade do |t|
655-
t.integer "user_id"
656656
t.string "user_type"
657+
t.integer "user_id"
657658
t.integer "proxy_exercise_id"
658659
t.integer "exercise_id"
659660
t.datetime "created_at"
660661
t.datetime "updated_at"
661662
t.string "reason"
662663
t.index ["exercise_id"], name: "index_user_proxy_exercise_exercises_on_exercise_id"
663664
t.index ["proxy_exercise_id"], name: "index_user_proxy_exercise_exercises_on_proxy_exercise_id"
664-
t.index ["user_type", "user_id"], name: "index_user_proxy_exercise_exercises_on_user"
665+
t.index ["user_type", "user_id"], name: "index_user_proxy_exercise_exercises_on_user_type_and_user_id"
665666
end
666667

667668
add_foreign_key "anomaly_notifications", "exercise_collections"

0 commit comments

Comments
 (0)