Skip to content

Commit ff25d49

Browse files
authored
Merge pull request #188 from openHPI/add_meta_data_to_task
Add meta data to task
2 parents 0a43226 + c3bbc07 commit ff25d49

File tree

14 files changed

+186
-33
lines changed

14 files changed

+186
-33
lines changed

Gemfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
proforma (0.6.1)
4+
proforma (0.7)
55
activemodel (>= 5.2.3, < 7.0.0)
66
activesupport (>= 5.2.3, < 7.0.0)
77
nokogiri (>= 1.10.2, < 2.0.0)

README.md

+25-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This gem offers a ruby implementation of https://github.com/ProFormA/proformaxml
1111
Add this line to your application's Gemfile:
1212

1313
```ruby
14-
gem 'proforma', git: 'git://github.com/openHPI/proforma.git', tag: 'v0.5'
14+
gem 'proforma', git: 'git://github.com/openHPI/proforma.git', tag: 'v0.7'
1515
```
1616

1717
And then execute:
@@ -52,6 +52,15 @@ Proforma::Task.new(
5252
description: 'description',
5353
internal_description: 'internal_description',
5454
proglang: {name: 'proglang_name', version: '123'},
55+
meta_data: {
56+
CodeOcean: {
57+
meta_data_key: 'meta_data_content',
58+
secrets: {
59+
server_key: 'the key',
60+
other_key: 'another key'
61+
}
62+
}
63+
},
5564
files: [
5665
Proforma::TaskFile.new(
5766
id: 'file_id_1',
@@ -91,7 +100,11 @@ Proforma::Task.new(
91100
internal_description: 'internal_description',
92101
)
93102
],
94-
meta_data: [{ namespace: 'prefix', key: 'key', value: 'value' }]
103+
meta_data: {
104+
CodeOcean: {
105+
entry_point: 'junit/assert123.java'
106+
}
107+
}
95108
)
96109
],
97110
uuid: '2c8ee23e-fa98-4ea9-b6a5-9a0066ebac1f',
@@ -117,10 +130,10 @@ Proforma::Task.new(
117130
)
118131

119132
```
120-
Generated XML from task above with `custom_namespaces: [{prefix: 'prefix', uri: 'test.namespace'}]`
133+
Generated XML from task above with `custom_namespaces: [{prefix: 'CodeOcean', uri: 'codeocean.openhpi.de'}]`
121134
```xml
122135
<?xml version="1.0" encoding="UTF-8"?>
123-
<task xmlns="urn:proforma:v2.0.1" xmlns:prefix="test.namespace" uuid="2c8ee23e-fa98-4ea9-b6a5-9a0066ebac1f" lang="de" parent-uuid="abf097f5-0df0-468d-8ce4-13460c34cd3b">
136+
<task xmlns="urn:proforma:v2.0.1" xmlns:CodeOcean="codeocean.openhpi.de" uuid="2c8ee23e-fa98-4ea9-b6a5-9a0066ebac1f" lang="de" parent-uuid="abf097f5-0df0-468d-8ce4-13460c34cd3b">
124137
<title>title</title>
125138
<description>description</description>
126139
<internal-description>internal_description</internal-description>
@@ -160,12 +173,18 @@ Generated XML from task above with `custom_namespaces: [{prefix: 'prefix', uri:
160173
<fileref refid="test_file_1"/>
161174
</filerefs>
162175
<test-meta-data>
163-
<prefix:key>value</prefix:key>
176+
<CodeOcean:entry_point>junit/assert123.java</CodeOcean:entry_point>
164177
</test-meta-data>
165178
</test-configuration>
166179
</test>
167180
</tests>
168-
<meta-data/>
181+
<meta-data>
182+
<CodeOcean:meta_data_key>meta_data_content</CodeOcean:meta_data_key>
183+
<CodeOcean:secrets>
184+
<CodeOcean:server_key>the key</CodeOcean:server_key>
185+
<CodeOcean:other_key>another key</CodeOcean:other_key>
186+
</CodeOcean:secrets>
187+
</meta-data>
169188
</task>
170189
```
171190
## Development

bin/console

+18-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ Pry.start
2424
# proglang: { name: 'Ruby', version: '1' },
2525
# uuid: SecureRandom.uuid,
2626
# language: 'de',
27+
# meta_data: {
28+
# CodeOcean: {
29+
# metao: 'datao',
30+
# bla: 'bloo',
31+
# secrets: {
32+
# server_key: 'asdf',
33+
# other_key: 'fdsa'
34+
# }
35+
# }
36+
# },
2737
# files: [
2838
# Proforma::TaskFile.new(
2939
# id: 'file1',
@@ -62,12 +72,16 @@ Pry.start
6272
# binary: false
6373
# )
6474
# ],
65-
# meta_data: [
66-
# { namespace: 'openHPI', key: 'meta', value: 'data' }
67-
# ]
75+
# meta_data: {
76+
# CodeOcean: {
77+
# meta: 'data'
78+
# }
79+
# }
6880
# )
6981
# ]
7082
# )
7183

