Skip to content

Commit

Permalink
Merge pull request #188 from openHPI/add_meta_data_to_task
Browse files Browse the repository at this point in the history
Add meta data to task
  • Loading branch information
kkoehn authored Dec 9, 2021
2 parents 0a43226 + c3bbc07 commit ff25d49
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 33 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
proforma (0.6.1)
proforma (0.7)
activemodel (>= 5.2.3, < 7.0.0)
activesupport (>= 5.2.3, < 7.0.0)
nokogiri (>= 1.10.2, < 2.0.0)
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This gem offers a ruby implementation of https://github.com/ProFormA/proformaxml
Add this line to your application's Gemfile:

```ruby
gem 'proforma', git: 'git://github.com/openHPI/proforma.git', tag: 'v0.5'
gem 'proforma', git: 'git://github.com/openHPI/proforma.git', tag: 'v0.7'
```

And then execute:
Expand Down Expand Up @@ -52,6 +52,15 @@ Proforma::Task.new(
description: 'description',
internal_description: 'internal_description',
proglang: {name: 'proglang_name', version: '123'},
meta_data: {
CodeOcean: {
meta_data_key: 'meta_data_content',
secrets: {
server_key: 'the key',
other_key: 'another key'
}
}
},
files: [
Proforma::TaskFile.new(
id: 'file_id_1',
Expand Down Expand Up @@ -91,7 +100,11 @@ Proforma::Task.new(
internal_description: 'internal_description',
)
],
meta_data: [{ namespace: 'prefix', key: 'key', value: 'value' }]
meta_data: {
CodeOcean: {
entry_point: 'junit/assert123.java'
}
}
)
],
uuid: '2c8ee23e-fa98-4ea9-b6a5-9a0066ebac1f',
Expand All @@ -117,10 +130,10 @@ Proforma::Task.new(
)

