iqvoc 4.14.5 → 4.15.0

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.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Gemfile +9 -16
  4. data/Gemfile.lock +313 -257
  5. data/README.md +1 -1
  6. data/app/aides/entity_logger.rb +27 -0
  7. data/app/aides/maker.rb +1 -1
  8. data/app/aides/origin.rb +1 -1
  9. data/app/aides/{rdfapi.rb → rdf_api.rb} +1 -1
  10. data/app/aides/skos_importer.rb +1 -1
  11. data/app/assets/javascripts/iqvoc/iqvoc.js +4 -3
  12. data/app/assets/javascripts/iqvoc/treeview.js +3 -3
  13. data/app/assets/stylesheets/_framework.scss +1 -1
  14. data/app/controllers/collections/alphabetical_controller.rb +75 -0
  15. data/app/controllers/collections/expired_controller.rb +37 -0
  16. data/app/controllers/collections_controller.rb +22 -28
  17. data/app/controllers/concepts/alphabetical_controller.rb +4 -6
  18. data/app/controllers/concepts/expired_controller.rb +1 -1
  19. data/app/controllers/concepts/hierarchical_controller.rb +18 -15
  20. data/app/controllers/concepts_controller.rb +0 -3
  21. data/app/controllers/concepts_movement_controller.rb +1 -1
  22. data/app/controllers/concerns/controller_extensions.rb +15 -0
  23. data/app/controllers/dashboard_controller.rb +1 -1
  24. data/app/controllers/exports_controller.rb +17 -8
  25. data/app/controllers/rdf_controller.rb +0 -2
  26. data/app/controllers/remote_labels_controller.rb +0 -2
  27. data/app/controllers/search_results_controller.rb +59 -43
  28. data/app/helpers/concepts_helper.rb +2 -2
  29. data/app/helpers/navigation_helper.rb +7 -7
  30. data/app/helpers/rdf_helper.rb +2 -0
  31. data/app/jobs/export_job.rb +4 -7
  32. data/app/models/collection/base.rb +6 -8
  33. data/app/models/collection/member/skos/base.rb +2 -2
  34. data/app/models/collection/skos/base.rb +1 -1
  35. data/app/models/collection/skos/unordered.rb +1 -1
  36. data/app/models/collection/unordered.rb +2 -2
  37. data/app/models/concept/base.rb +2 -10
  38. data/app/models/concept/relation/base.rb +1 -1
  39. data/app/models/concept/relation/skos/base.rb +2 -2
  40. data/app/models/concept/relation/skos/broader/base.rb +2 -2
  41. data/app/models/concept/relation/skos/broader/mono.rb +1 -1
  42. data/app/models/concept/relation/skos/broader/poly.rb +1 -1
  43. data/app/models/concept/relation/skos/narrower/base.rb +1 -1
  44. data/app/models/concept/relation/skos/related.rb +1 -1
  45. data/app/models/concept/skos/base.rb +1 -1
  46. data/app/models/concept/skos/scheme.rb +2 -2
  47. data/app/models/concept/validations.rb +14 -0
  48. data/app/models/concerns/expirable.rb +14 -0
  49. data/app/models/export.rb +21 -9
  50. data/app/models/label/base.rb +19 -1
  51. data/app/models/label/skos/base.rb +1 -1
  52. data/app/models/labeling/base.rb +12 -0
  53. data/app/models/labeling/skos/alt_label.rb +1 -1
  54. data/app/models/labeling/skos/base.rb +4 -4
  55. data/app/models/labeling/skos/hidden_label.rb +1 -1
  56. data/app/models/labeling/skos/pref_label.rb +1 -1
  57. data/app/models/match/skos/base.rb +3 -3
  58. data/app/models/match/skos/broad_match.rb +2 -2
  59. data/app/models/match/skos/close_match.rb +1 -1
  60. data/app/models/match/skos/exact_match.rb +1 -1
  61. data/app/models/match/skos/mapping_relation.rb +1 -1
  62. data/app/models/match/skos/narrow_match.rb +2 -2
  63. data/app/models/match/skos/related_match.rb +1 -1
  64. data/app/models/note/base.rb +2 -2
  65. data/app/models/note/rdfs/see_also.rb +1 -1
  66. data/app/models/note/skos/base.rb +4 -4
  67. data/app/models/note/skos/change_note.rb +2 -2
  68. data/app/models/note/skos/definition.rb +1 -1
  69. data/app/models/note/skos/editorial_note.rb +1 -1
  70. data/app/models/note/skos/example.rb +1 -1
  71. data/app/models/note/skos/history_note.rb +1 -1
  72. data/app/models/note/skos/scope_note.rb +1 -1
  73. data/app/presenters/alphabetical_search_result.rb +2 -2
  74. data/app/services/rdf_sync_service.rb +1 -1
  75. data/app/uploaders/base.rb +4 -3
  76. data/app/view_models/concept_view.rb +1 -1
  77. data/app/views/collections/_data.html.erb +1 -1
  78. data/app/views/collections/_form.html.erb +14 -2
  79. data/app/views/collections/alphabetical/_search_result.html.erb +17 -0
  80. data/app/views/collections/alphabetical/_search_result_remote.html.erb +14 -0
  81. data/app/views/collections/alphabetical/index.html.erb +23 -0
  82. data/app/views/collections/edit.html.erb +1 -1
  83. data/app/views/collections/expired/index.html.erb +23 -0
  84. data/app/views/collections/index.html.erb +1 -1
  85. data/app/views/collections/new.html.erb +1 -1
  86. data/app/views/collections/show_published.html.erb +1 -1
  87. data/app/views/collections/show_unpublished.html.erb +1 -1
  88. data/app/views/collections/sidebars/_plural.html.erb +28 -0
  89. data/app/views/collections/sidebars/_singular.html.erb +22 -0
  90. data/app/views/concepts/_form.html.erb +1 -1
  91. data/app/views/concepts/alphabetical/index.html.erb +1 -1
  92. data/app/views/concepts/expired/index.html.erb +1 -1
  93. data/app/views/concepts/glance.html.erb +1 -1
  94. data/app/views/concepts/scheme/edit.html.erb +1 -1
  95. data/app/views/concepts/sidebars/_plural.html.erb +1 -1
  96. data/app/views/concepts/sidebars/_singular.html.erb +1 -1
  97. data/app/views/dashboard/glance.html.erb +1 -1
  98. data/app/views/exports/index.html.erb +5 -1
  99. data/app/views/exports/show.html.erb +1 -1
  100. data/app/views/partials/collection/_inline_base.html.erb +4 -0
  101. data/app/views/search_results/_sidebar.html.erb +1 -1
  102. data/config/application.rb +7 -2
  103. data/config/ci.rb +20 -0
  104. data/config/database.yml +15 -21
  105. data/config/database.yml.postgresql +13 -20
  106. data/config/engine.rb +1 -0
  107. data/config/environments/development.rb +1 -1
  108. data/config/environments/production.rb +1 -1
  109. data/config/environments/test.rb +1 -1
  110. data/config/initializers/content_security_policy.rb +6 -2
  111. data/config/initializers/filter_parameter_logging.rb +4 -4
  112. data/config/initializers/new_framework_defaults_7_1.rb +280 -0
  113. data/config/initializers/new_framework_defaults_8_0.rb +30 -0
  114. data/config/initializers/new_framework_defaults_8_1.rb +74 -0
  115. data/config/initializers/permissions_policy.rb +11 -9
  116. data/config/initializers/zeitwerk.rb +1 -3
  117. data/config/locales/de.yml +4 -1
  118. data/config/locales/en.yml +4 -1
  119. data/config/locales/pt.yml +2 -1
  120. data/config/puma.rb +34 -35
  121. data/config/routes.rb +3 -1
  122. data/db/migrate/20110510162719_use_mono_hierarchy_instead_of_poly_hierarchy.rb +2 -2
  123. data/db/migrate/20130227145825_fix_collection_type.rb +3 -3
  124. data/db/migrate/20130502151221_fix_collection_member_types.rb +1 -1
  125. data/db/migrate/20250218160045_adapt_zeitwerk_naming_to_iqvoc.rb +25 -0
  126. data/db/migrate/20250326182601_adapt_zeitwerk_skos_naming_to_instance_configuration.rb +11 -0
  127. data/db/schema.rb +100 -1
  128. data/iqvoc.gemspec +4 -4
  129. data/lib/iqvoc/configuration/collection.rb +16 -4
  130. data/lib/iqvoc/configuration/concept.rb +22 -18
  131. data/lib/iqvoc/configuration/core.rb +9 -23
  132. data/lib/iqvoc/configuration/label.rb +1 -1
  133. data/lib/iqvoc/environments/development.rb +62 -57
  134. data/lib/iqvoc/environments/production.rb +70 -67
  135. data/lib/iqvoc/environments/test.rb +44 -38
  136. data/lib/iqvoc/version.rb +1 -1
  137. data/test/controllers/concepts_movement_controller_test.rb +14 -14
  138. data/test/controllers/hierarchy_test.rb +0 -1
  139. data/test/controllers/reverse_match_test.rb +6 -6
  140. data/test/integration/alphabetical_test.rb +5 -5
  141. data/test/integration/browse_concepts_and_labels_test.rb +4 -4
  142. data/test/integration/client_edit_concept_test.rb +1 -1
  143. data/test/integration/collection_browsing_test.rb +2 -2
  144. data/test/integration/collection_circularity_test.rb +10 -10
  145. data/test/integration/collection_test.rb +79 -0
  146. data/test/integration/concept_browsing_test.rb +3 -3
  147. data/test/integration/concept_collection_assignment_test.rb +4 -4
  148. data/test/integration/concept_scheme_browsing_test.rb +7 -7
  149. data/test/integration/edit_collections_test.rb +1 -1
  150. data/test/integration/edit_concepts_test.rb +2 -2
  151. data/test/integration/instance_configuration_browsing_test.rb +1 -1
  152. data/test/integration/note_annotations_test.rb +2 -2
  153. data/test/integration/reverse_match_job_test.rb +9 -9
  154. data/test/integration/search_test.rb +20 -10
  155. data/test/integration/tree_test.rb +5 -5
  156. data/test/integration/untranslated_test.rb +2 -2
  157. data/test/integration_test_helper.rb +7 -0
  158. data/test/models/concept_scheme_test.rb +6 -6
  159. data/test/models/concept_test.rb +38 -38
  160. data/test/models/deep_cloning_test.rb +6 -6
  161. data/test/models/note_test.rb +4 -4
  162. data/test/models/rdf_sync_test.rb +1 -1
  163. data/test/models/rdfapi_test.rb +24 -24
  164. data/test/models/skos_collection_import_test.rb +3 -3
  165. data/test/models/skos_import_test.rb +4 -4
  166. metadata +44 -32
  167. data/app/views/collections/_sidebar.html.erb +0 -20
  168. data/config/environments/heroku.rb +0 -81
  169. data/config/initializers/heroku.rb +0 -24
  170. data/config/secrets.yml +0 -35
  171. /data/{app/helpers → lib}/iqvoc_module_helper.rb +0 -0
  172. /data/{app/models/concerns → lib}/search_extension.rb +0 -0
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # iQvoc
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/iqvoc.png)](http://badge.fury.io/rb/iqvoc)
4
- ![CI](https://github.com/innoq/iqvoc/workflows/CI/badge.svg?branch=master)
4
+ ![CI](https://github.com/innoq/iqvoc/workflows/CI/badge.svg)
5
5
  [![Code Climate](https://codeclimate.com/github/innoq/iqvoc.png)](https://codeclimate.com/github/innoq/iqvoc)
6
6
 
7
7
  iQvoc is a vocabulary management tool that combines easy-to-use human interfaces
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EntityLogger
4
+ def initialize(object)
5
+ @object = object
6
+ end
7
+
8
+ def info(message)
9
+ append_log(message)
10
+ end
11
+
12
+ def warn(message)
13
+ append_log("[WARNING] #{message}")
14
+ end
15
+
16
+ def error(message)
17
+ append_log("[ERROR] #{message}")
18
+ end
19
+
20
+ private
21
+
22
+ def append_log(message)
23
+ @object.output ||= ""
24
+ @object.output += "#{Time.now} - #{message}\n"
25
+ @object.save!(touch: false)
26
+ end
27
+ end
data/app/aides/maker.rb CHANGED
@@ -120,7 +120,7 @@ module Maker
120
120
  }
121
121
  attributes = defaults.merge(attributes)
122
122
 
123
- klass = Iqvoc::XLLabel rescue Iqvoc::Label # FIXME: breaks encapsulation (hard-coded iqvoc_skosxl dependency)
123
+ klass = Iqvoc::Xllabel rescue Iqvoc::Label # FIXME: breaks encapsulation (hard-coded iqvoc_skosxl dependency)
124
124
  label = klass.base_class.create!(attributes)
125
125
 
126
126
  inflectionals.each { |inf|
data/app/aides/origin.rb CHANGED
@@ -25,7 +25,7 @@ class Origin
25
25
  def valid?
26
26
  valid = true
27
27
 
28
- if blank_node = initial_value.match(RDFAPI::BLANK_NODE_REGEXP)
28
+ if blank_node = initial_value.match(RdfApi::BLANK_NODE_REGEXP)
29
29
  # blank node validation, should not contain special chars
30
30
  valid = false if CGI.escape(blank_node[1]) != blank_node[1]
31
31
  else
@@ -14,7 +14,7 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- class RDFAPI
17
+ class RdfApi
18
18
  FIRST_LEVEL_OBJECT_CLASSES = [Iqvoc::Concept.base_class, Iqvoc::Collection.base_class]
19
19
  SECOND_LEVEL_OBJECT_CLASSES = Iqvoc::Concept.labeling_classes.keys +
20
20
  Iqvoc::Concept.note_classes +
@@ -269,7 +269,7 @@ class SkosImporter
269
269
  end
270
270
 
271
271
  def blank_node?(str)
272
- str.to_s =~ RDFAPI::BLANK_NODE_REGEXP
272
+ str.to_s =~ RdfApi::BLANK_NODE_REGEXP
273
273
  end
274
274
 
275
275
  def extract_triple(line)
@@ -84,11 +84,12 @@ jQuery(document).ready(function($) {
84
84
 
85
85
  $('.datepicker').datepicker({
86
86
  autoclose: true,
87
- todayHighlight: true,
88
- todayBtn: 'linked',
89
87
  clearBtn: true,
90
88
  format: "yyyy-mm-dd",
91
- language: locale
89
+ keyboardNavigation: false,
90
+ language: locale,
91
+ todayHighlight: true,
92
+ todayBtn: 'linked',
92
93
  });
93
94
 
94
95
  //$("tr.highlightable").click(function(ev) {
@@ -96,9 +96,9 @@ import 'jqtree/tree.jquery.js';
96
96
  'glance-url': node.glance_url
97
97
  });
98
98
 
99
- var saveButton = $('<button type="button" class="btn btn-primary btn-xs node-btn" data-tree-action="move"><i class="fa fa-save"></i> ' + saveLabel + '</button>');
100
- var copyButton = $('<button type="button" class="btn btn-primary btn-xs node-btn" data-tree-action="copy"><i class="fa fa-copy"></i> ' + copyLabel + '</button>');
101
- var undoButton = $('<button type="button" class="btn btn-primary btn-xs reset-node-btn"><i class="fa fa-undo"></i> ' + undoLabel + '</button>');
99
+ var saveButton = $('<button type="button" class="btn btn-primary btn-sm node-btn" data-tree-action="move"><i class="fa fa-save"></i> ' + saveLabel + '</button>');
100
+ var copyButton = $('<button type="button" class="btn btn-primary btn-sm node-btn" data-tree-action="copy"><i class="fa fa-copy"></i> ' + copyLabel + '</button>');
101
+ var undoButton = $('<button type="button" class="btn btn-primary btn-sm reset-node-btn"><i class="fa fa-undo"></i> ' + undoLabel + '</button>');
102
102
 
103
103
  // add icon only to the first element of the collection.
104
104
  // the second one could be a nodelist for parents nodes.
@@ -1,4 +1,4 @@
1
1
  @import 'bootstrap/scss/bootstrap';
2
2
 
3
3
  @import 'font-awesome/scss/font-awesome';
4
- @import 'bootstrap-datepicker/dist/css/bootstrap-datepicker3';
4
+ @import 'bootstrap-datepicker/dist/css/bootstrap-datepicker3.standalone';
@@ -0,0 +1,75 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2011-2025 innoQ Deutschland GmbH
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ class Collections::AlphabeticalController < CollectionsController
18
+ include DatasetInitialization
19
+
20
+ def index
21
+ authorize! :read, Concept::Base
22
+
23
+ # only initialize dataset if dataset param is set
24
+ # prevent obsolet http request when using matches widget
25
+ datasets = params[:dataset] ? init_datasets : []
26
+
27
+ identify_used_first_letters
28
+
29
+ if dataset = datasets.detect { |dataset| dataset.name == params[:dataset] }
30
+ query = params[:prefix].to_s.downcase
31
+ @search_results = dataset.alphabetical_search(query, I18n.locale) || []
32
+ @search_results = Kaminari.paginate_array(@search_results).page(params[:page])
33
+ else
34
+ # When in single query mode, AR handles ALL includes to be loaded by that
35
+ # one query. We don't want that! So let's do it manually :-)
36
+ includes = Iqvoc::Collection.base_class.default_includes
37
+ if Iqvoc::Collection.note_classes.include?(Note::Skos::Definition)
38
+ includes << Note::Skos::Definition.name.to_relation_name
39
+ end
40
+
41
+ search_results_size = find_labelings.count
42
+ search_results = find_labelings.page(params[:page])
43
+ Iqvoc::Collection.pref_labeling_class.preload(search_results, owner: includes)
44
+
45
+ @search_results = search_results.to_a.map { |pl| AlphabeticalSearchResult.new(pl) }
46
+ @search_results = Kaminari.paginate_array(@search_results, total_count: search_results_size).page(params[:page])
47
+ end
48
+
49
+ respond_to do |format|
50
+ format.html { render :index, layout: with_layout? }
51
+ end
52
+ end
53
+
54
+ protected
55
+
56
+ def identify_used_first_letters
57
+ @letters = Label::Base.where("#{Label::Base.table_name}.language = ?", I18n.locale).joins(:pref_labeled_collections).where("concepts.published_at IS NOT NULL").where("concepts.expired_at IS NULL OR concepts.expired_at >= ?", Time.now).where("concepts.type = ?", Iqvoc::Collection.base_class_name).select("DISTINCT UPPER(SUBSTR(value, 1, 1)) AS letter").order("letter").map(&:letter)
58
+ end
59
+
60
+ def find_labelings
61
+ letter = (@letters.include?('A')) ? 'a' : @letters.first
62
+ query = (params[:prefix] || letter)&.to_s&.downcase
63
+
64
+ Iqvoc::Collection.pref_labeling_class
65
+ .collection_published
66
+ .collection_not_expired
67
+ .label_begins_with(query)
68
+ .by_label_language(I18n.locale)
69
+ .includes(:target)
70
+ .order(Arel.sql("LOWER(#{Label::Base.table_name}.value)"))
71
+ .joins(:owner)
72
+ .where(concepts: { type: Iqvoc::Collection.base_class_name })
73
+ .references(:collections, :labels, :labelings)
74
+ end
75
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright 2011-2025 innoQ Deutschland GmbH
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ class Collections::ExpiredController < Collections::AlphabeticalController
18
+ protected
19
+
20
+ def identify_used_first_letters
21
+ @letters = Label::Base.where("#{Label::Base.table_name}.language = ?", I18n.locale).joins(:pref_labeled_collections).where("concepts.expired_at < ?", Time.now).where("concepts.type = ?", Iqvoc::Collection.base_class_name).select("DISTINCT UPPER(SUBSTR(value, 1, 1)) AS letter").order("letter").map(&:letter)
22
+ end
23
+
24
+ def find_labelings
25
+ query = (params[:prefix] || @letters.first || 'a').to_s.downcase
26
+
27
+ Iqvoc::Collection.pref_labeling_class
28
+ .collection_expired
29
+ .label_begins_with(query)
30
+ .by_label_language(I18n.locale)
31
+ .includes(:target)
32
+ .order(Arel.sql("LOWER(#{Label::Base.table_name}.value)"))
33
+ .joins(:owner)
34
+ .where(concepts: { type: Iqvoc::Collection.base_class_name })
35
+ .references(:collections, :labels, :labelings)
36
+ end
37
+ end
@@ -18,14 +18,18 @@ class CollectionsController < ApplicationController
18
18
  def index
19
19
  authorize! :read, Iqvoc::Collection.base_class
20
20
 
21
+ scope = Iqvoc::Collection.base_class
22
+ .with_pref_labels
23
+ .published
24
+ .not_expired
25
+
21
26
  respond_to do |format|
22
27
  format.html do
23
- @top_collections = Iqvoc::Collection.base_class.with_pref_labels.published
24
28
  @top_collections = if params[:root].present?
25
- @top_collections.by_parent_id(params[:root])
26
- else
27
- @top_collections.tops
28
- end
29
+ scope.by_parent_id(params[:root])
30
+ else
31
+ scope.tops
32
+ end
29
33
 
30
34
  @top_collections.to_a.sort! { |a, b| a.pref_label.to_s <=> b.pref_label.to_s }
31
35
 
@@ -33,30 +37,22 @@ class CollectionsController < ApplicationController
33
37
  end
34
38
  format.json do # For the widget and treeview
35
39
  response = if params[:root].present?
36
- collections = Iqvoc::Collection.base_class
37
- .with_pref_labels
38
- .published
39
- .by_parent_id(params[:root])
40
- .sort_by { |c| c.pref_label.to_s }
40
+ collections = scope.by_parent_id(params[:root])
41
+ .sort_by { |c| c.pref_label.to_s }
41
42
 
42
43
  collections.map do |collection|
43
- res = {
44
+ {
44
45
  id: collection.id,
45
46
  url: collection_path(id: collection, format: :html),
46
47
  name: CGI.escapeHTML(collection.pref_label.to_s),
47
- load_on_demand: collection.subcollections.any?
48
- }
49
- res[:additionalText] = " (#{collection.additional_info})" if collection.additional_info
50
-
51
- res
48
+ load_on_demand: collection.subcollections.any?,
49
+ additionalText: collection.additional_info&.then { |info| " (#{info})" }
50
+ }.compact
52
51
  end
53
52
  else
54
- collections = Iqvoc::Collection.base_class
55
- .with_pref_labels
56
- .published
57
- .merge(Label::Base.by_query_value("#{params[:query]}%"))
58
- .sort_by { |c| c.pref_label.to_s }
59
- .map { |c| collection_widget_data(c) }
53
+ scope.merge(Label::Base.by_query_value("#{params[:query]}%"))
54
+ .sort_by { |c| c.pref_label.to_s }
55
+ .map { |c| collection_widget_data(c) }
60
56
  end
61
57
  render json: response
62
58
  end
@@ -99,9 +95,7 @@ class CollectionsController < ApplicationController
99
95
  def create
100
96
  authorize! :create, Iqvoc::Collection.base_class
101
97
 
102
- @collection = Iqvoc::Collection.base_class.new(concept_params)
103
-
104
- @collection.lock_by_user(current_user.id)
98
+ @collection = Iqvoc::Collection.base_class.new(collection_params)
105
99
 
106
100
  if @collection.save
107
101
  flash[:success] = I18n.t('txt.controllers.collections.save.success')
@@ -130,9 +124,9 @@ class CollectionsController < ApplicationController
130
124
  authorize! :update, @collection
131
125
 
132
126
  # set to_review to false if someone edits a concepts
133
- concept_params["to_review"] = "false"
127
+ collection_params["to_review"] = "false"
134
128
 
135
- if @collection.update(concept_params)
129
+ if @collection.update(collection_params)
136
130
  flash[:success] = I18n.t('txt.controllers.collections.save.success')
137
131
  redirect_to collection_path(@collection, published: 0)
138
132
  else
@@ -156,7 +150,7 @@ class CollectionsController < ApplicationController
156
150
 
157
151
  private
158
152
 
159
- def concept_params
153
+ def collection_params
160
154
  params.require(:concept).permit!
161
155
  end
162
156
 
@@ -14,8 +14,6 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- require 'concerns/dataset_initialization'
18
-
19
17
  class Concepts::AlphabeticalController < ConceptsController
20
18
  include DatasetInitialization
21
19
 
@@ -29,15 +27,15 @@ class Concepts::AlphabeticalController < ConceptsController
29
27
  identify_used_first_letters
30
28
 
31
29
  if dataset = datasets.detect { |dataset| dataset.name == params[:dataset] }
32
- query = params[:prefix].mb_chars.downcase.to_s
30
+ query = params[:prefix].to_s.downcase
33
31
  @search_results = dataset.alphabetical_search(query, I18n.locale) || []
34
32
  @search_results = Kaminari.paginate_array(@search_results).page(params[:page])
35
33
  else
36
34
  # When in single query mode, AR handles ALL includes to be loaded by that
37
35
  # one query. We don't want that! So let's do it manually :-)
38
36
  includes = Iqvoc::Concept.base_class.default_includes
39
- if Iqvoc::Concept.note_classes.include?(Note::SKOS::Definition)
40
- includes << Note::SKOS::Definition.name.to_relation_name
37
+ if Iqvoc::Concept.note_classes.include?(Note::Skos::Definition)
38
+ includes << Note::Skos::Definition.name.to_relation_name
41
39
  end
42
40
 
43
41
  search_results_size = find_labelings.count
@@ -61,7 +59,7 @@ class Concepts::AlphabeticalController < ConceptsController
61
59
 
62
60
  def find_labelings
63
61
  letter = (@letters.include?('A')) ? 'a' : @letters.first
64
- query = (params[:prefix] || letter)&.mb_chars&.downcase.to_s
62
+ query = (params[:prefix] || letter)&.to_s&.downcase
65
63
 
66
64
  Iqvoc::Concept.pref_labeling_class
67
65
  .concept_published
@@ -22,7 +22,7 @@ class Concepts::ExpiredController < Concepts::AlphabeticalController
22
22
  end
23
23
 
24
24
  def find_labelings
25
- query = (params[:prefix] || @letters.first || 'a').mb_chars.downcase.to_s
25
+ query = (params[:prefix] || @letters.first || 'a').to_s.downcase
26
26
 
27
27
  Iqvoc::Concept.pref_labeling_class
28
28
  .concept_expired
@@ -36,21 +36,24 @@ class Concepts::HierarchicalController < ConceptsController
36
36
 
37
37
  # if params[:broader] is given, the action is handling the reversed tree
38
38
  root_id = params[:root]
39
- if root_id && root_id =~ /\d+/
40
- # NB: order matters; see the following `where`
41
- if params[:broader]
42
- scope = scope.includes(:narrower_relations, :broader_relations).references(:relations)
43
- else
44
- scope = scope.includes(:broader_relations, :narrower_relations).references(:relations)
45
- end
46
- @concepts = scope.where(Concept::Relation::Base.arel_table[:target_id].eq(root_id))
47
- else
48
- if params[:broader]
49
- @concepts = scope.broader_tops.includes(:broader_relations).references(:concepts)
50
- else
51
- @concepts = scope.tops.includes(:narrower_relations).references(:concepts)
52
- end
53
- end
39
+ broader = params[:broader].present?
40
+
41
+ @concepts = if root_id && root_id =~ /\d+/
42
+ # NB: order matters; see the following `where`
43
+ if broader
44
+ scope = scope.includes(:narrower_relations, :broader_relations).references(:relations)
45
+ else
46
+ scope = scope.includes(:broader_relations, :narrower_relations).references(:relations)
47
+ end
48
+ scope.where(Concept::Relation::Base.arel_table[:target_id].eq(root_id))
49
+ else
50
+ if broader
51
+ scope.broader_tops.includes(:broader_relations).references(:concepts)
52
+ else
53
+ scope.tops.includes(:narrower_relations).references(:concepts)
54
+ end
55
+ end
56
+ @concepts = @concepts.sort_by { |c| c.pref_label.to_s }
54
57
 
55
58
  respond_to do |format|
56
59
  format.html
@@ -14,9 +14,6 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- require 'concerns/dataset_initialization'
18
- require 'reverse_match_job'
19
-
20
17
  class ConceptsController < ApplicationController
21
18
  include DatasetInitialization
22
19
 
@@ -83,7 +83,7 @@ class ConceptsMovementController < ApplicationController
83
83
  r.target = new_parent
84
84
  end
85
85
 
86
- Concept::Relation::SKOS::Narrower::Base.create! do |r|
86
+ Concept::Relation::Skos::Narrower::Base.create! do |r|
87
87
  r.owner = new_parent
88
88
  r.target = moved_concept
89
89
  end
@@ -16,10 +16,25 @@ module ControllerExtensions
16
16
  rescue_from CanCan::AccessDenied, with: :handle_access_denied
17
17
  rescue_from ActionController::ParameterMissing, with: :handle_bad_request
18
18
  rescue_from ActionController::UnknownFormat, with: :handle_unknown_format
19
+ rescue_from ActionController::BadRequest, with: :handle_bad_request
19
20
  end
20
21
 
21
22
  protected
22
23
 
24
+ def append_info_to_payload(payload)
25
+ super
26
+
27
+ # not set by rails because we rescue exceptions using :handle_server_error
28
+ if @exception.present?
29
+ payload[:exception] = [@exception.class.name, @exception.message]
30
+ payload[:exception_object] = @exception
31
+ end
32
+
33
+ if current_user.present?
34
+ payload[:user_id] = current_user.id
35
+ end
36
+ end
37
+
23
38
  def default_url_options(options = nil)
24
39
  { format: params[:format], lang: I18n.locale }.
25
40
  merge(options || {})
@@ -69,7 +69,7 @@ class DashboardController < ApplicationController
69
69
  object = objects.send(params[:published] == "1" ? 'published' : 'unpublished').first
70
70
 
71
71
  @title = object.to_s
72
- @editorial_notes = object.notes_for_class(Note::SKOS::EditorialNote)
72
+ @editorial_notes = object.notes_for_class(Note::Skos::EditorialNote)
73
73
 
74
74
  @path = send(object.class_path, id: object, published: params[:published])
75
75
 
@@ -26,6 +26,7 @@ class ExportsController < ApplicationController
26
26
 
27
27
  def show
28
28
  @export = Export.find(params[:id])
29
+ flash[:warning] = t('txt.views.export.success') unless @export.finished_at?
29
30
  end
30
31
 
31
32
  def create
@@ -45,17 +46,25 @@ class ExportsController < ApplicationController
45
46
  redirect_to exports_path
46
47
  end
47
48
 
49
+ def destroy
50
+ export = Export.find(params[:id])
51
+ if export.destroy
52
+ flash[:success] = t('txt.views.export.delete.success')
53
+ else
54
+ flash[:error] = t('txt.views.export.delete.error')
55
+ end
56
+ redirect_to exports_path
57
+ end
58
+
48
59
  def download
49
60
  export = Export.find(params[:export_id])
50
- time = export.finished_at.strftime('%Y-%m-%d_%H-%M')
61
+ time = export.finished_at&.strftime('%Y-%m-%d_%H-%M')
51
62
 
52
- begin
53
- Rails.logger.debug("Try to serve export from: #{export.build_filename}")
54
- send_file export.build_filename, filename: "export-#{time}.#{export.file_type}"
55
- rescue ::ActionController::MissingFile => e
56
- flash[:error] = t('txt.views.export.missing_file')
57
- redirect_to exports_path
58
- end
63
+ Rails.logger.debug("Try to serve export from: #{export.build_filename}")
64
+ send_file export.build_filename, filename: "export-#{time}.#{export.file_type}"
65
+ rescue ::ActionController::MissingFile => e
66
+ flash[:error] = t('txt.views.export.missing_file')
67
+ redirect_to exports_path
59
68
  end
60
69
 
61
70
  private
@@ -15,8 +15,6 @@
15
15
  # limitations under the License.
16
16
 
17
17
  class RdfController < ApplicationController
18
- skip_before_action :set_locale
19
-
20
18
  def show
21
19
  scope = if params[:published] == '0'
22
20
  Iqvoc::Concept.base_class.unpublished
@@ -1,5 +1,3 @@
1
- require 'concerns/dataset_initialization'
2
-
3
1
  class RemoteLabelsController < ApplicationController
4
2
  include DatasetInitialization
5
3
 
@@ -14,8 +14,6 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- require 'concerns/dataset_initialization'
18
-
19
17
  class SearchResultsController < ApplicationController
20
18
  include DatasetInitialization
21
19
 
@@ -92,55 +90,73 @@ class SearchResultsController < ApplicationController
92
90
  @remote_result_collections = []
93
91
 
94
92
  if params[:query]
95
- # Deal with language parameter patterns
96
- languages = []
97
- # Either "l[]=de&l[]=en" as well as "l=de,en" should be possible
98
- if params[:languages].respond_to?(:each) && params[:languages].include?('none')
99
- # Special treatment for the "nil language"
100
- languages << nil
101
- elsif params[:languages].respond_to?(:split)
102
- languages = params[:languages].split(',')
103
- end
93
+ if params[:query].blank? and request.format.to_sym.in? [:ttl, :nt, :rdf]
94
+ # support blank searches via web ui, but not for api requests
95
+ respond_to do |format|
96
+ format.any(:ttl, :rdf, :nt) do
97
+ head :bad_request
98
+ end
99
+ end
100
+ else
101
+ # Deal with language parameter patterns
102
+ languages = []
103
+ # Either "l[]=de&l[]=en" as well as "l=de,en" should be possible
104
+ if params[:languages].respond_to?(:each) && params[:languages].include?('none')
105
+ # Special treatment for the "nil language"
106
+ languages << nil
107
+ elsif params[:languages].respond_to?(:split)
108
+ languages = params[:languages].split(',')
109
+ end
104
110
 
105
- # Ensure a valid class was selected
106
- unless klass = Iqvoc.searchable_class_names.detect { |key, value| value == params[:type] }.try(:first)
107
- raise "'#{params[:type]}' is not a searchable class! Must be one of " + Iqvoc.searchable_class_names.keys.join(', ')
108
- end
109
- klass = klass.constantize
111
+ # Ensure a valid class was selected
112
+ unless klass = Iqvoc.searchable_class_names.detect { |key, value| value == params[:type] }.try(:first)
113
+ raise "'#{params[:type]}' is not a searchable class! Must be one of " + Iqvoc.searchable_class_names.keys.join(', ')
114
+ end
115
+ klass = klass.constantize
110
116
 
111
- @results = klass.single_query(params.merge({ languages: languages.flatten }))
112
- .filter { |search_result| result_allowed?(search_result) }
117
+ @results = klass.single_query(params.merge({ languages: languages.flatten }))
118
+ .filter { |search_result| result_allowed?(search_result) }
113
119
 
114
- if params[:limit] && Iqvoc.unlimited_search_results
115
- @results = @results.per(params[:limit].to_i)
116
- end
120
+ if params[:limit] && Iqvoc.unlimited_search_results
121
+ @results = @results.per(params[:limit].to_i)
122
+ end
117
123
 
118
- if params[:datasets] && datasets = @datasets.select { |a| params[:datasets].include?(a.name) }
119
- @results = SearchResultCollection.new(@results)
120
- datasets.each do |dataset|
121
- results = dataset.search(params)
122
- if results
123
- @results = @results + results
124
- else
125
- flash.now[:error] ||= []
126
- flash.now[:error] << t('txt.controllers.search_results.remote_source_error', source: dataset)
124
+ if params[:datasets] && datasets = @datasets.select { |a| params[:datasets].include?(a.name) }
125
+ @results = SearchResultCollection.new(@results)
126
+ datasets.each do |dataset|
127
+ results = dataset.search(params)
128
+ if results
129
+ @results = @results + results
130
+ else
131
+ flash.now[:error] ||= []
132
+ flash.now[:error] << t('txt.controllers.search_results.remote_source_error', source: dataset)
133
+ end
127
134
  end
135
+ @results = @results.sort { |x, y| x.to_s <=> y.to_s }
128
136
  end
129
- @results = @results.sort { |x, y| x.to_s <=> y.to_s }
130
- end
131
-
132
- @results = Kaminari.paginate_array(@results)
133
- @results = @results.page(params[:page])
134
137
 
138
+ @results = Kaminari.paginate_array(@results)
139
+ @results = @results.page(params[:page])
140
+
141
+ respond_to do |format|
142
+ format.html {
143
+ if request.headers['Accept'] == 'text/html; fragment=true'
144
+ render template: 'search_results/_result_list', layout: false
145
+ else
146
+ render :index, layout: with_layout?
147
+ end
148
+ }
149
+ format.any(:ttl, :rdf, :nt)
150
+ end
151
+ end
152
+ else
135
153
  respond_to do |format|
136
- format.html {
137
- if request.headers['Accept'] == 'text/html; fragment=true'
138
- render template: 'search_results/_result_list', layout: false
139
- else
140
- render :index, layout: with_layout?
141
- end
142
- }
143
- format.any(:ttl, :rdf, :nt)
154
+ format.html do
155
+ render :index
156
+ end
157
+ format.any(:ttl, :rdf, :nt) do
158
+ head :bad_request
159
+ end
144
160
  end
145
161
  end
146
162
  end