ftr_ruby 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fad967881ddfedcde26bbeb0866164d3648ddb01f79f5deb7ab011fc5d12ffb
4
- data.tar.gz: d8a29cba7c2b4eff908fdf878826c31a54f73c6375a8561d3074906ddd8efa46
3
+ metadata.gz: be4cb2cd61574feb95c0359b6d3c1534ae2bd13090c9cc77c874e7efeda209f2
4
+ data.tar.gz: 1b20953f8acffa00d4ef3b6af4658a5eb05e657728f3c55bdf7230311ecccd9d
5
5
  SHA512:
6
- metadata.gz: 7e28a1b1e8c3a4b66e2a263e8111747bd42c739fb3169f754d07de3e51ca5d3bbf01bcc264e9d10331584525014d1b2cfd41a1f4d231432ddc5cd7f692e6f3bd
7
- data.tar.gz: c0ba5c7ee46a2355008283e977f2284103cd25ccda4feb6d0bf0117b8489d5bcb7d32d6337be30d8530a9622a628ec1962d9a919e7c78f16ecd3690c6a7abb46
6
+ metadata.gz: '0983e92af3a5199f1ba54a9023edb50bacbde94cb46805ae1c913a9110beabdc2c9da6d2a2968677b506f2cde1c7c41171f9143b1689fa3222c4b43f70f6d932'
7
+ data.tar.gz: 3f2853d3956422992ca5d640c92113b34eb0336633724e039f2f70689d79758a32f5e46e7841ee6ee7ba12e14b6f74ebcb9ce34ed08f7687fee1b14ff75e07a5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.11] - 2026-05-26
4
+
5
+ ### Changed
6
+ - `TestInfra#get_tests_metrics` now fetches all test metrics in parallel using Ruby threads, significantly reducing latency when querying 40+ tests
7
+ - Refactored `TestInfra` internals into focused private helpers (`fetch_metric`, `fetch_metric_url`, `load_metric_graph`, `query_metric_graph`)
8
+ - `test_infrastructure.rb` now explicitly requires `jsonpath` rather than relying on transitive loading
9
+
10
+ ### Added
11
+ - RSpec tests for `TestInfra#get_tests_metrics` and `#fetch_metric_url` covering happy path, missing metadata, RDF load failures, and empty input
12
+
3
13
  ## [0.1.0] - 2026-03-28
4
14
 
5
15
  - Initial release
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FtrRuby
4
- VERSION = "0.1.9"
4
+ VERSION = "0.1.11"
5
5
  end
data/lib/openapi.rb CHANGED
@@ -1,10 +1,26 @@
1
1
  module FtrRuby
2
+ # Generates a valid OpenAPI 3.0 YAML document describing a single FAIR test endpoint.
3
+ #
4
+ # The document is built from metadata supplied by the test author (title, description,
5
+ # contact info, etc.) and is served as a machine-readable API specification that client
6
+ # UIs fetch to dynamically build submission forms.
7
+ #
8
+ # Key concern: author-supplied text (especially +description+) may contain arbitrary
9
+ # Markdown, including blank lines and heading markers (##). YAML block scalars require
10
+ # every continuation line to be indented at least as deeply as the first content line;
11
+ # raw multi-line strings break this rule. All free-text fields are therefore passed
12
+ # through +yaml_block_indent+ before interpolation.
2
13
  class OpenAPI
3
14
  attr_accessor :title, :metric, :description, :indicator, :testid,
4
15
  :organization, :org_url, :version, :creator,
5
16
  :responsible_developer, :email, :developer_ORCiD, :protocol,
6
17
  :host, :basePath, :path, :response_description, :schemas, :endpointpath
7
18
 
19
+ # Initialises the OpenAPI document from a metadata hash.
20
+ #
21
+ # @param meta [Hash] keys: :testid, :testname, :testversion, :metric, :description,
22
+ # :indicators, :organization, :org_url, :responsible_developer, :email, :creator,
23
+ # :host, :protocol, :basePath, :response_description, :schemas
8
24
  def initialize(meta:)
9
25
  indics = [meta[:indicators]] unless meta[:indicators].is_a? Array
10
26
  @testid = meta[:testid]
@@ -30,7 +46,14 @@ module FtrRuby
30
46
  # @end_url = "#{protocol}://#{host}#{basePath}/#{endpointpath}/#{testid}" # basepath starts with /
31
47
  end
32
48
 
49
+ # Returns the complete OpenAPI 3.0 YAML document as a String.
50
+ #
51
+ # Free-text fields that may contain Markdown (description, response_description) are
52
+ # pre-processed with +yaml_block_indent+ so that embedded newlines do not escape the
53
+ # YAML block scalar — see that method for details.
33
54
  def get_api
55
+ safe_desc = yaml_block_indent(description, 4)
56
+ safe_resp = yaml_block_indent(response_description, 12)
34
57
  <<~"EOF_EOF"
35
58
 
36
59
  openapi: 3.0.0
@@ -39,7 +62,7 @@ module FtrRuby
39
62
  title: "#{title}"
40
63
  x-tests_metric: "#{metric}"
41
64
  description: >-
42
- #{description}
65
+ #{safe_desc}
43
66
  x-applies_to_principle: "#{indicator}"
44
67
  contact:
45
68
  x-organization: "#{organization}"
@@ -60,7 +83,7 @@ module FtrRuby
60
83
  responses:
61
84
  "200":
62
85
  description: >-
63
- #{response_description}
86
+ #{safe_resp}
64
87
  servers:
65
88
  - url: "#{protocol}://#{host}#{basePath}/#{endpointpath}"
66
89
  components:
@@ -75,5 +98,29 @@ module FtrRuby
75
98
 
