iqvoc 4.14.5 → 4.17.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 (260) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/Gemfile +17 -21
  4. data/README.md +1 -1
  5. data/app/aides/entity_logger.rb +27 -0
  6. data/app/aides/maker.rb +1 -1
  7. data/app/aides/origin.rb +1 -1
  8. data/app/aides/{rdfapi.rb → rdf_api.rb} +1 -1
  9. data/app/aides/skos_exporter.rb +71 -43
  10. data/app/aides/skos_importer.rb +1 -1
  11. data/app/assets/builds/.keep +0 -0
  12. data/app/assets/images/.gitkeep +0 -0
  13. data/app/assets/stylesheets/_fontawesome.scss +4 -0
  14. data/app/assets/stylesheets/_framework.scss +2 -2
  15. data/app/assets/stylesheets/application.scss +1 -0
  16. data/app/assets/stylesheets/iqvoc/_bootstrap_overrides.scss +30 -1
  17. data/app/assets/stylesheets/iqvoc/components/_components.scss +2 -2
  18. data/app/assets/stylesheets/iqvoc/components/_onebox.scss +1 -1
  19. data/app/assets/stylesheets/iqvoc/components/_simplete.scss +5 -0
  20. data/app/assets/stylesheets/iqvoc/components/_treeview.scss +4 -4
  21. data/app/assets/stylesheets/iqvoc/hacks/_hacks.scss +1 -12
  22. data/app/assets/stylesheets/iqvoc/settings/_fonts.scss +33 -33
  23. data/app/assets/stylesheets/iqvoc/settings/_mixins.scss +0 -14
  24. data/app/controllers/collections/alphabetical_controller.rb +75 -0
  25. data/app/controllers/collections/expired_controller.rb +37 -0
  26. data/app/controllers/collections_controller.rb +22 -28
  27. data/app/controllers/concepts/alphabetical_controller.rb +4 -6
  28. data/app/controllers/concepts/expired_controller.rb +1 -1
  29. data/app/controllers/concepts/hierarchical_controller.rb +18 -15
  30. data/app/controllers/concepts_controller.rb +0 -3
  31. data/app/controllers/concepts_movement_controller.rb +1 -1
  32. data/app/controllers/concerns/controller_extensions.rb +26 -0
  33. data/app/controllers/dashboard_controller.rb +1 -1
  34. data/app/controllers/exports_controller.rb +17 -10
  35. data/app/controllers/rdf_controller.rb +0 -2
  36. data/app/controllers/remote_labels_controller.rb +14 -4
  37. data/app/controllers/search_results_controller.rb +71 -44
  38. data/app/exceptions/service_unavailable_error.rb +8 -0
  39. data/app/helpers/application_helper.rb +2 -2
  40. data/app/helpers/concepts_helper.rb +2 -2
  41. data/app/helpers/link_helper.rb +4 -0
  42. data/app/helpers/navigation_helper.rb +8 -8
  43. data/app/helpers/rdf_helper.rb +11 -2
  44. data/app/javascript/application.js +4 -0
  45. data/app/{assets/javascripts → javascript}/iqvoc/concept_mapping_manager.js +2 -2
  46. data/app/{assets/javascripts → javascript}/iqvoc/entityselect.js +4 -6
  47. data/app/{assets/javascripts → javascript}/iqvoc/federated_concept_mapper.js +3 -4
  48. data/app/{assets/javascripts → javascript/iqvoc}/framework.js +0 -1
  49. data/app/javascript/iqvoc/globals.js +13 -0
  50. data/app/javascript/iqvoc/hover_menues.js +25 -0
  51. data/app/{assets/javascripts → javascript}/iqvoc/iqvoc.js +10 -5
  52. data/app/{assets/javascripts → javascript}/iqvoc/treeview.js +11 -10
  53. data/app/javascript/manifest.js +12 -0
  54. data/app/jobs/export_job.rb +4 -7
  55. data/app/jobs/reverse_match_job.rb +3 -3
  56. data/app/models/collection/base.rb +6 -8
  57. data/app/models/collection/member/skos/base.rb +2 -2
  58. data/app/models/collection/skos/base.rb +1 -1
  59. data/app/models/collection/skos/unordered.rb +1 -1
  60. data/app/models/collection/unordered.rb +2 -2
  61. data/app/models/concept/base.rb +6 -13
  62. data/app/models/concept/relation/base.rb +1 -1
  63. data/app/models/concept/relation/skos/base.rb +2 -2
  64. data/app/models/concept/relation/skos/broader/base.rb +2 -2
  65. data/app/models/concept/relation/skos/broader/mono.rb +1 -1
  66. data/app/models/concept/relation/skos/broader/poly.rb +1 -1
  67. data/app/models/concept/relation/skos/narrower/base.rb +1 -1
  68. data/app/models/concept/relation/skos/related.rb +1 -1
  69. data/app/models/concept/skos/base.rb +1 -1
  70. data/app/models/concept/skos/scheme.rb +2 -2
  71. data/app/models/concept/validations.rb +14 -0
  72. data/app/models/concerns/expirable.rb +14 -0
  73. data/app/models/concerns/versioning.rb +6 -0
  74. data/app/models/dataset/adaptors/iqvoc/label_adaptor.rb +4 -2
  75. data/app/models/dataset/iqvoc_dataset.rb +6 -1
  76. data/app/models/export.rb +28 -9
  77. data/app/models/label/base.rb +19 -1
  78. data/app/models/label/skos/base.rb +1 -1
  79. data/app/models/labeling/base.rb +12 -0
  80. data/app/models/labeling/skos/alt_label.rb +1 -1
  81. data/app/models/labeling/skos/base.rb +4 -4
  82. data/app/models/labeling/skos/hidden_label.rb +1 -1
  83. data/app/models/labeling/skos/pref_label.rb +1 -1
  84. data/app/models/match/skos/base.rb +3 -3
  85. data/app/models/match/skos/broad_match.rb +2 -2
  86. data/app/models/match/skos/close_match.rb +1 -1
  87. data/app/models/match/skos/exact_match.rb +1 -1
  88. data/app/models/match/skos/mapping_relation.rb +1 -1
  89. data/app/models/match/skos/narrow_match.rb +2 -2
  90. data/app/models/match/skos/related_match.rb +1 -1
  91. data/app/models/note/base.rb +8 -2
  92. data/app/models/note/rdfs/see_also.rb +1 -1
  93. data/app/models/note/skos/base.rb +5 -5
  94. data/app/models/note/skos/change_note.rb +3 -3
  95. data/app/models/note/skos/definition.rb +1 -1
  96. data/app/models/note/skos/editorial_note.rb +1 -1
  97. data/app/models/note/skos/example.rb +1 -1
  98. data/app/models/note/skos/history_note.rb +1 -1
  99. data/app/models/note/skos/scope_note.rb +1 -1
  100. data/app/presenters/alphabetical_search_result.rb +2 -2
  101. data/app/services/rdf_sync_service.rb +1 -1
  102. data/app/uploaders/base.rb +4 -3
  103. data/app/view_models/concept_view.rb +1 -1
  104. data/app/views/collections/_data.html.erb +1 -1
  105. data/app/views/collections/_form.html.erb +14 -2
  106. data/app/views/collections/alphabetical/_search_result.html.erb +17 -0
  107. data/app/views/collections/alphabetical/_search_result_remote.html.erb +14 -0
  108. data/app/views/collections/alphabetical/index.html.erb +23 -0
  109. data/app/views/collections/edit.html.erb +1 -1
  110. data/app/views/collections/expired/index.html.erb +23 -0
  111. data/app/views/collections/index.html.erb +1 -1
  112. data/app/views/collections/new.html.erb +1 -1
  113. data/app/views/collections/show_published.html.erb +1 -1
  114. data/app/views/collections/show_unpublished.html.erb +1 -1
  115. data/app/views/collections/sidebars/_plural.html.erb +28 -0
  116. data/app/views/collections/sidebars/_singular.html.erb +22 -0
  117. data/app/views/concepts/_form.html.erb +3 -5
  118. data/app/views/concepts/alphabetical/_search_result.html.erb +1 -1
  119. data/app/views/concepts/alphabetical/index.html.erb +1 -1
  120. data/app/views/concepts/expired/index.html.erb +1 -1
  121. data/app/views/concepts/glance.html.erb +4 -7
  122. data/app/views/concepts/scheme/edit.html.erb +1 -1
  123. data/app/views/concepts/sidebars/_plural.html.erb +1 -1
  124. data/app/views/concepts/sidebars/_singular.html.erb +1 -1
  125. data/app/views/dashboard/_table.html.erb +2 -2
  126. data/app/views/dashboard/glance.html.erb +4 -7
  127. data/app/views/exports/index.html.erb +16 -3
  128. data/app/views/exports/show.html.erb +1 -1
  129. data/app/views/frontpage/index.html.erb +3 -3
  130. data/app/views/hierarchy/show.iqrdf +1 -1
  131. data/app/views/imports/index.html.erb +3 -4
  132. data/app/views/instance_configuration/index.html.erb +1 -1
  133. data/app/views/layouts/_controls.html.erb +2 -2
  134. data/app/views/layouts/_navigation.html.erb +1 -1
  135. data/app/views/layouts/_sections.html.erb +1 -1
  136. data/app/views/layouts/_user_navigation.html.erb +2 -2
  137. data/app/views/layouts/application.html.erb +2 -4
  138. data/app/views/pages/components.html.erb +5 -5
  139. data/app/views/partials/collection/_inline_base.html.erb +4 -0
  140. data/app/views/partials/labeling/skos/_edit_base.html.erb +1 -3
  141. data/app/views/partials/labeling/skos/_search_result.html.erb +1 -1
  142. data/app/views/partials/match/_edit_base.html.erb +1 -1
  143. data/app/views/partials/notation/_edit_base.html.erb +1 -1
  144. data/app/views/partials/note/_edit_base.html.erb +1 -1
  145. data/app/views/partials/note/_search_result.html.erb +1 -1
  146. data/app/views/partials/note/skos/change_note/_search_result.html.erb +1 -1
  147. data/app/views/partials/note/skos/definition/_search_result.html.erb +1 -1
  148. data/app/views/search_results/_form.html.erb +4 -4
  149. data/app/views/search_results/_sidebar.html.erb +1 -1
  150. data/app/views/search_results/sections/_change_note.html.erb +5 -11
  151. data/app/views/search_results/sections/_collection.html.erb +1 -1
  152. data/app/views/search_results/sections/_datasets.html.erb +1 -1
  153. data/app/views/search_results/sections/_klass.html.erb +1 -1
  154. data/app/views/search_results/sections/_languages.html.erb +4 -3
  155. data/app/views/search_results/sections/_mode.html.erb +1 -1
  156. data/app/views/search_results/sections/_options.html.erb +1 -1
  157. data/app/views/search_results/sections/_terms.html.erb +4 -3
  158. data/app/views/search_results/sections/_type.html.erb +3 -5
  159. data/app/views/users/_form.html.erb +1 -3
  160. data/config/application.rb +9 -5
  161. data/config/ci.rb +20 -0
  162. data/config/database.yml.postgresql +13 -20
  163. data/config/engine.rb +6 -2
  164. data/config/environments/development.rb +1 -1
  165. data/config/environments/production.rb +1 -1
  166. data/config/environments/test.rb +1 -1
  167. data/config/initializers/assets.rb +8 -0
  168. data/config/initializers/content_security_policy.rb +6 -2
  169. data/config/initializers/filter_parameter_logging.rb +4 -4
  170. data/config/initializers/iqvoc.rb +1 -0
  171. data/config/initializers/linked_data.rb +13 -1
  172. data/config/initializers/new_framework_defaults_7_1.rb +280 -0
  173. data/config/initializers/new_framework_defaults_8_0.rb +30 -0
  174. data/config/initializers/new_framework_defaults_8_1.rb +74 -0
  175. data/config/initializers/permissions_policy.rb +11 -9
  176. data/config/initializers/zeitwerk.rb +1 -3
  177. data/config/locales/activerecord.de.yml +1 -0
  178. data/config/locales/activerecord.en.yml +1 -0
  179. data/config/locales/de.yml +7 -1
  180. data/config/locales/en.yml +7 -1
  181. data/config/locales/pt.yml +2 -1
  182. data/config/puma.rb +34 -35
  183. data/config/routes.rb +3 -1
  184. data/db/migrate/20110510162719_use_mono_hierarchy_instead_of_poly_hierarchy.rb +2 -2
  185. data/db/migrate/20130227145825_fix_collection_type.rb +3 -3
  186. data/db/migrate/20130502151221_fix_collection_member_types.rb +1 -1
  187. data/db/migrate/20250218160045_adapt_zeitwerk_naming_to_iqvoc.rb +25 -0
  188. data/db/migrate/20250326182601_adapt_zeitwerk_skos_naming_to_instance_configuration.rb +11 -0
  189. data/db/migrate/20260723172701_add_zip_to_exports.rb +5 -0
  190. data/db/migrate/20260917120000_add_index_on_labelings_target_id.rb +5 -0
  191. data/iqvoc.gemspec +14 -11
  192. data/lib/generators/app/template.rb +1 -0
  193. data/lib/iqvoc/configuration/additional_associations.rb +54 -0
  194. data/lib/iqvoc/configuration/collection.rb +16 -4
  195. data/lib/iqvoc/configuration/concept.rb +37 -21
  196. data/lib/iqvoc/configuration/core.rb +10 -23
  197. data/lib/iqvoc/configuration/instance_configuration.rb +2 -0
  198. data/lib/iqvoc/configuration/label.rb +1 -1
  199. data/lib/iqvoc/environments/development.rb +62 -57
  200. data/lib/iqvoc/environments/production.rb +70 -67
  201. data/lib/iqvoc/environments/test.rb +44 -38
  202. data/lib/iqvoc/version.rb +1 -1
  203. data/lib/iqvoc.rb +1 -0
  204. data/lib/tasks/.gitkeep +0 -0
  205. data/lib/tasks/exporter.rake +14 -4
  206. data/test/controllers/concepts_movement_controller_test.rb +14 -14
  207. data/test/controllers/hierarchy_test.rb +0 -1
  208. data/test/controllers/remote_labels_controller_test.rb +90 -0
  209. data/test/controllers/reverse_match_test.rb +6 -6
  210. data/test/integration/alphabetical_test.rb +5 -5
  211. data/test/integration/browse_concepts_and_labels_test.rb +4 -4
  212. data/test/integration/client_edit_concept_test.rb +1 -1
  213. data/test/integration/collection_browsing_test.rb +2 -2
  214. data/test/integration/collection_circularity_test.rb +10 -10
  215. data/test/integration/collection_test.rb +79 -0
  216. data/test/integration/concept_browsing_test.rb +3 -3
  217. data/test/integration/concept_collection_assignment_test.rb +4 -4
  218. data/test/integration/concept_scheme_browsing_test.rb +7 -7
  219. data/test/integration/edit_collections_test.rb +1 -1
  220. data/test/integration/edit_concepts_test.rb +2 -2
  221. data/test/integration/instance_configuration_browsing_test.rb +1 -1
  222. data/test/integration/note_annotations_test.rb +2 -2
  223. data/test/integration/reverse_match_job_test.rb +10 -10
  224. data/test/integration/search_test.rb +20 -10
  225. data/test/integration/tree_test.rb +5 -5
  226. data/test/integration/untranslated_test.rb +2 -2
  227. data/test/integration_test_helper.rb +8 -1
  228. data/test/models/additional_associations_test.rb +67 -0
  229. data/test/models/concept_scheme_test.rb +6 -6
  230. data/test/models/concept_test.rb +38 -38
  231. data/test/models/deep_cloning_test.rb +6 -6
  232. data/test/models/note_test.rb +4 -4
  233. data/test/models/rdf_sync_test.rb +1 -1
  234. data/test/models/rdfapi_test.rb +24 -24
  235. data/test/models/skos_collection_import_test.rb +3 -3
  236. data/test/models/skos_export_link_test.rb +93 -0
  237. data/test/models/skos_export_test.rb +71 -0
  238. data/test/models/skos_import_test.rb +4 -4
  239. data/test/system/.keep +0 -0
  240. metadata +133 -58
  241. data/Gemfile.lock +0 -551
  242. data/app/assets/javascripts/iqvoc/hover_menues.js +0 -14
  243. data/app/assets/javascripts/manifest.js +0 -2
  244. data/app/views/collections/_sidebar.html.erb +0 -20
  245. data/config/database.yml +0 -26
  246. data/config/environments/heroku.rb +0 -81
  247. data/config/initializers/heroku.rb +0 -24
  248. data/config/secrets.yml +0 -35
  249. data/db/schema.rb +0 -206
  250. data/test/models/hygiene_test.rb +0 -35
  251. /data/app/assets/stylesheets/{manifest.scss → _manifest.scss} +0 -0
  252. /data/app/{assets/javascripts → javascript}/iqvoc/autocomplete.js +0 -0
  253. /data/app/{assets/javascripts → javascript}/iqvoc/create_note.js +0 -0
  254. /data/app/{assets/javascripts → javascript}/iqvoc/debounce.js +0 -0
  255. /data/app/{assets/javascripts → javascript}/iqvoc/entityselect_qualified.js +0 -0
  256. /data/app/{assets/javascripts → javascript}/iqvoc/label_resolver.js +0 -0
  257. /data/app/{assets/javascripts → javascript}/iqvoc/langselect.js +0 -0
  258. /data/app/{assets/javascripts → javascript}/iqvoc/manifest.js +0 -0
  259. /data/{app/helpers → lib}/iqvoc_module_helper.rb +0 -0
  260. /data/{app/models/concerns → lib}/search_extension.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4675fc7aeeaad25a7468870e2a22d76170f368db324488b0d42c5f4cb17faf16
