-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathconvert_task_to_exercise.rb
More file actions
136 lines (117 loc) · 5.24 KB
/
convert_task_to_exercise.rb
File metadata and controls
136 lines (117 loc) · 5.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# frozen_string_literal: true
module ProformaService
class ConvertTaskToExercise < ServiceBase
def initialize(task:, user:, exercise: nil)
super()
@task = task
@user = user
@exercise = exercise || Exercise.new(unpublished: true)
end
def execute
ActiveRecord::Base.transaction do
import_task
end
@exercise
end
private
def import_task
destroy_old_files
@exercise.assign_attributes(
user: @user,
title: @task.title,
description: @task.description.presence || @task.title,
public: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'public')) || false,
hide_file_tree: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'hide_file_tree')) || false,
allow_file_creation: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'allow_file_creation')) || false,
allow_auto_completion: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'allow_auto_completion')) || false,
expected_difficulty: extract_meta_data(@task.meta_data&.dig('meta-data'), 'expected_difficulty') || 1,
execution_environment_id:,
files:
)
end
def destroy_old_files
file_ids = (@task.files + @task.tests.flat_map(&:files) + @task.model_solutions.flat_map(&:files)).map(&:id)
@exercise.files.reject {|file| file_ids.include? file.xml_id_path.last }.each(&:destroy)
end
def extract_meta_data(meta_data, *path)
current_level = meta_data
path.each {|attribute| current_level = current_level.is_a?(Hash) ? current_level&.dig("CodeOcean:#{attribute}") : current_level&.find {|entry| entry['@id'] == attribute } } # || current_level
current_level&.dig('$1')
end
def execution_environment_id
from_meta_data = extract_meta_data(@task.meta_data&.dig('meta-data'), 'execution_environment_id')
return from_meta_data if from_meta_data
return nil unless @task.proglang
ex_envs_with_name_and_version = ExecutionEnvironment.where('docker_image ilike ?', "%#{@task.proglang[:name]}%#{@task.proglang[:version]}%")
return ex_envs_with_name_and_version.first.id if ex_envs_with_name_and_version.any?
ex_envs_with_name = ExecutionEnvironment.where('docker_image like ?', "%#{@task.proglang[:name]}%")
return ex_envs_with_name.first.id if ex_envs_with_name.any?
nil
end
def string_to_bool(str)
return true if str == 'true'
return false if str == 'false'
nil
end
def files
model_solution_files + test_files + task_files
end
def test_files
@task.tests.flat_map do |test|
test.files.map do |task_file|
codeocean_file_from_task_file(task_file, test).tap do |file|
file.weight = extract_meta_data(test.meta_data&.dig('test-meta-data'), 'test-file', task_file.id, 'weight').presence || 1.0
file.feedback_message = extract_meta_data(test.meta_data&.dig('test-meta-data'), 'test-file', task_file.id, 'feedback-message').presence || 'Feedback'
file.hidden_feedback = extract_meta_data(test.meta_data&.dig('test-meta-data'), 'test-file', task_file.id, 'hidden-feedback').presence || false
file.role = 'teacher_defined_test' unless file.teacher_defined_assessment?
end
end
end
end
def model_solution_files
@task.model_solutions.flat_map do |model_solution|
model_solution.files.map do |task_file|
codeocean_file_from_task_file(task_file, model_solution).tap do |file|
file.role ||= 'reference_implementation'
end
end
end
end
def task_files
@task.files.reject {|file| file.id == 'ms-placeholder-file' }.map do |task_file|
codeocean_file_from_task_file(task_file).tap do |file|
file.role ||= 'regular_file'
end
end
end
def codeocean_file_from_task_file(file, parent_object = nil)
extension = File.extname(file.filename)
# checking the last element of xml_id_path array for file.id
codeocean_file = @exercise.files.detect {|f| f.xml_id_path.last == file.id } || @exercise.files.new
codeocean_file.assign_attributes(
context: @exercise,
file_type: file_type(extension),
hidden: file.visible != 'yes', # hides 'delayed' and 'no'
name: File.basename(file.filename, '.*'),
read_only: file.usage_by_lms != 'edit',
role: extract_meta_data(@task.meta_data&.dig('meta-data'), 'files', "CO-#{file.id}", 'role'),
path: File.dirname(file.filename).in?(['.', '']) ? nil : File.dirname(file.filename),
xml_id_path: (parent_object.nil? ? [file.id] : [parent_object.id, file.id]).map(&:to_s)
)
if file.binary
codeocean_file.native_file = FileIO.new(file.content.dup.force_encoding('UTF-8'), File.basename(file.filename))
else
codeocean_file.content = file.content
end
codeocean_file
end
def file_type(extension)
FileType.find_or_create_by(file_extension: extension) do |file_type|
file_type.name = "Imported #{extension}"
file_type.user = @user
file_type.indent_size = 4
file_type.editor_mode = 'ace/mode/plain_text'
end
end
end
end