72-
# File.open('../testfiles/testfile.zip', 'wb') { |file| file.write(Proforma::Exporter.new(task: task, custom_namespaces: [{prefix: 'openHPI', uri: 'open.hpi.de'}]).perform.string) }
84+
# task = Proforma::Task.new(title: 't', description: 'd', internal_description:'id', proglang: { name: 'Ruby', version: '1' }, uuid: SecureRandom.uuid, language: 'de', meta_data: { CodeOcean: { metao: 'datao', bla: 'bloo', secrets: { server_key: 'asdf', other_key: 'fdsa' } } }, files: [Proforma::TaskFile.new(id: 'file1', content: 'c', used_by_grader: true, visible: 'yes', binary: false)], model_solutions: [Proforma::ModelSolution.new(id: 'ms1', description: 'd', internal_description:'id', files: [Proforma::TaskFile.new(id: 'ms-file1', content: 'ms-c', used_by_grader: true, visible: 'yes', binary: false)])], tests: [Proforma::Test.new(id: 'test1', test_type: 'type', files: [Proforma::TaskFile.new(id: 'testfile1', content: 'testc', used_by_grader: true, visible: 'yes', binary: false)], meta_data: { CodeOcean: { meta: 'data' } })])
85+
86+
# File.open('../testfiles/testfile.zip', 'wb') { |file| file.write(Proforma::Exporter.new(task: task, custom_namespaces: [{prefix: 'CodeOcean', uri: 'codeocean.openhpi.de'}]).perform.string) }
7387
#

lib/proforma/helpers/export_helpers.rb

+21-2
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,32 @@ def add_test_configuration(xml, test)
2929
add_unittest_configuration(xml, test)
3030
if test.meta_data
3131
xml.send('test-meta-data') do
32-
# underscore is used to disambiguate tag names from ruby methods
33-
test.meta_data.each { |entry| xml[entry[:namespace]].send("#{entry[:key]}_", entry[:value]) }
32+
meta_data(xml, test.meta_data)
3433
end
3534
end
3635
end
3736
end
3837

38+
def inner_meta_data(xml, namespace, data)
39+
data.each do |key, value|
40+
case value.class.name
41+
when 'Hash'
42+
# underscore is used to disambiguate tag names from ruby methods
43+
xml[namespace].send("#{key}_") do |meta_data_xml|
44+
inner_meta_data(meta_data_xml, namespace, value)
45+
end
46+
else
47+
xml[namespace].send("#{key}_", value)
48+
end
49+
end
50+
end
51+
52+
def meta_data(xml, meta_data)
53+
meta_data.each do |namespace, data|
54+
inner_meta_data(xml, namespace, data)
55+
end
56+
end
57+
3958
def add_unittest_configuration(xml, test)
4059
return unless test.test_type == 'unittest' && !test.configuration.nil?
4160

lib/proforma/helpers/import_helpers.rb

+12-8
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def add_test_configuration(test, test_node)
5858
test.files = test_files_from_test_configuration(test_configuration_node)
5959
test.configuration = extra_configuration_from_test_configuration(test_configuration_node)
6060
meta_data_node = test_node.xpath('xmlns:test-configuration').xpath('xmlns:test-meta-data')
61-
test.meta_data = any_data_tag(meta_data_node.first) unless meta_data_node.blank?
61+
test.meta_data = meta_data(meta_data_node, use_namespace: true) unless meta_data_node.blank?
6262
end
6363

6464
def extra_configuration_from_test_configuration(test_configuration_node)
@@ -74,13 +74,17 @@ def test_files_from_test_configuration(test_configuration_node)
7474
files_from_filerefs(test_configuration_node.search('filerefs'))
7575
end
7676

77-
def any_data_tag(any_data_node)
78-
[].tap do |any_data|
79-
return any_data if any_data_node.nil?
80-
81-
any_data_node.children.each do |any_data_tag|
82-
any_data << {namespace: any_data_node.children.first.namespace.prefix, key: any_data_tag.name,
83-
value: any_data_tag.children.first.text}
77+
def meta_data(any_data_node, use_namespace: false)
78+
# use_namespace forces the use of the namespace as hash key - it should only be used at the entry of the recursion
79+
{}.tap do |any_data|
80+
any_data_node.children.each do |node|
81+
key = (use_namespace ? node.namespace.prefix : any_data_node.name).to_sym
82+
any_data[key] = if node.node_type == Nokogiri::XML::Node::TEXT_NODE
83+
node.text
84+
else
85+
# preserve any existing data in the nested hash
86+
(any_data[key] || {}).merge meta_data(node)
87+
end
8488
end
8589
end
8690
end