4
- data.tar.gz: b12be8abd067d409fc9c9b8d97aba7bde99a15414d939c774302dbdb5cb8fbb8
3
+ metadata.gz: 6f67e560f1aac50c13bd28d33a5929ac876f297bda3e023c3474f728c397bfba
4
+ data.tar.gz: '0818c7dfe12e3b7fd16fcf9098871d3fa1d50859c03f5264a4665ec8a8e840c7'
5
5
  SHA512:
6
- metadata.gz: 0a8c358af72300889daecfe8068a4b128c55f78442e58a66aaf8a88490809381bebcb93be60aa51335933c21740593a0f18191f4b64fba80ddf2c80d283c6fc2
7
- data.tar.gz: ebbf5674a0677ac7faaa3bb239a3110ab703e153406a2d00d872b52cc248a82a9a310776aea565c6d07563048618e8ff89da3667b36afa70bf8b0b90935d7ae8
6
+ metadata.gz: c5d7c4b4a4a3c79a9add3e8e350cdb021ff72bdeb620fa31622dca5c1848c963a94f75cce7547df16076de497a0bde490d07db348cec3ba198b8e24740fd1e7d
7
+ data.tar.gz: 990188563110861ce3ad6a80095e772f003502e64bacf81a78c044ba4f7ac4a45648929ac01cc47e37b1372636bc2afbbaabb67069954939477293713fb577f5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,45 @@
1
+ ## [4.17.0]
2
+
3
+ * upgrade to Bootstrap v5 (and `bootstrap_form` v5)
4
+ * replace faucet-pipeline with Propshaft, `jsbundling-rails` (esbuild) and
5
+ `cssbundling-rails` (Sass); JavaScript moved to `app/javascript`
6
+ * upgrade to Font Awesome v7, replace `jquery-ujs` with `@rails/ujs`
7
+ * `bin/dev` now starts all processes from `Procfile.dev` via foreman
8
+ * add a `draft` relation to the `Versioning` concern
9
+ * add an index on `labelings.target_id` (requires migration)
10
+ * rename SKOS dumps to `<export_file_prefix>-<timestamp>.<type>` and default
11
+ `Iqvoc.export_file_prefix` to `iqvoc-data`
12
+ * drop some inline styles
13
+ * test against Ruby 4 on CI
14
+
15
+ ## [4.16.0]
16
+
17
+ * export SKOS as a ZIP archive, via `rake iqvoc:export ZIP=true` and the web interface
18
+ * add `LINK` to `rake iqvoc:export`, keeping a symlink on the newest dump
19
+ * add `Iqvoc.export_file_prefix` to name dumps after the hosting application
20
+ * speed up the SKOS export on a large thesaurus
21
+ * restore preloading of concept associations, which had silently become a no-op
22
+ * let additional associations declare their `inverse_of`, so that rendering a
23
+ child no longer reloads the record it belongs to
24
+ * look up the concept scheme once per export instead of once per concept
25
+ * sort note annotations in Ruby, so that preloaded annotations are used
26
+ * no longer load ActiveStorage and ActionText, which were unused
27
+ * upgrade to Faraday v2
28
+ * render 503 when a remote label target is unreachable
29
+ * fix rendering the concept scheme
30
+ * fix i18n glitches in exports
31
+
32
+ ## [4.15.0]
33
+
34
+ * upgrade to Rails v8.1
35
+ * drop EOL rubies (3.2 and older)
36
+ * fix some style and markup glitches
37
+ * datepicker optimisations
38
+ * refactor collection management
39
+ * async logging for exports
40
+ * add ability to delete exports
41
+ * adapt zeitwerk naming
42
+
1
43
  ## [4.13.1]
