iqvoc 4.15.0 → 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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/Gemfile +10 -7
  4. data/app/aides/skos_exporter.rb +71 -43
  5. data/app/assets/builds/.keep +0 -0
  6. data/app/assets/images/.gitkeep +0 -0
  7. data/app/assets/stylesheets/_fontawesome.scss +4 -0
  8. data/app/assets/stylesheets/_framework.scss +1 -1
  9. data/app/assets/stylesheets/application.scss +1 -0
  10. data/app/assets/stylesheets/iqvoc/_bootstrap_overrides.scss +30 -1
  11. data/app/assets/stylesheets/iqvoc/components/_components.scss +2 -2
  12. data/app/assets/stylesheets/iqvoc/components/_onebox.scss +1 -1
  13. data/app/assets/stylesheets/iqvoc/components/_simplete.scss +5 -0
  14. data/app/assets/stylesheets/iqvoc/components/_treeview.scss +4 -4
  15. data/app/assets/stylesheets/iqvoc/hacks/_hacks.scss +1 -12
  16. data/app/assets/stylesheets/iqvoc/settings/_fonts.scss +33 -33
  17. data/app/assets/stylesheets/iqvoc/settings/_mixins.scss +0 -14
  18. data/app/controllers/concerns/controller_extensions.rb +11 -0
  19. data/app/controllers/exports_controller.rb +2 -4
  20. data/app/controllers/remote_labels_controller.rb +14 -2
  21. data/app/controllers/search_results_controller.rb +22 -11
  22. data/app/exceptions/service_unavailable_error.rb +8 -0
  23. data/app/helpers/application_helper.rb +2 -2
  24. data/app/helpers/link_helper.rb +4 -0
  25. data/app/helpers/navigation_helper.rb +1 -1
  26. data/app/helpers/rdf_helper.rb +9 -2
  27. data/app/javascript/application.js +4 -0
  28. data/app/{assets/javascripts → javascript}/iqvoc/concept_mapping_manager.js +2 -2
  29. data/app/{assets/javascripts → javascript}/iqvoc/entityselect.js +4 -6
  30. data/app/{assets/javascripts → javascript}/iqvoc/federated_concept_mapper.js +3 -4
  31. data/app/{assets/javascripts → javascript/iqvoc}/framework.js +0 -1
  32. data/app/javascript/iqvoc/globals.js +13 -0
  33. data/app/javascript/iqvoc/hover_menues.js +25 -0
  34. data/app/{assets/javascripts → javascript}/iqvoc/iqvoc.js +6 -2
  35. data/app/{assets/javascripts → javascript}/iqvoc/treeview.js +11 -10
  36. data/app/javascript/manifest.js +12 -0
  37. data/app/jobs/export_job.rb +1 -1
  38. data/app/jobs/reverse_match_job.rb +3 -3
  39. data/app/models/concept/base.rb +4 -3
  40. data/app/models/concerns/versioning.rb +6 -0
  41. data/app/models/dataset/adaptors/iqvoc/label_adaptor.rb +4 -2
  42. data/app/models/dataset/iqvoc_dataset.rb +6 -1
  43. data/app/models/export.rb +8 -1
  44. data/app/models/note/base.rb +6 -0
  45. data/app/models/note/skos/base.rb +1 -1
  46. data/app/models/note/skos/change_note.rb +1 -1
  47. data/app/views/collections/alphabetical/_search_result.html.erb +1 -1
  48. data/app/views/concepts/_form.html.erb +2 -4
  49. data/app/views/concepts/alphabetical/_search_result.html.erb +1 -1
  50. data/app/views/concepts/glance.html.erb +3 -6
  51. data/app/views/dashboard/_table.html.erb +2 -2
  52. data/app/views/dashboard/glance.html.erb +3 -6
  53. data/app/views/exports/index.html.erb +14 -5
  54. data/app/views/frontpage/index.html.erb +3 -3
  55. data/app/views/hierarchy/show.iqrdf +1 -1
  56. data/app/views/imports/index.html.erb +3 -4
  57. data/app/views/instance_configuration/index.html.erb +1 -1
  58. data/app/views/layouts/_controls.html.erb +2 -2
  59. data/app/views/layouts/_navigation.html.erb +1 -1
  60. data/app/views/layouts/_sections.html.erb +1 -1
  61. data/app/views/layouts/_user_navigation.html.erb +2 -2
  62. data/app/views/layouts/application.html.erb +2 -4
  63. data/app/views/pages/components.html.erb +5 -5
  64. data/app/views/partials/labeling/skos/_edit_base.html.erb +1 -3
  65. data/app/views/partials/labeling/skos/_search_result.html.erb +1 -1
  66. data/app/views/partials/match/_edit_base.html.erb +1 -1
  67. data/app/views/partials/notation/_edit_base.html.erb +1 -1
  68. data/app/views/partials/note/_edit_base.html.erb +1 -1
  69. data/app/views/partials/note/_search_result.html.erb +1 -1
  70. data/app/views/partials/note/skos/change_note/_search_result.html.erb +1 -1
  71. data/app/views/partials/note/skos/definition/_search_result.html.erb +1 -1
  72. data/app/views/search_results/_form.html.erb +4 -4
  73. data/app/views/search_results/sections/_change_note.html.erb +5 -11
  74. data/app/views/search_results/sections/_collection.html.erb +1 -1
  75. data/app/views/search_results/sections/_datasets.html.erb +1 -1
  76. data/app/views/search_results/sections/_klass.html.erb +1 -1
  77. data/app/views/search_results/sections/_languages.html.erb +4 -3
  78. data/app/views/search_results/sections/_mode.html.erb +1 -1
  79. data/app/views/search_results/sections/_options.html.erb +1 -1
  80. data/app/views/search_results/sections/_terms.html.erb +4 -3
  81. data/app/views/search_results/sections/_type.html.erb +3 -5
  82. data/app/views/users/_form.html.erb +1 -3
  83. data/config/application.rb +2 -3
  84. data/config/engine.rb +5 -2
  85. data/config/initializers/assets.rb +8 -0
  86. data/config/initializers/iqvoc.rb +1 -0
  87. data/config/initializers/linked_data.rb +13 -1
  88. data/config/locales/activerecord.de.yml +1 -0
  89. data/config/locales/activerecord.en.yml +1 -0
  90. data/config/locales/de.yml +3 -0
  91. data/config/locales/en.yml +3 -0
  92. data/db/migrate/20260723172701_add_zip_to_exports.rb +5 -0
  93. data/db/migrate/20260917120000_add_index_on_labelings_target_id.rb +5 -0
  94. data/iqvoc.gemspec +12 -9
  95. data/lib/generators/app/template.rb +1 -0
  96. data/lib/iqvoc/configuration/additional_associations.rb +54 -0
  97. data/lib/iqvoc/configuration/concept.rb +15 -3
  98. data/lib/iqvoc/configuration/core.rb +1 -0
  99. data/lib/iqvoc/configuration/instance_configuration.rb +2 -0
  100. data/lib/iqvoc/environments/production.rb +1 -1
  101. data/lib/iqvoc/environments/test.rb +1 -1
  102. data/lib/iqvoc/version.rb +1 -1
  103. data/lib/iqvoc.rb +1 -0
  104. data/lib/tasks/.gitkeep +0 -0
  105. data/lib/tasks/exporter.rake +14 -4
  106. data/test/controllers/remote_labels_controller_test.rb +90 -0
  107. data/test/integration/reverse_match_job_test.rb +1 -1
  108. data/test/integration_test_helper.rb +2 -2
  109. data/test/models/additional_associations_test.rb +67 -0
  110. data/test/models/skos_export_link_test.rb +93 -0
  111. data/test/models/skos_export_test.rb +71 -0
  112. data/test/system/.keep +0 -0
  113. metadata +105 -42
  114. data/Gemfile.lock +0 -607
  115. data/app/assets/javascripts/iqvoc/hover_menues.js +0 -14
  116. data/app/assets/javascripts/manifest.js +0 -2
  117. data/config/database.yml +0 -20
  118. data/db/schema.rb +0 -305
  119. data/test/models/hygiene_test.rb +0 -35
  120. /data/app/assets/stylesheets/{manifest.scss → _manifest.scss} +0 -0
  121. /data/app/{assets/javascripts → javascript}/iqvoc/autocomplete.js +0 -0
  122. /data/app/{assets/javascripts → javascript}/iqvoc/create_note.js +0 -0
  123. /data/app/{assets/javascripts → javascript}/iqvoc/debounce.js +0 -0
  124. /data/app/{assets/javascripts → javascript}/iqvoc/entityselect_qualified.js +0 -0
  125. /data/app/{assets/javascripts → javascript}/iqvoc/label_resolver.js +0 -0
  126. /data/app/{assets/javascripts → javascript}/iqvoc/langselect.js +0 -0
  127. /data/app/{assets/javascripts → javascript}/iqvoc/manifest.js +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2740d42eb0dd6bb005e754e8a9da763ba97b94035517d3c9be03c66722a451a1