```
Generated XML from task above with `custom_namespaces: [{prefix: 'prefix', uri: 'test.namespace'}]`
Generated XML from task above with `custom_namespaces: [{prefix: 'CodeOcean', uri: 'codeocean.openhpi.de'}]`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<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">
<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">
<title>title</title>
<description>description</description>
<internal-description>internal_description</internal-description>
Expand Down Expand Up @@ -160,12 +173,18 @@ Generated XML from task above with `custom_namespaces: [{prefix: 'prefix', uri:
<fileref refid="test_file_1"/>
</filerefs>
<test-meta-data>
<prefix:key>value</prefix:key>
<CodeOcean:entry_point>junit/assert123.java</CodeOcean:entry_point>
</test-meta-data>
</test-configuration>
</test>
</tests>
<meta-data/>
<meta-data>
<CodeOcean:meta_data_key>meta_data_content</CodeOcean:meta_data_key>
<CodeOcean:secrets>
<CodeOcean:server_key>the key</CodeOcean:server_key>
<CodeOcean:other_key>another key</CodeOcean:other_key>
</CodeOcean:secrets>
</meta-data>
</task>
```
## Development
Expand Down
22 changes: 18 additions & 4 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ Pry.start
# 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',
Expand Down Expand Up @@ -62,12 +72,16 @@ Pry.start
# binary: false
# )
# ],
# meta_data: [
# { namespace: 'openHPI', key: 'meta', value: 'data' }
# ]
# meta_data: {
# CodeOcean: {
# meta: 'data'
# }
# }
# )
# ]
# )

# File.open('../testfiles/testfile.zip', 'wb') { |file| file.write(Proforma::Exporter.new(task: task, custom_namespaces: [{prefix: 'openHPI', uri: 'open.hpi.de'}]).perform.string) }
# 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' } })])

# File.open('../testfiles/testfile.zip', 'wb') { |file| file.write(Proforma::Exporter.new(task: task, custom_namespaces: [{prefix: 'CodeOcean', uri: 'codeocean.openhpi.de'}]).perform.string) }
#
23 changes: 21 additions & 2 deletions lib/proforma/helpers/export_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,32 @@ def add_test_configuration(xml, test)
add_unittest_configuration(xml, test)
if test.meta_data
xml.send('test-meta-data') do
# underscore is used to disambiguate tag names from ruby methods
test.meta_data.each { |entry| xml[entry[:namespace]].send("#{entry[:key]}_", entry[:value]) }
meta_data(xml, test.meta_data)
end
end
end
end

def inner_meta_data(xml, namespace, data)
data.each do |key, value|
case value.class.name
when 'Hash'
# underscore is used to disambiguate tag names from ruby methods
xml[namespace].send("#{key}_") do |meta_data_xml|
inner_meta_data(meta_data_xml, namespace, value)
end
else
xml[namespace].send("#{key}_", value)
end
end
end

def meta_data(xml, meta_data)
meta_data.each do |namespace, data|
inner_meta_data(xml, namespace, data)
end
end

def add_unittest_configuration(xml, test)
return unless test.test_type == 'unittest' && !test.configuration.nil?

Expand Down
20 changes: 12 additions & 8 deletions lib/proforma/helpers/import_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def add_test_configuration(test, test_node)
test.files = test_files_from_test_configuration(test_configuration_node)
test.configuration = extra_configuration_from_test_configuration(test_configuration_node)
meta_data_node = test_node.xpath('xmlns:test-configuration').xpath('xmlns:test-meta-data')
test.meta_data = any_data_tag(meta_data_node.first) unless meta_data_node.blank?
test.meta_data = meta_data(meta_data_node, use_namespace: true) unless meta_data_node.blank?
end

def extra_configuration_from_test_configuration(test_configuration_node)
Expand All @@ -74,13 +74,17 @@ def test_files_from_test_configuration(test_configuration_node)
files_from_filerefs(test_configuration_node.search('filerefs'))
end

def any_data_tag(any_data_node)
[].tap do |any_data|
return any_data if any_data_node.nil?

any_data_node.children.each do |any_data_tag|
any_data << {namespace: any_data_node.children.first.namespace.prefix, key: any_data_tag.name,
value: any_data_tag.children.first.text}
def meta_data(any_data_node, use_namespace: false)
# use_namespace forces the use of the namespace as hash key - it should only be used at the entry of the recursion
{}.tap do |any_data|
any_data_node.children.each do |node|
key = (use_namespace ? node.namespace.prefix : any_data_node.name).to_sym
any_data[key] = if node.node_type == Nokogiri::XML::Node::TEXT_NODE
node.text
else
# preserve any existing data in the nested hash
(any_data[key] || {}).merge meta_data(node)
end
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/proforma/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
module Proforma
class Task < Base
attr_accessor :title, :description, :internal_description, :proglang, :uuid, :parent_uuid,
:language, :model_solutions, :files, :tests
:language, :model_solutions, :files, :tests, :meta_data

def initialize(attributes = {})
super
self.files = [] if files.nil?
self.tests = [] if tests.nil?
self.model_solutions = [] if model_solutions.nil?
self.meta_data = {} if meta_data.nil?
end

def all_files
Expand Down
2 changes: 1 addition & 1 deletion lib/proforma/services/exporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def add_internal_description_to_xml(xml, internal_description)
end

def add_meta_data(xml)
xml.send('meta-data') {}
xml.send('meta-data') { meta_data(xml, @task.meta_data) }
end

def add_objects_to_xml(xml)
Expand Down
6 changes: 6 additions & 0 deletions lib/proforma/services/importer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def set_data
set_files
set_model_solutions
set_tests
set_meta_data
end

def set_namespaces
Expand Down Expand Up @@ -80,6 +81,11 @@ def set_model_solutions
end
end

def set_meta_data
meta_data_node = @task_node.xpath('xmlns:meta-data')
@task.meta_data = meta_data(meta_data_node, use_namespace: true) if meta_data_node.text.present?
end

def add_model_solution(model_solution_node)
model_solution = ModelSolution.new
model_solution.id = model_solution_node.attributes['id'].value
Expand Down
2 changes: 1 addition & 1 deletion lib/proforma/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Proforma
VERSION = '0.6.1'
VERSION = '0.7'
end
34 changes: 33 additions & 1 deletion spec/custom_matchers/equal_task_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
end
end

context 'when only one task has a file' do
let(:task2) { build(:task, files: [build(:task_file)]) }

it 'fails the comparison' do
expect(task).not_to be_an_equal_task_as task2
end
end

context 'when the tasks are complex' do
let(:task) { build(:task, :with_everything) }
let(:task2) { build(:task, :with_everything) }
Expand All @@ -40,6 +48,22 @@
expect(task).to be_an_equal_task_as task2
end

context 'with a tiny change in the meta_data' do
before { task.meta_data[:namespace][:meta] = 'doto' }

it 'fails' do
expect(task).not_to be_an_equal_task_as task2
end
end

context 'with a tiny change in the nested meta_data' do
before { task.meta_data[:namespace][:nested][:test] = 'doto' }

it 'fails' do
expect(task).not_to be_an_equal_task_as task2
end
end

context 'with a tiny change in a file' do
before { task.files.first.content += 'a' }

Expand All @@ -63,6 +87,14 @@
expect(task).not_to be_an_equal_task_as task2
end
end

context 'with a tiny change in a test meta_data' do
before { task.tests.first.meta_data[:test_meta] = 'diti' }

it 'fails' do
expect(task).not_to be_an_equal_task_as task2
end
end
end

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

context 'when both tasks have two equal exercises, but are still different' do
context 'when both tasks have two equal files, but are still different' do
let(:files_3_id) { 2 }
let(:files2_3_id) { 1 }

Expand Down
2 changes: 2 additions & 0 deletions spec/factories/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
trait(:with_embedded_bin_file) { files { build_list(:task_file, 1, :populated, :small_content, :binary) } }
trait(:with_attached_txt_file) { files { build_list(:task_file, 1, :populated, :large_content, :text) } }
trait(:with_attached_bin_file) { files { build_list(:task_file, 1, :populated, :large_content, :binary) } }
trait(:with_meta_data) { meta_data { {namespace: {meta: 'data', nested: {test: {abc: '123'}, foo: 'bar'}}} } }

trait(:with_model_solution) { model_solutions { build_list(:model_solution, 1, :populated) } }
trait(:with_test) { tests { build_list(:test, 1, :populated) } }
Expand All @@ -26,6 +27,7 @@
with_multiple_embedded_txt_files
with_model_solution
with_test_with_meta_data
with_meta_data
end

trait(:invalid) { files { build_list(:task_file, 1, :invalid) } }
Expand Down
2 changes: 1 addition & 1 deletion spec/factories/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
end

trait(:with_meta_data) do
meta_data { [{namespace: 'test', key: 'meta', value: 'data'}] }
meta_data { {namespace: {test_meta: 'data', test: 'test_data'}} }
end

trait(:with_multiple_files) do
Expand Down
48 changes: 42 additions & 6 deletions spec/proforma/exporter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,37 @@
it_behaves_like 'populated task node'
end

context 'when task has meta_data' do
let(:custom_namespaces) { [{prefix: 'namespace', uri: 'custom_namespace.org'}] }
let(:task) do
build(:task, :populated, meta_data: {namespace: {meta: 'data', nested: {test: {abc: '123'}, foo: 'bar'}}})
end
let(:meta_data_node) { doc.xpath('/xmlns:task/xmlns:meta-data') }

it_behaves_like 'task node'
it_behaves_like 'populated task node'

it 'adds meta-data node task node' do
expect(meta_data_node).to have(1).items
end

it 'adds two children nodes to meta-data node' do
expect(meta_data_node.children).to have(2).items
end

it 'adds meta node with correct namespace to meta-data node' do
expect(meta_data_node.xpath('namespace:meta').text).to eql 'data'
end

it 'adds nested test node with correct namespace to meta-data node' do
expect(meta_data_node.xpath('namespace:nested/namespace:foo').text).to eql 'bar'
end

it 'adds multiple times nested node with correct namespace to meta-data node' do
expect(meta_data_node.xpath('namespace:nested/namespace:test/namespace:abc').text).to eql '123'
end
end

context 'when a populated task with embedded text file is supplied' do
let(:task) { build(:task, :populated, :with_embedded_txt_file) }
let(:file) { task.all_files.filter { |file| file.id != 'ms-placeholder-file' }.first }
Expand Down Expand Up @@ -280,9 +311,10 @@
end

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

Expand All @@ -296,12 +328,16 @@
expect(meta_data_node.children).to have(2).items
end

it 'adds test node with correct namespace to test-meta-data node' do
expect(meta_data_node.xpath('test:test').text).to eql 'data'
it 'adds meta node with correct namespace to test-meta-data node' do
expect(meta_data_node.xpath('namespace:meta').text).to eql 'data'
end

it 'adds meta node with correct namespace to test-meta-data node' do
expect(meta_data_node.xpath('test:meta').text).to eql 'data'
it 'adds nested node with correct namespace to test-meta-data node' do
expect(meta_data_node.xpath('namespace:nested/namespace:foo').text).to eql 'bar'
end

it 'adds multiple times nested node with correct namespace to test-meta-data node' do
expect(meta_data_node.xpath('namespace:nested/namespace:test/namespace:abc').text).to eql '123'
end
end

Expand Down
Loading

0 comments on commit ff25d49

Please sign in to comment.