2
44
 
3
45
  * add config option to disable creating reverse matches for concept mappings
data/Gemfile CHANGED
@@ -19,34 +19,37 @@ source 'https://rubygems.org'
19
19
  # TODO: The following dependencies could be included by the "gemspec" command.
20
20
  # There is only one problem: gemspec puts the dependencies automatically to a
21
21
  # group (:development by default). This is not what we need.
22
- gem 'rails', '~> 7.0.8'
23
- gem 'puma', '< 7.0'
22
+ gem 'rails', '>= 8.1.2', '< 9'
23
+ gem 'puma', '>= 5.0'
24
24
  gem 'kaminari'
25
25
  gem 'authlogic'
26
26
  gem 'scrypt'
27
27
  gem 'cancancan'
28
28
  gem 'iq_rdf'
29
29
  gem 'iq_triplestorage'
30
- gem 'json'
30
+ # TODO: Remove once a Rails 8.1.x release with the fix shipped: https://github.com/rails/rails/pull/58601
31
+ gem 'json', '< 3'
32
+ gem 'csv'
31
33
  gem 'rails_autolink'
32
- gem 'faraday'
33
- gem 'faraday_middleware'
34
+ gem 'faraday', '~> 2.0'
35
+ gem 'faraday-follow_redirects', '~> 0.5'
34
36
  gem 'nokogiri'