4
- data.tar.gz: 45e4438aa2325af76c97f51e35a04d05955333bfa6f184b031c73c3535167752
3
+ metadata.gz: 6f67e560f1aac50c13bd28d33a5929ac876f297bda3e023c3474f728c397bfba
4
+ data.tar.gz: '0818c7dfe12e3b7fd16fcf9098871d3fa1d50859c03f5264a4665ec8a8e840c7'
5
5
  SHA512:
6
- metadata.gz: fd84849fc8e6186979cd4a1a9ab657a5ad8a256b86575809b5dce0c0c085ec7f74c218c7b8b3a3fa0ee9ad1980c91590a86fb37b63418e0265c26da4b1c7b72c
7
- data.tar.gz: d4f0907c035dda3e447b448f54ed96cc127d352451ee5c51a1a7ae59877b0632be4f3a47150804ac27b988c5933d08005ccfb0a8572ca567b99d1b449fca73a8
6
+ metadata.gz: c5d7c4b4a4a3c79a9add3e8e350cdb021ff72bdeb620fa31622dca5c1848c963a94f75cce7547df16076de497a0bde490d07db348cec3ba198b8e24740fd1e7d
7
+ data.tar.gz: 990188563110861ce3ad6a80095e772f003502e64bacf81a78c044ba4f7ac4a45648929ac01cc47e37b1372636bc2afbbaabb67069954939477293713fb577f5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
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
+
1
32
  ## [4.15.0]