lib/proforma/models/task.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
module Proforma
1010
class Task < Base
1111
attr_accessor :title, :description, :internal_description, :proglang, :uuid, :parent_uuid,
12-
:language, :model_solutions, :files, :tests
12+
:language, :model_solutions, :files, :tests, :meta_data
1313

1414
def initialize(attributes = {})
1515
super
1616
self.files = [] if files.nil?
1717
self.tests = [] if tests.nil?
1818
self.model_solutions = [] if model_solutions.nil?
19+
self.meta_data = {} if meta_data.nil?
1920
end
2021

2122
def all_files

lib/proforma/services/exporter.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def add_internal_description_to_xml(xml, internal_description)
4747
end
4848

4949
def add_meta_data(xml)
50-
xml.send('meta-data') {}
50+
xml.send('meta-data') { meta_data(xml, @task.meta_data) }
5151
end
5252

5353
def add_objects_to_xml(xml)

lib/proforma/services/importer.rb

+6
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def set_data
4343
set_files
4444
set_model_solutions
4545
set_tests
46+
set_meta_data
4647
end
4748

4849
def set_namespaces
@@ -80,6 +81,11 @@ def set_model_solutions
8081
end
8182
end
8283

84+
def set_meta_data
85+
meta_data_node = @task_node.xpath('xmlns:meta-data')
86+
@task.meta_data = meta_data(meta_data_node, use_namespace: true) if meta_data_node.text.present?
87+
end
88+
8389
def add_model_solution(model_solution_node)
8490
model_solution = ModelSolution.new
8591
model_solution.id = model_solution_node.attributes['id'].value

lib/proforma/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module Proforma
4-
VERSION = '0.6.1'
4+
VERSION = '0.7'
55
end

spec/custom_matchers/equal_task_spec.rb

+33-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@
2525
end
2626
end
2727

28+
context 'when only one task has a file' do
29+
let(:task2) { build(:task, files: [build(:task_file)]) }
30+
31+
it 'fails the comparison' do
32+
expect(task).not_to be_an_equal_task_as task2
33+
end
34+
end
35+
2836
context 'when the tasks are complex' do
2937
let(:task) { build(:task, :with_everything) }
3038
let(:task2) { build(:task, :with_everything) }
@@ -40,6 +48,22 @@
4048
expect(task).to be_an_equal_task_as task2
4149
end
4250

51+
context 'with a tiny change in the meta_data' do
52+
before { task.meta_data[:namespace][:meta] = 'doto' }
53+
54+
it 'fails' do
55+
expect(task).not_to be_an_equal_task_as task2
56+
end
57+
end
58+
59+
context 'with a tiny change in the nested meta_data' do
60+
before { task.meta_data[:namespace][:nested][:test] = 'doto' }
61+
62+
it 'fails' do
63+
expect(task).not_to be_an_equal_task_as task2
64+
end
65+
end
66+
4367
context 'with a tiny change in a file' do
4468
before { task.files.first.content += 'a' }
4569

@@ -63,6 +87,14 @@
6387
expect(task).not_to be_an_equal_task_as task2
6488
end
6589
end
90+
91+
context 'with a tiny change in a test meta_data' do
92+
before { task.tests.first.meta_data[:test_meta] = 'diti' }
93+
94+
it 'fails' do
95+
expect(task).not_to be_an_equal_task_as task2
96+
end
97+
end
6698
end
6799

68100
context 'with two similar tasks with 3 files' do
@@ -78,7 +110,7 @@
78110
expect(task).to be_an_equal_task_as task2
79111
end
80112

81-
context 'when both tasks have two equal exercises, but are still different' do
113+
context 'when both tasks have two equal files, but are still different' do
82114
let(:files_3_id) { 2 }
83115
let(:files2_3_id) { 1 }
84116

spec/factories/task.rb

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
trait(:with_embedded_bin_file) { files { build_list(:task_file, 1, :populated, :small_content, :binary) } }
1818
trait(:with_attached_txt_file) { files { build_list(:task_file, 1, :populated, :large_content, :text) } }
1919
trait(:with_attached_bin_file) { files { build_list(:task_file, 1, :populated, :large_content, :binary) } }
20+
trait(:with_meta_data) { meta_data { {namespace: {meta: 'data', nested: {test: {abc: '123'}, foo: 'bar'}}} } }
2021