35
37
  gem 'linkeddata'
36
38
  gem 'rdf-vocab'
37
39
  gem "deep_cloneable"
38
- gem "bootstrap_form", "~> 4.0"
40
+ gem "bootstrap_form", "~> 5.0"
39
41
  gem 'apipie-rails'
40
- gem 'maruku', require: false
41
- gem 'database_cleaner', '~> 1.8.5'
42
+ gem 'maruku', require: false # needed for apipie documentation
43
+ gem 'database_cleaner'
42
44
  gem 'delayed_job_active_record'
45
+ gem 'rubyzip'
43
46
  gem 'carrierwave'
44
47
  gem 'carrierwave-i18n'
45
- gem 'faucet_pipeline_rails'
48
+ gem 'propshaft'
49
+ gem 'cssbundling-rails'
50
+ gem 'jsbundling-rails'
46
51
  gem 'bootsnap', require: false
47
52
 
48
- gem 'net-http' # silence already initialized constant errors for ruby < 3.0: https://github.com/ruby/net-imap/issues/16
49
-
50
53
  # database adapters
51
54
  # comment out those you do don't need or use a different Gemfile
52
55
  #gem 'mysql2', '~> 0.4.0'
@@ -56,24 +59,17 @@ gem 'pg'
56
59
  gem 'rack-mini-profiler'