2
33
 
3
34
  * upgrade to Rails v8.1
data/Gemfile CHANGED
@@ -19,7 +19,7 @@ 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', '~> 8.1.2'
22
+ gem 'rails', '>= 8.1.2', '< 9'
23
23
  gem 'puma', '>= 5.0'
24
24
  gem 'kaminari'
25
25
  gem 'authlogic'
@@ -27,23 +27,27 @@ 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'
31
32
  gem 'csv'
32
33
  gem 'rails_autolink'
33
- gem 'faraday', '1.10.3'
34
- gem 'faraday_middleware'
34
+ gem 'faraday', '~> 2.0'
35
+ gem 'faraday-follow_redirects', '~> 0.5'
35
36
  gem 'nokogiri'
36
37
  gem 'linkeddata'
37
38
  gem 'rdf-vocab'
38
39
  gem "deep_cloneable"
39
- gem "bootstrap_form", "~> 4.0"
40
+ gem "bootstrap_form", "~> 5.0"
40
41
  gem 'apipie-rails'
41
42
  gem 'maruku', require: false # needed for apipie documentation
42
43
  gem 'database_cleaner'
43
44
  gem 'delayed_job_active_record'
45
+ gem 'rubyzip'
44
46
  gem 'carrierwave'
45
47
  gem 'carrierwave-i18n'
46
- gem 'faucet_pipeline_rails'
48
+ gem 'propshaft'
49
+ gem 'cssbundling-rails'
50
+ gem 'jsbundling-rails'
47
51
  gem 'bootsnap', require: false
48
52
 
49
53
  # database adapters
@@ -56,7 +60,6 @@ gem 'rack-mini-profiler'
56
60
 
57
61
  group :development do
58
62
  gem 'web-console', '>= 4.1.0'
59
- gem 'listen', '~> 3.3'
60
63
  end
61
64
 
