Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: JSON-LD files in zip format #37

Open
wants to merge 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9362875
feat: JSON-LD files in zip format
nickevansuk Mar 22, 2024
5b71572
Try again
nickevansuk Mar 22, 2024
f43a62c
Fix file name
nickevansuk Mar 22, 2024
27b526b
Fix naming
nickevansuk Mar 22, 2024
fc3a93a
Attempt to add publishing button
nickevansuk Mar 26, 2024
8e1fa22
Fix
nickevansuk Mar 26, 2024
664b2ea
Fixes
nickevansuk Mar 26, 2024
4a8de62
Fix link
nickevansuk Mar 26, 2024
b998528
Fix
nickevansuk Mar 26, 2024
2e3fd0b
Fix CSS
nickevansuk Mar 26, 2024
a915fa4
Template strings
nickevansuk Mar 26, 2024
6bc94ab
Fixes
nickevansuk Mar 26, 2024
3d71b85
Test
nickevansuk Mar 26, 2024
9a3b14e
Fix
nickevansuk Mar 26, 2024
85fe1b7
Better publishing page
nickevansuk Mar 26, 2024
1976044
Improve link
nickevansuk Mar 26, 2024
b5cbd07
Test
nickevansuk Mar 26, 2024
6e9bb95
Make editor generic
nickevansuk Mar 27, 2024
8a697d2
Fix
nickevansuk Mar 27, 2024
f2ff725
Fix
nickevansuk Mar 27, 2024
bdbe5ff
fix trigger
nickevansuk Mar 27, 2024
61afda6
Attempt fix
nickevansuk Mar 28, 2024
371d60b
Fix
nickevansuk Mar 28, 2024
48e71de
Improved variable names
nickevansuk Mar 28, 2024
89313f8
Remove old suggestions Google forms
nickevansuk Mar 28, 2024
0ed7392
Remove VOCAB_JSONLD to simplify
nickevansuk Mar 28, 2024
5ba8b55
Remove VOCAB_RELEASES_URL
nickevansuk Mar 28, 2024
75e893f
Fix branch name
nickevansuk Mar 30, 2024
d3df2ab
Fix build
nickevansuk Mar 30, 2024
fe5189b
Generalise instructions
nickevansuk Mar 30, 2024
da83774
Fix link
nickevansuk Mar 30, 2024
62a88f0
Merge branch 'master' into feature/zip-activity-list
nickevansuk May 14, 2024
271a928
Add timestamp override support
nickevansuk May 15, 2024
732de91
Improvements to publishing page
nickevansuk May 15, 2024
f0b4e69
Minor fix
nickevansuk May 15, 2024
86f9517
Merge branch 'master' into feature/zip-activity-list
nickevansuk May 15, 2024
87f764c
Merge branch 'master' into feature/zip-activity-list
nickevansuk May 15, 2024
3d08f5a
Fix example
nickevansuk May 15, 2024
3241414
Include matches in export
nickevansuk May 15, 2024
3d021d9
Debug add
nickevansuk May 15, 2024
f85d181
Fix to related export
nickevansuk May 15, 2024
1f8c554
Attempted fix
nickevansuk May 15, 2024
f18326d
Better property names
nickevansuk May 15, 2024
357881b
Better SKOS id
nickevansuk May 17, 2024
e314524
Better SKOS id
nickevansuk May 17, 2024
15a5a9e
Better SKOS id
nickevansuk May 17, 2024
18cef96
Add draft vocab warning
nickevansuk May 17, 2024
fbb3666
Rename to VOCAB_SUGGESTIONS_LINK
nickevansuk May 17, 2024
dc980d9
Fix draft ordering
nickevansuk May 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ db/schema.rb
public/export
public/uploads
coverage/*
activity-list/validated_activity_list.jsonld

# Ignore application configuration
/config/application.yml

bash_history.txt
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ gem 'carrierwave'
gem 'autoprefixer-rails', '~> 6.5.1.1'
gem 'daemons'
gem "octokit", "~> 4.0"
gem 'rubyzip', '~> 2.0'

# database adapters
# comment out those you do don't need or use a different Gemfile
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ GEM
request_store (1.5.0)
rack (>= 1.4)
rexml (3.2.5)
rubyzip (2.3.2)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
Expand Down Expand Up @@ -470,6 +471,7 @@ DEPENDENCIES
rails_12factor
rails_autolink
rdf-vocab
rubyzip (~> 2.0)
sass-rails (~> 5.0.0)
simplecov
sqlite3 (~> 1.3.0)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Activity List Editor
# SKOS Vocabulary Editor

## Coding framework: iQvoc

The Activity List Editor is a fork of iQvoc, a vocabulary management tool that combines easy-to-use human interfaces
The OpenActive SKOS Vocabulary Editor is a fork of iQvoc, a vocabulary management tool that combines easy-to-use human interfaces
with Semantic Web interoperability. For more information about iQvoc, including setup and configuration, please see the [iQvoc GitHub repository](https://github.com/innoq/iqvoc).

## Using the Editor
Expand Down
4 changes: 3 additions & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
"VOCAB_HELP_URL": "https://developer.openactive.io/publishing-data/activity-list-references",
"VOCAB_IDENTIFIER": "activity-list",
"VOCAB_PROPERTY": "activity",
"VOCAB_DRAFT": "false",
"VOCAB_EXPORT_RELATED_MATCHES": "facility-types:facilityType;activity-list:activity",
"VOCAB_NAME": "Activity List",
"VOCAB_SUGGESTIONS_FORM": "https://docs.google.com/forms/d/e/1FAIpQLSfaKgMC-dySy8G7Lvv_9Uh-o48Db37B3BwHSHANyPlEpiEmFA/viewform",
"VOCAB_SUGGESTIONS_LINK": "https://docs.google.com/forms/d/e/1FAIpQLSfaKgMC-dySy8G7Lvv_9Uh-o48Db37B3BwHSHANyPlEpiEmFA/viewform",
"VOCAB_WORKFLOW_GH_ACCESS_TOKEN": {
"required": true
},
Expand Down
267 changes: 168 additions & 99 deletions app/controllers/concepts/openactive_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

require 'zip'

#Derived from hierarchy_controller.rb
class Concepts::OpenactiveController < ConceptsController

Expand All @@ -37,114 +39,181 @@ def index
# Iqvoc::Concept.base_class.default_includes + [])

@concepts.to_a.sort_by! {|c| c.pref_label }

respond_to do |format|
format.jsonld do
format.zip do
# Create in-memory zip file
buffer = Zip::OutputStream.write_buffer do |zip|
# Adding <skos-vocabulary-identifier>.jsonld to the ZIP
concepts_json = generate_concepts_json(@concepts) # This method encapsulates the JSON generation logic
zip.put_next_entry("#{ENV['VOCAB_IDENTIFIER']}.jsonld")
zip.write(concepts_json)

# Generate and add collections JSON files
@collections.select { |c| can? :read, c }.each do |collection|
collection_json = generate_collection_json(collection) # This method encapsulates the JSON generation logic
zip.put_next_entry("collections/#{collection.origin[1..-1]}.jsonld")
zip.write(collection_json)
end
end

# Create main unvalidated_activity_list.jsonld file (which is then validated by CI within the activity-list repo)
# Rewind the buffer to allow for reading
buffer.rewind

concepts = @concepts.select { |c| can? :read, c }.map do |c|
url = "https://openactive.io/activity-list##{c.origin[1..-1]}"
# definition = c.notes_for_class(Note::SKOS::Definition).empty? ? "" : c.notes_for_class(Note::SKOS::Definition).first.value
broader = []
c.broader_relations.each do |rel|
broader << "https://openactive.io/activity-list##{rel.target.origin[1..-1]}"
end
narrower = []
c.narrower_relations.each do |rel|
narrower << "https://openactive.io/activity-list##{rel.target.origin[1..-1]}"
end
klass = Iqvoc::Concept.further_relation_classes.first # XXX: arbitrary; bad heuristic?
only_published = params[:published] != "0"
related = []
c.related_concepts_for_relation_class(klass, only_published).each do |related_concept|
related << "https://openactive.io/activity-list##{related_concept.origin[1..-1]}"
end
concept = {
id: url,
identifier: c.origin[1..-1],
type: "Concept",
prefLabel: c.pref_label.to_s
}
concept[:broader] = broader if broader.any?
concept[:narrower] = narrower if narrower.any?
concept[:related] = related if related.any?
c.notes_for_class(Note::SKOS::Definition).each do |n|
concept[:definition] = n.value
end
c.notations.each do |n|
concept[:notation] = n.value
end
# Send the data to the client as a file download
send_data(buffer.read, filename: "#{ENV['VOCAB_IDENTIFIER']}.zip", type: 'application/zip')
end
end
end

concept[:topConceptOf] = "https://openactive.io/activity-list" if c.top_term?
c.alt_labels.each do |l|
concept[:altLabel] ||= []
concept[:altLabel] << l.value
end
concept
def confirm_export
# This action will render a view asking for confirmation
end

def trigger_export
if params[:confirm] == 'yes'
client = Octokit::Client.new(:login => ENV['VOCAB_WORKFLOW_GH_UID'], :password => ENV['VOCAB_WORKFLOW_GH_ACCESS_TOKEN'])
repo = "openactive/#{ENV['VOCAB_IDENTIFIER']}"
workflow_id = 'create-and-merge-pr.yaml'
ref = 'main'

release_timestamp = params[:release_timestamp]

# Check if the chosen release date/time is today
if release_timestamp.present?
if Date.parse(release_timestamp) == Date.today
release_timestamp = ''
else
release_timestamp = DateTime.parse(release_timestamp).strftime("%Y-%m-%d_%H-%M-%S")
end
raw_hash = {
"@context": "https://openactive.io/",
id: "https://openactive.io/activity-list",
title: "OpenActive Activity List",
description: "This document describes the OpenActive standard activity list.",
type: "ConceptScheme",
license: "https://creativecommons.org/licenses/by/4.0/",
concept: concepts
end

options = {
'inputs' => {
'publisher' => current_user.name,
'timestamp' => release_timestamp || ''
}
render json: raw_hash
pretty_json = JSON.pretty_generate(raw_hash)

client = Octokit::Client.new(:login => ENV["GIT_UID"], :password => ENV["GIT_PSW"])
orig_file = client.contents("openactive/activity-list", :path => 'unvalidated_activity_list.jsonld')
sha = orig_file[:sha]
client.create_contents("openactive/activity-list",
"unvalidated_activity_list.jsonld",
"Adding unvalidated content",
pretty_json,
:branch => "master",
:sha => sha
)

# Create collections jsonld files (which are not validated)

collections = @collections.select { |c| can? :read, c }.each do |c|
collectionname = c.origin[1..-1]
filename = "collections/#{collectionname}.jsonld"
url = "https://openactive.io/activity-list/#{filename}"
members = []
c.concepts.each do |rel|
members << "https://openactive.io/activity-list##{rel.origin[1..-1]}"
end
collection = {
"@context": "https://openactive.io/",
"@type": "ConceptCollection",
"@id": url,
prefLabel: c.pref_label.to_s,
inScheme: "https://openactive.io/activity-list",
license: "https://creativecommons.org/licenses/by/4.0/"
}
c.alt_labels.each do |l|
collection[:altLabel] ||= []
collection[:altLabel] << l.value
end
c.notes_for_class(Note::SKOS::Definition).each do |n|
collection[:definition] = n.value
}

client.workflow_dispatch(repo, workflow_id, ref, options)
else
redirect_to confirm_export_path
end

end

private

# Define the methods to generate JSON for concepts and collections
def generate_concepts_json(input_concepts)
concepts = input_concepts.select { |c| can? :read, c }.map do |c|
url = "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}##{c.origin[1..-1]}"
# definition = c.notes_for_class(Note::SKOS::Definition).empty? ? "" : c.notes_for_class(Note::SKOS::Definition).first.value
broader = []
c.broader_relations.each do |rel|
broader << "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}##{rel.target.origin[1..-1]}"
end
narrower = []
c.narrower_relations.each do |rel|
narrower << "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}##{rel.target.origin[1..-1]}"
end
related = []
c.related_concepts_for_relation_class(Concept::Relation::SKOS::Related).each do |related_concept|
related << "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}##{related_concept.origin[1..-1]}"
end

# Parse the VOCAB_EXPORT_RELATED_MATCHES environment variable (e.g. "facility-types:facilityType;activity-list:activity")
export_related_matches = ENV['VOCAB_EXPORT_RELATED_MATCHES']
match_mappings = {}
if export_related_matches
export_related_matches.split(';').each do |mapping|
vocab_identifier, property = mapping.split(':')
match_mappings[vocab_identifier] = property.to_sym
end
end

# Hashmap to collect matches for each property
matches = []
additional_match_properties = Hash.new { |hash, key| hash[key] = [] }

matches = []
c.matches_for_class(Match::SKOS::RelatedMatch).each do |match|
# Transform format from e.g. https://facility-types.openactive.io/_93927309-8e8a-460d-9a55-2a9a4844a7c0 to https://openactive.io/facility-types#93927309-8e8a-460d-9a55-2a9a4844a7c0
if match.value =~ %r{https://([^.]+)\.openactive\.io/_([0-9a-f-]+)$}
match_vocab_identifier = $1
match_id = $2
if match_mappings.key?(match_vocab_identifier)
additional_match_properties[match_mappings[match_vocab_identifier]] << "https://openactive.io/#{match_vocab_identifier}##{match_id}"
end
collection[:member] = members if members.any?
collection_pretty_json = JSON.pretty_generate(collection)

orig_file = client.contents("openactive/activity-list", :path => filename)
sha = orig_file[:sha]
client.create_contents("openactive/activity-list",
filename,
"Updating collection #{collectionname}",
collection_pretty_json,
:branch => "master",
:sha => sha
)
end
end

concept = {
id: url,
identifier: c.origin[1..-1],
type: "Concept",
prefLabel: c.pref_label.to_s
}
concept[:broader] = broader if broader.any?
concept[:narrower] = narrower if narrower.any?
concept[:related] = related if related.any?

# Add additional properties based on matches
additional_match_properties.each do |property, urls|
concept[property] = urls if urls.any?
end

c.notes_for_class(Note::SKOS::Definition).each do |n|
concept[:definition] = n.value
end
c.notations.each do |n|
concept[:notation] = n.value
end

concept[:topConceptOf] = "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}" if c.top_term?
c.alt_labels.each do |l|
concept[:altLabel] ||= []
concept[:altLabel] << l.value
end
concept
end
raw_hash = {
"@context": "https://openactive.io/",
id: "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}",
title: "OpenActive #{ENV['VOCAB_NAME']}",
description: "#{ENV['VOCAB_DESCRIPTION']}",
type: "ConceptScheme",
license: "https://creativecommons.org/licenses/by/4.0/",
concept: concepts
}
JSON.pretty_generate(raw_hash)
end

def generate_collection_json(c)
collectionname = c.origin[1..-1]
filename = "collections/#{collectionname}.jsonld"
url = "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}/#{filename}"
members = []
c.concepts.each do |rel|
members << "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}##{rel.origin[1..-1]}"
end
collection = {
"@context": "https://openactive.io/",
"@type": "ConceptCollection",
"@id": url,
prefLabel: c.pref_label.to_s,
inScheme: "https://openactive.io/#{ENV['VOCAB_IDENTIFIER']}",
license: "https://creativecommons.org/licenses/by/4.0/"
}
c.alt_labels.each do |l|
collection[:altLabel] ||= []
collection[:altLabel] << l.value
end
c.notes_for_class(Note::SKOS::Definition).each do |n|
collection[:definition] = n.value
end
collection[:member] = members if members.any?

JSON.pretty_generate(collection)
end
end
4 changes: 0 additions & 4 deletions app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ def version
def intro
end

def changelog
# authorize! :read, :version
end

def instructions
# authorize! :read, :version
end
Expand Down
19 changes: 19 additions & 0 deletions app/helpers/link_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,23 @@ def link_to_object(object, name, html_options = nil, &block)

link_to name, path, html_options, &block
end

def render_skos_id(url)
if url =~ %r{^https://([^.]+)\.openactive\.io/_([0-9a-f-]+)$}
vocab_identifier = $1
id = $2
"https://openactive.io/#{vocab_identifier}##{id}"
else
url
end
end

def render_property_url(property_name)
if property_name =~ %r{^beta:(.+)$}
property = $1
"https://openactive.io/ns-beta/#" + property
else
property_name
end
end
end
Loading