57
60
 
58
61
  group :development do
59
- gem 'better_errors'
60
62
  gem 'web-console', '>= 4.1.0'
61
- gem 'listen', '~> 3.3'
62
63
  end
63
64
 
64
65
  group :development, :test do
65
- gem 'pry-rails', require: 'pry'
66
+ # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
67
+ gem "debug", platforms: %i[ mri mingw x64_mingw ]
66
68
  end
67
69
 
68
70
  group :test do
69
71
  gem 'capybara'
70
72
  gem 'cuprite'
71
73
  gem 'webmock'
72
- end
73
-
74
- group :production do
75
- end
76
-
77
- group :heroku do
78
- gem 'rails_12factor'
74
+ gem "minitest", "< 6"
79
75
  end
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 +
@@ -1,19 +1,25 @@
1
1
  require 'iq_rdf'
2
2
  require 'uri'
3
3
  require 'fileutils'
4
+ require 'pathname'
5
+ require 'zip'
4
6
 
5
7
  class SkosExporter
6
8
  include RdfHelper # necessary to use render_concept helper
7
9
  include RdfNamespacesHelper
8
10
  include Rails.application.routes.url_helpers
9
11
 
10
- def initialize(file_path, type, default_namespace_url, logger = Rails.logger)
12
+ def initialize(file_path, type, default_namespace_url, logger = Rails.logger, zip: false, entry_name: nil, link_path: nil, batch_size: 500)
11
13
  default_url_options[:port] = URI.parse(default_namespace_url).port
12
14
  default_url_options[:host] = URI.parse(default_namespace_url).to_s.gsub(/\/$/, '')
13
15
 
14
16
  @file_path = file_path
15
17
  @type = type
16
18
  @logger = logger
19
+ @zip = zip
20
+ @entry_name = entry_name
21
+ @link_path = link_path
22
+ @batch_size = batch_size
17
23
  @document = IqRdf::Document.new
18
24
 
19
25
  unless ['ttl', 'nt', 'xml'].include? @type
@@ -48,6 +54,7 @@ class SkosExporter
48
54
 
49
55
  # saving export to disk
50
56
  save_file(@file_path, @type, @document)
57
+ update_link(@link_path, @file_path) if @link_path
51
58
 
52
59
  done = Time.now
53
60
  @logger.info "Export Job finished in #{(done - start).to_i} seconds."
@@ -69,67 +76,88 @@ class SkosExporter
69
76
  def add_collections(document)
70
77
  @logger.info 'Exporting collections...'
71
78
 
72
- offset = 0
73
- while true
74
- collections = Iqvoc::Collection.base_class.published.order('id').limit(100).offset(offset)
75
- limit = collections.size < 100 ? collections.size : 100
76
- break if collections.size == 0
77
-
79
+ total = 0
80
+ Iqvoc::Collection.base_class.published.find_in_batches(batch_size: @batch_size) do |collections|
78
81
  # Todo: Preloading???
79
- collections.each do |collection|
80
- render_collection(document, collection)
81
- end
82
-
83
- @logger.info "Collections #{offset+1}-#{offset+limit} exported."
84
- offset += collections.size # Size is important!
82
+ collections.each { |collection| render_collection(document, collection) }
83
+ @logger.info "Collections #{total + 1}-#{total + collections.size} exported."
84
+ total += collections.size
85
85
  end
86
86
 