62
65
  group :development, :test do
@@ -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)
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';
3
+ @import 'fontawesome';
4
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
  }
@@ -56,6 +56,9 @@ module ControllerExtensions
56
56
  @status = current_user ? 403 : 401
57
57
  @user_session = UserSession.new if @status == 401
58
58
  @return_url = request.fullpath
59
+
60
+ return if self.performed?
61
+
59
62
  respond_to do |format|
60
63
  format.html { render template: 'errors/access_denied', status: @status }
61
64
  format.any { head @status }
@@ -66,6 +69,8 @@ module ControllerExtensions
66
69
  @exception = exception
67
70
  SearchResultsController.prepare_basic_variables(self)
68
71
 
72
+ return if self.performed?
73
+
69
74
  respond_to do |format|
70
75
  format.html { render template: 'errors/not_found', status: 404 }
71
76
  format.any { head 404 }
@@ -75,6 +80,8 @@ module ControllerExtensions
75
80
  def handle_bad_request(exception)
76
81
  @exception = exception
77
82
 
83
+ return if self.performed?
84
+
78
85
  respond_to do |format|
79
86
  format.any { head 400 }
80
87
  end
@@ -85,6 +92,8 @@ module ControllerExtensions
85
92
  Rails.logger.error(exception.backtrace.join("\n"))
86
93
  @exception = exception
87
94
 
95
+ return if self.performed?
96
+
88
97
  respond_to do |format|
89
98
  format.html { render template: 'errors/server_error', status: 500 }
90
99
  format.any { head 500 }
@@ -94,6 +103,8 @@ module ControllerExtensions
94
103
  def handle_unknown_format(exception)
95
104
  @exception = exception
96
105
 
106
+ return if self.performed?
107
+
97
108
  respond_to do |format|
98
109
  format.any { head 406 }
99
110
  end
@@ -58,10 +58,8 @@ class ExportsController < ApplicationController
58
58
 
59
59
  def download
60
60
  export = Export.find(params[:export_id])
61
- time = export.finished_at&.strftime('%Y-%m-%d_%H-%M')
62
-
63
61
  Rails.logger.debug("Try to serve export from: #{export.build_filename}")
64
- send_file export.build_filename, filename: "export-#{time}.#{export.file_type}"
62
+ send_file export.build_filename, filename: export.download_filename
65
63
  rescue ::ActionController::MissingFile => e
66
64
  flash[:error] = t('txt.views.export.missing_file')
67
65
  redirect_to exports_path
@@ -70,6 +68,6 @@ class ExportsController < ApplicationController
70
68
  private
71
69
 
72
70
  def export_params
73
- params.require(:export).permit(:file_type, :default_namespace)
71
+ params.require(:export).permit(:file_type, :default_namespace, :zip)
74
72
  end
75
73
  end
@@ -1,6 +1,8 @@
1
1
  class RemoteLabelsController < ApplicationController
2
2
  include DatasetInitialization
3
3
 
4
+ rescue_from ServiceUnavailableError, with: :handle_service_unavailable
5
+
4
6
  def show
5
7
  @datasets = init_datasets
6
8
 
@@ -9,13 +11,13 @@ class RemoteLabelsController < ApplicationController
9
11
  # ensure known dataset
10
12
  @dataset = @datasets.detect { |d| concept_url.to_s.start_with?(d.url.to_s) }
11
13
  unless @dataset
12
- head 422
14
+ head :unprocessable_entity
13
15
  return
14
16
  end
15
17
 
16
18
  label = @dataset.find_label(concept_url)
17
19
  unless label
18
- head 404
20
+ head :not_found
19
21
  return
20
22
  end
21
23
 
@@ -25,4 +27,14 @@ class RemoteLabelsController < ApplicationController
25
27
  end
26
28
  end
27
29
  end
30
+
31
+ private
32
+
33
+ def handle_service_unavailable(exception)
34
+ Rails.logger.error("External dataset unavailable: #{exception.url} - #{exception.message}")
35
+ render json: {
36
+ error: 'External service unavailable',
37
+ dataset_url: exception.url.to_s
38
+ }, status: :service_unavailable
39
+ end
28
40
  end