76
99
  EOF_EOF
77
100
  end
101
+
102
+ private
103
+
104
+ # Ensures a multi-line string is safe for use inside a YAML block scalar (>- or |).
105
+ #
106
+ # YAML block scalars determine their indentation level from the first content line.
107
+ # Any subsequent line that is indented less than that level terminates the scalar,
108
+ # which causes parse errors when the text contains blank lines followed by
109
+ # unindented content (a common pattern in Markdown).
110
+ #
111
+ # This method leaves the first line untouched (the heredoc template already places
112
+ # it at the correct column) and prepends +spaces+ spaces to every non-empty
113
+ # continuation line so they remain inside the block scalar. Blank lines are left
114
+ # blank intentionally — YAML allows empty lines within a block scalar without
115
+ # requiring indentation.
116
+ #
117
+ # @param text [String] the raw author-supplied text
118
+ # @param spaces [Integer] number of spaces matching the block scalar's indentation
119
+ # in the rendered YAML (4 for +description+, 12 for +response_description+)
120
+ # @return [String]
121
+ def yaml_block_indent(text, spaces)
122
+ indent = " " * spaces
123
+ text.split("\n").map.with_index { |line, i| i.zero? || line.empty? ? line : "#{indent}#{line}" }.join("\n")
124
+ end
78
125
  end
79
126
  end
@@ -1,3 +1,5 @@
1
+ require "jsonpath"
2
+
1
3
  module FtrRuby
2
4
  class TestInfra
3
5
  attr_accessor :test_protocol, :test_host, :basepath
@@ -8,46 +10,57 @@ module FtrRuby
8
10
  @basepath = basepath
9
11
  end
10
12
 
11
- # there is a need to map between a test and its registered Metric in FS. This will return the label for the test
12
- # in principle, we cojuld return a more complex object, but all I need now is the label
13
+ # there is a need to map between a test and its registered Metric in FS.
14
+ # This will return the label for the test
15
+ # in principle, we cojuld return a more complex object,
16
+ # but all I need now is the label
13
17
  def get_tests_metrics(tests:)
18
+ threads = tests.map { |testid| Thread.new(testid) { |tid| fetch_metric(tid) } }
19
+
14
20
  labels = {}
15
21
  landingpages = {}
16
- tests.each do |testid|
17
- warn "getting dcat for #{testid} #{test_protocol}://#{test_host}/#{basepath}/#{testid}"
18
- dcat = RestClient::Request.execute({
19
- method: :get,
20
- url: "#{test_protocol}://#{test_host}/#{basepath}/#{testid}",
21
- headers: { "Accept" => "application/json" }
22
- }).body
23
- parseddcat = JSON.parse(dcat)
24
- # this next line should probably be done with SPARQL
25
- # # TODO TODO TODO
26
- jpath = JsonPath.new('[0]["http://semanticscience.org/resource/SIO_000233"][0]["@id"]') # is implementation of
27
- metricurl = jpath.on(parseddcat).first
28
-
29
- begin
30
- g = RDF::Graph.load(metricurl, format: :turtle)
31
- rescue StandardError => e
32
- warn "DCAT Metric loading failed #{e.inspect}"
33
- g = RDF::Graph.new
34
- end
35
-
36
- title = g.query([nil, RDF::Vocab::DC.title, nil])&.first&.object&.to_s
37
- lp = g.query([nil, RDF::Vocab::DCAT.landingPage, nil])&.first&.object&.to_s
38
-
39
- labels[testid] = if title != ""
40
- title
41
- else
42
- "Metric label not available"
43
- end
44
- landingpages[testid] = if lp != ""
45
- lp
46
- else
47
- ""
48
- end
22
+ threads.each do |t|
23
+ tid, label, lpage = t.value
24
+ labels[tid] = label
25
+ landingpages[tid] = lpage
49
26
  end
50
27
  [labels, landingpages]
51
28
  end
29
+
30
+ private
31
+
32
+ def fetch_metric(testid)
33
+ metricurl = fetch_metric_url(testid)
34
+ g = load_metric_graph(metricurl)
35
+ title, lp = query_metric_graph(g)
36
+ label = title.to_s != "" ? title : "Metric label not available"
37
+ [testid, label, lp.to_s]
38
+ end
39
+
40
+ def fetch_metric_url(testid)
41
+ warn "getting dcat for #{testid} #{test_protocol}://#{test_host}/#{basepath}/#{testid}"
42
+ dcat = RestClient::Request.execute({
43
+ method: :get,
44
+ url: "#{test_protocol}://#{test_host}/#{basepath}/#{testid}",
45
+ headers: { "Accept" => "application/json" }
46
+ }).body
47
+ parseddcat = JSON.parse(dcat)
48
+ # TODO: this should probably be done with SPARQL
49
+ jpath = JsonPath.new('[0]["http://semanticscience.org/resource/SIO_000233"][0]["@id"]') # is implementation of
50
+ jpath.on(parseddcat).first
51
+ end
52
+
53
+ def load_metric_graph(metricurl)
54
+ RDF::Graph.load(metricurl, format: :turtle)
55
+ rescue StandardError => e
56
+ warn "DCAT Metric loading failed #{e.inspect}"
57
+ RDF::Graph.new
58
+ end
59
+
60
+ def query_metric_graph(graph)
61
+ title = graph.query([nil, RDF::Vocab::DC.title, nil])&.first&.object&.to_s
62
+ lp = graph.query([nil, RDF::Vocab::DCAT.landingPage, nil])&.first&.object&.to_s
63
+ [title, lp]
64
+ end
52
65
  end
53
66
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ftr_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - markwilkinson