87
- @logger.info "Finished exporting collections (#{offset} collections exported)."
87
+ @logger.info "Finished exporting collections (#{total} collections exported)."
88
88
  end
89
89
 
90
90
  def add_concepts(document)
91
91
  @logger.info 'Exporting concepts...'
92
92
 
93
- offset = 0
94
- while true
95
- concepts = Iqvoc::Concept.base_class.published.order('id').limit(100).offset(offset)
96
- limit = concepts.size < 100 ? concepts.size : 100
97
- break if concepts.size == 0
98
-
99
- # When in single query mode, AR handles ALL includes to be loaded by that
100
- # one query. We don't want that! So let's do it manually :-)
101
- Iqvoc::Concept.base_class.preload(concepts,
102
- Iqvoc::Concept.base_class.default_includes + [
93
+ total = 0
94
+ Iqvoc::Concept.base_class.published.find_in_batches(batch_size: @batch_size) do |concepts|
95
+ # rendering touches every association below, so preload them per batch
96
+ # rather than letting each concept load them one by one. Note that this
97
+ # has to be the Preloader: Model.preload builds a relation and would
98
+ # leave the records passed to it untouched.
99
+ associations = Iqvoc::Concept.base_class.default_includes + [
103
100
  :matches,
104
101
  :collection_members,
105
102
  :notations,
106
- { relations: :target, labelings: :target, notes: :annotations }
107
- ])
103
+ { relations: :target, labelings: :target }
104
+ ] + Iqvoc::Concept.additional_association_preloads
105
+ # notes are rendered behind the same condition, see render_concept
106
+ associations << { notes: :annotations } if Iqvoc.rdf_show_change_notes
108
107
 
109
- concepts.each do |concept|
110
- render_concept(document, concept, true)
111
- end
108
+ ActiveRecord::Associations::Preloader.new(records: concepts, associations: associations).call
112
109
 
113
- @logger.info "Concepts #{offset+1}-#{offset+limit} exported."
114
- offset += concepts.size # Size is important!
110
+ concepts.each { |concept| render_concept(document, concept, true) }
111
+ @logger.info "Concepts #{total + 1}-#{total + concepts.size} exported."
112
+ total += concepts.size
115
113
  end
116
114
 
117
- @logger.info "Finished exporting concepts (#{offset} concepts exported)."
115
+ @logger.info "Finished exporting concepts (#{total} concepts exported)."
118
116
  end
119
117
 
120
118
  def save_file(file_path, type, content)
121
- begin
122
- @logger.info "Saving export to '#{@file_path}'"
123
- create_directory(@file_path)
124
- file = File.open(@file_path, 'w')
125
- content = serialize_rdf(content, type)
126
- file.write(content)
127
- rescue IOError => e
128
- # some error occur
129
- # e.g not writable
130
- ensure
131
- file.close unless file == nil
119
+ serialized = serialize_rdf(content, type)
120
+ create_directory(file_path)
121
+
122
+ @logger.info "Saving export to '#{file_path}'"
123
+ if @zip
124
+ save_zipped(file_path, serialized)
125
+ else
126
+ File.write(file_path, serialized)
132
127
  end
128
+ rescue IOError, SystemCallError, Zip::Error => e
129
+ @logger.error "Failed to save export: #{e.message}"
130
+ raise
131
+ end
132
+
133
+ def save_zipped(file_path, serialized)
134
+ entry_name = @entry_name || File.basename(file_path, '.zip')
135
+ Zip::OutputStream.open(file_path) do |zos|
136
+ zos.put_next_entry(entry_name)
137
+ zos.write(serialized)
138
+ end
139
+ end
140
+
141
+ # points the symlink at the file just written, so that a stable URL can serve
142
+ # the most recent export while its file name carries the creation date
143
+ def update_link(link_path, file_path)
144
+ link_dir = File.dirname(File.expand_path(link_path))
145
+ FileUtils.mkdir_p(link_dir)
146
+
147
+ # a relative target survives the export directory being moved
148
+ target = Pathname.new(File.expand_path(file_path)).
149
+ relative_path_from(Pathname.new(link_dir)).to_s
150
+
151
+ # symlink under a temporary name, then rename it into place: rename is
152
+ # atomic, so a concurrent download never sees a missing link
153
+ tmp_path = "#{link_path}.tmp#{Process.pid}"
154
+ File.symlink(target, tmp_path)
155
+ File.rename(tmp_path, link_path)
156
+
157
+ @logger.info "Updated symlink '#{link_path}' -> '#{target}'"
158
+ rescue IOError, SystemCallError => e
159
+ @logger.error "Failed to update symlink: #{e.message}"
160
+ raise
133
161
  end
134
162
 
135
163
  def create_directory(file_path)
@@ -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)
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ @use '@fortawesome/fontawesome-free/scss/variables' with ($font-path: '');
2
+ @use '@fortawesome/fontawesome-free/scss/fontawesome';
3
+ @use '@fortawesome/fontawesome-free/scss/solid';
4
+ @use '@fortawesome/fontawesome-free/scss/regular';
@@ -1,4 +1,4 @@
1
1
  @import 'bootstrap/scss/bootstrap';
2
2
 