2122
trait(:with_model_solution) { model_solutions { build_list(:model_solution, 1, :populated) } }
2223
trait(:with_test) { tests { build_list(:test, 1, :populated) } }
@@ -26,6 +27,7 @@
2627
with_multiple_embedded_txt_files
2728
with_model_solution
2829
with_test_with_meta_data
30+
with_meta_data
2931
end
3032

3133
trait(:invalid) { files { build_list(:task_file, 1, :invalid) } }

spec/factories/test.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
end
2121

2222
trait(:with_meta_data) do
23-
meta_data { [{namespace: 'test', key: 'meta', value: 'data'}] }
23+
meta_data { {namespace: {test_meta: 'data', test: 'test_data'}} }
2424
end
2525

2626
trait(:with_multiple_files) do

spec/proforma/exporter_spec.rb

+42-6
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,37 @@
131131
it_behaves_like 'populated task node'
132132
end
133133

134+
context 'when task has meta_data' do
135+
let(:custom_namespaces) { [{prefix: 'namespace', uri: 'custom_namespace.org'}] }
136+
let(:task) do
137+
build(:task, :populated, meta_data: {namespace: {meta: 'data', nested: {test: {abc: '123'}, foo: 'bar'}}})
138+
end
139+
let(:meta_data_node) { doc.xpath('/xmlns:task/xmlns:meta-data') }
140+
141+
it_behaves_like 'task node'
142+
it_behaves_like 'populated task node'
143+
144+
it 'adds meta-data node task node' do
145+
expect(meta_data_node).to have(1).items
146+
end
147+
148+
it 'adds two children nodes to meta-data node' do
149+
expect(meta_data_node.children).to have(2).items
150+
end
151+
152+
it 'adds meta node with correct namespace to meta-data node' do
153+
expect(meta_data_node.xpath('namespace:meta').text).to eql 'data'
154+
end
155+
156+
it 'adds nested test node with correct namespace to meta-data node' do
157+
expect(meta_data_node.xpath('namespace:nested/namespace:foo').text).to eql 'bar'
158+
end
159+
160+
it 'adds multiple times nested node with correct namespace to meta-data node' do
161+
expect(meta_data_node.xpath('namespace:nested/namespace:test/namespace:abc').text).to eql '123'
162+
end
163+
end
164+
134165
context 'when a populated task with embedded text file is supplied' do
135166
let(:task) { build(:task, :populated, :with_embedded_txt_file) }
136167
let(:file) { task.all_files.filter { |file| file.id != 'ms-placeholder-file' }.first }
@@ -280,9 +311,10 @@
280311
end
281312

282313
context 'when test has meta-data' do
314+
let(:custom_namespaces) { [{prefix: 'namespace', uri: 'custom_namespace.org'}] }
283315
let(:task) do
284-
build(:task, :populated, tests: build_list(:test, 1, meta_data: [{namespace: 'test', key: 'test', value: 'data'},
285-
{namespace: 'test', key: 'meta', value: 'data'}]))
316+
build(:task, :populated,
317+
tests: build_list(:test, 1, meta_data: {namespace: {meta: 'data', nested: {test: {abc: '123'}, foo: 'bar'}}}))
286318
end
287319
let(:meta_data_node) { doc.xpath('/xmlns:task/xmlns:tests/xmlns:test/xmlns:test-configuration/xmlns:test-meta-data') }
288320

@@ -296,12 +328,16 @@
296328
expect(meta_data_node.children).to have(2).items
297329
end
298330

299-
it 'adds test node with correct namespace to test-meta-data node' do
300-
expect(meta_data_node.xpath('test:test').text).to eql 'data'
331+
it 'adds meta node with correct namespace to test-meta-data node' do
332+
expect(meta_data_node.xpath('namespace:meta').text).to eql 'data'
301333
end
302334

303-
it 'adds meta node with correct namespace to test-meta-data node' do
304-
expect(meta_data_node.xpath('test:meta').text).to eql 'data'
335+
it 'adds nested node with correct namespace to test-meta-data node' do
336+
expect(meta_data_node.xpath('namespace:nested/namespace:foo').text).to eql 'bar'
337+
end
338+
339+
it 'adds multiple times nested node with correct namespace to test-meta-data node' do
340+
expect(meta_data_node.xpath('namespace:nested/namespace:test/namespace:abc').text).to eql '123'
305341
end
306342
end
307343

0 commit comments

Comments
 (0)