3
- @import 'font-awesome/scss/font-awesome';
4
- @import 'bootstrap-datepicker/dist/css/bootstrap-datepicker3';
3
+ @import 'fontawesome';
4
+ @import 'bootstrap-datepicker/dist/css/bootstrap-datepicker3.standalone';
@@ -0,0 +1 @@
1
+ @import 'manifest';
@@ -1,7 +1,36 @@
1
+ // These placeholders live here (rather than in settings/_mixins.scss, which
2
+ // is imported before Bootstrap) so that Sass's @extend emits their merged
3
+ // selector at this position in the cascade. Bootstrap 5's own .btn rule
4
+ // sets font-family explicitly, so if the merged selector compiled in
5
+ // earlier (pre-Bootstrap), Bootstrap's later same-specificity rule would
6
+ // win and silently override it.
7
+ %font-interaction {
8
+ font-family: 'Fira Sans Light SC';
9
+ text-transform: lowercase;
10
+ }
11
+
12
+ %font-bold {
13
+ font-family: 'Fira Sans Bold';
14
+ font-weight: 700;
15
+ }
16
+
17
+ %font-light {
18
+ font-family: 'Fira Sans Light';
19
+ }
20
+
1
21
  body > .container {
2
22
  padding-top: 5rem;
3
23
  }
4
24
 
25
+ // Bootstrap 5 floats <legend> (float: left; width: 100%) to work around a
26
+ // Firefox fieldset/flex bug, clearing it only on the very next sibling
27
+ // (`legend + *`). Fieldsets with more than one element after the legend
28
+ // (like ours) leave the float uncleared, which can squeeze later siblings
29
+ // to zero width. We don't need the Firefox fix here, so disable the float.
30
+ legend {
31
+ float: none;
32
+ }
33
+
5
34
  form#search {
6
35
  .btn-group {
7
36
  margin-top: 10px;
@@ -48,7 +77,7 @@ small {
48
77
  margin-bottom: -0.5em;
49
78
  }
50
79
 
51
- > .close {
80
+ > .btn-close {
52
81
  font-size: $h3-font-size;
53
82
  }
54
83
  }
@@ -1,9 +1,9 @@
1
1
  .indicator {
2
- .fa {
2
+ .fa-solid {
3
3
  visibility: hidden;
4
4
  }
5
5
 
6
- &.active .fa {
6
+ &.active .fa-solid {
7
7
  visibility: visible;
8
8
  }
9
9
  }
@@ -3,7 +3,7 @@
3
3
  justify-content: center;
4
4
  padding-top: 80px;
5
5
  margin-top: 2em;
6
- background: asset-url("iqvoc_logo.svg") top center no-repeat;
6
+ background: url("/iqvoc_logo.svg") top center no-repeat;
7
7
 
8
8
  .logo {
9
9
  margin: 2em 0 1em 0;
@@ -1,3 +1,8 @@
1
+ simplete-form {
2
+ position: relative;
3
+ display: block;
4
+ }
5
+
1
6
  simplete-form #loading,
2
7
  simplete-suggestions #loading {
3
8
  opacity: 0;
@@ -7,7 +7,7 @@ ul.jqtree-tree {
7
7
  margin-left: 0;
8
8
 
9
9
  li {
10
- background: asset-url('treeview-default-line.gif') no-repeat;
10
+ background: url('/treeview-default-line.gif') no-repeat;
11
11
  background-position: 2px 0px;
12
12
  line-height: 1.8em;
13
13
  padding-left: 18px;
@@ -75,7 +75,7 @@ ul.jqtree-tree {
75
75
  border-bottom: 0;
76
76
  color: #333;
77
77
  cursor: pointer;
78
- margin-left: -14px;
78
+ margin-left: -18px;
79
79
  margin-right: .5em;
80
80
  text-decoration: none;
81
81
 
@@ -96,10 +96,10 @@ ul.jqtree-tree {
96
96
  }
97
97
  }
98
98
 
99
- .fa-arrows { cursor: move;}
99
+ .drag { cursor: move;}
100
100
 
101
101
  .jqtree-loading > div.jqtree-element.jqtree_common:after {
102
- content: asset-url('ajax-loader.gif');
102
+ content: url('/ajax-loader.gif');
103
103
  margin-left: 8px;
104
104
  }
105
105
 
@@ -1,12 +1 @@
1
- legend {
2
- margin-bottom: 0;
3
- }
4
-
5
- fieldset > div:first-of-type, ol:first-of-type {
6
- -webkit-margin-top-collapse: separate;
7
- margin-top: 27px;
8
- }
9
-
10
- .fa {
11
- font-family: FontAwesome;
12
- }
1
+ // Hacks - single place of shame
@@ -1,79 +1,79 @@
1
1
  @font-face {
2
2
  font-family: 'Fira Sans Light SC';
3
- src: asset-url('FiraSans-Light-SC.eot');
4
- src: asset-url('FiraSans-Light-SC.eot') format('embedded-opentype'),
5
- asset-url('FiraSans-Light-SC.woff2') format('woff2'),
6
- asset-url('FiraSans-Light-SC.woff') format('woff'),
7
- asset-url('FiraSans-Light-SC.ttf') format('truetype');
3
+ src: url('/FiraSans-Light-SC.eot');
4
+ src: url('/FiraSans-Light-SC.eot') format('embedded-opentype'),
5
+ url('/FiraSans-Light-SC.woff2') format('woff2'),
6
+ url('/FiraSans-Light-SC.woff') format('woff'),
7
+ url('/FiraSans-Light-SC.ttf') format('truetype');
8
8
  font-style: normal;
9
9
  }
10
10
 
11
11
  @font-face {
12
12
  font-family: 'Fira Sans ExtraLight';
13
- src: asset-url('FiraSans-ExtraLight.eot');
14
- src: asset-url('FiraSans-ExtraLight.eot') format('embedded-opentype'),
15
- asset-url('FiraSans-ExtraLight.woff') format('woff'),
16
- asset-url('FiraSans-ExtraLight.ttf') format('truetype');
13
+ src: url('/FiraSans-ExtraLight.eot');
14
+ src: url('/FiraSans-ExtraLight.eot') format('embedded-opentype'),
15
+ url('/FiraSans-ExtraLight.woff') format('woff'),
16
+ url('/FiraSans-ExtraLight.ttf') format('truetype');
17
17
  font-style: normal;
18
18
  }
19
19
 
20
20
  @font-face {
21
21
  font-family: 'Fira Sans Regular';
22
- src: asset-url('FiraSans-Regular.eot');
23
- src: asset-url('FiraSans-Regular.eot') format('embedded-opentype'),
24
- asset-url('FiraSans-Regular.woff') format('woff'),
25
- asset-url('FiraSans-Regular.ttf') format('truetype');
22
+ src: url('/FiraSans-Regular.eot');
23
+ src: url('/FiraSans-Regular.eot') format('embedded-opentype'),
24
+ url('/FiraSans-Regular.woff') format('woff'),
25
+ url('/FiraSans-Regular.ttf') format('truetype');
26
26
  font-weight: 400;
27
27
  font-style: normal;
28
28
  }
29
29
 
30
30
  @font-face {
31
31
  font-family: 'Fira Sans Regular Italic';
32
- src: asset-url('FiraSans-Italic.eot');
33
- src: asset-url('FiraSans-Italic.eot') format('embedded-opentype'),
34
- asset-url('FiraSans-Italic.woff') format('woff'),
35
- asset-url('FiraSans-Italic.ttf') format('truetype');
32
+ src: url('/FiraSans-Italic.eot');
33
+ src: url('/FiraSans-Italic.eot') format('embedded-opentype'),
34
+ url('/FiraSans-Italic.woff') format('woff'),
35
+ url('/FiraSans-Italic.ttf') format('truetype');
36
36
  font-weight: 400;
37
37
  font-style: italic;
38
38
  }
39
39
 
40
40
  @font-face {
41
41
  font-family: 'Fira Sans Bold';
42
- src: asset-url('FiraSans-Bold.eot');
43
- src: asset-url('FiraSans-Bold.eot') format('embedded-opentype'),
44
- asset-url('FiraSans-Bold.woff') format('woff'),
45
- asset-url('FiraSans-Bold.ttf') format('truetype');
42
+ src: url('/FiraSans-Bold.eot');
43
+ src: url('/FiraSans-Bold.eot') format('embedded-opentype'),
44
+ url('/FiraSans-Bold.woff') format('woff'),
45
+ url('/FiraSans-Bold.ttf') format('truetype');
46
46
  font-weight: 700;
47
47
  font-style: normal;
48
48
  }
49
49
 
50
50
  @font-face {
51
51
  font-family: 'Fira Sans Bold Italic';
52
- src: asset-url('FiraSans-BoldItalic.eot');
53
- src: asset-url('FiraSans-BoldItalic.eot') format('embedded-opentype'),
54
- asset-url('FiraSans-BoldItalic.woff') format('woff'),
55
- asset-url('FiraSans-BoldItalic.ttf') format('truetype');
52
+ src: url('/FiraSans-BoldItalic.eot');
53
+ src: url('/FiraSans-BoldItalic.eot') format('embedded-opentype'),
54
+ url('/FiraSans-BoldItalic.woff') format('woff'),
55
+ url('/FiraSans-BoldItalic.ttf') format('truetype');
56
56
  font-weight: 700;
57
57
  font-style: italic;
58
58
  }
59
59
 
60
60
  @font-face {
61
61
  font-family: 'Fira Mono Regular';
62
- src: asset-url('FiraMono-Regular.eot');
63
- src: asset-url('FiraMono-Regular.eot') format('embedded-opentype'),
64
- asset-url('FiraMono-Regular.woff') format('woff'),
65
- asset-url('FiraMono-Regular.ttf') format('truetype');
62
+ src: url('/FiraMono-Regular.eot');
63
+ src: url('/FiraMono-Regular.eot') format('embedded-opentype'),
64
+ url('/FiraMono-Regular.woff') format('woff'),
65
+ url('/FiraMono-Regular.ttf') format('truetype');
66
66
  font-weight: 400;
67
67
  font-style: normal;
68
68
  }
69
69
 
70
70
  @font-face {
71
71
  font-family: 'Fira Sans Light';
72
- src: asset-url('FiraSans-Light.eot');
72
+ src: url('/FiraSans-Light.eot');
73
73
  src: local('Fira Sans Light'),
74
- asset-url('FiraSans-Light.eot') format('embedded-opentype'),
75
- asset-url('FiraSans-Light.woff') format('woff'),
76
- asset-url('FiraSans-Light.ttf') format('truetype');
74
+ url('/FiraSans-Light.eot') format('embedded-opentype'),
75
+ url('/FiraSans-Light.woff') format('woff'),
76
+ url('/FiraSans-Light.ttf') format('truetype');
77
77
  font-weight: 300;
78
78
  font-style: normal;
79
79
  }
@@ -1,17 +1,3 @@
1
- %font-interaction {
2
- font-family: 'Fira Sans Light SC';
3
- text-transform: lowercase;
4
- }
5
-
6
- %font-bold {
7
- font-family: 'Fira Sans Bold';
8
- font-weight: 700;
9
- }
10
-
11
- %font-light {
12
- font-family: 'Fira Sans Light';
13
- }
14
-
15
1
  @mixin transitional($property: all, $duration: .25s) {
16
2
  transition: $property $duration ease-in-out;
17
3
  }