From f2060fc946ef2281f7faed62482126b5cacaace8 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 12 Jul 2023 09:28:58 +0100 Subject: [PATCH 001/383] updated version on main branch --- config/version.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/version.yml b/config/version.yml index 900e1ec9e4..3bc73edb05 100644 --- a/config/version.yml +++ b/config/version.yml @@ -8,5 +8,5 @@ # patch: sprint-555 major: 1 -minor: 14 -patch: 0-pre +minor: 15 +patch: 0-main From d3658e90aacbf36cc3c209839d900ead72a77197 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:40:05 +0000 Subject: [PATCH 002/383] Bump ipython from 7.33.0 to 8.10.0 Bumps [ipython](https://github.com/ipython/ipython) from 7.33.0 to 8.10.0. - [Release notes](https://github.com/ipython/ipython/releases) - [Commits](https://github.com/ipython/ipython/compare/7.33.0...8.10.0) --- updated-dependencies: - dependency-name: ipython dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 325347f74f..5bd999d84f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ galaxy2cwl==0.1.4 #isatools==0.11.0 gxformat2==0.15.0 nbconvert==7.4.0 -ipython==7.33.0 +ipython==8.10.0 From 655b6c68fce205af20fc6f76a84029d791b7e644 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer <82407142+kdp-cloud@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:32:21 +0200 Subject: [PATCH 003/383] Fix paste from clipboard in single page (#1517) * Add extra column to be preserved in `pasteFromClipboard` function. * Added error handling for the pasteFromClipboard function * Replaced hard-coded URL by help_link from HelpDictionary --- .../single_page/dynamic_table.js.erb | 84 +++++++++++-------- config/help_links.yml | 5 +- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index ef7ac4badd..63e0d31d09 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -192,44 +192,54 @@ const handleSelect = (e) => { highlightTitleCol(this.table); }, pasteFromClipboard: function() { - navigator.clipboard.readText().then((text) => { - const colsCount = this.table.columns().data().length; - const splitter = text.includes("\r\n") ? "\r\n" : "\n"; - const delimeter = "\t"; - const _defaultCols = defaultCols.map((c) => c.defaultValue || ""); - - const cols = this.table.settings()[0].aoColumns; - const sampleLinks = getSampleLinking(cols); - const sampleLinkIndexes = sampleLinks.map((x) => x.idx).sort((a, b) => b - a); - const sampleLinksTitles = sampleLinks.map((x) => x.title); - - const rows = text - .split(splitter) - .filter((x) => x) - .map((r) => { - // Preserve row status and id - let splitted = [rowStatus.new, ""].concat(r.split(delimeter)); - const missingColsCount = colsCount - defaultCols.length - splitted.length; - splitted = _defaultCols.concat(splitted); - - // Preserve empty placeholder for columns of type of 'Registered Sample (multiple)' - sampleLinkIndexes.forEach((x) => splitted.splice(x, 0, [])); - - return missingColsCount < 0 ? - splitted.slice(0, colsCount) : - splitted.concat(Array(missingColsCount).fill("")); - }) - .filter((x) => x); - - this.table.rows.add(rows).draw(); + try { + navigator.clipboard.readText().then((text) => { + const colsCount = this.table.columns().data().length; + const splitter = text.includes("\r\n") ? "\r\n" : "\n"; + const delimeter = "\t"; + const _defaultCols = defaultCols.map((c) => c.defaultValue || ""); + + const cols = this.table.settings()[0].aoColumns; + const sampleLinks = getSampleLinking(cols); + const sampleLinkIndexes = sampleLinks.map((x) => x.idx).sort((a, b) => b - a); + const sampleLinksTitles = sampleLinks.map((x) => x.title); + + const rows = text + .split(splitter) + .filter((x) => x) + .map((r) => { + // Preserve row status, id and uuid + let splitted = [rowStatus.new, "", ""].concat(r.split(delimeter)); + const missingColsCount = colsCount - defaultCols.length - splitted.length; + splitted = _defaultCols.concat(splitted); + + // Preserve empty placeholder for columns of type of 'Registered Sample (multiple)' + sampleLinkIndexes.forEach((x) => splitted.splice(x, 0, [])); + + return missingColsCount < 0 ? + splitted.slice(0, colsCount) : + splitted.concat(Array(missingColsCount).fill("")); + }) + .filter((x) => x); + + this.table.rows.add(rows).draw(); + + if (sampleLinksTitles.length) + alert( + "No value is pasted for the following column(s): \n" + + sampleLinksTitles.map((x) => `"${x}"`).join(", ") + + "\nYou need to manually input them." + ); + }); - if (sampleLinksTitles.length) - alert( - "No value is pasted for the following column(s): \n" + - sampleLinksTitles.map((x) => `"${x}"`).join(", ") + - "\nYou need to manually input them." - ); - }); + } catch (error) { + if (error instanceof TypeError && navigator.userAgent.toLowerCase().includes('firefox')){ + if(confirm('Your Firefox browser might not support clipboard access. By clicking OK, you\'ll be redirected to the official documentation.')){ + window.open("<%= Seek::Help::HelpDictionary.instance.help_link(:clipboard_api_mozilla) %>", "_blank"); + } + } + console.log('An error occurred:', error); + } }, newRow: function() { const colsCount = this.table.columns().data().length; diff --git a/config/help_links.yml b/config/help_links.yml index 87a82daebf..0636a4b163 100644 --- a/config/help_links.yml +++ b/config/help_links.yml @@ -21,7 +21,7 @@ Investigation: https://docs.seek4science.org/help/isa-guide.html Study: https://docs.seek4science.org/help/isa-guide.html Assay: https://docs.seek4science.org/help/isa-guide.html DataFile: https://docs.seek4science.org/help/user-guide/general-attributes.html#data-files -Sop: https://docs.seek4science.org/help/user-guide/general-attributes.html#sops +Sop: https://docs.seek4science.org/help/user-guide/general-attributes.html#sops Publication: https://docs.seek4science.org/help/user-guide/general-attributes.html#publications Sample: https://docs.seek4science.org/help/user-guide/samples.html SampleType: https://docs.seek4science.org/help/user-guide/create-sample-type.html @@ -29,4 +29,5 @@ Organism: https://docs.seek4science.org/help/user-guide/general-attributes.html# Model: https://docs.seek4science.org/help/user-guide/adding-assets.html Document: https://docs.seek4science.org/help/user-guide/adding-assets.html Presentation: https://docs.seek4science.org/help/user-guide/adding-assets.html -Event: https://docs.seek4science.org/help/user-guide/general-attributes.html#events \ No newline at end of file +Event: https://docs.seek4science.org/help/user-guide/general-attributes.html#events +clipboard_api_mozilla: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API From 754384a15af2060fc0e6b7b3a7b25dc1346f6d31 Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Fri, 14 Jul 2023 11:29:09 +0100 Subject: [PATCH 004/383] Refactored options for publishing - more compact and collapsible. --- app/assets/javascripts/checkbox.js | 42 +++++++++ .../_options_for_publishing.html.erb | 87 ++++++++++++------- .../publishing/batch_publishing_test.rb | 2 +- .../publishing/single_publishing_test.rb | 10 +-- 4 files changed, 104 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js index d5751b2fa2..9c060735a9 100644 --- a/app/assets/javascripts/checkbox.js +++ b/app/assets/javascripts/checkbox.js @@ -15,6 +15,15 @@ $j(document).ready(function () { event.preventDefault(); deselectChildren(this,$j(this).data("cb_parent_selector")) }) + $j("a.managed_by_toggle").click(function () { + toggleManagers(this,$j(this).data("managed_by_selector")) + }) + $j("div.isa-tree-toggle-open").click(function () { + isaTreeShow(this,$j(this).data("cb_parent_selector")) + }) + $j("div.isa-tree-toggle-close").click(function () { + isaTreeHide(this,$j(this).data("cb_parent_selector")) + }) }) function selectChildren(select_all_element,cb_parent_selector){ @@ -39,4 +48,37 @@ function checkRepeatedItems(checkbox_element) { for(let element of repeated_elements){ element.checked = check } +} + +function toggleManagers(item,managed_by_selector) { + $j(managed_by_selector).toggle() +} + +function isaTreeShow(item,cb_parent_selector) { + let children_assets = $j('.isa-tree', $j(item).parents(cb_parent_selector)) + children_assets.splice(0, 1); + let keep_closed = [] + for (let asset of children_assets) { + $j(asset).show() + for (let caret of $j('.isa-tree-toggle-close', $j(asset))){ + if (caret.style.display == "none"){ + keep_closed.push(caret) + } + } + } + $j($j('.isa-tree-toggle-open', $j(item).parents(cb_parent_selector))[0]).hide() + $j($j('.isa-tree-toggle-close', $j(item).parents(cb_parent_selector))[0]).show() + + for (let caret of keep_closed){ + isaTreeHide(caret,$j(caret).data("cb_parent_selector")) + } +} +function isaTreeHide(item,cb_parent_selector){ + let children_assets = $j('.isa-tree', $j(item).parents(cb_parent_selector)) + children_assets.splice(0, 1); + for (let asset of children_assets) { + $j(asset).hide() + } + $j($j('.isa-tree-toggle-open', $j(item).parents(cb_parent_selector))[0]).show() + $j($j('.isa-tree-toggle-close', $j(item).parents(cb_parent_selector))[0]).hide() } \ No newline at end of file diff --git a/app/views/assets/publishing/_options_for_publishing.html.erb b/app/views/assets/publishing/_options_for_publishing.html.erb index 403fe471ea..3bf01616e9 100644 --- a/app/views/assets/publishing/_options_for_publishing.html.erb +++ b/app/views/assets/publishing/_options_for_publishing.html.erb @@ -8,44 +8,69 @@ toggle ||=false cb_parent_selector ||="div\##{item.class.name}_#{item.id}.split_button_parent" + show_managers=true + managed_by_selector ||="div\##{item.class.name}_#{item.id}.managed_by_toggle" -%>
- <% if item.can_view? %> -
- <%= text_for_resource item -%>: <%= link_to item.title, item, :target => "_blank" -%> - <%=list_item_visibility(item)-%> -
-
Manageable by <%= item.managers.empty? ? "None".html_safe : item.managers.collect { |m| link_to(h(m.title), m) }.join(", ").html_safe -%>
- <% else %> -
<%= text_for_resource item -%>: This item is hidden to you
- <% if current_user.try(:person) && item.can_see_hidden_item?(current_user.person) %> -
Manageable by <%= item.managers.empty? ? "
None
".html_safe : item.managers.collect { |m| link_to(h(m.title), m) }.join(", ").html_safe -%>
- <% end %> + <% if toggle %> +
+ +
+
+ +
<% end %> - -
- <% if item.is_published? %> - <%= render :partial => 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: "#{item.class.name}_#{item.id}", - published: true, - toggle: toggle, - cb_parent_selector: cb_parent_selector} -%> - <% elsif item.can_publish? %> - <%= render :partial => 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: "#{item.class.name}_#{item.id}", - checkbox_text: "Publish?", - checked: checked, - toggle: toggle, - cb_parent_selector: cb_parent_selector} -%> - <% else %> + <% if item.can_view? %> +
+ <% if item.is_published? %> + <%= render :partial => 'general/split_button_checkbox', + locals: { checkbox_id: publishing_item_param(item), + checkbox_class: "#{item.class.name}_#{item.id}", + published: true, + toggle: toggle, + cb_parent_selector: cb_parent_selector} -%> + <% elsif item.can_publish? %> + <%= render :partial => 'general/split_button_checkbox', + locals: { checkbox_id: publishing_item_param(item), + checkbox_class: "#{item.class.name}_#{item.id}", + checkbox_text: "", + checked: checked, + toggle: toggle, + cb_parent_selector: cb_parent_selector} -%> + <% else %> - Can't publish + <% end %> +
+
+ <%= text_for_resource item -%>: <%= link_to item.title, item, :target => "_blank" -%> + <%=list_item_visibility(item)-%> +
+ <% else %> +
+ + + +
+
<%= text_for_resource item -%>: This item is hidden to you
+ <% unless current_user.try(:person) && item.can_see_hidden_item?(current_user.person) %> + <% show_managers=false%> <% end %> -
+ <% end %> + <% if show_managers %> +
+ +
style="display:none;margin-left:5em"> + Manageable by <%= item.managers.empty? ? "None".html_safe : item.managers.collect { |m| link_to(h(m.title), m) }.join(", ").html_safe -%> +
+
+ <% end %>
+ diff --git a/test/functional/publishing/batch_publishing_test.rb b/test/functional/publishing/batch_publishing_test.rb index b90cdd2993..aeac3eb6ab 100644 --- a/test/functional/publishing/batch_publishing_test.rb +++ b/test/functional/publishing/batch_publishing_test.rb @@ -59,7 +59,7 @@ def setup assert_select '.type_and_title img[src*=?][title=?]', 'lock.png', 'Private', count: total_asset_count end - assert_select '.parent-btn-checkbox', text: /Publish/, count: total_asset_count do + assert_select '.parent-btn-checkbox', count: total_asset_count do publish_immediately_assets.each do |a| assert_select "input[type='checkbox'][id=?]", "publish_#{a.class.name}_#{a.id}" end diff --git a/test/functional/publishing/single_publishing_test.rb b/test/functional/publishing/single_publishing_test.rb index 8f77468a73..4638c54f76 100644 --- a/test/functional/publishing/single_publishing_test.rb +++ b/test/functional/publishing/single_publishing_test.rb @@ -109,21 +109,21 @@ def setup assert_select '.type_and_title', text: /Investigation/, count: 1 do assert_select 'a[href=?]', investigation_path(investigation), text: /#{investigation.title}/ end - assert_select '.parent-btn-checkbox', text: /Publish/ do + assert_select '.parent-btn-checkbox' do assert_select "input[type='checkbox'][id=?]", "publish_Investigation_#{investigation.id}" end assert_select '.type_and_title', text: /Study/, count: 1 do assert_select 'a[href=?]', study_path(study), text: /#{study.title}/ end - assert_select '.parent-btn-checkbox', text: /Publish/ do + assert_select '.parent-btn-checkbox' do assert_select "input[type='checkbox'][id=?]", "publish_Study_#{study.id}" end assert_select '.type_and_title', text: /Assay/, count: 1 do assert_select 'a[href=?]', assay_path(assay), text: /#{assay.title}/ end - assert_select '.parent-btn-checkbox', text: /Publish/ do + assert_select '.parent-btn-checkbox' do assert_select "input[type='checkbox'][id=?]", "publish_Assay_#{assay.id}" end @@ -133,12 +133,12 @@ def setup assert_select 'a[href=?]', data_file_path(request_publishing_df), text: /#{request_publishing_df.title}/ assert_select 'a[href=?]', data_file_path(notifying_df), text: /#{notifying_df.title}/ end - assert_select '.parent-btn-checkbox', text: /Publish/ do + assert_select '.parent-btn-checkbox' do assert_select "input[type='checkbox'][id=?]", "publish_DataFile_#{publishing_df.id}" assert_select "input[type='checkbox'][id=?]", "publish_DataFile_#{request_publishing_df.id}" end - assert_select 'span.label-warning', text: "Can't publish", count: 1 + assert_select 'span.label-warning[data-tooltip=?]', 'You do not have permission to publish this item.', count: 1 end test 'split-button recursive selection' do From 8741deae0f554bf257b501bc8dda9e7eb460c7aa Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Wed, 19 Jul 2023 17:26:39 +0100 Subject: [PATCH 005/383] Refactored batch publishing preview - options for sorting and js --- app/assets/javascripts/checkbox.js | 54 ++++++- .../_isa_publishing_preview.html.erb | 1 + .../batch_publishing_preview.html.erb | 148 +++++++++++++++--- lib/seek/publishing/publishing_common.rb | 19 ++- 4 files changed, 194 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js index 9c060735a9..cfaecfd5d3 100644 --- a/app/assets/javascripts/checkbox.js +++ b/app/assets/javascripts/checkbox.js @@ -24,6 +24,22 @@ $j(document).ready(function () { $j("div.isa-tree-toggle-close").click(function () { isaTreeHide(this,$j(this).data("cb_parent_selector")) }) + $j("a.collapseChildren").click(function (event) { + event.preventDefault(); + collapseRecursively($j(this).data("cb_parent_selector")) + }) + $j("a.expandChildren").click(function (event) { + event.preventDefault(); + expandRecursively($j(this).data("cb_parent_selector")) + }) + $j(".hideBlocked").click(function (event) { + event.preventDefault(); + hideBlocked($j(this).data("cb_parent_selector"),$j(this).data("blocked_selector")) + }) + $j(".showBlocked").click(function (event) { + event.preventDefault(); + showBlocked($j(this).data("cb_parent_selector"),$j(this).data("blocked_selector")) + }) }) function selectChildren(select_all_element,cb_parent_selector){ @@ -56,7 +72,9 @@ function toggleManagers(item,managed_by_selector) { function isaTreeShow(item,cb_parent_selector) { let children_assets = $j('.isa-tree', $j(item).parents(cb_parent_selector)) - children_assets.splice(0, 1); + if(cb_parent_selector.includes('split_button_parent')){ + children_assets.splice(0, 1); + } let keep_closed = [] for (let asset of children_assets) { $j(asset).show() @@ -75,10 +93,40 @@ function isaTreeShow(item,cb_parent_selector) { } function isaTreeHide(item,cb_parent_selector){ let children_assets = $j('.isa-tree', $j(item).parents(cb_parent_selector)) - children_assets.splice(0, 1); + if(cb_parent_selector.includes('split_button_parent')){ + children_assets.splice(0, 1); + } for (let asset of children_assets) { $j(asset).hide() } $j($j('.isa-tree-toggle-open', $j(item).parents(cb_parent_selector))[0]).show() $j($j('.isa-tree-toggle-close', $j(item).parents(cb_parent_selector))[0]).hide() -} \ No newline at end of file +} + +function collapseRecursively(cb_parent_selector){ + let children_assets = $j('[class^=isa-tree-toggle]', $j(cb_parent_selector)) + for (let asset of children_assets) { + isaTreeHide(asset,$j(asset).data("cb_parent_selector")) + } +} + +function expandRecursively(cb_parent_selector){ + let children_assets = $j('[class^=isa-tree-toggle]', $j(cb_parent_selector)) + for (let asset of children_assets) { + isaTreeShow(asset,$j(asset).data("cb_parent_selector")) + } +} + +function hideBlocked(cb_parent_selector,blocked_selector){ + let children_assets = $j(blocked_selector, $j(cb_parent_selector)) + for (let asset of children_assets) { + $j($j(asset).parents('div.split_button_parent')[0]).hide() + } +} + +function showBlocked(cb_parent_selector,blocked_selector){ + let children_assets = $j(blocked_selector, $j(cb_parent_selector)) + for (let asset of children_assets) { + $j($j(asset).parents('div.split_button_parent')[0]).show() + } +} diff --git a/app/views/assets/publishing/_isa_publishing_preview.html.erb b/app/views/assets/publishing/_isa_publishing_preview.html.erb index 53779ae75a..5e22c36ac8 100644 --- a/app/views/assets/publishing/_isa_publishing_preview.html.erb +++ b/app/views/assets/publishing/_isa_publishing_preview.html.erb @@ -1,5 +1,6 @@ <% item = isa_publishing_preview + preselected ||=[] case item when Investigation diff --git a/app/views/assets/publishing/batch_publishing_preview.html.erb b/app/views/assets/publishing/batch_publishing_preview.html.erb index 625fca6bae..cc9055d779 100644 --- a/app/views/assets/publishing/batch_publishing_preview.html.erb +++ b/app/views/assets/publishing/batch_publishing_preview.html.erb @@ -11,17 +11,10 @@

- You can select an item to be published by checking the Publish - checkbox beside that item. + You can select an item to be published by checking the checkbox beside that item.

-
- - | - <%= form_tag({:action => :check_related_items},:method=>:post) do -%> <% if @assets.empty? %> @@ -30,23 +23,124 @@
<%= link_to "Back to profile", person_path(params[:id].to_i) -%> <% else %> - <% @assets.sort_by { |k, v| v.first.class.name }.each do |type, items| %> -
-

<%= text_for_resource items.first %>(s)

- | - - <% items.each do |item| %> - <%= render :partial => "assets/publishing/options_for_publishing", - :object => item, - :locals => { :html_classes => "publishing_options" } -%> -
+ +
+ + +
+

+ + +
+ <% @assets.sort_by { |k, v| v.first.class.name }.each do |type, items| %> +
+
+ +
+
+ +
+

<%= text_for_resource items.first %>(s)

+ +
+ <% end %> +
+
+ +
+ <% unless @assets_not_in_isa.empty? %> +
+
+ +
+
+ +
+

Items not in ISA

+ + <% @assets_not_in_isa.each do |item| %> + <%= render :partial => "assets/publishing/options_for_publishing", + :object => item, + :locals => { :html_classes => "publishing_options" } -%> + <% end %> +
+
<% end %> -
- <% end %> + +
+ + <% unless @investigations.empty? %> +
+
+ +
+
+ +
+

Items in ISA

+ +
+ <% @investigations.each do |inv| %> + <%= render :partial => "assets/publishing/isa_publishing_preview", + :collection => inv.assays.map(&:study).map(&:investigation).flatten.uniq %> + <% end %> +
+
+
+ <% end %> +
+
<%= submit_tag "Next",data: { disable_with: 'Next' }, :class => 'btn btn-primary' -%> @@ -54,3 +148,9 @@ <%= cancel_button person_path(params[:id].to_i)-%> <% end -%> <% end -%> + + diff --git a/lib/seek/publishing/publishing_common.rb b/lib/seek/publishing/publishing_common.rb index f8410d2515..1979158ba8 100644 --- a/lib/seek/publishing/publishing_common.rb +++ b/lib/seek/publishing/publishing_common.rb @@ -3,7 +3,7 @@ module Publishing module PublishingCommon def self.included(base) base.before_action :set_asset, only: [:check_related_items, :publish_related_items, :check_gatekeeper_required, :publish, :published] - base.before_action :set_assets, only: [:batch_publishing_preview] + base.before_action :set_assets, :set_investigations, only: [:batch_publishing_preview] base.before_action :set_items_for_publishing, only: [:check_gatekeeper_required, :publish] base.before_action :set_items_for_potential_publishing, only: [:check_related_items, :publish_related_items] base.before_action :publish_auth, only: [:batch_publishing_preview, :check_related_items, :publish_related_items, :check_gatekeeper_required, :publish, :waiting_approval_assets, :cancel_publishing_request] @@ -145,6 +145,23 @@ def set_assets end end + def set_investigations + @investigations = [] + @assets_not_in_isa = [] + @assets.each do |type, klass| + klass.each do |asset| + if asset.investigations.empty? + @assets_not_in_isa.push(asset) + else + asset.investigations.each do |inv| + next if @investigations.include?(inv) + @investigations.push(inv) + end + end + end + end + end + # sets the @items_for_publishing based on the :publish param, and filtered by whether than can_publish? def set_items_for_publishing @items_for_publishing = resolve_publish_params(params[:publish]).select(&:can_publish?) From 8b42a159ccbc18f17589a5cd0b4f14372c3fa925 Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Fri, 21 Jul 2023 16:05:16 +0100 Subject: [PATCH 006/383] Created batch_asset_selection and isa_tree_toggle partials --- .../assets/_batch_asset_selection.html.erb | 119 ++++++++++++++++ app/views/assets/_isa-tree-toggle.html.erb | 16 +++ .../_isa_publishing_preview.html.erb | 2 + .../_options_for_publishing.html.erb | 14 +- .../batch_publishing_preview.html.erb | 131 +----------------- .../publishing/publish_related_items.html.erb | 4 +- 6 files changed, 147 insertions(+), 139 deletions(-) create mode 100644 app/views/assets/_batch_asset_selection.html.erb create mode 100644 app/views/assets/_isa-tree-toggle.html.erb diff --git a/app/views/assets/_batch_asset_selection.html.erb b/app/views/assets/_batch_asset_selection.html.erb new file mode 100644 index 0000000000..866b42c46d --- /dev/null +++ b/app/views/assets/_batch_asset_selection.html.erb @@ -0,0 +1,119 @@ +<% + publishing ||= true + show_hide_blocked ||= false + blocked_selector ||= '' +-%> + + +
+ + +
+

+ + +
+ <% @assets.sort_by { |k, v| v.first.class.name }.each do |type, items| %> +
+ <%= render :partial => 'assets/isa-tree-toggle', locals: { cb_parent_selector: "div##{items.first.class.table_name}" } -%> +

<%= text_for_resource items.first %>(s)

+
+ | + + <% items.each do |item| %> + <%= render :partial => "assets/publishing/options_for_publishing", + :object => item, + :block_published => publishing, + :locals => { :html_classes => "publishing_options" } -%> + <% end %> +
+
+ <% end %> +
+
+ +
+ <% unless @assets_not_in_isa.empty? %> +
+ <%= render :partial => 'assets/isa-tree-toggle', locals: { cb_parent_selector: "div#items_not_in_isa" } -%> +

Items not in ISA

+ + <% @assets_not_in_isa.each do |item| %> + <%= render :partial => "assets/publishing/options_for_publishing", + :object => item, + :block_published => publishing, + :locals => { :html_classes => "publishing_options" } -%> + <% end %> +
+
+ <% end %> + +
+ + <% unless @investigations.empty? %> +
+ <%= render :partial => 'assets/isa-tree-toggle', locals: { cb_parent_selector: "div#items_in_isa" } -%> +

Items in ISA

+ +
+ <% @investigations.each do |inv| %> + <%= render :partial => "assets/publishing/isa_publishing_preview", + :locals => { :publishing => publishing }, + :collection => inv.assays.map(&:study).map(&:investigation).flatten.uniq %> + <% end %> +
+
+
+ <% end %> +
+ + \ No newline at end of file diff --git a/app/views/assets/_isa-tree-toggle.html.erb b/app/views/assets/_isa-tree-toggle.html.erb new file mode 100644 index 0000000000..985efd7d17 --- /dev/null +++ b/app/views/assets/_isa-tree-toggle.html.erb @@ -0,0 +1,16 @@ +<% + cb_parent_selector ||="" +-%> + +
+ +
+
+ +
+ + diff --git a/app/views/assets/publishing/_isa_publishing_preview.html.erb b/app/views/assets/publishing/_isa_publishing_preview.html.erb index 5e22c36ac8..ca8e354963 100644 --- a/app/views/assets/publishing/_isa_publishing_preview.html.erb +++ b/app/views/assets/publishing/_isa_publishing_preview.html.erb @@ -1,6 +1,7 @@ <% item = isa_publishing_preview preselected ||=[] + publishing ||= false case item when Investigation @@ -19,6 +20,7 @@ :locals => { :html_classes => "publishing_options", :toggle => children.any?, :cb_parent_selector => "div\##{item.class.name}_#{item.id}.split_button_parent", + :block_published => publishing, :checked => (item == preselected) } -%> <% if children.any? %> diff --git a/app/views/assets/publishing/_options_for_publishing.html.erb b/app/views/assets/publishing/_options_for_publishing.html.erb index 3bf01616e9..d8bc91eae1 100644 --- a/app/views/assets/publishing/_options_for_publishing.html.erb +++ b/app/views/assets/publishing/_options_for_publishing.html.erb @@ -1,6 +1,7 @@ <% item = options_for_publishing checked ||= false + block_published ||= false tree_class = 'not-publishable' tree_class = 'publishable' if item.can_publish? @@ -14,12 +15,7 @@ -%>
<% if toggle %> -
- -
-
- -
+ <%= render :partial => 'assets/isa-tree-toggle', locals: { cb_parent_selector: cb_parent_selector } -%> <% end %> <% if item.can_view? %>
@@ -68,9 +64,3 @@
<% end %>
- - diff --git a/app/views/assets/publishing/batch_publishing_preview.html.erb b/app/views/assets/publishing/batch_publishing_preview.html.erb index cc9055d779..56b9145061 100644 --- a/app/views/assets/publishing/batch_publishing_preview.html.erb +++ b/app/views/assets/publishing/batch_publishing_preview.html.erb @@ -23,134 +23,15 @@
<%= link_to "Back to profile", person_path(params[:id].to_i) -%> <% else %> - -
- - -
-

- - -
- <% @assets.sort_by { |k, v| v.first.class.name }.each do |type, items| %> -
-
- -
-
- -
-

<%= text_for_resource items.first %>(s)

- -
- <% end %> -
-
- -
- <% unless @assets_not_in_isa.empty? %> -
-
- -
-
- -
-

Items not in ISA

- - <% @assets_not_in_isa.each do |item| %> - <%= render :partial => "assets/publishing/options_for_publishing", - :object => item, - :locals => { :html_classes => "publishing_options" } -%> - <% end %> -
-
- <% end %> - -
- - <% unless @investigations.empty? %> -
-
- -
-
- -
-

Items in ISA

- -
- <% @investigations.each do |inv| %> - <%= render :partial => "assets/publishing/isa_publishing_preview", - :collection => inv.assays.map(&:study).map(&:investigation).flatten.uniq %> - <% end %> -
-
-
- <% end %> -
- + <%= render :partial => "assets/batch_asset_selection", + :locals => { :publishing => true, + :show_hide_blocked => true, + :blocked_selector => '.not-visible,.not-publishable,.already-published' + } + -%>
- <%= submit_tag "Next",data: { disable_with: 'Next' }, :class => 'btn btn-primary' -%> Or <%= cancel_button person_path(params[:id].to_i)-%> <% end -%> <% end -%> - - diff --git a/app/views/assets/publishing/publish_related_items.html.erb b/app/views/assets/publishing/publish_related_items.html.erb index 7bb8cdc55e..8e368393e4 100644 --- a/app/views/assets/publishing/publish_related_items.html.erb +++ b/app/views/assets/publishing/publish_related_items.html.erb @@ -18,10 +18,10 @@ <% if item.is_asset? %> <%= render :partial => "assets/publishing/isa_publishing_preview", :collection => item.assays.map(&:study).map(&:investigation).flatten.uniq, - :locals => { :preselected => item } %> + :locals => { :preselected => item, :publishing => true } %> <% else %> <%= render :partial => "assets/publishing/isa_publishing_preview", :object => item, - :locals => { :preselected => item } %> + :locals => { :preselected => item, :publishing => true } %> <% end %> <% else %> <%= check_box_tag publishing_item_param(item), 1, true, {:style => 'display:none;'} %> From 39630f1d4fec1e45ef8f67788ad3b5c097a3de4e Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Fri, 21 Jul 2023 16:09:06 +0100 Subject: [PATCH 007/383] Homogenised parent-btn-checkbox appearance and fixed hideBlocked. --- app/assets/javascripts/checkbox.js | 5 ++- app/assets/stylesheets/publishing.scss | 4 ++ app/assets/stylesheets/sharing.scss | 1 + .../_options_for_publishing.html.erb | 39 ++++++++----------- .../general/_split_button_checkbox.html.erb | 34 ++++++++++++---- .../publishing/batch_publishing_test.rb | 36 +++++++++-------- .../publishing/single_publishing_test.rb | 4 +- 7 files changed, 75 insertions(+), 48 deletions(-) diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js index cfaecfd5d3..6aad9d7537 100644 --- a/app/assets/javascripts/checkbox.js +++ b/app/assets/javascripts/checkbox.js @@ -120,7 +120,10 @@ function expandRecursively(cb_parent_selector){ function hideBlocked(cb_parent_selector,blocked_selector){ let children_assets = $j(blocked_selector, $j(cb_parent_selector)) for (let asset of children_assets) { - $j($j(asset).parents('div.split_button_parent')[0]).hide() + // Don't hide "parents" of non-blocked items + if(!$j('input[type=checkbox]',$j(asset).parent()).length>0) { + $j($j(asset).parents('div.split_button_parent')[0]).hide() + } } } diff --git a/app/assets/stylesheets/publishing.scss b/app/assets/stylesheets/publishing.scss index 1de7aec917..6e47fdcdd7 100644 --- a/app/assets/stylesheets/publishing.scss +++ b/app/assets/stylesheets/publishing.scss @@ -73,6 +73,10 @@ ul.item_for_decision { border-left-color: $btn-warning-bg; } + &.not-visible { + border-left-color: $btn-danger-bg; + } + &.already-published { border-left-color: $gray-light; } diff --git a/app/assets/stylesheets/sharing.scss b/app/assets/stylesheets/sharing.scss index d396101a0c..9a4ce89df3 100644 --- a/app/assets/stylesheets/sharing.scss +++ b/app/assets/stylesheets/sharing.scss @@ -216,6 +216,7 @@ border-radius: 5px; display: inline-block; height: 25px; + cursor: default; } .parent-btn-dropdown { padding: 2px 6px 10px 0px; diff --git a/app/views/assets/publishing/_options_for_publishing.html.erb b/app/views/assets/publishing/_options_for_publishing.html.erb index d8bc91eae1..87831e6b81 100644 --- a/app/views/assets/publishing/_options_for_publishing.html.erb +++ b/app/views/assets/publishing/_options_for_publishing.html.erb @@ -6,6 +6,7 @@ tree_class = 'not-publishable' tree_class = 'publishable' if item.can_publish? tree_class = 'already-published' if item.is_published? + tree_class = 'not-visible' unless item.can_view? toggle ||=false cb_parent_selector ||="div\##{item.class.name}_#{item.id}.split_button_parent" @@ -19,26 +20,15 @@ <% end %> <% if item.can_view? %>
- <% if item.is_published? %> - <%= render :partial => 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: "#{item.class.name}_#{item.id}", - published: true, - toggle: toggle, - cb_parent_selector: cb_parent_selector} -%> - <% elsif item.can_publish? %> - <%= render :partial => 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: "#{item.class.name}_#{item.id}", - checkbox_text: "", - checked: checked, - toggle: toggle, - cb_parent_selector: cb_parent_selector} -%> - <% else %> - - - - <% end %> + <%= render :partial => 'general/split_button_checkbox', + locals: { checkbox_id: publishing_item_param(item), + checkbox_class: "#{item.class.name}_#{item.id}", + checked: checked, + checkbox_text: "", + published: (item.is_published? && block_published), + cant_manage: !(item.can_manage?), + toggle: toggle, + cb_parent_selector: cb_parent_selector} -%>
<%= text_for_resource item -%>: <%= link_to item.title, item, :target => "_blank" -%> @@ -46,9 +36,12 @@
<% else %>
- - - + <%= render :partial => 'general/split_button_checkbox', + locals: { checkbox_id: publishing_item_param(item), + checkbox_class: "#{item.class.name}_#{item.id}", + not_visible: true, + toggle: toggle, + cb_parent_selector: cb_parent_selector} -%>
<%= text_for_resource item -%>: This item is hidden to you
<% unless current_user.try(:person) && item.can_see_hidden_item?(current_user.person) %> diff --git a/app/views/general/_split_button_checkbox.html.erb b/app/views/general/_split_button_checkbox.html.erb index fbf98b52f3..9bdd827f5e 100644 --- a/app/views/general/_split_button_checkbox.html.erb +++ b/app/views/general/_split_button_checkbox.html.erb @@ -2,7 +2,9 @@ #<%= render :partial => 'general/split_button_checkbox', #locals: { checkbox_id: publishing_item_param(item), # checkbox_class: "#{item.class.name}_#{item.id}", - # published: true, + # published: false, + # cant_manage: false + # not_visible: false # checkbox_text: "Publish?", # checked: checked, # toggle: children.any?, @@ -11,27 +13,45 @@ checkbox_class ||= "no_name" # <- Needed! checkbox_id ||= checkbox_class published ||= false + cant_manage ||= false + not_visible ||= false checkbox_text ||= "" checked ||= false toggle ||= false cb_parent_selector ||= "div\##{checkbox_class}.split_button_parent" -%>
- + <% elsif cant_manage %> + + <% elsif not_visible %> + + <% else %> + + + <% end %> + <% if toggle %>
<% @investigations.each do |inv| %> - <%= render :partial => "assets/publishing/isa_publishing_preview", + <%= render :partial => "assets/isa_tree_preview", :locals => { :publishing => publishing }, :collection => inv.assays.map(&:study).map(&:investigation).flatten.uniq %> <% end %> diff --git a/app/views/assets/_isa_tree_preview.html.erb b/app/views/assets/_isa_tree_preview.html.erb new file mode 100644 index 0000000000..f0c827885a --- /dev/null +++ b/app/views/assets/_isa_tree_preview.html.erb @@ -0,0 +1,37 @@ +<% + item = isa_tree_preview + preselected ||=[] + publishing ||= false + + case item + when Investigation + children = item.studies + when Study + children = item.assays + when Assay + children = item.assets + else + children = [] + end +-%> +
"> + <%= render :partial => "assets/asset_checkbox_row", + :object => item, + :locals => { :html_classes => "publishing_options", + :toggle => children.any?, + :cb_parent_selector => "div\##{item.class.name}_#{item.id}.split_button_parent", + :block_published => publishing, + :checked => (item == preselected) } -%> + + <% if children.any? %> +
+ <% children.each do |child| %> + <%= render :partial => "assets/isa_tree_preview", + :object => child, + :locals => { :preselected => preselected, + :publishing => publishing, + :html_classes => "publishing_options"} -%> + <% end %> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/assets/publishing/_isa_publishing_preview.html.erb b/app/views/assets/publishing/_isa_publishing_preview.html.erb deleted file mode 100644 index ca8e354963..0000000000 --- a/app/views/assets/publishing/_isa_publishing_preview.html.erb +++ /dev/null @@ -1,36 +0,0 @@ -<% - item = isa_publishing_preview - preselected ||=[] - publishing ||= false - - case item - when Investigation - children = item.studies - when Study - children = item.assays - when Assay - children = item.assets - else - children = [] - end --%> -
"> - <%= render :partial => "assets/publishing/options_for_publishing", - :object => item, - :locals => { :html_classes => "publishing_options", - :toggle => children.any?, - :cb_parent_selector => "div\##{item.class.name}_#{item.id}.split_button_parent", - :block_published => publishing, - :checked => (item == preselected) } -%> - - <% if children.any? %> -
- <% children.each do |child| %> - <%= render :partial => "assets/publishing/isa_publishing_preview", - :object => child, - :locals => { :preselected => preselected, - :html_classes => "publishing_options"} -%> - <% end %> -
- <% end %> -
\ No newline at end of file diff --git a/app/views/assets/publishing/batch_publishing_preview.html.erb b/app/views/assets/publishing/batch_publishing_preview.html.erb index 56b9145061..bab2590b4b 100644 --- a/app/views/assets/publishing/batch_publishing_preview.html.erb +++ b/app/views/assets/publishing/batch_publishing_preview.html.erb @@ -26,7 +26,7 @@ <%= render :partial => "assets/batch_asset_selection", :locals => { :publishing => true, :show_hide_blocked => true, - :blocked_selector => '.not-visible,.not-publishable,.already-published' + :blocked_selector => '.not-visible,.not-manageable,.already-published' } -%>
diff --git a/app/views/assets/publishing/publish_related_items.html.erb b/app/views/assets/publishing/publish_related_items.html.erb index 8e368393e4..ac7ea874a1 100644 --- a/app/views/assets/publishing/publish_related_items.html.erb +++ b/app/views/assets/publishing/publish_related_items.html.erb @@ -16,11 +16,11 @@ <% @items_for_publishing.each do |item| %> <% if item.contains_publishable_items? %> <% if item.is_asset? %> - <%= render :partial => "assets/publishing/isa_publishing_preview", + <%= render :partial => "assets/isa_tree_preview", :collection => item.assays.map(&:study).map(&:investigation).flatten.uniq, :locals => { :preselected => item, :publishing => true } %> <% else %> - <%= render :partial => "assets/publishing/isa_publishing_preview", :object => item, + <%= render :partial => "assets/isa_tree_preview", :object => item, :locals => { :preselected => item, :publishing => true } %> <% end %> <% else %> From 0b386762130ce700d2e6e07a8140c5a9ae76e483 Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Tue, 25 Jul 2023 10:48:38 +0100 Subject: [PATCH 009/383] Fixes issues with published and blocked items, and hides blocked by default --- app/assets/javascripts/checkbox.js | 18 ++++++++++++++---- app/views/assets/_asset_checkbox_row.html.erb | 2 +- .../assets/_batch_asset_selection.html.erb | 17 +++++++++++++---- lib/seek/publishing/publishing_common.rb | 3 +-- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js index 6aad9d7537..1245bd5675 100644 --- a/app/assets/javascripts/checkbox.js +++ b/app/assets/javascripts/checkbox.js @@ -120,9 +120,15 @@ function expandRecursively(cb_parent_selector){ function hideBlocked(cb_parent_selector,blocked_selector){ let children_assets = $j(blocked_selector, $j(cb_parent_selector)) for (let asset of children_assets) { - // Don't hide "parents" of non-blocked items - if(!$j('input[type=checkbox]',$j(asset).parent()).length>0) { - $j($j(asset).parents('div.split_button_parent')[0]).hide() + //Items in isa tree + if($j($j(asset).parents('div.split_button_parent')).length>0) { + // Don't hide "parents" of non-blocked items + if (!$j('input[type=checkbox]', $j(asset).parent()).length > 0) { + $j($j(asset).parents('div.split_button_parent')[0]).hide() + } + //Items not in isa tree + } else { + $j(asset).hide() } } } @@ -130,6 +136,10 @@ function hideBlocked(cb_parent_selector,blocked_selector){ function showBlocked(cb_parent_selector,blocked_selector){ let children_assets = $j(blocked_selector, $j(cb_parent_selector)) for (let asset of children_assets) { - $j($j(asset).parents('div.split_button_parent')[0]).show() + if($j($j(asset).parents('div.split_button_parent')).length>0) { + $j($j(asset).parents('div.split_button_parent')[0]).show() + } else{ + $j(asset).show() + } } } diff --git a/app/views/assets/_asset_checkbox_row.html.erb b/app/views/assets/_asset_checkbox_row.html.erb index f83be55ac1..5e7dbe6b59 100644 --- a/app/views/assets/_asset_checkbox_row.html.erb +++ b/app/views/assets/_asset_checkbox_row.html.erb @@ -5,7 +5,7 @@ tree_class = 'not-manageable' tree_class = 'publishable' if item.can_publish? && item.can_manage? - tree_class = 'already-published' if item.is_published? && block_published + tree_class = 'already-published' if item.is_published? tree_class = 'not-visible' unless item.can_view? toggle ||=false diff --git a/app/views/assets/_batch_asset_selection.html.erb b/app/views/assets/_batch_asset_selection.html.erb index a3f383219d..d441bbd234 100644 --- a/app/views/assets/_batch_asset_selection.html.erb +++ b/app/views/assets/_batch_asset_selection.html.erb @@ -27,6 +27,12 @@ Collapse all | + <% if show_hide_blocked %> + | + | + <% end %>
@@ -44,8 +50,8 @@ <% items.each do |item| %> <%= render :partial => "assets/asset_checkbox_row", :object => item, - :block_published => publishing, - :locals => { :html_classes => "publishing_options" } -%> + :locals => { :html_classes => "publishing_options", + :block_published => publishing } -%> <% end %>
@@ -69,8 +75,8 @@ <% @assets_not_in_isa.each do |item| %> <%= render :partial => "assets/asset_checkbox_row", :object => item, - :block_published => publishing, - :locals => { :html_classes => "publishing_options" } -%> + :locals => { :html_classes => "publishing_options", + :block_published => publishing } -%> <% end %>
@@ -116,4 +122,7 @@ $j(document).ready(function () { $j('div#sorted_by_isa').hide() }) + $j(document).ready(function () { + hideBlocked(document,"<%="#{blocked_selector}"%>") + }) \ No newline at end of file diff --git a/lib/seek/publishing/publishing_common.rb b/lib/seek/publishing/publishing_common.rb index 1979158ba8..80aacac50b 100644 --- a/lib/seek/publishing/publishing_common.rb +++ b/lib/seek/publishing/publishing_common.rb @@ -133,12 +133,11 @@ def set_asset end def set_assets - # get the assets that current_user can manage, then take the one that can_publish? + # get the assets that current_user can manage @assets = {} publishable_types = Seek::Util.authorized_types.select { |authorized_type| authorized_type.first.try(:is_in_isa_publishable?) } publishable_types.each do |klass| can_manage_assets = klass.authorized_for 'manage', current_user - can_manage_assets = can_manage_assets.select(&:can_publish?) unless can_manage_assets.empty? @assets[klass.name] = can_manage_assets end From a31030760e229b4f469d0b7507b7e71545d36965 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 11:46:19 +0100 Subject: [PATCH 010/383] Add SPDX licenses --- app/helpers/license_helper.rb | 54 +- app/views/admin/settings.html.erb | 8 +- app/views/assets/_license_selector.html.erb | 2 +- app/views/snapshots/export_preview.html.erb | 2 +- lib/seek/license.rb | 61 +- public/spdx_licenses.json | 7011 +++++++++++++++++++ test/unit/license_test.rb | 63 +- 7 files changed, 7126 insertions(+), 75 deletions(-) create mode 100644 public/spdx_licenses.json diff --git a/app/helpers/license_helper.rb b/app/helpers/license_helper.rb index db4f841fb0..e957006f9d 100644 --- a/app/helpers/license_helper.rb +++ b/app/helpers/license_helper.rb @@ -2,10 +2,6 @@ require 'seek/license' module LicenseHelper - def license_select(name, selected = nil, opts = {}) - select_tag(name, options_for_select(license_options(opts), selected), opts) - end - def grouped_license_select(name, selected = nil, opts = {}) select_tag(name, grouped_options_for_select(grouped_license_options(opts), selected), opts) end @@ -67,50 +63,24 @@ def license_description_content(license) end end - def license_values(opts = {}) - opts.delete(:source) || Seek::License.open_definition[:all] - end - - def license_options(opts = {}) - license_values(opts).map { |value| [value['title'], value['id'], { 'data-url' => value['url'] }] } - end - def grouped_license_options(opts = {}) - grouped_licenses = sort_grouped_licenses(group_licenses(opts)) - - grouped_licenses.each do |g, licenses| - licenses.map! { |value| [value['title'], value['id'], { 'data-url' => value['url'] }] } - end - - grouped_licenses - end - - def sort_grouped_licenses(licenses) - s = licenses.sort_by do |pair| - case pair[0] - when 'recommended' - 0 -# when 'Generic' -# 2 - else - 3 - end - end - s.each do |pair| - pair[0] = "#{t('licenses.' + pair[0])}" - end - s - end - - def group_licenses(opts) + source = opts.delete(:source) || Seek::License.combined recommended = opts.delete(:recommended) - license_values(opts).group_by do |l| + grouped_licenses = source.values.group_by do |l| if recommended&.include?(l['id']) 'recommended' else 'other' end - end.to_a + end + + grouped_licenses.transform_values! do |licenses| + licenses.map! { |value| [value['title'], value['id'], { 'data-url' => value['url'] }] }.sort_by!(&:first) + end + + # Transform into array to ensure recommended licenses come first + ['recommended', 'other'].map do |key| + [t("licenses.#{key}"), grouped_licenses[key] || []] + end end - end diff --git a/app/views/admin/settings.html.erb b/app/views/admin/settings.html.erb index 87068624e8..aae32a23e4 100644 --- a/app/views/admin/settings.html.erb +++ b/app/views/admin/settings.html.erb @@ -58,20 +58,20 @@ <%= admin_setting_block('Default License', "The default license to use when one is not specified by a #{t('project').pluralize}.") do grouped_license_select(:default_license, Seek::Config.default_license, id: 'license-select', - class: 'form-control', source: Seek::License.open_definition[:data]) + class: 'form-control', source: Seek::License.combined) end %> <%= admin_setting_block('Recommended data licenses', "The licenses to recommend when data, such as a #{t('data_file')} or #{t('document')}, is registered. Hold down CTRL (or Command on Mac) to select multiple or to deselect.") do - select_tag :recommended_data_licenses, options_for_select(Seek::License.open_definition[:data].map { |l| [l['title'], l['id']]}, Seek::Config.recommended_data_licenses), multiple: true, class: 'form-control' + select_tag :recommended_data_licenses, options_for_select(Seek::License.combined.map { |id, l| [l['title'], id]}, Seek::Config.recommended_data_licenses), multiple: true, class: 'form-control' end %> <%= admin_setting_block('Recommended software licenses', "The licenses to recommend when software such as a #{t('workflow')}, is registered. Hold down CTRL (or Command on Mac) to select multiple or to deselect.") do - select_tag :recommended_software_licenses, options_for_select(Seek::License.open_definition[:software].map { |l| [l['title'], l['id']]}, Seek::Config.recommended_software_licenses), multiple: true, class: 'form-control' + select_tag :recommended_software_licenses, options_for_select(Seek::License.combined.map { |id, l| [l['title'], id]}, Seek::Config.recommended_software_licenses), multiple: true, class: 'form-control' end %> <%= admin_setting_block('Metadata License', "The license granted on metadata produced by this SEEK instance.") do grouped_license_select(:metadata_license, Seek::Config.metadata_license, id: 'metadata-license-select', - class: 'form-control', source: Seek::License.open_definition[:data]) + class: 'form-control', source: Seek::License.combined) end %> <%= admin_dropdown_setting(:permissions_popup, diff --git a/app/views/assets/_license_selector.html.erb b/app/views/assets/_license_selector.html.erb index d8091c5ecd..83e0f71a5d 100644 --- a/app/views/assets/_license_selector.html.erb +++ b/app/views/assets/_license_selector.html.erb @@ -30,7 +30,7 @@ <%= grouped_license_select("#{resource.class.name.underscore}[#{accessor.to_s}]", selected_license, id: 'license-select', class: 'form-control', data: { 'can-overwrite': using_default }, - source: Seek::License.open_definition[:all], + source: Seek::License.combined, recommended: recommended_licenses) %> diff --git a/app/views/snapshots/export_preview.html.erb b/app/views/snapshots/export_preview.html.erb index f32da5df67..bd7dc27ed8 100644 --- a/app/views/snapshots/export_preview.html.erb +++ b/app/views/snapshots/export_preview.html.erb @@ -34,7 +34,7 @@
<%= grouped_license_select('metadata[license]', 'cc-by', :id => 'license-select', :class => 'form-control', - :source => Seek::License.zenodo[:all]) %> + :source => Seek::License.zenodo) %> For more information on this license, please visit diff --git a/lib/seek/license.rb b/lib/seek/license.rb index c1fd083798..7beaa64b9c 100644 --- a/lib/seek/license.rb +++ b/lib/seek/license.rb @@ -10,47 +10,62 @@ class License < OpenStruct json end - private_class_method def self.organize_licenses(licenses) - category = {} + private_class_method def self.parse_zenodo(licenses) + hash = {} + licenses.each do |l| + hash[l['id']] = l + end + hash + end - category[:all] = licenses - .sort_by { |l| l['title'] } - .sort_by { |l| l.key?('is_generic') && l['is_generic'] ? 0 : 1 } + private_class_method def self.parse_spdx(licenses) + hash = {} + licenses['licenses'].each do |l| + hash[l['licenseId']] = { + 'title' => l['name'], + 'id' => l['licenseId'], + 'url' => (l['seeAlso'] || []).first || l['reference'] + } + end + hash + end - category[:data] = category[:all].select do |l| - l['domain_data'] || - l['domain_content'] || - l['id'] == NULL_LICENSE - end - - category[:software] = category[:all].select do |l| - (l['domain_software'] || l['id'] == NULL_LICENSE) - end - - category - end + private_class_method def self.combine(*licenses) + combined = {} + licenses.each do |set| + set.each { |l| combined[l['id']] ||= l } + end + combined + end - def self.find(id, source = nil) + def self.find(id, source = Seek::License.combined) if (license = find_as_hash(id, source)) new(license) end end - def self.find_as_hash(id, source = nil) - source ||= Seek::License.open_definition[:all] - source.find { |l| l['id'] == id } + def self.find_as_hash(id, source = Seek::License.combined) + source[id] end def is_null_license? id == NULL_LICENSE end + def self.combined + @combined ||= open_definition.merge(spdx) + end + + def self.spdx + @spdx_licenses ||= parse_spdx(JSON.parse(File.read(File.join(Rails.root, 'public', 'spdx_licenses.json')))) + end + def self.open_definition - @od_licenses ||= organize_licenses(override_json(JSON.parse(File.read(File.join(Rails.root, 'public', 'od_licenses.json')))).values) + @od_licenses ||= override_json(JSON.parse(File.read(File.join(Rails.root, 'public', 'od_licenses.json')))) end def self.zenodo - @zenodo_licenses ||= organize_licenses(JSON.parse(File.read(File.join(Rails.root, 'public', 'zenodo_licenses.json')))) + @zenodo_licenses ||= parse_zenodo(JSON.parse(File.read(File.join(Rails.root, 'public', 'zenodo_licenses.json')))) end end end diff --git a/public/spdx_licenses.json b/public/spdx_licenses.json new file mode 100644 index 0000000000..9b21d11b73 --- /dev/null +++ b/public/spdx_licenses.json @@ -0,0 +1,7011 @@ +{ + "licenseListVersion": "3.21", + "licenses": [ + { + "reference": "https://spdx.org/licenses/LPL-1.02.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPL-1.02.json", + "referenceNumber": 0, + "name": "Lucent Public License v1.02", + "licenseId": "LPL-1.02", + "seeAlso": [ + "http://plan9.bell-labs.com/plan9/license.html", + "https://opensource.org/licenses/LPL-1.02" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0.json", + "referenceNumber": 1, + "name": "GNU General Public License v2.0 only", + "licenseId": "GPL-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NTP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NTP.json", + "referenceNumber": 2, + "name": "NTP License", + "licenseId": "NTP", + "seeAlso": [ + "https://opensource.org/licenses/NTP" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1.json", + "referenceNumber": 3, + "name": "SIL Open Font License 1.1", + "licenseId": "OFL-1.1", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Bahyph.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Bahyph.json", + "referenceNumber": 4, + "name": "Bahyph License", + "licenseId": "Bahyph", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Bahyph" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Hippocratic-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Hippocratic-2.1.json", + "referenceNumber": 5, + "name": "Hippocratic License 2.1", + "licenseId": "Hippocratic-2.1", + "seeAlso": [ + "https://firstdonoharm.dev/version/2/1/license.html", + "https://github.com/EthicalSource/hippocratic-license/blob/58c0e646d64ff6fbee275bfe2b9492f914e3ab2a/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/VOSTROM.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/VOSTROM.json", + "referenceNumber": 6, + "name": "VOSTROM Public License for Open Source", + "licenseId": "VOSTROM", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/VOSTROM" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-AT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-AT.json", + "referenceNumber": 7, + "name": "Creative Commons Attribution 3.0 Austria", + "licenseId": "CC-BY-3.0-AT", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/at/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.5.json", + "referenceNumber": 8, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic", + "licenseId": "CC-BY-NC-SA-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/W3C-20150513.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C-20150513.json", + "referenceNumber": 9, + "name": "W3C Software Notice and Document License (2015-05-13)", + "licenseId": "W3C-20150513", + "seeAlso": [ + "https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0-only.json", + "referenceNumber": 10, + "name": "GNU Lesser General Public License v3.0 only", + "licenseId": "LGPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-only.json", + "referenceNumber": 11, + "name": "GNU Free Documentation License v1.1 only", + "licenseId": "GFDL-1.1-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDL-1.0.json", + "referenceNumber": 12, + "name": "Common Documentation License 1.0", + "licenseId": "CDL-1.0", + "seeAlso": [ + "http://www.opensource.apple.com/cdl/", + "https://fedoraproject.org/wiki/Licensing/Common_Documentation_License", + "https://www.gnu.org/licenses/license-list.html#ACDL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-2.5.json", + "referenceNumber": 13, + "name": "Creative Commons Attribution No Derivatives 2.5 Generic", + "licenseId": "CC-BY-ND-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/2.5/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-IGO.json", + "referenceNumber": 14, + "name": "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO", + "licenseId": "CC-BY-NC-SA-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGC-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGC-1.0.json", + "referenceNumber": 15, + "name": "OGC Software License, Version 1.0", + "licenseId": "OGC-1.0", + "seeAlso": [ + "https://www.ogc.org/ogc/software/1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPL-1.1.json", + "referenceNumber": 16, + "name": "Reciprocal Public License 1.1", + "licenseId": "RPL-1.1", + "seeAlso": [ + "https://opensource.org/licenses/RPL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-or-later.json", + "referenceNumber": 17, + "name": "GNU General Public License v2.0 or later", + "licenseId": "GPL-2.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Sendmail.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sendmail.json", + "referenceNumber": 18, + "name": "Sendmail License", + "licenseId": "Sendmail", + "seeAlso": [ + "http://www.sendmail.com/pdfs/open_source/sendmail_license.pdf", + "https://web.archive.org/web/20160322142305/https://www.sendmail.com/pdfs/open_source/sendmail_license.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0-only.json", + "referenceNumber": 19, + "name": "GNU Library General Public License v2 only", + "licenseId": "LGPL-2.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OSL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-3.0.json", + "referenceNumber": 20, + "name": "Open Software License 3.0", + "licenseId": "OSL-3.0", + "seeAlso": [ + "https://web.archive.org/web/20120101081418/http://rosenlaw.com:80/OSL3.0.htm", + "https://opensource.org/licenses/OSL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Libpng.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Libpng.json", + "referenceNumber": 21, + "name": "libpng License", + "licenseId": "Libpng", + "seeAlso": [ + "http://www.libpng.org/pub/png/src/libpng-LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-FreeBSD.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-FreeBSD.json", + "referenceNumber": 22, + "name": "BSD 2-Clause FreeBSD License", + "licenseId": "BSD-2-Clause-FreeBSD", + "seeAlso": [ + "http://www.freebsd.org/copyright/freebsd-license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MS-RL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-RL.json", + "referenceNumber": 23, + "name": "Microsoft Reciprocal License", + "licenseId": "MS-RL", + "seeAlso": [ + "http://www.microsoft.com/opensource/licenses.mspx", + "https://opensource.org/licenses/MS-RL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MIT-CMU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-CMU.json", + "referenceNumber": 24, + "name": "CMU License", + "licenseId": "MIT-CMU", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT?rd\u003dLicensing/MIT#CMU_Style", + "https://github.com/python-pillow/Pillow/blob/fffb426092c8db24a5f4b6df243a8a3c01fb63cd/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-OpenIB.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-OpenIB.json", + "referenceNumber": 25, + "name": "Linux Kernel Variant of OpenIB.org license", + "licenseId": "Linux-OpenIB", + "seeAlso": [ + "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/infiniband/core/sa.h" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Latex2e-translated-notice.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Latex2e-translated-notice.json", + "referenceNumber": 26, + "name": "Latex2e with translated notice permission", + "licenseId": "Latex2e-translated-notice", + "seeAlso": [ + "https://git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id\u003da74c6b4ee49397cf330b333da1042bffa60ed14f#n74" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/psutils.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/psutils.json", + "referenceNumber": 27, + "name": "psutils License", + "licenseId": "psutils", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/psutils" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Clips.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Clips.json", + "referenceNumber": 28, + "name": "Clips License", + "licenseId": "Clips", + "seeAlso": [ + "https://github.com/DrItanium/maya/blob/master/LICENSE.CLIPS" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-FR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-FR.json", + "referenceNumber": 29, + "name": "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France", + "licenseId": "CC-BY-NC-SA-2.0-FR", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/fr/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EUDatagrid.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUDatagrid.json", + "referenceNumber": 30, + "name": "EU DataGrid Software License", + "licenseId": "EUDatagrid", + "seeAlso": [ + "http://eu-datagrid.web.cern.ch/eu-datagrid/license.html", + "https://opensource.org/licenses/EUDatagrid" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-3.0.json", + "referenceNumber": 31, + "name": "Creative Commons Attribution No Derivatives 3.0 Unported", + "licenseId": "CC-BY-ND-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/3.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/w3m.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/w3m.json", + "referenceNumber": 32, + "name": "w3m License", + "licenseId": "w3m", + "seeAlso": [ + "https://github.com/tats/w3m/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.3.json", + "referenceNumber": 33, + "name": "Open LDAP Public License v2.3", + "licenseId": "OLDAP-2.3", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dd32cf54a32d581ab475d23c810b0a7fbaf8d63c3" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0-only.json", + "referenceNumber": 34, + "name": "GNU Affero General Public License v3.0 only", + "licenseId": "AGPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0-or-later.json", + "referenceNumber": 35, + "name": "Affero General Public License v1.0 or later", + "licenseId": "AGPL-1.0-or-later", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TMate.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TMate.json", + "referenceNumber": 36, + "name": "TMate Open Source License", + "licenseId": "TMate", + "seeAlso": [ + "http://svnkit.com/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-invariants-only.json", + "referenceNumber": 37, + "name": "GNU Free Documentation License v1.3 only - invariants", + "licenseId": "GFDL-1.3-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SchemeReport.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SchemeReport.json", + "referenceNumber": 38, + "name": "Scheme Language Report License", + "licenseId": "SchemeReport", + "seeAlso": [], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NAIST-2003.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NAIST-2003.json", + "referenceNumber": 39, + "name": "Nara Institute of Science and Technology License (2003)", + "licenseId": "NAIST-2003", + "seeAlso": [ + "https://enterprise.dejacode.com/licenses/public/naist-2003/#license-text", + "https://github.com/nodejs/node/blob/4a19cc8947b1bba2b2d27816ec3d0edf9b28e503/LICENSE#L343" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/eCos-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/eCos-2.0.json", + "referenceNumber": 40, + "name": "eCos license version 2.0", + "licenseId": "eCos-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/ecos-license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause-Shortened.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause-Shortened.json", + "referenceNumber": 41, + "name": "BSD 4 Clause Shortened", + "licenseId": "BSD-4-Clause-Shortened", + "seeAlso": [ + "https://metadata.ftp-master.debian.org/changelogs//main/a/arpwatch/arpwatch_2.1a15-7_copyright" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PDDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PDDL-1.0.json", + "referenceNumber": 42, + "name": "Open Data Commons Public Domain Dedication \u0026 License 1.0", + "licenseId": "PDDL-1.0", + "seeAlso": [ + "http://opendatacommons.org/licenses/pddl/1.0/", + "https://opendatacommons.org/licenses/pddl/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/XSkat.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/XSkat.json", + "referenceNumber": 43, + "name": "XSkat License", + "licenseId": "XSkat", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/XSkat_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-4.0.json", + "referenceNumber": 44, + "name": "Creative Commons Attribution No Derivatives 4.0 International", + "licenseId": "CC-BY-ND-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-LBNL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-LBNL.json", + "referenceNumber": 45, + "name": "Lawrence Berkeley National Labs BSD variant license", + "licenseId": "BSD-3-Clause-LBNL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/LBNLBSD" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/MIT-open-group.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-open-group.json", + "referenceNumber": 46, + "name": "MIT Open Group variant", + "licenseId": "MIT-open-group", + "seeAlso": [ + "https://gitlab.freedesktop.org/xorg/app/iceauth/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xvinfo/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xsetroot/-/blob/master/COPYING", + "https://gitlab.freedesktop.org/xorg/app/xauth/-/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UnixCrypt.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UnixCrypt.json", + "referenceNumber": 47, + "name": "UnixCrypt License", + "licenseId": "UnixCrypt", + "seeAlso": [ + "https://foss.heptapod.net/python-libs/passlib/-/blob/branch/stable/LICENSE#L70", + "https://opensource.apple.com/source/JBoss/JBoss-737/jboss-all/jetty/src/main/org/mortbay/util/UnixCrypt.java.auto.html", + "https://archive.eclipse.org/jetty/8.0.1.v20110908/xref/org/eclipse/jetty/http/security/UnixCrypt.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-P-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-P-1.1.json", + "referenceNumber": 48, + "name": "Licence Libre du Québec – Permissive version 1.1", + "licenseId": "LiLiQ-P-1.1", + "seeAlso": [ + "https://forge.gouv.qc.ca/licence/fr/liliq-v1-1/", + "http://opensource.org/licenses/LiLiQ-P-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Eurosym.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Eurosym.json", + "referenceNumber": 49, + "name": "Eurosym License", + "licenseId": "Eurosym", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Eurosym" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/JPNIC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JPNIC.json", + "referenceNumber": 50, + "name": "Japan Network Information Center License", + "licenseId": "JPNIC", + "seeAlso": [ + "https://gitlab.isc.org/isc-projects/bind9/blob/master/COPYRIGHT#L366" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPL-1.1.json", + "referenceNumber": 51, + "name": "Netscape Public License v1.1", + "licenseId": "NPL-1.1", + "seeAlso": [ + "http://www.mozilla.org/MPL/NPL/1.1/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NLOD-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLOD-2.0.json", + "referenceNumber": 52, + "name": "Norwegian Licence for Open Government Data (NLOD) 2.0", + "licenseId": "NLOD-2.0", + "seeAlso": [ + "http://data.norge.no/nlod/en/2.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPL-1.0.json", + "referenceNumber": 53, + "name": "Netscape Public License v1.0", + "licenseId": "NPL-1.0", + "seeAlso": [ + "http://www.mozilla.org/MPL/NPL/1.0/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.1-JP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.1-JP.json", + "referenceNumber": 54, + "name": "Creative Commons Attribution Share Alike 2.1 Japan", + "licenseId": "CC-BY-SA-2.1-JP", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0.json", + "referenceNumber": 55, + "name": "GNU General Public License v3.0 only", + "licenseId": "GPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPLLR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPLLR.json", + "referenceNumber": 56, + "name": "Lesser General Public License For Linguistic Resources", + "licenseId": "LGPLLR", + "seeAlso": [ + "http://www-igm.univ-mlv.fr/~unitex/lgpllr.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/wxWindows.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/wxWindows.json", + "referenceNumber": 57, + "name": "wxWindows Library License", + "licenseId": "wxWindows", + "seeAlso": [ + "https://opensource.org/licenses/WXwindows" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/PHP-3.01.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PHP-3.01.json", + "referenceNumber": 58, + "name": "PHP License v3.01", + "licenseId": "PHP-3.01", + "seeAlso": [ + "http://www.php.net/license/3_01.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1.json", + "referenceNumber": 59, + "name": "GNU Free Documentation License v1.1", + "licenseId": "GFDL-1.1", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Ruby.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Ruby.json", + "referenceNumber": 60, + "name": "Ruby License", + "licenseId": "Ruby", + "seeAlso": [ + "http://www.ruby-lang.org/en/LICENSE.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.0.json", + "referenceNumber": 61, + "name": "Creative Commons Attribution 2.0 Generic", + "licenseId": "CC-BY-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/QPL-1.0-INRIA-2004.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/QPL-1.0-INRIA-2004.json", + "referenceNumber": 62, + "name": "Q Public License 1.0 - INRIA 2004 variant", + "licenseId": "QPL-1.0-INRIA-2004", + "seeAlso": [ + "https://github.com/maranget/hevea/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Aladdin.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Aladdin.json", + "referenceNumber": 63, + "name": "Aladdin Free Public License", + "licenseId": "Aladdin", + "seeAlso": [ + "http://pages.cs.wisc.edu/~ghost/doc/AFPL/6.01/Public.htm" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/ODC-By-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ODC-By-1.0.json", + "referenceNumber": 64, + "name": "Open Data Commons Attribution License v1.0", + "licenseId": "ODC-By-1.0", + "seeAlso": [ + "https://opendatacommons.org/licenses/by/1.0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Intel-ACPI.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Intel-ACPI.json", + "referenceNumber": 65, + "name": "Intel ACPI Software License Agreement", + "licenseId": "Intel-ACPI", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Intel_ACPI_Software_License_Agreement" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1-or-later.json", + "referenceNumber": 66, + "name": "GNU Lesser General Public License v2.1 or later", + "licenseId": "LGPL-2.1-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MPL-2.0-no-copyleft-exception.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-2.0-no-copyleft-exception.json", + "referenceNumber": 67, + "name": "Mozilla Public License 2.0 (no copyleft exception)", + "licenseId": "MPL-2.0-no-copyleft-exception", + "seeAlso": [ + "https://www.mozilla.org/MPL/2.0/", + "https://opensource.org/licenses/MPL-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-bison-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-bison-exception.json", + "referenceNumber": 68, + "name": "GNU General Public License v2.0 w/Bison exception", + "licenseId": "GPL-2.0-with-bison-exception", + "seeAlso": [ + "http://git.savannah.gnu.org/cgit/bison.git/tree/data/yacc.c?id\u003d193d7c7054ba7197b0789e14965b739162319b5e#n141" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Parity-6.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Parity-6.0.0.json", + "referenceNumber": 69, + "name": "The Parity Public License 6.0.0", + "licenseId": "Parity-6.0.0", + "seeAlso": [ + "https://paritylicense.com/versions/6.0.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zlib.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zlib.json", + "referenceNumber": 70, + "name": "zlib License", + "licenseId": "Zlib", + "seeAlso": [ + "http://www.zlib.net/zlib_license.html", + "https://opensource.org/licenses/Zlib" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-1.0.json", + "referenceNumber": 71, + "name": "Creative Commons Attribution Share Alike 1.0 Generic", + "licenseId": "CC-BY-SA-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.0-UK.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.0-UK.json", + "referenceNumber": 72, + "name": "Creative Commons Attribution Share Alike 2.0 England and Wales", + "licenseId": "CC-BY-SA-2.0-UK", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.0/uk/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ADSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ADSL.json", + "referenceNumber": 73, + "name": "Amazon Digital Services License", + "licenseId": "ADSL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AmazonDigitalServicesLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Net-SNMP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Net-SNMP.json", + "referenceNumber": 74, + "name": "Net-SNMP License", + "licenseId": "Net-SNMP", + "seeAlso": [ + "http://net-snmp.sourceforge.net/about/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/YPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/YPL-1.0.json", + "referenceNumber": 75, + "name": "Yahoo! Public License v1.0", + "licenseId": "YPL-1.0", + "seeAlso": [ + "http://www.zimbra.com/license/yahoo_public_license_1.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Baekmuk.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Baekmuk.json", + "referenceNumber": 76, + "name": "Baekmuk License", + "licenseId": "Baekmuk", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:Baekmuk?rd\u003dLicensing/Baekmuk" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BitTorrent-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BitTorrent-1.1.json", + "referenceNumber": 77, + "name": "BitTorrent Open Source License v1.1", + "licenseId": "BitTorrent-1.1", + "seeAlso": [ + "http://directory.fsf.org/wiki/License:BitTorrentOSL1.1" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Arphic-1999.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Arphic-1999.json", + "referenceNumber": 78, + "name": "Arphic Public License", + "licenseId": "Arphic-1999", + "seeAlso": [ + "http://ftp.gnu.org/gnu/non-gnu/chinese-fonts-truetype/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-Warranty.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-Warranty.json", + "referenceNumber": 79, + "name": "BSD 3-Clause No Nuclear Warranty", + "licenseId": "BSD-3-Clause-No-Nuclear-Warranty", + "seeAlso": [ + "https://jogamp.org/git/?p\u003dgluegen.git;a\u003dblob_plain;f\u003dLICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MTLL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MTLL.json", + "referenceNumber": 80, + "name": "Matrix Template Library License", + "licenseId": "MTLL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Matrix_Template_Library_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/JPL-image.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JPL-image.json", + "referenceNumber": 81, + "name": "JPL Image Use Policy", + "licenseId": "JPL-image", + "seeAlso": [ + "https://www.jpl.nasa.gov/jpl-image-use-policy" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0+.json", + "referenceNumber": 82, + "name": "GNU Library General Public License v2 or later", + "licenseId": "LGPL-2.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ZPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-2.0.json", + "referenceNumber": 83, + "name": "Zope Public License 2.0", + "licenseId": "ZPL-2.0", + "seeAlso": [ + "http://old.zope.org/Resources/License/ZPL-2.0", + "https://opensource.org/licenses/ZPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/TCP-wrappers.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TCP-wrappers.json", + "referenceNumber": 84, + "name": "TCP Wrappers License", + "licenseId": "TCP-wrappers", + "seeAlso": [ + "http://rc.quest.com/topics/openssh/license.php#tcpwrappers" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0-or-later.json", + "referenceNumber": 85, + "name": "GNU General Public License v1.0 or later", + "licenseId": "GPL-1.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PSF-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PSF-2.0.json", + "referenceNumber": 86, + "name": "Python Software Foundation License 2.0", + "licenseId": "PSF-2.0", + "seeAlso": [ + "https://opensource.org/licenses/Python-2.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-font-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-font-exception.json", + "referenceNumber": 87, + "name": "GNU General Public License v2.0 w/Font exception", + "licenseId": "GPL-2.0-with-font-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-faq.html#FontException" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Nokia.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Nokia.json", + "referenceNumber": 88, + "name": "Nokia Open Source License", + "licenseId": "Nokia", + "seeAlso": [ + "https://opensource.org/licenses/nokia" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Entessa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Entessa.json", + "referenceNumber": 89, + "name": "Entessa Public License v1.0", + "licenseId": "Entessa", + "seeAlso": [ + "https://opensource.org/licenses/Entessa" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/AMPAS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AMPAS.json", + "referenceNumber": 90, + "name": "Academy of Motion Picture Arts and Sciences BSD", + "licenseId": "AMPAS", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD#AMPASBSD" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TU-Berlin-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TU-Berlin-1.0.json", + "referenceNumber": 91, + "name": "Technische Universitaet Berlin License 1.0", + "licenseId": "TU-Berlin-1.0", + "seeAlso": [ + "https://github.com/swh/ladspa/blob/7bf6f3799fdba70fda297c2d8fd9f526803d9680/gsm/COPYRIGHT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-Glyph.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-Glyph.json", + "referenceNumber": 92, + "name": "Adobe Glyph List License", + "licenseId": "Adobe-Glyph", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#AdobeGlyph" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DL-DE-BY-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DL-DE-BY-2.0.json", + "referenceNumber": 93, + "name": "Data licence Germany – attribution – version 2.0", + "licenseId": "DL-DE-BY-2.0", + "seeAlso": [ + "https://www.govdata.de/dl-de/by-2-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-1.0.json", + "referenceNumber": 94, + "name": "Mozilla Public License 1.0", + "licenseId": "MPL-1.0", + "seeAlso": [ + "http://www.mozilla.org/MPL/MPL-1.0.html", + "https://opensource.org/licenses/MPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Dotseqn.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Dotseqn.json", + "referenceNumber": 95, + "name": "Dotseqn License", + "licenseId": "Dotseqn", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Dotseqn" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IBM-pibs.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IBM-pibs.json", + "referenceNumber": 96, + "name": "IBM PowerPC Initialization and Boot Software", + "licenseId": "IBM-pibs", + "seeAlso": [ + "http://git.denx.de/?p\u003du-boot.git;a\u003dblob;f\u003darch/powerpc/cpu/ppc4xx/miiphy.c;h\u003d297155fdafa064b955e53e9832de93bfb0cfb85b;hb\u003d9fab4bf4cc077c21e43941866f3f2c196f28670d" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Plexus.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Plexus.json", + "referenceNumber": 97, + "name": "Plexus Classworlds License", + "licenseId": "Plexus", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Plexus_Classworlds_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HP-1986.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HP-1986.json", + "referenceNumber": 98, + "name": "Hewlett-Packard 1986 License", + "licenseId": "HP-1986", + "seeAlso": [ + "https://sourceware.org/git/?p\u003dnewlib-cygwin.git;a\u003dblob;f\u003dnewlib/libc/machine/hppa/memchr.S;h\u003d1cca3e5e8867aa4bffef1f75a5c1bba25c0c441e;hb\u003dHEAD#l2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.1.json", + "referenceNumber": 99, + "name": "LaTeX Project Public License v1.1", + "licenseId": "LPPL-1.1", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.2.json", + "referenceNumber": 100, + "name": "Open LDAP Public License v1.2", + "licenseId": "OLDAP-1.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d42b0383c50c299977b5893ee695cf4e486fb0dc7" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ZPL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-2.1.json", + "referenceNumber": 101, + "name": "Zope Public License 2.1", + "licenseId": "ZPL-2.1", + "seeAlso": [ + "http://old.zope.org/Resources/ZPL/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPL-1.0.json", + "referenceNumber": 102, + "name": "Lucent Public License Version 1.0", + "licenseId": "LPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/LPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/HPND-sell-variant-MIT-disclaimer.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-sell-variant-MIT-disclaimer.json", + "referenceNumber": 103, + "name": "HPND sell variant with MIT disclaimer", + "licenseId": "HPND-sell-variant-MIT-disclaimer", + "seeAlso": [ + "https://github.com/sigmavirus24/x11-ssh-askpass/blob/master/README" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFFIS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFFIS.json", + "referenceNumber": 104, + "name": "OFFIS License", + "licenseId": "OFFIS", + "seeAlso": [ + "https://sourceforge.net/p/xmedcon/code/ci/master/tree/libs/dicom/README" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CrystalStacker.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CrystalStacker.json", + "referenceNumber": 105, + "name": "CrystalStacker License", + "licenseId": "CrystalStacker", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:CrystalStacker?rd\u003dLicensing/CrystalStacker" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Adobe-2006.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Adobe-2006.json", + "referenceNumber": 106, + "name": "Adobe Systems Incorporated Source Code License Agreement", + "licenseId": "Adobe-2006", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AdobeLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zimbra-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zimbra-1.3.json", + "referenceNumber": 107, + "name": "Zimbra Public License v1.3", + "licenseId": "Zimbra-1.3", + "seeAlso": [ + "http://web.archive.org/web/20100302225219/http://www.zimbra.com/license/zimbra-public-license-1-3.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CUA-OPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CUA-OPL-1.0.json", + "referenceNumber": 108, + "name": "CUA Office Public License v1.0", + "licenseId": "CUA-OPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CUA-OPL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.1.json", + "referenceNumber": 109, + "name": "European Union Public License 1.1", + "licenseId": "EUPL-1.1", + "seeAlso": [ + "https://joinup.ec.europa.eu/software/page/eupl/licence-eupl", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl1.1.-licence-en_0.pdf", + "https://opensource.org/licenses/EUPL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/IJG.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IJG.json", + "referenceNumber": 110, + "name": "Independent JPEG Group License", + "licenseId": "IJG", + "seeAlso": [ + "http://dev.w3.org/cvsweb/Amaya/libjpeg/Attic/README?rev\u003d1.2" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Afmparse.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Afmparse.json", + "referenceNumber": 111, + "name": "Afmparse License", + "licenseId": "Afmparse", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Afmparse" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PolyForm-Noncommercial-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PolyForm-Noncommercial-1.0.0.json", + "referenceNumber": 112, + "name": "PolyForm Noncommercial License 1.0.0", + "licenseId": "PolyForm-Noncommercial-1.0.0", + "seeAlso": [ + "https://polyformproject.org/licenses/noncommercial/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NCGL-UK-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NCGL-UK-2.0.json", + "referenceNumber": 113, + "name": "Non-Commercial Government Licence", + "licenseId": "NCGL-UK-2.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/non-commercial-government-licence/version/2/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPL-2.0.json", + "referenceNumber": 114, + "name": "Eclipse Public License 2.0", + "licenseId": "EPL-2.0", + "seeAlso": [ + "https://www.eclipse.org/legal/epl-2.0", + "https://www.opensource.org/licenses/EPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-1.0.json", + "referenceNumber": 115, + "name": "Creative Commons Attribution No Derivatives 1.0 Generic", + "licenseId": "CC-BY-ND-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/1.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-2.0.json", + "referenceNumber": 116, + "name": "Creative Commons Attribution No Derivatives 2.0 Generic", + "licenseId": "CC-BY-ND-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/2.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1-no-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1-no-RFN.json", + "referenceNumber": 117, + "name": "SIL Open Font License 1.1 with no Reserved Font Name", + "licenseId": "OFL-1.1-no-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/HPND-Markus-Kuhn.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-Markus-Kuhn.json", + "referenceNumber": 118, + "name": "Historical Permission Notice and Disclaimer - Markus Kuhn variant", + "licenseId": "HPND-Markus-Kuhn", + "seeAlso": [ + "https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c", + "https://sourceware.org/git/?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dreadline/readline/support/wcwidth.c;h\u003d0f5ec995796f4813abbcf4972aec0378ab74722a;hb\u003dHEAD#l55" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xnet.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xnet.json", + "referenceNumber": 119, + "name": "X.Net License", + "licenseId": "Xnet", + "seeAlso": [ + "https://opensource.org/licenses/Xnet" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CNRI-Python.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Python.json", + "referenceNumber": 120, + "name": "CNRI Python License", + "licenseId": "CNRI-Python", + "seeAlso": [ + "https://opensource.org/licenses/CNRI-Python" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Zimbra-1.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zimbra-1.4.json", + "referenceNumber": 121, + "name": "Zimbra Public License v1.4", + "licenseId": "Zimbra-1.4", + "seeAlso": [ + "http://www.zimbra.com/legal/zimbra-public-license-1-4" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-1.0.json", + "referenceNumber": 122, + "name": "SGI Free Software License B v1.0", + "licenseId": "SGI-B-1.0", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/SGIFreeSWLicB.1.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MakeIndex.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MakeIndex.json", + "referenceNumber": 123, + "name": "MakeIndex License", + "licenseId": "MakeIndex", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MakeIndex" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-no-invariants-only.json", + "referenceNumber": 124, + "name": "GNU Free Documentation License v1.1 only - no invariants", + "licenseId": "GFDL-1.1-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TCL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TCL.json", + "referenceNumber": 125, + "name": "TCL/TK License", + "licenseId": "TCL", + "seeAlso": [ + "http://www.tcl.tk/software/tcltk/license.html", + "https://fedoraproject.org/wiki/Licensing/TCL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Protection.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Protection.json", + "referenceNumber": 126, + "name": "BSD Protection License", + "licenseId": "BSD-Protection", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD_Protection_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xdebug-1.03.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xdebug-1.03.json", + "referenceNumber": 127, + "name": "Xdebug License v 1.03", + "licenseId": "Xdebug-1.03", + "seeAlso": [ + "https://github.com/xdebug/xdebug/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.5-AU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.5-AU.json", + "referenceNumber": 128, + "name": "Creative Commons Attribution 2.5 Australia", + "licenseId": "CC-BY-2.5-AU", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.5/au/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-C.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-C.json", + "referenceNumber": 129, + "name": "CeCILL-C Free Software License Agreement", + "licenseId": "CECILL-C", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-4.3RENO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4.3RENO.json", + "referenceNumber": 130, + "name": "BSD 4.3 RENO License", + "licenseId": "BSD-4.3RENO", + "seeAlso": [ + "https://sourceware.org/git/?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dlibiberty/strcasecmp.c;h\u003d131d81c2ce7881fa48c363dc5bf5fb302c61ce0b;hb\u003dHEAD" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Brian-Gladman-3-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Brian-Gladman-3-Clause.json", + "referenceNumber": 131, + "name": "Brian Gladman 3-Clause License", + "licenseId": "Brian-Gladman-3-Clause", + "seeAlso": [ + "https://github.com/SWI-Prolog/packages-clib/blob/master/sha1/brg_endian.h" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-1.0.json", + "referenceNumber": 132, + "name": "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic", + "licenseId": "CC-BY-NC-SA-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGTSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGTSL.json", + "referenceNumber": 133, + "name": "Open Group Test Suite License", + "licenseId": "OGTSL", + "seeAlso": [ + "http://www.opengroup.org/testing/downloads/The_Open_Group_TSL.txt", + "https://opensource.org/licenses/OGTSL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.7.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.7.json", + "referenceNumber": 134, + "name": "Open LDAP Public License v2.7", + "licenseId": "OLDAP-2.7", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d47c2415c1df81556eeb39be6cad458ef87c534a2" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/HaskellReport.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HaskellReport.json", + "referenceNumber": 135, + "name": "Haskell Language Report License", + "licenseId": "HaskellReport", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Haskell_Language_Report_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RPL-1.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPL-1.5.json", + "referenceNumber": 136, + "name": "Reciprocal Public License 1.5", + "licenseId": "RPL-1.5", + "seeAlso": [ + "https://opensource.org/licenses/RPL-1.5" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Unlicense.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unlicense.json", + "referenceNumber": 137, + "name": "The Unlicense", + "licenseId": "Unlicense", + "seeAlso": [ + "https://unlicense.org/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/XFree86-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/XFree86-1.1.json", + "referenceNumber": 138, + "name": "XFree86 License 1.1", + "licenseId": "XFree86-1.1", + "seeAlso": [ + "http://www.xfree86.org/current/LICENSE4.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0.json", + "referenceNumber": 139, + "name": "Creative Commons Attribution Share Alike 3.0 Unported", + "licenseId": "CC-BY-SA-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.2.json", + "referenceNumber": 140, + "name": "Open LDAP Public License 2.2.2", + "licenseId": "OLDAP-2.2.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003ddf2cc1e21eb7c160695f5b7cffd6296c151ba188" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-IGO.json", + "referenceNumber": 141, + "name": "Creative Commons Attribution 3.0 IGO", + "licenseId": "CC-BY-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/snprintf.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/snprintf.json", + "referenceNumber": 142, + "name": "snprintf License", + "licenseId": "snprintf", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/master/openbsd-compat/bsd-snprintf.c#L2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-2.0.json", + "referenceNumber": 143, + "name": "Creative Commons Attribution Non Commercial 2.0 Generic", + "licenseId": "CC-BY-NC-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/2.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/dtoa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/dtoa.json", + "referenceNumber": 144, + "name": "David M. Gay dtoa License", + "licenseId": "dtoa", + "seeAlso": [ + "https://github.com/SWI-Prolog/swipl-devel/blob/master/src/os/dtoa.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MITNFA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MITNFA.json", + "referenceNumber": 145, + "name": "MIT +no-false-attribs license", + "licenseId": "MITNFA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MITNFA" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0+.json", + "referenceNumber": 146, + "name": "GNU General Public License v3.0 or later", + "licenseId": "GPL-3.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AML.json", + "referenceNumber": 147, + "name": "Apple MIT License", + "licenseId": "AML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Apple_MIT_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Artistic-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-2.0.json", + "referenceNumber": 148, + "name": "Artistic License 2.0", + "licenseId": "Artistic-2.0", + "seeAlso": [ + "http://www.perlfoundation.org/artistic_license_2_0", + "https://www.perlfoundation.org/artistic-license-20.html", + "https://opensource.org/licenses/artistic-license-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CECILL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-2.0.json", + "referenceNumber": 149, + "name": "CeCILL Free Software License Agreement v2.0", + "licenseId": "CECILL-2.0", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V2-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/App-s2p.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/App-s2p.json", + "referenceNumber": 150, + "name": "App::s2p License", + "licenseId": "App-s2p", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/App-s2p" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSH-short.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSH-short.json", + "referenceNumber": 151, + "name": "SSH short notice", + "licenseId": "SSH-short", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/pathnames.h", + "http://web.mit.edu/kolya/.f/root/athena.mit.edu/sipb.mit.edu/project/openssh/OldFiles/src/openssh-2.9.9p2/ssh-add.1", + "https://joinup.ec.europa.eu/svn/lesoll/trunk/italc/lib/src/dsa_key.cpp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AAL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AAL.json", + "referenceNumber": 152, + "name": "Attribution Assurance License", + "licenseId": "AAL", + "seeAlso": [ + "https://opensource.org/licenses/attribution" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/VSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/VSL-1.0.json", + "referenceNumber": 153, + "name": "Vovida Software License v1.0", + "licenseId": "VSL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/VSL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/AFL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-2.0.json", + "referenceNumber": 154, + "name": "Academic Free License v2.0", + "licenseId": "AFL-2.0", + "seeAlso": [ + "http://wayback.archive.org/web/20060924134533/http://www.opensource.org/licenses/afl-2.0.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OML.json", + "referenceNumber": 155, + "name": "Open Market License", + "licenseId": "OML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Open_Market_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-US.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-US.json", + "referenceNumber": 156, + "name": "Creative Commons Attribution 3.0 United States", + "licenseId": "CC-BY-3.0-US", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/us/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/mplus.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mplus.json", + "referenceNumber": 157, + "name": "mplus Font License", + "licenseId": "mplus", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:Mplus?rd\u003dLicensing/mplus" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Qhull.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Qhull.json", + "referenceNumber": 158, + "name": "Qhull License", + "licenseId": "Qhull", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Qhull" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FDK-AAC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FDK-AAC.json", + "referenceNumber": 159, + "name": "Fraunhofer FDK AAC Codec Library", + "licenseId": "FDK-AAC", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FDK-AAC", + "https://directory.fsf.org/wiki/License:Fdk" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause-UC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause-UC.json", + "referenceNumber": 160, + "name": "BSD-4-Clause (University of California-Specific)", + "licenseId": "BSD-4-Clause-UC", + "seeAlso": [ + "http://www.freebsd.org/copyright/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PolyForm-Small-Business-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PolyForm-Small-Business-1.0.0.json", + "referenceNumber": 161, + "name": "PolyForm Small Business License 1.0.0", + "licenseId": "PolyForm-Small-Business-1.0.0", + "seeAlso": [ + "https://polyformproject.org/licenses/small-business/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Sleepycat.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sleepycat.json", + "referenceNumber": 162, + "name": "Sleepycat License", + "licenseId": "Sleepycat", + "seeAlso": [ + "https://opensource.org/licenses/Sleepycat" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.json", + "referenceNumber": 163, + "name": "Open LDAP Public License v2.2", + "licenseId": "OLDAP-2.2", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d470b0c18ec67621c85881b2733057fecf4a1acc3" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-99.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-99.json", + "referenceNumber": 164, + "name": "Spencer License 99", + "licenseId": "Spencer-99", + "seeAlso": [ + "http://www.opensource.apple.com/source/tcl/tcl-5/tcl/generic/regfronts.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-1.0.json", + "referenceNumber": 165, + "name": "Open Government Licence v1.0", + "licenseId": "OGL-UK-1.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/1/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.2.json", + "referenceNumber": 166, + "name": "European Union Public License 1.2", + "licenseId": "EUPL-1.2", + "seeAlso": [ + "https://joinup.ec.europa.eu/page/eupl-text-11-12", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl_v1.2_en.pdf", + "https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/2020-03/EUPL-1.2%20EN.txt", + "https://joinup.ec.europa.eu/sites/default/files/inline-files/EUPL%20v1_2%20EN(1).txt", + "http://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri\u003dCELEX:32017D0863", + "https://opensource.org/licenses/EUPL-1.2" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-copyleft-2-para.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-copyleft-2-para.json", + "referenceNumber": 167, + "name": "Linux man-pages Copyleft - 2 paragraphs", + "licenseId": "Linux-man-pages-copyleft-2-para", + "seeAlso": [ + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/move_pages.2#n5", + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/migrate_pages.2#n8" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FreeBSD-DOC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FreeBSD-DOC.json", + "referenceNumber": 168, + "name": "FreeBSD Documentation License", + "licenseId": "FreeBSD-DOC", + "seeAlso": [ + "https://www.freebsd.org/copyright/freebsd-doc-license/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RSCPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RSCPL.json", + "referenceNumber": 169, + "name": "Ricoh Source Code Public License", + "licenseId": "RSCPL", + "seeAlso": [ + "http://wayback.archive.org/web/20060715140826/http://www.risource.org/RPL/RPL-1.0A.shtml", + "https://opensource.org/licenses/RSCPL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/eGenix.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/eGenix.json", + "referenceNumber": 170, + "name": "eGenix.com Public License 1.1.0", + "licenseId": "eGenix", + "seeAlso": [ + "http://www.egenix.com/products/eGenix.com-Public-License-1.1.0.pdf", + "https://fedoraproject.org/wiki/Licensing/eGenix.com_Public_License_1.1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-2.5.json", + "referenceNumber": 171, + "name": "Creative Commons Attribution 2.5 Generic", + "licenseId": "CC-BY-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND.json", + "referenceNumber": 172, + "name": "Historical Permission Notice and Disclaimer", + "licenseId": "HPND", + "seeAlso": [ + "https://opensource.org/licenses/HPND" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SCEA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SCEA.json", + "referenceNumber": 173, + "name": "SCEA Shared Source License", + "licenseId": "SCEA", + "seeAlso": [ + "http://research.scea.com/scea_shared_source_license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-only.json", + "referenceNumber": 174, + "name": "GNU General Public License v3.0 only", + "licenseId": "GPL-3.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Bitstream-Charter.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Bitstream-Charter.json", + "referenceNumber": 175, + "name": "Bitstream Charter Font License", + "licenseId": "Bitstream-Charter", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Charter#License_Text", + "https://raw.githubusercontent.com/blackhole89/notekit/master/data/fonts/Charter%20license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LAL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LAL-1.2.json", + "referenceNumber": 176, + "name": "Licence Art Libre 1.2", + "licenseId": "LAL-1.2", + "seeAlso": [ + "http://artlibre.org/licence/lal/licence-art-libre-12/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.0.json", + "referenceNumber": 177, + "name": "Apple Public Source License 1.0", + "licenseId": "APSL-1.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Apple_Public_Source_License_1.0" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/Caldera.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Caldera.json", + "referenceNumber": 178, + "name": "Caldera License", + "licenseId": "Caldera", + "seeAlso": [ + "http://www.lemis.com/grog/UNIX/ancient-source-all.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xerox.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xerox.json", + "referenceNumber": 179, + "name": "Xerox License", + "licenseId": "Xerox", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Xerox" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SWL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SWL.json", + "referenceNumber": 180, + "name": "Scheme Widget Library (SWL) Software License Agreement", + "licenseId": "SWL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/SWL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MS-LPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-LPL.json", + "referenceNumber": 181, + "name": "Microsoft Limited Public License", + "licenseId": "MS-LPL", + "seeAlso": [ + "https://www.openhub.net/licenses/mslpl", + "https://github.com/gabegundy/atlserver/blob/master/License.txt", + "https://en.wikipedia.org/wiki/Shared_Source_Initiative#Microsoft_Limited_Public_License_(Ms-LPL)" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Cube.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Cube.json", + "referenceNumber": 182, + "name": "Cube License", + "licenseId": "Cube", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Cube" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-2.0.json", + "referenceNumber": 183, + "name": "Apple Public Source License 2.0", + "licenseId": "APSL-2.0", + "seeAlso": [ + "http://www.opensource.apple.com/license/apsl/" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/APAFML.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APAFML.json", + "referenceNumber": 184, + "name": "Adobe Postscript AFM License", + "licenseId": "APAFML", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AdobePostscriptAFM" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Watcom-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Watcom-1.0.json", + "referenceNumber": 185, + "name": "Sybase Open Watcom Public License 1.0", + "licenseId": "Watcom-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Watcom-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/SISSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SISSL.json", + "referenceNumber": 186, + "name": "Sun Industry Standards Source License v1.1", + "licenseId": "SISSL", + "seeAlso": [ + "http://www.openoffice.org/licenses/sissl_license.html", + "https://opensource.org/licenses/SISSL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CDDL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDDL-1.0.json", + "referenceNumber": 187, + "name": "Common Development and Distribution License 1.0", + "licenseId": "CDDL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/cddl1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2.json", + "referenceNumber": 188, + "name": "GNU Free Documentation License v1.2", + "licenseId": "GFDL-1.2", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0-AT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0-AT.json", + "referenceNumber": 189, + "name": "Creative Commons Attribution Share Alike 3.0 Austria", + "licenseId": "CC-BY-SA-3.0-AT", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/at/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/psfrag.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/psfrag.json", + "referenceNumber": 190, + "name": "psfrag License", + "licenseId": "psfrag", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/psfrag" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/C-UDA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/C-UDA-1.0.json", + "referenceNumber": 191, + "name": "Computational Use of Data Agreement v1.0", + "licenseId": "C-UDA-1.0", + "seeAlso": [ + "https://github.com/microsoft/Computational-Use-of-Data-Agreement/blob/master/C-UDA-1.0.md", + "https://cdla.dev/computational-use-of-data-agreement-v1-0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-1.1.json", + "referenceNumber": 192, + "name": "Mozilla Public License 1.1", + "licenseId": "MPL-1.1", + "seeAlso": [ + "http://www.mozilla.org/MPL/MPL-1.1.html", + "https://opensource.org/licenses/MPL-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-NL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-NL.json", + "referenceNumber": 193, + "name": "Creative Commons Attribution 3.0 Netherlands", + "licenseId": "CC-BY-3.0-NL", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/nl/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-invariants-only.json", + "referenceNumber": 194, + "name": "GNU Free Documentation License v1.2 only - invariants", + "licenseId": "GFDL-1.2-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Attribution.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Attribution.json", + "referenceNumber": 195, + "name": "BSD with attribution", + "licenseId": "BSD-3-Clause-Attribution", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/BSD_with_Attribution" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-3.0-DE.json", + "referenceNumber": 196, + "name": "Creative Commons Attribution Non Commercial 3.0 Germany", + "licenseId": "CC-BY-NC-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-or-later.json", + "referenceNumber": 197, + "name": "GNU Free Documentation License v1.1 or later", + "licenseId": "GFDL-1.1-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1+.json", + "referenceNumber": 198, + "name": "GNU Lesser General Public License v2.1 or later", + "licenseId": "LGPL-2.1+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NCSA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NCSA.json", + "referenceNumber": 199, + "name": "University of Illinois/NCSA Open Source License", + "licenseId": "NCSA", + "seeAlso": [ + "http://otm.illinois.edu/uiuc_openSource", + "https://opensource.org/licenses/NCSA" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-1-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-1-Clause.json", + "referenceNumber": 200, + "name": "BSD 1-Clause License", + "licenseId": "BSD-1-Clause", + "seeAlso": [ + "https://svnweb.freebsd.org/base/head/include/ifaddrs.h?revision\u003d326823" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ANTLR-PD-fallback.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ANTLR-PD-fallback.json", + "referenceNumber": 201, + "name": "ANTLR Software Rights Notice with license fallback", + "licenseId": "ANTLR-PD-fallback", + "seeAlso": [ + "http://www.antlr2.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Modification.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Modification.json", + "referenceNumber": 202, + "name": "BSD 3-Clause Modification", + "licenseId": "BSD-3-Clause-Modification", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:BSD#Modification_Variant" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/COIL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/COIL-1.0.json", + "referenceNumber": 203, + "name": "Copyfree Open Innovation License", + "licenseId": "COIL-1.0", + "seeAlso": [ + "https://coil.apotheon.org/plaintext/01.0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UPL-1.0.json", + "referenceNumber": 204, + "name": "Universal Permissive License v1.0", + "licenseId": "UPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/UPL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-1.0.json", + "referenceNumber": 205, + "name": "Creative Commons Attribution 1.0 Generic", + "licenseId": "CC-BY-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Leptonica.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Leptonica.json", + "referenceNumber": 206, + "name": "Leptonica License", + "licenseId": "Leptonica", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Leptonica" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Frameworx-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Frameworx-1.0.json", + "referenceNumber": 207, + "name": "Frameworx Open License 1.0", + "licenseId": "Frameworx-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Frameworx-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.1.json", + "referenceNumber": 208, + "name": "Open LDAP Public License v1.1", + "licenseId": "OLDAP-1.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d806557a5ad59804ef3a44d5abfbe91d706b0791f" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FTL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FTL.json", + "referenceNumber": 209, + "name": "Freetype Project License", + "licenseId": "FTL", + "seeAlso": [ + "http://freetype.fis.uniroma2.it/FTL.TXT", + "http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT", + "http://gitlab.freedesktop.org/freetype/freetype/-/raw/master/docs/FTL.TXT" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0-cl8.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0-cl8.json", + "referenceNumber": 210, + "name": "Artistic License 1.0 w/clause 8", + "licenseId": "Artistic-1.0-cl8", + "seeAlso": [ + "https://opensource.org/licenses/Artistic-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-IGO.json", + "referenceNumber": 211, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO", + "licenseId": "CC-BY-NC-ND-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0.json", + "referenceNumber": 212, + "name": "GNU General Public License v1.0 only", + "licenseId": "GPL-1.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0-IGO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0-IGO.json", + "referenceNumber": 213, + "name": "Creative Commons Attribution-ShareAlike 3.0 IGO", + "licenseId": "CC-BY-SA-3.0-IGO", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/igo/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-Canada-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-Canada-2.0.json", + "referenceNumber": 214, + "name": "Open Government Licence - Canada", + "licenseId": "OGL-Canada-2.0", + "seeAlso": [ + "https://open.canada.ca/en/open-government-licence-canada" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/YPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/YPL-1.1.json", + "referenceNumber": 215, + "name": "Yahoo! Public License v1.1", + "licenseId": "YPL-1.1", + "seeAlso": [ + "http://www.zimbra.com/license/yahoo_public_license_1.1.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-3.0.json", + "referenceNumber": 216, + "name": "Creative Commons Attribution Non Commercial 3.0 Unported", + "licenseId": "CC-BY-NC-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/3.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0-or-later.json", + "referenceNumber": 217, + "name": "GNU Affero General Public License v3.0 or later", + "licenseId": "AGPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/SSPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSPL-1.0.json", + "referenceNumber": 218, + "name": "Server Side Public License, v 1", + "licenseId": "SSPL-1.0", + "seeAlso": [ + "https://www.mongodb.com/licensing/server-side-public-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0+.json", + "referenceNumber": 219, + "name": "GNU General Public License v1.0 or later", + "licenseId": "GPL-1.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IPL-1.0.json", + "referenceNumber": 220, + "name": "IBM Public License v1.0", + "licenseId": "IPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/IPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/TPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TPL-1.0.json", + "referenceNumber": 221, + "name": "THOR Public License 1.0", + "licenseId": "TPL-1.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:ThorPublicLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Knuth-CTAN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Knuth-CTAN.json", + "referenceNumber": 222, + "name": "Knuth CTAN License", + "licenseId": "Knuth-CTAN", + "seeAlso": [ + "https://ctan.org/license/knuth" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT.json", + "referenceNumber": 223, + "name": "MIT License", + "licenseId": "MIT", + "seeAlso": [ + "https://opensource.org/licenses/MIT" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Rdisc.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Rdisc.json", + "referenceNumber": 224, + "name": "Rdisc License", + "licenseId": "Rdisc", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Rdisc_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Abstyles.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Abstyles.json", + "referenceNumber": 225, + "name": "Abstyles License", + "licenseId": "Abstyles", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Abstyles" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-2.1.json", + "referenceNumber": 226, + "name": "CeCILL Free Software License Agreement v2.1", + "licenseId": "CECILL-2.1", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/libtiff.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libtiff.json", + "referenceNumber": 227, + "name": "libtiff License", + "licenseId": "libtiff", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/libtiff" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ErlPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ErlPL-1.1.json", + "referenceNumber": 228, + "name": "Erlang Public License v1.1", + "licenseId": "ErlPL-1.1", + "seeAlso": [ + "http://www.erlang.org/EPLICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Kazlib.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Kazlib.json", + "referenceNumber": 229, + "name": "Kazlib License", + "licenseId": "Kazlib", + "seeAlso": [ + "http://git.savannah.gnu.org/cgit/kazlib.git/tree/except.c?id\u003d0062df360c2d17d57f6af19b0e444c51feb99036" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RPSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RPSL-1.0.json", + "referenceNumber": 230, + "name": "RealNetworks Public Source License v1.0", + "licenseId": "RPSL-1.0", + "seeAlso": [ + "https://helixcommunity.org/content/rpsl", + "https://opensource.org/licenses/RPSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0+.json", + "referenceNumber": 231, + "name": "GNU Lesser General Public License v3.0 or later", + "licenseId": "LGPL-3.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/AGPL-3.0.json", + "referenceNumber": 232, + "name": "GNU Affero General Public License v3.0", + "licenseId": "AGPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/agpl.txt", + "https://opensource.org/licenses/AGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Clear.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Clear.json", + "referenceNumber": 233, + "name": "BSD 3-Clause Clear License", + "licenseId": "BSD-3-Clause-Clear", + "seeAlso": [ + "http://labs.metacarta.com/license-explanation.html#license" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/MIT-feh.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-feh.json", + "referenceNumber": 234, + "name": "feh License", + "licenseId": "MIT-feh", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#feh" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-1.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-1.0-only.json", + "referenceNumber": 235, + "name": "GNU General Public License v1.0 only", + "licenseId": "GPL-1.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MPL-2.0.json", + "referenceNumber": 236, + "name": "Mozilla Public License 2.0", + "licenseId": "MPL-2.0", + "seeAlso": [ + "https://www.mozilla.org/MPL/2.0/", + "https://opensource.org/licenses/MPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.3c.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.3c.json", + "referenceNumber": 237, + "name": "LaTeX Project Public License v1.3c", + "licenseId": "LPPL-1.3c", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-3c.txt", + "https://opensource.org/licenses/LPPL-1.3c" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CDLA-Permissive-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Permissive-1.0.json", + "referenceNumber": 238, + "name": "Community Data License Agreement Permissive 1.0", + "licenseId": "CDLA-Permissive-1.0", + "seeAlso": [ + "https://cdla.io/permissive-1-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Xfig.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Xfig.json", + "referenceNumber": 239, + "name": "Xfig License", + "licenseId": "Xfig", + "seeAlso": [ + "https://github.com/Distrotech/transfig/blob/master/transfig/transfig.c", + "https://fedoraproject.org/wiki/Licensing:MIT#Xfig_Variant", + "https://sourceforge.net/p/mcj/xfig/ci/master/tree/src/Makefile.am" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-PDDC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-PDDC.json", + "referenceNumber": 240, + "name": "Creative Commons Public Domain Dedication and Certification", + "licenseId": "CC-PDDC", + "seeAlso": [ + "https://creativecommons.org/licenses/publicdomain/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Inner-Net-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Inner-Net-2.0.json", + "referenceNumber": 241, + "name": "Inner Net License v2.0", + "licenseId": "Inner-Net-2.0", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Inner_Net_License", + "https://sourceware.org/git/?p\u003dglibc.git;a\u003dblob;f\u003dLICENSES;h\u003d530893b1dc9ea00755603c68fb36bd4fc38a7be8;hb\u003dHEAD#l207" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ECL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ECL-1.0.json", + "referenceNumber": 242, + "name": "Educational Community License v1.0", + "licenseId": "ECL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/ECL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/SMLNJ.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SMLNJ.json", + "referenceNumber": 243, + "name": "Standard ML of New Jersey License", + "licenseId": "SMLNJ", + "seeAlso": [ + "https://www.smlnj.org/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-only.json", + "referenceNumber": 244, + "name": "GNU Free Documentation License v1.2 only", + "licenseId": "GFDL-1.2-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/bzip2-1.0.5.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/bzip2-1.0.5.json", + "referenceNumber": 245, + "name": "bzip2 and libbzip2 License v1.0.5", + "licenseId": "bzip2-1.0.5", + "seeAlso": [ + "https://sourceware.org/bzip2/1.0.5/bzip2-manual-1.0.5.html", + "http://bzip.org/1.0.5/bzip2-manual-1.0.5.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ECL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ECL-2.0.json", + "referenceNumber": 246, + "name": "Educational Community License v2.0", + "licenseId": "ECL-2.0", + "seeAlso": [ + "https://opensource.org/licenses/ECL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Multics.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Multics.json", + "referenceNumber": 247, + "name": "Multics License", + "licenseId": "Multics", + "seeAlso": [ + "https://opensource.org/licenses/Multics" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-4.0.json", + "referenceNumber": 248, + "name": "Creative Commons Attribution Non Commercial 4.0 International", + "licenseId": "CC-BY-NC-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-no-invariants-only.json", + "referenceNumber": 249, + "name": "GNU Free Documentation License v1.3 only - no invariants", + "licenseId": "GFDL-1.3-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0-RFN.json", + "referenceNumber": 250, + "name": "SIL Open Font License 1.0 with Reserved Font Name", + "licenseId": "OFL-1.0-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-2.1.json", + "referenceNumber": 251, + "name": "Open Software License 2.1", + "licenseId": "OSL-2.1", + "seeAlso": [ + "http://web.archive.org/web/20050212003940/http://www.rosenlaw.com/osl21.htm", + "https://opensource.org/licenses/OSL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.0.json", + "referenceNumber": 252, + "name": "Creative Commons Attribution Share Alike 2.0 Generic", + "licenseId": "CC-BY-SA-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CATOSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CATOSL-1.1.json", + "referenceNumber": 253, + "name": "Computer Associates Trusted Open Source License 1.1", + "licenseId": "CATOSL-1.1", + "seeAlso": [ + "https://opensource.org/licenses/CATOSL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ICU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ICU.json", + "referenceNumber": 254, + "name": "ICU License", + "licenseId": "ICU", + "seeAlso": [ + "http://source.icu-project.org/repos/icu/icu/trunk/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BUSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BUSL-1.1.json", + "referenceNumber": 255, + "name": "Business Source License 1.1", + "licenseId": "BUSL-1.1", + "seeAlso": [ + "https://mariadb.com/bsl11/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0.json", + "referenceNumber": 256, + "name": "Affero General Public License v1.0", + "licenseId": "AGPL-1.0", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OPL-UK-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OPL-UK-3.0.json", + "referenceNumber": 257, + "name": "United Kingdom Open Parliament Licence v3.0", + "licenseId": "OPL-UK-3.0", + "seeAlso": [ + "https://www.parliament.uk/site-information/copyright-parliament/open-parliament-licence/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/copyleft-next-0.3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/copyleft-next-0.3.0.json", + "referenceNumber": 258, + "name": "copyleft-next 0.3.0", + "licenseId": "copyleft-next-0.3.0", + "seeAlso": [ + "https://github.com/copyleft-next/copyleft-next/blob/master/Releases/copyleft-next-0.3.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Symlinks.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Symlinks.json", + "referenceNumber": 259, + "name": "Symlinks License", + "licenseId": "Symlinks", + "seeAlso": [ + "https://www.mail-archive.com/debian-bugs-rc@lists.debian.org/msg11494.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Crossword.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Crossword.json", + "referenceNumber": 260, + "name": "Crossword License", + "licenseId": "Crossword", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Crossword" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-classpath-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-classpath-exception.json", + "referenceNumber": 261, + "name": "GNU General Public License v2.0 w/Classpath exception", + "licenseId": "GPL-2.0-with-classpath-exception", + "seeAlso": [ + "https://www.gnu.org/software/classpath/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DOC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DOC.json", + "referenceNumber": 262, + "name": "DOC License", + "licenseId": "DOC", + "seeAlso": [ + "http://www.cs.wustl.edu/~schmidt/ACE-copying.html", + "https://www.dre.vanderbilt.edu/~schmidt/ACE-copying.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ISC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ISC.json", + "referenceNumber": 263, + "name": "ISC License", + "licenseId": "ISC", + "seeAlso": [ + "https://www.isc.org/licenses/", + "https://www.isc.org/downloads/software-support-policy/isc-license/", + "https://opensource.org/licenses/ISC" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Apache-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-2.0.json", + "referenceNumber": 264, + "name": "Apache License 2.0", + "licenseId": "Apache-2.0", + "seeAlso": [ + "https://www.apache.org/licenses/LICENSE-2.0", + "https://opensource.org/licenses/Apache-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/copyleft-next-0.3.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/copyleft-next-0.3.1.json", + "referenceNumber": 265, + "name": "copyleft-next 0.3.1", + "licenseId": "copyleft-next-0.3.1", + "seeAlso": [ + "https://github.com/copyleft-next/copyleft-next/blob/master/Releases/copyleft-next-0.3.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ASWF-Digital-Assets-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ASWF-Digital-Assets-1.1.json", + "referenceNumber": 266, + "name": "ASWF Digital Assets License 1.1", + "licenseId": "ASWF-Digital-Assets-1.1", + "seeAlso": [ + "https://github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SISSL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SISSL-1.2.json", + "referenceNumber": 267, + "name": "Sun Industry Standards Source License v1.2", + "licenseId": "SISSL-1.2", + "seeAlso": [ + "http://gridscheduler.sourceforge.net/Gridengine_SISSL_license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Unicode-TOU.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-TOU.json", + "referenceNumber": 268, + "name": "Unicode Terms of Use", + "licenseId": "Unicode-TOU", + "seeAlso": [ + "http://web.archive.org/web/20140704074106/http://www.unicode.org/copyright.html", + "http://www.unicode.org/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause.json", + "referenceNumber": 269, + "name": "BSD 2-Clause \"Simplified\" License", + "licenseId": "BSD-2-Clause", + "seeAlso": [ + "https://opensource.org/licenses/BSD-2-Clause" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CDLA-Permissive-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Permissive-2.0.json", + "referenceNumber": 270, + "name": "Community Data License Agreement Permissive 2.0", + "licenseId": "CDLA-Permissive-2.0", + "seeAlso": [ + "https://cdla.dev/permissive-2-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-3.0.json", + "referenceNumber": 271, + "name": "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported", + "licenseId": "CC-BY-NC-SA-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-export-US.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-export-US.json", + "referenceNumber": 272, + "name": "HPND with US Government export control warning", + "licenseId": "HPND-export-US", + "seeAlso": [ + "https://www.kermitproject.org/ck90.html#source" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/etalab-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/etalab-2.0.json", + "referenceNumber": 273, + "name": "Etalab Open License 2.0", + "licenseId": "etalab-2.0", + "seeAlso": [ + "https://github.com/DISIC/politique-de-contribution-open-source/blob/master/LICENSE.pdf", + "https://raw.githubusercontent.com/DISIC/politique-de-contribution-open-source/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSET-PL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSET-PL-2.1.json", + "referenceNumber": 274, + "name": "OSET Public License version 2.1", + "licenseId": "OSET-PL-2.1", + "seeAlso": [ + "http://www.osetfoundation.org/public-license", + "https://opensource.org/licenses/OPL-2.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-invariants-or-later.json", + "referenceNumber": 275, + "name": "GNU Free Documentation License v1.1 or later - invariants", + "licenseId": "GFDL-1.1-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OpenSSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OpenSSL.json", + "referenceNumber": 276, + "name": "OpenSSL License", + "licenseId": "OpenSSL", + "seeAlso": [ + "http://www.openssl.org/source/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ASWF-Digital-Assets-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ASWF-Digital-Assets-1.0.json", + "referenceNumber": 277, + "name": "ASWF Digital Assets License version 1.0", + "licenseId": "ASWF-Digital-Assets-1.0", + "seeAlso": [ + "https://github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-2.0.json", + "referenceNumber": 278, + "name": "SGI Free Software License B v2.0", + "licenseId": "SGI-B-2.0", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/SGIFreeSWLicB.2.0.pdf" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC0-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC0-1.0.json", + "referenceNumber": 279, + "name": "Creative Commons Zero v1.0 Universal", + "licenseId": "CC0-1.0", + "seeAlso": [ + "https://creativecommons.org/publicdomain/zero/1.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-Attribution-HPND-disclaimer.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Attribution-HPND-disclaimer.json", + "referenceNumber": 280, + "name": "BSD with Attribution and HPND disclaimer", + "licenseId": "BSD-Attribution-HPND-disclaimer", + "seeAlso": [ + "https://github.com/cyrusimap/cyrus-sasl/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/mpich2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mpich2.json", + "referenceNumber": 281, + "name": "mpich2 License", + "licenseId": "mpich2", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0.json", + "referenceNumber": 282, + "name": "Artistic License 1.0", + "licenseId": "Artistic-1.0", + "seeAlso": [ + "https://opensource.org/licenses/Artistic-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-only.json", + "referenceNumber": 283, + "name": "GNU Free Documentation License v1.3 only", + "licenseId": "GFDL-1.3-only", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OGDL-Taiwan-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGDL-Taiwan-1.0.json", + "referenceNumber": 284, + "name": "Taiwan Open Government Data License, version 1.0", + "licenseId": "OGDL-Taiwan-1.0", + "seeAlso": [ + "https://data.gov.tw/license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-no-invariants-or-later.json", + "referenceNumber": 285, + "name": "GNU Free Documentation License v1.2 or later - no invariants", + "licenseId": "GFDL-1.2-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-Rplus-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-Rplus-1.1.json", + "referenceNumber": 286, + "name": "Licence Libre du Québec – Réciprocité forte version 1.1", + "licenseId": "LiLiQ-Rplus-1.1", + "seeAlso": [ + "https://www.forge.gouv.qc.ca/participez/licence-logicielle/licence-libre-du-quebec-liliq-en-francais/licence-libre-du-quebec-reciprocite-forte-liliq-r-v1-1/", + "http://opensource.org/licenses/LiLiQ-Rplus-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ImageMagick.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ImageMagick.json", + "referenceNumber": 287, + "name": "ImageMagick License", + "licenseId": "ImageMagick", + "seeAlso": [ + "http://www.imagemagick.org/script/license.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/X11-distribute-modifications-variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/X11-distribute-modifications-variant.json", + "referenceNumber": 288, + "name": "X11 License Distribution Modification Variant", + "licenseId": "X11-distribute-modifications-variant", + "seeAlso": [ + "https://github.com/mirror/ncurses/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/dvipdfm.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/dvipdfm.json", + "referenceNumber": 289, + "name": "dvipdfm License", + "licenseId": "dvipdfm", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/dvipdfm" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MulanPSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MulanPSL-1.0.json", + "referenceNumber": 290, + "name": "Mulan Permissive Software License, Version 1", + "licenseId": "MulanPSL-1.0", + "seeAlso": [ + "https://license.coscl.org.cn/MulanPSL/", + "https://github.com/yuwenlong/longphp/blob/25dfb70cc2a466dc4bb55ba30901cbce08d164b5/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Unicode-DFS-2015.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-DFS-2015.json", + "referenceNumber": 291, + "name": "Unicode License Agreement - Data Files and Software (2015)", + "licenseId": "Unicode-DFS-2015", + "seeAlso": [ + "https://web.archive.org/web/20151224134844/http://unicode.org/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-1.0.json", + "referenceNumber": 292, + "name": "Creative Commons Attribution Non Commercial 1.0 Generic", + "licenseId": "CC-BY-NC-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/1.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0-or-later.json", + "referenceNumber": 293, + "name": "GNU Lesser General Public License v3.0 or later", + "licenseId": "LGPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GD.json", + "referenceNumber": 294, + "name": "GD License", + "licenseId": "GD", + "seeAlso": [ + "https://libgd.github.io/manuals/2.3.0/files/license-txt.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/mpi-permissive.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/mpi-permissive.json", + "referenceNumber": 295, + "name": "mpi Permissive License", + "licenseId": "mpi-permissive", + "seeAlso": [ + "https://sources.debian.org/src/openmpi/4.1.0-10/ompi/debuggers/msgq_interface.h/?hl\u003d19#L19" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-autoconf-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-autoconf-exception.json", + "referenceNumber": 296, + "name": "GNU General Public License v2.0 w/Autoconf exception", + "licenseId": "GPL-2.0-with-autoconf-exception", + "seeAlso": [ + "http://ac-archive.sourceforge.net/doc/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.6.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.6.json", + "referenceNumber": 297, + "name": "Open LDAP Public License v2.6", + "licenseId": "OLDAP-2.6", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d1cae062821881f41b73012ba816434897abf4205" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-3.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-3.0.json", + "referenceNumber": 298, + "name": "GNU Lesser General Public License v3.0 only", + "licenseId": "LGPL-3.0", + "seeAlso": [ + "https://www.gnu.org/licenses/lgpl-3.0-standalone.html", + "https://www.gnu.org/licenses/lgpl+gpl-3.0.txt", + "https://opensource.org/licenses/LGPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/StandardML-NJ.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/StandardML-NJ.json", + "referenceNumber": 299, + "name": "Standard ML of New Jersey License", + "licenseId": "StandardML-NJ", + "seeAlso": [ + "https://www.smlnj.org/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Minpack.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Minpack.json", + "referenceNumber": 300, + "name": "Minpack License", + "licenseId": "Minpack", + "seeAlso": [ + "http://www.netlib.org/minpack/disclaimer", + "https://gitlab.com/libeigen/eigen/-/blob/master/COPYING.MINPACK" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NGPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NGPL.json", + "referenceNumber": 301, + "name": "Nethack General Public License", + "licenseId": "NGPL", + "seeAlso": [ + "https://opensource.org/licenses/NGPL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/diffmark.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/diffmark.json", + "referenceNumber": 302, + "name": "diffmark license", + "licenseId": "diffmark", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/diffmark" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Latex2e.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Latex2e.json", + "referenceNumber": 303, + "name": "Latex2e License", + "licenseId": "Latex2e", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Latex2e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-2.0.json", + "referenceNumber": 304, + "name": "Open Government Licence v2.0", + "licenseId": "OGL-UK-2.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/2/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AFL-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-2.1.json", + "referenceNumber": 305, + "name": "Academic Free License v2.1", + "licenseId": "AFL-2.1", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-2.1.txt" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AFL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-1.2.json", + "referenceNumber": 306, + "name": "Academic Free License v1.2", + "licenseId": "AFL-1.2", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-1.2.txt", + "http://wayback.archive.org/web/20021204204652/http://www.opensource.org/licenses/academic.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Python-2.0.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Python-2.0.1.json", + "referenceNumber": 307, + "name": "Python License 2.0.1", + "licenseId": "Python-2.0.1", + "seeAlso": [ + "https://www.python.org/download/releases/2.0.1/license/", + "https://docs.python.org/3/license.html", + "https://github.com/python/cpython/blob/main/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-B.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-B.json", + "referenceNumber": 308, + "name": "CeCILL-B Free Software License Agreement", + "licenseId": "CECILL-B", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OCCT-PL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OCCT-PL.json", + "referenceNumber": 309, + "name": "Open CASCADE Technology Public License", + "licenseId": "OCCT-PL", + "seeAlso": [ + "http://www.opencascade.com/content/occt-public-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-1.1.json", + "referenceNumber": 310, + "name": "Open Software License 1.1", + "licenseId": "OSL-1.1", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/OSL1.1" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Spencer-86.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-86.json", + "referenceNumber": 311, + "name": "Spencer License 86", + "licenseId": "Spencer-86", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Henry_Spencer_Reg-Ex_Library_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/xinetd.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xinetd.json", + "referenceNumber": 312, + "name": "xinetd License", + "licenseId": "xinetd", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Xinetd_License" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-invariants-or-later.json", + "referenceNumber": 313, + "name": "GNU Free Documentation License v1.2 or later - invariants", + "licenseId": "GFDL-1.2-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Boehm-GC.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Boehm-GC.json", + "referenceNumber": 314, + "name": "Boehm-Demers-Weiser GC License", + "licenseId": "Boehm-GC", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT#Another_Minimal_variant_(found_in_libatomic_ops)", + "https://github.com/uim/libgcroots/blob/master/COPYING", + "https://github.com/ivmai/libatomic_ops/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Graphics-Gems.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Graphics-Gems.json", + "referenceNumber": 315, + "name": "Graphics Gems License", + "licenseId": "Graphics-Gems", + "seeAlso": [ + "https://github.com/erich666/GraphicsGems/blob/master/LICENSE.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CAL-1.0-Combined-Work-Exception.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CAL-1.0-Combined-Work-Exception.json", + "referenceNumber": 316, + "name": "Cryptographic Autonomy License 1.0 (Combined Work Exception)", + "licenseId": "CAL-1.0-Combined-Work-Exception", + "seeAlso": [ + "http://cryptographicautonomylicense.com/license-text.html", + "https://opensource.org/licenses/CAL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0-DE.json", + "referenceNumber": 317, + "name": "Creative Commons Attribution 3.0 Germany", + "licenseId": "CC-BY-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GLWTPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GLWTPL.json", + "referenceNumber": 318, + "name": "Good Luck With That Public License", + "licenseId": "GLWTPL", + "seeAlso": [ + "https://github.com/me-shaon/GLWTPL/commit/da5f6bc734095efbacb442c0b31e33a65b9d6e85" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NIST-PD-fallback.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-PD-fallback.json", + "referenceNumber": 319, + "name": "NIST Public Domain Notice with license fallback", + "licenseId": "NIST-PD-fallback", + "seeAlso": [ + "https://github.com/usnistgov/jsip/blob/59700e6926cbe96c5cdae897d9a7d2656b42abe3/LICENSE", + "https://github.com/usnistgov/fipy/blob/86aaa5c2ba2c6f1be19593c5986071cf6568cc34/LICENSE.rst" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause.json", + "referenceNumber": 320, + "name": "BSD 3-Clause \"New\" or \"Revised\" License", + "licenseId": "BSD-3-Clause", + "seeAlso": [ + "https://opensource.org/licenses/BSD-3-Clause", + "https://www.eclipse.org/org/documents/edl-v10.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NetCDF.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NetCDF.json", + "referenceNumber": 321, + "name": "NetCDF license", + "licenseId": "NetCDF", + "seeAlso": [ + "http://www.unidata.ucar.edu/software/netcdf/copyright.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-ND-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-ND-3.0-DE.json", + "referenceNumber": 322, + "name": "Creative Commons Attribution No Derivatives 3.0 Germany", + "licenseId": "CC-BY-ND-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Parity-7.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Parity-7.0.0.json", + "referenceNumber": 323, + "name": "The Parity Public License 7.0.0", + "licenseId": "Parity-7.0.0", + "seeAlso": [ + "https://paritylicense.com/versions/7.0.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.0.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.0.1.json", + "referenceNumber": 324, + "name": "Open LDAP Public License v2.0.1", + "licenseId": "OLDAP-2.0.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003db6d68acd14e51ca3aab4428bf26522aa74873f0e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DRL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DRL-1.0.json", + "referenceNumber": 325, + "name": "Detection Rule License 1.0", + "licenseId": "DRL-1.0", + "seeAlso": [ + "https://github.com/Neo23x0/sigma/blob/master/LICENSE.Detection.Rules.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TU-Berlin-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TU-Berlin-2.0.json", + "referenceNumber": 326, + "name": "Technische Universitaet Berlin License 2.0", + "licenseId": "TU-Berlin-2.0", + "seeAlso": [ + "https://github.com/CorsixTH/deps/blob/fd339a9f526d1d9c9f01ccf39e438a015da50035/licences/libgsm.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Borceux.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Borceux.json", + "referenceNumber": 327, + "name": "Borceux license", + "licenseId": "Borceux", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Borceux" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.3.json", + "referenceNumber": 328, + "name": "Open LDAP Public License v1.3", + "licenseId": "OLDAP-1.3", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003de5f8117f0ce088d0bd7a8e18ddf37eaa40eb09b1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Giftware.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Giftware.json", + "referenceNumber": 329, + "name": "Giftware License", + "licenseId": "Giftware", + "seeAlso": [ + "http://liballeg.org/license.html#allegro-4-the-giftware-license" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGI-B-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGI-B-1.1.json", + "referenceNumber": 330, + "name": "SGI Free Software License B v1.1", + "licenseId": "SGI-B-1.1", + "seeAlso": [ + "http://oss.sgi.com/projects/FreeB/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License.json", + "referenceNumber": 331, + "name": "BSD 3-Clause No Nuclear License", + "licenseId": "BSD-3-Clause-No-Nuclear-License", + "seeAlso": [ + "http://download.oracle.com/otn-pub/java/licenses/bsd.txt?AuthParam\u003d1467140197_43d516ce1776bd08a58235a7785be1cc" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/curl.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/curl.json", + "referenceNumber": 332, + "name": "curl License", + "licenseId": "curl", + "seeAlso": [ + "https://github.com/bagder/curl/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-1.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-1.4.json", + "referenceNumber": 333, + "name": "Open LDAP Public License v1.4", + "licenseId": "OLDAP-1.4", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dc9f95c2f3f2ffb5e0ae55fe7388af75547660941" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SNIA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SNIA.json", + "referenceNumber": 334, + "name": "SNIA Public License 1.1", + "licenseId": "SNIA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/SNIA_Public_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/W3C.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C.json", + "referenceNumber": 335, + "name": "W3C Software Notice and License (2002-12-31)", + "licenseId": "W3C", + "seeAlso": [ + "http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231.html", + "https://opensource.org/licenses/W3C" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-or-later.json", + "referenceNumber": 336, + "name": "GNU Free Documentation License v1.3 or later", + "licenseId": "GFDL-1.3-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/D-FSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/D-FSL-1.0.json", + "referenceNumber": 337, + "name": "Deutsche Freie Software Lizenz", + "licenseId": "D-FSL-1.0", + "seeAlso": [ + "http://www.dipp.nrw.de/d-fsl/lizenzen/", + "http://www.dipp.nrw.de/d-fsl/index_html/lizenzen/de/D-FSL-1_0_de.txt", + "http://www.dipp.nrw.de/d-fsl/index_html/lizenzen/en/D-FSL-1_0_en.txt", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/deutsche-freie-software-lizenz", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/german-free-software-license", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/D-FSL-1_0_de.txt/at_download/file", + "https://www.hbz-nrw.de/produkte/open-access/lizenzen/dfsl/D-FSL-1_0_en.txt/at_download/file" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-4.0.json", + "referenceNumber": 338, + "name": "Creative Commons Attribution Non Commercial Share Alike 4.0 International", + "licenseId": "CC-BY-NC-SA-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Naumen.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Naumen.json", + "referenceNumber": 339, + "name": "Naumen Public License", + "licenseId": "Naumen", + "seeAlso": [ + "https://opensource.org/licenses/Naumen" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/FSFAP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFAP.json", + "referenceNumber": 340, + "name": "FSF All Permissive License", + "licenseId": "FSFAP", + "seeAlso": [ + "https://www.gnu.org/prep/maintain/html_node/License-Notices-for-Other-Files.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Military-License.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Military-License.json", + "referenceNumber": 341, + "name": "BSD 3-Clause No Military License", + "licenseId": "BSD-3-Clause-No-Military-License", + "seeAlso": [ + "https://gitlab.syncad.com/hive/dhive/-/blob/master/LICENSE", + "https://github.com/greymass/swift-eosio/blob/master/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-4.0.json", + "referenceNumber": 342, + "name": "Creative Commons Attribution Share Alike 4.0 International", + "licenseId": "CC-BY-SA-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/xlock.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xlock.json", + "referenceNumber": 343, + "name": "xlock License", + "licenseId": "xlock", + "seeAlso": [ + "https://fossies.org/linux/tiff/contrib/ras/ras2tif.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Sendmail-8.23.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Sendmail-8.23.json", + "referenceNumber": 344, + "name": "Sendmail License 8.23", + "licenseId": "Sendmail-8.23", + "seeAlso": [ + "https://www.proofpoint.com/sites/default/files/sendmail-license.pdf", + "https://web.archive.org/web/20181003101040/https://www.proofpoint.com/sites/default/files/sendmail-license.pdf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MS-PL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MS-PL.json", + "referenceNumber": 345, + "name": "Microsoft Public License", + "licenseId": "MS-PL", + "seeAlso": [ + "http://www.microsoft.com/opensource/licenses.mspx", + "https://opensource.org/licenses/MS-PL" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NIST-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-PD.json", + "referenceNumber": 346, + "name": "NIST Public Domain Notice", + "licenseId": "NIST-PD", + "seeAlso": [ + "https://github.com/tcheneau/simpleRPL/blob/e645e69e38dd4e3ccfeceb2db8cba05b7c2e0cd3/LICENSE.txt", + "https://github.com/tcheneau/Routing/blob/f09f46fcfe636107f22f2c98348188a65a135d98/README.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Community-Spec-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Community-Spec-1.0.json", + "referenceNumber": 347, + "name": "Community Specification License 1.0", + "licenseId": "Community-Spec-1.0", + "seeAlso": [ + "https://github.com/CommunitySpecification/1.0/blob/master/1._Community_Specification_License-v1.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-1.1.json", + "referenceNumber": 348, + "name": "CERN Open Hardware Licence v1.1", + "licenseId": "CERN-OHL-1.1", + "seeAlso": [ + "https://www.ohwr.org/project/licenses/wikis/cern-ohl-v1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-or-later.json", + "referenceNumber": 349, + "name": "GNU Free Documentation License v1.2 or later", + "licenseId": "GFDL-1.2-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0-or-later.json", + "referenceNumber": 350, + "name": "GNU Library General Public License v2 or later", + "licenseId": "LGPL-2.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Condor-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Condor-1.1.json", + "referenceNumber": 351, + "name": "Condor Public License v1.1", + "licenseId": "Condor-1.1", + "seeAlso": [ + "http://research.cs.wisc.edu/condor/license.html#condor", + "http://web.archive.org/web/20111123062036/http://research.cs.wisc.edu/condor/license.html#condor" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CDDL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDDL-1.1.json", + "referenceNumber": 352, + "name": "Common Development and Distribution License 1.1", + "licenseId": "CDDL-1.1", + "seeAlso": [ + "http://glassfish.java.net/public/CDDL+GPL_1_1.html", + "https://javaee.github.io/glassfish/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Glide.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Glide.json", + "referenceNumber": 353, + "name": "3dfx Glide License", + "licenseId": "Glide", + "seeAlso": [ + "http://www.users.on.net/~triforce/glidexp/COPYING.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0-no-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0-no-RFN.json", + "referenceNumber": 354, + "name": "SIL Open Font License 1.0 with no Reserved Font Name", + "licenseId": "OFL-1.0-no-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CMU-Mach.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CMU-Mach.json", + "referenceNumber": 355, + "name": "CMU Mach License", + "licenseId": "CMU-Mach", + "seeAlso": [ + "https://www.cs.cmu.edu/~410/licenses.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ODbL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ODbL-1.0.json", + "referenceNumber": 356, + "name": "Open Data Commons Open Database License v1.0", + "licenseId": "ODbL-1.0", + "seeAlso": [ + "http://www.opendatacommons.org/licenses/odbl/1.0/", + "https://opendatacommons.org/licenses/odbl/1-0/" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LOOP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LOOP.json", + "referenceNumber": 357, + "name": "Common Lisp LOOP License", + "licenseId": "LOOP", + "seeAlso": [ + "https://gitlab.com/embeddable-common-lisp/ecl/-/blob/develop/src/lsp/loop.lsp", + "http://git.savannah.gnu.org/cgit/gcl.git/tree/gcl/lsp/gcl_loop.lsp?h\u003dVersion_2_6_13pre", + "https://sourceforge.net/p/sbcl/sbcl/ci/master/tree/src/code/loop.lisp", + "https://github.com/cl-adams/adams/blob/master/LICENSE.md", + "https://github.com/blakemcbride/eclipse-lisp/blob/master/lisp/loop.lisp", + "https://gitlab.common-lisp.net/cmucl/cmucl/-/blob/master/src/code/loop.lisp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Motosoto.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Motosoto.json", + "referenceNumber": 358, + "name": "Motosoto License", + "licenseId": "Motosoto", + "seeAlso": [ + "https://opensource.org/licenses/Motosoto" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1-only.json", + "referenceNumber": 359, + "name": "GNU Lesser General Public License v2.1 only", + "licenseId": "LGPL-2.1-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NASA-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NASA-1.3.json", + "referenceNumber": 360, + "name": "NASA Open Source Agreement 1.3", + "licenseId": "NASA-1.3", + "seeAlso": [ + "http://ti.arc.nasa.gov/opensource/nosa/", + "https://opensource.org/licenses/NASA-1.3" + ], + "isOsiApproved": true, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/EUPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EUPL-1.0.json", + "referenceNumber": 361, + "name": "European Union Public License 1.0", + "licenseId": "EUPL-1.0", + "seeAlso": [ + "http://ec.europa.eu/idabc/en/document/7330.html", + "http://ec.europa.eu/idabc/servlets/Doc027f.pdf?id\u003d31096" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/zlib-acknowledgement.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/zlib-acknowledgement.json", + "referenceNumber": 362, + "name": "zlib/libpng License with Acknowledgement", + "licenseId": "zlib-acknowledgement", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/ZlibWithAcknowledgement" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SugarCRM-1.1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SugarCRM-1.1.3.json", + "referenceNumber": 363, + "name": "SugarCRM Public License v1.1.3", + "licenseId": "SugarCRM-1.1.3", + "seeAlso": [ + "http://www.sugarcrm.com/crm/SPL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Widget-Workshop.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Widget-Workshop.json", + "referenceNumber": 364, + "name": "Widget Workshop License", + "licenseId": "Widget-Workshop", + "seeAlso": [ + "https://github.com/novnc/noVNC/blob/master/core/crypto/des.js#L24" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-NetBSD.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-NetBSD.json", + "referenceNumber": 365, + "name": "BSD 2-Clause NetBSD License", + "licenseId": "BSD-2-Clause-NetBSD", + "seeAlso": [ + "http://www.netbsd.org/about/redistribution.html#default" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Info-ZIP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Info-ZIP.json", + "referenceNumber": 366, + "name": "Info-ZIP License", + "licenseId": "Info-ZIP", + "seeAlso": [ + "http://www.info-zip.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Advertising-Acknowledgement.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Advertising-Acknowledgement.json", + "referenceNumber": 367, + "name": "BSD Advertising Acknowledgement License", + "licenseId": "BSD-Advertising-Acknowledgement", + "seeAlso": [ + "https://github.com/python-excel/xlrd/blob/master/LICENSE#L33" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-1.0.json", + "referenceNumber": 368, + "name": "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic", + "licenseId": "CC-BY-NC-ND-1.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nd-nc/1.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-0.json", + "referenceNumber": 369, + "name": "MIT No Attribution", + "licenseId": "MIT-0", + "seeAlso": [ + "https://github.com/aws/mit-0", + "https://romanrm.net/mit-zero", + "https://github.com/awsdocs/aws-cloud9-user-guide/blob/master/LICENSE-SAMPLECODE" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OCLC-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OCLC-2.0.json", + "referenceNumber": 370, + "name": "OCLC Research Public License 2.0", + "licenseId": "OCLC-2.0", + "seeAlso": [ + "http://www.oclc.org/research/activities/software/license/v2final.htm", + "https://opensource.org/licenses/OCLC-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-1.0.json", + "referenceNumber": 371, + "name": "Open Software License 1.0", + "licenseId": "OSL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/OSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Saxpath.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Saxpath.json", + "referenceNumber": 372, + "name": "Saxpath License", + "licenseId": "Saxpath", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Saxpath_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IJG-short.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IJG-short.json", + "referenceNumber": 373, + "name": "Independent JPEG Group License - short", + "licenseId": "IJG-short", + "seeAlso": [ + "https://sourceforge.net/p/xmedcon/code/ci/master/tree/libs/ljpg/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zend-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zend-2.0.json", + "referenceNumber": 374, + "name": "Zend License v2.0", + "licenseId": "Zend-2.0", + "seeAlso": [ + "https://web.archive.org/web/20130517195954/http://www.zend.com/license/2_00.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Cornell-Lossless-JPEG.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Cornell-Lossless-JPEG.json", + "referenceNumber": 375, + "name": "Cornell Lossless JPEG License", + "licenseId": "Cornell-Lossless-JPEG", + "seeAlso": [ + "https://android.googlesource.com/platform/external/dng_sdk/+/refs/heads/master/source/dng_lossless_jpeg.cpp#16", + "https://www.mssl.ucl.ac.uk/~mcrw/src/20050920/proto.h", + "https://gitlab.freedesktop.org/libopenraw/libopenraw/blob/master/lib/ljpegdecompressor.cpp#L32" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-1.0.json", + "referenceNumber": 376, + "name": "CeCILL Free Software License Agreement v1.0", + "licenseId": "CECILL-1.0", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V1-fr.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OpenPBS-2.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OpenPBS-2.3.json", + "referenceNumber": 377, + "name": "OpenPBS v2.3 Software License", + "licenseId": "OpenPBS-2.3", + "seeAlso": [ + "https://github.com/adaptivecomputing/torque/blob/master/PBS_License.txt", + "https://www.mcs.anl.gov/research/projects/openpbs/PBS_License.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-2.5.json", + "referenceNumber": 378, + "name": "Creative Commons Attribution Share Alike 2.5 Generic", + "licenseId": "CC-BY-SA-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/DSDP.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/DSDP.json", + "referenceNumber": 379, + "name": "DSDP License", + "licenseId": "DSDP", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/DSDP" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Martin-Birgmeier.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Martin-Birgmeier.json", + "referenceNumber": 380, + "name": "Martin Birgmeier License", + "licenseId": "Martin-Birgmeier", + "seeAlso": [ + "https://github.com/Perl/perl5/blob/blead/util.c#L6136" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CPOL-1.02.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPOL-1.02.json", + "referenceNumber": 381, + "name": "Code Project Open License 1.02", + "licenseId": "CPOL-1.02", + "seeAlso": [ + "http://www.codeproject.com/info/cpol10.aspx" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/MIT-advertising.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-advertising.json", + "referenceNumber": 382, + "name": "Enlightenment License (e16)", + "licenseId": "MIT-advertising", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT_With_Advertising" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IPA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IPA.json", + "referenceNumber": 383, + "name": "IPA Font License", + "licenseId": "IPA", + "seeAlso": [ + "https://opensource.org/licenses/IPA" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0-DE.json", + "referenceNumber": 384, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany", + "licenseId": "CC-BY-NC-ND-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-SA-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-SA-3.0-DE.json", + "referenceNumber": 385, + "name": "Creative Commons Attribution Share Alike 3.0 Germany", + "licenseId": "CC-BY-SA-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-sa/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AMDPLPA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AMDPLPA.json", + "referenceNumber": 386, + "name": "AMD\u0027s plpa_map.c License", + "licenseId": "AMDPLPA", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/AMD_plpa_map_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SimPL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SimPL-2.0.json", + "referenceNumber": 387, + "name": "Simple Public License 2.0", + "licenseId": "SimPL-2.0", + "seeAlso": [ + "https://opensource.org/licenses/SimPL-2.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/EPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPL-1.0.json", + "referenceNumber": 388, + "name": "Eclipse Public License 1.0", + "licenseId": "EPL-1.0", + "seeAlso": [ + "http://www.eclipse.org/legal/epl-v10.html", + "https://opensource.org/licenses/EPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/AGPL-1.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AGPL-1.0-only.json", + "referenceNumber": 389, + "name": "Affero General Public License v1.0 only", + "licenseId": "AGPL-1.0-only", + "seeAlso": [ + "http://www.affero.org/oagpl.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SAX-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SAX-PD.json", + "referenceNumber": 390, + "name": "Sax Public Domain Notice", + "licenseId": "SAX-PD", + "seeAlso": [ + "http://www.saxproject.org/copying.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-no-invariants-or-later.json", + "referenceNumber": 391, + "name": "GNU Free Documentation License v1.1 or later - no invariants", + "licenseId": "GFDL-1.1-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/bzip2-1.0.6.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/bzip2-1.0.6.json", + "referenceNumber": 392, + "name": "bzip2 and libbzip2 License v1.0.6", + "licenseId": "bzip2-1.0.6", + "seeAlso": [ + "https://sourceware.org/git/?p\u003dbzip2.git;a\u003dblob;f\u003dLICENSE;hb\u003dbzip2-1.0.6", + "http://bzip.org/1.0.5/bzip2-manual-1.0.5.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFUL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFUL.json", + "referenceNumber": 393, + "name": "FSF Unlimited License", + "licenseId": "FSFUL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FSF_Unlimited_License" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Spencer-94.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Spencer-94.json", + "referenceNumber": 394, + "name": "Spencer License 94", + "licenseId": "Spencer-94", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Henry_Spencer_Reg-Ex_Library_License", + "https://metacpan.org/release/KNOK/File-MMagic-1.30/source/COPYING#L28" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CFITSIO.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CFITSIO.json", + "referenceNumber": 395, + "name": "CFITSIO License", + "licenseId": "CFITSIO", + "seeAlso": [ + "https://heasarc.gsfc.nasa.gov/docs/software/fitsio/c/f_user/node9.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AdaCore-doc.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AdaCore-doc.json", + "referenceNumber": 396, + "name": "AdaCore Doc License", + "licenseId": "AdaCore-doc", + "seeAlso": [ + "https://github.com/AdaCore/xmlada/blob/master/docs/index.rst", + "https://github.com/AdaCore/gnatcoll-core/blob/master/docs/index.rst", + "https://github.com/AdaCore/gnatcoll-db/blob/master/docs/index.rst" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-Source-Code.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-Source-Code.json", + "referenceNumber": 397, + "name": "BSD Source Code Attribution", + "licenseId": "BSD-Source-Code", + "seeAlso": [ + "https://github.com/robbiehanson/CocoaHTTPServer/blob/master/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Noweb.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Noweb.json", + "referenceNumber": 398, + "name": "Noweb License", + "licenseId": "Noweb", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Noweb" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SMPPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SMPPL.json", + "referenceNumber": 399, + "name": "Secure Messaging Protocol Public License", + "licenseId": "SMPPL", + "seeAlso": [ + "https://github.com/dcblake/SMP/blob/master/Documentation/License.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-copyleft-var.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-copyleft-var.json", + "referenceNumber": 400, + "name": "Linux man-pages Copyleft Variant", + "licenseId": "Linux-man-pages-copyleft-var", + "seeAlso": [ + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/set_mempolicy.2#n5" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Barr.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Barr.json", + "referenceNumber": 401, + "name": "Barr License", + "licenseId": "Barr", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Barr" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.1.json", + "referenceNumber": 402, + "name": "Open LDAP Public License v2.1", + "licenseId": "OLDAP-2.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003db0d176738e96a0d3b9f85cb51e140a86f21be715" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TTWL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TTWL.json", + "referenceNumber": 403, + "name": "Text-Tabs+Wrap License", + "licenseId": "TTWL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TTWL", + "https://github.com/ap/Text-Tabs/blob/master/lib.modern/Text/Tabs.pm#L148" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CNRI-Python-GPL-Compatible.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Python-GPL-Compatible.json", + "referenceNumber": 404, + "name": "CNRI Python Open Source GPL Compatible License Agreement", + "licenseId": "CNRI-Python-GPL-Compatible", + "seeAlso": [ + "http://www.python.org/download/releases/1.6.1/download_win/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OSL-2.0.json", + "referenceNumber": 405, + "name": "Open Software License 2.0", + "licenseId": "OSL-2.0", + "seeAlso": [ + "http://web.archive.org/web/20041020171434/http://www.rosenlaw.com/osl2.0.html" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-invariants-or-later.json", + "referenceNumber": 406, + "name": "GNU Free Documentation License v1.3 or later - invariants", + "licenseId": "GFDL-1.3-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/xpp.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/xpp.json", + "referenceNumber": 407, + "name": "XPP License", + "licenseId": "xpp", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/xpp" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/W3C-19980720.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/W3C-19980720.json", + "referenceNumber": 408, + "name": "W3C Software Notice and License (1998-07-20)", + "licenseId": "W3C-19980720", + "seeAlso": [ + "http://www.w3.org/Consortium/Legal/copyright-software-19980720.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-1-para.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-1-para.json", + "referenceNumber": 409, + "name": "Linux man-pages - 1 paragraph", + "licenseId": "Linux-man-pages-1-para", + "seeAlso": [ + "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/getcpu.2#n4" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APL-1.0.json", + "referenceNumber": 410, + "name": "Adaptive Public License 1.0", + "licenseId": "APL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/APL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CPAL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPAL-1.0.json", + "referenceNumber": 411, + "name": "Common Public Attribution License 1.0", + "licenseId": "CPAL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CPAL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/ClArtistic.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ClArtistic.json", + "referenceNumber": 412, + "name": "Clarified Artistic License", + "licenseId": "ClArtistic", + "seeAlso": [ + "http://gianluca.dellavedova.org/2011/01/03/clarified-artistic-license/", + "http://www.ncftp.com/ncftp/doc/LICENSE.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NIST-Software.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NIST-Software.json", + "referenceNumber": 413, + "name": "NIST Software License", + "licenseId": "NIST-Software", + "seeAlso": [ + "https://github.com/open-quantum-safe/liboqs/blob/40b01fdbb270f8614fde30e65d30e9da18c02393/src/common/rand/rand_nist.c#L1-L15" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UCL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UCL-1.0.json", + "referenceNumber": 414, + "name": "Upstream Compatibility License v1.0", + "licenseId": "UCL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/UCL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OGL-UK-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OGL-UK-3.0.json", + "referenceNumber": 415, + "name": "Open Government Licence v3.0", + "licenseId": "OGL-UK-3.0", + "seeAlso": [ + "http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TORQUE-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TORQUE-1.1.json", + "referenceNumber": 416, + "name": "TORQUE v2.5+ Software License v1.1", + "licenseId": "TORQUE-1.1", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TORQUEv1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NOSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NOSL.json", + "referenceNumber": 417, + "name": "Netizen Open Source License", + "licenseId": "NOSL", + "seeAlso": [ + "http://bits.netizen.com.au/licenses/NOSL/nosl.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LiLiQ-R-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LiLiQ-R-1.1.json", + "referenceNumber": 418, + "name": "Licence Libre du Québec – Réciprocité version 1.1", + "licenseId": "LiLiQ-R-1.1", + "seeAlso": [ + "https://www.forge.gouv.qc.ca/participez/licence-logicielle/licence-libre-du-quebec-liliq-en-francais/licence-libre-du-quebec-reciprocite-liliq-r-v1-1/", + "http://opensource.org/licenses/LiLiQ-R-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OFL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.0.json", + "referenceNumber": 419, + "name": "SIL Open Font License 1.0", + "licenseId": "OFL-1.0", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL10_web" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0.json", + "referenceNumber": 420, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic", + "licenseId": "CC-BY-NC-SA-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-Wu.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-Wu.json", + "referenceNumber": 421, + "name": "MIT Tom Wu Variant", + "licenseId": "MIT-Wu", + "seeAlso": [ + "https://github.com/chromium/octane/blob/master/crypto.js" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RHeCos-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RHeCos-1.1.json", + "referenceNumber": 422, + "name": "Red Hat eCos Public License v1.1", + "licenseId": "RHeCos-1.1", + "seeAlso": [ + "http://ecos.sourceware.org/old-license.html" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/MIT-Festival.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-Festival.json", + "referenceNumber": 423, + "name": "MIT Festival Variant", + "licenseId": "MIT-Festival", + "seeAlso": [ + "https://github.com/festvox/flite/blob/master/COPYING", + "https://github.com/festvox/speech_tools/blob/master/COPYING" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HPND-sell-variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HPND-sell-variant.json", + "referenceNumber": 424, + "name": "Historical Permission Notice and Disclaimer - sell variant", + "licenseId": "HPND-sell-variant", + "seeAlso": [ + "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sunrpc/auth_gss/gss_generic_token.c?h\u003dv4.19" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-or-later.json", + "referenceNumber": 425, + "name": "GNU General Public License v3.0 or later", + "licenseId": "GPL-3.0-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/gpl-3.0-standalone.html", + "https://opensource.org/licenses/GPL-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/TOSL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TOSL.json", + "referenceNumber": 426, + "name": "Trusster Open Source License", + "licenseId": "TOSL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/TOSL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.2-no-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.2-no-invariants-only.json", + "referenceNumber": 427, + "name": "GNU Free Documentation License v1.2 only - no invariants", + "licenseId": "GFDL-1.2-no-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.2.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BlueOak-1.0.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BlueOak-1.0.0.json", + "referenceNumber": 428, + "name": "Blue Oak Model License 1.0.0", + "licenseId": "BlueOak-1.0.0", + "seeAlso": [ + "https://blueoakcouncil.org/license/1.0.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.2.json", + "referenceNumber": 429, + "name": "LaTeX Project Public License v1.2", + "licenseId": "LPPL-1.2", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-2.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/iMatix.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/iMatix.json", + "referenceNumber": 430, + "name": "iMatix Standard Function Library Agreement", + "licenseId": "iMatix", + "seeAlso": [ + "http://legacy.imatix.com/html/sfl/sfl4.htm#license" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/LZMA-SDK-9.11-to-9.20.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LZMA-SDK-9.11-to-9.20.json", + "referenceNumber": 431, + "name": "LZMA SDK License (versions 9.11 to 9.20)", + "licenseId": "LZMA-SDK-9.11-to-9.20", + "seeAlso": [ + "https://www.7-zip.org/sdk.html", + "https://sourceforge.net/projects/sevenzip/files/LZMA%20SDK/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TPDL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TPDL.json", + "referenceNumber": 432, + "name": "Time::ParseDate License", + "licenseId": "TPDL", + "seeAlso": [ + "https://metacpan.org/pod/Time::ParseDate#LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-3.0.json", + "referenceNumber": 433, + "name": "Creative Commons Attribution 3.0 Unported", + "licenseId": "CC-BY-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Apache-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-1.0.json", + "referenceNumber": 434, + "name": "Apache License 1.0", + "licenseId": "Apache-1.0", + "seeAlso": [ + "http://www.apache.org/licenses/LICENSE-1.0" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3.json", + "referenceNumber": 435, + "name": "GNU Free Documentation License v1.3", + "licenseId": "GFDL-1.3", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Fair.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Fair.json", + "referenceNumber": 436, + "name": "Fair License", + "licenseId": "Fair", + "seeAlso": [ + "http://fairlicense.org/", + "https://opensource.org/licenses/Fair" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/EFL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EFL-2.0.json", + "referenceNumber": 437, + "name": "Eiffel Forum License v2.0", + "licenseId": "EFL-2.0", + "seeAlso": [ + "http://www.eiffel-nice.org/license/eiffel-forum-license-2.html", + "https://opensource.org/licenses/EFL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-only.json", + "referenceNumber": 438, + "name": "GNU General Public License v2.0 only", + "licenseId": "GPL-2.0-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-P-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-P-2.0.json", + "referenceNumber": 439, + "name": "CERN Open Hardware Licence Version 2 - Permissive", + "licenseId": "CERN-OHL-P-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Wsuipa.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Wsuipa.json", + "referenceNumber": 440, + "name": "Wsuipa License", + "licenseId": "Wsuipa", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Wsuipa" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SPL-1.0.json", + "referenceNumber": 441, + "name": "Sun Public License v1.0", + "licenseId": "SPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/SPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License-2014.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-No-Nuclear-License-2014.json", + "referenceNumber": 442, + "name": "BSD 3-Clause No Nuclear License 2014", + "licenseId": "BSD-3-Clause-No-Nuclear-License-2014", + "seeAlso": [ + "https://java.net/projects/javaeetutorial/pages/BerkeleyLicense" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MirOS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MirOS.json", + "referenceNumber": 443, + "name": "The MirOS Licence", + "licenseId": "MirOS", + "seeAlso": [ + "https://opensource.org/licenses/MirOS" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/blessing.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/blessing.json", + "referenceNumber": 444, + "name": "SQLite Blessing", + "licenseId": "blessing", + "seeAlso": [ + "https://www.sqlite.org/src/artifact/e33a4df7e32d742a?ln\u003d4-9", + "https://sqlite.org/src/artifact/df5091916dbb40e6" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Jam.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Jam.json", + "referenceNumber": 445, + "name": "Jam License", + "licenseId": "Jam", + "seeAlso": [ + "https://www.boost.org/doc/libs/1_35_0/doc/html/jam.html", + "https://web.archive.org/web/20160330173339/https://swarm.workshop.perforce.com/files/guest/perforce_software/jam/src/README" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-with-GCC-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-with-GCC-exception.json", + "referenceNumber": 446, + "name": "GNU General Public License v3.0 w/GCC Runtime Library exception", + "licenseId": "GPL-3.0-with-GCC-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/gcc-exception-3.1.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.4.json", + "referenceNumber": 447, + "name": "Open LDAP Public License v2.4", + "licenseId": "OLDAP-2.4", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dcd1284c4a91a8a380d904eee68d1583f989ed386" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/ANTLR-PD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ANTLR-PD.json", + "referenceNumber": 448, + "name": "ANTLR Software Rights Notice", + "licenseId": "ANTLR-PD", + "seeAlso": [ + "http://www.antlr2.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LZMA-SDK-9.22.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LZMA-SDK-9.22.json", + "referenceNumber": 449, + "name": "LZMA SDK License (versions 9.22 and beyond)", + "licenseId": "LZMA-SDK-9.22", + "seeAlso": [ + "https://www.7-zip.org/sdk.html", + "https://sourceforge.net/projects/sevenzip/files/LZMA%20SDK/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/PHP-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PHP-3.0.json", + "referenceNumber": 450, + "name": "PHP License v3.0", + "licenseId": "PHP-3.0", + "seeAlso": [ + "http://www.php.net/license/3_0.txt", + "https://opensource.org/licenses/PHP-3.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.2.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.2.1.json", + "referenceNumber": 451, + "name": "Open LDAP Public License v2.2.1", + "licenseId": "OLDAP-2.2.1", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d4bc786f34b50aa301be6f5600f58a980070f481e" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-DE.json", + "referenceNumber": 452, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany", + "licenseId": "CC-BY-NC-SA-2.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libpng-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libpng-2.0.json", + "referenceNumber": 453, + "name": "PNG Reference Library version 2", + "licenseId": "libpng-2.0", + "seeAlso": [ + "http://www.libpng.org/pub/png/src/libpng-LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/UCAR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/UCAR.json", + "referenceNumber": 454, + "name": "UCAR License", + "licenseId": "UCAR", + "seeAlso": [ + "https://github.com/Unidata/UDUNITS-2/blob/master/COPYRIGHT" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/gnuplot.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/gnuplot.json", + "referenceNumber": 455, + "name": "gnuplot License", + "licenseId": "gnuplot", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Gnuplot" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Nunit.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/Nunit.json", + "referenceNumber": 456, + "name": "Nunit License", + "licenseId": "Nunit", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Nunit" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-2.5.json", + "referenceNumber": 457, + "name": "Creative Commons Attribution Non Commercial 2.5 Generic", + "licenseId": "CC-BY-NC-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc/2.5/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/NRL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NRL.json", + "referenceNumber": 458, + "name": "NRL License", + "licenseId": "NRL", + "seeAlso": [ + "http://web.mit.edu/network/isakmp/nrllicense.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Python-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Python-2.0.json", + "referenceNumber": 459, + "name": "Python License 2.0", + "licenseId": "Python-2.0", + "seeAlso": [ + "https://opensource.org/licenses/Python-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-UK.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-2.0-UK.json", + "referenceNumber": 460, + "name": "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales", + "licenseId": "CC-BY-NC-SA-2.0-UK", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/2.0/uk/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GL2PS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GL2PS.json", + "referenceNumber": 461, + "name": "GL2PS License", + "licenseId": "GL2PS", + "seeAlso": [ + "http://www.geuz.org/gl2ps/COPYING.GL2PS" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-2.0.json", + "referenceNumber": 462, + "name": "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic", + "licenseId": "CC-BY-NC-ND-2.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/2.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AFL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-1.1.json", + "referenceNumber": 463, + "name": "Academic Free License v1.1", + "licenseId": "AFL-1.1", + "seeAlso": [ + "http://opensource.linux-mirror.org/licenses/afl-1.1.txt", + "http://wayback.archive.org/web/20021004124254/http://www.opensource.org/licenses/academic.php" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-2.5.json", + "referenceNumber": 464, + "name": "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic", + "licenseId": "CC-BY-NC-ND-2.5", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/2.5/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-enna.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-enna.json", + "referenceNumber": 465, + "name": "enna License", + "licenseId": "MIT-enna", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/MIT#enna" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-4.0.json", + "referenceNumber": 466, + "name": "Creative Commons Attribution Non Commercial No Derivatives 4.0 International", + "licenseId": "CC-BY-NC-ND-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSL-1.0.json", + "referenceNumber": 467, + "name": "Boost Software License 1.0", + "licenseId": "BSL-1.0", + "seeAlso": [ + "http://www.boost.org/LICENSE_1_0.txt", + "https://opensource.org/licenses/BSL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0-with-GCC-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0-with-GCC-exception.json", + "referenceNumber": 468, + "name": "GNU General Public License v2.0 w/GCC Runtime Library exception", + "licenseId": "GPL-2.0-with-GCC-exception", + "seeAlso": [ + "https://gcc.gnu.org/git/?p\u003dgcc.git;a\u003dblob;f\u003dgcc/libgcc1.c;h\u003d762f5143fc6eed57b6797c82710f3538aa52b40b;hb\u003dcb143a3ce4fb417c68f5fa2691a1b1b1053dfba9#l10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Linux-man-pages-copyleft.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Linux-man-pages-copyleft.json", + "referenceNumber": 469, + "name": "Linux man-pages Copyleft", + "licenseId": "Linux-man-pages-copyleft", + "seeAlso": [ + "https://www.kernel.org/doc/man-pages/licenses.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.0.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.0.json", + "referenceNumber": 470, + "name": "GNU Library General Public License v2 only", + "licenseId": "LGPL-2.0", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.0-standalone.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-4-Clause.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4-Clause.json", + "referenceNumber": 471, + "name": "BSD 4-Clause \"Original\" or \"Old\" License", + "licenseId": "BSD-4-Clause", + "seeAlso": [ + "http://directory.fsf.org/wiki/License:BSD_4Clause" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/QPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/QPL-1.0.json", + "referenceNumber": 472, + "name": "Q Public License 1.0", + "licenseId": "QPL-1.0", + "seeAlso": [ + "http://doc.qt.nokia.com/3.3/license.html", + "https://opensource.org/licenses/QPL-1.0", + "https://doc.qt.io/archives/3.3/license.html" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-1.2.json", + "referenceNumber": 473, + "name": "CERN Open Hardware Licence v1.2", + "licenseId": "CERN-OHL-1.2", + "seeAlso": [ + "https://www.ohwr.org/project/licenses/wikis/cern-ohl-v1.2" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/metamail.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/metamail.json", + "referenceNumber": 474, + "name": "metamail License", + "licenseId": "metamail", + "seeAlso": [ + "https://github.com/Dual-Life/mime-base64/blob/master/Base64.xs#L12" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/checkmk.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/checkmk.json", + "referenceNumber": 475, + "name": "Checkmk License", + "licenseId": "checkmk", + "seeAlso": [ + "https://github.com/libcheck/check/blob/master/checkmk/checkmk.in" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NTP-0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NTP-0.json", + "referenceNumber": 476, + "name": "NTP No Attribution", + "licenseId": "NTP-0", + "seeAlso": [ + "https://github.com/tytso/e2fsprogs/blob/master/lib/et/et_name.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Imlib2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Imlib2.json", + "referenceNumber": 477, + "name": "Imlib2 License", + "licenseId": "Imlib2", + "seeAlso": [ + "http://trac.enlightenment.org/e/browser/trunk/imlib2/COPYING", + "https://git.enlightenment.org/legacy/imlib2.git/tree/COPYING" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-ND-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-ND-3.0.json", + "referenceNumber": 478, + "name": "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported", + "licenseId": "CC-BY-NC-ND-3.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-nd/3.0/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.2.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.2.json", + "referenceNumber": 479, + "name": "Apple Public Source License 1.2", + "licenseId": "APSL-1.2", + "seeAlso": [ + "http://www.samurajdata.se/opensource/mirror/licenses/apsl.php" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Mup.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Mup.json", + "referenceNumber": 480, + "name": "Mup License", + "licenseId": "Mup", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Mup" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SSH-OpenSSH.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SSH-OpenSSH.json", + "referenceNumber": 481, + "name": "SSH OpenSSH license", + "licenseId": "SSH-OpenSSH", + "seeAlso": [ + "https://github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/LICENCE#L10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLFL-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLFL-1.3.json", + "referenceNumber": 482, + "name": "Open Logistics Foundation License Version 1.3", + "licenseId": "OLFL-1.3", + "seeAlso": [ + "https://openlogisticsfoundation.org/licenses/", + "https://opensource.org/license/olfl-1-3/" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-3-Clause-Open-MPI.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-3-Clause-Open-MPI.json", + "referenceNumber": 483, + "name": "BSD 3-Clause Open MPI variant", + "licenseId": "BSD-3-Clause-Open-MPI", + "seeAlso": [ + "https://www.open-mpi.org/community/license.php", + "http://www.netlib.org/lapack/LICENSE.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-3.0-with-autoconf-exception.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-3.0-with-autoconf-exception.json", + "referenceNumber": 484, + "name": "GNU General Public License v3.0 w/Autoconf exception", + "licenseId": "GPL-3.0-with-autoconf-exception", + "seeAlso": [ + "https://www.gnu.org/licenses/autoconf-exception-3.0.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EFL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EFL-1.0.json", + "referenceNumber": 485, + "name": "Eiffel Forum License v1.0", + "licenseId": "EFL-1.0", + "seeAlso": [ + "http://www.eiffel-nice.org/license/forum.txt", + "https://opensource.org/licenses/EFL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/Intel.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Intel.json", + "referenceNumber": 486, + "name": "Intel Open Source License", + "licenseId": "Intel", + "seeAlso": [ + "https://opensource.org/licenses/Intel" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/Beerware.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Beerware.json", + "referenceNumber": 487, + "name": "Beerware License", + "licenseId": "Beerware", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Beerware", + "https://people.freebsd.org/~phk/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CPL-1.0.json", + "referenceNumber": 488, + "name": "Common Public License 1.0", + "licenseId": "CPL-1.0", + "seeAlso": [ + "https://opensource.org/licenses/CPL-1.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/TermReadKey.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TermReadKey.json", + "referenceNumber": 489, + "name": "TermReadKey License", + "licenseId": "TermReadKey", + "seeAlso": [ + "https://github.com/jonathanstowe/TermReadKey/blob/master/README#L9-L10" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MulanPSL-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MulanPSL-2.0.json", + "referenceNumber": 490, + "name": "Mulan Permissive Software License, Version 2", + "licenseId": "MulanPSL-2.0", + "seeAlso": [ + "https://license.coscl.org.cn/MulanPSL2/" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CNRI-Jython.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CNRI-Jython.json", + "referenceNumber": 491, + "name": "CNRI Jython License", + "licenseId": "CNRI-Jython", + "seeAlso": [ + "http://www.jython.org/license.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SHL-0.51.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SHL-0.51.json", + "referenceNumber": 492, + "name": "Solderpad Hardware License, Version 0.51", + "licenseId": "SHL-0.51", + "seeAlso": [ + "https://solderpad.org/licenses/SHL-0.51/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-W-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-W-2.0.json", + "referenceNumber": 493, + "name": "CERN Open Hardware Licence Version 2 - Weakly Reciprocal", + "licenseId": "CERN-OHL-W-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-Patent.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-Patent.json", + "referenceNumber": 494, + "name": "BSD-2-Clause Plus Patent License", + "licenseId": "BSD-2-Clause-Patent", + "seeAlso": [ + "https://opensource.org/licenses/BSDplusPatent" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/SunPro.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SunPro.json", + "referenceNumber": 495, + "name": "SunPro License", + "licenseId": "SunPro", + "seeAlso": [ + "https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_acosh.c", + "https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_lgammal.c" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/TAPR-OHL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/TAPR-OHL-1.0.json", + "referenceNumber": 496, + "name": "TAPR Open Hardware License v1.0", + "licenseId": "TAPR-OHL-1.0", + "seeAlso": [ + "https://www.tapr.org/OHL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CERN-OHL-S-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CERN-OHL-S-2.0.json", + "referenceNumber": 497, + "name": "CERN Open Hardware Licence Version 2 - Strongly Reciprocal", + "licenseId": "CERN-OHL-S-2.0", + "seeAlso": [ + "https://www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/ZPL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/ZPL-1.1.json", + "referenceNumber": 498, + "name": "Zope Public License 1.1", + "licenseId": "ZPL-1.1", + "seeAlso": [ + "http://old.zope.org/Resources/License/ZPL-1.1" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CC-BY-4.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-4.0.json", + "referenceNumber": 499, + "name": "Creative Commons Attribution 4.0 International", + "licenseId": "CC-BY-4.0", + "seeAlso": [ + "https://creativecommons.org/licenses/by/4.0/legalcode" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/BitTorrent-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BitTorrent-1.0.json", + "referenceNumber": 500, + "name": "BitTorrent Open Source License v1.0", + "licenseId": "BitTorrent-1.0", + "seeAlso": [ + "http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/licenses/BitTorrent?r1\u003d1.1\u0026r2\u003d1.1.1.1\u0026diff_format\u003ds" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libselinux-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libselinux-1.0.json", + "referenceNumber": 501, + "name": "libselinux public domain notice", + "licenseId": "libselinux-1.0", + "seeAlso": [ + "https://github.com/SELinuxProject/selinux/blob/master/libselinux/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/AFL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/AFL-3.0.json", + "referenceNumber": 502, + "name": "Academic Free License v3.0", + "licenseId": "AFL-3.0", + "seeAlso": [ + "http://www.rosenlaw.com/AFL3.0.htm", + "https://opensource.org/licenses/afl-3.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/X11.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/X11.json", + "referenceNumber": 503, + "name": "X11 License", + "licenseId": "X11", + "seeAlso": [ + "http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-DE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CC-BY-NC-SA-3.0-DE.json", + "referenceNumber": 504, + "name": "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany", + "licenseId": "CC-BY-NC-SA-3.0-DE", + "seeAlso": [ + "https://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Bitstream-Vera.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Bitstream-Vera.json", + "referenceNumber": 505, + "name": "Bitstream Vera Font License", + "licenseId": "Bitstream-Vera", + "seeAlso": [ + "https://web.archive.org/web/20080207013128/http://www.gnome.org/fonts/", + "https://docubrain.com/sites/default/files/licenses/bitstream-vera.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/RSA-MD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/RSA-MD.json", + "referenceNumber": 506, + "name": "RSA Message-Digest License", + "licenseId": "RSA-MD", + "seeAlso": [ + "http://www.faqs.org/rfcs/rfc1321.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/BSD-4.3TAHOE.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-4.3TAHOE.json", + "referenceNumber": 507, + "name": "BSD 4.3 TAHOE License", + "licenseId": "BSD-4.3TAHOE", + "seeAlso": [ + "https://github.com/389ds/389-ds-base/blob/main/ldap/include/sysexits-compat.h#L15", + "https://git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id\u003da74c6b4ee49397cf330b333da1042bffa60ed14f#n1788" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/EPICS.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/EPICS.json", + "referenceNumber": 508, + "name": "EPICS Open License", + "licenseId": "EPICS", + "seeAlso": [ + "https://epics.anl.gov/license/open.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GPL-2.0+.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/GPL-2.0+.json", + "referenceNumber": 509, + "name": "GNU General Public License v2.0 or later", + "licenseId": "GPL-2.0+", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html", + "https://opensource.org/licenses/GPL-2.0" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OPL-1.0.json", + "referenceNumber": 510, + "name": "Open Public License v1.0", + "licenseId": "OPL-1.0", + "seeAlso": [ + "http://old.koalateam.com/jackaroo/OPL_1_0.TXT", + "https://fedoraproject.org/wiki/Licensing/Open_Public_License" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/SHL-0.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SHL-0.5.json", + "referenceNumber": 511, + "name": "Solderpad Hardware License v0.5", + "licenseId": "SHL-0.5", + "seeAlso": [ + "https://solderpad.org/licenses/SHL-0.5/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FSFULLRWD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFULLRWD.json", + "referenceNumber": 512, + "name": "FSF Unlimited License (With License Retention and Warranty Disclaimer)", + "licenseId": "FSFULLRWD", + "seeAlso": [ + "https://lists.gnu.org/archive/html/autoconf/2012-04/msg00061.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/WTFPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/WTFPL.json", + "referenceNumber": 513, + "name": "Do What The F*ck You Want To Public License", + "licenseId": "WTFPL", + "seeAlso": [ + "http://www.wtfpl.net/about/", + "http://sam.zoy.org/wtfpl/COPYING" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/OPUBL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OPUBL-1.0.json", + "referenceNumber": 514, + "name": "Open Publication License v1.0", + "licenseId": "OPUBL-1.0", + "seeAlso": [ + "http://opencontent.org/openpub/", + "https://www.debian.org/opl", + "https://www.ctan.org/license/opl" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LAL-1.3.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LAL-1.3.json", + "referenceNumber": 515, + "name": "Licence Art Libre 1.3", + "licenseId": "LAL-1.3", + "seeAlso": [ + "https://artlibre.org/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.3a.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.3a.json", + "referenceNumber": 516, + "name": "LaTeX Project Public License v1.3a", + "licenseId": "LPPL-1.3a", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-3a.txt" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NBPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NBPL-1.0.json", + "referenceNumber": 517, + "name": "Net Boolean Public License v1", + "licenseId": "NBPL-1.0", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d37b4b3f6cc4bf34e1d3dec61e69914b9819d8894" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OFL-1.1-RFN.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OFL-1.1-RFN.json", + "referenceNumber": 518, + "name": "SIL Open Font License 1.1 with Reserved Font Name", + "licenseId": "OFL-1.1-RFN", + "seeAlso": [ + "http://scripts.sil.org/cms/scripts/page.php?item_id\u003dOFL_web", + "https://opensource.org/licenses/OFL-1.1" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.0.json", + "referenceNumber": 519, + "name": "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", + "licenseId": "OLDAP-2.0", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003dcbf50f4e1185a21abd4c0a54d3f4341fe28f36ea" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/SGP4.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/SGP4.json", + "referenceNumber": 520, + "name": "SGP4 Permission Notice", + "licenseId": "SGP4", + "seeAlso": [ + "https://celestrak.org/publications/AIAA/2006-6753/faq.php" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.1-invariants-only.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.1-invariants-only.json", + "referenceNumber": 521, + "name": "GNU Free Documentation License v1.1 only - invariants", + "licenseId": "GFDL-1.1-invariants-only", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/fdl-1.1.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/CECILL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CECILL-1.1.json", + "referenceNumber": 522, + "name": "CeCILL Free Software License Agreement v1.1", + "licenseId": "CECILL-1.1", + "seeAlso": [ + "http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/GFDL-1.3-no-invariants-or-later.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/GFDL-1.3-no-invariants-or-later.json", + "referenceNumber": 523, + "name": "GNU Free Documentation License v1.3 or later - no invariants", + "licenseId": "GFDL-1.3-no-invariants-or-later", + "seeAlso": [ + "https://www.gnu.org/licenses/fdl-1.3.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Apache-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Apache-1.1.json", + "referenceNumber": 524, + "name": "Apache License 1.1", + "licenseId": "Apache-1.1", + "seeAlso": [ + "http://apache.org/licenses/LICENSE-1.1", + "https://opensource.org/licenses/Apache-1.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NLOD-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLOD-1.0.json", + "referenceNumber": 525, + "name": "Norwegian Licence for Open Government Data (NLOD) 1.0", + "licenseId": "NLOD-1.0", + "seeAlso": [ + "http://data.norge.no/nlod/en/1.0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Vim.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Vim.json", + "referenceNumber": 526, + "name": "Vim License", + "licenseId": "Vim", + "seeAlso": [ + "http://vimdoc.sourceforge.net/htmldoc/uganda.html" + ], + "isOsiApproved": false, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/PostgreSQL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/PostgreSQL.json", + "referenceNumber": 527, + "name": "PostgreSQL License", + "licenseId": "PostgreSQL", + "seeAlso": [ + "http://www.postgresql.org/about/licence", + "https://opensource.org/licenses/PostgreSQL" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/FSFULLR.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FSFULLR.json", + "referenceNumber": 528, + "name": "FSF Unlimited License (with License Retention)", + "licenseId": "FSFULLR", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/FSF_Unlimited_License#License_Retention_Variant" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/NLPL.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NLPL.json", + "referenceNumber": 529, + "name": "No Limit Public License", + "licenseId": "NLPL", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/NLPL" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Glulxe.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Glulxe.json", + "referenceNumber": 530, + "name": "Glulxe License", + "licenseId": "Glulxe", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Glulxe" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/libutil-David-Nugent.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/libutil-David-Nugent.json", + "referenceNumber": 531, + "name": "libutil David Nugent License", + "licenseId": "libutil-David-Nugent", + "seeAlso": [ + "http://web.mit.edu/freebsd/head/lib/libutil/login_ok.3", + "https://cgit.freedesktop.org/libbsd/tree/man/setproctitle.3bsd" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Zed.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Zed.json", + "referenceNumber": 532, + "name": "Zed License", + "licenseId": "Zed", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Zed" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/FreeImage.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/FreeImage.json", + "referenceNumber": 533, + "name": "FreeImage Public License v1.0", + "licenseId": "FreeImage", + "seeAlso": [ + "http://freeimage.sourceforge.net/freeimage-license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/0BSD.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/0BSD.json", + "referenceNumber": 534, + "name": "BSD Zero Clause License", + "licenseId": "0BSD", + "seeAlso": [ + "http://landley.net/toybox/license.html", + "https://opensource.org/licenses/0BSD" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CDLA-Sharing-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CDLA-Sharing-1.0.json", + "referenceNumber": 535, + "name": "Community Data License Agreement Sharing 1.0", + "licenseId": "CDLA-Sharing-1.0", + "seeAlso": [ + "https://cdla.io/sharing-1-0" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/APSL-1.1.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/APSL-1.1.json", + "referenceNumber": 536, + "name": "Apple Public Source License 1.1", + "licenseId": "APSL-1.1", + "seeAlso": [ + "http://www.opensource.apple.com/source/IOSerialFamily/IOSerialFamily-7/APPLE_LICENSE" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/JasPer-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JasPer-2.0.json", + "referenceNumber": 537, + "name": "JasPer License", + "licenseId": "JasPer-2.0", + "seeAlso": [ + "http://www.ece.uvic.ca/~mdadams/jasper/LICENSE" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/HTMLTIDY.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/HTMLTIDY.json", + "referenceNumber": 538, + "name": "HTML Tidy License", + "licenseId": "HTMLTIDY", + "seeAlso": [ + "https://github.com/htacg/tidy-html5/blob/next/README/LICENSE.md" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Newsletr.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Newsletr.json", + "referenceNumber": 539, + "name": "Newsletr License", + "licenseId": "Newsletr", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing/Newsletr" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.8.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.8.json", + "referenceNumber": 540, + "name": "Open LDAP Public License v2.8", + "licenseId": "OLDAP-2.8", + "seeAlso": [ + "http://www.openldap.org/software/release/license.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/LPPL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/LPPL-1.0.json", + "referenceNumber": 541, + "name": "LaTeX Project Public License v1.0", + "licenseId": "LPPL-1.0", + "seeAlso": [ + "http://www.latex-project.org/lppl/lppl-1-0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/O-UDA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/O-UDA-1.0.json", + "referenceNumber": 542, + "name": "Open Use of Data Agreement v1.0", + "licenseId": "O-UDA-1.0", + "seeAlso": [ + "https://github.com/microsoft/Open-Use-of-Data-Agreement/blob/v1.0/O-UDA-1.0.md", + "https://cdla.dev/open-use-of-data-agreement-v1-0/" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/JSON.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/JSON.json", + "referenceNumber": 543, + "name": "JSON License", + "licenseId": "JSON", + "seeAlso": [ + "http://www.json.org/license.html" + ], + "isOsiApproved": false, + "isFsfLibre": false + }, + { + "reference": "https://spdx.org/licenses/Unicode-DFS-2016.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Unicode-DFS-2016.json", + "referenceNumber": 544, + "name": "Unicode License Agreement - Data Files and Software (2016)", + "licenseId": "Unicode-DFS-2016", + "seeAlso": [ + "https://www.unicode.org/license.txt", + "http://web.archive.org/web/20160823201924/http://www.unicode.org/copyright.html#License", + "http://www.unicode.org/copyright.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/NICTA-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NICTA-1.0.json", + "referenceNumber": 545, + "name": "NICTA Public Software License, Version 1.0", + "licenseId": "NICTA-1.0", + "seeAlso": [ + "https://opensource.apple.com/source/mDNSResponder/mDNSResponder-320.10/mDNSPosix/nss_ReadMe.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/IEC-Code-Components-EULA.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/IEC-Code-Components-EULA.json", + "referenceNumber": 546, + "name": "IEC Code Components End-user licence agreement", + "licenseId": "IEC-Code-Components-EULA", + "seeAlso": [ + "https://www.iec.ch/webstore/custserv/pdf/CC-EULA.pdf", + "https://www.iec.ch/CCv1", + "https://www.iec.ch/copyright" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Elastic-2.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Elastic-2.0.json", + "referenceNumber": 547, + "name": "Elastic License 2.0", + "licenseId": "Elastic-2.0", + "seeAlso": [ + "https://www.elastic.co/licensing/elastic-license", + "https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE-2.0.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/MIT-Modern-Variant.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/MIT-Modern-Variant.json", + "referenceNumber": 548, + "name": "MIT License Modern Variant", + "licenseId": "MIT-Modern-Variant", + "seeAlso": [ + "https://fedoraproject.org/wiki/Licensing:MIT#Modern_Variants", + "https://ptolemy.berkeley.edu/copyright.htm", + "https://pirlwww.lpl.arizona.edu/resources/guide/software/PerlTk/Tixlic.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/OLDAP-2.5.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/OLDAP-2.5.json", + "referenceNumber": 549, + "name": "Open LDAP Public License v2.5", + "licenseId": "OLDAP-2.5", + "seeAlso": [ + "http://www.openldap.org/devel/gitweb.cgi?p\u003dopenldap.git;a\u003dblob;f\u003dLICENSE;hb\u003d6852b9d90022e8593c98205413380536b1b5a7cf" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Artistic-1.0-Perl.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Artistic-1.0-Perl.json", + "referenceNumber": 550, + "name": "Artistic License 1.0 (Perl)", + "licenseId": "Artistic-1.0-Perl", + "seeAlso": [ + "http://dev.perl.org/licenses/artistic.html" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/CAL-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/CAL-1.0.json", + "referenceNumber": 551, + "name": "Cryptographic Autonomy License 1.0", + "licenseId": "CAL-1.0", + "seeAlso": [ + "http://cryptographicautonomylicense.com/license-text.html", + "https://opensource.org/licenses/CAL-1.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/BSD-2-Clause-Views.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/BSD-2-Clause-Views.json", + "referenceNumber": 552, + "name": "BSD 2-Clause with views sentence", + "licenseId": "BSD-2-Clause-Views", + "seeAlso": [ + "http://www.freebsd.org/copyright/freebsd-license.html", + "https://people.freebsd.org/~ivoras/wine/patch-wine-nvidia.sh", + "https://github.com/protegeproject/protege/blob/master/license.txt" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/Interbase-1.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/Interbase-1.0.json", + "referenceNumber": 553, + "name": "Interbase Public License v1.0", + "licenseId": "Interbase-1.0", + "seeAlso": [ + "https://web.archive.org/web/20060319014854/http://info.borland.com/devsupport/interbase/opensource/IPL.html" + ], + "isOsiApproved": false + }, + { + "reference": "https://spdx.org/licenses/LGPL-2.1.html", + "isDeprecatedLicenseId": true, + "detailsUrl": "https://spdx.org/licenses/LGPL-2.1.json", + "referenceNumber": 554, + "name": "GNU Lesser General Public License v2.1 only", + "licenseId": "LGPL-2.1", + "seeAlso": [ + "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html", + "https://opensource.org/licenses/LGPL-2.1" + ], + "isOsiApproved": true, + "isFsfLibre": true + }, + { + "reference": "https://spdx.org/licenses/NPOSL-3.0.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/NPOSL-3.0.json", + "referenceNumber": 555, + "name": "Non-Profit Open Software License 3.0", + "licenseId": "NPOSL-3.0", + "seeAlso": [ + "https://opensource.org/licenses/NOSL3.0" + ], + "isOsiApproved": true + }, + { + "reference": "https://spdx.org/licenses/gSOAP-1.3b.html", + "isDeprecatedLicenseId": false, + "detailsUrl": "https://spdx.org/licenses/gSOAP-1.3b.json", + "referenceNumber": 556, + "name": "gSOAP Public License v1.3b", + "licenseId": "gSOAP-1.3b", + "seeAlso": [ + "http://www.cs.fsu.edu/~engelen/license.html" + ], + "isOsiApproved": false + } + ], + "releaseDate": "2023-06-18" +} \ No newline at end of file diff --git a/test/unit/license_test.rb b/test/unit/license_test.rb index f654981899..f75fd599df 100644 --- a/test/unit/license_test.rb +++ b/test/unit/license_test.rb @@ -2,8 +2,10 @@ class LicenseTest < ActiveSupport::TestCase setup do - @zenodo = Seek::License.zenodo[:all] - @od = Seek::License.open_definition[:all] + @zenodo = Seek::License.zenodo + @od = Seek::License.open_definition + @spdx = Seek::License.spdx + @combined = Seek::License.combined end test 'can find licenses in zenodo vocab' do @@ -39,6 +41,59 @@ class LicenseTest < ActiveSupport::TestCase assert_equal 'https://creativecommons.org/licenses/by/4.0/', license['url'] end + test 'can find licenses in spdx vocab' do + license = Seek::License.find('CC-BY-4.0', @spdx) + assert license.is_a?(Seek::License) + assert_equal 'Creative Commons Attribution 4.0 International', license.title + assert_equal 'https://creativecommons.org/licenses/by/4.0/legalcode', license.url + + license = Seek::License.find('Zed', @spdx) + assert license.is_a?(Seek::License) + assert_equal 'Zed License', license.title + assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license.url + end + + test 'can find licenses as hash in spdx vocab' do + license = Seek::License.find_as_hash('Zed', @spdx) + assert license.is_a?(Hash) + assert_equal 'Zed License', license['title'] + assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license['url'] + end + + test 'can find licenses in combined vocab' do + license = Seek::License.find('other-at', @combined) + assert license.is_a?(Seek::License) + assert_equal 'Other (Attribution)', license.title + assert_equal '', license.url + + license = Seek::License.find('notspecified', @combined) + assert license.is_a?(Seek::License) + assert_equal 'No license - no permission to use unless the owner grants a licence', license.title + assert_equal 'https://choosealicense.com/no-permission/', license.url + + license = Seek::License.find('Zed', @combined) + assert license.is_a?(Seek::License) + assert_equal 'Zed License', license.title + assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license.url + end + + test 'can find licenses as hash in combined vocab' do + license = Seek::License.find_as_hash('other-at', @combined) + assert license.is_a?(Hash) + assert_equal 'Other (Attribution)', license['title'] + assert_equal '', license['url'] + + license = Seek::License.find_as_hash('notspecified', @combined) + assert license.is_a?(Hash) + assert_equal 'No license - no permission to use unless the owner grants a licence', license['title'] + assert_equal 'https://choosealicense.com/no-permission/', license['url'] + + license = Seek::License.find_as_hash('Zed', @combined) + assert license.is_a?(Hash) + assert_equal 'Zed License', license['title'] + assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license['url'] + end + test 'returns nil when cannot find license' do assert_nil Seek::License.find('license-to-kill') assert_nil Seek::License.find('cc-by', @od) @@ -46,14 +101,14 @@ class LicenseTest < ActiveSupport::TestCase end test 'override notspecified text and url' do - refute_nil (license = Seek::License.find('notspecified')) + refute_nil(license = Seek::License.find('notspecified')) assert license.is_a?(Seek::License) assert license.is_null_license? assert_equal 'No license - no permission to use unless the owner grants a licence',license['title'] assert_equal 'https://choosealicense.com/no-permission/',license['url'] #double check the main hash - license_json = Seek::License.open_definition[:all].find{|x| x['id']=='notspecified'} + license_json = Seek::License.open_definition['notspecified'] assert_equal license,Seek::License.new(license_json) end From be6b21e0725e1612c00091d34e2956063b59075f Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 14:32:52 +0100 Subject: [PATCH 011/383] Use `select2` to allow filtering of the license selector --- app/assets/javascripts/application.js | 1 + .../javascripts/application_shared.js.erb | 2 ++ app/assets/javascripts/licenses.js | 24 +++++++++++++++++++ app/assets/javascripts/zenodo_form.js | 15 ------------ app/helpers/license_helper.rb | 6 +++-- app/views/assets/_license_selector.html.erb | 7 ------ app/views/projects/_project_selector.html.erb | 3 +-- app/views/snapshots/export_preview.html.erb | 2 -- 8 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 app/assets/javascripts/licenses.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4f80e6a46e..c2cecc0619 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -75,3 +75,4 @@ //= require git //= require jquery.splitter/jquery.splitter.min //= require select2.full.min +//= require licenses diff --git a/app/assets/javascripts/application_shared.js.erb b/app/assets/javascripts/application_shared.js.erb index a3c400fb40..3b8c59fe21 100644 --- a/app/assets/javascripts/application_shared.js.erb +++ b/app/assets/javascripts/application_shared.js.erb @@ -399,6 +399,8 @@ $j(document).ready(function () { $j('#sidebar-toggle').click(Sidebar.toggle); $j('#sidebar-close').click(Sidebar.toggle); $j(document).on('click', '.sidebar-backdrop', Sidebar.toggle); + + Licenses.init(); }); var URL_ROOT = '<%= Rails.application.config.relative_url_root %>'; diff --git a/app/assets/javascripts/licenses.js b/app/assets/javascripts/licenses.js new file mode 100644 index 0000000000..ed16307950 --- /dev/null +++ b/app/assets/javascripts/licenses.js @@ -0,0 +1,24 @@ +var Licenses = { + init: function () { + $j('[data-role="seek-license-select"]').change(Licenses.displayUrl); + $j('[data-role="seek-license-select"]').change(); + $j('[data-role="seek-license-select"]').select2({ theme: 'bootstrap' }); + }, + + displayUrl: function () { + var element = $j('option:selected', $j(this)); + var link = $j('#license-url'); + if (link.length) { + var block = link.parents('.license-url-block'); + + if (element.data('url')) { + block.show(); + } else { + block.hide(); + } + + link.attr('href', element.data('url')); + link.html(element.data('url')); + } + } +} diff --git a/app/assets/javascripts/zenodo_form.js b/app/assets/javascripts/zenodo_form.js index 8f003bee94..30c43b5722 100644 --- a/app/assets/javascripts/zenodo_form.js +++ b/app/assets/javascripts/zenodo_form.js @@ -1,21 +1,6 @@ var zenodoExport = { creatorList: [], - setLicenseUrl: function () { - var element = $j('#license-select option:selected'); - var link = $j('#license-url'); - var block = link.parents('.license-url-block'); - - if(element.data('url') == '') { - block.hide(); - } else { - block.show(); - } - - link.attr('href', element.data('url')); - link.html(element.data('url')); - }, - enableSection: function (sectionElement) { sectionElement.show().children(":input").prop("disabled", false); }, diff --git a/app/helpers/license_helper.rb b/app/helpers/license_helper.rb index e957006f9d..1907aab5c0 100644 --- a/app/helpers/license_helper.rb +++ b/app/helpers/license_helper.rb @@ -3,11 +3,13 @@ module LicenseHelper def grouped_license_select(name, selected = nil, opts = {}) + opts[:data] ||= {} + opts[:data]['role'] = 'seek-license-select' select_tag(name, grouped_options_for_select(grouped_license_options(opts), selected), opts) end - def describe_license(id, source = nil) - license = Seek::License.find(id, source) + def describe_license(id) + license = Seek::License.find(id) content = license_description_content(license) if !license || license.is_null_license? diff --git a/app/views/assets/_license_selector.html.erb b/app/views/assets/_license_selector.html.erb index 83e0f71a5d..9455392c4f 100644 --- a/app/views/assets/_license_selector.html.erb +++ b/app/views/assets/_license_selector.html.erb @@ -38,10 +38,3 @@
<% end %> - - diff --git a/app/views/projects/_project_selector.html.erb b/app/views/projects/_project_selector.html.erb index e0585fc7bf..dd9e624c7e 100644 --- a/app/views/projects/_project_selector.html.erb +++ b/app/views/projects/_project_selector.html.erb @@ -106,8 +106,7 @@ var licenseSelect = $j('#license-select'); if (licenseSelect.length && licenseSelect.data('canOverwrite') && $j('option[value="'+Sharing.defaultLicenses[project.id]+'"]', licenseSelect).length) { - licenseSelect.val(Sharing.defaultLicenses[project.id]); - zenodoExport.setLicenseUrl(); + licenseSelect.val(Sharing.defaultLicenses[project.id]).change(); } } this.possibilities.splice(i, 1); diff --git a/app/views/snapshots/export_preview.html.erb b/app/views/snapshots/export_preview.html.erb index bd7dc27ed8..2badf6ab93 100644 --- a/app/views/snapshots/export_preview.html.erb +++ b/app/views/snapshots/export_preview.html.erb @@ -89,7 +89,6 @@ sort_by { |p| p[:name] }.to_json.html_safe %>; $j(document).ready(function () { - $j('#license-select').change(zenodoExport.setLicenseUrl); $j('#access-right-section input:radio').change(zenodoExport.toggleSections); $j('#add-creator-button').click(zenodoExport.addCreator); $j('#add-creator').keypress(function(e) { @@ -101,7 +100,6 @@ }); zenodoExport.toggleSections(); - zenodoExport.setLicenseUrl(); zenodoExport.renderCreatorList(); }); From aee773e98e031bcdfbf495b440d112e6e5c8ccc7 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 15:15:37 +0100 Subject: [PATCH 012/383] Use `select2` for recommended licenses Don't group select options if no recommended licenses provided --- app/controllers/admin_controller.rb | 4 +- app/helpers/admin_helper.rb | 8 +-- app/helpers/license_helper.rb | 23 ++++++--- app/views/admin/settings.html.erb | 56 ++++++++++++++++----- app/views/assets/_license_selector.html.erb | 2 +- app/views/snapshots/export_preview.html.erb | 2 +- 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 218c6a81b4..ae3cfc8d31 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -331,8 +331,8 @@ def update_settings Seek::Config.default_license = params[:default_license] Seek::Config.metadata_license = params[:metadata_license] - Seek::Config.recommended_data_licenses = params[:recommended_data_licenses] - Seek::Config.recommended_software_licenses = params[:recommended_software_licenses] + Seek::Config.recommended_data_licenses = params[:recommended_data_licenses].compact_blank + Seek::Config.recommended_software_licenses = params[:recommended_software_licenses].compact_blank update_flag = (pubmed_email == '' || pubmed_email_valid) && (crossref_email == '' || crossref_email_valid) update_redirect_to update_flag, 'settings' end diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 3b8261316c..ab67f6ea1b 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -94,11 +94,11 @@ def git_link_tag end end - def admin_setting_block(title, description) + def admin_setting_block(title, description = nil) content_tag(:div, class: 'form-group') do - content_tag(:label, title) + - (description ? content_tag(:p, description.html_safe, class: 'help-block') : ''.html_safe) + - yield + concat content_tag(:label, title) + concat content_tag(:p, description.html_safe, class: 'help-block') if description + concat yield end end end diff --git a/app/helpers/license_helper.rb b/app/helpers/license_helper.rb index 1907aab5c0..68bfe9dd41 100644 --- a/app/helpers/license_helper.rb +++ b/app/helpers/license_helper.rb @@ -2,10 +2,17 @@ require 'seek/license' module LicenseHelper - def grouped_license_select(name, selected = nil, opts = {}) + def license_select(name, selected = nil, opts = {}) opts[:data] ||= {} - opts[:data]['role'] = 'seek-license-select' - select_tag(name, grouped_options_for_select(grouped_license_options(opts), selected), opts) + opts[:data]['role'] ||= 'seek-license-select' + recommended = opts.delete(:recommended) + source = opts.delete(:source) || Seek::License.combined + if recommended + options = grouped_options_for_select(grouped_license_options(source, recommended), selected) + else + options = options_for_select(license_options(source), selected) + end + select_tag(name, options, opts) end def describe_license(id) @@ -65,9 +72,13 @@ def license_description_content(license) end end - def grouped_license_options(opts = {}) - source = opts.delete(:source) || Seek::License.combined - recommended = opts.delete(:recommended) + def license_options(source) + source.values.map do |value| + [value['title'], value['id'], { 'data-url' => value['url'] }] + end.sort_by!(&:first) + end + + def grouped_license_options(source, recommended) grouped_licenses = source.values.group_by do |l| if recommended&.include?(l['id']) 'recommended' diff --git a/app/views/admin/settings.html.erb b/app/views/admin/settings.html.erb index aae32a23e4..ab9577f768 100644 --- a/app/views/admin/settings.html.erb +++ b/app/views/admin/settings.html.erb @@ -56,21 +56,28 @@ admin_dropdown_setting(:max_all_visitors_access_type, option_tags, 'Permission Limits', 'Maximum permission option for all visitors, including those without a login') %> - <%= admin_setting_block('Default License', "The default license to use when one is not specified by a #{t('project').pluralize}.") do - grouped_license_select(:default_license, Seek::Config.default_license, id: 'license-select', - class: 'form-control', source: Seek::License.combined) - end %> - - <%= admin_setting_block('Recommended data licenses', "The licenses to recommend when data, such as a #{t('data_file')} or #{t('document')}, is registered. Hold down CTRL (or Command on Mac) to select multiple or to deselect.") do - select_tag :recommended_data_licenses, options_for_select(Seek::License.combined.map { |id, l| [l['title'], id]}, Seek::Config.recommended_data_licenses), multiple: true, class: 'form-control' - end %> - - <%= admin_setting_block('Recommended software licenses', "The licenses to recommend when software such as a #{t('workflow')}, is registered. Hold down CTRL (or Command on Mac) to select multiple or to deselect.") do - select_tag :recommended_software_licenses, options_for_select(Seek::License.combined.map { |id, l| [l['title'], id]}, Seek::Config.recommended_software_licenses), multiple: true, class: 'form-control' - end %> + <%= admin_setting_block('Default License', "The default license to use when one is not specified by a #{t('project').pluralize}.") do %> + <%= license_select(:default_license, Seek::Config.default_license, id: 'license-select', class: 'form-control') %> + <% end %> + + <%= admin_setting_block('Recommended data licenses', "The licenses to recommend when data, such as a #{t('data_file')} or #{t('document')}, is registered.") do %> + <%= associations_list('recommended-data-licenses', 'associations/general', + Seek::Config.recommended_data_licenses.map { |id| { id: id, title: Seek::License.find(id).title } }.to_json, + 'data-field-name' => 'recommended_data_licenses', + empty_text: 'No recommended licenses') %> + <%= license_select(:_recommended_data_license_select, nil, prompt: 'Type to select a license', class: 'form-control') %> + <% end %> + + <%= admin_setting_block('Recommended software licenses', "The licenses to recommend when software, such as a #{t('workflow')}, is registered.") do %> + <%= associations_list('recommended-software-licenses', 'associations/general', + Seek::Config.recommended_software_licenses.map { |id| { id: id, title: Seek::License.find(id).title } }.to_json, + 'data-field-name' => 'recommended_software_licenses', + empty_text: 'No recommended licenses') %> + <%= license_select(:_recommended_software_license_select, nil, prompt: 'Type to select a license', class: 'form-control') %> + <% end %> <%= admin_setting_block('Metadata License', "The license granted on metadata produced by this SEEK instance.") do - grouped_license_select(:metadata_license, Seek::Config.metadata_license, id: 'metadata-license-select', + license_select(:metadata_license, Seek::Config.metadata_license, id: 'metadata-license-select', class: 'form-control', source: Seek::License.combined) end %> @@ -122,3 +129,26 @@ <%= submit_tag "Update", data: { disable_with: 'Updating...' }, :class => 'btn btn-primary' -%> or <%= cancel_button admin_path %> <% end -%> + + + \ No newline at end of file diff --git a/app/views/assets/_license_selector.html.erb b/app/views/assets/_license_selector.html.erb index 9455392c4f..43caab681f 100644 --- a/app/views/assets/_license_selector.html.erb +++ b/app/views/assets/_license_selector.html.erb @@ -27,7 +27,7 @@
- <%= grouped_license_select("#{resource.class.name.underscore}[#{accessor.to_s}]", selected_license, + <%= license_select("#{resource.class.name.underscore}[#{accessor.to_s}]", selected_license, id: 'license-select', class: 'form-control', data: { 'can-overwrite': using_default }, source: Seek::License.combined, diff --git a/app/views/snapshots/export_preview.html.erb b/app/views/snapshots/export_preview.html.erb index 2badf6ab93..ff130bd7a8 100644 --- a/app/views/snapshots/export_preview.html.erb +++ b/app/views/snapshots/export_preview.html.erb @@ -33,7 +33,7 @@
- <%= grouped_license_select('metadata[license]', 'cc-by', :id => 'license-select', :class => 'form-control', + <%= license_select('metadata[license]', 'cc-by', :id => 'license-select', :class => 'form-control', :source => Seek::License.zenodo) %> For more information on this license, please visit From 50f947aaad02b7a8f1db87175b5a2f0d2d7dfc3e Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 15:28:53 +0100 Subject: [PATCH 013/383] Sort "null" license first in list --- app/helpers/license_helper.rb | 24 +++++++++++++++--------- app/views/admin/settings.html.erb | 4 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/helpers/license_helper.rb b/app/helpers/license_helper.rb index 68bfe9dd41..a85fa81a11 100644 --- a/app/helpers/license_helper.rb +++ b/app/helpers/license_helper.rb @@ -8,9 +8,9 @@ def license_select(name, selected = nil, opts = {}) recommended = opts.delete(:recommended) source = opts.delete(:source) || Seek::License.combined if recommended - options = grouped_options_for_select(grouped_license_options(source, recommended), selected) + options = grouped_options_for_select(grouped_license_options(source.values, recommended), selected) else - options = options_for_select(license_options(source), selected) + options = options_for_select(license_options(source.values), selected) end select_tag(name, options, opts) end @@ -72,14 +72,20 @@ def license_description_content(license) end end - def license_options(source) - source.values.map do |value| + def license_options(licenses) + licenses.map do |value| [value['title'], value['id'], { 'data-url' => value['url'] }] - end.sort_by!(&:first) + end.sort_by do |value| + if value[1] == Seek::License::NULL_LICENSE # Put null license first + '-' + else + value[0] # Otherwise sort by title + end + end end - def grouped_license_options(source, recommended) - grouped_licenses = source.values.group_by do |l| + def grouped_license_options(licenses, recommended) + grouped_licenses = licenses.group_by do |l| if recommended&.include?(l['id']) 'recommended' else @@ -87,8 +93,8 @@ def grouped_license_options(source, recommended) end end - grouped_licenses.transform_values! do |licenses| - licenses.map! { |value| [value['title'], value['id'], { 'data-url' => value['url'] }] }.sort_by!(&:first) + grouped_licenses.transform_values! do |l| + license_options(l) end # Transform into array to ensure recommended licenses come first diff --git a/app/views/admin/settings.html.erb b/app/views/admin/settings.html.erb index ab9577f768..5296ef1fb0 100644 --- a/app/views/admin/settings.html.erb +++ b/app/views/admin/settings.html.erb @@ -65,7 +65,7 @@ Seek::Config.recommended_data_licenses.map { |id| { id: id, title: Seek::License.find(id).title } }.to_json, 'data-field-name' => 'recommended_data_licenses', empty_text: 'No recommended licenses') %> - <%= license_select(:_recommended_data_license_select, nil, prompt: 'Type to select a license', class: 'form-control') %> + <%= license_select(:_recommended_data_license_select, nil, prompt: 'Select a license to add it to the list...', class: 'form-control') %> <% end %> <%= admin_setting_block('Recommended software licenses', "The licenses to recommend when software, such as a #{t('workflow')}, is registered.") do %> @@ -73,7 +73,7 @@ Seek::Config.recommended_software_licenses.map { |id| { id: id, title: Seek::License.find(id).title } }.to_json, 'data-field-name' => 'recommended_software_licenses', empty_text: 'No recommended licenses') %> - <%= license_select(:_recommended_software_license_select, nil, prompt: 'Type to select a license', class: 'form-control') %> + <%= license_select(:_recommended_software_license_select, nil, prompt: 'Select a license to add it to the list...', class: 'form-control') %> <% end %> <%= admin_setting_block('Metadata License', "The license granted on metadata produced by this SEEK instance.") do From 5164025a56ec392daee1fbb7ca56487d7c0d3a26 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 15:48:53 +0100 Subject: [PATCH 014/383] Test fix to use new default --- test/unit/license_test.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/unit/license_test.rb b/test/unit/license_test.rb index f75fd599df..8219795558 100644 --- a/test/unit/license_test.rb +++ b/test/unit/license_test.rb @@ -27,11 +27,6 @@ class LicenseTest < ActiveSupport::TestCase assert license.is_a?(Seek::License) assert_equal 'Creative Commons Attribution 4.0', license.title assert_equal 'https://creativecommons.org/licenses/by/4.0/', license.url - - license = Seek::License.find('CC-BY-4.0') - assert license.is_a?(Seek::License) - assert_equal 'Creative Commons Attribution 4.0', license.title - assert_equal 'https://creativecommons.org/licenses/by/4.0/', license.url end test 'can find licenses as hash in opendefinition vocab' do @@ -75,6 +70,11 @@ class LicenseTest < ActiveSupport::TestCase assert license.is_a?(Seek::License) assert_equal 'Zed License', license.title assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license.url + + license = Seek::License.find('CC-BY-4.0') + assert license.is_a?(Seek::License) + assert_equal 'Creative Commons Attribution 4.0 International', license.title + assert_equal 'https://creativecommons.org/licenses/by/4.0/legalcode', license.url end test 'can find licenses as hash in combined vocab' do @@ -92,6 +92,11 @@ class LicenseTest < ActiveSupport::TestCase assert license.is_a?(Hash) assert_equal 'Zed License', license['title'] assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license['url'] + + license = Seek::License.find_as_hash('CC-BY-4.0') + assert license.is_a?(Hash) + assert_equal 'Creative Commons Attribution 4.0 International', license['title'] + assert_equal 'https://creativecommons.org/licenses/by/4.0/legalcode', license['url'] end test 'returns nil when cannot find license' do From 58645dcf2a46fa62e83ba5c20065a7fb92b8beaf Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 16:48:34 +0100 Subject: [PATCH 015/383] Use spdx.org URL as license URL. Fix tests --- lib/seek/license.rb | 2 +- test/functional/data_files_controller_test.rb | 8 ++++---- test/functional/models_controller_test.rb | 8 ++++---- test/functional/presentations_controller_test.rb | 8 ++++---- test/functional/sops_controller_test.rb | 8 ++++---- test/functional/workflows_controller_test.rb | 8 ++++---- test/unit/bio_schema/decorator_test.rb | 2 +- test/unit/bio_schema/schema_ld_generation_test.rb | 4 ++-- test/unit/helpers/resource_list_helper_test.rb | 2 +- test/unit/license_test.rb | 14 +++++++------- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/lib/seek/license.rb b/lib/seek/license.rb index 7beaa64b9c..d41406f610 100644 --- a/lib/seek/license.rb +++ b/lib/seek/license.rb @@ -24,7 +24,7 @@ class License < OpenStruct hash[l['licenseId']] = { 'title' => l['name'], 'id' => l['licenseId'], - 'url' => (l['seeAlso'] || []).first || l['reference'] + 'url' => l['reference'].chomp('.html') } end hash diff --git a/test/functional/data_files_controller_test.rb b/test/functional/data_files_controller_test.rb index 0f3f355d8d..abf36b83f9 100644 --- a/test/functional/data_files_controller_test.rb +++ b/test/functional/data_files_controller_test.rb @@ -2094,7 +2094,7 @@ def test_show_item_attributed_to_jerm_file get :show, params: { id: df } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' end test 'should display license for current version' do @@ -2105,11 +2105,11 @@ def test_show_item_attributed_to_jerm_file get :show, params: { id: df, version: 1 } assert_response :success - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' get :show, params: { id: df, version: dfv.version } assert_response :success - assert_select '.panel .panel-body a', text: 'CC0 1.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Zero v1.0 Universal' end test 'should update license' do @@ -2123,7 +2123,7 @@ def test_show_item_attributed_to_jerm_file assert_response :redirect get :show, params: { id: df } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share-Alike 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share Alike 4.0 International' assert_equal 'CC-BY-SA-4.0', assigns(:data_file).license end diff --git a/test/functional/models_controller_test.rb b/test/functional/models_controller_test.rb index d9dfac8a73..49a122ecfe 100644 --- a/test/functional/models_controller_test.rb +++ b/test/functional/models_controller_test.rb @@ -1040,7 +1040,7 @@ def test_update_should_not_overwrite_contributor get :show, params: { id: model } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' end test 'should display license for current version' do @@ -1051,11 +1051,11 @@ def test_update_should_not_overwrite_contributor get :show, params: { id: model, version: 1 } assert_response :success - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' get :show, params: { id: model, version: modelv.version } assert_response :success - assert_select '.panel .panel-body a', text: 'CC0 1.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Zero v1.0 Universal' end test 'should update license' do @@ -1070,7 +1070,7 @@ def test_update_should_not_overwrite_contributor assert_response :redirect get :show, params: { id: model } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share-Alike 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share Alike 4.0 International' assert_equal 'CC-BY-SA-4.0', assigns(:model).license end diff --git a/test/functional/presentations_controller_test.rb b/test/functional/presentations_controller_test.rb index 071fe16201..6f8abaf093 100644 --- a/test/functional/presentations_controller_test.rb +++ b/test/functional/presentations_controller_test.rb @@ -315,7 +315,7 @@ def setup get :show, params: { id: presentation } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' end test 'should display license for current version' do @@ -326,11 +326,11 @@ def setup get :show, params: { id: presentation, version: 1 } assert_response :success - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' get :show, params: { id: presentation, version: presentationv.version } assert_response :success - assert_select '.panel .panel-body a', text: 'CC0 1.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Zero v1.0 Universal' end test 'should update license' do @@ -345,7 +345,7 @@ def setup assert_response :redirect get :show, params: { id: presentation } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share-Alike 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share Alike 4.0 International' assert_equal 'CC-BY-SA-4.0', assigns(:presentation).license end diff --git a/test/functional/sops_controller_test.rb b/test/functional/sops_controller_test.rb index 9a70d565ce..d6859c4011 100644 --- a/test/functional/sops_controller_test.rb +++ b/test/functional/sops_controller_test.rb @@ -1051,7 +1051,7 @@ def test_editing_doesnt_change_contributor get :show, params: { id: sop } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' end test 'should display license for current version' do @@ -1062,11 +1062,11 @@ def test_editing_doesnt_change_contributor get :show, params: { id: sop, version: 1 } assert_response :success - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' get :show, params: { id: sop, version: sopv.version } assert_response :success - assert_select '.panel .panel-body a', text: 'CC0 1.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Zero v1.0 Universal' end test 'should update license' do @@ -1079,7 +1079,7 @@ def test_editing_doesnt_change_contributor assert_response :redirect get :show, params: { id: sop } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share-Alike 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share Alike 4.0 International' assert_equal 'CC-BY-SA-4.0', assigns(:sop).license end diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index b452baa3ea..9d30c4b2f2 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -220,7 +220,7 @@ def setup get :show, params: { id: workflow } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' end test 'should display license for current version' do @@ -231,11 +231,11 @@ def setup get :show, params: { id: workflow, version: 1 } assert_response :success - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution 4.0 International' get :show, params: { id: workflow, version: workflowv.version } assert_response :success - assert_select '.panel .panel-body a', text: 'CC0 1.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Zero v1.0 Universal' end test 'should update license' do @@ -250,7 +250,7 @@ def setup assert_response :redirect get :show, params: { id: workflow } - assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share-Alike 4.0' + assert_select '.panel .panel-body a', text: 'Creative Commons Attribution Share Alike 4.0 International' assert_equal 'CC-BY-SA-4.0', assigns(:workflow).license end diff --git a/test/unit/bio_schema/decorator_test.rb b/test/unit/bio_schema/decorator_test.rb index 8e87095e53..03dcc49747 100644 --- a/test/unit/bio_schema/decorator_test.rb +++ b/test/unit/bio_schema/decorator_test.rb @@ -27,7 +27,7 @@ class DecoratorTest < ActiveSupport::TestCase identifier = "http://localhost:3000/documents/#{document.id}" assert_equal identifier, decorator.identifier assert_equal %w[lorry yellow], decorator.keywords.split(',').collect(&:strip).sort - assert_equal 'https://creativecommons.org/licenses/by/4.0/', decorator.license + assert_equal 'https://spdx.org/licenses/CC-BY-4.0', decorator.license assert_equal 'application/pdf', decorator.content_type project = document.projects.first person = document.creators.first diff --git a/test/unit/bio_schema/schema_ld_generation_test.rb b/test/unit/bio_schema/schema_ld_generation_test.rb index 1a9a844db9..1806135cff 100644 --- a/test/unit/bio_schema/schema_ld_generation_test.rb +++ b/test/unit/bio_schema/schema_ld_generation_test.rb @@ -858,7 +858,7 @@ def setup 'name' => 'Workflows', 'url' => 'http://localhost:3000/workflows', 'keywords' => [], - 'license' => 'https://creativecommons.org/publicdomain/zero/1.0/', + 'license' => 'https://spdx.org/licenses/CC0-1.0', 'creator' => [{'@type' => 'Organization', '@id' => 'http://www.sysmo-db.org', 'name' => 'SysMO-DB', @@ -892,7 +892,7 @@ def setup 'name' => 'Workflows', 'url' => 'http://localhost:3000/workflows', 'keywords' => [], - 'license' => 'https://creativecommons.org/licenses/by/4.0/', + 'license' => 'https://spdx.org/licenses/CC-BY-4.0', 'creator' => [{ '@type' => 'Organization', '@id' => 'http://www.sysmo-db.org', 'name' => 'SysMO-DB', diff --git a/test/unit/helpers/resource_list_helper_test.rb b/test/unit/helpers/resource_list_helper_test.rb index d9dd811ed2..cd356228f8 100644 --- a/test/unit/helpers/resource_list_helper_test.rb +++ b/test/unit/helpers/resource_list_helper_test.rb @@ -24,7 +24,7 @@ class ResourceListHelperTest < ActionView::TestCase assert_equal link_to(data_file.contributor.name, data_file.contributor), resource_list_column_display_value(data_file, 'contributor') - assert_equal link_to('Creative Commons Attribution 4.0', 'https://creativecommons.org/licenses/by/4.0/', target: :_blank), + assert_equal link_to('Creative Commons Attribution 4.0 International', 'https://spdx.org/licenses/CC-BY-4.0', target: :_blank), resource_list_column_display_value(data_file, 'license') assert_match(/href="#{data_file_path(data_file)}".*#{data_file.title}/, diff --git a/test/unit/license_test.rb b/test/unit/license_test.rb index 8219795558..c4b250a358 100644 --- a/test/unit/license_test.rb +++ b/test/unit/license_test.rb @@ -40,19 +40,19 @@ class LicenseTest < ActiveSupport::TestCase license = Seek::License.find('CC-BY-4.0', @spdx) assert license.is_a?(Seek::License) assert_equal 'Creative Commons Attribution 4.0 International', license.title - assert_equal 'https://creativecommons.org/licenses/by/4.0/legalcode', license.url + assert_equal 'https://spdx.org/licenses/CC-BY-4.0', license.url license = Seek::License.find('Zed', @spdx) assert license.is_a?(Seek::License) assert_equal 'Zed License', license.title - assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license.url + assert_equal 'https://spdx.org/licenses/Zed', license.url end test 'can find licenses as hash in spdx vocab' do license = Seek::License.find_as_hash('Zed', @spdx) assert license.is_a?(Hash) assert_equal 'Zed License', license['title'] - assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license['url'] + assert_equal 'https://spdx.org/licenses/Zed', license['url'] end test 'can find licenses in combined vocab' do @@ -69,12 +69,12 @@ class LicenseTest < ActiveSupport::TestCase license = Seek::License.find('Zed', @combined) assert license.is_a?(Seek::License) assert_equal 'Zed License', license.title - assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license.url + assert_equal 'https://spdx.org/licenses/Zed', license.url license = Seek::License.find('CC-BY-4.0') assert license.is_a?(Seek::License) assert_equal 'Creative Commons Attribution 4.0 International', license.title - assert_equal 'https://creativecommons.org/licenses/by/4.0/legalcode', license.url + assert_equal 'https://spdx.org/licenses/CC-BY-4.0', license.url end test 'can find licenses as hash in combined vocab' do @@ -91,12 +91,12 @@ class LicenseTest < ActiveSupport::TestCase license = Seek::License.find_as_hash('Zed', @combined) assert license.is_a?(Hash) assert_equal 'Zed License', license['title'] - assert_equal 'https://fedoraproject.org/wiki/Licensing/Zed', license['url'] + assert_equal 'https://spdx.org/licenses/Zed', license['url'] license = Seek::License.find_as_hash('CC-BY-4.0') assert license.is_a?(Hash) assert_equal 'Creative Commons Attribution 4.0 International', license['title'] - assert_equal 'https://creativecommons.org/licenses/by/4.0/legalcode', license['url'] + assert_equal 'https://spdx.org/licenses/CC-BY-4.0', license['url'] end test 'returns nil when cannot find license' do From daabe6eddc07dcaf2c2527bd9eb2f22ec8121c0a Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 17:44:45 +0100 Subject: [PATCH 016/383] Fix nil error --- app/controllers/admin_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index ae3cfc8d31..bacc1830b1 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -331,8 +331,8 @@ def update_settings Seek::Config.default_license = params[:default_license] Seek::Config.metadata_license = params[:metadata_license] - Seek::Config.recommended_data_licenses = params[:recommended_data_licenses].compact_blank - Seek::Config.recommended_software_licenses = params[:recommended_software_licenses].compact_blank + Seek::Config.recommended_data_licenses = params[:recommended_data_licenses]&.compact_blank + Seek::Config.recommended_software_licenses = params[:recommended_software_licenses]&.compact_blank update_flag = (pubmed_email == '' || pubmed_email_valid) && (crossref_email == '' || crossref_email_valid) update_redirect_to update_flag, 'settings' end From 6027cc966ff431faaf07b71f7c6e4db421657009 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 18:26:40 +0100 Subject: [PATCH 017/383] Allow setting license by URI --- app/validators/license_validator.rb | 10 +++- lib/seek/license.rb | 56 +++++++++++------- .../ro-crate-with-uri-license.crate.zip | Bin 0 -> 3168 bytes .../git_workflow_ro_crate_api_test.rb | 14 +++++ test/unit/license_test.rb | 24 +++++++- 5 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 test/fixtures/files/workflows/ro-crate-with-uri-license.crate.zip diff --git a/app/validators/license_validator.rb b/app/validators/license_validator.rb index 1ca5fefb7d..047e83dd43 100644 --- a/app/validators/license_validator.rb +++ b/app/validators/license_validator.rb @@ -2,7 +2,15 @@ class LicenseValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return if Seek::License.find(value) - record.errors.add(attribute, options[:message] || "isn't a valid license ID") + # Try looking up by URI + if value.start_with? /https?:/ + id = Seek::License.uri_to_id(value) + if id + record.send("#{attribute}=", id) + return + end + end + record.errors.add(attribute, options[:message] || "isn't a recognized license") end end \ No newline at end of file diff --git a/lib/seek/license.rb b/lib/seek/license.rb index d41406f610..7bcd929724 100644 --- a/lib/seek/license.rb +++ b/lib/seek/license.rb @@ -2,20 +2,15 @@ module Seek class License < OpenStruct NULL_LICENSE = 'notspecified'.freeze - # overrides values taken from the JSON. - # Preferable to modifying the JSON file directly which is a definitive source and may be replaced with an updated copy - private_class_method def self.override_json(json) - json['notspecified']['title'] = I18n.t('null_license') - json['notspecified']['url'] = Seek::Help::HelpDictionary.instance.help_link(:null_license) - json - end - - private_class_method def self.parse_zenodo(licenses) - hash = {} - licenses.each do |l| - hash[l['id']] = l + private_class_method def self.parse_od(licenses) + # overrides values taken from the JSON. + # Preferable to modifying the JSON file directly which is a definitive source and may be replaced with an updated copy + licenses['notspecified']['title'] = I18n.t('null_license') + licenses['notspecified']['url'] = Seek::Help::HelpDictionary.instance.help_link(:null_license) + licenses.each_value do |license| + license['urls'] = [license['url']].compact_blank end - hash + licenses end private_class_method def self.parse_spdx(licenses) @@ -24,18 +19,19 @@ class License < OpenStruct hash[l['licenseId']] = { 'title' => l['name'], 'id' => l['licenseId'], - 'url' => l['reference'].chomp('.html') + 'url' => l['reference'].chomp('.html'), + 'urls' => [l['reference'].chomp('.html'), l['reference'], l['detailsUrl'], *l['seeAlso']].compact_blank } end hash end - private_class_method def self.combine(*licenses) - combined = {} - licenses.each do |set| - set.each { |l| combined[l['id']] ||= l } + private_class_method def self.parse_zenodo(licenses) + hash = {} + licenses.each do |l| + hash[l['id']] = l end - combined + hash end def self.find(id, source = Seek::License.combined) @@ -44,6 +40,10 @@ def self.find(id, source = Seek::License.combined) end end + def self.uri_to_id(uri) + uri_map[uri] + end + def self.find_as_hash(id, source = Seek::License.combined) source[id] end @@ -53,7 +53,10 @@ def is_null_license? end def self.combined - @combined ||= open_definition.merge(spdx) + @combined ||= open_definition.merge(spdx) do |key, od_license, spdx_license| + spdx_license['urls'] |= od_license['urls'] # Merge together alternate URLs + spdx_license + end end def self.spdx @@ -61,11 +64,22 @@ def self.spdx end def self.open_definition - @od_licenses ||= override_json(JSON.parse(File.read(File.join(Rails.root, 'public', 'od_licenses.json')))) + @od_licenses ||= parse_od(JSON.parse(File.read(File.join(Rails.root, 'public', 'od_licenses.json')))) end def self.zenodo @zenodo_licenses ||= parse_zenodo(JSON.parse(File.read(File.join(Rails.root, 'public', 'zenodo_licenses.json')))) end + + def self.uri_map + return @uri_map if @uri_map + @uri_map = {} + combined.each do |id, license| + (license['urls'] || []).each do |url| + @uri_map[url] ||= id + end + end + @uri_map + end end end diff --git a/test/fixtures/files/workflows/ro-crate-with-uri-license.crate.zip b/test/fixtures/files/workflows/ro-crate-with-uri-license.crate.zip new file mode 100644 index 0000000000000000000000000000000000000000..d1c1d9ca68c57be65c34ac57d9ffba985b4224f2 GIT binary patch literal 3168 zcmbW42{cr1AIGo8z7&HQZ5Uf5OtM6TkV$rCB#Md2NHe6d{j*FeDy6a|8Edx4HoaNH z7>&~3zSSU=vS*Mi+1~Lly_|H;`@Z*{=iK|8^E=8yB-#8R{9B8Y<#kwCgE+S9E#VS{6`~={;@wX4zCbNWq!&E|=H{Vs|C- z4lpjObA91}plD~8cfWXMbsEvxufvE)kP{f#j`&W1g%vylI?f#~Fhd+dQeTSRIM)hQ zcmx_T(Iid9zn!UYa_kz&68d5nWbrEL1uDr0RVt7)A909aP~qIFbX&f z0que&pcT(u^uRaR_PKY%`GXja=$?0`ZFmh4ISC} zKB#U+@nql5?tLhcX zG$QC_yN9+yuYwIlysMRW@s@-M$(}uLAb8}I4~nc#sT2>CuqhjKI~bSEeIiN}OMl0H zv}Y8tPgO`Grq4lEiOX6&k1Q0bSEn?EJn3MIbvGYqQTG&MqQX}pKc^J3m^@? zuxWGesz2Hxv>)%UT#4CaiO1kKLN_DIed>i z-A`9YIp-nTK$lRcm0n2&2LC#g&;ImjKox?m5!*1qztO?kILj7#pb{Leln1)aC9bwFm~XUp+uNsf?$-N}ze@ z@gl#TIMDp!89>i2ro?m@S$4Eo#WX#EODXPf)3Z28(yM#!NkNIoX%ow(?}bmRlt3zT zh5uEU41MB&DxO^NkG8R`M1AXQi{}f}e{ib!>EJu-67?oBKEt)w;Z8Ze9Lc_<^!1B! zyEq4L&k>q+J3J@Nxhv5B#en8@UQ&4{X;N?(m}V3!w{0QDI;wl2+JlZPVWtYwkVM=b0FZNE&}uVXRwM0Zxsb z*SdZJB~}hoLeC7?cD`L6Q~!^RR$X0c9DVC3(?*bO003p$=%R-gK>>|-QE}o1gCR6KFH(MjP6R<0hrgR+S4im+>IljhuTx zcABK&Mc0g~-lo}#N&V22*iR`&@T*$Kto5#m$*63dB0^5gspxNqDAP`$SW0ykL=FzO z40eouem>H^oj34~an~vLwE1n7IQ)lOKF56PEwIwU9?9A;;pE9t(ZOLQdAgHFQye)V zb}^?*<6fToo9N&}Dh*3~Y2g)xX**4H;_T{g59?>XLz>QBUUD+;jey9MJu^hp^fLmK z?Cr3ivwLd>c}n>tpdI7=@sYaGC~G`p1l+Lj`wV6S!9x+O-zl?Mi*4$>B>I@Ek-(Bkh3baDJ>qEdq9%^&5|XQ@m5mjetF& z4=uZSqHm>XMF#|2f?gZNH4^p(WtxuXiGA`1?nc5psa)ITs~Sse!MMY!Sg5g8tn}rK zV9?C*W4TB|gD0M(;VDBCdYrO5$#v*$cLe!lS##q9HJvQ_AsnN z9md`sY#1QGcI2$3f%HT)O?lGIG9_8-w)6H!Xd9QsyL0+|Y2f5Z$B^a9ps;KmN+3%a z-h69}{z??KtI?LV(e{jWlKWTfYmQofjg3`pSJJ@ed^*d-SrynRH#GR`46SaLK%EOz$WI1B|JpbXnEEFKZc|q0m z2Q)~;zAlyaF&*9yk>d=;AfF^sZgFBFd`D)vJ;E7Q;d4K|)2ul|3A{^eBg~_DZ;cBR z63c9|8)2J$sT5Y{M$sR4-ZZ?0?+TpPVCBI_2$o|4I3=k}0cW?;Y9gEha;rIJeHr7= z3!a*I?yox?z(x&nkgs?%v>wr3K?5OAq#q^x| z*tb$I5axr+(i(*8dn}H#Y|#Pz>3}4~%y#t>CqBKr`uufV;s(T5`v40d0s!t&SNIv7 zEJS(a^V<7wS%|U@jOCR#EbFCzKkTfRzQa`7{OGfRvqwc(q>8COvIJRu0zdofIlotf z^_)&-jo56)nYCd9=;wd{RYz!h{AL5%Z11mu_=!t;mK;Bwu&HceyyM5p|4x<%ST9Rh zll|eI|4n=|kFANXt}kh$_%Fw8O8Mi=Y-X?Z6i&{cGxK8z8_o9rpf;LoJ<59{YO{s^ bWwp)7zwc)@_SHSl$-L#6J4tNy2Lkvvs$=*6 literal 0 HcmV?d00001 diff --git a/test/integration/git_workflow_ro_crate_api_test.rb b/test/integration/git_workflow_ro_crate_api_test.rb index b3b34c5fd9..c7e84558a4 100644 --- a/test/integration/git_workflow_ro_crate_api_test.rb +++ b/test/integration/git_workflow_ro_crate_api_test.rb @@ -103,6 +103,20 @@ class GitWorkflowRoCrateApiTest < ActionDispatch::IntegrationTest end end + test 'can identify license from URI' do + assert_difference('Workflow.count', 1) do + post workflows_path, params: { + ro_crate: fixture_file_upload('workflows/ro-crate-with-uri-license.crate.zip'), + workflow: { + project_ids: [@project.id] + } + } + + assert_response :success + assert_equal 'MIT', assigns(:workflow).license + end + end + private def login_as(user) diff --git a/test/unit/license_test.rb b/test/unit/license_test.rb index c4b250a358..320807399a 100644 --- a/test/unit/license_test.rb +++ b/test/unit/license_test.rb @@ -124,7 +124,7 @@ class LicenseTest < ActiveSupport::TestCase sop.license = 'CCZZ' refute sop.valid? assert_equal 1,sop.errors.count - assert sop.errors.added?(:license, "isn't a valid license ID") + assert sop.errors.added?(:license, "isn't a recognized license") #allow blank sop.license=nil @@ -137,4 +137,26 @@ class LicenseTest < ActiveSupport::TestCase assert sop.valid? end + test 'lookup license ID from URI' do + assert_equal 'CC-BY-4.0', Seek::License.uri_to_id('https://spdx.org/licenses/CC-BY-4.0.html') + assert_equal 'CC-BY-4.0', Seek::License.uri_to_id('https://spdx.org/licenses/CC-BY-4.0') + assert_equal 'CC-BY-4.0', Seek::License.uri_to_id('https://creativecommons.org/licenses/by/4.0/') + assert_equal 'CC-BY-4.0', Seek::License.uri_to_id('https://creativecommons.org/licenses/by/4.0/legalcode') + + assert_equal 'MIT-open-group', Seek::License.uri_to_id('https://gitlab.freedesktop.org/xorg/app/iceauth/-/blob/master/COPYING') + + assert_nil Seek::License.uri_to_id('https://creativecommons.org/licenses/by/5.0') + end + + test 'set license via URI' do + sop = FactoryBot.create(:sop, license: 'https://creativecommons.org/licenses/by/4.0/') + assert sop.valid? + assert_equal 'CC-BY-4.0', sop.license + + sop.license = 'CCZZ' + refute sop.valid? + assert_equal 1,sop.errors.count + assert sop.errors.added?(:license, "isn't a recognized license") + assert_equal 'CCZZ', sop.license + end end From 9f5b507b861c599731601c35cb9be57ce89d8917 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 25 Jul 2023 19:28:57 +0100 Subject: [PATCH 018/383] Test fix --- test/functional/data_files_controller_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/data_files_controller_test.rb b/test/functional/data_files_controller_test.rb index abf36b83f9..6f3ab1061f 100644 --- a/test/functional/data_files_controller_test.rb +++ b/test/functional/data_files_controller_test.rb @@ -2132,7 +2132,7 @@ def test_show_item_attributed_to_jerm_file get :edit, params: { id: df } assert_response :success - assert_select '#license-select option[selected=?]', 'selected', text: 'Creative Commons Attribution Share-Alike 4.0' + assert_select '#license-select option[selected=?]', 'selected', text: 'Creative Commons Attribution Share Alike 4.0 International' df2 = FactoryBot.create :data_file, license: nil, policy: FactoryBot.create(:public_policy) @@ -2142,7 +2142,7 @@ def test_show_item_attributed_to_jerm_file register_content_blob assert_response :success - assert_select '#license-select option[selected=?]', 'selected', text: 'Creative Commons Attribution 4.0' + assert_select '#license-select option[selected=?]', 'selected', text: 'Creative Commons Attribution 4.0 International' end test 'can disambiguate sample type' do From db40534c180689189b3b05b186c8d7103fee253c Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Thu, 27 Jul 2023 16:50:33 +0100 Subject: [PATCH 019/383] Uses batch_asset_selection for batch_sharing_permission_preview --- app/helpers/assets_helper.rb | 12 - .../batch_sharing_permission_preview.html.erb | 322 ++---------------- .../sharing_bulk_change_preview.html.erb | 2 +- ...ample_sharing_bulk_change_preview.html.erb | 2 +- lib/seek/publishing/publishing_common.rb | 2 +- lib/seek/sharing/sharing_common.rb | 11 +- 6 files changed, 27 insertions(+), 324 deletions(-) diff --git a/app/helpers/assets_helper.rb b/app/helpers/assets_helper.rb index b3b74a9228..9e79a75ce5 100644 --- a/app/helpers/assets_helper.rb +++ b/app/helpers/assets_helper.rb @@ -94,18 +94,6 @@ def publishing_item_param(item) "publish[#{item.class.name}][#{item.id}]" end - def sharing_item_param(item) - if item.try(:is_isa?) - "share_isa[#{item.class.name}][#{item.id}]" - elsif (item.respond_to? (:investigations)) && (!item.investigations.any?) - "share_not_isa[#{item.class.name}][#{item.id}]" - elsif !item.respond_to? (:investigations) - "share_not_isa[#{item.class.name}][#{item.id}]" - else - "share_isa[#{item.class.name}][#{item.id}]" - end - end - def include_downloadable_item?(items) has_downloadable_item = false items.each do |item| diff --git a/app/views/assets/sharing/batch_sharing_permission_preview.html.erb b/app/views/assets/sharing/batch_sharing_permission_preview.html.erb index 14f8355a67..6c1ebd757e 100644 --- a/app/views/assets/sharing/batch_sharing_permission_preview.html.erb +++ b/app/views/assets/sharing/batch_sharing_permission_preview.html.erb @@ -1,14 +1,4 @@ <% - items_not_in_isa_hash = SharingPermissionsHelper::ITEMS_NOT_IN_ISA_HASH - items_not_in_isa_hash[:children] =[] - - all_investigations_hash = SharingPermissionsHelper::ALL_INVESTIGATIONS_HASH - all_investigations_hash[:children] = [] - - #include items which can change sharing permissions, the 'publication' is intendedly excluded. - all_related_items_hash = get_related_resources(current_user.person).slice(*Seek::Util.authorized_types.map(&:name)).except!('Publication') - - items_not_in_isa = filter_items_not_in_ISA(all_related_items_hash) @policy_params = params[:policy_attributes] @@ -26,296 +16,24 @@
-<%= form_tag({:action => :batch_change_permission_for_selected_items}, :method => :post) do -%> - - <%= folding_panel("Items not in ISA", false, :id => 'not_isa_item', :body_options => {:id => 'items_fold_content'}, - :help_text => nil) do %> - - | - | - | - - - - <% items_not_in_isa.each do |resource_type, resource_items| %> - <% asset_node= asset_node_json(resource_type, resource_items) %> - <% items_not_in_isa_hash[:children].append(asset_node) %> - <% end %> - -
- - <% end %> - - - <% all_related_items_hash.each do |resource_type, resource_items| %> - <% if resource_type == "Investigation" %> - <% resource_items[:items].each do |item| %> - <% options ||= { depth: 4, parent_depth: nil, sibling_depth: nil, include_self: true } %> - <% hash = Seek::IsaGraphGenerator.new(item).generate(**options) %> - <% tree = JSON.parse(build_tree_json(hash, item)) %> - <% tree = add_permissions_to_tree_json(tree) %> - <% all_investigations_hash[:children].append(tree.first) %> - <% end %> - <% end %> - <% end %> - - <%= folding_panel("Items in ISA: ", false, :id => 'isa_item', :body_options => {:id => 'items_fold_content'}, - :help_text => nil) do %> - - | - | - | - - -
- + <%= form_tag({:action => :batch_change_permission_for_selected_items}, :method => :post) do -%> + + <% if @assets.empty? %> + You have no manageable assets in <%= Seek::Config.instance_name %> +
+
+ <%= link_to "Back to profile", person_path(params[:id].to_i) -%> + <% else %> + <%= render :partial => "assets/batch_asset_selection", + :locals => { :publishing => false, + :show_hide_blocked => true, + :blocked_selector => '.not-visible,.not-manageable' + } + -%> +
+ <%= submit_tag "Next",data: { disable_with: 'Next' }, :class => 'btn btn-primary' -%> + Or + <%= cancel_button person_path(params[:id].to_i)-%> + <% end -%> <% end -%> - - <% resource = (controller_name == 'people') ? current_user.person : @asset %> - - <%= submit_tag "Next", data: {disable_with: 'Next'}, :class => 'btn btn-primary', :onclick => 'show_all_checkboxes()' -%> - - or - <%= cancel_button(resource, button_text: "Back to profile") %> - -<% end -%> -
- - - +
\ No newline at end of file diff --git a/app/views/assets/sharing/sharing_bulk_change_preview.html.erb b/app/views/assets/sharing/sharing_bulk_change_preview.html.erb index 2e78eadd38..1bbb6f93b5 100644 --- a/app/views/assets/sharing/sharing_bulk_change_preview.html.erb +++ b/app/views/assets/sharing/sharing_bulk_change_preview.html.erb @@ -13,7 +13,7 @@ ").html_safe - respond_to do |format| - if params[:single_page] - format.html { render 'single_pages/sample_batch_sharing_permission_preview', { layout: false } } - else + if params[:single_page] + render 'single_pages/sample_batch_sharing_permissions_changed', { layout: false } + else + respond_to do |format| format.html { render 'assets/sharing/batch_sharing_permission_preview' } end end From c9a64a88dbe2aa78d52bab8fe7123a0fb1a789a5 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer <82407142+kdp-cloud@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:19:01 +0200 Subject: [PATCH 024/383] Issue 1535 Fix sharing permission of Assay samples (#1561) * Fix table select and a little reformatting * Assigning the `DynamicTable` to the global `window` variable --- .../single_page/dynamic_table.js.erb | 2 +- app/views/isa_assays/_assay_samples.html.erb | 13 ++++++------- app/views/isa_assays/_assay_table.html.erb | 16 ++++++++-------- app/views/isa_studies/_buttons.html.erb | 4 ++-- .../isa_studies/_source_material.html.erb | 13 ++++++------- app/views/isa_studies/_study_samples.html.erb | 13 ++++++------- app/views/isa_studies/_study_table.html.erb | 18 +++++++++--------- 7 files changed, 38 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index 63e0d31d09..dbcbbb2601 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -35,7 +35,7 @@ const handleSelect = (e) => { dtSelectAll.html("Select all rows") dtSelectAll.css("display", "none") } - const dtName = $j(e).parents("table").data("dtname") + const dtName = $j(e).parents("table").data("dtname"); window[dtName].setPermissionBTNActivity() } diff --git a/app/views/isa_assays/_assay_samples.html.erb b/app/views/isa_assays/_assay_samples.html.erb index e6aa9aad0a..071779286b 100644 --- a/app/views/isa_assays/_assay_samples.html.erb +++ b/app/views/isa_assays/_assay_samples.html.erb @@ -14,13 +14,12 @@ <% if valid_assay %> <% end %> diff --git a/app/views/isa_studies/_buttons.html.erb b/app/views/isa_studies/_buttons.html.erb index a17d0ccfc3..abfe3eb576 100644 --- a/app/views/isa_studies/_buttons.html.erb +++ b/app/views/isa_studies/_buttons.html.erb @@ -6,6 +6,7 @@ delete||="SampleSetAsDeleted()" save||="SampleSave()" permissions||="loadBatchPermission('sampleDynamicTable',this)" exportToExcel||="sampleExportExcel()" +uploadExcel||="studySampleUploadExcel()" %>
@@ -17,6 +18,18 @@ exportToExcel||="sampleExportExcel()"
+
+ +
+
+ +
+
+ +
+
+
+ diff --git a/app/views/isa_studies/_source_material.html.erb b/app/views/isa_studies/_source_material.html.erb index 63766ee298..4d43e14d5e 100644 --- a/app/views/isa_studies/_source_material.html.erb +++ b/app/views/isa_studies/_source_material.html.erb @@ -1,5 +1,8 @@ -<% study ||= nil %> -<% sample_type = study&.sample_types&.first %> +<% + study ||= nil + sample_type = study&.sample_types&.first + project = study&.projects&.first +%>
@@ -11,7 +14,8 @@ <%= render partial: 'isa_studies/buttons', locals:{ add_row: "SourceAddNewRow()", paste_cb: "SourcePasteFromClipboard()", delete: "SourceSetAsDeleted()", save: "SourceSave()", -permissions: "loadBatchPermission('sourceDynamicTable',this)", exportToExcel: "sourceExportExcel()" } %> +permissions: "loadBatchPermission('sourceDynamicTable',this)", exportToExcel: "sourceExportExcel()", +uploadExcel: 'sourceUploadExcel()', sample_type_id: sample_type&.id} %> <% if sample_type %> <% end %> diff --git a/app/views/isa_studies/_study_samples.html.erb b/app/views/isa_studies/_study_samples.html.erb index 46bfad6614..57b7ff0b13 100644 --- a/app/views/isa_studies/_study_samples.html.erb +++ b/app/views/isa_studies/_study_samples.html.erb @@ -1,5 +1,8 @@ -<% study ||= nil %> -<% sample_type = study&.sample_types&.second %> +<% + study ||= nil + sample_type = study&.sample_types&.second + project = study&.projects&.first +%>
@@ -9,7 +12,7 @@
-<%= render partial: 'isa_studies/buttons' %> +<%= render partial: 'isa_studies/buttons', locals: {uploadExcel: "studySampleUploadExcel()", sample_type_id: sample_type&.id} %> <% if sample_type %> <% end %> diff --git a/app/views/single_pages/_duplicate_samples_panel.html.erb b/app/views/single_pages/_duplicate_samples_panel.html.erb new file mode 100644 index 0000000000..3e7696e7a5 --- /dev/null +++ b/app/views/single_pages/_duplicate_samples_panel.html.erb @@ -0,0 +1,51 @@ +<% unless @possible_duplicates.nil? or @possible_duplicates.compact.none? %> + <%= folding_panel("Possible Duplicates #{@possible_duplicates.size}", true, :id => "duplicate-samples-panel", :body_options => {:id => "duplicate-samples-panel-content"}, + :help_text => "These new samples have been matched to already existing samples.") do %> +
+ + + + <% for key in @possible_duplicates[0].keys %> + <% unless %w[uuid duplicate].include?(key) %> + + <% end %> + <% end %> + + <% for dupl_sample in @possible_duplicates %> + ' > + + <% dupl_sample.map do |key, val| %> + <% val = '' if key =='id' %> + <% unless %w[uuid duplicate].include?(key) %> + <% if @multiple_input_fields.include?(key)%> + + <% else %> + + <% end %> + <% end %> + <% end %> + ' class="danger"> + <% dupl_sample['duplicate'].map do |key, val| %> + <% unless %w[uuid duplicate].include?(key) %> + <% if @multiple_input_fields.include?(key)%> + + <% else %> + + <% end %> + <% end %> + <% end %> + + + <% end %> +
<%= key %>
+ <% val.each do |sub_sample| %> + '><%= sub_sample['title'] %> + <% end %> + ' ><%= val %>
+ <% val.each do |sub_sample| %> + '><%= sub_sample['title'] %> + <% end %> + <%= val%>
+
+ <% end %> +<% end %> diff --git a/app/views/single_pages/_general_panel.html.erb b/app/views/single_pages/_general_panel.html.erb new file mode 100644 index 0000000000..6880ad7eaf --- /dev/null +++ b/app/views/single_pages/_general_panel.html.erb @@ -0,0 +1,29 @@ +<%= folding_panel("General Information", true, :id => "general-information-panel", +:help_text => "This pane contains an overview of the permissions needed to upload.") do %> + <% [@project, @study, @assay]. map do | asset | %> + <% next if asset.nil? %> + <% asset_name = asset.class.to_s.singularize %> +

<%= asset_name %>

+

ID: <%= asset.id%>

+

UUID: <%= asset.uuid %>

+

Title: <%= asset.title %>

+ <% if asset_name == "Project"%> + <% if current_user.person.member_of?(asset)%> +

<%= current_user.person.name %> is member of this project:

+ <% else %> +

<%= current_user.person.name %> is member of this project:

+ <% can_upload = false %> + <% errors.append("You must be a member of this project!") %> + <% end %> + <% else %> + <% if asset.can_view?%> +

<%= current_user.person.name %> can view:

+ <% else %> +

<%= current_user.person.name %> can view:

+ <% can_upload = false %> + <% errors.append("You must have at least viewing permission for the #{asset_name} '#{asset.title}'.") %> + <% end %> + <% end %> +
+ <% end %> +<% end %> diff --git a/app/views/single_pages/_new_samples_panel.html.erb b/app/views/single_pages/_new_samples_panel.html.erb new file mode 100644 index 0000000000..cf157efaa2 --- /dev/null +++ b/app/views/single_pages/_new_samples_panel.html.erb @@ -0,0 +1,37 @@ +<% unless @new_samples.nil? or @new_samples.compact.none? %> + <%= folding_panel("New Samples #{@new_samples.size}", true, :id => "new-samples-panel", :body_options => {:id => "new-samples-panel-content"}, + :help_text => "These samples have been detected as new samples and will be created.") do %> +
+ + + + <% for key in @new_samples[0].keys %> + <% unless key == 'uuid' %> + + <% end %> + <% end %> + + <% for new_sample in @new_samples %> + <% new_sample_id = UUID.generate %> + + + <% new_sample.map do |key, val| %> + <% val = '' if key =='id' %> + <% unless key == 'uuid' %> + <% if @multiple_input_fields.include?(key)%> + + <% else %> + + <% end %> + <% end %> + <% end %> + + <% end %> +
<%= key %>
+ <% val.each do |sub_sample| %> + '><%= sub_sample['title'] %> + <% end %> + <%= val %>
+
+ <% end %> +<% end %> diff --git a/app/views/single_pages/_unauthorized_samples_panel.html.erb b/app/views/single_pages/_unauthorized_samples_panel.html.erb new file mode 100644 index 0000000000..8ad35705d2 --- /dev/null +++ b/app/views/single_pages/_unauthorized_samples_panel.html.erb @@ -0,0 +1,40 @@ +<% unless @unauthorized_samples.nil? or @unauthorized_samples.compact.none? %> + <% @can_upload = false %> + <% errors.append("There are unauthorized samples present in the spreadsheet.") %> + <%= folding_panel("Unauthorized Samples", false, :id => "unauthorized-samples-panel", :body_options => {:id => "unauthorized-samples-panel-content"}, + :help_text => "Sample the current user does not have permission to edit them.") do %> +
+

+ You don't have permission to edit the samples listed below. Please contact the submitter of these samples or revert the changes in the spreadsheet to its original values. +

+
+
+ + + <% for key in @unauthorized_samples[0].keys %> + <% unless key == 'uuid' %> + + <% end %> + <% end %> + + <% for unauthorized_sample in @unauthorized_samples %> + ' class=""> + <% unauthorized_sample.map do |key, val| %> + <% unless key == 'uuid' %> + <% if @multiple_input_fields.include?(key) %> + + <% else %> + + <% end %> + <% end %> + <% end %> + + <% end %> +
<%= key %>
+ <% val.each do |sub_sample| %> + '><%= sub_sample['title'] %> + <% end %> + <%= val %>
+
+ <% end %> +<% end %> diff --git a/app/views/single_pages/_update_samples_panel.html.erb b/app/views/single_pages/_update_samples_panel.html.erb new file mode 100644 index 0000000000..b2de42c349 --- /dev/null +++ b/app/views/single_pages/_update_samples_panel.html.erb @@ -0,0 +1,51 @@ +<% unless @update_samples.nil? or @update_samples.compact.none? %> + <%= folding_panel("Samples to Update #{@update_samples.size}", true, :id => "existing-samples-panel", :body_options => {:id => "existing-samples-panel-content"}, + :help_text => "These samples were detected existing samples and will be updated.") do %> +
+ + + + <% for key in @update_samples[0].keys %> + <% unless key == 'uuid' %> + + <% end %> + <% end %> + + <% for update_sample in @update_samples %> + ' > + <% db_sample = @authorized_db_samples.select { |s| s['id'] == update_sample['id'] }.first %> + + <% update_sample.map do |key, val| %> + <% unless key == 'uuid' %> + <% if @multiple_input_fields.include?(key)%> + + <% else %> + + <% end %> + <% end %> + <% end %> + + ' > + <% db_sample.map do |key, val| %> + <% unless key == 'uuid' %> + <% if @multiple_input_fields.include?(key)%> + + <% else %> + + <% end %> + <% end %> + <% end %> + + <% end %> +
<%= key %>
' > + <% val.each do |sub_sample| %> + '><%= sub_sample['title'] %> + <% end %> + ' ><%= val %>
+ <% val.each do |sub_sample| %> + '><%= sub_sample['title'] %> + <% end %> + <%= val %>
+
+ <% end %> +<% end %> diff --git a/app/views/single_pages/download_samples_excel.axlsx b/app/views/single_pages/download_samples_excel.axlsx index 82b8837615..800b4a4d09 100644 --- a/app/views/single_pages/download_samples_excel.axlsx +++ b/app/views/single_pages/download_samples_excel.axlsx @@ -41,12 +41,6 @@ workbook.add_worksheet(name: 'Metadata') do |sheet| sheet.add_row ['Fairdom ID:', @study.id] sheet.add_row ['UUID:', @study.uuid] - if @assay - sheet.add_row ['Assay:'], style: title_style - sheet.add_row ['Fairdom ID:', @assay.id] - sheet.add_row ['UUID:', @assay.uuid] - end - sheet.add_row ['Sample Type:'], style: title_style sheet.add_row ['Fairdom ID:', @sample_type.id] sheet.add_row ['UUID:', @sample_type.uuid] @@ -55,6 +49,12 @@ workbook.add_worksheet(name: 'Metadata') do |sheet| sheet.add_row ['Fairdom ID:', @template.id] sheet.add_row ['UUID:', @template.uuid] + if @assay + sheet.add_row ['Assay:'], style: title_style + sheet.add_row ['Fairdom ID:', @assay.id] + sheet.add_row ['UUID:', @assay.uuid] + end + sheet.sheet_protection.password = secret_pwd end diff --git a/app/views/single_pages/sample_upload_content.html.erb b/app/views/single_pages/sample_upload_content.html.erb new file mode 100644 index 0000000000..28728d8dd6 --- /dev/null +++ b/app/views/single_pages/sample_upload_content.html.erb @@ -0,0 +1,211 @@ +<% + def any_samples_to_upload? + !((@new_samples.nil? or @new_samples.compact.none?) and (@update_samples.nil? or @update_samples.compact.none?) and (@possible_duplicates.nil? or @possible_duplicates.compact.none?)) + end + errors = [] +%> + +<% @can_upload = true %> +
+ + <%# General information panel %> + <%= render partial: 'general_panel' %> + + <%# New Samples panel %> + <%= render partial: 'new_samples_panel' %> + + <%# Update Samples panel %> + <%= render partial: 'update_samples_panel' %> + + <%# Possible Duplicate Samples panel %> + <%= render partial: 'duplicate_samples_panel' %> + + <%# Panel for Sample with wrong permissions %> + <%= render partial: 'unauthorized_samples_panel' %> + +
+ <% unless @can_upload %> + + <% end %> + + <% if (@can_upload and !any_samples_to_upload?) %> + + <% end %> + + <%= submit_tag "Upload", data: {disable_with: 'Uploading ...'}, :class => 'btn btn-primary', onclick: "submitUpload()" if (@can_upload and any_samples_to_upload?)%> + <%= submit_tag "Cancel", data: {id: 'cancelModalUploadExcel'}, :class => 'btn btn-secondary', onclick: "closeModalForm()" %> + <%= image_tag('ajax-loader.gif', id: 'sample-upload-spinner', style: 'display: none') %> +
+ + diff --git a/app/views/single_pages/show.html.erb b/app/views/single_pages/show.html.erb index 327d96e652..29c6185021 100644 --- a/app/views/single_pages/show.html.erb +++ b/app/views/single_pages/show.html.erb @@ -56,6 +56,13 @@ <% end %> <% end %> + <%= modal(id: 'upload-excel-modal', size: 'xl') do %> + <%= modal_header("Upload from spreadsheet") %> + <%= modal_body do %> +
...
+ <% end %> + <% end %> +
+ diff --git a/app/views/assets/_isa_tree_preview.html.erb b/app/views/assets/_isa_tree_preview.html.erb index 84b62c90a6..673ff4dcdf 100644 --- a/app/views/assets/_isa_tree_preview.html.erb +++ b/app/views/assets/_isa_tree_preview.html.erb @@ -17,12 +17,11 @@ show_permissions ||= false show_managers ||= false -%> -
"> +
<%= render partial: "assets/asset_checkbox_row", object: item, locals: { html_classes: "publishing_options", toggle: children.any?, - cb_parent_selector: "div\##{item.class.name}_#{item.id}.split_button_parent", block_published: publishing, checked: (item == preselected), show_permissions: show_permissions, @@ -30,7 +29,7 @@ } -%> <% if children.any? %> -
+
<% children.each do |child| %> <%= render partial: "assets/isa_tree_preview", object: child, @@ -43,4 +42,4 @@ <% end %>
<% end %> -
\ No newline at end of file +
diff --git a/app/views/general/_split_button_checkbox.html.erb b/app/views/general/_split_button_checkbox.html.erb index ed573bfcf7..e3d1457365 100644 --- a/app/views/general/_split_button_checkbox.html.erb +++ b/app/views/general/_split_button_checkbox.html.erb @@ -7,7 +7,6 @@ checkbox_text ||= "" checked ||= false toggle ||= false - cb_parent_selector ||= "div\##{checkbox_class}.split_button_parent" -%>
@@ -48,10 +47,10 @@ Toggle Dropdown From 56b5ff6cb035d9ae3017f938b6018889ec0b50aa Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 26 Sep 2023 13:07:06 +0100 Subject: [PATCH 054/383] Namespace JavaScript functions and avoid `onclick` --- app/assets/javascripts/checkbox.js | 246 +++++++++--------- .../assets/_batch_asset_selection.html.erb | 2 +- .../general/_split_button_checkbox.html.erb | 6 +- 3 files changed, 128 insertions(+), 126 deletions(-) diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js index 7b2f8e09d9..9b9b5b7ee7 100644 --- a/app/assets/javascripts/checkbox.js +++ b/app/assets/javascripts/checkbox.js @@ -1,153 +1,157 @@ $j(document).ready(function () { - $j("a.selectChildren").click(selectChildren); - $j("a.deselectChildren").click(deselectChildren); - $j("a.managed_by_toggle").click(toggleManagers); + $j("a.selectChildren").click(BatchAssetSelection.selectChildren); + $j("a.deselectChildren").click(BatchAssetSelection.deselectChildren); + $j("a.managed_by_toggle").click(BatchAssetSelection.toggleManagers); $j("a.permissions_toggle").click(function () { - togglePermissions($j(this).closest('.isa-tree')); + BatchAssetSelection.togglePermissions($j(this).closest('.isa-tree')); return false; }); $j("a.showPermissions").click(function () { - togglePermissions($j(this).closest('.batch-selection-scope'), 'show'); + BatchAssetSelection.togglePermissions($j(this).closest('.batch-selection-scope'), 'show'); return false; }) $j("a.hidePermissions").click(function () { - togglePermissions($j(this).closest('.batch-selection-scope'), 'hide'); + BatchAssetSelection.togglePermissions($j(this).closest('.batch-selection-scope'), 'hide'); return false; }) - $j(".isa-tree-toggle-open").click(isaTreeShow); - $j(".isa-tree-toggle-close").click(isaTreeHide); - $j("a.collapseChildren").click(collapseRecursively); - $j("a.expandChildren").click(expandRecursively); - $j(".hideBlocked").click(hideBlocked); - $j(".showBlocked").click(showBlocked); -}) - -function selectChildren() { - let children_checkboxes = $j(':checkbox', $j(this).closest('.batch-selection-scope')); - for(let checkbox of children_checkboxes){ - let checkbox_element = { className: checkbox.className, checked: true } - checkRepeatedItems(checkbox_element) - } - - return false; -} - -function deselectChildren() { - let children_checkboxes = $j(':checkbox', $j(this).closest('.batch-selection-scope')); - for(let checkbox of children_checkboxes){ - let checkbox_element = { className: checkbox.className, checked: false } - checkRepeatedItems(checkbox_element) - } - - return false; -} - -function checkRepeatedItems(checkbox_element) { - let repeated_elements = document.getElementsByClassName(checkbox_element.className) - let check = checkbox_element.checked - for(let element of repeated_elements){ - element.checked = check - } -} + $j(".isa-tree-toggle-open").click(BatchAssetSelection.isaTreeShow); + $j(".isa-tree-toggle-close").click(BatchAssetSelection.isaTreeHide); + $j("a.collapseChildren").click(BatchAssetSelection.collapseRecursively); + $j("a.expandChildren").click(BatchAssetSelection.expandRecursively); + $j(".hideBlocked").click(BatchAssetSelection.hideBlocked); + $j(".showBlocked").click(BatchAssetSelection.showBlocked); + $j(".batch-asset-select-btn").click(BatchAssetSelection.button_checkRepeatedItems); + $j('.batch-asset-select-btn input[type="checkbox"]').click(BatchAssetSelection.checkRepeatedItems); +}); + +const BatchAssetSelection = { + selectChildren: function () { + let children_checkboxes = $j(':checkbox', $j(this).closest('.batch-selection-scope')); + for(let checkbox of children_checkboxes){ + let checkbox_element = { className: checkbox.className, checked: true } + BatchAssetSelection.checkRepeatedItems(checkbox_element) + } -function button_checkRepeatedItems(button_element) { - if(this.event.target.nodeName.includes("BUTTON")){ - let checkbox_element = $j(button_element).find('input')[0] - checkbox_element.checked = !(checkbox_element.checked) - checkRepeatedItems(checkbox_element) - } -} + return false; + }, -function toggleManagers() { - $j(this).siblings('.managed_by_list').toggle(); + deselectChildren: function () { + let children_checkboxes = $j(':checkbox', $j(this).closest('.batch-selection-scope')); + for(let checkbox of children_checkboxes){ + let checkbox_element = { className: checkbox.className, checked: false } + BatchAssetSelection.checkRepeatedItems(checkbox_element) + } - return false; -} + return false; + }, -function togglePermissions(scope, state) { - const permissions = $j('.permission_list', scope); - switch(state){ - case 'show': - permissions.show() - break - case 'hide': - permissions.hide() - break - default: - permissions.toggle() - } -} + checkRepeatedItems: function () { + let repeated_elements = document.getElementsByClassName(this.className) + let check = this.checked + for(let element of repeated_elements){ + element.checked = check + } + }, -function isaTreeShow() { - $j(this).closest('.batch-selection-scope').children('.collapse-scope').show(); - $j(this).siblings('.isa-tree-toggle-close').show(); - $j(this).hide(); + button_checkRepeatedItems: function (event) { + if (event.target.nodeName.includes("BUTTON")){ + let checkbox_element = $j(this).find('input')[0] + checkbox_element.checked = !(checkbox_element.checked) + BatchAssetSelection.checkRepeatedItems(checkbox_element) + } + }, - return false; -} + toggleManagers: function () { + $j(this).siblings('.managed_by_list').toggle(); -function isaTreeHide(){ - $j(this).closest('.batch-selection-scope').children('.collapse-scope').hide(); - $j(this).siblings('.isa-tree-toggle-open').show(); - $j(this).hide(); + return false; + }, + + togglePermissions: function (scope, state) { + const permissions = $j('.permission_list', scope); + switch(state){ + case 'show': + permissions.show() + break + case 'hide': + permissions.hide() + break + default: + permissions.toggle() + } + }, - return false; -} + isaTreeShow: function () { + $j(this).closest('.batch-selection-scope').children('.collapse-scope').show(); + $j(this).siblings('.isa-tree-toggle-close').show(); + $j(this).hide(); -function collapseRecursively() { - const scope = $j(this).closest('.batch-selection-scope').children('.collapse-scope'); - const toggles = $j('.isa-tree-toggle-close', scope); - for (let toggle of toggles) { - if (toggle.style.display === 'none') // Skip those that are already closed - continue; - isaTreeHide.apply(toggle); - } + return false; + }, - return false; -} + isaTreeHide: function (){ + $j(this).closest('.batch-selection-scope').children('.collapse-scope').hide(); + $j(this).siblings('.isa-tree-toggle-open').show(); + $j(this).hide(); -function expandRecursively() { - const scope = $j(this).closest('.batch-selection-scope').children('.collapse-scope'); - const toggles = $j('.isa-tree-toggle-open', scope); - for (let toggle of toggles) { - if (toggle.style.display === 'none') - continue; - isaTreeShow.apply(toggle); - } + return false; + }, + + collapseRecursively: function () { + const scope = $j(this).closest('.batch-selection-scope').children('.collapse-scope'); + const toggles = $j('.isa-tree-toggle-close', scope); + for (let toggle of toggles) { + if (toggle.style.display === 'none') // Skip those that are already closed + continue; + BatchAssetSelection.isaTreeHide.apply(toggle); + } - return false; -} + return false; + }, + + expandRecursively: function () { + const scope = $j(this).closest('.batch-selection-scope').children('.collapse-scope'); + const toggles = $j('.isa-tree-toggle-open', scope); + for (let toggle of toggles) { + if (toggle.style.display === 'none') + continue; + BatchAssetSelection.isaTreeShow.apply(toggle); + } -function hideBlocked(){ - let children_assets = $j($j(this).data('blocked_selector'), $j(this).closest('.batch-selection-scope')); - for (let asset of children_assets) { - //Items in isa tree - if($j($j(asset).parents('div.split_button_parent')).length>0) { - // Don't hide "parents" of non-blocked items - if (!$j('input[type=checkbox]', $j(asset).parent()).length > 0) { - $j($j(asset).parents('div.split_button_parent')[0]).hide() + return false; + }, + + hideBlocked: function (){ + let children_assets = $j($j(this).data('blocked_selector'), $j(this).closest('.batch-selection-scope')); + for (let asset of children_assets) { + //Items in isa tree + if($j($j(asset).parents('div.split_button_parent')).length>0) { + // Don't hide "parents" of non-blocked items + if (!$j('input[type=checkbox]', $j(asset).parent()).length > 0) { + $j($j(asset).parents('div.split_button_parent')[0]).hide() + } + //Items not in isa tree + } else { + $j(asset).hide() } - //Items not in isa tree - } else { - $j(asset).hide() } - } - - return false; -} -function showBlocked(){ - let children_assets = $j($j(this).data('blocked_selector'), $j(this).closest('.batch-selection-scope')); - for (let asset of children_assets) { - if($j($j(asset).parents('div.split_button_parent')).length>0) { - $j($j(asset).parents('div.split_button_parent')[0]).show() - } else{ - $j(asset).show() + return false; + }, + + showBlocked: function (){ + let children_assets = $j($j(this).data('blocked_selector'), $j(this).closest('.batch-selection-scope')); + for (let asset of children_assets) { + if($j($j(asset).parents('div.split_button_parent')).length>0) { + $j($j(asset).parents('div.split_button_parent')[0]).show() + } else{ + $j(asset).show() + } } - } - return false; + return false; + } } diff --git a/app/views/assets/_batch_asset_selection.html.erb b/app/views/assets/_batch_asset_selection.html.erb index 0b1e1c0224..2d0789246b 100644 --- a/app/views/assets/_batch_asset_selection.html.erb +++ b/app/views/assets/_batch_asset_selection.html.erb @@ -117,6 +117,6 @@ $j('div#sorted_by_isa').hide() }) $j(document).ready(function () { - hideBlocked(document,"<%="#{blocked_selector}"%>") + BatchAssetSelection.hideBlocked('<%= blocked_selector -%>'); }) \ No newline at end of file diff --git a/app/views/general/_split_button_checkbox.html.erb b/app/views/general/_split_button_checkbox.html.erb index e3d1457365..4e2fcc75d0 100644 --- a/app/views/general/_split_button_checkbox.html.erb +++ b/app/views/general/_split_button_checkbox.html.erb @@ -30,11 +30,9 @@ <% else %> - From 722f36569dcf66b5166ad03f296c08e639444aae Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 26 Sep 2023 13:23:39 +0100 Subject: [PATCH 055/383] Fix checkRepeatedItems regression --- app/assets/javascripts/checkbox.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js index 9b9b5b7ee7..ad2542903c 100644 --- a/app/assets/javascripts/checkbox.js +++ b/app/assets/javascripts/checkbox.js @@ -30,9 +30,9 @@ $j(document).ready(function () { const BatchAssetSelection = { selectChildren: function () { let children_checkboxes = $j(':checkbox', $j(this).closest('.batch-selection-scope')); - for(let checkbox of children_checkboxes){ + for (let checkbox of children_checkboxes){ let checkbox_element = { className: checkbox.className, checked: true } - BatchAssetSelection.checkRepeatedItems(checkbox_element) + BatchAssetSelection.checkRepeatedItems.apply(checkbox_element); } return false; @@ -40,9 +40,9 @@ const BatchAssetSelection = { deselectChildren: function () { let children_checkboxes = $j(':checkbox', $j(this).closest('.batch-selection-scope')); - for(let checkbox of children_checkboxes){ + for (let checkbox of children_checkboxes){ let checkbox_element = { className: checkbox.className, checked: false } - BatchAssetSelection.checkRepeatedItems(checkbox_element) + BatchAssetSelection.checkRepeatedItems.apply(checkbox_element) } return false; @@ -60,7 +60,7 @@ const BatchAssetSelection = { if (event.target.nodeName.includes("BUTTON")){ let checkbox_element = $j(this).find('input')[0] checkbox_element.checked = !(checkbox_element.checked) - BatchAssetSelection.checkRepeatedItems(checkbox_element) + BatchAssetSelection.checkRepeatedItems.apply(checkbox_element) } }, From d7009434ca2d99d5d5de11e8ce83a341bce55f0d Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 26 Sep 2023 13:27:05 +0100 Subject: [PATCH 056/383] Use tabs to separate type/ISA views Avoids need for custom JS to show/hide --- .../assets/_batch_asset_selection.html.erb | 43 ++++----- .../_batch_asset_selection_buttons.html.erb | 92 +++++++++---------- 2 files changed, 62 insertions(+), 73 deletions(-) diff --git a/app/views/assets/_batch_asset_selection.html.erb b/app/views/assets/_batch_asset_selection.html.erb index 2d0789246b..02860aa997 100644 --- a/app/views/assets/_batch_asset_selection.html.erb +++ b/app/views/assets/_batch_asset_selection.html.erb @@ -6,36 +6,34 @@ blocked_selector ||= '' -%> -
- - -
-

-
+
<%= render partial: 'assets/batch_asset_selection_buttons', locals: { - id: "items", text: "your items", select_deselect_all: true, collapse_expand: true, show_hide_permissions: show_permissions } %> -
-
+ + +
+
<% @assets.sort_by { |k, v| v.first.class.name }.each do |type, items| %>

<%= render partial: 'assets/isa-tree-toggle' -%> <%= text_for_resource items.first %>(s)

<%= render partial: 'assets/batch_asset_selection_buttons', locals: { - id: items.first.class.table_name, text: (text_for_resource items.first).downcase.pluralize, select_deselect_all: true } %> @@ -53,13 +51,12 @@ <% end %>
-
+
<% unless @assets_not_in_isa.empty? %>

<%= render partial: 'assets/isa-tree-toggle' -%> Items not in ISA

<%= render partial: 'assets/batch_asset_selection_buttons', locals: { - id: "items_not_in_isa", text: "items not in ISA", select_deselect_all: true } %> @@ -84,7 +81,6 @@

<%= render partial: 'assets/isa-tree-toggle'-%> Items in ISA

<%= render partial: 'assets/batch_asset_selection_buttons', locals: { - id: "items_in_isa", text: "items in ISA", blocked_selector: blocked_selector, select_deselect_all: true, @@ -113,10 +109,7 @@
\ No newline at end of file + diff --git a/app/views/assets/_batch_asset_selection_buttons.html.erb b/app/views/assets/_batch_asset_selection_buttons.html.erb index 61b1357dca..a0ba4b80d6 100644 --- a/app/views/assets/_batch_asset_selection_buttons.html.erb +++ b/app/views/assets/_batch_asset_selection_buttons.html.erb @@ -1,5 +1,4 @@ <% - id ||= "items" text ||= "your items" blocked_selector ||= '' @@ -9,50 +8,47 @@ show_hide_permissions ||= false %> -<% if select_deselect_all %> - - -<% end %> -<% if collapse_expand %> - -<% end %> -<% if show_hide_blocked %> - -<% end %> -<% if show_hide_permissions %> - -<% end %> +
+ <% if select_deselect_all %> + + <% end %> + <% if collapse_expand %> + + <% end %> + <% if show_hide_blocked %> + + <% end %> + <% if show_hide_permissions %> + + <% end %> +
From 81f8e704cf28178a4baae89179441679b6500bdf Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 26 Sep 2023 16:38:11 +0100 Subject: [PATCH 057/383] Restructure some views, replace inline styles Re-use bootstrap classes where possible for consistency --- app/assets/javascripts/checkbox.js | 10 +-- app/assets/stylesheets/publishing.scss | 49 ++++++----- app/assets/stylesheets/sharing.scss | 15 ---- app/views/assets/_asset_checkbox_row.html.erb | 87 ++++++++++--------- .../_batch_asset_selection_buttons.html.erb | 26 +++--- app/views/assets/_isa_tree_preview.html.erb | 4 +- app/views/assets/_permission_list.html.erb | 7 +- .../publishing/publish_related_items.html.erb | 3 +- .../general/_split_button_checkbox.html.erb | 39 +++------ 9 files changed, 113 insertions(+), 127 deletions(-) diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js index ad2542903c..59cb770d85 100644 --- a/app/assets/javascripts/checkbox.js +++ b/app/assets/javascripts/checkbox.js @@ -65,7 +65,7 @@ const BatchAssetSelection = { }, toggleManagers: function () { - $j(this).siblings('.managed_by_list').toggle(); + $j('.managed_by_list', $j(this).closest('.isa-tree')).toggle(); return false; }, @@ -128,10 +128,10 @@ const BatchAssetSelection = { let children_assets = $j($j(this).data('blocked_selector'), $j(this).closest('.batch-selection-scope')); for (let asset of children_assets) { //Items in isa tree - if($j($j(asset).parents('div.split_button_parent')).length>0) { + if($j($j(asset).parents('div.batch-asset-selection-isa')).length>0) { // Don't hide "parents" of non-blocked items if (!$j('input[type=checkbox]', $j(asset).parent()).length > 0) { - $j($j(asset).parents('div.split_button_parent')[0]).hide() + $j($j(asset).parents('div.batch-asset-selection-isa')[0]).hide() } //Items not in isa tree } else { @@ -145,8 +145,8 @@ const BatchAssetSelection = { showBlocked: function (){ let children_assets = $j($j(this).data('blocked_selector'), $j(this).closest('.batch-selection-scope')); for (let asset of children_assets) { - if($j($j(asset).parents('div.split_button_parent')).length>0) { - $j($j(asset).parents('div.split_button_parent')[0]).show() + if($j($j(asset).parents('div.batch-asset-selection-isa')).length>0) { + $j($j(asset).parents('div.batch-asset-selection-isa')[0]).show() } else{ $j(asset).show() } diff --git a/app/assets/stylesheets/publishing.scss b/app/assets/stylesheets/publishing.scss index 9af8d7a905..776e408101 100644 --- a/app/assets/stylesheets/publishing.scss +++ b/app/assets/stylesheets/publishing.scss @@ -17,14 +17,6 @@ ul.publishing_options li.secondary { } -.published { - color: limegreen; -} - -span.published { - color: green; -} - span.approve { color: limegreen; font-weight: bolder; @@ -77,18 +69,6 @@ ul.item_for_decision { } } -.publish-colour { - color: $btn-success-bg; -} - -.publish-checkbox { - background-color: $btn-success-bg; - color: $btn-success-color; - padding: 0px 10px; - border-radius: 5px; - display: inline-block; -} - ul.decided_item { padding-left: 1em; } @@ -107,3 +87,32 @@ ul.decided_item li.type_and_title { padding: 0.5em 15px; margin: 0.3em 0em; } + +.batch-asset-selection-buttons { + margin-bottom: 1em; + display: flex; + gap: 1em; +} + +.batch-asset-selection-row { + display: flex; + gap: 1em; + align-items: center; + .visibility_icon { + margin: 0; + } +} + +.permission_list, .managed_by_list { + margin-left: 3em; +} + +.batch-asset-selection-isa-children { + margin-left: 3em; +} + +.batch-asset-select-btn { + input { + margin: 0; + } +} diff --git a/app/assets/stylesheets/sharing.scss b/app/assets/stylesheets/sharing.scss index 9a4ce89df3..6f56e31c5f 100644 --- a/app/assets/stylesheets/sharing.scss +++ b/app/assets/stylesheets/sharing.scss @@ -210,18 +210,3 @@ padding-left: 16px; } } - -.parent-btn-checkbox { - padding: 1px 6px 0px 6px; - border-radius: 5px; - display: inline-block; - height: 25px; - cursor: default; -} -.parent-btn-dropdown { - padding: 2px 6px 10px 0px; - border-radius: 5px; - display: inline-block; - height: 25px; -} - diff --git a/app/views/assets/_asset_checkbox_row.html.erb b/app/views/assets/_asset_checkbox_row.html.erb index bc1191f9c5..c3588019bd 100644 --- a/app/views/assets/_asset_checkbox_row.html.erb +++ b/app/views/assets/_asset_checkbox_row.html.erb @@ -18,58 +18,63 @@ itemid="#{item.class.name}_#{item.id}" - toggle ||=false + toggle ||= false show_managers ||= false show_permissions ||= false + show_managers = show_managers && (can_view || item.can_see_hidden_item?(current_user.person)) -%>
- <% if toggle %> - <%= render partial: 'assets/isa-tree-toggle' -%> - <% end %> - <% if can_view %> -
- <%= render partial: 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: itemid, - checked: checked, - checkbox_text: "", - published: published, - cant_manage: !(can_manage), - toggle: toggle } -%> -
+
+ <% if toggle %> + <%= render partial: 'assets/isa-tree-toggle' -%> + <% end %> + + <%= render partial: 'general/split_button_checkbox', + locals: { checkbox_id: publishing_item_param(item), + checkbox_class: itemid, + checked: can_view && checked, + not_visible: !can_view, + published: can_view && published, + cant_manage: !(can_manage), + toggle: toggle } -%> +
- <%= text_for_resource item -%>: <%= link_to item.title, item, target: "_blank" -%> - <% if show_permissions %> - + <%= text_for_resource item -%>: + <% if can_view %> + <%= link_to item.title, item, target: "_blank" -%> + <% else %> + This item is hidden to you + <% end %> +
+ + <% if show_permissions %> + <% if can_view %> + <%= list_item_visibility(item)-%> + <% else %> + <%= list_item_visibility(item)-%> <% end %> - - <% else %> -
- <%= render partial: 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: itemid, - not_visible: true, - toggle: toggle, - cb_parent_selector: cb_parent_selector} -%> -
-
<%= text_for_resource item -%>: This item is hidden to you
- <%=list_item_visibility(item)-%> - <% show_permissions=false%> - <% unless current_user.try(:person) && item.can_see_hidden_item?(current_user.person) %> - <% show_managers=false%> <% end %> - <% end %> + + <% if show_managers %> + + + + <% end %> +
+ <% if show_managers %> - - - - diff --git a/app/views/assets/_batch_asset_selection_buttons.html.erb b/app/views/assets/_batch_asset_selection_buttons.html.erb index a0ba4b80d6..bf2a1f9ae2 100644 --- a/app/views/assets/_batch_asset_selection_buttons.html.erb +++ b/app/views/assets/_batch_asset_selection_buttons.html.erb @@ -8,45 +8,45 @@ show_hide_permissions ||= false %> -
+
<% if select_deselect_all %> -
- + <% end %> <% if collapse_expand %> -
- + <% end %> <% if show_hide_blocked %> - <% end %> <% if show_hide_permissions %> -
- + diff --git a/app/views/assets/_isa_tree_preview.html.erb b/app/views/assets/_isa_tree_preview.html.erb index 673ff4dcdf..53bbc3363c 100644 --- a/app/views/assets/_isa_tree_preview.html.erb +++ b/app/views/assets/_isa_tree_preview.html.erb @@ -17,7 +17,7 @@ show_permissions ||= false show_managers ||= false -%> -
+
<%= render partial: "assets/asset_checkbox_row", object: item, locals: { html_classes: "publishing_options", @@ -29,7 +29,7 @@ } -%> <% if children.any? %> -
+
<% children.each do |child| %> <%= render partial: "assets/isa_tree_preview", object: child, diff --git a/app/views/assets/_permission_list.html.erb b/app/views/assets/_permission_list.html.erb index 094d22a222..d5fb3fed95 100644 --- a/app/views/assets/_permission_list.html.erb +++ b/app/views/assets/_permission_list.html.erb @@ -1,17 +1,16 @@ <% item ||= permission_list - item_id = "#{item.class.name}_#{item.id}" downloadable = item.try(:is_downloadable?) -%> -
" style="display:none;margin-left:5em" > + diff --git a/app/views/presentations/edit.html.erb b/app/views/presentations/edit.html.erb index ef53d5205d..4f02f995fa 100644 --- a/app/views/presentations/edit.html.erb +++ b/app/views/presentations/edit.html.erb @@ -14,8 +14,8 @@ <%= f.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%>
- <%= render :partial => 'extended_metadata/extended_metadata_type_selection', :locals => {:f => f,:resource => @presentation } %> - <%= render :partial => 'extended_metadata/extended_metadata_attribute_input', :locals => {:f => f, :resource => @presentation } %>s + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @presentation } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @presentation } %> <%# only the owner should get to see this option (ability to reload defaults remain in 'edit' action, but project selector is disabled) -%> <% if @presentation.can_manage? -%> diff --git a/app/views/presentations/new.html.erb b/app/views/presentations/new.html.erb index 3662439502..6dd32274fb 100644 --- a/app/views/presentations/new.html.erb +++ b/app/views/presentations/new.html.erb @@ -21,8 +21,8 @@
<%= fields_for(@presentation) do |presentation_fields| %> - <%= render :partial => 'extended_metadata/extended_metadata_type_selection', :locals => {:f => presentation_fields,:resource => @presentation } %> - <%= render :partial => 'extended_metadata/extended_metadata_attribute_input', :locals => {:f => presentation_fields, :resource => @presentation } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: presentation_fields, resource: @presentation } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: presentation_fields, resource: @presentation } %> <% end %> <%= render :partial => "projects/project_selector", :locals => { :resource => @presentation } -%> diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index 4a7a0410fa..53591297cd 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -99,7 +99,7 @@
- <%= render :partial => 'extended_metadata/extended_metadata_attribute_values', :locals => {:resource => @project} %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @project } %>
diff --git a/app/views/sops/edit.html.erb b/app/views/sops/edit.html.erb index 303cf80fe2..b15e10bee0 100644 --- a/app/views/sops/edit.html.erb +++ b/app/views/sops/edit.html.erb @@ -15,12 +15,12 @@ <%= f.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%>
- <%= render :partial => 'extended_metadata/extended_metadata_type_selection', :locals => {:f => f,:resource => @sop } %> - <%= render :partial => 'extended_metadata/extended_metadata_attribute_input', :locals => {:f => f, :resource => @sop } %>s + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @sop } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @sop } %> <%= render :partial => 'assets/license_selector', :locals => { :resource => @sop } %> - <%= render :partial=> "assets/discussion_links_form", :locals=>{ :resource => @sop} -%> + <%= render :partial=> "assets/discussion_links_form", :locals=>{:resource => @sop} -%> <%= render :partial=>"assets/asset_form_bottom", :locals=> {:show_publications=>false, :f => f}-%> diff --git a/app/views/sops/new.html.erb b/app/views/sops/new.html.erb index f6e2a27a49..d8480b0bf3 100644 --- a/app/views/sops/new.html.erb +++ b/app/views/sops/new.html.erb @@ -21,8 +21,8 @@
<%= fields_for(@sop) do |sop_fields| %> - <%= render :partial => 'extended_metadata/extended_metadata_type_selection', :locals => {:f => sop_fields,:resource => @sop } %> - <%= render :partial => 'extended_metadata/extended_metadata_attribute_input', :locals => {:f => sop_fields, :resource => @sop } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: sop_fields, resource: @sop } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: sop_fields, resource: @sop } %> <% end %> <%= render :partial => "projects/project_selector", :locals => { :resource => @sop } %> From d1bbe94ea3e4139349baf31192a73acc61645026 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 16 Nov 2023 14:31:12 +0100 Subject: [PATCH 194/383] Make export ISA button visible if user can view the investigation --- app/views/investigations/_buttons.html.erb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/investigations/_buttons.html.erb b/app/views/investigations/_buttons.html.erb index 7d01679ca0..1dfc891483 100644 --- a/app/views/investigations/_buttons.html.erb +++ b/app/views/investigations/_buttons.html.erb @@ -9,14 +9,15 @@ <% if item.can_edit? -%> <% if displaying_single_page? %> <%= button_link_to("Design #{t('study')}", 'new', new_isa_study_path(investigation_id: item, single_page: params[:single_page])) %> - <% if Seek::Config.isa_json_compliance_enabled %> - <%= button_link_to("Export ISA", 'download', export_isa_single_page_path(id: params[:single_page], investigation_id: item)) %> - <% end -%> <% else -%> <%= add_new_item_to_dropdown(item) %> <% end -%> <% end -%> +<% if item.can_view? && displaying_single_page? && Seek::Config.isa_json_compliance_enabled%> + <%= button_link_to("Export ISA", 'download', export_isa_single_page_path(id: params[:single_page], investigation_id: item)) %> +<% end %> + <%= item_actions_dropdown do %> <% if item.can_edit? %>
  • <%= image_tag_for_key('edit', edit_investigation_path(item), "Edit #{t('investigation')}", nil, "Edit #{t('investigation')}") -%>
  • From 9ac77fa255567f3e5931ff1573e1a76663b7a709 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 16 Nov 2023 14:58:39 +0100 Subject: [PATCH 195/383] Simplification of single pages controller --- app/controllers/single_pages_controller.rb | 9 ++------- lib/isa_exporter.rb | 3 ++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/controllers/single_pages_controller.rb b/app/controllers/single_pages_controller.rb index 13ecd12ade..9243ec9f61 100644 --- a/app/controllers/single_pages_controller.rb +++ b/app/controllers/single_pages_controller.rb @@ -9,7 +9,6 @@ class SinglePagesController < ApplicationController before_action :set_up_instance_variable before_action :project_single_page_enabled? - before_action :find_authorized_investigation, only: :export_isa before_action :check_user_logged_in, only: %i[batch_sharing_permission_preview batch_change_permission_for_selected_items] respond_to :html, :js @@ -50,9 +49,10 @@ def dynamic_table_data end def export_isa + @inv = Investigation.find(params[:investigation_id]) raise 'The investigation cannot be found!' if @inv.blank? - isa = IsaExporter::Exporter.new(@inv).export + isa = IsaExporter::Exporter.new(@inv, current_user).export send_data isa, filename: 'isa.json', type: 'application/json', deposition: 'attachment' rescue Exception => e respond_to do |format| @@ -358,11 +358,6 @@ def set_up_instance_variable @single_page = true end - def find_authorized_investigation - investigation = Investigation.find(params[:investigation_id]) - @inv = investigation if investigation.can_edit? - end - def check_user_logged_in return if current_user diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 0308d4e3c3..a5a1f179f2 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -1,8 +1,9 @@ # noinspection ALL module IsaExporter class Exporter - def initialize(investigation) + def initialize(investigation, user) @investigation = investigation + @current_user = user @OBJECT_MAP = {} end From 1205f2b3263090bccd25ba1ea788508b32b97e66 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 17 Nov 2023 16:33:59 +0100 Subject: [PATCH 196/383] Add Permission check for studies and assays. --- lib/isa_exporter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index a5a1f179f2..c898c1a15f 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -36,7 +36,7 @@ def convert_investigation isa_investigation[:people] = people studies = [] - @investigation.studies.each { |s| studies << convert_study(s) } + @investigation.studies.each { |s| studies << convert_study(s) if s.can_view?(@current_user) } isa_investigation[:studies] = studies @OBJECT_MAP = @OBJECT_MAP.merge(isa_investigation) @@ -62,7 +62,6 @@ def convert_study_comments(study) }) end end - ################################################### study_comments.append({ '@id': "#study_comment/#{ [study_id, UUID.new.generate].join('_') }", @@ -136,7 +135,7 @@ def convert_study(study) assay_stream end - isa_study[:assays] = assay_streams.map { |assay_stream| convert_assays(assay_stream) } + isa_study[:assays] = assay_streams.map { |assay_stream| convert_assays(assay_stream) }.compact isa_study[:factors] = [] isa_study[:unitCategories] = [] @@ -186,6 +185,8 @@ def convert_assay_comments(assays) end def convert_assays(assays) + return unless assays.all? { |a| a.can_view?(@current_user) } + all_sample_types = assays.map(&:sample_type) first_assay = assays.detect { |s| s.position.zero? } raise 'No assay could be found!' unless first_assay From 2fcf0e51936a9824f81bcd2676b56253b4c4fdf7 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 17 Nov 2023 16:48:16 +0100 Subject: [PATCH 197/383] Hide sources if user doesn't have viewing permission --- lib/isa_exporter.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index c898c1a15f..63d3a474e6 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -323,11 +323,19 @@ def convert_materials_sources(sample_type) sample_type.sample_attributes.select { |sa| sa.isa_tag&.isa_source_characteristic? } sample_type.samples.map do |s| - { - '@id': "#source/#{s.id}", - name: s.get_attribute_value(with_tag_source), - characteristics: convert_characteristics(s, with_tag_source_characteristic) - } + if s.can_view?(@current_user) + { + '@id': "#source/#{s.id}", + name: s.get_attribute_value(with_tag_source), + characteristics: convert_characteristics(s, with_tag_source_characteristic) + } + else + { + '@id': "#source/HIDDEN", + name: 'Hidden Sample', + characteristics: [] + } + end end end From 1ecc54b276b1bcbf1369473f080005af726ffe0b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 17 Nov 2023 17:23:59 +0100 Subject: [PATCH 198/383] Hide unauthorized samples and references to unauthorized sources --- lib/isa_exporter.rb | 73 ++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 63d3a474e6..71e06a634e 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -345,25 +345,47 @@ def convert_materials_samples(sample_type) sample_type.sample_attributes.select { |sa| sa.isa_tag&.isa_sample_characteristic? } seek_sample_multi_attribute = sample_type.sample_attributes.detect(&:seek_sample_multi?) sample_type.samples.map do |s| - { - '@id': "#sample/#{s.id}", - name: s.get_attribute_value(with_tag_sample), - derivesFrom: extract_sample_ids(s.get_attribute_value(seek_sample_multi_attribute), 'source'), - characteristics: convert_characteristics(s, with_tag_sample_characteristic), - factorValues: [ - { - category: { - '@id': '' - }, - value: { - annotationValue: '', - termSource: '', - termAccession: '' - }, - unit: get_unit - } - ] - } + if s.can_view?(@current_user) + { + '@id': "#sample/#{s.id}", + name: s.get_attribute_value(with_tag_sample), + derivesFrom: extract_sample_ids(s.get_attribute_value(seek_sample_multi_attribute), 'source'), + characteristics: convert_characteristics(s, with_tag_sample_characteristic), + factorValues: [ + { + category: { + '@id': '' + }, + value: { + annotationValue: '', + termSource: '', + termAccession: '' + }, + unit: get_unit + } + ] + } + else + { + '@id': "#sample/HIDDEN", + name: '', + derivesFrom: extract_sample_ids(s.get_attribute_value(seek_sample_multi_attribute), 'source'), + characteristics: [], + factorValues: [ + { + category: { + '@id': '' + }, + value: { + annotationValue: '', + termSource: '', + termAccession: '' + }, + unit: { termSource: '', termAccession: '', comments: [] } + } + ] + } + end end end @@ -633,8 +655,17 @@ def convert_parameter_values(sample_group_hash, isa_parameter_value_attributes) end end - def extract_sample_ids(obj, type) - Array.wrap(obj).map { |item| { '@id': "##{type}/#{item[:id]}" } } + def extract_sample_ids(input_obj_list, type) + sample_ids = input_obj_list.map { |io| io[:id] } + authorized_sample_ids = Sample.where(id: sample_ids).select { |sample| sample.can_view?(@current_user) }.map(&:id) + + sample_ids.map do |s_id| + if authorized_sample_ids.include?(s_id) + { '@id': "##{type}/#{s_id}" } + else + { '@id': "##{type}/HIDDEN" } + end + end end def get_ontology_details(sample_attribute, label, vocab_term) From e73f459c10e0f21e9b8af9d10115cf31ced0e43e Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 17 Nov 2023 19:17:17 +0100 Subject: [PATCH 199/383] Hide unauthorized samples in processSequence --- lib/isa_exporter.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 71e06a634e..e8b5203a2a 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -458,7 +458,7 @@ def convert_process_sequence(sample_type, sop_ids, id) parameterValues: convert_parameter_values(samples_group, isa_parameter_value_attributes), performer: '', date: '', - inputs: input_ids.first.map { |input| { '@id': "##{type}/#{input[:id]}" } }, + inputs: process_sequence_input(input_ids.first, type), outputs: process_sequence_output(samples_group) } # Study processes don't have a previousProcess and nextProcess @@ -617,6 +617,17 @@ def group_samples_by_input_and_parameter_value(sample_type) grouped_samples.transform_keys { |key| group_id(key) } end + def process_sequence_input(input_ids, type) + authorized_sample_ids = Sample.where(id: input_ids).select { |s| s.can_view?(@current_user).map(&:id) } + input_ids.map do |input| + if authorized_sample_ids.include?(input[:id]) + { '@id': "##{type}/#{input[:id]}" } + else + { '@id': "##{type}/HIDDEN" } + end + end + end + def process_sequence_output(samples_hash) prefix = 'sample' samples_hash.map do |sample_hash| @@ -630,7 +641,11 @@ def process_sequence_output(samples_hash) raise 'Defective ISA process!' end end - { '@id': "##{prefix}/#{sample.id}" } + if sample.can_view?(@current_user) + { '@id': "##{prefix}/#{sample.id}" } + else + { '@id': "##{prefix}/HIDDEN" } + end end end From 43a598035c4b0a8269ec33bc6d5b79b8743070dc Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 17 Nov 2023 20:32:01 +0100 Subject: [PATCH 200/383] Hide unauthorized samples in assays --- lib/isa_exporter.rb | 57 +++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index e8b5203a2a..c4b82b60dd 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -208,8 +208,7 @@ def convert_assays(assays) isa_assay[:characteristicCategories] = convert_characteristic_categories(nil, assays) isa_assay[:materials] = { # Here, the first assay's samples will be enough - samples: - first_assay.samples.map { |s| find_sample_origin([s], 1) }.flatten.uniq.map { |s| { '@id': "#sample/#{s}" } }, # the samples from study level that are referenced in this assay's samples, + samples: assay_samples(first_assay), # the samples from study level that are referenced in this assay's samples, otherMaterials: convert_other_materials(all_sample_types) } isa_assay[:processSequence] = @@ -391,7 +390,7 @@ def convert_materials_samples(sample_type) def convert_characteristics(sample, attributes) attributes.map do |c| - value = sample.get_attribute_value(c) || '' + value = sample.can_view?(@current_user) ? (sample.get_attribute_value(c) || '') : '' ontology = get_ontology_details(c, value, true) { category: { @@ -482,12 +481,21 @@ def convert_data_files(sample_types) return [] unless with_tag_data_file st.samples.map do |s| - { - '@id': "#data_file/#{s.id}", - name: s.get_attribute_value(with_tag_data_file), - type: with_tag_data_file.title, - comments: with_tag_data_file_comment.map { |d| { name: d.title, value: s.get_attribute_value(d).to_s } } - } + if s.can_view?(@current_user) + { + '@id': "#data_file/#{s.id}", + name: s.get_attribute_value(with_tag_data_file), + type: with_tag_data_file.title, + comments: with_tag_data_file_comment.map { |d| { name: d.title, value: s.get_attribute_value(d).to_s } } + } + else + { + '@id': "#data_file/HIDDEN", + name: 'HIDDEN', + type: with_tag_data_file.title, + comments: [] + } + end end end @@ -513,20 +521,35 @@ def convert_other_materials(sample_types) st .samples .map do |s| - { - '@id': "#other_material/#{s.id}", - name: s.get_attribute_value(with_tag_isa_other_material), - type: with_tag_isa_other_material.title, - characteristics: convert_characteristics(s, with_tag_isa_other_material_characteristics), - # It can sometimes be other_material or sample!!!! SHOULD BE DYNAMIC - derivesFrom: extract_sample_ids(s.get_attribute_value(seek_sample_multi_attribute), type) - } + if s.can_view?(@current_user) + { + '@id': "#other_material/#{s.id}", + name: s.get_attribute_value(with_tag_isa_other_material), + type: with_tag_isa_other_material.title, + characteristics: convert_characteristics(s, with_tag_isa_other_material_characteristics), + # It can sometimes be other_material or sample!!!! SHOULD BE DYNAMIC + derivesFrom: extract_sample_ids(s.get_attribute_value(seek_sample_multi_attribute), type) + } + else + { + '@id': "#other_material/HIDDEN", + name: 'HIDDEN', + type: with_tag_isa_other_material.title, + characteristics: convert_characteristics(s, with_tag_isa_other_material_characteristics), + # It can sometimes be other_material or sample!!!! SHOULD BE DYNAMIC + derivesFrom: extract_sample_ids(s.get_attribute_value(seek_sample_multi_attribute), type) + } + end end .flatten end other_materials end + def assay_samples(first_assay) + first_assay.samples.map { |s| find_sample_origin([s], 1) }.flatten.uniq.map { |s| { '@id': "#sample/#{s}" } } + end + def export convert_investigation @OBJECT_MAP.to_json From 958853c161f7edab5218bdc3611ab359d798fa75 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 20 Nov 2023 13:29:06 +0100 Subject: [PATCH 201/383] Fixed existing integration tests --- lib/isa_exporter.rb | 11 ++++++----- test/integration/isa_exporter_test.rb | 18 +++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index c4b82b60dd..b9e13aa287 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -640,11 +640,12 @@ def group_samples_by_input_and_parameter_value(sample_type) grouped_samples.transform_keys { |key| group_id(key) } end - def process_sequence_input(input_ids, type) - authorized_sample_ids = Sample.where(id: input_ids).select { |s| s.can_view?(@current_user).map(&:id) } - input_ids.map do |input| - if authorized_sample_ids.include?(input[:id]) - { '@id': "##{type}/#{input[:id]}" } + def process_sequence_input(inputs, type) + input_ids = inputs.map { |input| input[:id] } + authorized_sample_ids = Sample.where(id: input_ids).select { |s| s.can_view?(@current_user) }.map(&:id).compact + input_ids.map do |input_id| + if authorized_sample_ids.include?(input_id) + { '@id': "##{type}/#{input_id}" } else { '@id': "##{type}/HIDDEN" } end diff --git a/test/integration/isa_exporter_test.rb b/test/integration/isa_exporter_test.rb index 19304204ff..1b4aeb2f9d 100644 --- a/test/integration/isa_exporter_test.rb +++ b/test/integration/isa_exporter_test.rb @@ -195,7 +195,7 @@ def before_all end materials = materials.map { |so| so['@id'] } processes = processes.flatten.map { |p| p['@id'] } - materials.each { |p| assert processes.include?(p) } +materials.each { |p| assert processes.include?(p) } end # 23 @@ -295,7 +295,7 @@ def valid_date?(date) end def create_basic_isa_project - person = FactoryBot.create(:person, project: @project) + person = @current_user.person source = FactoryBot.create( @@ -326,7 +326,8 @@ def create_basic_isa_project :study, investigation: @investigation, sample_types: [source, sample_collection], - sops: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy))] + sops: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy))], + contributor: person ) FactoryBot.create( @@ -334,7 +335,7 @@ def create_basic_isa_project study: study, sample_type: assay_sample_type, sop_ids: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy)).id], - contributor: @current_user.person, + contributor: person, position: 0 ) @@ -355,7 +356,8 @@ def create_basic_isa_project .sample_controlled_vocab_terms .first .label - } + }, + contributor: person sample_2 = FactoryBot.create :sample, @@ -368,7 +370,8 @@ def create_basic_isa_project 'sample collection parameter value 1': 'sample collection parameter value 1', 'Sample Name': 'sample name', 'sample characteristic 1': 'sample characteristic 1' - } + }, + contributor: person FactoryBot.create :sample, title: 'sample_3', @@ -380,7 +383,8 @@ def create_basic_isa_project 'Assay 1 parameter value 1': 'Assay 1 parameter value 1', 'Extract Name': 'Extract Name', 'other material characteristic 1': 'other material characteristic 1' - } + }, + contributor: person { source: source, sample_collection: sample_collection, From 2ea284f03cadfe671a42a91937a0addf9794f28e Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 20 Nov 2023 13:44:21 +0100 Subject: [PATCH 202/383] Fix unit tests --- test/unit/isa_exporter_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/isa_exporter_test.rb b/test/unit/isa_exporter_test.rb index 96936387f1..c4cd891778 100644 --- a/test/unit/isa_exporter_test.rb +++ b/test/unit/isa_exporter_test.rb @@ -2,7 +2,8 @@ class IsaExporterTest < ActionController::TestCase test 'find sample origin' do - controller = IsaExporter::Exporter.new FactoryBot.create(:investigation) + current_user = FactoryBot.create(:user) + controller = IsaExporter::Exporter.new(FactoryBot.create(:investigation), current_user) project = FactoryBot.create(:project) type_1 = FactoryBot.create(:simple_sample_type, project_ids: [project.id]) From 5449d1d86f7b47320f9e52fbc92dc7c564d7648c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 20 Nov 2023 15:32:39 +0100 Subject: [PATCH 203/383] Fix functional tests --- lib/isa_exporter.rb | 2 +- .../single_pages_controller_test.rb | 28 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index b9e13aa287..58ac147b75 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -123,7 +123,7 @@ def convert_study(study) isa_study[:protocols] = protocols isa_study[:processSequence] = convert_process_sequence(study.sample_types.second, study.sops.map(&:id).join("_"), study.id) - assay_streams = study.assays.map { |assay| [assay] if assay.position.zero? } + assay_streams = study.assays.map { |assay| [assay] if assay&.position&.zero? }.compact .compact .map do |assay_stream| last_assay = assay_stream.first diff --git a/test/functional/single_pages_controller_test.rb b/test/functional/single_pages_controller_test.rb index a202f40e74..27986aa682 100644 --- a/test/functional/single_pages_controller_test.rb +++ b/test/functional/single_pages_controller_test.rb @@ -35,13 +35,33 @@ def setup assert_equal inv_two.title, json['children'][1]['text'] end - test 'should not export isa from unauthorized investigation' do + test 'Should not export an isa json with unauthorized studies and assays' do with_config_value(:project_single_page_enabled, true) do + current_user = FactoryBot.create(:user) + other_user = FactoryBot.create(:user) + + login_as(current_user) project = FactoryBot.create(:project) - investigation = FactoryBot.create(:investigation, policy: FactoryBot.create(:private_policy), projects: [project]) + current_user.person.add_to_project_and_institution(project, current_user.person.institutions.first) + other_user.person.add_to_project_and_institution(project, current_user.person.institutions.first) + investigation = FactoryBot.create(:investigation, projects: [project], contributor: current_user.person) + + source_sample_type = FactoryBot.create(:isa_source_sample_type) + sample_collection_sample_type = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: source_sample_type) + accessible_study = FactoryBot.create(:study, + investigation: investigation, + sample_types:[source_sample_type, sample_collection_sample_type], + contributor: current_user.person) + + assay_sample_type = FactoryBot.create(:isa_assay_sample_type, linked_sample_type: sample_collection_sample_type) + # Create a 'private' assay + FactoryBot.create(:assay, sample_type: assay_sample_type, study: accessible_study, contributor: other_user.person) + get :export_isa, params: { id: project.id, investigation_id: investigation.id } - assert_equal flash[:error], 'The investigation cannot be found!' - assert_redirected_to action: :show + + json_investigation = JSON.parse(response.body) + assert json_investigation['studies'].map { |s| s['title'] }.include? accessible_study.title + assert json_investigation['studies'].first['assays'].blank? end end From 14085e597567bd79218e3bda8aa3e2647ad002dd Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 21 Nov 2023 09:06:52 +0100 Subject: [PATCH 204/383] Create tests for sample permissions --- .../single_pages_controller_test.rb | 70 +++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/test/functional/single_pages_controller_test.rb b/test/functional/single_pages_controller_test.rb index 27986aa682..28be6a4463 100644 --- a/test/functional/single_pages_controller_test.rb +++ b/test/functional/single_pages_controller_test.rb @@ -46,14 +46,61 @@ def setup other_user.person.add_to_project_and_institution(project, current_user.person.institutions.first) investigation = FactoryBot.create(:investigation, projects: [project], contributor: current_user.person) - source_sample_type = FactoryBot.create(:isa_source_sample_type) - sample_collection_sample_type = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: source_sample_type) + source_sample_type = FactoryBot.create(:isa_source_sample_type, template_id: FactoryBot.create(:isa_source_template).id) + sample_collection_sample_type = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: source_sample_type, template_id: FactoryBot.create(:isa_sample_collection_template).id) accessible_study = FactoryBot.create(:study, investigation: investigation, sample_types:[source_sample_type, sample_collection_sample_type], contributor: current_user.person) - assay_sample_type = FactoryBot.create(:isa_assay_sample_type, linked_sample_type: sample_collection_sample_type) + + source_sample = FactoryBot.create(:sample, + title: 'source 1', + sample_type: source_sample_type, + project_ids: [project.id], + data: { + 'Source Name': 'Source Name', + 'Source Characteristic 1': 'Source Characteristic 1', + 'Source Characteristic 2': + source_sample_type + .sample_attributes + .find_by_title('Source Characteristic 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label + }, + contributor: current_user.person) + + study_sample = + FactoryBot.create(:sample, + title: 'study sample 1', + sample_type: sample_collection_sample_type, + project_ids: [project.id], + data: { + Input: [source_sample.id], + 'sample collection': 'sample collection', + 'sample collection parameter value 1': 'sample collection parameter value 1', + 'Sample Name': 'sample name', + 'sample characteristic 1': 'sample characteristic 1' + }, + contributor: current_user.person) + + hidden_study_sample = + FactoryBot.create(:sample, + title: 'study sample 2', + sample_type: sample_collection_sample_type, + project_ids: [project.id], + data: { + Input: [source_sample.id], + 'sample collection': 'sample collection', + 'sample collection parameter value 1': 'sample collection parameter value 2', + 'Sample Name': 'sample name 2', + 'sample characteristic 1': 'sample characteristic 2' + }, + contributor: other_user.person) + + assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id) # Create a 'private' assay FactoryBot.create(:assay, sample_type: assay_sample_type, study: accessible_study, contributor: other_user.person) @@ -61,7 +108,22 @@ def setup json_investigation = JSON.parse(response.body) assert json_investigation['studies'].map { |s| s['title'] }.include? accessible_study.title - assert json_investigation['studies'].first['assays'].blank? + study_json = json_investigation['studies'].first + assert study_json['assays'].blank? + + sample_names = study_json['materials']['samples'].map { |sample| sample['name'] } + + assert sample_names.include?(study_sample.title) + refute sample_names.include?(hidden_study_sample.title) + + study_process_sequence = study_json['processSequence'] + output_ids=[] + study_process_samples = study_process_sequence.map do |process| + process['outputs'].map { |output| output_ids.push(output['@id']) } + end + + assert output_ids.include? "#sample/#{study_sample.id}" + refute output_ids.include? "#sample/#{hidden_study_sample.id}" end end From ae138a69936e4840d864a824ba4e02dedd98348c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 21 Nov 2023 12:07:26 +0100 Subject: [PATCH 205/383] add test for assay samples --- .../single_pages_controller_test.rb | 229 +++++++++++++++++- 1 file changed, 216 insertions(+), 13 deletions(-) diff --git a/test/functional/single_pages_controller_test.rb b/test/functional/single_pages_controller_test.rb index 28be6a4463..3ad905c874 100644 --- a/test/functional/single_pages_controller_test.rb +++ b/test/functional/single_pages_controller_test.rb @@ -100,30 +100,233 @@ def setup }, contributor: other_user.person) - assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id) - # Create a 'private' assay - FactoryBot.create(:assay, sample_type: assay_sample_type, study: accessible_study, contributor: other_user.person) + # Create a 'private' assay in an assay stream + assay_1_stream_1_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id) + assay_1_stream_1 = FactoryBot.create(:assay, position: 0, sample_type: assay_1_stream_1_sample_type, study: accessible_study, contributor: current_user.person) + assay_2_stream_1_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: assay_1_stream_1_sample_type, template_id: FactoryBot.create(:isa_assay_data_file_template).id) + assay_2_stream_1 = FactoryBot.create(:assay, position:1, sample_type: assay_2_stream_1_sample_type, study: accessible_study, contributor: other_user.person) + + # Create an assay stream with all assays visible + assay_1_stream_2_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id) + assay_1_stream_2 = FactoryBot.create(:assay, position: 0, sample_type: assay_1_stream_2_sample_type, study: accessible_study, contributor: current_user.person) + assay_2_stream_2_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: assay_1_stream_2_sample_type, template_id: FactoryBot.create(:isa_assay_data_file_template).id) + assay_2_stream_2 = FactoryBot.create(:assay, position:1, sample_type: assay_2_stream_2_sample_type, study: accessible_study, contributor: current_user.person) + + # create samples in second assay stream with viewing permission + + assay_1_stream_2_sample = + FactoryBot.create(:sample, + title: 'Assay 1 - stream 2 - sample 1', + sample_type: assay_1_stream_2_sample_type, + project_ids: [project.id], + data: { + Input: [study_sample.id], + 'Protocol Assay 1': 'Protocol Assay 1', + 'Assay 1 parameter value 1': 'Assay 1 parameter value 1', + 'Assay 1 parameter value 2': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 1 parameter value 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label, + 'Assay 1 parameter value 3': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 1 parameter value 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label, + 'Extract Name': 'Extract 1 stream 2', + 'other material characteristic 1': 'other material characteristic 1', + 'other material characteristic 2': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'other material characteristic 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label, + 'other material characteristic 3': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'other material characteristic 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label}, + contributor: current_user.person) + + assay_1_stream_2_hidden_sample = + FactoryBot.create(:sample, + title: 'Assay 1 - stream 2 - sample 2', + sample_type: assay_1_stream_2_sample_type, + project_ids: [project.id], + data: { + Input: [study_sample.id], + 'Protocol Assay 1': 'Protocol Assay 1', + 'Assay 1 parameter value 1': 'Assay 1 parameter value 1', + 'Assay 1 parameter value 2': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 1 parameter value 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label, + 'Assay 1 parameter value 3': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 1 parameter value 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label, + 'Extract Name': 'Extract 1 stream 2', + 'other material characteristic 1': 'other material characteristic 1', + 'other material characteristic 2': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'other material characteristic 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label, + 'other material characteristic 3': assay_1_stream_2_sample_type + .sample_attributes + .find_by(title: 'other material characteristic 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label}, + contributor: other_user.person) + + assay_2_stream_2_sample = + FactoryBot.create(:sample, + title: 'Assay 2 - stream 2 - sample 1', + sample_type: assay_2_stream_2_sample_type, + project_ids: [project.id], + data: { + Input: [assay_1_stream_2_sample.id], + 'Protocol Assay 2': 'Protocol Assay 2', + 'Assay 2 parameter value 1': 'Assay 2 parameter value 1', + 'Assay 2 parameter value 2': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 2 parameter value 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label, + 'Assay 2 parameter value 3': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 2 parameter value 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label, + 'File Name': 'file 1 stream 2', + 'Data file comment 1': 'Data file comment 1', + 'Data file comment 2': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Data file comment 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label, + 'Data file comment 3': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Data file comment 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label}, + contributor: current_user.person) + + assay_2_stream_2_hidden_sample = + FactoryBot.create(:sample, + title: 'Assay 2 - stream 2 - sample 2', + sample_type: assay_2_stream_2_sample_type, + project_ids: [project.id], + data: { + Input: [assay_1_stream_2_sample.id], + 'Protocol Assay 2': 'Protocol Assay 2', + 'Assay 2 parameter value 1': 'Assay 2 parameter value 1', + 'Assay 2 parameter value 2': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 2 parameter value 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label, + 'Assay 2 parameter value 3': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Assay 2 parameter value 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label, + 'File Name': 'file 1 stream 2', + 'Data file comment 1': 'Data file comment 1', + 'Data file comment 2': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Data file comment 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label, + 'Data file comment 3': assay_2_stream_2_sample_type + .sample_attributes + .find_by(title: 'Data file comment 3') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .second + .label}, + contributor: other_user.person) + get :export_isa, params: { id: project.id, investigation_id: investigation.id } + assert_response :success json_investigation = JSON.parse(response.body) assert json_investigation['studies'].map { |s| s['title'] }.include? accessible_study.title study_json = json_investigation['studies'].first - assert study_json['assays'].blank? - sample_names = study_json['materials']['samples'].map { |sample| sample['name'] } + # Only one assay should end up in 1 assay stream in the ISA JSON + assert_equal accessible_study.assays.count, 4 + assert_equal study_json['assays'].count, 1 + + sample_ids = study_json['materials']['samples'].map { |sample| sample['@id'] } + + # Check whether permitted samples end up in the materials + assert sample_ids.include?("#sample/#{study_sample.id}") + refute sample_ids.include?("#sample/#{hidden_study_sample.id}") + + # Check whether permitted study samples end up in the study's processSequence + study_output_ids = [] + study_json['processSequence'].map do |process| + process['outputs'].map { |output| study_output_ids.push(output['@id']) } + end + + assert study_output_ids.include? "#sample/#{study_sample.id}" + refute study_output_ids.include? "#sample/#{hidden_study_sample.id}" + + assay_json = study_json['assays'].first + + # Check otherMaterials + other_material_ids = assay_json['materials']['otherMaterials'].map { |om| om['@id'] } + assert other_material_ids.include? "#other_material/#{assay_1_stream_2_sample.id}" + refute other_material_ids.include? "#other_material/#{assay_1_stream_2_hidden_sample.id}" - assert sample_names.include?(study_sample.title) - refute sample_names.include?(hidden_study_sample.title) + # Check dataFiles + data_file_ids = assay_json['dataFiles'].map { |df| df['@id'] } + assert data_file_ids.include? "#data_file/#{assay_2_stream_2_sample.id}" + refute data_file_ids.include? "#data_file/#{assay_2_stream_2_hidden_sample.id}" - study_process_sequence = study_json['processSequence'] - output_ids=[] - study_process_samples = study_process_sequence.map do |process| - process['outputs'].map { |output| output_ids.push(output['@id']) } + # Check whether permitted study samples end up in the assay's processSequence + assay_output_ids = [] + assay_json['processSequence'].map do |process| + process['outputs'].map { |output| assay_output_ids.push(output['@id']) } end - assert output_ids.include? "#sample/#{study_sample.id}" - refute output_ids.include? "#sample/#{hidden_study_sample.id}" + assert assay_output_ids.include? "#other_material/#{assay_1_stream_2_sample.id}" + assert assay_output_ids.include? "#data_file/#{assay_2_stream_2_sample.id}" + refute assay_output_ids.include? "#other_material/#{assay_1_stream_2_hidden_sample.id}" + refute assay_output_ids.include? "#data_file/#{assay_2_stream_2_hidden_sample.id}" end end From 05a748ad0a4326f47801d7da39bbdf999bc6e013 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 21 Nov 2023 12:09:59 +0100 Subject: [PATCH 206/383] remove redundant `compact` --- lib/isa_exporter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 58ac147b75..7c519a5964 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -123,7 +123,7 @@ def convert_study(study) isa_study[:protocols] = protocols isa_study[:processSequence] = convert_process_sequence(study.sample_types.second, study.sops.map(&:id).join("_"), study.id) - assay_streams = study.assays.map { |assay| [assay] if assay&.position&.zero? }.compact + assay_streams = study.assays.map { |assay| [assay] if assay&.position&.zero? } .compact .map do |assay_stream| last_assay = assay_stream.first From 6193cef0a81e4b98c180739e015fe35777d28c2c Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 21 Nov 2023 11:47:13 +0000 Subject: [PATCH 207/383] fix for use of controlled vocabularies without attributes #1659 --- app/helpers/samples_helper.rb | 13 ++++++------- test/functional/data_files_controller_test.rb | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index 81f87cb0e0..e1b3aefb90 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -6,9 +6,8 @@ def sample_form_field_for_attribute(attribute, resource) attribute_form_element(attribute, resource.get_attribute_value(attribute.title), element_name, element_class) end - def controlled_vocab_form_field(attribute, element_name, values, limit = 1) + def controlled_vocab_form_field(sample_controlled_vocab, element_name, values, allow_new = false, limit = 1) - sample_controlled_vocab = attribute.sample_controlled_vocab scv_id = sample_controlled_vocab.id object_struct = Struct.new(:id, :title) existing_objects = Array(values).collect do |value| @@ -33,13 +32,13 @@ def controlled_vocab_form_field(attribute, element_name, values, limit = 1) objects_input(element_name, existing_objects, typeahead: typeahead, limit: limit, - allow_new: attribute.allow_cv_free_text?, + allow_new: allow_new, class: 'form-control') end - def controlled_vocab_list_form_field(attribute, element_name, values) - controlled_vocab_form_field(attribute, element_name, values, nil) + def controlled_vocab_list_form_field(sample_controlled_vocab, element_name, values, allow_new) + controlled_vocab_form_field(sample_controlled_vocab, element_name, values, allow_new, nil) end def linked_extended_metadata_multi_form_field(attribute, value, element_name, element_class) @@ -341,9 +340,9 @@ def attribute_form_element(attribute, value, element_name, element_class, depth= :title, value.try(:[], 'id')) select_tag(element_name, options, include_blank: !attribute.required?, class: "form-control #{element_class}") when Seek::Samples::BaseType::CV - controlled_vocab_form_field attribute, element_name, value + controlled_vocab_form_field attribute.sample_controlled_vocab, element_name, value, attribute.allow_cv_free_text? when Seek::Samples::BaseType::CV_LIST - controlled_vocab_list_form_field attribute, element_name, value + controlled_vocab_list_form_field attribute.sample_controlled_vocab, element_name, value, attribute.allow_cv_free_text? when Seek::Samples::BaseType::SEEK_SAMPLE sample_form_field attribute, element_name, value when Seek::Samples::BaseType::SEEK_SAMPLE_MULTI diff --git a/test/functional/data_files_controller_test.rb b/test/functional/data_files_controller_test.rb index 43ffa57295..ab1ed8c62e 100644 --- a/test/functional/data_files_controller_test.rb +++ b/test/functional/data_files_controller_test.rb @@ -3499,6 +3499,23 @@ def test_show_item_attributed_to_jerm_file assert_equal [good_assay],data_file.assays end + test 'provide metadata with controlled vocabs' do + FactoryBot.create(:data_formats_controlled_vocab) + FactoryBot.create(:data_types_controlled_vocab) + df = FactoryBot.build(:data_file, content_blob:FactoryBot.create(:txt_content_blob)) + refute_nil df.content_blob + assay_to_be_created = FactoryBot.build(:assay,title:'new assay') + session[:processed_datafile]=df + session[:processed_assay]=assay_to_be_created + + get :provide_metadata + + assert_response :success + + assert_select 'input+select#data_file_data_type_annotations' + assert_select 'input+select#data_file_data_format_annotations' + end + test 'create assay should be checked with new assay containing title' do df = FactoryBot.build(:data_file, content_blob:FactoryBot.create(:txt_content_blob)) refute_nil df.content_blob From ad64830a7b72e2d8f812195e1f49574687652c98 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 21 Nov 2023 10:17:01 +0000 Subject: [PATCH 208/383] fix to add deleted_contributor to collections, and expanded the test #1668 --- ...1100719_add_deleted_contributor_to_collection.rb | 5 +++++ db/schema.rb | 3 ++- test/factories/publications.rb | 13 +++++++++++++ test/unit/asset_test.rb | 3 ++- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20231121100719_add_deleted_contributor_to_collection.rb diff --git a/db/migrate/20231121100719_add_deleted_contributor_to_collection.rb b/db/migrate/20231121100719_add_deleted_contributor_to_collection.rb new file mode 100644 index 0000000000..377ccde7cc --- /dev/null +++ b/db/migrate/20231121100719_add_deleted_contributor_to_collection.rb @@ -0,0 +1,5 @@ +class AddDeletedContributorToCollection < ActiveRecord::Migration[6.1] + def change + add_column :collections, :deleted_contributor, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index ebdc5d1588..0fc57ed21c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_11_14_110917) do +ActiveRecord::Schema.define(version: 2023_11_21_100719) do create_table "activity_logs", id: :integer, force: :cascade do |t| t.string "action" @@ -311,6 +311,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "avatar_id" + t.string "deleted_contributor" t.index ["avatar_id"], name: "index_collections_on_avatar_id" t.index ["contributor_id"], name: "index_collections_on_contributor_id" t.index ["policy_id"], name: "index_collections_on_policy_id" diff --git a/test/factories/publications.rb b/test/factories/publications.rb index 2c04389f6b..e357c89879 100644 --- a/test/factories/publications.rb +++ b/test/factories/publications.rb @@ -123,4 +123,17 @@ first_name { 'Author_registered' } last_name { 'LastReg' } end + + # Publication::Version + factory(:publication_version, class: Publication::Version) do + association :publication + projects { publication.projects } + after(:create) do |publication_version| + publication_version.publication.version += 1 + publication_version.publication.save + publication_version.version = publication_version.publication.version + publication_version.title = publication_version.publication.title + publication_version.save + end + end end diff --git a/test/unit/asset_test.rb b/test/unit/asset_test.rb index f024a25b51..e2d812f5f8 100644 --- a/test/unit/asset_test.rb +++ b/test/unit/asset_test.rb @@ -339,7 +339,8 @@ class AssetTest < ActiveSupport::TestCase end test 'has deleted contributor?' do - assets = [:data_file,:sop, :model, :presentation,:document, :event, :data_file_version,:sop_version, :model_version, :presentation_version,:document_version] + assets = [:data_file,:sop, :model, :presentation,:document, :event, :workflow, :publication, :collection, + :data_file_version,:sop_version, :model_version, :presentation_version, :document_version, :workflow_version, :publication_version] assets.each do |asset_type| item = FactoryBot.create(asset_type,deleted_contributor:'Person:99') item.update_column(:contributor_id,nil) From 67f50edb3aa87fa45f091c86e075df33000dd11d Mon Sep 17 00:00:00 2001 From: Xiaoming Hu Date: Tue, 21 Nov 2023 14:25:40 +0100 Subject: [PATCH 209/383] allow users to override URL validation when the URL check returns a 404 or 400 status code. --- .../examine_url/_override.html.erb | 19 ++++++ lib/seek/upload_handling/data_upload.rb | 18 +++-- lib/seek/upload_handling/examine_url.rb | 4 +- .../content_blobs_controller_test.rb | 10 ++- test/functional/data_files_controller_test.rb | 65 +++++++++++++++++++ 5 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 app/views/content_blobs/examine_url/_override.html.erb diff --git a/app/views/content_blobs/examine_url/_override.html.erb b/app/views/content_blobs/examine_url/_override.html.erb new file mode 100644 index 0000000000..76f60ae710 --- /dev/null +++ b/app/views/content_blobs/examine_url/_override.html.erb @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/lib/seek/upload_handling/data_upload.rb b/lib/seek/upload_handling/data_upload.rb index 47275bda91..4d21512bf2 100644 --- a/lib/seek/upload_handling/data_upload.rb +++ b/lib/seek/upload_handling/data_upload.rb @@ -144,13 +144,17 @@ def process_from_url(blob_params) when 'http', 'https' handler = Seek::DownloadHandling::HTTPHandler.new(@data_url) info = handler.info - if info[:code] == 490 - flash.now[:error] = 'The given URL is inaccessible.' - return false - end - unless [200, 401, 403].include?(info[:code]) - flash.now[:error] = "Processing the URL responded with a response code (#{info[:code]}), indicating the URL is inaccessible." - return false + if (info[:code] == 400 || 404) && blob_params[:override_url_check].present? + flash.now[:notice] = 'The given URL is inaccessible but you can override the url validation.' + else + if info[:code] == 490 + flash.now[:error] = 'The given URL is inaccessible.' + return false + end + unless [200, 401, 403].include?(info[:code]) + flash.now[:error] = "Processing the URL responded with a response code (#{info[:code]}), indicating the URL is inaccessible." + return false + end end when 'ftp' handler = Seek::DownloadHandling::FTPHandler.new(@data_url) diff --git a/lib/seek/upload_handling/examine_url.rb b/lib/seek/upload_handling/examine_url.rb index c99505c726..d813f389f9 100644 --- a/lib/seek/upload_handling/examine_url.rb +++ b/lib/seek/upload_handling/examine_url.rb @@ -30,7 +30,7 @@ def examine_url end respond_to do |format| - format.html { render partial: 'content_blobs/examine_url_result', status: @type == 'error' ? 400 : 200 } + format.html { render partial: 'content_blobs/examine_url_result', status: ( @type == 'error'|| @type == 'override') ? 400 : 200 } end end @@ -74,8 +74,10 @@ def handle_bad_http_response(code) when 405 @error_msg = "We can't find out information about this URL - Method not allowed response." when 404 + @type = 'override' @error_msg = 'Nothing can be found at that URL. Please check the address and try again' when 400 + @type = 'override' @error_msg = 'The URL appears to be invalid' when 490 @error_msg = 'That URL is inaccessible. Please check the address and try again' diff --git a/test/functional/content_blobs_controller_test.rb b/test/functional/content_blobs_controller_test.rb index 849f390020..11740ad6d3 100644 --- a/test/functional/content_blobs_controller_test.rb +++ b/test/functional/content_blobs_controller_test.rb @@ -169,7 +169,8 @@ def setup assert_response 400 assert_equal 404, assigns(:info)[:code] assert @response.body.include?('Nothing can be found at that URL') - assert_equal 'error', assigns(:type) + assert @response.body.include?('I understand the risks and want to override URL validation') + assert_equal 'override', assigns(:type) assert assigns(:error_msg) end @@ -192,7 +193,8 @@ def setup assert_response 400 assert_equal 404, assigns(:info)[:code] assert @response.body.include?('Nothing can be found at that URL') - assert_equal 'error', assigns(:type) + assert @response.body.include?('I understand the risks and want to override URL validation') + assert_equal 'override', assigns(:type) assert assigns(:error_msg) end @@ -201,7 +203,8 @@ def setup get :examine_url, xhr: true, params: { data_url: 'this is not a uri' } assert_response 400 assert @response.body.include?('The URL appears to be invalid') - assert_equal 'error', assigns(:type) + assert @response.body.include?('I understand the risks and want to override URL validation') + assert_equal 'override', assigns(:type) assert assigns(:error_msg) end @@ -222,6 +225,7 @@ def setup assert_response 400 assert_equal 490, assigns(:info)[:code] assert @response.body.include?('URL is inaccessible') + refute @response.body.include?('I understand the risks and want to override URL validation') assert_equal 'error', assigns(:type) assert assigns(:error_msg) end diff --git a/test/functional/data_files_controller_test.rb b/test/functional/data_files_controller_test.rb index 0f3f355d8d..b6f7b90b41 100644 --- a/test/functional/data_files_controller_test.rb +++ b/test/functional/data_files_controller_test.rb @@ -2081,6 +2081,70 @@ def test_show_item_attributed_to_jerm_file assert_equal 'http://mockedlocation.com/txt_test.txt', assigns(:data_file).content_blob.url end + + test 'users should be able to override URL validation when the URL examination returns a 404 or 400 status code.' do + mock_http + + params = { data_file: { + title: 'Remote File', + project_ids: [projects(:sysmo_project).id] + }, + content_blobs: [{ + data_url: 'http://mocked404.com', + make_local_copy: '1' + }], + policy_attributes: valid_sharing } + + assert_no_difference('DataFile.count') do + assert_no_difference('ContentBlob.count') do + post :create, params: params + end + end + + params = { data_file: { + title: 'Remote File', + project_ids: [projects(:sysmo_project).id] + }, + content_blobs: [{ + data_url: 'http://mocked404.com', + make_local_copy: '1', + override_url_check: 'yes' + }], + policy_attributes: valid_sharing } + + assert_difference('DataFile.count') do + assert_difference('ContentBlob.count') do + post :create, params: params + end + end + + assert_redirected_to data_file_path(assigns(:data_file)) + assert_equal 'http://mocked404.com', assigns(:data_file).content_blob.url + + + params = { data_file: { + title: 'Remote File', + project_ids: [projects(:sysmo_project).id] + }, + content_blobs: [{ + data_url: 'http://mocked400.com', + make_local_copy: '1', + override_url_check: 'yes' + }], + policy_attributes: valid_sharing } + + assert_difference('DataFile.count') do + assert_difference('ContentBlob.count') do + post :create, params: params + end + end + + assert_redirected_to data_file_path(assigns(:data_file)) + assert_equal 'http://mocked400.com', assigns(:data_file).content_blob.url + + + end + test 'should display null license text' do df = FactoryBot.create :data_file, policy: FactoryBot.create(:public_policy) @@ -3648,6 +3712,7 @@ def mock_http stub_request(:any, 'http://mocked302.com').to_return(status: 302, headers: { location: 'http://redirectlocation.com' }) stub_request(:any, 'http://mocked401.com/file.txt').to_return(status: 401) stub_request(:any, 'http://mocked403.com/file.txt').to_return(status: 403) + stub_request(:any, 'http://mocked400.com').to_return(status: 400) stub_request(:any, 'http://mocked404.com').to_return(status: 404) stub_request(:get, 'http://mockedlocation.com/small.txt').to_return(body: 'bananafish' * 10, status: 200, headers: { content_type: 'text/plain; charset=UTF-8', content_length: 100 }) From 46756a5f49516e93065afee78bfe4ffa16b3cbee Mon Sep 17 00:00:00 2001 From: Xiaoming Hu Date: Tue, 21 Nov 2023 21:29:19 +0100 Subject: [PATCH 210/383] small fix --- app/views/data_files/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/data_files/show.html.erb b/app/views/data_files/show.html.erb index e6c6ed2947..2b2585b194 100644 --- a/app/views/data_files/show.html.erb +++ b/app/views/data_files/show.html.erb @@ -29,7 +29,7 @@ <%= render :partial => "assets/asset_doi", :locals => {:displayed_resource=>@display_data_file} %> - <%#= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @data_file } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @data_file } %> <%= render :partial => 'nels/data_sheet', locals: { displayed_resource: @display_data_file } if @data_file.nels? %>
    From e66050c89da969b540eb9d964105fc6adc7c4411 Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Wed, 22 Nov 2023 10:59:51 +0000 Subject: [PATCH 211/383] Fix unnecessary send and loop --- app/controllers/sample_types_controller.rb | 2 +- app/helpers/multi_step_wizard_helper.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/sample_types_controller.rb b/app/controllers/sample_types_controller.rb index 1424d1ad48..16207ab68e 100644 --- a/app/controllers/sample_types_controller.rb +++ b/app/controllers/sample_types_controller.rb @@ -28,7 +28,7 @@ def show def new @tab = 'manual' - attr = params["sample_type"] ? send("sample_type_params") : {} + attr = params["sample_type"] ? sample_type_params : {} @sample_type = SampleType.new(attr) @sample_type.sample_attributes.build(is_title: true, required: true) # Initial attribute diff --git a/app/helpers/multi_step_wizard_helper.rb b/app/helpers/multi_step_wizard_helper.rb index cd180a4ddd..4f6c9ca563 100644 --- a/app/helpers/multi_step_wizard_helper.rb +++ b/app/helpers/multi_step_wizard_helper.rb @@ -46,8 +46,8 @@ def forward_params(key) if params[key][:publication_ids] html << hidden_field_tag('data_file[publication_ids][]', params[key][:publication_ids]) end - params[key][:project_ids]&.each do |p| - html << hidden_field_tag("#{key}[project_ids][]", p) + if params[key][:project_ids] + html << hidden_field_tag("#{key}[project_ids][]", params[key][:project_ids]) end end html.html_safe From 564ebb6cc46c5ca2a9d4d43bc3160a862c52aa63 Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Wed, 22 Nov 2023 11:00:55 +0000 Subject: [PATCH 212/383] Rollback deletion of script needed for project selection in single page --- app/views/investigations/_form.html.erb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/views/investigations/_form.html.erb b/app/views/investigations/_form.html.erb index 0786224709..7404a104c9 100644 --- a/app/views/investigations/_form.html.erb +++ b/app/views/investigations/_form.html.erb @@ -42,3 +42,12 @@ <%= form_submit_buttons(@investigation) %> + From 614e01f14bc648f007a7c4d0857ffc667434044a Mon Sep 17 00:00:00 2001 From: fherreazcue Date: Wed, 22 Nov 2023 12:29:25 +0000 Subject: [PATCH 213/383] Add project_ids to legacy_ro_crate_params --- app/controllers/concerns/legacy/workflow_support.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/concerns/legacy/workflow_support.rb b/app/controllers/concerns/legacy/workflow_support.rb index edbef66ce8..75ae62025e 100644 --- a/app/controllers/concerns/legacy/workflow_support.rb +++ b/app/controllers/concerns/legacy/workflow_support.rb @@ -11,7 +11,6 @@ module WorkflowSupport def create_ro_crate @crate_builder = Legacy::WorkflowCrateBuilder.new(legacy_ro_crate_params) @workflow.workflow_class = @crate_builder.workflow_class = WorkflowClass.find_by_id(params[:workflow_class_id]) - @workflow.project_ids |= params[:workflow] ? params[:workflow][:project_ids] : params[:project_ids] || [] blob_params = @crate_builder.build @content_blob = ContentBlob.new(blob_params) @@ -32,7 +31,6 @@ def create_content_blob if handle_upload_data && @workflow.content_blob.save @content_blob = @workflow.content_blob @workflow = workflow - @workflow.project_ids |= params[:workflow] ? params[:workflow][:project_ids] : params[:project_ids] || [] if extract_metadata(@content_blob) format.html { render :provide_metadata } else @@ -48,9 +46,11 @@ def create_content_blob private def legacy_ro_crate_params - params.require(:ro_crate).permit({ workflow: [:data, :data_url, :make_local_copy] }, + l_params = params.require(:ro_crate).permit({ workflow: [:data, :data_url, :make_local_copy]}, { abstract_cwl: [:data, :data_url, :make_local_copy] }, { diagram: [:data, :data_url, :make_local_copy] }) + l_params[:workflow][:project_ids] = params.dig(:workflow, :project_ids) || [] + l_params end def legacy_set_workflow From 36e70df651bbba90ba655d4f0c66609acb7a54f6 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 16 Nov 2023 10:17:21 +0000 Subject: [PATCH 214/383] support and handle the allow_cv_free_text param #1648 --- app/controllers/sample_types_controller.rb | 2 +- .../sample_types_controller_test.rb | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/controllers/sample_types_controller.rb b/app/controllers/sample_types_controller.rb index f1cdb6047f..e911c07c86 100644 --- a/app/controllers/sample_types_controller.rb +++ b/app/controllers/sample_types_controller.rb @@ -164,7 +164,7 @@ def sample_type_params sample_attributes_attributes: [:id, :title, :pos, :required, :is_title, :description, :pid, :sample_attribute_type_id, :sample_controlled_vocab_id, :isa_tag_id, - :linked_sample_type_id, + :allow_cv_free_text, :linked_sample_type_id, :unit_id, :_destroy] }, :assay_ids => []) end diff --git a/test/functional/sample_types_controller_test.rb b/test/functional/sample_types_controller_test.rb index d0d681aa58..fead33a78d 100644 --- a/test/functional/sample_types_controller_test.rb +++ b/test/functional/sample_types_controller_test.rb @@ -593,6 +593,7 @@ class SampleTypesControllerTest < ActionController::TestCase '1' => { pos: '2', title: 'cv', required: '1', sample_attribute_type_id: @controlled_vocab_type.id, + allow_cv_free_text: false, sample_controlled_vocab_id: cv.id, destroy: '0' } @@ -606,6 +607,38 @@ class SampleTypesControllerTest < ActionController::TestCase attr = type.sample_attributes.last assert attr.controlled_vocab? assert_equal cv, attr.sample_controlled_vocab + refute attr.allow_cv_free_text + end + + test 'create sample type with a controlled vocab with allow_cv_free_text' do + cv = FactoryBot.create(:apples_sample_controlled_vocab) + assert_difference('ActivityLog.count', 1) do + assert_difference('SampleType.count') do + post :create, params: { sample_type: { title: 'Hello!', + project_ids: @project_ids, + sample_attributes_attributes: { + '0' => { + pos: '1', title: 'a string', required: '1', is_title: '1', + sample_attribute_type_id: @string_type.id, _destroy: '0' + }, + '1' => { + pos: '2', title: 'cv', required: '1', + sample_attribute_type_id: @controlled_vocab_type.id, + allow_cv_free_text: true, + sample_controlled_vocab_id: cv.id, + destroy: '0' + } + } } } + end + end + + refute_nil type = assigns(:sample_type) + assert_redirected_to sample_type_path(type) + assert_equal 2, type.sample_attributes.count + attr = type.sample_attributes.last + assert attr.controlled_vocab? + assert_equal cv, attr.sample_controlled_vocab + assert attr.allow_cv_free_text end test 'only visible sample types are listed' do From a7a526125d387a65de1754b90f7165aa72cafa41 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 16 Nov 2023 11:58:02 +0000 Subject: [PATCH 215/383] UI checkbox with info for setting allow free text or not #1648 --- app/helpers/sample_types_helper.rb | 4 +++ .../_sample_attribute_form.html.erb | 30 ++++++++++++------- config/locales/en.yml | 1 + .../sample_types_controller_test.rb | 4 +-- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/app/helpers/sample_types_helper.rb b/app/helpers/sample_types_helper.rb index 91ada352c7..4fd5c36989 100644 --- a/app/helpers/sample_types_helper.rb +++ b/app/helpers/sample_types_helper.rb @@ -88,6 +88,10 @@ def sample_attribute_pid_help_icon help_icon(t('samples.pid_info_text')) end + def allow_free_text_help_icon + help_icon(t('samples.allow_free_text_info_text')) + end + private def displayed_sample_attribute_types diff --git a/app/views/sample_types/_sample_attribute_form.html.erb b/app/views/sample_types/_sample_attribute_form.html.erb index a641b022c3..42f5fa4211 100644 --- a/app/views/sample_types/_sample_attribute_form.html.erb +++ b/app/views/sample_types/_sample_attribute_form.html.erb @@ -1,14 +1,15 @@ <% sample_attribute ||= nil %> <% index ||= 'replace-me' %> -<% id = sample_attribute ? sample_attribute.id : '' %> -<% title = sample_attribute ? sample_attribute.title : '' %> -<% description = sample_attribute ? sample_attribute.description : '' %> -<% pid = sample_attribute ? sample_attribute.pid : '' %> -<% pos = sample_attribute ? sample_attribute.pos : '' %> -<% required = sample_attribute ? sample_attribute.required : false %> -<% is_title = sample_attribute ? sample_attribute.is_title : false %> -<% attribute_type_id = sample_attribute ? sample_attribute.sample_attribute_type_id : nil %> -<% sample_controlled_vocab_id = sample_attribute ? sample_attribute.sample_controlled_vocab_id : nil %> +<% id = sample_attribute&.id || '' %> +<% title = sample_attribute&.title || '' %> +<% description = sample_attribute&.description || '' %> +<% pid = sample_attribute&.pid || '' %> +<% pos = sample_attribute&.pos || '' %> +<% required = sample_attribute&.required || false %> +<% is_title = sample_attribute&.is_title || false %> +<% attribute_type_id = sample_attribute&.sample_attribute_type_id %> +<% sample_controlled_vocab_id = sample_attribute&.sample_controlled_vocab_id %> +<% allow_cv_free_text = sample_attribute&.allow_cv_free_text || false %> <% linked_sample_type_id = sample_attribute ? sample_attribute.linked_sample_type_id : nil %> <% unit_id = sample_attribute ? sample_attribute.unit_id : nil %> <% template_column_index = sample_attribute ? sample_attribute.template_column_index : nil %> @@ -19,7 +20,7 @@ <% allow_attribute_removal = constraints.allow_attribute_removal?(sample_attribute) %> <% allow_type_change = constraints.allow_type_change?(sample_attribute) %> <% allow_name_change = constraints.allow_name_change?(sample_attribute) %> -<% seek_sample_multi = sample_attribute.try(:seek_sample_multi?) %> +<% seek_sample_multi = sample_attribute&.seek_sample_multi? %> <% link_to_self = sample_attribute && sample_attribute.deferred_link_to_self %> <% hide_seek_sample_multi = seek_sample_multi && displaying_single_page? %> <% isa_tags_options = IsaTag.all.map { |it| [it.title, it.id] } %> @@ -68,6 +69,15 @@ include_blank: true, class: 'form-control controlled-vocab-selection', disabled: !allow_type_change, data: { attr: "cv_id" }%> + +
    + +
    + <% if allow_type_change %> <%= button_link_to('Edit', 'edit', '#', class:'cv-edit-button', disabled: true, target: :_blank) %> <%= create_sample_controlled_vocab_modal_button %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 99fb4d3833..fa7aa2a149 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -66,6 +66,7 @@ en: samples: pid_info_text: 'The identifier assigns a unique and unequivocal meaning to the attribute name.' + allow_free_text_info_text: 'Whether it is restricted to the terms in the Controlled Vocabulary, or if it is possible to use other text and the terms are just suggestions' templates: isa_tag_info_text: 'ISA tags define the relation of each attribute to either the sample or the protocol, following the categories specified by the ISA model.' diff --git a/test/functional/sample_types_controller_test.rb b/test/functional/sample_types_controller_test.rb index fead33a78d..3490543460 100644 --- a/test/functional/sample_types_controller_test.rb +++ b/test/functional/sample_types_controller_test.rb @@ -593,7 +593,7 @@ class SampleTypesControllerTest < ActionController::TestCase '1' => { pos: '2', title: 'cv', required: '1', sample_attribute_type_id: @controlled_vocab_type.id, - allow_cv_free_text: false, + allow_cv_free_text: '0', sample_controlled_vocab_id: cv.id, destroy: '0' } @@ -624,7 +624,7 @@ class SampleTypesControllerTest < ActionController::TestCase '1' => { pos: '2', title: 'cv', required: '1', sample_attribute_type_id: @controlled_vocab_type.id, - allow_cv_free_text: true, + allow_cv_free_text: '1', sample_controlled_vocab_id: cv.id, destroy: '0' } From deb3ff867fe25e680d199c4ba62bc88f0c58aa73 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 16 Nov 2023 15:09:17 +0000 Subject: [PATCH 216/383] update info text #1648 --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index fa7aa2a149..2a9e26e412 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -66,7 +66,7 @@ en: samples: pid_info_text: 'The identifier assigns a unique and unequivocal meaning to the attribute name.' - allow_free_text_info_text: 'Whether it is restricted to the terms in the Controlled Vocabulary, or if it is possible to use other text and the terms are just suggestions' + allow_free_text_info_text: 'If selected it is possible to provide any text for the attribute value and the Controlled Vocabulary terms are just suggestions; otherwise it values are strictly restricted to only the terms available.' templates: isa_tag_info_text: 'ISA tags define the relation of each attribute to either the sample or the protocol, following the categories specified by the ISA model.' From 07a45fe2d920c7e6d8b0d3e9f7e0b12c78f17c0c Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 16 Nov 2023 16:10:31 +0000 Subject: [PATCH 217/383] show a label on the sampel form, or sample type that indicates if free text is allowed #1648 --- app/helpers/sample_types_helper.rb | 1 + app/views/samples/_attribute_fields.html.erb | 11 +++- config/locales/en.yml | 1 + test/functional/samples_controller_test.rb | 51 ++++++++++++++----- test/unit/helpers/sample_types_helper_test.rb | 19 +++++++ 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/app/helpers/sample_types_helper.rb b/app/helpers/sample_types_helper.rb index 4fd5c36989..2778d0a0f6 100644 --- a/app/helpers/sample_types_helper.rb +++ b/app/helpers/sample_types_helper.rb @@ -106,6 +106,7 @@ def attribute_type_link(sample_type_attribute) if sample_type_attribute.controlled_vocab? || sample_type_attribute.seek_cv_list? type += ' - ' + link_to(sample_type_attribute.sample_controlled_vocab.title, sample_type_attribute.sample_controlled_vocab) + type += " (#{t('samples.allow_free_text_label_hint')})" if sample_type_attribute.allow_cv_free_text? end type end diff --git a/app/views/samples/_attribute_fields.html.erb b/app/views/samples/_attribute_fields.html.erb index 7d745019ba..4db4158b6e 100644 --- a/app/views/samples/_attribute_fields.html.erb +++ b/app/views/samples/_attribute_fields.html.erb @@ -1,9 +1,18 @@ <% sample_type.sample_attributes.each do |attribute| %>
    - <%= required_span if attribute.required? %> + + <%= required_span if attribute.required? %> <% if attribute.description %>

    <%= attribute.description %>

    <% end %> + + <%= sample_form_field_for_attribute(attribute, @sample) %> +
    <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 2a9e26e412..cf1c5c73ac 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -67,6 +67,7 @@ en: samples: pid_info_text: 'The identifier assigns a unique and unequivocal meaning to the attribute name.' allow_free_text_info_text: 'If selected it is possible to provide any text for the attribute value and the Controlled Vocabulary terms are just suggestions; otherwise it values are strictly restricted to only the terms available.' + allow_free_text_label_hint: 'free text allowed' templates: isa_tag_info_text: 'ISA tags define the relation of each attribute to either the sample or the protocol, following the categories specified by the ISA model.' diff --git a/test/functional/samples_controller_test.rb b/test/functional/samples_controller_test.rb index 4e3c712b1c..a6dc501c3e 100644 --- a/test/functional/samples_controller_test.rb +++ b/test/functional/samples_controller_test.rb @@ -1434,20 +1434,6 @@ class SamplesControllerTest < ActionController::TestCase end - private - - def populated_patient_sample - person = FactoryBot.create(:person) - sample = Sample.new title: 'My Sample', policy: FactoryBot.create(:public_policy), - project_ids:person.projects.collect(&:id),contributor:person - sample.sample_type = FactoryBot.create(:patient_sample_type) - sample.title = 'My sample' - sample.set_attribute_value('full name', 'Fred Bloggs') - sample.set_attribute_value(:age, 22) - sample.save! - sample - end - test 'unauthorized users should not do batch operations' do sample = FactoryBot.create(:sample) @@ -1466,4 +1452,41 @@ def populated_patient_sample delete :batch_delete, params: { data: [{ id: sample.id }] } assert_equal JSON.parse(response.body)['status'], 'unprocessable_entity' end + + test 'should show label to say controlled vocab allows free text' do + login_as(FactoryBot.create(:person)) + + type = FactoryBot.create(:simple_sample_type) + FactoryBot.create(:apples_controlled_vocab_attribute, is_title: true, title: 'allowed', allow_cv_free_text: true, sample_type: type) + FactoryBot.create(:apples_controlled_vocab_attribute, title: 'not allowed', allow_cv_free_text: false, sample_type: type) + + + get :new, params: { sample_type_id: type.id } + assert_response :success + + assert_select 'label',text: /allowed/ do + assert_select 'span.subtle', text:/#{I18n.t('samples.allow_free_text_label_hint')}/ + end + + assert_select 'label',text: /not allowed/ do + assert_select 'span.subtle', text:/#{I18n.t('samples.allow_free_text_label_hint')}/, count: 0 + end + + end + + private + + def populated_patient_sample + person = FactoryBot.create(:person) + sample = Sample.new title: 'My Sample', policy: FactoryBot.create(:public_policy), + project_ids:person.projects.collect(&:id),contributor:person + sample.sample_type = FactoryBot.create(:patient_sample_type) + sample.title = 'My sample' + sample.set_attribute_value('full name', 'Fred Bloggs') + sample.set_attribute_value(:age, 22) + sample.save! + sample + end + + end diff --git a/test/unit/helpers/sample_types_helper_test.rb b/test/unit/helpers/sample_types_helper_test.rb index 92195b370b..01df733b97 100644 --- a/test/unit/helpers/sample_types_helper_test.rb +++ b/test/unit/helpers/sample_types_helper_test.rb @@ -1,4 +1,23 @@ require 'test_helper' class SampleTypesHelperTest < ActionView::TestCase + + test 'attribute type link should indicate if free text is allowed' do + + st = FactoryBot.create(:simple_sample_type) + allowed_attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st, allow_cv_free_text: true) + not_allowed_attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st, allow_cv_free_text: false) + + assert_match /#{I18n.t('samples.allow_free_text_label_hint')}/, attribute_type_link(allowed_attr) + refute_match /#{I18n.t('samples.allow_free_text_label_hint')}/, attribute_type_link(not_allowed_attr) + + # also for list + allowed_attr = FactoryBot.create(:apples_list_controlled_vocab_attribute, sample_type: st, allow_cv_free_text: true) + not_allowed_attr = FactoryBot.create(:apples_list_controlled_vocab_attribute, sample_type: st, allow_cv_free_text: false) + + assert_match /#{I18n.t('samples.allow_free_text_label_hint')}/, attribute_type_link(allowed_attr) + refute_match /#{I18n.t('samples.allow_free_text_label_hint')}/, attribute_type_link(not_allowed_attr) + + end + end From 6b0d2a59f1cd068ec5b805ad950a6b78d597f4e2 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 17 Nov 2023 14:31:35 +0000 Subject: [PATCH 218/383] localise the allow free text checkbox label #1648 --- app/views/sample_types/_sample_attribute_form.html.erb | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/sample_types/_sample_attribute_form.html.erb b/app/views/sample_types/_sample_attribute_form.html.erb index 42f5fa4211..8aa5493470 100644 --- a/app/views/sample_types/_sample_attribute_form.html.erb +++ b/app/views/sample_types/_sample_attribute_form.html.erb @@ -74,7 +74,7 @@
    diff --git a/config/locales/en.yml b/config/locales/en.yml index cf1c5c73ac..ced43e1a21 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -68,6 +68,7 @@ en: pid_info_text: 'The identifier assigns a unique and unequivocal meaning to the attribute name.' allow_free_text_info_text: 'If selected it is possible to provide any text for the attribute value and the Controlled Vocabulary terms are just suggestions; otherwise it values are strictly restricted to only the terms available.' allow_free_text_label_hint: 'free text allowed' + allow_free_text_checkbox_label: 'Allow free text?' templates: isa_tag_info_text: 'ISA tags define the relation of each attribute to either the sample or the protocol, following the categories specified by the ISA model.' From 81a894c3bd0e0f04d12da57e805bbb086c618616 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 17 Nov 2023 16:15:47 +0000 Subject: [PATCH 219/383] refactor the attribute handlers to take the associated attribute rather than a hash of additional_options #1648 --- app/models/sample.rb | 2 +- app/models/sample_attribute_type.rb | 18 ++++++------- lib/seek/json_metadata/attribute.rb | 12 +++------ .../attribute_type_handler_factory.rb | 4 +-- .../base_attribute_handler.rb | 6 ++--- .../boolean_attribute_type_handler.rb | 4 +-- .../cv_attribute_type_handler.rb | 6 +++-- .../cv_list_attribute_type_handler.rb | 2 +- ...xtended_metadata_attribute_type_handler.rb | 2 +- .../seek_sample_attribute_type_handler.rb | 4 +-- ...eek_sample_multi_attribute_type_handler.rb | 4 +-- .../attribute_type_handler_factory_test.rb | 9 ++++--- .../samples/cv_attribute_type_handler_test.rb | 25 ++++++++++++------- .../cv_list_attribute_type_handler_test.rb | 18 +++++++------ 14 files changed, 62 insertions(+), 54 deletions(-) diff --git a/app/models/sample.rb b/app/models/sample.rb index e951f7e620..73610328e4 100644 --- a/app/models/sample.rb +++ b/app/models/sample.rb @@ -77,7 +77,7 @@ def related_sample_ids def referenced_resources sample_type.sample_attributes.select(&:seek_resource?).map do |sa| value = get_attribute_value(sa) - type = sa.sample_attribute_type.base_type_handler.type + type = sa.sample_attribute_type.base_type_handler(sa).type return [] unless type Array.wrap(value).map { |v| type.find_by_id(v['id']) if v } end.flatten.compact diff --git a/app/models/sample_attribute_type.rb b/app/models/sample_attribute_type.rb index f4cac9e336..e0ce1f5293 100644 --- a/app/models/sample_attribute_type.rb +++ b/app/models/sample_attribute_type.rb @@ -34,8 +34,8 @@ def test_blank?(value) base_type_handler({}).test_blank?(value) end - def validate_value?(value, additional_options = {}) - check_value_against_base_type(value, additional_options) && check_value_against_regular_expression(value) + def validate_value?(value, attribute = nil) + check_value_against_base_type(value, attribute) && check_value_against_regular_expression(value) end def as_json(_options = nil) @@ -65,12 +65,12 @@ def validate_resolution !resolution.present? || (resolution.include? '\\') end - def check_value_against_base_type(value, additional_options) - base_type_handler(additional_options).validate_value?(value) + def check_value_against_base_type(value, attribute) + base_type_handler(attribute).validate_value?(value) end - def pre_process_value(value, additional_options) - base_type_handler(additional_options).convert(value) + def pre_process_value(value, attribute) + base_type_handler(attribute).convert(value) end def controlled_vocab? @@ -82,7 +82,7 @@ def seek_cv_list? end def seek_resource? - base_type_handler.is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeTypeHandler) + base_type_handler(nil).is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeTypeHandler) end def linked_extended_metadata? @@ -109,7 +109,7 @@ def seek_data_file? base_type == Seek::Samples::BaseType::SEEK_DATA_FILE end - def base_type_handler(additional_options = {}) - Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance.for_base_type(base_type, additional_options) + def base_type_handler(attribute) + Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance.for_base_type(base_type, attribute) end end diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index 3795ff4aab..17713dec17 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -28,11 +28,8 @@ def validate_value?(value) return false if required? && test_blank?(value) return true if test_blank?(value) && !required? - sample_attribute_type.validate_value?(value, required: required?, - allow_cv_free_text: allow_cv_free_text, - controlled_vocab: sample_controlled_vocab, - linked_sample_type: linked_sample_type - ) + sample_attribute_type.validate_value?(value, self) + end def accessor_name @@ -47,10 +44,7 @@ def resolve(value) end def pre_process_value(value) - sample_attribute_type.pre_process_value(value, - controlled_vocab: sample_controlled_vocab, - linked_extended_metadata_type: linked_extended_metadata_type, - linked_sample_type: linked_sample_type) + sample_attribute_type.pre_process_value(value, self) end private diff --git a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb b/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb index ed6f4e3426..fcd8efd6cf 100644 --- a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb +++ b/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb @@ -10,8 +10,8 @@ def initialize @handlers = {} end - def for_base_type(base_type, additional_options = {}) - "Seek::Samples::AttributeTypeHandlers::#{base_type}AttributeTypeHandler".constantize.new(additional_options) + def for_base_type(base_type, attribute=nil) + "Seek::Samples::AttributeTypeHandlers::#{base_type}AttributeTypeHandler".constantize.new(attribute) rescue NameError raise UnrecognisedAttributeHandlerType, "unrecognised attribute base type '#{base_type}'" end diff --git a/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb b/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb index 8da4b4b304..64d748e101 100644 --- a/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb @@ -4,8 +4,8 @@ module AttributeTypeHandlers class AttributeHandlerException < RuntimeError; end class BaseAttributeHandler - def initialize(additional_options = {}) - self.additional_options = additional_options + def initialize(attribute=nil) + @attribute = attribute end def convert(value) @@ -30,7 +30,7 @@ def validate_value?(value) private - attr_accessor :additional_options + attr_reader :attribute end end end diff --git a/lib/seek/samples/attribute_type_handlers/boolean_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/boolean_attribute_type_handler.rb index 56a0becb72..c6f7bfdea3 100644 --- a/lib/seek/samples/attribute_type_handlers/boolean_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/boolean_attribute_type_handler.rb @@ -2,8 +2,8 @@ module Seek module Samples module AttributeTypeHandlers class BooleanAttributeTypeHandler < BaseAttributeHandler - def initialize(additional_options) - super(additional_options) + def initialize(attribute) + super(attribute) @conversion_map = { '1' => true, '0' => false, 'true' => true, 'false' => false } end diff --git a/lib/seek/samples/attribute_type_handlers/cv_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/cv_attribute_type_handler.rb index 6476a218ce..96c5f937be 100644 --- a/lib/seek/samples/attribute_type_handlers/cv_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/cv_attribute_type_handler.rb @@ -5,7 +5,7 @@ class CVAttributeTypeHandler < BaseAttributeHandler class MissingControlledVocabularyException < AttributeHandlerException; end def test_value(value) - unless additional_options[:allow_cv_free_text] || controlled_vocab.includes_term?(value) + unless allow_cv_free_text? || controlled_vocab.includes_term?(value) raise "'#{value}' is not included in the controlled vocabulary" end end @@ -18,8 +18,10 @@ def convert(value) private + delegate :allow_cv_free_text?, to: :attribute + def controlled_vocab - vocab = additional_options[:controlled_vocab] + vocab = attribute.sample_controlled_vocab raise MissingControlledVocabularyException unless vocab vocab diff --git a/lib/seek/samples/attribute_type_handlers/cv_list_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/cv_list_attribute_type_handler.rb index 59595160e2..2e4e2a27d6 100644 --- a/lib/seek/samples/attribute_type_handlers/cv_list_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/cv_list_attribute_type_handler.rb @@ -4,7 +4,7 @@ module AttributeTypeHandlers class CVListAttributeTypeHandler < CVAttributeTypeHandler def test_value(array_value) array_value.each do |value| - unless additional_options[:allow_cv_free_text] || controlled_vocab.includes_term?(value) + unless allow_cv_free_text? || controlled_vocab.includes_term?(value) raise "'#{value}' is not included in the controlled vocabulary" end end diff --git a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_type_handler.rb index f63abea256..3443c18a68 100644 --- a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_type_handler.rb @@ -17,7 +17,7 @@ def convert(value) private def linked_extended_metadata_type - linked_extended_metadata_type = additional_options[:linked_extended_metadata_type] + linked_extended_metadata_type = attribute.linked_extended_metadata_type raise MissingLinkedExtendedMetadataTypeException unless linked_extended_metadata_type linked_extended_metadata_type diff --git a/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_type_handler.rb index 2284bd5aa8..89bc83f45f 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_type_handler.rb @@ -9,7 +9,7 @@ def type end def test_value(value) - if additional_options[:required] + if attribute.required? sample = find_resource(value['id']) raise 'Unable to find Sample in database' unless sample raise 'Sample type does not match' unless sample.sample_type == linked_sample_type @@ -27,7 +27,7 @@ def find_resource(value) end def linked_sample_type - sample_type = additional_options[:linked_sample_type] + sample_type = attribute.linked_sample_type raise MissingLinkedSampleTypeException unless sample_type sample_type diff --git a/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_type_handler.rb index bb106f195a..f0f8a69716 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_type_handler.rb @@ -24,7 +24,7 @@ def convert(value) private def test_value_item(value) - if additional_options[:required] + if attribute.required? sample = find_resource(value['id']) raise 'Unable to find Sample in database' unless sample raise 'Sample type does not match' unless sample.sample_type == linked_sample_type @@ -36,7 +36,7 @@ def find_resource(value) end def linked_sample_type - sample_type = additional_options[:linked_sample_type] + sample_type = attribute.linked_sample_type raise MissingLinkedSampleTypeException unless sample_type sample_type diff --git a/test/unit/samples/attribute_type_handler_factory_test.rb b/test/unit/samples/attribute_type_handler_factory_test.rb index ebb0940e7b..02ae4b928d 100644 --- a/test/unit/samples/attribute_type_handler_factory_test.rb +++ b/test/unit/samples/attribute_type_handler_factory_test.rb @@ -14,10 +14,11 @@ def setup end end - test 'passes additional options' do - type = @factory.for_base_type('SeekSample', fish: 'soup') - options = type.send(:additional_options) - assert_equal({ fish: 'soup' }, options) + test 'for_base_type passes attribute' do + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) + type = @factory.for_base_type('SeekSample', attr) + assert_equal attr, type.send(:attribute) end test 'exception for invalid type' do diff --git a/test/unit/samples/cv_attribute_type_handler_test.rb b/test/unit/samples/cv_attribute_type_handler_test.rb index 8fd70bcf45..77f2c682f4 100644 --- a/test/unit/samples/cv_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_attribute_type_handler_test.rb @@ -1,10 +1,12 @@ require 'test_helper' class CVAttributeTypeHandlerTest < ActiveSupport::TestCase + test 'test value' do - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new - vocab = FactoryBot.create(:apples_sample_controlled_vocab) - handler.send('additional_options=', controlled_vocab: vocab) + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) + handler.test_value('Granny Smith') assert_raises(RuntimeError) do handler.test_value('Pear') @@ -12,23 +14,28 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase end test 'validate value' do - vocab = FactoryBot.create(:apples_sample_controlled_vocab) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(controlled_vocab: vocab, allow_cv_free_text: false) + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:apples_controlled_vocab_attribute, allow_cv_free_text: false, sample_type: st) + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) assert handler.validate_value?('Granny Smith') refute handler.validate_value?('Pear') end test 'exception thrown for missing controlled vocab' do - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) + assert_nil attr.sample_controlled_vocab + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) assert_raises(Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler::MissingControlledVocabularyException) do assert handler.validate_value?('Granny Smith') end end test 'bypass validation for controlled vocabs together with allow_cv_free_text' do - ontology_vocab = FactoryBot.create(:ontology_sample_controlled_vocab) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(controlled_vocab: ontology_vocab, allow_cv_free_text: true) - assert handler.validate_value?('Parent') + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:apples_controlled_vocab_attribute, allow_cv_free_text: true, sample_type: st) + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) + assert handler.validate_value?('Granny Smith') assert handler.validate_value?('custom value') end diff --git a/test/unit/samples/cv_list_attribute_type_handler_test.rb b/test/unit/samples/cv_list_attribute_type_handler_test.rb index 2cf1675fae..aa538ef564 100644 --- a/test/unit/samples/cv_list_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_list_attribute_type_handler_test.rb @@ -1,18 +1,19 @@ require 'test_helper' class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase + test 'test value' do - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new - vocab = FactoryBot.create(:apples_sample_controlled_vocab) - handler.send('additional_options=', controlled_vocab: vocab) + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) + handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new(attr) assert handler.test_value(['Granny Smith']) assert handler.test_value(['Granny Smith','Bramley']) - end test 'validate value' do - vocab = FactoryBot.create(:apples_sample_controlled_vocab) - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new(controlled_vocab: vocab) + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) + handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new(attr) assert handler.validate_value?(['Granny Smith','Bramley']) refute handler.validate_value?(['Peter']) refute handler.validate_value?('Granny Smith') @@ -20,7 +21,10 @@ class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase end test 'exception thrown for missing controlled vocab' do - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) + assert_nil attr.sample_controlled_vocab + handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new(attr) assert_raises(Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler::MissingControlledVocabularyException) do assert handler.validate_value?(['Granny Smith']) end From 2368924b6f465c9a30af8f931e83c7fa9063fc3f Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 21 Nov 2023 14:39:23 +0000 Subject: [PATCH 220/383] refactor the attribute handlers out of the attribute type, and into attribute #1648 it was mostly being delegated, so simplifies things and makes the now necessary link to attribute when validating cleaner. --- app/models/sample.rb | 2 +- app/models/sample_attribute_type.rb | 29 +--- lib/seek/json_metadata/attribute.rb | 27 ++- .../base_attribute_handler.rb | 2 +- test/unit/sample_attribute_test.rb | 160 ++++++++++++++++++ test/unit/sample_attribute_type_test.rb | 154 ----------------- test/unit/sample_type_test.rb | 52 +++--- 7 files changed, 213 insertions(+), 213 deletions(-) diff --git a/app/models/sample.rb b/app/models/sample.rb index 73610328e4..d731bcac4c 100644 --- a/app/models/sample.rb +++ b/app/models/sample.rb @@ -77,7 +77,7 @@ def related_sample_ids def referenced_resources sample_type.sample_attributes.select(&:seek_resource?).map do |sa| value = get_attribute_value(sa) - type = sa.sample_attribute_type.base_type_handler(sa).type + type = sa.base_type_handler.type return [] unless type Array.wrap(value).map { |v| type.find_by_id(v['id']) if v } end.flatten.compact diff --git a/app/models/sample_attribute_type.rb b/app/models/sample_attribute_type.rb index e0ce1f5293..b650eb986a 100644 --- a/app/models/sample_attribute_type.rb +++ b/app/models/sample_attribute_type.rb @@ -30,14 +30,6 @@ def default? self == self.class.default end - def test_blank?(value) - base_type_handler({}).test_blank?(value) - end - - def validate_value?(value, attribute = nil) - check_value_against_base_type(value, attribute) && check_value_against_regular_expression(value) - end - def as_json(_options = nil) { title: title, base_type: base_type, regexp: regexp } end @@ -56,23 +48,10 @@ def regular_expression /#{regexp}/m end - def check_value_against_regular_expression(value) - match = regular_expression.match(value.to_s) - match && (match.to_s == value.to_s) - end - def validate_resolution !resolution.present? || (resolution.include? '\\') end - def check_value_against_base_type(value, attribute) - base_type_handler(attribute).validate_value?(value) - end - - def pre_process_value(value, attribute) - base_type_handler(attribute).convert(value) - end - def controlled_vocab? [Seek::Samples::BaseType::CV, Seek::Samples::BaseType::CV_LIST].include?(base_type) end @@ -81,10 +60,6 @@ def seek_cv_list? base_type == Seek::Samples::BaseType::CV_LIST end - def seek_resource? - base_type_handler(nil).is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeTypeHandler) - end - def linked_extended_metadata? base_type == Seek::Samples::BaseType::LINKED_EXTENDED_METADATA end @@ -109,7 +84,5 @@ def seek_data_file? base_type == Seek::Samples::BaseType::SEEK_DATA_FILE end - def base_type_handler(attribute) - Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance.for_base_type(base_type, attribute) - end + end diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index 17713dec17..a0e2b8eae7 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -16,20 +16,23 @@ module Attribute # validates that the attribute type is SeekSample if linked_sample_type is set, and vice-versa validate :linked_sample_type_and_attribute_type_consistency - delegate :controlled_vocab?, :seek_cv_list?, :seek_sample?, :seek_sample_multi?, :seek_strain?, :seek_resource?, :linked_extended_metadata?,:linked_extended_metadata_multi?, to: :sample_attribute_type, allow_nil: true + delegate :controlled_vocab?, :seek_cv_list?, :seek_sample?, :seek_sample_multi?, :seek_strain?, :linked_extended_metadata?,:linked_extended_metadata_multi?, to: :sample_attribute_type, allow_nil: true end # checks whether the value is blank against the attribute type and base type def test_blank?(value) - sample_attribute_type.test_blank?(value) + base_type_handler.test_blank?(value) end def validate_value?(value) return false if required? && test_blank?(value) return true if test_blank?(value) && !required? - sample_attribute_type.validate_value?(value, self) + check_value_against_base_type(value) && check_value_against_regular_expression(value) + end + def pre_process_value(value) + base_type_handler.convert(value) end def accessor_name @@ -43,8 +46,12 @@ def resolve(value) resolution end - def pre_process_value(value) - sample_attribute_type.pre_process_value(value, self) + def seek_resource? + base_type_handler.is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeTypeHandler) + end + + def base_type_handler + Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance.for_base_type(sample_attribute_type.base_type, self) end private @@ -77,6 +84,16 @@ def linked_sample_type_and_attribute_type_consistency errors.add(:seek_sample_multi, 'Linked Sample Type must be set if attribute type is Registered Sample (multiple)') end end + + def check_value_against_regular_expression(value) + match = sample_attribute_type.regular_expression.match(value.to_s) + match && (match.to_s == value.to_s) + end + + def check_value_against_base_type(value) + base_type_handler.validate_value?(value) + end + end end end diff --git a/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb b/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb index 64d748e101..a59fc24ece 100644 --- a/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb @@ -4,7 +4,7 @@ module AttributeTypeHandlers class AttributeHandlerException < RuntimeError; end class BaseAttributeHandler - def initialize(attribute=nil) + def initialize(attribute) @attribute = attribute end diff --git a/test/unit/sample_attribute_test.rb b/test/unit/sample_attribute_test.rb index 9952334860..8951913607 100644 --- a/test/unit/sample_attribute_test.rb +++ b/test/unit/sample_attribute_test.rb @@ -353,6 +353,166 @@ class SampleAttributeTest < ActiveSupport::TestCase assert attribute.ontology_based? end + test 'boolean' do + bool_type = SampleAttributeType.new title: 'bool', base_type: Seek::Samples::BaseType::BOOLEAN + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: bool_type, sample_type: FactoryBot.create(:simple_sample_type)) + assert attribute.validate_value?(true) + assert attribute.validate_value?(false) + refute attribute.validate_value?('fish') + end + + test 'chebi atribute' do + type = SampleAttributeType.new title: 'CHEBI ID', regexp: 'CHEBI:[0-9]+', base_type: Seek::Samples::BaseType::STRING + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: type, sample_type: FactoryBot.create(:simple_sample_type)) + assert attribute.validate_value?('CHEBI:1111') + assert attribute.validate_value?('CHEBI:1121') + refute attribute.validate_value?('fish') + refute attribute.validate_value?('fish:22') + refute attribute.validate_value?('CHEBI:1121a') + refute attribute.validate_value?('chebi:222') + end + + test 'regular expression match' do + # whole string must match + type = SampleAttributeType.new(title: 'first name', base_type: Seek::Samples::BaseType::STRING, regexp: '[A-Z][a-z]+') + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: type, sample_type: FactoryBot.create(:simple_sample_type)) + assert attribute.validate_value?('Fred') + refute attribute.validate_value?(' Fred') + refute attribute.validate_value?('FRed') + refute attribute.validate_value?('Fred2') + refute attribute.validate_value?('Fred ') + end + + test 'uri attribute type' do + type = SampleAttributeType.new title: 'URI', base_type: Seek::Samples::BaseType::STRING, regexp: URI.regexp.to_s + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: type, sample_type: FactoryBot.create(:simple_sample_type)) + assert attribute.validate_value?('zzz:222') + assert attribute.validate_value?('http://ontology.org#term') + refute attribute.validate_value?('fish') + refute attribute.validate_value?('fish;cow') + end + + test 'validate text with newlines' do + type = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::TEXT) + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: type, sample_type: FactoryBot.create(:simple_sample_type)) + assert attribute.validate_value?('fish\\n\\rsoup') + assert attribute.validate_value?('fish\n\rsoup') + assert attribute.validate_value?('fish\r\nsoup') + assert attribute.validate_value?(' fish\n\rsoup ') + str = %(with + a + new + line%) + assert attribute.validate_value?(str) + end + + test 'validate_value' do + type = SampleAttributeType.new(title: 'x-type', base_type: Seek::Samples::BaseType::STRING, regexp: 'xxx') + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: type, sample_type: FactoryBot.create(:simple_sample_type)) + assert attribute.validate_value?('xxx') + refute attribute.validate_value?('fish') + refute attribute.validate_value?(nil) + + type = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::INTEGER) + attribute.sample_attribute_type = type + assert attribute.validate_value?(1) + assert attribute.validate_value?('1') + assert attribute.validate_value?('01') + refute attribute.validate_value?('frog') + refute attribute.validate_value?('1.1') + refute attribute.validate_value?(1.1) + refute attribute.validate_value?(nil) + refute attribute.validate_value?('') + + # contriversial, but after much argument decided to allow these values as integers + assert attribute.validate_value?(1.0) + assert attribute.validate_value?('1.0') + assert attribute.validate_value?(1.00) + assert attribute.validate_value?('1.00') + assert attribute.validate_value?(1.000) + assert attribute.validate_value?('1.000') + assert attribute.validate_value?(2.0) + assert attribute.validate_value?('2.0') + + type = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::STRING, regexp: '.*yyy') + attribute.sample_attribute_type = type + assert attribute.validate_value?('yyy') + assert attribute.validate_value?('happpp - yyy') + refute attribute.validate_value?('') + refute attribute.validate_value?(nil) + refute attribute.validate_value?(1) + refute attribute.validate_value?('xxx') + + type = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::TEXT, regexp: '.*yyy') + attribute.sample_attribute_type = type + assert attribute.validate_value?('yyy') + assert attribute.validate_value?('happpp - yyy') + + refute attribute.validate_value?('') + refute attribute.validate_value?(nil) + refute attribute.validate_value?(1) + refute attribute.validate_value?('xxx') + + type = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::FLOAT) + attribute.sample_attribute_type = type + assert attribute.validate_value?(1.0) + assert attribute.validate_value?(1.2) + assert attribute.validate_value?(0.78) + assert attribute.validate_value?('0.78') + assert attribute.validate_value?(12.70) + assert attribute.validate_value?('12.70') + refute attribute.validate_value?('fish') + refute attribute.validate_value?('2 Feb 2015') + refute attribute.validate_value?(nil) + + assert attribute.validate_value?(1.0) + assert attribute.validate_value?(1) + assert attribute.validate_value?('1.0') + assert attribute.validate_value?('012') + assert attribute.validate_value?('012.3') + assert attribute.validate_value?('12.30') + assert attribute.validate_value?('1') + + type = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::DATE_TIME) + attribute.sample_attribute_type = type + assert attribute.validate_value?('2 Feb 2015') + assert attribute.validate_value?('Thu, 11 Feb 2016 15:39:55 +0000') + assert attribute.validate_value?('2016-02-11T15:40:14+00:00') + assert attribute.validate_value?(DateTime.parse('2 Feb 2015')) + assert attribute.validate_value?(DateTime.now) + refute attribute.validate_value?(1) + refute attribute.validate_value?(1.2) + refute attribute.validate_value?(nil) + refute attribute.validate_value?('30 Feb 2015') + + type = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::DATE) + attribute.sample_attribute_type = type + assert attribute.validate_value?('2 Feb 2015') + assert attribute.validate_value?('Thu, 11 Feb 2016 15:39:55 +0000') + assert attribute.validate_value?('2016-02-11T15:40:14+00:00') + assert attribute.validate_value?(Date.parse('2 Feb 2015')) + assert attribute.validate_value?(Date.today) + refute attribute.validate_value?(1) + refute attribute.validate_value?(1.2) + refute attribute.validate_value?(nil) + refute attribute.validate_value?('30 Feb 2015') + end + + test 'web and email regexp' do + type = SampleAttributeType.new title: 'Email address', base_type: Seek::Samples::BaseType::STRING, regexp: RFC822::EMAIL.to_s + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: type, sample_type: FactoryBot.create(:simple_sample_type)) + assert_equal RFC822::EMAIL.to_s, type.regexp + + assert attribute.validate_value?('fred@email.com') + refute attribute.validate_value?('moonbeam') + + type = SampleAttributeType.new title: 'Web link', base_type: Seek::Samples::BaseType::STRING, regexp: URI.regexp(%w(http https)).to_s + attribute.sample_attribute_type = type + assert attribute.validate_value?('http://google.com') + assert attribute.validate_value?('https://google.com') + refute attribute.validate_value?('moonbeam') + end + private def valid_value?(attribute, value) diff --git a/test/unit/sample_attribute_type_test.rb b/test/unit/sample_attribute_type_test.rb index eee6d509ad..673fcb0de4 100644 --- a/test/unit/sample_attribute_type_test.rb +++ b/test/unit/sample_attribute_type_test.rb @@ -32,91 +32,6 @@ class SampleAttributeTypeTest < ActiveSupport::TestCase assert_equal '.*', type[:regexp] end - test 'validate_value' do - type = SampleAttributeType.new(title: 'x-type', base_type: Seek::Samples::BaseType::STRING, regexp: 'xxx') - assert type.validate_value?('xxx') - refute type.validate_value?('fish') - refute type.validate_value?(nil) - - attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::INTEGER) - assert attribute.validate_value?(1) - assert attribute.validate_value?('1') - assert attribute.validate_value?('01') - refute attribute.validate_value?('frog') - refute attribute.validate_value?('1.1') - refute attribute.validate_value?(1.1) - refute attribute.validate_value?(nil) - refute attribute.validate_value?('') - - # contriversial, but after much argument decided to allow these values as integers - assert attribute.validate_value?(1.0) - assert attribute.validate_value?('1.0') - assert attribute.validate_value?(1.00) - assert attribute.validate_value?('1.00') - assert attribute.validate_value?(1.000) - assert attribute.validate_value?('1.000') - assert attribute.validate_value?(2.0) - assert attribute.validate_value?('2.0') - - attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::STRING, regexp: '.*yyy') - assert attribute.validate_value?('yyy') - assert attribute.validate_value?('happpp - yyy') - refute attribute.validate_value?('') - refute attribute.validate_value?(nil) - refute attribute.validate_value?(1) - refute attribute.validate_value?('xxx') - - attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::TEXT, regexp: '.*yyy') - assert attribute.validate_value?('yyy') - assert attribute.validate_value?('happpp - yyy') - - refute attribute.validate_value?('') - refute attribute.validate_value?(nil) - refute attribute.validate_value?(1) - refute attribute.validate_value?('xxx') - - attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::FLOAT) - assert attribute.validate_value?(1.0) - assert attribute.validate_value?(1.2) - assert attribute.validate_value?(0.78) - assert attribute.validate_value?('0.78') - assert attribute.validate_value?(12.70) - assert attribute.validate_value?('12.70') - refute attribute.validate_value?('fish') - refute attribute.validate_value?('2 Feb 2015') - refute attribute.validate_value?(nil) - - assert attribute.validate_value?(1.0) - assert attribute.validate_value?(1) - assert attribute.validate_value?('1.0') - assert attribute.validate_value?('012') - assert attribute.validate_value?('012.3') - assert attribute.validate_value?('12.30') - assert attribute.validate_value?('1') - - attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::DATE_TIME) - assert attribute.validate_value?('2 Feb 2015') - assert attribute.validate_value?('Thu, 11 Feb 2016 15:39:55 +0000') - assert attribute.validate_value?('2016-02-11T15:40:14+00:00') - assert attribute.validate_value?(DateTime.parse('2 Feb 2015')) - assert attribute.validate_value?(DateTime.now) - refute attribute.validate_value?(1) - refute attribute.validate_value?(1.2) - refute attribute.validate_value?(nil) - refute attribute.validate_value?('30 Feb 2015') - - attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::DATE) - assert attribute.validate_value?('2 Feb 2015') - assert attribute.validate_value?('Thu, 11 Feb 2016 15:39:55 +0000') - assert attribute.validate_value?('2016-02-11T15:40:14+00:00') - assert attribute.validate_value?(Date.parse('2 Feb 2015')) - assert attribute.validate_value?(Date.today) - refute attribute.validate_value?(1) - refute attribute.validate_value?(1.2) - refute attribute.validate_value?(nil) - refute attribute.validate_value?('30 Feb 2015') - end - test 'validate resolution' do attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::STRING, regexp: '.*yyy') assert attribute.validate_resolution @@ -135,80 +50,11 @@ class SampleAttributeTypeTest < ActiveSupport::TestCase end - test 'validate text with newlines' do - attribute = SampleAttributeType.new(title: 'fish', base_type: Seek::Samples::BaseType::TEXT) - - assert attribute.validate_value?('fish\\n\\rsoup') - assert attribute.validate_value?('fish\n\rsoup') - assert attribute.validate_value?('fish\r\nsoup') - assert attribute.validate_value?(' fish\n\rsoup ') - str = %(with - a - new - line%) - assert attribute.validate_value?(str) - end - - test 'regular expression match' do - # whole string must match - attribute = SampleAttributeType.new(title: 'first name', base_type: Seek::Samples::BaseType::STRING, regexp: '[A-Z][a-z]+') - assert attribute.validate_value?('Fred') - refute attribute.validate_value?(' Fred') - refute attribute.validate_value?('FRed') - refute attribute.validate_value?('Fred2') - refute attribute.validate_value?('Fred ') - end - - test 'web and email regexp' do - email_type = SampleAttributeType.new title: 'Email address', base_type: Seek::Samples::BaseType::STRING, regexp: RFC822::EMAIL.to_s - email_type.save! - email_type.reload - assert_equal RFC822::EMAIL.to_s, email_type.regexp - - assert email_type.validate_value?('fred@email.com') - refute email_type.validate_value?('moonbeam') - - web_type = SampleAttributeType.new title: 'Web link', base_type: Seek::Samples::BaseType::STRING, regexp: URI.regexp(%w(http https)).to_s - web_type.save! - web_type.reload - assert web_type.validate_value?('http://google.com') - assert web_type.validate_value?('https://google.com') - refute web_type.validate_value?('moonbeam') - end - - test 'uri attribute type' do - type = SampleAttributeType.new title: 'URI', base_type: Seek::Samples::BaseType::STRING, regexp: URI.regexp.to_s - type.save! - type.reload - assert type.validate_value?('zzz:222') - assert type.validate_value?('http://ontology.org#term') - refute type.validate_value?('fish') - refute type.validate_value?('fish;cow') - end - - test 'boolean' do - bool_type = SampleAttributeType.new title: 'bool', base_type: Seek::Samples::BaseType::BOOLEAN - assert bool_type.valid? - assert bool_type.validate_value?(true) - assert bool_type.validate_value?(false) - refute bool_type.validate_value?('fish') - end - test 'to json' do type = SampleAttributeType.new(title: 'x-type', base_type: Seek::Samples::BaseType::STRING, regexp: 'xxx') assert_equal %({"title":"x-type","base_type":"String","regexp":"xxx"}), type.to_json end - test 'chebi atribute' do - type = SampleAttributeType.new title: 'CHEBI ID', regexp: 'CHEBI:[0-9]+', base_type: Seek::Samples::BaseType::STRING - assert type.validate_value?('CHEBI:1111') - assert type.validate_value?('CHEBI:1121') - refute type.validate_value?('fish') - refute type.validate_value?('fish:22') - refute type.validate_value?('CHEBI:1121a') - refute type.validate_value?('chebi:222') - end - test 'is_controlled_vocab?' do type = FactoryBot.create(:controlled_vocab_attribute_type) assert type.controlled_vocab? diff --git a/test/unit/sample_type_test.rb b/test/unit/sample_type_test.rb index 24c2fec2c5..4fc68f2e78 100644 --- a/test/unit/sample_type_test.rb +++ b/test/unit/sample_type_test.rb @@ -253,37 +253,41 @@ def setup end # thorough tests of a fairly complex factory, as it will be used in a lot of other tests - test 'patient sample type factory test' do + test 'patient sample type factory sanity test' do name_type = FactoryBot.create(:full_name_sample_attribute_type) - assert name_type.validate_value?('George Bush') - refute name_type.validate_value?('george bush') - refute name_type.validate_value?('GEorge Bush') - refute name_type.validate_value?('George BUsh') - refute name_type.validate_value?('G(eorge Bush') - refute name_type.validate_value?('George B2ush') - refute name_type.validate_value?('George') + attribute = FactoryBot.create(:sample_attribute, is_title: true, sample_attribute_type: name_type, sample_type: FactoryBot.create(:simple_sample_type)) + assert attribute.validate_value?('George Bush') + refute attribute.validate_value?('george bush') + refute attribute.validate_value?('GEorge Bush') + refute attribute.validate_value?('George BUsh') + refute attribute.validate_value?('G(eorge Bush') + refute attribute.validate_value?('George B2ush') + refute attribute.validate_value?('George') age_type = FactoryBot.create(:age_sample_attribute_type) - assert age_type.validate_value?(22) - assert age_type.validate_value?('97') - refute age_type.validate_value?(-6) - refute age_type.validate_value?('six') + attribute.sample_attribute_type = age_type + assert attribute.validate_value?(22) + assert attribute.validate_value?('97') + refute attribute.validate_value?(-6) + refute attribute.validate_value?('six') weight_type = FactoryBot.create(:weight_sample_attribute_type) - assert weight_type.validate_value?(22.223) - assert weight_type.validate_value?('97.332') - refute weight_type.validate_value?('97.332.44') - refute weight_type.validate_value?(-6) - refute weight_type.validate_value?(-6.4) - refute weight_type.validate_value?('-6.4') - refute weight_type.validate_value?('six') + attribute.sample_attribute_type = weight_type + assert attribute.validate_value?(22.223) + assert attribute.validate_value?('97.332') + refute attribute.validate_value?('97.332.44') + refute attribute.validate_value?(-6) + refute attribute.validate_value?(-6.4) + refute attribute.validate_value?('-6.4') + refute attribute.validate_value?('six') post_code = FactoryBot.create(:postcode_sample_attribute_type) - assert post_code.validate_value?('M13 9PL') - assert post_code.validate_value?('M12 7PL') - refute post_code.validate_value?('12 PL') - refute post_code.validate_value?('m12 7pl') - refute post_code.validate_value?('bob') + attribute.sample_attribute_type = post_code + assert attribute.validate_value?('M13 9PL') + assert attribute.validate_value?('M12 7PL') + refute attribute.validate_value?('12 PL') + refute attribute.validate_value?('m12 7pl') + refute attribute.validate_value?('bob') type = FactoryBot.create(:patient_sample_type) assert_equal 'Patient data', type.title From 68b2bfb6fd271069079d455c3ade25b4dadbba94 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 21 Nov 2023 15:50:20 +0000 Subject: [PATCH 221/383] made attribute required when calling attribute_type_handler_factory #1648 --- .../attribute_type_handler_factory.rb | 2 +- .../samples/attribute_type_handler_factory_test.rb | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb b/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb index fcd8efd6cf..7723863e80 100644 --- a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb +++ b/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb @@ -10,7 +10,7 @@ def initialize @handlers = {} end - def for_base_type(base_type, attribute=nil) + def for_base_type(base_type, attribute) "Seek::Samples::AttributeTypeHandlers::#{base_type}AttributeTypeHandler".constantize.new(attribute) rescue NameError raise UnrecognisedAttributeHandlerType, "unrecognised attribute base type '#{base_type}'" diff --git a/test/unit/samples/attribute_type_handler_factory_test.rb b/test/unit/samples/attribute_type_handler_factory_test.rb index 02ae4b928d..dec9bfc428 100644 --- a/test/unit/samples/attribute_type_handler_factory_test.rb +++ b/test/unit/samples/attribute_type_handler_factory_test.rb @@ -6,11 +6,13 @@ def setup end test 'handlers to for base type' do + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) # they are repeated twice to check for any caching issues types = Seek::Samples::BaseType::ALL_TYPES + Seek::Samples::BaseType::ALL_TYPES types.each do |type| expected = "Seek::Samples::AttributeTypeHandlers::#{type}AttributeTypeHandler".constantize - assert_kind_of expected, @factory.for_base_type(type), "Expected #{expected.name} for #{type}" + assert_kind_of expected, @factory.for_base_type(type, attr), "Expected #{expected.name} for #{type}" end end @@ -22,13 +24,15 @@ def setup end test 'exception for invalid type' do + st = FactoryBot.create(:simple_sample_type) + attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) e = assert_raise(Seek::Samples::AttributeTypeHandlers::UnrecognisedAttributeHandlerType) do - @factory.for_base_type('fish') + @factory.for_base_type('fish', attr) end assert_equal "unrecognised attribute base type 'fish'", e.message e = assert_raise(Seek::Samples::AttributeTypeHandlers::UnrecognisedAttributeHandlerType) do - @factory.for_base_type('fish') + @factory.for_base_type('fish', attr) end assert_equal "unrecognised attribute base type 'fish'", e.message end From 46552007f0cef9e3096bbd746c0a11daa2ded5b2 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 22 Nov 2023 16:14:35 +0100 Subject: [PATCH 222/383] Add allow_cv_free_text to params --- app/controllers/isa_assays_controller.rb | 1 + app/controllers/isa_studies_controller.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 4e9adc911a..df0643ba43 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -119,6 +119,7 @@ def sample_type_params(params) sample_controlled_vocab_id linked_sample_type_id description pid + allow_cv_free_text unit_id _destroy] }, { assay_ids: [] }] end diff --git a/app/controllers/isa_studies_controller.rb b/app/controllers/isa_studies_controller.rb index 94e25c3f64..a2386a0459 100644 --- a/app/controllers/isa_studies_controller.rb +++ b/app/controllers/isa_studies_controller.rb @@ -120,6 +120,7 @@ def sample_type_params(params, field) sample_controlled_vocab_id linked_sample_type_id description pid + allow_cv_free_text unit_id _destroy] }, { assay_ids: [] }] end From e79aa83a0f4b0ca466fc7ae3b8ec7d46c6c5d519 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 22 Nov 2023 17:15:17 +0100 Subject: [PATCH 223/383] Use authorized_for() --- lib/isa_exporter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 7c519a5964..b984a3c5a0 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -642,7 +642,7 @@ def group_samples_by_input_and_parameter_value(sample_type) def process_sequence_input(inputs, type) input_ids = inputs.map { |input| input[:id] } - authorized_sample_ids = Sample.where(id: input_ids).select { |s| s.can_view?(@current_user) }.map(&:id).compact + authorized_sample_ids = Sample.where(id: input_ids).authorized_for(:view, @current_user).map(&:id) input_ids.map do |input_id| if authorized_sample_ids.include?(input_id) { '@id': "##{type}/#{input_id}" } @@ -696,7 +696,7 @@ def convert_parameter_values(sample_group_hash, isa_parameter_value_attributes) def extract_sample_ids(input_obj_list, type) sample_ids = input_obj_list.map { |io| io[:id] } - authorized_sample_ids = Sample.where(id: sample_ids).select { |sample| sample.can_view?(@current_user) }.map(&:id) + authorized_sample_ids = Sample.where(id: sample_ids).authorized_for(:view, @current_user).map(&:id) sample_ids.map do |s_id| if authorized_sample_ids.include?(s_id) From 888fdf72f5d3daadc654d9c333089f9c678d5dfd Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 21 Nov 2023 15:10:19 +0000 Subject: [PATCH 224/383] Include `project_ids` in params --- app/controllers/workflows_controller.rb | 7 +++---- app/models/workflow_repository_builder.rb | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 4e2d7b3eef..f5d8600699 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -104,9 +104,8 @@ def update # Takes a single RO-Crate zip file def create_from_ro_crate - @crate_extractor = WorkflowCrateExtractor.new(ro_crate_extractor_params) + @crate_extractor = WorkflowCrateExtractor.new(ro_crate_extractor_params.merge(params: workflow_params)) @workflow = @crate_extractor.build - @workflow.project_ids |= params[:workflow] ? params[:workflow][:project_ids] : params[:project_ids] || [] respond_to do |format| if @crate_extractor.valid? @@ -122,7 +121,6 @@ def create_from_files @crate_builder = WorkflowRepositoryBuilder.new(ro_crate_params) @crate_builder.workflow_class = @workflow.workflow_class @workflow = @crate_builder.build - @workflow.project_ids |= params[:workflow] ? params[:workflow][:project_ids] : params[:project_ids] || [] respond_to do |format| if @crate_builder.valid? @@ -372,7 +370,8 @@ def workflow_params def ro_crate_params params.require(:ro_crate).permit({ main_workflow: [:data, :data_url, :make_local_copy] }, { abstract_cwl: [:data, :data_url, :make_local_copy] }, - { diagram: [:data, :data_url, :make_local_copy] }) + { diagram: [:data, :data_url, :make_local_copy] }).merge( + params.fetch(:workflow, {}).permit(project_ids: [])) end def ro_crate_extractor_params diff --git a/app/models/workflow_repository_builder.rb b/app/models/workflow_repository_builder.rb index a355d9c39e..def75543c8 100644 --- a/app/models/workflow_repository_builder.rb +++ b/app/models/workflow_repository_builder.rb @@ -6,14 +6,14 @@ class WorkflowRepositoryBuilder include ActiveModel::Model - attr_accessor :main_workflow, :abstract_cwl, :diagram, :workflow_class + attr_accessor :main_workflow, :abstract_cwl, :diagram, :workflow_class, :project_ids validates :main_workflow, presence: true validate :resolve_remotes validate :workflow_data_present def build - @workflow = Workflow.new(workflow_class: workflow_class, is_git_versioned: true) + @workflow = Workflow.new(workflow_class: workflow_class, is_git_versioned: true, project_ids: project_ids || []) if valid? gv = @workflow.git_version From dadfe1c5c6cef961c09086aabea4f613b2a470c5 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 22 Nov 2023 14:53:02 +0000 Subject: [PATCH 225/383] Handle case where workflow params not provided --- app/controllers/workflows_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index f5d8600699..1929cf7960 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -104,7 +104,8 @@ def update # Takes a single RO-Crate zip file def create_from_ro_crate - @crate_extractor = WorkflowCrateExtractor.new(ro_crate_extractor_params.merge(params: workflow_params)) + @crate_extractor = WorkflowCrateExtractor.new(ro_crate_extractor_params.merge( + params: params.key?(:workflow) ? workflow_params : {})) @workflow = @crate_extractor.build respond_to do |format| From 64c50ed86e5bfe8147c5a5a206d325fa75965fcb Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 23 Nov 2023 10:21:28 +0100 Subject: [PATCH 226/383] Usage of ObjectsInput for CV Lists in the dynamic table --- .../single_page/dynamic_table.js.erb | 125 ++++++++++++------ app/helpers/dynamic_table_helper.rb | 7 +- 2 files changed, 89 insertions(+), 43 deletions(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index f64dab4c4c..6814ec38ad 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -22,10 +22,11 @@ const defaultCols = [{ const objectInputTemp = '' + ''; + 'data-tags-limit="100" multiple="multiple" style="background-color: coral;" data-typeahead-template="_TYPEHEAD_"' + + 'data-typeahead-query-url="_URL_" data-allow-new-items=_ALLOW_FREE_TEXT_>_OPTIONS_'; -const typeaheadUrl = "<%= typeahead_samples_path(linked_sample_type_id: '_LINKED_') %>"; +const typeaheadSamplesUrl = "<%= typeahead_samples_path(linked_sample_type_id: '_LINKED_') %>"; +const typeaheadCVUrl = "<%= typeahead_sample_controlled_vocabs_path(scv_id: '_CVID_') %>"; const handleSelect = (e) => { $j(e).parents("table").DataTable().row(e.closest("tr")).data()[0] = e.is(":checked") @@ -46,49 +47,26 @@ const handleSelect = (e) => { $j.dynamicTable.prototype = { init: function(rows, columns, options = {}) { columns.forEach((c) => { - c["render"] = function(data, type, full, meta) { - if (c.multi_link) { - data = data && Array.isArray(data) ? data : [data]; + c["render"] = function(data_, type, full, meta) { + if(c.multi_link){ + data = data_ && Array.isArray(data_) ? data_ : [data_]; data = data[0]?.id ? data : []; - const existingOptions = data.map((e) => { - isHiddenInput = (e.title == '#HIDDEN') - if (isHiddenInput) { - return `` + return inputObjectsInput(c, data, options); + }else if(c.is_cv_list && data_ !== "#HIDDEN"){ + data = data_ && Array.isArray(data_) ? data_ : [data_]; + data = data.map((e) => { + if (e?.id){ + return e.id } else { - return `` + return e } - }).join(""); - if (options.readonly) { - return data.map((e) => `${e.title}`).join(" "); - } else { - const url = typeaheadUrl.replace("_LINKED_", c.linked_sample_type); - const objectInputName = data.map((e) => e.id).join('-') + '-' + crypto.randomUUID(); - setTimeout(ObjectsInput.init); - - const linkedSamples = retrieveLinkedSamples(url); - const linkedSampleIds = linkedSamples.map((ls) => ls.id); - const unLinkedSamples = data.reduce(function(filtered, sample) { - if(!linkedSampleIds.includes(parseInt(sample.id)) && sample.title != '#HIDDEN'){ - filtered.push(sample); - } - return filtered; - }, []); - const hasUnlinkedSamples = unLinkedSamples.length > 0 ? true : false; - - const extraClass = hasUnlinkedSamples ? 'select2__error' : ''; - const titleText = hasUnlinkedSamples ? `Sample(s) '${unLinkedSamples.map(uls => uls.title).join(', ')}' not recognised as input. Please correct this issue!` : ''; - - return objectInputTemp - .replace(/_NAME_/g, objectInputName) - .replace('_URL_', url) - .replace('_OPTIONS_', existingOptions) - .replace('_EXTRACLASS_', extraClass) - .replace('_TITLE_', titleText); - } - } else if (data === "#HIDDEN") { + }); + + return cvListObjectsInput(c, data, options); + }else if (data_ === "#HIDDEN") { return "Hidden"; } else { - return data; + return data_; } }; c["createdCell"] = function(td, cellData, rowData, row, col) { @@ -553,6 +531,71 @@ function retrieveLinkedSamples(url){ return linkedSamples; } +function inputObjectsInput(column, data, options){ + const existingOptions = data.map((e) => { + isHiddenInput = (e.title == '#HIDDEN'); + if (isHiddenInput) { + return `` + } else { + return `` + } + }).join(""); + if (options.readonly) { + return data.map((e) => `${e.title}`).join(" "); + } else { + const url = typeaheadSamplesUrl.replace("_LINKED_", column.linked_sample_type); + const typeaheadTemplate = 'typeahead/single_pages_samples' + const objectInputName = data.map((e) => e.id).join('-') + '-' + crypto.randomUUID(); + setTimeout(ObjectsInput.init); + + const linkedSamples = retrieveLinkedSamples(url); + const linkedSampleIds = linkedSamples.map((ls) => ls.id); + const unLinkedSamples = data.reduce(function(filtered, sample) { + if(!linkedSampleIds.includes(parseInt(sample.id)) && sample.title != '#HIDDEN'){ + filtered.push(sample); + } + return filtered; + }, []); + const hasUnlinkedSamples = unLinkedSamples.length > 0 ? true : false; + + const extraClass = hasUnlinkedSamples ? 'select2__error' : ''; + const titleText = hasUnlinkedSamples ? `Sample(s) '${unLinkedSamples.map(uls => uls.title).join(', ')}' not recognised as input. Please correct this issue!` : ''; + + return objectInputTemp + .replace(/_NAME_/g, objectInputName) + .replace('_TYPEHEAD_', typeaheadTemplate) + .replace('_URL_', url) + .replace('_OPTIONS_', existingOptions) + .replace('_EXTRACLASS_', extraClass) + .replace('_TITLE_', titleText) + .replace('_ALLOW_FREE_TEXT_', false); + } +} + +function cvListObjectsInput(column, data, options){ + const existingOptions = data.map((e) => ``); + if (options.readonly) { + return data.map((e) => `${e}`).join(" "); + } else { + const url = typeaheadCVUrl.replace("_CVID_", column.cv_id); + const typeaheadTemplate = 'typeahead/controlled_vocab_term'; + const objectInputName = data.map((e) => e.title).join('-') + '-' + crypto.randomUUID(); + const extraClass = ''; + const titleText = ''; + const allowNewItems = column.cv_allows_free_text; + setTimeout(ObjectsInput.init); + + return objectInputTemp + .replace(/_NAME_/g, objectInputName) + .replace('_TYPEHEAD_', typeaheadTemplate) + .replace('_URL_', url) + .replace('_OPTIONS_', existingOptions) + .replace('_EXTRACLASS_', extraClass) + .replace('_TITLE_', titleText) + .replace('_ALLOW_FREE_TEXT_', allowNewItems); + } +} + const handleFailure = (table, res) => { const errors = new Set(); errors.add("The operation can not be performed for one or some samples. The red cells indicate unacceptable values."); diff --git a/app/helpers/dynamic_table_helper.rb b/app/helpers/dynamic_table_helper.rb index 42c09495ae..8369da6315 100644 --- a/app/helpers/dynamic_table_helper.rb +++ b/app/helpers/dynamic_table_helper.rb @@ -66,8 +66,11 @@ def dt_cols(sample_type) attribute = { title: a.title, name: sample_type.id.to_s, required: a.required, description: a.description, is_title: a.is_title } attribute.merge!({ cv_id: a.sample_controlled_vocab_id }) unless a.sample_controlled_vocab_id.blank? - condition = a.sample_attribute_type.base_type == Seek::Samples::BaseType::SEEK_SAMPLE_MULTI - attribute.merge!({ multi_link: true, linked_sample_type: a.linked_sample_type.id }) if condition + is_seek_multi_sample = a.sample_attribute_type.base_type == Seek::Samples::BaseType::SEEK_SAMPLE_MULTI + is_cv_list = a.sample_attribute_type.base_type == Seek::Samples::BaseType::CV_LIST + cv_allows_free_text = a.allow_cv_free_text + attribute.merge!({ multi_link: true, linked_sample_type: a.linked_sample_type.id }) if is_seek_multi_sample + attribute.merge!({is_cv_list: , cv_allows_free_text:}) if is_cv_list attribute end (dt_default_cols(sample_type.id.to_s) + attribs).flatten From 5e556d66154247720609ad8a93d0ebdc0208b992 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 22 Nov 2023 16:35:33 +0000 Subject: [PATCH 227/383] simple seek_dev rake task for profiling a command --- lib/tasks/seek_dev.rake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/tasks/seek_dev.rake b/lib/tasks/seek_dev.rake index 26b24f736e..d3f72423fc 100644 --- a/lib/tasks/seek_dev.rake +++ b/lib/tasks/seek_dev.rake @@ -4,6 +4,7 @@ require 'rubygems' require 'rake' require 'active_record/fixtures' require 'benchmark' +require 'ruby-prof' include SysMODB::SpreadsheetExtractor @@ -36,6 +37,21 @@ namespace :seek_dev do puts output.read end + task(:profile_command, [:command] => :environment) do |_t, args| + unless args[:command].present? + puts "command not found" + puts + puts "Usage: bundle exec rake seek_dev:profile_command['the command']" + exit -1 + end + result = RubyProf.profile do + eval(args[:command]) + end + printer = RubyProf::GraphHtmlPrinter.new(result) + printer.print(STDOUT, {}) + end + + task(:dump_controlled_vocab, [:id] => :environment) do |_t, args| vocab = SampleControlledVocab.find(args.id) json = { title: vocab.title, description: vocab.description, ols_root_term_uri: vocab.ols_root_term_uri, From cb5fad0c918d49ce7197a10c69b59a5fc4e418b5 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 23 Nov 2023 13:50:15 +0100 Subject: [PATCH 228/383] Empty Spreadsheets can be downloaded --- app/controllers/single_pages_controller.rb | 28 ++++++++++--------- .../single_pages/download_samples_excel.axlsx | 28 ++++++++++--------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/app/controllers/single_pages_controller.rb b/app/controllers/single_pages_controller.rb index 9243ec9f61..ef678eec29 100644 --- a/app/controllers/single_pages_controller.rb +++ b/app/controllers/single_pages_controller.rb @@ -70,44 +70,46 @@ def download_samples_excel @project = @study.projects.first @samples = Sample.where(id: sample_ids)&.authorized_for(:view)&.sort_by(&:id) - if @samples.nil? || @samples == [] || sample_type_id.nil? - raise 'Nothing to export to Excel. Please select samples in the table and try downloading the table again.' - end + notice_message = "Contents of #{@assay ? 'Assay [ID: ' + @assay.id.to_s + ', Title: ' + @assay.title : 'Study [ID: ' + @study.id.to_s + ', Title: ' + @study.title}] downloaded:
      " + notice_message << "
    • #{@samples.count < 1 ? 'No' : @samples.count} sample#{@samples.count != 1 ? 's' : ''} visible to you #{@samples.count != 1 ? 'were' : 'was'} included
    • " + raise 'Export aborted! Sample type not included in request!' if sample_type_id.nil? @sample_type = SampleType.find(sample_type_id) + @template = Template.find(@sample_type.template_id) sample_attributes = @sample_type.sample_attributes.map do |sa| + is_cv_list = sa.sample_attribute_type.base_type == Seek::Samples::BaseType::CV_LIST obj = if sa.sample_controlled_vocab_id.nil? { sa_cv_title: sa.title, sa_cv_id: nil } else { sa_cv_title: sa.title, sa_cv_id: sa.sample_controlled_vocab_id, allows_custom_input: sa.allow_cv_free_text } end - obj.merge({ required: sa.required }) + obj.merge({ required: sa.required, is_cv_list: }) end - @sa_cv_terms = [{ 'name' => 'id', 'has_cv' => false, 'data' => nil, 'allows_custom_input' => nil, 'required' => nil }, - { 'name' => 'uuid', 'has_cv' => false, 'data' => nil, 'allows_custom_input' => nil, - 'required' => nil }] + @sa_cv_terms = [{ name: 'id', has_cv: false, data: nil, allows_custom_input: nil, required: nil, is_cv_list: nil }, + { name: 'uuid', has_cv: false, data: nil, allows_custom_input: nil, required: nil, is_cv_list: nil }] sample_attributes.map do |sa| if sa[:sa_cv_id].nil? - @sa_cv_terms.push({ 'name' => sa[:sa_cv_title], 'has_cv' => false, 'data' => nil, - 'allows_custom_input' => nil, 'required' => sa[:required] }) + @sa_cv_terms.push({ name: sa[:sa_cv_title], has_cv: false, data: nil, + allows_custom_input: nil, required: sa[:required], is_cv_list: nil }) else sa_terms = SampleControlledVocabTerm.where(sample_controlled_vocab_id: sa[:sa_cv_id]).map(&:label) - @sa_cv_terms.push({ 'name' => sa[:sa_cv_title], 'has_cv' => true, 'data' => sa_terms, - 'allows_custom_input' => sa[:allows_custom_input], 'required' => sa[:required] }) + @sa_cv_terms.push({ name: sa[:sa_cv_title], has_cv: true, data: sa_terms, + allows_custom_input: sa[:allows_custom_input], required: sa[:required], is_cv_list: sa[:is_cv_list] }) end end - @template = Template.find(@sample_type.template_id) + notice_message << '
    ' + flash[:notice] = notice_message.html_safe render xlsx: 'download_samples_excel', filename: 'samples_table.xlsx', disposition: 'inline' rescue StandardError => e flash[:error] = e.message respond_to do |format| format.html { redirect_to single_page_path(@project.id) } format.json do - render json: { parameters: { sample_ids:, sample_type_id:, study_id: } } + render json: { parameters: { sample_ids:, sample_type_id:, study_id: }, errors: e }, status: :bad_request end end end diff --git a/app/views/single_pages/download_samples_excel.axlsx b/app/views/single_pages/download_samples_excel.axlsx index 800b4a4d09..f488c8d5ef 100644 --- a/app/views/single_pages/download_samples_excel.axlsx +++ b/app/views/single_pages/download_samples_excel.axlsx @@ -76,10 +76,10 @@ end workbook.add_worksheet name: 'cv_ontology', state: :hidden do |sheet| rows = [] @sa_cv_terms.map do |cv| - row = [cv['name']] + row = [cv[:name]] - if cv['has_cv'] - cv['data']&.map do |val| + if cv[:has_cv] + cv[:data]&.map do |val| row.push val end end @@ -96,19 +96,21 @@ end workbook.add_worksheet(name: 'Samples') do |sheet| ## Adding the header cells header_row = @sa_cv_terms.map do |sa_cv_term| - sa_cv_term['required'] ? "#{sa_cv_term['name']} *" : sa_cv_term['name'] + sa_cv_term[:required] ? "#{sa_cv_term[:name]} *" : sa_cv_term[:name] end sheet.add_row header_row ## populating the sheet with the data - sample_data.each do |item| - row = item.collect { |_key, val| val } - sheet.add_row row, style: unlocked + unless sample_data.none? + sample_data.each do |item| + row = item.collect { |_key, val| val } + sheet.add_row row, style: unlocked + end end ## Adding extra empty rows so new samples can be added to the table 1000.times do - sheet.add_row Array::new(sample_data[0].keys.length), style: unlocked + sheet.add_row Array::new(header_row.size), style: unlocked end ## styling @@ -124,24 +126,24 @@ workbook.add_worksheet(name: 'Samples') do |sheet| (0..attribute_size).map do |col_nr| # If the has_cv field is false, it should skip this iteration and not apply the data validation - next if @sa_cv_terms[col_nr]['has_cv'] == false + next if @sa_cv_terms[col_nr][:has_cv] == false # Get sa_cv_terms_length - sa_cv_terms_size = @sa_cv_terms[col_nr]['data'].size + sa_cv_terms_size = @sa_cv_terms[col_nr][:data].size - prompt_text = "Choose a valid option. #{(@sa_cv_terms[col_nr]['required'] ? 'This value is REQUIRED!' : 'This value is optional.')}" + prompt_text = "Choose a valid option. #{(@sa_cv_terms[col_nr][:required] ? 'This value is REQUIRED!' : 'This value is optional.')}" col_ref = Axlsx.cell_r(col_nr, 1).gsub(/\d+/, '') dv_range = "#{col_ref}2:#{col_ref}1000000" sheet.add_data_validation(dv_range, type: :list, formula1: "'cv_ontology'!$#{col_ref}$2:$#{col_ref}$#{sa_cv_terms_size + 1}", - showErrorMessage: !@sa_cv_terms[col_nr]['allows_custom_input'], + showErrorMessage: !@sa_cv_terms[col_nr][:allows_custom_input], errorTitle: 'Input Error!', error: 'Please select one of the available options', errorStyle: :stop, # options here are: 'information', 'stop', 'warning' showInputMessage: true, - promptTitle: @sa_cv_terms[col_nr]['name'], + promptTitle: @sa_cv_terms[col_nr][:name], prompt: prompt_text) end From b2055cee45c0b0de2b4994c560a33aad95818c9a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 23 Nov 2023 15:14:56 +0100 Subject: [PATCH 229/383] upgrade caxlsx gem to use 'escape_formulas' functionality --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 5d20b1f252..42489f52ae 100644 --- a/Gemfile +++ b/Gemfile @@ -154,7 +154,7 @@ gem 'remotipart', '~> 1.4.4' # Allows file upload in AJAX forms gem 'rails-static-router' -gem 'caxlsx', '>= 3.0' # Write content to an xlsx file +gem 'caxlsx', '>= 4.0' # Write content to an xlsx file gem 'caxlsx_rails', '~> 0.6.2' gem 'net-ftp' diff --git a/Gemfile.lock b/Gemfile.lock index 031462837a..36e59003a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -168,7 +168,7 @@ GEM builder (3.2.4) case_transform (0.2) activesupport - caxlsx (3.3.0) + caxlsx (4.0.0) htmlentities (~> 4.3, >= 4.3.4) marcel (~> 1.0) nokogiri (~> 1.10, >= 1.10.4) @@ -979,7 +979,7 @@ DEPENDENCIES bootsnap (>= 1.4.4) bootstrap-sass (>= 3.4.1) bundler (>= 1.8.4) - caxlsx (>= 3.0) + caxlsx (>= 4.0) caxlsx_rails (~> 0.6.2) cff (~> 0.9.0) citeproc-ruby (~> 2.0.0) From b70a80757fd965fb97e1fa72841ce3ecd8d70891 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 23 Nov 2023 15:28:52 +0100 Subject: [PATCH 230/383] Prevent formula injection --- app/views/single_pages/download_samples_excel.axlsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/single_pages/download_samples_excel.axlsx b/app/views/single_pages/download_samples_excel.axlsx index f488c8d5ef..b9da9931f4 100644 --- a/app/views/single_pages/download_samples_excel.axlsx +++ b/app/views/single_pages/download_samples_excel.axlsx @@ -13,8 +13,7 @@ end workbook = xlsx_package.workbook # Prevents formula injections -# TODO: Find out why this doesn't work -# Axlsx.escape_formulas = true +Axlsx.escape_formulas = true # Get Sample data sample_data = @samples.map do |sample| From 5488029553c3d586221943fb1035785e8c88223d Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 23 Nov 2023 19:16:03 +0100 Subject: [PATCH 231/383] Remove dropdown from view --- app/views/single_pages/download_samples_excel.axlsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/views/single_pages/download_samples_excel.axlsx b/app/views/single_pages/download_samples_excel.axlsx index b9da9931f4..0e234fbd51 100644 --- a/app/views/single_pages/download_samples_excel.axlsx +++ b/app/views/single_pages/download_samples_excel.axlsx @@ -125,18 +125,24 @@ workbook.add_worksheet(name: 'Samples') do |sheet| (0..attribute_size).map do |col_nr| # If the has_cv field is false, it should skip this iteration and not apply the data validation - next if @sa_cv_terms[col_nr][:has_cv] == false + next unless @sa_cv_terms[col_nr][:has_cv] # Get sa_cv_terms_length sa_cv_terms_size = @sa_cv_terms[col_nr][:data].size - prompt_text = "Choose a valid option. #{(@sa_cv_terms[col_nr][:required] ? 'This value is REQUIRED!' : 'This value is optional.')}" + if @sa_cv_terms[col_nr][:is_cv_list] + terms = @sa_cv_terms[col_nr][:data] + prompt_text = "Any combination of these terms between are accepted: #{terms.join(', ')}. E.g. [''#{terms.first}'', ''#{terms.last}''].\n\r#{(@sa_cv_terms[col_nr][:required] ? 'This field is REQUIRED!' : 'This field is optional.')}" + else + prompt_text = "Choose a valid option. #{(@sa_cv_terms[col_nr][:required] ? 'This field is REQUIRED!' : 'This field is optional.')}" + end col_ref = Axlsx.cell_r(col_nr, 1).gsub(/\d+/, '') dv_range = "#{col_ref}2:#{col_ref}1000000" sheet.add_data_validation(dv_range, type: :list, formula1: "'cv_ontology'!$#{col_ref}$2:$#{col_ref}$#{sa_cv_terms_size + 1}", + hideDropDown: @sa_cv_terms[col_nr][:is_cv_list], # CV lists should not have dropdown showErrorMessage: !@sa_cv_terms[col_nr][:allows_custom_input], errorTitle: 'Input Error!', error: 'Please select one of the available options', @@ -156,6 +162,3 @@ workbook.add_worksheet(name: 'Samples') do |sheet| sheet.sheet_protection.delete_rows = false sheet.sheet_protection.sort = false end - -# TODO: Extra 'very hidden' sheet with identification attributes -# This is necessary to ensure that the user uploads the same spreadsheet back to the UI and not a altered copy. From a74fdb54c1ff4550307dcd7e69edcd5581c257e8 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 24 Nov 2023 12:09:34 +0100 Subject: [PATCH 232/383] Implement CV List in the upload feature --- .../single_pages/_duplicate_samples_panel.html.erb | 12 ++++++++++++ app/views/single_pages/_new_samples_panel.html.erb | 6 ++++++ .../single_pages/_update_samples_panel.html.erb | 12 ++++++++++++ .../single_pages/sample_upload_content.html.erb | 5 +++++ 4 files changed, 35 insertions(+) diff --git a/app/views/single_pages/_duplicate_samples_panel.html.erb b/app/views/single_pages/_duplicate_samples_panel.html.erb index 3e7696e7a5..a5d6d213c9 100644 --- a/app/views/single_pages/_duplicate_samples_panel.html.erb +++ b/app/views/single_pages/_duplicate_samples_panel.html.erb @@ -23,6 +23,12 @@ '><%= sub_sample['title'] %> <% end %> + <% elsif @cv_list_fields.include?(key) %> + + <% val.each do |cv_term| %> + <%= cv_term %> + <% end %> + <% else %> ' ><%= val %> <% end %> @@ -37,6 +43,12 @@ '><%= sub_sample['title'] %> <% end %> + <% elsif @cv_list_fields.include?(key) %> + + <% val.each do |cv_term| %> + <%= cv_term %> + <% end %> + <% else %> <%= val%> <% end %> diff --git a/app/views/single_pages/_new_samples_panel.html.erb b/app/views/single_pages/_new_samples_panel.html.erb index cf157efaa2..6f57cb0d87 100644 --- a/app/views/single_pages/_new_samples_panel.html.erb +++ b/app/views/single_pages/_new_samples_panel.html.erb @@ -24,6 +24,12 @@ '><%= sub_sample['title'] %> <% end %> + <% elsif @cv_list_fields.include?(key) %> + + <% val.each do |cv_term| %> + <%= cv_term %> + <% end %> + <% else %> <%= val %> <% end %> diff --git a/app/views/single_pages/_update_samples_panel.html.erb b/app/views/single_pages/_update_samples_panel.html.erb index b2de42c349..856169798c 100644 --- a/app/views/single_pages/_update_samples_panel.html.erb +++ b/app/views/single_pages/_update_samples_panel.html.erb @@ -23,6 +23,12 @@ '><%= sub_sample['title'] %> <% end %> + <% elsif @cv_list_fields.include?(key) %> + ' > + <% val.each do |cv_term| %> + <%= cv_term %> + <% end %> + <% else %> ' ><%= val %> <% end %> @@ -38,6 +44,12 @@ '><%= sub_sample['title'] %> <% end %> + <% elsif @cv_list_fields.include?(key) %> + + <% val.each do |cv_term| %> + <%= cv_term %> + <% end %> + <% else %> <%= val %> <% end %> diff --git a/app/views/single_pages/sample_upload_content.html.erb b/app/views/single_pages/sample_upload_content.html.erb index 28728d8dd6..5ab66142f4 100644 --- a/app/views/single_pages/sample_upload_content.html.erb +++ b/app/views/single_pages/sample_upload_content.html.erb @@ -125,9 +125,13 @@ val = cell.textContent; key = cell.id.match(/\[.*\]/)[0].replace('[', "").replace("]", ''); const multiInputfields = $j(cell).find('span[class*="badge"]').toArray(); + const cvListFields = $j(cell).find('span[class*="label"]').toArray(); if (multiInputfields.length > 0 ){ const inputIds = multiInputfields.map(is => is.title.split(" ").pop()).join(','); samplesObj[key] = inputIds; + } else if (cvListFields.length > 0){ + cvTerms = cvListFields.map(cvt => cvt.title) + samplesObj[key] = cvTerms; } else { samplesObj[key] = val; } @@ -183,6 +187,7 @@ }; } } + function closeModalForm(){ $j('#upload-excel-modal').modal('hide'); } From 3ba824a639d4c4338027ff34fc3a4364228dc56c Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 23 Nov 2023 15:57:57 +0000 Subject: [PATCH 233/383] enhancements to speed of doing authlook update_lookup_table_for_all_users using in_batches with a large batch ordered desc gives a significant improvement to either delete_all/import and batch_update --- app/models/auth_lookup.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/models/auth_lookup.rb b/app/models/auth_lookup.rb index 5aa14de1d5..fd611761ab 100644 --- a/app/models/auth_lookup.rb +++ b/app/models/auth_lookup.rb @@ -7,11 +7,12 @@ class AuthLookup < ActiveRecord::Base def self.prepare c = count - if c != (User.count + 1) # 1 entry for each user + anonymous - delete_all unless c.zero? + user_c = User.where.not(person_id: nil).count + if c != (user_c + 1) # 1 entry for each user + anonymous + in_batches(of: 100000, order: :desc) { |r| r.delete_all } unless c.zero? # Only need to specify user ID on insert, since all permission fields are `false` by default. - import [:user_id], ([0] + User.pluck(:id)).map { |i| [i] }, + import [:user_id], ([0] + User.where.not(person_id: nil).pluck(:id)).map { |i| [i] }, validate: false, batch_size: Seek::Util.bulk_insert_batch_size else @@ -29,7 +30,7 @@ def self.batch_update(permission, overwrite = true) updates["can_#{a}"] = permission[index] if overwrite || permission[index] end - update_all(updates) unless updates.empty? + in_batches(of: 100000, order: :desc) { |r| r.update_all(updates) } unless updates.empty? end def as_array From 13c982857852ba89c707974009c9ab48083a718e Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 24 Nov 2023 09:53:17 +0000 Subject: [PATCH 234/383] temporary fix for ruby-prof dependency --- Gemfile | 2 +- lib/tasks/seek_dev.rake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index fd40d4e720..53db06845e 100644 --- a/Gemfile +++ b/Gemfile @@ -178,10 +178,10 @@ group :development do gem 'web-console', '>= 4.1.0' gem 'rack-mini-profiler', '~> 2.0' gem 'listen', '~> 3.3' + gem 'ruby-prof' end group :test do - gem 'ruby-prof' gem 'test-prof' gem 'rails-perftest' gem 'minitest', '~> 5.14' diff --git a/lib/tasks/seek_dev.rake b/lib/tasks/seek_dev.rake index d3f72423fc..4e1202f0f8 100644 --- a/lib/tasks/seek_dev.rake +++ b/lib/tasks/seek_dev.rake @@ -4,7 +4,7 @@ require 'rubygems' require 'rake' require 'active_record/fixtures' require 'benchmark' -require 'ruby-prof' +#require 'ruby-prof' include SysMODB::SpreadsheetExtractor From 76ce64834a8fab9f6afa9c052a1a665545cb1921 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 24 Nov 2023 12:45:39 +0100 Subject: [PATCH 235/383] Adapt controller for CV Lists --- app/controllers/single_pages_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/single_pages_controller.rb b/app/controllers/single_pages_controller.rb index ef678eec29..e2c7a7a964 100644 --- a/app/controllers/single_pages_controller.rb +++ b/app/controllers/single_pages_controller.rb @@ -172,7 +172,11 @@ def upload_samples end @multiple_input_fields = @sample_type.sample_attributes.map do |sa_attr| - sa_attr.title if sa_attr.sample_attribute_type.base_type == 'SeekSampleMulti' + sa_attr.title if sa_attr.sample_attribute_type.base_type == Seek::Samples::BaseType::SEEK_SAMPLE_MULTI + end + + @cv_list_fields = @sample_type.sample_attributes.map do |sa_attr| + sa_attr.title if sa_attr.sample_attribute_type.base_type == Seek::Samples::BaseType::CV_LIST end sample_fields, samples_data = get_spreadsheet_data(samples_sheet) @@ -260,6 +264,9 @@ def generate_excel_samples(samples_data, sample_fields) subsample end obj.merge!(sample_fields[i] => parsed_excel_input_samples) + elsif @cv_list_fields.include?(sample_fields[i]) + parsed_cv_terms = JSON.parse(excel_sample[i]) + obj.merge!(sample_fields[i] => parsed_cv_terms) elsif sample_fields[i] == 'id' if excel_sample[i] == '' obj.merge!(sample_fields[i] => nil) From c4ce7a321e3fdd6289756bd87b7747ccf9d82bf2 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 24 Nov 2023 14:34:39 +0100 Subject: [PATCH 236/383] Add CV term validation to upload feature --- app/controllers/single_pages_controller.rb | 32 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/app/controllers/single_pages_controller.rb b/app/controllers/single_pages_controller.rb index e2c7a7a964..86fa3bbfcc 100644 --- a/app/controllers/single_pages_controller.rb +++ b/app/controllers/single_pages_controller.rb @@ -183,14 +183,24 @@ def upload_samples # Compare Excel header row to Sample Type Sample Attributes # Should raise an error if they don't match - sample_type_attributes = %w[id uuid].concat(@sample_type.sample_attributes.map(&:title)) - has_unmapped_sample_attributes = sample_type_attributes.map { |sa| sample_fields.include?(sa) }.include?(false) + sample_type_attributes = [{ id: nil, title: 'id', is_cv: false, allows_custom_input: false, cv_terms: nil }, + { id: nil, title: 'uuid', is_cv: false, allows_custom_input: false, cv_terms: nil }] + .concat(@sample_type.sample_attributes.includes(sample_controlled_vocab: [:sample_controlled_vocab_terms]).map do |sa| + if sa.controlled_vocab? + cv_terms = sa.sample_controlled_vocab.sample_controlled_vocab_terms.map(&:label) + { id: sa.id, title: sa.title, is_cv: sa.controlled_vocab?, allows_custom_input: sa.allow_cv_free_text?, cv_terms: } + else + { id: sa.id, title: sa.title, is_cv: sa.controlled_vocab?, allows_custom_input: sa.allow_cv_free_text?, cv_terms: nil } + end + end) + + has_unmapped_sample_attributes = sample_type_attributes.map { |sa| sample_fields.include?(sa[:title]) }.include?(false) if has_unmapped_sample_attributes raise "The Sample Attributes from the excel sheet don't match those of the Sample Type in the database. Sample upload was aborted!" end # Construct Samples objects from Excel data - excel_samples = generate_excel_samples(samples_data, sample_fields) + excel_samples = generate_excel_samples(samples_data, sample_fields, sample_type_attributes) existing_excel_samples = excel_samples.map { |sample| sample unless sample['id'].nil? }.compact new_excel_samples = excel_samples.map { |sample| sample if sample['id'].nil? }.compact @@ -250,10 +260,13 @@ def get_spreadsheet_data(samples_sheet) [sample_fields, samples_data] end - def generate_excel_samples(samples_data, sample_fields) + def generate_excel_samples(samples_data, sample_fields, sample_type_attributes) + cv_sample_attributes = sample_type_attributes.select { |sa| sa[:is_cv] && !sa[:allows_custom_input] } samples_data.map do |excel_sample| obj = {} (0..sample_fields.size - 1).map do |i| + validate_cv_terms = cv_sample_attributes.map{ |cv_sa| cv_sa[:title] }.include?(sample_fields[i]) + attr_terms = validate_cv_terms ? cv_sample_attributes.detect { |sa| sa[:title] == sample_fields[i] }[:cv_terms] : [] if @multiple_input_fields.include?(sample_fields[i]) parsed_excel_input_samples = JSON.parse(excel_sample[i].gsub(/"=>/x, '":')).map do |subsample| # Uploader should at least have viewing permissions for the inputs he's using @@ -266,6 +279,12 @@ def generate_excel_samples(samples_data, sample_fields) obj.merge!(sample_fields[i] => parsed_excel_input_samples) elsif @cv_list_fields.include?(sample_fields[i]) parsed_cv_terms = JSON.parse(excel_sample[i]) + # CV validation for CV_LIST attributes + parsed_cv_terms.map do |term| + unless attr_terms.include?(term) || !validate_cv_terms + raise "Invalid Controlled vocabulary term detected '#{term}' in sample ID #{excel_sample[0]}: #{sample_fields[i]} => #{parsed_cv_terms.inspect}" + end + end obj.merge!(sample_fields[i] => parsed_cv_terms) elsif sample_fields[i] == 'id' if excel_sample[i] == '' @@ -274,6 +293,11 @@ def generate_excel_samples(samples_data, sample_fields) obj.merge!(sample_fields[i] => excel_sample[i]&.to_i) end else + if validate_cv_terms + unless attr_terms.include?(excel_sample[i]) + raise "Invalid Controlled vocabulary term detected '#{excel_sample[i]}' in sample ID #{excel_sample[0]}: #{sample_fields[i]} => #{excel_sample[i]}" + end + end obj.merge!(sample_fields[i] => excel_sample[i]) end end From 6ff47d1501d1e0c1d52892802c43dac12b2de7b8 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 24 Nov 2023 14:58:21 +0100 Subject: [PATCH 237/383] Update syntax to ajax 3 and set a timeout before reloading the page --- app/assets/javascripts/single_page/index.js.erb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/single_page/index.js.erb b/app/assets/javascripts/single_page/index.js.erb index 0ccd19f280..ec97c201fb 100644 --- a/app/assets/javascripts/single_page/index.js.erb +++ b/app/assets/javascripts/single_page/index.js.erb @@ -270,14 +270,17 @@ async function exportToExcel(tableName, studyId, assayId, sampleTypeId) { sample_type_id: JSON.stringify(sampleTypeId), study_id: JSON.stringify(studyId), assay_id: JSON.stringify(assayId), - }, - success: function(response) { - downloadUrl = `<%= download_samples_excel_single_pages_path() %>?uuid=${response.uuid}`; - window.location.href = downloadUrl; - }, - error: function(response) { - alert(`Failed to export through excel!\nStatus: ${response.status}\nError: ${JSON.stringify(response.error().statusText)}`); } + }) + .done( function(response){ + downloadUrl = `<%= download_samples_excel_single_pages_path() %>?uuid=${response.uuid}`; + window.location.href = downloadUrl; + }) + .fail( function(response){ + alert(`Failed to export through excel!\nStatus: ${response.status}\nError: ${JSON.stringify(response.error().statusText)}`); + }) + .always( function(response){ + setTimeout(() => {location.reload()}, 2000); }); } From aef82ff408d725be639c7bd8016846e74c3471d4 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 24 Nov 2023 16:10:33 +0100 Subject: [PATCH 238/383] Removing error message of xlsx data validation --- app/views/single_pages/download_samples_excel.axlsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/single_pages/download_samples_excel.axlsx b/app/views/single_pages/download_samples_excel.axlsx index 0e234fbd51..f61f80dd80 100644 --- a/app/views/single_pages/download_samples_excel.axlsx +++ b/app/views/single_pages/download_samples_excel.axlsx @@ -2,7 +2,7 @@ require 'caxlsx' require 'uuid' ##################################### -debug = false +debug = true ##################################### if debug @@ -143,7 +143,7 @@ workbook.add_worksheet(name: 'Samples') do |sheet| type: :list, formula1: "'cv_ontology'!$#{col_ref}$2:$#{col_ref}$#{sa_cv_terms_size + 1}", hideDropDown: @sa_cv_terms[col_nr][:is_cv_list], # CV lists should not have dropdown - showErrorMessage: !@sa_cv_terms[col_nr][:allows_custom_input], + showErrorMessage: !@sa_cv_terms[col_nr][:allows_custom_input] && !@sa_cv_terms[col_nr][:is_cv_list], # CV Lists must have free text input errorTitle: 'Input Error!', error: 'Please select one of the available options', errorStyle: :stop, # options here are: 'information', 'stop', 'warning' From c7fe193f683345610f0f091b3a50d7a7e12ecd97 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 24 Nov 2023 14:12:23 +0000 Subject: [PATCH 239/383] update to the order items are dequeued from the AuthLookupUpdateQueue, priority then id no longer include item_type in the order, so that they are dequeued in the order they were added --- app/models/concerns/resource_queue.rb | 6 ++---- lib/tasks/seek_dev.rake | 1 - .../permissions/auth_lookup_update_queue_test.rb | 12 ++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/models/concerns/resource_queue.rb b/app/models/concerns/resource_queue.rb index 7552b07f1f..adf1b87b19 100644 --- a/app/models/concerns/resource_queue.rb +++ b/app/models/concerns/resource_queue.rb @@ -12,10 +12,8 @@ def queue_enabled? end def prioritized - # including item_type in the order, encourages assets to be processed before users - # (since they are much quicker, in the case of auth lookup processing), due to the happy coincidence that User - # falls last alphabetically. Its not that important if a new authorized type is added after User in the future. - order(:priority, :item_type, :id) + # order by priority first, but then by id to make sure items come of the queue in the order they were added + order(:priority, :id) end def enqueue(*items, priority: DEFAULT_PRIORITY, queue_job: true) diff --git a/lib/tasks/seek_dev.rake b/lib/tasks/seek_dev.rake index 4e1202f0f8..ada5f1807e 100644 --- a/lib/tasks/seek_dev.rake +++ b/lib/tasks/seek_dev.rake @@ -4,7 +4,6 @@ require 'rubygems' require 'rake' require 'active_record/fixtures' require 'benchmark' -#require 'ruby-prof' include SysMODB::SpreadsheetExtractor diff --git a/test/unit/permissions/auth_lookup_update_queue_test.rb b/test/unit/permissions/auth_lookup_update_queue_test.rb index f1dd09a1a3..a1cc616690 100644 --- a/test/unit/permissions/auth_lookup_update_queue_test.rb +++ b/test/unit/permissions/auth_lookup_update_queue_test.rb @@ -296,20 +296,20 @@ def teardown end test 'dequeue' do - df = FactoryBot.create(:data_file) + df1 = FactoryBot.create(:data_file) df2 = FactoryBot.create(:data_file) df3 = FactoryBot.create(:data_file) - user = df.contributor.user + user = df1.contributor.user AuthLookupUpdateQueue.destroy_all - AuthLookupUpdateQueue.enqueue(df3, priority: 1) - AuthLookupUpdateQueue.enqueue(df2, priority: 3) - AuthLookupUpdateQueue.enqueue(user, df, priority: 2) + AuthLookupUpdateQueue.enqueue(df1, priority: 1) + AuthLookupUpdateQueue.enqueue(user, df2, priority: 2) + AuthLookupUpdateQueue.enqueue(df3, priority: 3) assert_difference('AuthLookupUpdateQueue.count', -4) do items = AuthLookupUpdateQueue.dequeue(4) - assert_equal [df3, df, user, df2], items, "should be ordered by priority, type, then ID" + assert_equal [df1, user, df2, df3], items, "should be ordered by priority, then ID" end FactoryBot.create_list(:document, 10) From 38357f1d75bedae2a4dc27e3724f903e7a544fcb Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 27 Nov 2023 16:01:13 +0100 Subject: [PATCH 240/383] Change Registered sample type name --- db/seeds/017_minimal_starter_isa_templates.seeds.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index eb8746beca..e78e477916 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -38,7 +38,7 @@ sample_temp_attributes = [] sample_temp_attributes << TemplateAttribute.new(title: 'Input', description: 'Registered Samples in the platform used as input for this protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample (multiple)'), + sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample List'), is_title: false, required: true) @@ -94,7 +94,7 @@ material_temp_attributes = [] material_temp_attributes << TemplateAttribute.new(title: 'Input', description: 'Registered Samples in the platform used as input for this protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample (multiple)'), + sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample List'), is_title: false, required: true) @@ -150,7 +150,7 @@ data_file_temp_attributes = [] data_file_temp_attributes << TemplateAttribute.new(title: 'Input', description: 'Registered Samples in the platform used as input for this protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample (multiple)'), + sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample List'), is_title: false, required: true) From b65cd6982d1c35780a2e811622ea01573f042278 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 28 Nov 2023 09:50:47 +0100 Subject: [PATCH 241/383] Refactor dynamic table --- .../single_page/dynamic_table.js.erb | 30 ++++++++++++------- app/views/isa_assays/_assay_samples.html.erb | 15 ++++++++++ app/views/isa_assays/_assay_table.html.erb | 6 ++++ .../isa_studies/_source_material.html.erb | 6 ++++ app/views/isa_studies/_study_samples.html.erb | 6 ++++ app/views/isa_studies/_study_table.html.erb | 6 ++++ 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index 6814ec38ad..1084e88075 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -47,11 +47,23 @@ const handleSelect = (e) => { $j.dynamicTable.prototype = { init: function(rows, columns, options = {}) { columns.forEach((c) => { + let linkedSamplesUrl; + let cvUrl; + if (c.linked_sample_type) { + linkedSamplesUrl = typeaheadSamplesUrl.replace("_LINKED_", c.linked_sample_type); + const linkedSamples = retrieveLinkedSamples(linkedSamplesUrl); + c.linkedSampleIds = linkedSamples.map((ls) => ls.id); + } + + if(c.is_cv_list && 'cv_id' in c){ + cvUrl = typeaheadCVUrl.replace("_CVID_", c.cv_id); + } + c["render"] = function(data_, type, full, meta) { if(c.multi_link){ data = data_ && Array.isArray(data_) ? data_ : [data_]; data = data[0]?.id ? data : []; - return inputObjectsInput(c, data, options); + return inputObjectsInput(c, data, options, linkedSamplesUrl); }else if(c.is_cv_list && data_ !== "#HIDDEN"){ data = data_ && Array.isArray(data_) ? data_ : [data_]; data = data.map((e) => { @@ -62,7 +74,7 @@ const handleSelect = (e) => { } }); - return cvListObjectsInput(c, data, options); + return cvListObjectsInput(c, data, options, cvUrl); }else if (data_ === "#HIDDEN") { return "Hidden"; } else { @@ -520,7 +532,6 @@ function retrieveLinkedSamples(url){ contentType: "application/json", success: function (data) { linkedSamples = data.results; - // console.log("data", data.results); }, error: function (xhr, status) { linkedSamples = [] @@ -531,7 +542,7 @@ function retrieveLinkedSamples(url){ return linkedSamples; } -function inputObjectsInput(column, data, options){ +function inputObjectsInput(column, data, options, url){ const existingOptions = data.map((e) => { isHiddenInput = (e.title == '#HIDDEN'); if (isHiddenInput) { @@ -543,15 +554,12 @@ function inputObjectsInput(column, data, options){ if (options.readonly) { return data.map((e) => `${e.title}`).join(" "); } else { - const url = typeaheadSamplesUrl.replace("_LINKED_", column.linked_sample_type); const typeaheadTemplate = 'typeahead/single_pages_samples' const objectInputName = data.map((e) => e.id).join('-') + '-' + crypto.randomUUID(); - setTimeout(ObjectsInput.init); - const linkedSamples = retrieveLinkedSamples(url); - const linkedSampleIds = linkedSamples.map((ls) => ls.id); + const unLinkedSamples = data.reduce(function(filtered, sample) { - if(!linkedSampleIds.includes(parseInt(sample.id)) && sample.title != '#HIDDEN'){ + if(!column.linkedSampleIds.includes(parseInt(sample.id)) && sample.title != '#HIDDEN'){ filtered.push(sample); } return filtered; @@ -560,6 +568,7 @@ function inputObjectsInput(column, data, options){ const extraClass = hasUnlinkedSamples ? 'select2__error' : ''; const titleText = hasUnlinkedSamples ? `Sample(s) '${unLinkedSamples.map(uls => uls.title).join(', ')}' not recognised as input. Please correct this issue!` : ''; + setTimeout(ObjectsInput.init); return objectInputTemp .replace(/_NAME_/g, objectInputName) @@ -572,12 +581,11 @@ function inputObjectsInput(column, data, options){ } } -function cvListObjectsInput(column, data, options){ +function cvListObjectsInput(column, data, options, url){ const existingOptions = data.map((e) => ``); if (options.readonly) { return data.map((e) => `${e}`).join(" "); } else { - const url = typeaheadCVUrl.replace("_CVID_", column.cv_id); const typeaheadTemplate = 'typeahead/controlled_vocab_term'; const objectInputName = data.map((e) => e.title).join('-') + '-' + crypto.randomUUID(); const extraClass = ''; diff --git a/app/views/isa_assays/_assay_samples.html.erb b/app/views/isa_assays/_assay_samples.html.erb index 42f336c3b9..86bfe5e249 100644 --- a/app/views/isa_assays/_assay_samples.html.erb +++ b/app/views/isa_assays/_assay_samples.html.erb @@ -1,3 +1,9 @@ +<% +=begin%> + The dynamic table where users can interact with +<% +=end%> + <% assay ||= nil valid_assay = assay&.sample_type.present? @@ -26,6 +32,15 @@ const elem = $j("#btn_save_assay_sample") const options = { assayId: selectedItem.type =="assay" ? selectedItem.id : null, + url: dynamicTableDataPath, + data: function(d) { + if (initialLoad) { + initialLoad = false; + return; + } + d.sample_type_id = '<%=sample_type.id%>'; + d.rows_pad = "true" + }, callback: () => assayDynamicTable.table.ajax.reload(), enableLoading: () => { $j("#dt-overlay").css("display", "flex") diff --git a/app/views/isa_assays/_assay_table.html.erb b/app/views/isa_assays/_assay_table.html.erb index ee3a55c700..7d87cec1ad 100644 --- a/app/views/isa_assays/_assay_table.html.erb +++ b/app/views/isa_assays/_assay_table.html.erb @@ -1,3 +1,9 @@ +<% +=begin%> + The read-only table in experiments overview +<% +=end%> + <% assay ||= nil %> <% valid_assay = assay&.sample_type.present? %> diff --git a/app/views/isa_studies/_source_material.html.erb b/app/views/isa_studies/_source_material.html.erb index 4d43e14d5e..ffeb169e49 100644 --- a/app/views/isa_studies/_source_material.html.erb +++ b/app/views/isa_studies/_source_material.html.erb @@ -1,3 +1,9 @@ +<% +=begin%> + Dynamic table with the source samples where users interact with sources +<% +=end%> + <% study ||= nil sample_type = study&.sample_types&.first diff --git a/app/views/isa_studies/_study_samples.html.erb b/app/views/isa_studies/_study_samples.html.erb index 57b7ff0b13..14c8f780b7 100644 --- a/app/views/isa_studies/_study_samples.html.erb +++ b/app/views/isa_studies/_study_samples.html.erb @@ -1,3 +1,9 @@ +<% +=begin%> + Dynamic table of the Study samples +<% +=end%> + <% study ||= nil sample_type = study&.sample_types&.second diff --git a/app/views/isa_studies/_study_table.html.erb b/app/views/isa_studies/_study_table.html.erb index 77b236b2bb..4776365c9c 100644 --- a/app/views/isa_studies/_study_table.html.erb +++ b/app/views/isa_studies/_study_table.html.erb @@ -1,3 +1,9 @@ +<% +=begin%> + The study experiment overview table +<% +=end%> + <% study ||= nil %> <% valid_study = study&.sample_types&.second %> From e5c0e8300ea95763b9ec0036a66ca754affc4775 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 27 Nov 2023 14:38:56 +0000 Subject: [PATCH 242/383] fix to the decouple_extracted_samples_policies, samples where being saved with the updated policy_id. Also optimized slightly fix for #1679 --- lib/tasks/seek_upgrades.rake | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index a6dd2ad3a4..3b2dfccb81 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -99,23 +99,27 @@ namespace :seek do end task(decouple_extracted_samples_policies: [:environment]) do - puts '... creating independent policies for extracted samples...' - decoupled = 0 + puts '..... creating independent policies for extracted samples (this can take a while if there are many samples) ...' + affected_samples = [] disable_authorization_checks do - Sample.find_each do |sample| + Sample.includes(:originating_data_file).find_each do |sample| # check if the sample was extracted from a datafile and their policies are linked - if sample.extracted? && sample.policy == sample.originating_data_file&.policy - sample.policy = sample.policy.deep_copy - sample.policy.save - decoupled += 1 + if sample.extracted? && sample.policy_id == sample.originating_data_file&.policy_id + policy = sample.policy.deep_copy + policy.save + sample.update_column(:policy_id, policy.id) + putc('.') + affected_samples << sample end end + #won't have been queued, as the policy has no associated assets yet when saved + AuthLookupUpdateQueue.enqueue(affected_samples) if affected_samples.any? end - puts " ... finished creating independent policies of #{decoupled.to_s} extracted samples" + puts "..... finished creating independent policies of #{affected_samples.count} extracted samples" end task(decouple_extracted_samples_projects: [:environment]) do - puts '... copying project ids for extracted samples...' + puts '..... copying project ids for extracted samples...' decoupled = 0 hash_array = [] disable_authorization_checks do From a536bb4d53f5f6dc7b271cd776f85672ff5baf0f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 28 Nov 2023 13:31:36 +0100 Subject: [PATCH 243/383] Fixing tests --- app/controllers/single_pages_controller.rb | 18 ++++++++++-------- test/factories/sample_types.rb | 14 +++++++------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/controllers/single_pages_controller.rb b/app/controllers/single_pages_controller.rb index 86fa3bbfcc..ae77768257 100644 --- a/app/controllers/single_pages_controller.rb +++ b/app/controllers/single_pages_controller.rb @@ -183,14 +183,14 @@ def upload_samples # Compare Excel header row to Sample Type Sample Attributes # Should raise an error if they don't match - sample_type_attributes = [{ id: nil, title: 'id', is_cv: false, allows_custom_input: false, cv_terms: nil }, - { id: nil, title: 'uuid', is_cv: false, allows_custom_input: false, cv_terms: nil }] + sample_type_attributes = [{ id: nil, title: 'id', is_cv: false, allows_custom_input: false, cv_terms: nil, required: true }, + { id: nil, title: 'uuid', is_cv: false, allows_custom_input: false, cv_terms: nil, required: true }] .concat(@sample_type.sample_attributes.includes(sample_controlled_vocab: [:sample_controlled_vocab_terms]).map do |sa| if sa.controlled_vocab? cv_terms = sa.sample_controlled_vocab.sample_controlled_vocab_terms.map(&:label) - { id: sa.id, title: sa.title, is_cv: sa.controlled_vocab?, allows_custom_input: sa.allow_cv_free_text?, cv_terms: } + { id: sa.id, title: sa.title, is_cv: sa.controlled_vocab?, allows_custom_input: sa.allow_cv_free_text?, cv_terms:, required: sa.required? } else - { id: sa.id, title: sa.title, is_cv: sa.controlled_vocab?, allows_custom_input: sa.allow_cv_free_text?, cv_terms: nil } + { id: sa.id, title: sa.title, is_cv: sa.controlled_vocab?, allows_custom_input: sa.allow_cv_free_text?, cv_terms: nil, required: sa.required?} end end) @@ -265,7 +265,9 @@ def generate_excel_samples(samples_data, sample_fields, sample_type_attributes) samples_data.map do |excel_sample| obj = {} (0..sample_fields.size - 1).map do |i| + current_sample_attribute = sample_type_attributes.detect { |sa| sa[:title] == sample_fields[i] } validate_cv_terms = cv_sample_attributes.map{ |cv_sa| cv_sa[:title] }.include?(sample_fields[i]) + validate_cv_terms &&= current_sample_attribute[:required] && !excel_sample[i].blank? attr_terms = validate_cv_terms ? cv_sample_attributes.detect { |sa| sa[:title] == sample_fields[i] }[:cv_terms] : [] if @multiple_input_fields.include?(sample_fields[i]) parsed_excel_input_samples = JSON.parse(excel_sample[i].gsub(/"=>/x, '":')).map do |subsample| @@ -281,13 +283,13 @@ def generate_excel_samples(samples_data, sample_fields, sample_type_attributes) parsed_cv_terms = JSON.parse(excel_sample[i]) # CV validation for CV_LIST attributes parsed_cv_terms.map do |term| - unless attr_terms.include?(term) || !validate_cv_terms - raise "Invalid Controlled vocabulary term detected '#{term}' in sample ID #{excel_sample[0]}: #{sample_fields[i]} => #{parsed_cv_terms.inspect}" + if !attr_terms.include?(term) && validate_cv_terms + raise "Invalid Controlled vocabulary term detected '#{term}' in sample ID #{excel_sample[0]}: { #{sample_fields[i]}: #{parsed_cv_terms.inspect} }" end end obj.merge!(sample_fields[i] => parsed_cv_terms) elsif sample_fields[i] == 'id' - if excel_sample[i] == '' + if excel_sample[i].blank? obj.merge!(sample_fields[i] => nil) else obj.merge!(sample_fields[i] => excel_sample[i]&.to_i) @@ -295,7 +297,7 @@ def generate_excel_samples(samples_data, sample_fields, sample_type_attributes) else if validate_cv_terms unless attr_terms.include?(excel_sample[i]) - raise "Invalid Controlled vocabulary term detected '#{excel_sample[i]}' in sample ID #{excel_sample[0]}: #{sample_fields[i]} => #{excel_sample[i]}" + raise "Invalid Controlled vocabulary term detected '#{excel_sample[i]}' in sample ID #{excel_sample[0]}: { #{sample_fields[i]}: #{excel_sample[i]} }" end end obj.merge!(sample_fields[i] => excel_sample[i]) diff --git a/test/factories/sample_types.rb b/test/factories/sample_types.rb index 461c617b73..3b7e93c489 100644 --- a/test/factories/sample_types.rb +++ b/test/factories/sample_types.rb @@ -147,7 +147,7 @@ type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Source Name', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, is_title: true, isa_tag_id: FactoryBot.create(:source_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Source Characteristic 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:source_characteristic_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Source Characteristic 2', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), required: true, isa_tag_id: FactoryBot.create(:source_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) - type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Source Characteristic 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title:'Ontology'), isa_tag_id: FactoryBot.create(:source_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Source Characteristic 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title:'Ontology'), isa_tag_id: FactoryBot.create(:source_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type, allow_cv_free_text: true) end end @@ -163,11 +163,11 @@ type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample collection', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:protocol_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample collection parameter value 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample collection parameter value 2', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) - type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample collection parameter value 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample collection parameter value 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type, allow_cv_free_text: true) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Sample Name', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type),is_title: true, required: true, isa_tag_id: FactoryBot.create(:sample_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample characteristic 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:sample_characteristic_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample characteristic 2', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), isa_tag_id: FactoryBot.create(:sample_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) - type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample characteristic 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:sample_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:obi_ontology), pid: 'pid:pid', sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'sample characteristic 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:sample_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:obi_ontology), pid: 'pid:pid', sample_type: type, allow_cv_free_text: true) end end @@ -183,11 +183,11 @@ type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Protocol Assay 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:protocol_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 1 parameter value 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 1 parameter value 2', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) - type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 1 parameter value 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:obi_ontology), pid: 'pid:pid', sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 1 parameter value 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:obi_ontology), pid: 'pid:pid', sample_type: type, allow_cv_free_text: true) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Extract Name', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, is_title: true, isa_tag_id: FactoryBot.create(:other_material_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'other material characteristic 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:other_material_characteristic_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'other material characteristic 2', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), isa_tag_id: FactoryBot.create(:other_material_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) - type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'other material characteristic 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:other_material_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'other material characteristic 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:other_material_characteristic_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type, allow_cv_free_text: true) end end @@ -203,11 +203,11 @@ type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Protocol Assay 2', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:protocol_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 2 parameter value 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 2 parameter value 2', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) - type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 2 parameter value 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:obi_ontology), pid: 'pid:pid', sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Assay 2 parameter value 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:parameter_value_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:obi_ontology), pid: 'pid:pid', sample_type: type, allow_cv_free_text: true) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'File Name', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, is_title: true, isa_tag_id: FactoryBot.create(:data_file_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Data file comment 1', sample_attribute_type: FactoryBot.create(:string_sample_attribute_type), required: true, isa_tag_id: FactoryBot.create(:data_file_comment_isa_tag).id, sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Data file comment 2', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), isa_tag_id: FactoryBot.create(:data_file_comment_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) - type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Data file comment 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:data_file_comment_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'Data file comment 3', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type, title: 'Ontology'), isa_tag_id: FactoryBot.create(:data_file_comment_isa_tag).id, sample_controlled_vocab: FactoryBot.create(:efo_ontology), pid: 'pid:pid', sample_type: type, allow_cv_free_text: true) end end end From fff2a00dc8cea27991ddf4bffbe5a2956c8e9404 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 28 Nov 2023 14:11:48 +0100 Subject: [PATCH 244/383] Update fixture spreadsheets --- .../01_combo_update_sources_spreadsheet.xlsx | Bin 24765 -> 24742 bytes ...ombo_update_assay_samples_spreadsheet.xlsx | Bin 27917 -> 27918 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/fixtures/files/upload_single_page/01_combo_update_sources_spreadsheet.xlsx b/test/fixtures/files/upload_single_page/01_combo_update_sources_spreadsheet.xlsx index 026a2c5946675578ce8535ebd58baeddf84342be..faba1182a3c4ea10275a19708e242fb2d620a0c9 100644 GIT binary patch literal 24742 zcmeI4cT`i`x9_nbDn$@Qnn6)PP(TrZ0HN4`01-q{AVld%2^}JV*a8X&QWOv=YAA|| zN9lnCMS7DigwR8f7CHn-@^*60c*lG0{hjxF#m`-A03%-Z|2=H7d*x#r9wV|*_` zw`>*W;^E=pva>;3aQ)JD0iO}x4sJe*3hejyv0qFolz2mFLE&=CDr6m=mzj7Yef+`G z2g(n7i040g{q6qT(z!iCypKOsM0tf^n6?>xMjprel#W7e9$mTqXmi<7yWp&E^-M36 z;+e)*AytBE7tN4o<-DU|h6@dYx^=i9-zSDU$L5WpEt{xzllEL%I$_@MD*>mN_76uN zT%U{-@^ZTR>IY_P=>ubTR}~Kfki>0>@{q-$xP$&RhDNW^S!$(1d=8;IQGE`<#r6HP zcz#h{C4FPTgoC5gHT&8MqYg6S>whLD?kOoA_4c$~;BG7xdZ6Uw8)PgUtZy`(wpUUa zVHjbVx?VrJ=Mr=~5BB-#=hJ`_nl^KBUHX@95&(!mH^l&?x2r7@>8cRu?)La{tp`bI zciFQ1);{+WHn+r|ZLx@o;WD=R=Hi`+i>Z8mXq$Iw{unXf-1SEh`_2o*)?7c(FAix$ znmk?Lzwz$7oa0UH8wawDsCw>pON%b#WToaq7U~I>x(!V?x*Lbc>)DM89k;2b#7`&X z1t*edq<0U(NrVr&rkUR}hxDyJFS@(mp%!c{H(WK{9mxpaRcZMki4Nk03m>}wv}tLt zteY;ce$=7acP*ee_%k^#4^)DIQge=F0 zsSD!v`r$?K4fX1JYJ8#aTI6oAH?dJKZ|n)&=b4A3J5KDTnW%vwU|}$HZr}% z1kcAXPJDLZUi@~Mv<;@HM_JvkT=HtZV|DDA;-TtT1y9XHUi=TqhwhI~CO>F$uKhX#8#;n4$Bg{ltu*;e^sRUypJRy1L2R&0`B%_SL;B7mWB5 zy~I*#pJZw8@mP44d9VU|c+i}AXTRAUBUkZj#&_JG2=!La8oB)7?tl$z=x9DX@!al` z4vLjJdP;9+xI_xBMKCl_z_~I==BA&J=y>RzCG)`ur*e_zOLj7nkq6ReBuCsUw+p`I zgMamtjFi&jdp>o>QTRkx>ihs9@nW#hVzZ!FSDJW%?vDNUg)gpGIe$nqIRZMD7=24L z(_>ljz4A_zT~3Q=o%5GcdG=jboAe#g?D3Fv3>ba*_4~7J;@{9NXF_VyMY=7ktd^~&pOn(4bJ~#)4E}i5tUKv-lM&WApy`Kp3eoyeW%jCN9ebNY2 zBhV{Wxar=pVHfArsmi1nvyTTOW9}jH_UoRzFs%@{y8qC^_2U_cwy68k`VwvR83J9Z zXzae;WT@zy;!O^ZOaujW$KS~EMf=6Y{M4#bwl4o1nkIWw$#F3*{S0!;_cN!L9*kRGYL&~=e5KW3dUnlswuDq1i4&p{=GT(zVs~61L zZ&GZojcDw?sz`d9&}dpWx{mM}{@{Q=<>SHi;v!GX+*ki7W^3hU7a}a4<~&n{*BE(7xz=|*;m#A` zZO$actI~IJw(ZA|SiVxot#k3|TQYW6XYtP~eLbFg^Fnc_bnpG0%R5>VyVCEkNY@x{ z6U`Z|nM${rag>iuJCBN1+L3JaeCk57fqyX1Lh;1|hOk0qKhgJO|I3t?a}mD#MPUgS zr>?uM*^yk|UtDQ#@lDL%Cp~!!<{Xh$*G4;@8iWqX4w5U_qy@`z&VC2mKkhrPN$Nb` zws=v>7j-U^m6=cISrzmSmKeKr+x@}V?YE#uC8^{2`wrxL49x`l<@=#@byvT{CAR_ zY04~&ktDUCkdloNp_jXAC?OrOIK7js}Y+zhT3Cu==(my_w+nj8&V2)E=l`q zy}|LKkvHStG;^~hE!1a^Dvmb{7w)c)2|OOL%w?_PC9n6WM>NVqSZqdP5cfdtIALb% zYV0AolaqU$A17Q3KG8t9JrJ-vyRb}l;cdigrGrv0J@C?=U_V8?h{c}ZuM#WK1Ml5} z{q3s?eFiMd4!f%DS+{sH5~{>&dZbP+u(?p#MEmY;rR!6VY5Rq9w%mKCd?WDwlwIsz)f=PydO77?ankDZ;ck_x7p zAGhAF>wEu1a*kJ!yQ1HSK%3wXSFy~yq_l~?hQ=D5T=^q9Sma^T?)DU4Zr$8|aXzu9 z>BrJOK9kVjE~&rH+n6o=w7s_ueP1>4E9z{P*nK$%PdQ5VmclfTM<+T% zjHR%zF3r4`<~2?JG2S}Tx_P^9HAv#OkJXr#t!D?aA+lD~w$I0nEsL({`R<{=ENtN& z1nH|Z+(Rf$EG;OlU$?z`a9`d-KXKd9Z&%i~ewIP^(^510HIx~Bf{cMXA;DiYZuDp` z^DIPPNDr{A_>!a|Q3~FCKTdjP&uWEo8;AhjT#*)8KGrL(1Qxr?bm6OZ>hxS6=ZS~!ikyTR^O#%kKNsds<6R+V^Ffa;O&_U_Sy^Fyo>N{HYfAnLd)d!MBY zkFaY4v;9QTrOkp$=JMe~)zD|?`iHklKWynFP~`1CS}3PZAo$EQ&W8Hg9DKUuQ|LhW zx3WiFgX7pRJ?OH!B+S=@Po4)Y<>su97G4&k=m&f*8=ElH2^DD-Vq&0>F zzJ4O}$nSfLM!X2fadeR!9h)N{FhI`m*+p1Sr9NZ?M|a(5z>-f>OYWlZyk%OE7xL^w z1Wa9r5}O0K3%>3w^H2Hc79xIuwS}FGOkrke7lCAi;Qza1B=&nU`h9ot>o;f4PNOWg z`4+I#2%`n>e7!bwSlljBtt?(Q*J_!o%5>=aw==OzSxD{Z$0x96{<0dPwM)xuI=4D$ z^EjKBz{oE;SK`x^ihex4sWej>R8(@`4}a@`*QeODkDxs{>oOlVDaKCcJ5s$0tiL@t z*uN0tC|``zm6=%b*MTQc-e(@!(j%SOm3R1cTvYt{P(c>=~7Paf!srDWU;Qh!Qc$t@_4)yfo zr2bPm7eG@sYn#{}@}zwH*#kWPr@!4*2>R`ddY6Jt?9vbpteB8H-mSNmrnHPP;0!r|^sa5)UREdh)NHrj5$Z5J zTC1loe8A0fb9cb^npBU2eU6?|JSlUyt@5;2IruSf%!f?J?RxvJo2KF4$-2gLTyC?+ zXF6woB7bRrWK}0iQ_nrTl(WU|jQ{uW(Qo@mAKp!bLT(ev~2j*gHKn~!4$Mtl0a-7_j%k)e*IRoAW!*KX4_coEgD zvyaNn^UP}8uzpbZ;(T+WT(zcQM$7IteNSCGl}vJ#@>@zB;ti%*AsR_q5ogb?(<7gc zFE(*;iEjV9_!k6b*B)ti$s6hEqiBcpc3@XwtQj2kRY^E_+2VLG2(G%{vWq)z;?85}IPxH@Wgt5%_>)aYjSIy2TR=~|@Ohhw?CrHO|3y*9fIJdV$>c5)tmk-6WBu&H*kTs~gL;%Q6+ z%DMBJGPAPt&aiArwUC5-qFvD$UO(DHlV{s#Cl)*7AM1WO884-)9#MN|;B)8Qn5FUi zp~aUB2cyJ37;1d70KFT-t}#MB!o=Rt(gq5<=SxLZgx}ii-5#HnbwO6OTtfJ+YPx%I z6w3HO+P=&2)ut~Vv?v{}JtWpbI%!-*JH}ijoN*hj8&L`3hA5PKOXtn2y48YqL_;$> zt%x%c=0z`5V$p79(# z?F@QtbyH&H!0BD*ZzZ??eBX6Evz7L7DyVj_aZdOCqawL_v5Ng)cy<@#H@9Gmz~3&u z{5sbbe3NtlK4jW@&p-Tp`quu|&I_5AZ|+{5$06kBhF-+JsI=cft^o;vsFH!E4dLfa zjnB05+AWv8!{1IN^ZEV^LktKu*VRr)J*|1 zXtPsWR{^gi1HSv$e-zpCtKaPWe0-7a9M?7dwi5_(>>xRdr<-KHJ*G7;LQTVb`AJ{F zCLd&taYS&C^u6h*Fc8F~O^b2%-R^F|Tg8g<$GA@L@ynb*GBVT*zSIufIPf5y!hIa? z-(@Ym?P2Yvh~rYO>RH0KGJZ4-@Fl3*DM+73nx%S~osMeynlw4IDGqGjVcLz|LqCL8 zr_YY7FrfVQ{j`-nZG0PcDgc>VozS)E0C_&Rv_BFs^q4nA=9 zb5&l^fK@2%?pu>Tlq>5`+H^2JZ%eeGoT;PzB-B<++a906SpKoYeD4FD4xXd(gf*Ma zpRtXFpG?l&Nck>zagXMO9kQ9lmmH!FBozMK_PqH*x3^RIk(U<55?A_?p?VTII%Z+{ zoA%0K3u0GiT%fkHRxLs`zM2pjBYAmRsMJ=tv7?#w{2LN^tHCbGm^}P2%ys0BIXu-?Pv4%6r48 z1^3Q=O7HA96&0(wY@|0+jw*>SP(gM0FTPvcxvU)&_FQ4-mpiTn&oe1NVT6rNJt9dESXIq>C(g+hwvsi+t2(?DD&NP$yu|kS>Jq=u@rh zCmfONnc73$CGP@6pEdX2&D6ix@0?;!*AyA<^MdM?H9OA`=9Pc?`f&}O7I5CI&chFB zM)_t_Z}4`BcVF0{I;k&QPlVUnXk2K)lU;dJJ)hQY$g8Krqk2MjEqE8J@OdyGv;6R_{X#)>_ z3z2~81kBp_+|*Q<(^{?wrgOfmsCZ>Iuw5XR8kexLh|fi%39U2eTt#A8F`+C3wcK6U zmD^7|o$9Skif*E>lC-JVGC~oZyo_WqSH2;m!`7xK9TYMRWHa2BO9%{{Ly$F5D3o77 z5j|l-q6av^_cBPNu-tye=&D8a%wT;pVRfi`t$)2kL{TBSaE9rQ z@%9O%Q`*v=Hkb8kvpz42tSr}8L>H#5uh&lHOn?|GLPTOo5o0D&U=~BRCahFak}H1e zBcoXm=IZLjGQw)saG6?25rpxTr7nT&3G?)!lXHe6)++}ooz1n$ckmRuR>Vy(`QU=s{UMr9=}mxw7kK_lK~ zF=j{;6aD0@HEM7O3!?R6h{;bZD`NGbSaPY5Vzo{XKh5T`0sVC8S{clPH9u%v%o?gz zonpSxX3)b_t3i-6)yr#)GPS-iRuS538MuqJ&kFe+-P*uK@pG`SwV)7m_gwdE(3(JX z!0=fVhLzh-9z#fks98&bSTnaF?$uOG0Kq9*fz=^WoqT(3W|%QmRuUK##NsAkT9HIz z89s!;^eYRBuz?c-y#jrfJ_&T8gJ%)d7N~V9B{>Y!*T-NoPm~b5X{nAOS3!^um~Q#H zHTk+E`2u?PJn`$1i8W}aPl8)oKJXcER)J3fn%O;$p+lFJ6DFw&*R_KnthodUnxc9W z+zdimRK(QISwnhB!?5OHH>i4u6$EWXTnj}lcQsa$D=iSc)~uD0(ZLGl^c;;)2(g)E zCYz6Zrb^~g$Q7&_)wzmY<;SAKh{V3;X~bxSLo|`TbPcoe7Bf2;wq~)y6x60lR}Ej3 z1)T(G54l2YwL%`u3a-sczlfG?)`pC9&oYBcAI)9#uzj0s<(1Z|beNlpH5oZ*3CB z21PZ2;4D$2aj>6Vi?IIU0D+%rkdcwNNv8v!nlL-9=E~fhXp~w(OkbcPjL?!dXj3;I z7u3yMC>!}aaQVkQ*i9J0hcTI=fy&pW75R9tGDv_)B4#ZOI1Iz=Y}P!yA{;dmq~6q5 z-$uO54cLNz{}P0ni$ZJtVMOg7Y|x0{GtbaMEe#$`;SLt5$|@WgsR{`mJt%|W7X#h1 z@*cfDN{{X`Bp6IA00bO8C1W)_p)8FmvZEhv9+X~ME9LrVM{RmLu&&IB;)! zHMRqfctPXu`=P9ZPOF%@NB{PK|C7Og&K{V0ky_>h{wMvIG9PYW zJU_>t;M9x%_CAxA0yuZ9yh-<@FsP+Tv|Gaoh}VC+p9Jtg_>X;Kk56l9`XBHAWF6IH zT?u63XE-ZRxkp_&h-w75C*@!D<%6ilf%9YsB)96wEf8HnW zJ$zjM`fm1`MPJmQ>i&Rfja2LfrNtU(<6up-_C`$E6bTjbF0_f#Zs5?xiy5L1N1|J6 zP20N5slHoc7S7)GHE+GE&%xjVXcNdEnA9Jb;1{a-*55I`d%t63A+(9nADGM^n86q7 z>21GbpZ>t$`_Lwqe_#r~W1qLTR_ujEhd)|4s0Eh0AQY!y?y$Z3DX9-%XBP<{l@@cbAo4M+p9#E9Oc^5sKi=imj z&51hFBR`|LEg{k;s@uH@`Dkt{$mz&Fe*Q}dl5}Fh1oxW3)$LB=->5+Je zihBCooak+OWEq+p0eP*MHke?CQ3u_CNGqisOl;oY;Iw;Gd8bc<5>=zYNorJCXkIj& z9$AUzwu@33MMu#itI^!{5b0BC2a}o;8&1}}$UT*IGpV_#!Rf}R@~L^z=jwS$P;irV zwz9Iz%jO1qBOAR}#W?XstiE#GzEh1xRU=2@pF_bN*3K#b(8&Ql7ULosvGxG)iUas2 zL&3e)vZ??;<^Y6Z+*%{{4gd%>u`QUtfP#mu$J78|gab4b<6N7tF96^k2RNMq1&>>2 zg8^WI1N0Q*NKM!x0GQ(d!l_X3thKWy0L*cKv0@yw8QTs3w$1EIEC9fYwJZbx=p10F z7>94h&Hz9w2WU@&f?3vM+5o`S$bQVNB{=!-Sl-id`}DrEE###`!QAj{T>#k50fb6$ z$=|WE0Pu+e#J_}s`QXlH0f3(aNR{ArwP4Qyz@Zkl1>aXturOTqJOJ$F0173zpcd>6 z0Ep!P=C7e(aroE;0FdASnkBf27AzV7Hn*}ZoW?=Hhv3=z03gi)3`=lUt=L2W@a6!* z8Bp*sxU(Su$Z>#cCAj)lY#sm%aexH?P=d=E0l-NP;8=pwXv2O70JApsCE7EgU{&~- zF#xD>0N)Z^P8)U-0KRg7yeudf0?&p4fHnuXSAr97$MUEE@zBn;5T6YNpNBh}0>A|h z@T3G6(T+U~0IxWJ?;9xC2rhdW0E{_6Y6)(w9jgNXLLF=i=5L|k%kVLC05InO?@Dm4 z9aw7sxW@rb=Rm=h@a(GqV8sDSN^qnOY#;#4aRA|WQ1DH-^ECjl=c^fX)&ez7zWq09rXf`+F!D2_Lfp08b7u0>ncXwgCY2y4V)-K0v|# z@N75$1aN>kARfA~V*v1p1H|V-!NG861OS9^04_Xk*AML0GeA82U|aD02n9cY%iaV4 zEC=AnE<)poYiQcVm44V2A@O06;oi))@d^a)1kXoJJ4!2>_V& zurJY`2L)%r$6Np)n**5RaXCHMcL4B}1LWmH!5`q+t^knB0c`O&aT2x$08WwE7UBz_ z;C#5VI{*}L08czFf`lCbfL9#Aw-5?0h07uVpo{~A;BjjtESD+}54~&)=0#9&1$@jC z0KRd6NIcH97b^h(_c*}mVko!{p6v|)^&H?i9!KiMssX?p2M{iSf?MFuz5vk50kZKp zC>d)G0JdcIB^ChS2VB-40J=Fq0Un1ZV?6<&l>@Znq2PY_SO5SFaDZ=koO~ZP5&-o2 z*cS3ikC5l~S@s#p<&`Ey)^7AS{IL-a8|z^sA8h2qf3qIAmQdg>C}o!n3cJ9Zt;c6t z^*w#28g~^aDpeyYd8He`h95TKVPjovjNavMq z06@jr@WVztY^;lo{IHQH|9LrZ0xAx$$N7olRGhQ*LBQ`gaX`f};Zz*uywVK-s5l#b z*ocRXbzubA$PXKN@}JfVixNs1dyn&qU2zD&9tZdxCjzKA3Y>}~pI5p802OD$4;%5Y zu`V|9!$zL`=j9*(s5tCBP9~?~0N-JN-*J+GisQklI2w7S8vsyoHvF&=4;$;kh_I0# zHuB^@trzw4z71>SBOuU{QCsv>+;FOkeZO%$*G!zAYkOqL zG#!A^6dvudg9N)p!y-5N{eki=veB#UZ1jieA84{!mPM1*8Q@J^jP_xD63{d8TwdfT zJd~<2S7;&@3Dp=I8)C5*Gn3ZeOD3vjA#Qtn`S~%1n{yTV)~D;U0#y4!xq(H+#gNni z3yZJ_Qbio8qGOPRm+3s#QiXe4i<_DL{h;@sN0U}5RBm$)UAol}ieRXto zs;f(z>FYxqu7Iv2R7_1(283Ni1O{S3#F^o$44|LNb>oh)_478hr!A8S#<1V%Bx! zlYSx~x!AKxTc+kRWqQ`vCl)Q3ZIg$4Ll!GZ$@QuMM07woQdRmZR>uF<>E?`R`2>kz(5>`Uo{}1e{NUxiMge~T};Z*D87Ce zo6D~l-MqB^nLfiZV2+ae^FSdebaFIC8M9PczrLbfExt&azz-wpsRVCKWJMzkHZ!-n zIvs}~5|?JV`iWZ1*?{$Uv=2ROSc}N&4~SA+ocSmeMVy$MR_$VW(TAoxPL{GLVS=9P zi;T_@*19KD85D?`nK zausMHt45_o#ewv;?3E=?zl^S28gV{40EiH)!M5bJMN<9n@luQnXnGt*Ax#t}EYr-2 zwaNJTwTd@sfP?6Bfzas>)u%*C1EI0S-72%Oi(1g}i5=;Be!m0{I;Q7hHWgutb?wqEUFyvRIj zDUt~8`za%!&FZLVBVax#rUu_`8dk)x1%oO&SI1c#X=#M0RbG`o7ng6&7C^OX(`I3= zKegO{l`m&&x&O!Zi?aJ)2mY#8%~m)6v&jImOMlTi|Lfpi)o9t0)_*o6Ab$-E{!`%k zuS0*;j%3R>{n?rTHN8Ks`=7*X|7zt|eMPp;<9U)HsfC{{3Y!p-T7H(ZA}MvDMiA zY??y9jQ&%b?XL!YmD&1_284m`e<6bQR|CJs=6{?{0^ra8Vmdk6T$iBR09`LGu3f;t MDnOgB4$kiVFI#2O{Qv*} literal 24765 zcmeI4cT`i`y7oaqM3Jr(fhZV3NDl+5eeWIT-0^*XupEibtlxSv*E{EY-^^tA=>cRXYvwCB9ouUG5j*un_@k0jU5VAS`fXIEUlRB`mQ z$d_X-hl?XJ>K_nE7kIeYo_wl{atlIHEVHneU8J#c$92skZWu@Gsyc2Jm{Z?Ear2cu z+x9x>JC~xCA@aPWd$gX;O6!P5bEd!NQ=NTN%U5)}81Ytf)(ooZ;qLGo-lr(m4<|#6 z=N@yr+1-5o6U9^+a>d!{jEgo%$PX$%Y9tzW)T>eFaz;RoVkP$ho3MSpSewAI798Oj zy8xTqr7K(sM<*8=4|SJD9o>G`GM$*nQ(iXd?rOQRr>&AZM9$9B|B6`PrOOLx2ZiMk zI*}%+TP>43`noK8LtmVJu>b_2eHR0R{yz;92SE7Tk@ZHpJ6R%;P7ojGJ5R1PxeUs2 zR;@`fVVz~n9S_IvHj0X2xMEuG;GUfs^X-KwvwLOnRE_rq#v-`G&^F}N z=PT^D-*-ydTBzL?d2<;LbG9O_I$)CJIz)|>5==B&+i&-`jbgUmv_X3O@CG%XPD*jj zCJ_eThddgr&et%=?#v#&WLmiD?0gSj!t_OF!+`Vg_M`pZOhS^#AT~I!=!56&q=OQ7 zG}tah)nnsdm)toi8xn%Q`LtnngQwu3SFO$Wt#4BEvfp)mO9uO#-bERI&0^*@nHj^c z9Japn=<~DI7A2VCfiQRzl2h<)Y*hMf9-l+5MM$#kEGMCe3s($-C?s_merIc=xC?S= z#%#+JI_z1kzc$FMCkva{;Fm9dz1*`g6)!8=5DRftNo1?|Dg4km;$(72_e0;t?gm@^ zlZTmq+}vx`B|NIHctVLGMT`H54pk?nqHtmMghD5a`qjFT1ICZA_$!Dp-pp~0TQNGL zcWm!-PeP*wTW2W;3LLe~{hXpHT~OC&=R{5yJcj5W;eV&MVjdIrfy?HVPp6WGbVMtd z>Q|Alz3-SidWd6J4)4|VuGgF9?gAwp~OL5DDGH8|Mx8-?{@XsCyy9kGF*@wN)*49L@23YKB#Vm=A~jH-B-9yW9dZVfRSaMj#d9^4!?);5~>)io( z?Oox=M_>nD%%8R8mFY`e9OH znrxo@zN`D~Rs+;E^;7pAGFF`P99J1|5w`W7d|2BV&wRK(z~O9A<14;?lLjeUQ?k>V z!SlR=6(^Fj1?t0j&9gXfj8tH*UO?sC-Ye@f3TD2UO_$uCa zG?c6>@V1Q6CgLg=m&VLni38Dh<6@>&o8`^E6o#cqSjgF~#=Sa=+}(NhG$~|K&DZ|q z(kI)AgB(@l({j?G7veOv}o=)rOVru?-5Gs^BkV?af;SY-m z^IPM}oHu0$-zBseG*50JJjU{EDo%OW^_%OJ7$s({6>eN{D7vCsyk}!q|B!KsskLeQ zCyeNzW4v?iCrnq|4@dK07E=by?BWBZ4}zAacdUYV52x8Ley>m-e>iaqYn*WJDKE4A zpzKYtd%4W~s6ndd5hN4w*{j`IoDDhb%W}2S1r`^}dc}qw>|0|+C-%L1urAi9!z_?H z**O2oe9=}aHcitvT8=f@^u_$eWNoj&y(?u}B074d@^=ODFkaUtx-LX|@(buCXw4ft zZCVXF+0^Xf>VXX|_dua3M?dF~DZh81i1O5#;zJ_EE~ATqcZ=`(X6mJADR^b-O>kN1 zJaK~?+z?j4u=Vu+86-Eb!D-;=ujb~Ym!oIkti82#mqz~}t-Ze+6>D)o_l zx#YtP^X`sbWG=ifkAl(}M9|Vp3^N1Wtu9Z zE671b&MHCyr{|P|Qxnl!GWXKU)e?0<7PoR{zYK7*C@e-&yWcHnn_XM`=ABa3N41QW z$>(R>WC;29R><>UqO+1wA z6E%BSdVBFS_+<5@Tas0_^usT_Ctk_Q8#W5gpYY`|7wtTxlSeL+Fdt0WQ!uDj+oCP~ zdHn56y-L9wVI!rbk<1WD>FPzMjaX61 zlXC~{pCsH0lxeN@8}{aWQ(7gl@-8w%?&y(pmkKf0b9ZGc_>6c0YlYUMhx6_PdRc!j z^%yoXJm#dxvt{&jJWP(w0NgC;(@`paRV|oP&UpR_fuA>b_x<rt01GO>JQ z6&lCqa475bQ=76DqnBs*vR*aNem==S{`_X_@GF(ONxgBqj>~#R31naIn?Cmux+%cT zwwb@&>RbJoi}`tZHO>V0qJ<;Fq~mbWSXNioK#zCbqh&2&^ylh@*c0bxO^*cPuS=V; zXm~!5NzQficb2_7&e6^F(@8Kpcra}i+uGKsULXZl4@Dj`=>L)8xd-!Aj{mf%h91-+ zPtleA+|xAyKLahvtg;*AJxICG<*+NtdlpL+37RAcCe1A0o&m$_jW;((4JA{JiteCJ z#U4vcx$NXVImLRbR@q__{w`%9`sP6G%4SNCUdyuGwLLeQhp#squq;3^T&C~0d>T94+G)MXn!1rj8TOBQpeQ)t)*g zGS}Shou9T?FRb5nUfP~(_H2D?L@tTdR&Tu;r+THv+jgT$Y~w5A_C|>Jmm<^x*A(c9 zI-w}+fxMHV&t0ZH;#$MvE8%w8Y5{kd!qfzUyUMg~d!F94-7Of^@qT2zhl@W=D0swS zBTl|Mt@7NixH_&y6OK?AXZE2`x9qEXZtfi#$DYeuE18ztMauKFchg8!y7sXB%YtQ> zUwyEh(J?4?@jc7d{rVT`mX58@aRN`Y9yGhnC*0P4j0Z7gOmbM8xiOcg3^YB?7~Gxt zGPifYY0J*|-t37Fw_@$?oD(-?Z&^prj11+)KRM$jeBdMJalP8oxubP=7<*u|Ou?2< z`qIjo`E-u#3Ubsm*z#$LMG6=^>ACx*zx0j`+k)eFkL?T%N2|sU?NXU&uZ5CUqo2?k z-3Wa6NZm~{J|+?E@~7o2o0nSm-*3b0HMvzhc~MOKsKdTM%jRK%L+^-Y>{ekv2a=a~ z?Bkwue&NK?y4K3-O#}DU5ytlwo-SYGamzvlq#UGL^b?s-LoW$sfhN-0rn+M=@^&8J#$$kF$#6oVd(*JY$gS!=uB5t2z(>{QBa<{}kFv`X*6x-t6O3dm*j^b8g zY^BOFHNRl8md9UF0cKQPBupDZF5h2D&K{^ zXE~e;ImNOvy}l8oE*%dw@Dmq9o-$++3s}; z)tsrGJM?NkD=ufupD3+)eP;v~eD#Y;+cP*yhCM*{SO7)BWMh*H@w{ zm{vd%o4fPsN|4uP23I6EqgRBBKrgv)IG>H~h+LI>#V1Af z<#Ue|uiBSa>wf!}&IPk*PTf6{cOi%mKBSp&xtu>&_{~w>PsE{P#^&!bg>ohPZy2AM zTxI`$5o35UsAJReBTnU3MZ|-y(eCs#^?dM&?7b+_#{Sqdw>Igl5;rYm#)vZ`#> zx1T?(o~ymji=8gd?QjiBbo%BQ2$6FVnKZ$<-&wL0IIGKJZuS13106b8$5|$tQ(H)ZMg^_p=`o`OL_`5W(_yYl90Id26JVzB|&@L)HrEZbRQs z8nysiZ_di)gE54NW%bNkS0ou{B6Kb}GmqNNIXhkwYVk@Y5!_;r%XIE8O-WgrYw7M! zm#k8?7jsNXm)IM_65C_eD0+UjXDIDR$1O=E87EeObN4k0w``kvEsDeV;8LA8?=8KI zIUVwr%{lJ|lT}@bz5$P7sONZg@4;gBnO#!N)%J{v>~+d!!C5a&9+{p;Jc*jJZtAyx z>s3C-rdX#BV@3-KF>^tkWTu+U0y|IgHS7G$xSxv-7V2RUyJ*pUS|iTWun!f2JJiwQ zavjm=b%RYGs(J>NxW_Trzy03I#Gc7Bz5ym}1|s2Zqv3Bs;9OUj3qjkR3jJ1tEe#7a zo2|6z`0`DNts7?SU6;BQI};tCREhF|u%Vr43Qa(<{hY+P5Q{2XE9DUokDYuP<2SFu>}d>}g*rscY8w zOcs1Xk{ZN#dRQfGuCLc>6FjS(JI4UMIIOM=Ay7=f?P&UC&O zV=Bt#7_T#Bf$Hov7ESDY9r)NK)NRFP;3fI1+kKy=lN;t-Ug@cAF+}^Ax0_#ld%>J1fI~T^l&`>H z_4>0D?YmKeUlmNJWodEic>U9M1Bgm85W9c=O$pEMakIMX;fZvnhpzqOzFIln&=E|| z-u}VsEHUV!NJZuAYfrHyj2_6wE0KZzV)qxK!a>lh-KyK?-*fhJIhM&vonVkTz%DL> z+|E+e{@OHrTO{Pw#2)DiuRb#|=7&v;k1DW=0Q7nZc>Mi+ znVv_wc-pvlBCh+o+IYZe_x0_DvL3K%v71t&1rlC^j7Ogp?T+S>G_bXv)3p>-v#!Y6 zUi-;<{a}cC&)(xw)tlzM)3I%(pRS(0ozf|(#iMeORU*4g-zG{Vp>&%0MaRW{ce^j( zbfYq%8`xxBm{6{|VR$j)LCMgP*o{R8T}uhmF78H86{z@SDURkGsQW8puLI2RK4@8g z*z8S(F9z;CB+a)%23C$=-*DJ)=VtQQrF*GTZ3{qvt*_2LW!1RJkihAn>aEq_*W#<$07_5JF;H8ub67m$5l z?>UvkGu}`6s9u)KQ2E}?!%{nRLjy;KA8@f>4O`)BI($#8?WDlZ`c&#AP&bO$6@?j5 z+3MO`{>WsQ0~@9)zkW=}eT}?#J+$Y0=mDD%A0AMd*ts*od*AbIi+wg?`yrka_Sr*m z(WM(Uqzf=EeYnoTxW%(SAo_6V>3dd3u{c|IzZaH$(NqM5h5S=l;MC&xQ2I zLq}9R)Z~v)Bk~Z{luc@Y2YIStc{VvRw-l!W(Mweg@}!Wy^|VBmss${sBT%!b3K6yP zGFAUg;v8{io~#g!LIo|a6Kb|W2&(7Z?TX|+JiauVU2l;zncPr-MB)NhSEkz{`|!wi zmsGPVWt9JVb8haef?a7i!koIeGKpCw_qS}~RwjvWRs7GURx4HbY_8YI2i)DxQEBMO zHR6+PPeWAsP$#$M=flOzpfxHLs+C*GeS8ELsOli9dMhrKFtOUN7oHL1r;4bo$AhYg zWU@}(a#fj+Ti{)f)frM#WUc}TA*ffDw!nf9SRU2$wLrG#qt+-GoJi_AX2cbTFf$*b zP$*}h6w>UbICXV3!-y}T6yHY;SS2o2SDve=R8k9=$MkdHaVa>;Y{g~)jzXI03tyaW z(W}|sm~Oyr&G*U5vX?F{IHNoRDKp6pRQ+nA``VVCn)xse(vB)8kK&_U%n{q;;R)*$ z;@o7nJhp*)H*krVz=!PMSS4+2H>@Y;rmf5nK^$wNiz6Gv{Z#yxYd17>HWFkGG9Q=i zKwyG`ng@~_2n^^L?q=?!%MFLEt_m&G)*vb?1Gn48wlfo; znW$>*@X~Qm2f<~1-I(`yw8F3rxw+G0S`9lmRw5+ zAX%@fQNf!Hx}<9f@AXpKfJQ@uZq&7eXW|tUvaE4xQ;xs?`t;jq=M#G6LntHEdKh`p zyRCn|zZ8K$Y_CjhQ0gcnL{N1p6tT3Be0{vBFshF@GJ+aDOr|goKMSwJ!3hh|b;+A8 zB06CS6>c(`mcfsPASkPCk$KlcXP)le)y$94%H6Ub(YjZ^GWwb;GRBe2Egc4XC zL1b~UeCv%OZTk|^hiX3gQJb+r0X7{vO6AcA=*-xxn%~19pG%v>qwb#KYAP4111_nS zKNnBU62`RXarpXq;vI=8x#EPOt|XL!FFp#IY2rJXp*Ov}(Lfjq4_}c5xvyxGcsPu6 z)Iw#*<|T1!#Hoih_LT`*IQs!t*@^zHsynDHbE5I$i926&K8WMWf{5+HRRajmAtfaW zWxmp*0avpfjVq%xsD+A)a0l$uB322B6DF2$CmsM=kg4G+6+f-iXtf|&vh+G{)bKUM zIKt4Dk6vm}xpsqwTFBCbj8gf>4n39esSXY0rKe-jeG%bIpn~TCalK1d&as!T4@6V$ z`L~Dv$WR-et3MxA-M?LbethN1K$C(42$wnb3RJsw9%lv;`9$ed;7eo1nJKSNN_NB& znlQ{DEHCf&tpPky=$rml3vux=%D$DV*?!CQ``BwxOM;yWBy1EBA8yx*9nhq`Mtic9 zFn?`e_Q}ZYt>Tzlm!TW&6^f0o&-b}v6Tx;Lv!c^4mp=19O0d5fKLM<)v_ZYXrOcwqtjzLF-|TgY zSiiE?2M_)0z=Q1@ct9z(%7f{D?erOc{B@vTc|VmT!bZ;wDE)nVi$kfKdhP0e|APNY z@V`DUbs+K=Z_ zq-_kGw*b!9(#|8g+W&uR_XGU{rpwfKTK{le?f=i))A>g%G@9?z*R1k@(pqKE%uzzY z`r@<88z%zYt{%4{vP@6CnKmDmzaM;QKn;1h8Da;Dz&SA&zWiCY@y&N-ILuRrcQ5*wt$5vAkA*#ycIb5jhINpXFy_oZ3f zdV;5j$ZAUF7;Yw)6Isp4Aq4@uZb6SqryYr}D}EL&ou(hJC)XnWa)PIm$m*b!9lxA6 z!Sj>I>P%)T4X8DRGC~?g-JxXxwPw&@NSc0v-iH?Pp$UHT_Jjnz&KB{x37&N#Yap4a zMrgBz%&^QFOlJBTPA;{FdSnt-1_1MPfCwlcAxtj; z1y?#SYK9iT!x{hpY)5+w%XTo_12PT(oSt-G+6--ghxGseoDQJd!5A+{eF~u9O$Qdu z&=Gjp7yvw{1F#M-+z*nO3Mlx~fi*MqS3GP90NUulrw%YC5E7CGC*K?g*jSjr-2E$Vz);WMeDjiUTqnD;( zfdH^g2e^NLF|QyO-T(^ebl?IUePRX{2>@ptX`Q_P0}RiE9D55WWYGa#IND+cmIMIK zbbtsbLdF4r^F19fhNHV?V8sA{p#$h1FeV>T zp9?4y(1F`=9Uj@FokbpgN?I`F9%j46hMAmaeQ*+>Us;OLk+7#ILj=zw8A7}E@?{{$$s(1Aoax@it34*)%Mptc_j zZ--A?L#FnkDdtQ1hd(18j# zdSo8v3jp(UfCwnyAWWYDg<(2S3r7nqz`_9l>_Y3L1V1hpMhkS0}8Wrpbw7zx&ZqK0Bv;O69$Z# zhlErB3JY`q3rCX|U^M{1jHLBJ6bptgL98nQ1tJ}okeQAz?IhY7rcbn4UxuXbw0GjM zGaq)=!%jZf$%p@~dI0a1A?UOuYfH({3XGr$_6&xYUj!5wG_AlCq)+SsJ8{^V4?F8( zCqL}u$$v{Zz@TLa7lDd%m_DNpnP6owL>F3y>7rE}WctJouoH)!`LMGtcJjkcp8WTe z17MGH6{tAM^cf{$f>p&37ibx#n^tiMK*iYscH*!zA9mIS?K_a2{IHWJ|J8b-3hZ$- zfQrLHuQ*&LSTPJy1gJQkz)v-&j)T%Cc7UBY?97Lqb+MBlcJk!EryKxV7;T{90RIsP z_zWXpf|bV*Q)wB7{yR<@P;qvEQ^$AWurnWa*2PYK*vXUsdO4^9_BfhA#o?n@oP#D< zDGbq^R&nUR;~>%}b^u_HvlEA%`LMGttcW}LVJA=i%k=`-!dw9=jsm^n2%BJ)F+?n_ z;?RG`!2uO#2RLjgYD%L)sEkQYCQzX)lsY0L;6ukq=hOa3Ob{( zr6o5l$jz5T^2VuDR|TOykCIncdq$XQs#Vk~lu(`?LB5nxQdiI5vX)VS2&Ni}@+6J* zf1BuEO9)?Fp!BOz;t4J&zyqUgtWE$MC|y(axk`$ zTcn)j<oG_7WF zC#_~{I+!?L*l~j$*;`jSLZy~YbX6424yrBtTM?gcYa^dvs?L!5vf7t$$TocG z=ONY}s@=>1^&DM!qJtO_8s!40qwDAJ9tt*?;3E`#W zMTO-!JNP7?sxZ1S(}?l!Wi7Z<#GLQj31XhfhfGTb*1` zrn)xZM5+%~SC)0);6k<|7~M@zBNRk79}6F<(w$iF1ho^^a)MTfqdmEGDJcJ0zCHyt z#Eb`}rzL?j*5QO7EGRIq-fGwsUne2iLFF~wn;`0>8Ww?}V34xdmpGM;gyl4m?a7(8 z{y{1^c^SkGDw^d$RHDpcHc?x3eR`;%7Zr9}iQZ00^7a%3UsH`5i0)Wj?UwEE zU7yf1>Z_>$96~8{OW1qnoEwdo7#Q4gcLTmvjJtRlep$T#?rKi6c>l-aH^cY8UiiC> zHO=(=*C7rVFa5;={jV4QZcI=|ca(QF!j9sa<5(Qf3grj379_}yfVX7~5&xC?~! zKbZgh_441n)@V+2zYYOF?f+i>r!U=Kum0W9jONGo>oDN{?do6dY=0H_-Dv9{1x^6X s|HKCEuL8f%&3_!uZXllj$#Bx$x%73J0cS4;hW)^w?|?hs2%OgXKX|}JG5`Po diff --git a/test/fixtures/files/upload_single_page/04_combo_update_assay_samples_spreadsheet.xlsx b/test/fixtures/files/upload_single_page/04_combo_update_assay_samples_spreadsheet.xlsx index 7d7a6f61db678411aa3d77a4c99c4340fadb43ca..fbc99d30c1e867375c7e3a7e7819d240c4b3e6f6 100644 GIT binary patch delta 7415 zcma*seLPd`{{V0#6tzg{7B)#LYI+cA5A=lUCMoVH>yDHWrNlHll|o6XCA64ONQ{nW-|vs#UazyAbDYn0z4y63uETZWdzA0>C@(#H zaDl3>l7@zc(n@!tN6}IfWmUxv-AZ|{3bYrw;GF9G9@%+?30X^H1#p{|JGSQHx88+q zYaQEGc3FF7Sgg^_efY?b%?Wcn@cOj5p6*HY*fUFVx1DX)%1qfygOM8eewz)?7RS+0*UPeE4`>A}^z7gDs$zxTN#KWIwPkx_ zp9TMA%vsmncDqxrIQ+aknzk^TZJ$$;Y{*>HSNAQgi? z%XoFSgt(18rMX`Dd58C$UX*XK{rzKVy2y(!^&M|L4oe>Rqkk}k!a3qP8B)@e^mK>D zB4<~p#33cwyPWZWbW~`{%10{e0OXql+ZRXGM|T$WiD5RYW=^ti3nZKO>-sJxGD3kf zM$8htJ+k$2(*7j;F?ftA8`hI`F*0@C^5~WC>_o#yEl#Lk4~^eM5jctaFr#(Ub*6>i zd=D)sVRXJfp8Vvxug8IFi?NOwuXA^eEUB{3T()*l^IOP*{zCN_(IOpJuZ=*;`@}gN z+Ll|==mWiKT|2gNQ#i*}Lk*zcJ*?&M7JOu|VOX_Su5$hQj@e3z&PKmM3bS5^Bl z731&%OQfqtBOiSn?lQI9x&At-B>I<0fDMVRT9ayOIq?E(yH#3GF|{N-15{PO;b0Xo zUBlbPu`(by7u-{W2&ZeMo$v0jpWCfkOQI}{q0yxW&b{6K{AaN!YWK1i1)AG3Y<_$5 z$!iU6y4Vbl{uAudJH8^XbktAN-R*hP8fz z+A(%{>8)?=T0qo=cFDE-2MYkZ_XUZcFVF0{_UKZ_huJ6gSr+NyO@;4FT)GTScaOel zx^`sR-#)bWa&AGUaltJfd2&f>UDB>7df# zgX$VNc6R=us!B>{mnq4Qwf;S0(X+LT{xfCOFEN3pEb=7G7`S!hq)U{gZse@T{fa#s zEIw$Ty8Q(G{*&*j_~7DlpJVHP?~RmR)zsP%dOLXb`=y8pkFUaK1!ackO!QNXb#B%z zsMp`@oANB_bid1kvlfX@-Q#a+8CIqfNaU@Fh5M`MLAvM6KZZ9uKDn8zb^g_{MK#gq z(_gH+uy~{{G&2Bi*bbPwKZyh)J9lu?Oes)7Y z%PLt_m0G;=^7#VHaI)r^O=GT=$42b#jYer_USKfQ#qY=;WQ#~%nEVf}k;c8pw%8FO z@N56n&sjKO8z30u&Ol4gA4C6UVr~^i;a*b9W(pQZG3|{eyq(fa?>>?Jhe9Q`xn>92C21n;9-&GXj@$cavKhm}Yy#8Fj zq-Xp1%{PWS-*_dOZuZ4v9%bc7#0$S%+lJ|;<=4&}^vIl`qvz6J<+keTtzTgB>ZqSr zCT$3fOSjk?w3eHHUEdIqzT#>+wQuL7t9_kLcso~PsOpba2rJfCuph(^*V3eu|^o@U3CU6lLkrT;;mI(B;?Dt5ubz9dZwz7v5tP# zgQ17ZKOa00^rr)VXv~U&>0eXr{}Y!vh&LB6P^3efQa&J7Qc|MGQzB1?=D%*JE_D-%f@;obnNM1Hv}kBS4w6Tm5!3j^e_ zJeXzDeN15X48S`L2&EoO*`%=4V~zp`(KOy@2}3%=NfM26utbm_LuHoN0SQQkc$D24 z3}!PU#bPR^tcO7qa3jitNs>_)8g?{hR?zCz9)=SNIK{U+#j;)+K%D-X$COE`<=+he zzs+r+RuDylmq_3=gB>3Ub|iq37$%tAiIj*t;S+F%bfT9EPLE%eNIV$f;sJ2HwuK}f z55qDfJOBWun>b>AFRem6!s5`RvOFL{2txugI0Fz*Q$cYaIHQjt3LA0KfyP+Qcl@Lwc9l4v%jEXG@Ua)K>>U+U>%)I&F4Ux)PlH;em|| zuPzzJlEh6+KwQ<>DH0?A;R#@89u}z2;|@$0hfABLd3rKt5)Ij-KF8|?L_avz!dWCB z7Ep0~HciHZd&`G~L&cEPcV;t*R+0Zlx;0lFjK|x0z7wqTIDy~MR zdgHDq$Yfx+xRJ`Cn2Tm{QcfPWtlWaj5Dl^W;ka@U+j?p!6j%1%B^X;xF_(yGeUh;< zKu=y;9Rbo`jbYpxb}ax-B#~ymg0nMeWwIV>a5qIyHb=vL*S{+MT_6f#Fm!0Nzd*q_ zV2zbuNySDMiA91H5s}yd79~$AS~83D29JU*owNN>Weh&8@8kdj5Y{F!%Q3QXFJYHA zW2V$)hGmv5>p*(rnyGyawIr%2I zMP8C1Uv#oU#tjC-#Y`+9mC%BNnkkKvi8IZ5K8*LX10T&wH*lle z3dGeFfYj|R47=6^)@(bWM`Or3vFtcmzt`|I4kM~LKMuroFlEit6SKF%K$LWpO(RXm z!Ew?NI-Dsx#MmV*W$u;{YpfZ1)3WNgTeJL$NyenOqu2VHi%?X>uw?=S0i~|@YlW;D zTmB6z!-E5j*a|`nH(}1p=pqKaJN%P$N`s~CLN($A7`1v6BVy4uS&&`tGYFjQLE@NPEOSbx7ZkBNSoUD5vz#p*-?ciJ*$v~; zrsH^Hp0e>;hjIXH36@0dNn;AbVm>Fp2qX@{5g>7xhHY5PpfDet+=wH+Yi;Sb=Kid% zMYh42_g#s5YN}TA$9ByIs}}%QfSFWt*iynwYF?CwxI1iu1~=eKqVEFY=(1hDk;FPW z)AKZnvF{f%GR}{;=?N^jmCgZ6$ep0D<+HqB@be*Wkd56YZ-Nvz#E6U=5>Qiw!F_Zl z%0@sBZU;q@GvI{D+~B%`0m{2RoER277EFyQi;}ixuWl&-@-Q}o-Jwz|Smh7Gg*rj0 zp#MIWnaZA|s3Z9jZxm<+%xV)ukXLx+T`k<|OZUSr$l9k!e-In2)%3dM!W`Gg6iMJQKQi6z|xE41n{VYM@<7HY($Iw{O5cG$`sBhUoVpcJI{?Qyr!|2+33)z zbb49Lr&|6<=-feY^Bg?HP`{w+l6GuGQUa~&(w5jg13tp3oku~AE9c$PmVJnxG*Dba zGgiCkNVK4O$BY8z{tG($-vb(Zdj+mwK&-ui4!kkD^I3$bqwFW#0S<*`Yq3s?JMQpH5X5rm9O* z)s@rK7p5`p|I7`ZM8!^`awbuAlc?cIlm-vAo`-VeWgRTW)CkgR1c+KedaVFaCrGao zAnFC_^<)RaXdVjo8U_1+g8hYpHK1T^C|C~)Hh?0eW@B0eKzfS+@kNmSMSy4(q_+wX zZG!YR^4W;dU1->DH0%Hxb_5OcM8i&_VR$qw1Wll2W4Z+CT>?b6AiZ0F=nnz)zL;YtPKt8LBj^nuu(LOkA{iRFcl1;G8@w`NbeUQ1_bE?0>m#t`Y!=uP>?=I z2C|8xUKrSE3=EHfg<$L@5aa(fJJBIhKM%9k*x#os#&@mRzJtFo`DGJd zlV^<=Ijz>jt~IgU;OQy!XQfbUnqf|kJ;@?td6l%beRnbEO1G;4^3tgpg4H`Z-+6om z@FdJ-7fJT$Jls(OIPMQQ@@BhIdjp_p7rzMc@cx@3mAzXZk~5y0rn{%9-~*K(+{}u% zMLc})RCm+wD#iSDhkbNo*RDN3rt;wKVAeVR)j5f^*Y!De3L7GJ-ezK-U#8QZyl9Qw zcF-QOY2sP$(`&q!DQsG6=52w8&ADn;(NPN9m1V#@ZYIi;V3$*b*el2FzX{>iwl^Hy z9lya?VOs~;_C0e?)5QlaR@jakLpIG~{@cSoC+2OMkj*D2(IHyje2>C*4zl^|$uHZ{ zp1fLNi`xU)4Dqb)XF$!wJkAKhN#r<~m0GLw>>aeL#P z)+lTTAlsP?_q28Rz~u@XVGU%{E#~ic^~svI=|Z+VdEV~m2kcYW3gtFHKB$@d^2=`wA84SkrImb{%+{9Y4KF3L6v%3uy+=2YE zjP_*L`GPwD+1&6f+vhcD^R}aq?UmfN(JDGoVS5YNW)eLKr*ex5_v!%hf_n_%-pC8? zV0^Br0{0e5T=jGJv=jKi%?ew$DP%(y^Jk9u5a(@3$QG5G=#Z>$en?@9g={{D^2=)4 zlYQn3?hs@4F$NFbGKQkJyGEHFNYHM#FLPdThuxFw-5I)daVH2){Y&OOGXg8lv^EMmE)+o>0Q~dyEh3yMu%XH2!8*NVx zRoH+=X9yRDXA!b%D&}$F5bm?whO&xIQ`o*iwwW|f!enmI+Aa4zv9W(u1O zvMtPUPrHT>v{TrATMyani}@>$`e@xzjHx|jTmCW;aCoMlcUXa2bqAW^4(FFG?MRN9 zFF04ob{o%1$*FPHRV3~XWIJ)xoV(9TEmL9h(}nVu=}EA8S!A$J@r#-X*$^EKnn&Un zZBW?O?1Lt_T=%qx_&_Ix&2|F-;SLn@UmW#0I*&U5;k;fZ);`zg98uU#K{gY&{Iabb z$tm*%=LXp*@)<6-rf=T%6ta!UZO&HF*$UeXWShzMBwTx0WT&w0&w*@vI~ooi-5$ST zqXM@d!tHzMo|c6VJfgrI-w4^n?C?^Vyv&jE|JXw5WapR4uMeSBUmtp@Om{nabpidO ztDY)d*Ym~#`l%B;l;v;BlpWhZX5Wu#n~SCwSAiRyby@fo-VZIY=;XZ!*Hqp+1AtB zaK`H7pQlZM(Qi%t4_-E&tiY~?TZ*p<6U(=4gcr_TW2wLDD`|Z2z6eWs5ru>`if5gN zIzfxhJRuu@se_EO1^mljrZR3W#4-Z9ESX012-e6!cwyn{B3&9fsaGo8$ot;-K%i&Zt7a;fc5hY~6v|H!a^@EB!*`aUMf9ei>Bgb(?j9n~xs<~l zt1E=r-s(PNH*#s@_3t@lWDyl(%$if{@qkhiM-f@b{Co zk52juszwJd?5Lttm%z*g{EN$BVR~ZQ6t=9s`?Bl;rdRl+H~${+SFN^G^lRKNtZS2p zAb(~gLO50%-0VUEY`hE$kg?<+{3%gCyZeoI?@%>wrDejLP!cvd@l$lg3j4_bHa?!? z-AHe`68iMHS1r911-`T4RM(GAT{aR5_Pn|x=A1mqt0I%tX$91V31;upr|s2h6TnW~ zWn6bPCmUP--Jl}kb;Oo&J@BbF(&)UmMGMG37lFZ@D?QqnsK+f06%W)}&jy_c9(q^Z z7$s!Bos**4;^LSLM%NhS{Et%J1fe;OW?c$LroEp4^%QHA`%V|7zC5a=^c1BkU!zo3 z(N+5IQ$Mmj-U@oc7lF5hc1rNpOBBxm|5TwSy<*4)fpDlR<6nO1sJ51I^X8;uRt4 zWSGiURClr+WM?Mpm>FhYelzahIp6bro`0Ul>oqgi-1q&tuJ?Sd&s_J*HTU{tQ~P9< zJPt0G+aRN;s3`L%Zq~P4$v{?4+Nd;>-MFt)MwlVXv`4!wf z+TMpv2)!_?D`cDFfL6^tpX4pc;fY3b@(0<>g|v{bP0zpV@>P=AWp!oGA9wbE_$!H5 z4XsJ<*Z6wv3@;e=HL*FS2;5a=dbrhnXDbBVQ@pz4t%a>=Jk;+%gXdRAG@cFW(A&~PZ-n*ay#{5i~A8H3dZpwvbEd-t8z|W?o=0R^l=VsvlL zfuO~CQ&&aj;*L4RT(_Uh;Te6!#f_C?)*mkP^5#C>B@*LLxMaMf-=(ANGBX)^y&pvNwe1ZYk^WQe!pV}8_)Ey(1Rlwy>Bd+Fy!TaqB-?IbQ)f9SQ z>Ba^5i&5vVmG~}RIeJ-Cd|0!m=lt&{L*iE#q`5VHj97Qn@2`p)jcVHL^Lv8xUdt{V zZZ^HlkIBoq;Jqh(#&xB~h=Plc`IVNu_#xWb0}WV%^`|FRcRyY}{c5=pe-lvQ8L+La zri&|2+;vNs=-w}{6c#nOTHK*%nnm$0J?N z@8g#vFH*myclUi=p!_ zdaAeaP4bGhyDP_@)!}-z>uCx}lh@r3k!zihKcAv#^N|l(Zh_0{>QsN}biV>lt>rr% zd;+|Z<0VZhyBz{9zm}6H-hV&exnU%vKQ?tsF<6W1P!IE@P}PQbwv z0@sytqR&Heq97GgczQzhpqDu5uHEI=Yl6ahyX`oxaz)sj6?|glfNRJb(?5o*csToY zg=cR)^3GkuJN0nKo4AGWeg~TWlSgQt`p!>#&aG-csDXVI5@EgTgRv?N9u=w2xzm_= zBRF*LMe-igz^AWHbyj~TJYM+1@%br=`M%{_yRdaP`HH}wGX4ix= zzA4ASBw+TJBlym?Cgp#Z9JrbT#2eoT2r}c|LV`{VLfn|D&;|mNMXTTulym zw_e*h4_CH@vf=~fP{KyvvJ1~M@ad+v5WCOJSDRj7nz)GkFScFRq#KrZbS^A!O>xhr zFdM+kQg`KDpIoRL&%TbyH(*h1f?GGXgvlx6;V(W)$1qw~M3R`Xf|Xw|~cD(eBMUTtm- zyVjAIY8kZK%zf$pF56_MZo4{PTrqlf=3=O`R$C+rNW+_+kJouSJ9GCO>RVybJ4WP_ z)R5Enp6Tw_aqhmX*QQnWU=PwUzuYI>-%K_9njEgM>2ULglc|a716#lQ-k|4ZnBFYW zy7=;lTyTTAhxdBRTi3?lwVPEbO#!OgTkAtsS}HYIAIJTX>whP}0hN01*Xhr_Ati6# zO|7wP@UUC^LsM&oitU9Rzxs~Hec!F#>i^gCgwh}JT3reT2S%`DWtNLkgwTm|QAGN6 zVDVO%Uz3blZ;8rBQ;*d@ugVv|Ep`zr#ayBd*I?wb)$o11Qb(gr-m}KrLuYRxGd!TCc;clcQF5tX-{^qY^+CjXULt^_v2N3 zw^f$5`!Vz3o*VToGTGhxQtz$%{@AA79j(nN);)dugncS&_d7K`yBRy?4e$N}w{vxr z-C0>8Hg@URuFCSwom<^c`K6lHF8_KOBX{8Q zj*DUAanDU(VX_u3IHGc=vxavc`)EGZ($IoYI5xZ~XgIvW^Q=Zc?M#<1bLP^M!$?6w z@@d~gK?+@Mq=IpsNPRRssB&`8POn@Ak(ukIHx)~j`Q^fy1fGnHOpYWY(!yNzpW7+^ zn`$R?6D?d{2$iVhCb}J{45VHuoWzJ%$x|Iih1fI)FGp+dMAxXDZv+n;(vc|+qnfKCu|YtcylSSbuEpe(Y<0#Y1( zlnDmKiMytWWUh#S<#NRw8jCPTB?vhkZ8S2LJ3CE72>}KZz$}hY#auseKU}oHOA>R( zXhC(n$w~|OyfBR{5L`zJ7x-i@qYXyp4V2P?M4YDSAu><&HBG=;R354mOnya*=G{>u z4j&d&C*V;D3j@_CmO7l3EjdZygF|&6hXnI9Oll8VIBS8b`_Y3Ej@0@Mi5KP^0c_Ob zY#U*aB!mI%uQWg~A1W3slBXBzMI#f0!Prh^8ufW~F%3Ho5_^6NqSd17c_WKyM1bF+ zj;a&Q2?NH8VtI^7JjfmJ6X#p72HIdEDhw-@bU9BOihT_E5cm)#8W7&amO)$u1;`Hwg$Ny@)5tWDy!Y+@K6_OHr|0 zk&$>x9R)Bcs`YiV1mXNtwIn?N8em?ZZAwu`EAzMy#rzyUfJc8$7SFT{)rs(Rm=u7+ znn;5M(a$Zw>bX3cWH!1s3n!D1q!#h?B0v~ltfo`^N!(E$M?o~?hu+zzz!}K_cmp{W zTs9HQ?;xNUMcHDGGO<2xvCExUFXB*z1b{QyZ(+nu0=RQ96u_sdb9h~7lxU=%Fj#-f zq9{h#pdcR0xh`k}$v})FIgXi01jMWjjH!O~5cUU%hMVpz~5MNJ9d19dfQI1(IbB_?9&l*b|tQ(Ry7PBM?eG&F69uP&TYMoOx=mW`en(B=(Tpa9_% zKdDH_&6U(7)q=+OijqtOl?;eD9b{6S1Dd#4n{9=1m`|iRNb1|d$SGN^m%g;m9Dc#sJx`+Ac27K(_)F6Z1w+Y0)bb;1a73&EXF zF%okQh&iRg6u6kf(ym92qA`u!1;r5+c89@zCq!IyBN4MdhCZps8ct_T>WPNaMU#4T zU^rcJh>W@XNwAPEW(=nd;dG$xHLAX52&kVF!hbH94~n)=5@SFt}~c^4pjqK1DC7#mBizc@g>jNq&~1d91sqtQ!P#l`Ypx>^rpug@`XQC z$GAH*#d86sDd_gtsFeIbF*%PU0?FreB;&@MW$=*Vd`TC~08{a7fGGh0zW~g9A?3p( zLflhPHWkjl4*OtaaYb#YaZ%I5ZB;afm0HrILsEwECEo2ZW0;H$d z+!jEIr6sUxJZvlSrndmmB02$wP>5T4l}6C-#|l`_I83t5NRhgX{<=6gEO2?Uot7%B zt*B=NE)1GSj_)-+os@2ku0N zMVf2h`-muD=dyNIY^PZBpMZSP#nHV8_4tsO`UGI@?qr8&XU1xd5UGjWH7d#V*=fXv ztCEAG!Q6$tKJcEz$yNW?QDSpqucAt_xrvihxFaB~EZWw5#wmH<>y!5OYev&UdhdpH zSy!BNwwHURxHdplGW7m7{Fs)}@-{IIkI~Y{PiVQwv8HT#@dPbBytnpxJVtxVaxP$o z->^iK7wqwm`8O5!;}u}uYmZL=yktO- z42TqC!tupA-uMF87W@#*0)G*wmiPu;Rs2R>%Z(x* zUE7Vx;EGXPJm<8ot!6SPKiUwFx70Pl>*!s=b9G(uKlM)IpXzx44C+Is9VIr?3U=n( zxkt>Cl-S2suygrHkQD79ifwi$%DFi6;%(n)7#dg^+de&~ULg2PfpbExrl&6~j|;cs zL|skCkFJl7v}=W35hZ|@3M*ZZ-;LC7f>sKD2OOiqIj{Z+c>kY(Xc3U$Zu579yX`*_ zladI|KM{)8{uu%%8N%-G0MP!Q0L(8qXWzBIO&)&-yhxe^&&6Wxb{%$+e25y|qeX^B zO-2hd3HOnD!WFO#B|tS3`^0^xA`Gt-?iFhf-q|y8aM3P?Y8Okji=*0MsdfO>?grH^ zo(eaNn%ay|)<-CBLn!}_P~MABwnr$tB9y%llU`+MbVM;7@rjQ3MMo$y5L+1tX9gmK zG47eq+XmuHK-^9cw+qDm4&qEfoH>ZIbPuAIfJ_ANi-}NNKx|z=I4>YV77)n`h~kCu zNBKNA5a$8nyg-}}i1P(;M?suFh&%2+I9;O5LTqIroLPtv79yF2C}tr(u@Jvl<0JXJ za1eJ5#6^O*C=eG7;$lG@7R23f51KD==O98jh-40;n1lGlLHyz%6uF44+;PB|zI? z;`EyZsdQLoM(&0kYh|h!z%q~EWyywy7hlR`+#N3nIlr}-k@7p?*& zF2x^HUtEq_O59z@7TnoteIQlOKx#V!*=(Ns>X?R_Xi9CK29RxY6{Es4xNpg}8M2K? z@^(*ivy;?Dfo$GRWu%Bs4QoJ(i*bT*h|r1l=gm_~I9n)j9ElBPMoE*}mZ?Gom+p^| zFDb_@))2vo8giTIB#5% zRg|e2;3CECybTqcOBv}=r^ex>f^&gvZlM$Og63#dY2rK}Te8=7;#RY>_ocQ>$ku(| zALCY1UbK|AEXY!rsw*yMt zm69x@Y|Wg5Qd=Zs>o{0Os_WE1Efw5B$aW%hLZz^I)q1Jz6lBxz-cH92?K z_Q)UeprpKQDQ}M;8wf}W?oev{Z&F-4gv%@R)d>wXF_R{){5QyEUd1@&9bBLJ^Dw=&X5r^c0~f^&mx;h__|i<%!V*{(shVu@|9+1Wg)4XA){-Fg0) z!IE;?QsVL<98*$o?x|0Wq_#!KR#)V!6B}w`E48&7K{jL+gW(;_Te2Y`o9v4$qbHi1 zJ)}1IJ4L8Xn_sk#j z+l%t`j?ypcGsvdi)f(fO`eCcow#gA%;fj5A?uD8-Np1A4kj=S@k>C?-e@9wy&XCRJ zMOIOPW`LK}=5_~w3eKyHWZb2ZwN!9k5bl9wg)3=(w`6+=**-~Zhs@3vN^L(NTX&&9 z=F*FD3#sk5BFJXl)mrPFI$|uf*+4d7iLcI+P!kWSO=TNo^Q>a19x)BRvxM`6a2b-s zm1ySpNO2FK#C7oghGqRQMj|R&* zkBiZ?x#--tnU;3}HKRM>Rjc4G512-B#sQ8;9BYqNyubA4+Kz^!yMtTIpNw~)W8}~3 zDm&fr{akbO)@pEpIE%F^DjHCCR>r5HV3pT`_R8X~1w~X=oUoU#d~$k)EbuPkYE;2g zcs5P>v%(kK6WBAfyR8Z)wkNo@8)0bwz!pY&b28O5(&s{{eEryC}`f_Phd^84Y zS@e5bWJL5l5diqV;(-O$RWz%j z*9MhOWWgyXt@=r0oqRfC4QabMm4Uv%KrLN z3p@C2drztGq)t-@_i;>3(BM096!%nZJ_{D7O88lCP5$|#ERxpTHC2r!#fd3dgZb1c z;W3Vwkku4fV=egRjjyIr(2pM@+zw;Ir`^n zQ0?^E+)L(3*B=bc#Ms7X_j7G6QTl0qx^N_h9mQT-N;$gry z-=yi)qo=D8byyC@ysYO9<_c-W7Xz%VSes=1wm$Hy^6nOg80!~3jIhc;?Q@ge^qHL? za_6_`D;0Zf@|;A1b)^HMXg@!}{P_5X(&?fKKHJ>LD55-`Pxz)j;1@~fO}fJ^l8E^) zgAVbVkVQQyPC4prOfuqeKB(R|=Mz5B#J@C9&!K;<+uW05b&1Y6A*$YiMW!u`js(99xj*^h71B-DZ57Y%mHNoYJVnS!wi#uYZIJobKBIl88T1of zOsEyqcpGZ5R{BHUKl|o*L8ui>dgSuNtI0meer1JZ_f}K#`k%i?c%KW#P Date: Mon, 27 Nov 2023 13:18:02 +0000 Subject: [PATCH 245/383] removed need to pass base_type to the attribute type handler factory #1648 --- lib/seek/json_metadata/attribute.rb | 2 +- .../attribute_type_handler_factory.rb | 6 +++--- .../attribute_type_handler_factory_test.rb | 18 ++++-------------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index 4ce562f20f..1793c50636 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -51,7 +51,7 @@ def seek_resource? end def base_type_handler - Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance.for_base_type(sample_attribute_type.base_type, self) + Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance.for_base_type(self) end private diff --git a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb b/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb index 7723863e80..ce4ba7794f 100644 --- a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb +++ b/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb @@ -10,10 +10,10 @@ def initialize @handlers = {} end - def for_base_type(base_type, attribute) - "Seek::Samples::AttributeTypeHandlers::#{base_type}AttributeTypeHandler".constantize.new(attribute) + def for_base_type(attribute) + "Seek::Samples::AttributeTypeHandlers::#{attribute.sample_attribute_type.base_type}AttributeTypeHandler".constantize.new(attribute) rescue NameError - raise UnrecognisedAttributeHandlerType, "unrecognised attribute base type '#{base_type}'" + raise UnrecognisedAttributeHandlerType, "unrecognised attribute base type '#{attribute.sample_attribute_type.base_type}'" end end diff --git a/test/unit/samples/attribute_type_handler_factory_test.rb b/test/unit/samples/attribute_type_handler_factory_test.rb index dec9bfc428..12344c6714 100644 --- a/test/unit/samples/attribute_type_handler_factory_test.rb +++ b/test/unit/samples/attribute_type_handler_factory_test.rb @@ -11,28 +11,18 @@ def setup # they are repeated twice to check for any caching issues types = Seek::Samples::BaseType::ALL_TYPES + Seek::Samples::BaseType::ALL_TYPES types.each do |type| + attr.sample_attribute_type.base_type = type expected = "Seek::Samples::AttributeTypeHandlers::#{type}AttributeTypeHandler".constantize - assert_kind_of expected, @factory.for_base_type(type, attr), "Expected #{expected.name} for #{type}" + assert_kind_of expected, @factory.for_base_type(attr), "Expected #{expected.name} for #{type}" end end - test 'for_base_type passes attribute' do - st = FactoryBot.create(:simple_sample_type) - attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) - type = @factory.for_base_type('SeekSample', attr) - assert_equal attr, type.send(:attribute) - end - test 'exception for invalid type' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) + attr.sample_attribute_type.base_type = 'fish' e = assert_raise(Seek::Samples::AttributeTypeHandlers::UnrecognisedAttributeHandlerType) do - @factory.for_base_type('fish', attr) - end - assert_equal "unrecognised attribute base type 'fish'", e.message - - e = assert_raise(Seek::Samples::AttributeTypeHandlers::UnrecognisedAttributeHandlerType) do - @factory.for_base_type('fish', attr) + @factory.for_base_type(attr) end assert_equal "unrecognised attribute base type 'fish'", e.message end From 2f5e950566898ce9fdc05ee07d03a844b485d2c5 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 27 Nov 2023 13:31:15 +0000 Subject: [PATCH 246/383] renamed the TYPE handlers, to just handlers #1648 --- lib/seek/json_metadata/attribute.rb | 4 ++-- ...handler_factory.rb => attribute_handler_factory.rb} | 8 ++++---- ...te_type_handler.rb => boolean_attribute_handler.rb} | 2 +- ...tribute_type_handler.rb => cv_attribute_handler.rb} | 2 +- ...te_type_handler.rb => cv_list_attribute_handler.rb} | 2 +- ...ibute_type_handler.rb => date_attribute_handler.rb} | 2 +- ..._type_handler.rb => date_time_attribute_handler.rb} | 2 +- ...bute_type_handler.rb => float_attribute_handler.rb} | 2 +- ...te_type_handler.rb => integer_attribute_handler.rb} | 2 +- ...b => linked_extended_metadata_attribute_handler.rb} | 6 +++--- ...inked_extended_metadata_multi_attribute_handler.rb} | 2 +- ..._handler.rb => seek_data_file_attribute_handler.rb} | 2 +- ...e_handler.rb => seek_resource_attribute_handler.rb} | 2 +- ...ype_handler.rb => seek_sample_attribute_handler.rb} | 2 +- ...ndler.rb => seek_sample_multi_attribute_handler.rb} | 2 +- ...ype_handler.rb => seek_strain_attribute_handler.rb} | 2 +- ...ute_type_handler.rb => string_attribute_handler.rb} | 2 +- ...ibute_type_handler.rb => text_attribute_handler.rb} | 2 +- .../samples/attribute_type_handler_factory_test.rb | 6 +++--- .../samples/boolean_attribute_type_handler_test.rb | 2 +- test/unit/samples/cv_attribute_type_handler_test.rb | 10 +++++----- .../samples/cv_list_attribute_type_handler_test.rb | 8 ++++---- .../seek_resource_attribute_type_handler_test.rb | 2 +- 23 files changed, 38 insertions(+), 38 deletions(-) rename lib/seek/samples/attribute_type_handlers/{attribute_type_handler_factory.rb => attribute_handler_factory.rb} (52%) rename lib/seek/samples/attribute_type_handlers/{boolean_attribute_type_handler.rb => boolean_attribute_handler.rb} (91%) rename lib/seek/samples/attribute_type_handlers/{cv_attribute_type_handler.rb => cv_attribute_handler.rb} (92%) rename lib/seek/samples/attribute_type_handlers/{cv_list_attribute_type_handler.rb => cv_list_attribute_handler.rb} (88%) rename lib/seek/samples/attribute_type_handlers/{date_attribute_type_handler.rb => date_attribute_handler.rb} (76%) rename lib/seek/samples/attribute_type_handlers/{date_time_attribute_type_handler.rb => date_time_attribute_handler.rb} (75%) rename lib/seek/samples/attribute_type_handlers/{float_attribute_type_handler.rb => float_attribute_handler.rb} (71%) rename lib/seek/samples/attribute_type_handlers/{integer_attribute_type_handler.rb => integer_attribute_handler.rb} (88%) rename lib/seek/samples/attribute_type_handlers/{linked_extended_metadata_attribute_type_handler.rb => linked_extended_metadata_attribute_handler.rb} (67%) rename lib/seek/samples/attribute_type_handlers/{linked_extended_metadata_multi_attribute_type_handler.rb => linked_extended_metadata_multi_attribute_handler.rb} (81%) rename lib/seek/samples/attribute_type_handlers/{seek_data_file_attribute_type_handler.rb => seek_data_file_attribute_handler.rb} (63%) rename lib/seek/samples/attribute_type_handlers/{seek_resource_attribute_type_handler.rb => seek_resource_attribute_handler.rb} (91%) rename lib/seek/samples/attribute_type_handlers/{seek_sample_attribute_type_handler.rb => seek_sample_attribute_handler.rb} (92%) rename lib/seek/samples/attribute_type_handlers/{seek_sample_multi_attribute_type_handler.rb => seek_sample_multi_attribute_handler.rb} (93%) rename lib/seek/samples/attribute_type_handlers/{seek_strain_attribute_type_handler.rb => seek_strain_attribute_handler.rb} (63%) rename lib/seek/samples/attribute_type_handlers/{string_attribute_type_handler.rb => string_attribute_handler.rb} (75%) rename lib/seek/samples/attribute_type_handlers/{text_attribute_type_handler.rb => text_attribute_handler.rb} (57%) diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index 1793c50636..aaa6dd644b 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -47,11 +47,11 @@ def resolve(value) end def seek_resource? - base_type_handler.is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeTypeHandler) + base_type_handler.is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeHandler) end def base_type_handler - Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance.for_base_type(self) + Seek::Samples::AttributeTypeHandlers::AttributeHandlerFactory.instance.for_base_type(self) end private diff --git a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb b/lib/seek/samples/attribute_type_handlers/attribute_handler_factory.rb similarity index 52% rename from lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb rename to lib/seek/samples/attribute_type_handlers/attribute_handler_factory.rb index ce4ba7794f..fecee5b098 100644 --- a/lib/seek/samples/attribute_type_handlers/attribute_type_handler_factory.rb +++ b/lib/seek/samples/attribute_type_handlers/attribute_handler_factory.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class AttributeTypeHandlerFactory + class AttributeHandlerFactory include Singleton attr_accessor :handlers @@ -11,13 +11,13 @@ def initialize end def for_base_type(attribute) - "Seek::Samples::AttributeTypeHandlers::#{attribute.sample_attribute_type.base_type}AttributeTypeHandler".constantize.new(attribute) + "Seek::Samples::AttributeTypeHandlers::#{attribute.sample_attribute_type.base_type}AttributeHandler".constantize.new(attribute) rescue NameError - raise UnrecognisedAttributeHandlerType, "unrecognised attribute base type '#{attribute.sample_attribute_type.base_type}'" + raise UnrecognisedAttributeHandler, "unrecognised attribute base type '#{attribute.sample_attribute_type.base_type}'" end end - class UnrecognisedAttributeHandlerType < RuntimeError; end + class UnrecognisedAttributeHandler < RuntimeError; end end end end diff --git a/lib/seek/samples/attribute_type_handlers/boolean_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/boolean_attribute_handler.rb similarity index 91% rename from lib/seek/samples/attribute_type_handlers/boolean_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/boolean_attribute_handler.rb index c6f7bfdea3..3d647d0f7b 100644 --- a/lib/seek/samples/attribute_type_handlers/boolean_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/boolean_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class BooleanAttributeTypeHandler < BaseAttributeHandler + class BooleanAttributeHandler < BaseAttributeHandler def initialize(attribute) super(attribute) @conversion_map = { '1' => true, '0' => false, 'true' => true, 'false' => false } diff --git a/lib/seek/samples/attribute_type_handlers/cv_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/cv_attribute_handler.rb similarity index 92% rename from lib/seek/samples/attribute_type_handlers/cv_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/cv_attribute_handler.rb index 96c5f937be..8586fbccfe 100644 --- a/lib/seek/samples/attribute_type_handlers/cv_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/cv_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class CVAttributeTypeHandler < BaseAttributeHandler + class CVAttributeHandler < BaseAttributeHandler class MissingControlledVocabularyException < AttributeHandlerException; end def test_value(value) diff --git a/lib/seek/samples/attribute_type_handlers/cv_list_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/cv_list_attribute_handler.rb similarity index 88% rename from lib/seek/samples/attribute_type_handlers/cv_list_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/cv_list_attribute_handler.rb index 2e4e2a27d6..588a805815 100644 --- a/lib/seek/samples/attribute_type_handlers/cv_list_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/cv_list_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class CVListAttributeTypeHandler < CVAttributeTypeHandler + class CVListAttributeHandler < CVAttributeHandler def test_value(array_value) array_value.each do |value| unless allow_cv_free_text? || controlled_vocab.includes_term?(value) diff --git a/lib/seek/samples/attribute_type_handlers/date_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/date_attribute_handler.rb similarity index 76% rename from lib/seek/samples/attribute_type_handlers/date_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/date_attribute_handler.rb index 5f410c1c9d..32e3982d58 100644 --- a/lib/seek/samples/attribute_type_handlers/date_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/date_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class DateAttributeTypeHandler < BaseAttributeHandler + class DateAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not a date time' unless Date.parse(value.to_s) end diff --git a/lib/seek/samples/attribute_type_handlers/date_time_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/date_time_attribute_handler.rb similarity index 75% rename from lib/seek/samples/attribute_type_handlers/date_time_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/date_time_attribute_handler.rb index 07423be584..77a2729de4 100644 --- a/lib/seek/samples/attribute_type_handlers/date_time_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/date_time_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class DateTimeAttributeTypeHandler < BaseAttributeHandler + class DateTimeAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not a date time' unless DateTime.parse(value.to_s) end diff --git a/lib/seek/samples/attribute_type_handlers/float_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/float_attribute_handler.rb similarity index 71% rename from lib/seek/samples/attribute_type_handlers/float_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/float_attribute_handler.rb index 190d415a2f..3c053b61be 100644 --- a/lib/seek/samples/attribute_type_handlers/float_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/float_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class FloatAttributeTypeHandler < BaseAttributeHandler + class FloatAttributeHandler < BaseAttributeHandler def test_value(value) Float(value) end diff --git a/lib/seek/samples/attribute_type_handlers/integer_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/integer_attribute_handler.rb similarity index 88% rename from lib/seek/samples/attribute_type_handlers/integer_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/integer_attribute_handler.rb index fa426d4c45..459cadee9d 100644 --- a/lib/seek/samples/attribute_type_handlers/integer_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/integer_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class IntegerAttributeTypeHandler < BaseAttributeHandler + class IntegerAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not an integer' unless Integer(value.to_f) # the to_f is to allow "1.0" type numbers raise 'Not an integer' unless (Float(value) % 1).zero? diff --git a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_handler.rb similarity index 67% rename from lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_handler.rb index 3443c18a68..2157c0b76c 100644 --- a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_handler.rb @@ -1,8 +1,8 @@ module Seek module Samples module AttributeTypeHandlers - class LinkedExtendedMetadataAttributeTypeHandler < BaseAttributeHandler - class MissingLinkedExtendedMetadataTypeException < AttributeHandlerException; end + class LinkedExtendedMetadataAttributeHandler < BaseAttributeHandler + class MissingLinkedExtendedMetadataException < AttributeHandlerException; end def test_value(value) fail 'Not a extended metadata' unless value.is_a?(Hash) @@ -18,7 +18,7 @@ def convert(value) def linked_extended_metadata_type linked_extended_metadata_type = attribute.linked_extended_metadata_type - raise MissingLinkedExtendedMetadataTypeException unless linked_extended_metadata_type + raise MissingLinkedExtendedMetadataException unless linked_extended_metadata_type linked_extended_metadata_type end diff --git a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_handler.rb similarity index 81% rename from lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_handler.rb index c38d6c7656..652f3ea0d1 100644 --- a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class LinkedExtendedMetadataMultiAttributeTypeHandler < LinkedExtendedMetadataAttributeTypeHandler + class LinkedExtendedMetadataMultiAttributeHandler < LinkedExtendedMetadataAttributeHandler def test_value(value) fail 'Not a extended metadata multi' unless value.is_a?(Array) diff --git a/lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_handler.rb similarity index 63% rename from lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_handler.rb index 8605269ea9..bb7d1ec542 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class SeekDataFileAttributeTypeHandler < SeekResourceAttributeTypeHandler + class SeekDataFileAttributeHandler < SeekResourceAttributeHandler def type DataFile end diff --git a/lib/seek/samples/attribute_type_handlers/seek_resource_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/seek_resource_attribute_handler.rb similarity index 91% rename from lib/seek/samples/attribute_type_handlers/seek_resource_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/seek_resource_attribute_handler.rb index 2ef4fba1e8..5d5bbc345c 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_resource_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/seek_resource_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class SeekResourceAttributeTypeHandler < BaseAttributeHandler + class SeekResourceAttributeHandler < BaseAttributeHandler def test_value(value) raise "Not a valid SEEK #{type.name.humanize} ID" unless value[:id].to_i.positive? end diff --git a/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_handler.rb similarity index 92% rename from lib/seek/samples/attribute_type_handlers/seek_sample_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/seek_sample_attribute_handler.rb index 89bc83f45f..870cda0f21 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class SeekSampleAttributeTypeHandler < SeekResourceAttributeTypeHandler + class SeekSampleAttributeHandler < SeekResourceAttributeHandler class MissingLinkedSampleTypeException < AttributeHandlerException; end def type diff --git a/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_handler.rb similarity index 93% rename from lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_handler.rb index f0f8a69716..436b1b26d9 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class SeekSampleMultiAttributeTypeHandler < SeekSampleAttributeTypeHandler + class SeekSampleMultiAttributeHandler < SeekSampleAttributeHandler class MissingLinkedSampleTypeException < AttributeHandlerException; end def type diff --git a/lib/seek/samples/attribute_type_handlers/seek_strain_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/seek_strain_attribute_handler.rb similarity index 63% rename from lib/seek/samples/attribute_type_handlers/seek_strain_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/seek_strain_attribute_handler.rb index 38b3b4a410..38311f6710 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_strain_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/seek_strain_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class SeekStrainAttributeTypeHandler < SeekResourceAttributeTypeHandler + class SeekStrainAttributeHandler < SeekResourceAttributeHandler def type Strain end diff --git a/lib/seek/samples/attribute_type_handlers/string_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/string_attribute_handler.rb similarity index 75% rename from lib/seek/samples/attribute_type_handlers/string_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/string_attribute_handler.rb index fbb7f34e05..92788b030c 100644 --- a/lib/seek/samples/attribute_type_handlers/string_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/string_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class StringAttributeTypeHandler < BaseAttributeHandler + class StringAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not a string' unless value.is_a?(String) end diff --git a/lib/seek/samples/attribute_type_handlers/text_attribute_type_handler.rb b/lib/seek/samples/attribute_type_handlers/text_attribute_handler.rb similarity index 57% rename from lib/seek/samples/attribute_type_handlers/text_attribute_type_handler.rb rename to lib/seek/samples/attribute_type_handlers/text_attribute_handler.rb index 5ed184b7a0..40b790ba83 100644 --- a/lib/seek/samples/attribute_type_handlers/text_attribute_type_handler.rb +++ b/lib/seek/samples/attribute_type_handlers/text_attribute_handler.rb @@ -1,7 +1,7 @@ module Seek module Samples module AttributeTypeHandlers - class TextAttributeTypeHandler < StringAttributeTypeHandler + class TextAttributeHandler < StringAttributeHandler end end end diff --git a/test/unit/samples/attribute_type_handler_factory_test.rb b/test/unit/samples/attribute_type_handler_factory_test.rb index 12344c6714..c45c4f1597 100644 --- a/test/unit/samples/attribute_type_handler_factory_test.rb +++ b/test/unit/samples/attribute_type_handler_factory_test.rb @@ -2,7 +2,7 @@ class AttributeTypeHandlerFactoryTest < ActiveSupport::TestCase def setup - @factory = Seek::Samples::AttributeTypeHandlers::AttributeTypeHandlerFactory.instance + @factory = Seek::Samples::AttributeTypeHandlers::AttributeHandlerFactory.instance end test 'handlers to for base type' do @@ -12,7 +12,7 @@ def setup types = Seek::Samples::BaseType::ALL_TYPES + Seek::Samples::BaseType::ALL_TYPES types.each do |type| attr.sample_attribute_type.base_type = type - expected = "Seek::Samples::AttributeTypeHandlers::#{type}AttributeTypeHandler".constantize + expected = "Seek::Samples::AttributeTypeHandlers::#{type}AttributeHandler".constantize assert_kind_of expected, @factory.for_base_type(attr), "Expected #{expected.name} for #{type}" end end @@ -21,7 +21,7 @@ def setup st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) attr.sample_attribute_type.base_type = 'fish' - e = assert_raise(Seek::Samples::AttributeTypeHandlers::UnrecognisedAttributeHandlerType) do + e = assert_raise(Seek::Samples::AttributeTypeHandlers::UnrecognisedAttributeHandler) do @factory.for_base_type(attr) end assert_equal "unrecognised attribute base type 'fish'", e.message diff --git a/test/unit/samples/boolean_attribute_type_handler_test.rb b/test/unit/samples/boolean_attribute_type_handler_test.rb index f5abf7629b..ad189634d7 100644 --- a/test/unit/samples/boolean_attribute_type_handler_test.rb +++ b/test/unit/samples/boolean_attribute_type_handler_test.rb @@ -3,7 +3,7 @@ class BooleanAttributeTypeHandlerTest < ActiveSupport::TestCase test 'blank?' do - handler = Seek::Samples::AttributeTypeHandlers::BooleanAttributeTypeHandler.new({}) + handler = Seek::Samples::AttributeTypeHandlers::BooleanAttributeHandler.new({}) assert handler.test_blank?(nil) assert handler.test_blank?('') diff --git a/test/unit/samples/cv_attribute_type_handler_test.rb b/test/unit/samples/cv_attribute_type_handler_test.rb index 77f2c682f4..19a97ce980 100644 --- a/test/unit/samples/cv_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_attribute_type_handler_test.rb @@ -5,7 +5,7 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase test 'test value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) handler.test_value('Granny Smith') assert_raises(RuntimeError) do @@ -16,7 +16,7 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase test 'validate value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, allow_cv_free_text: false, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) assert handler.validate_value?('Granny Smith') refute handler.validate_value?('Pear') end @@ -25,8 +25,8 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) assert_nil attr.sample_controlled_vocab - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) - assert_raises(Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler::MissingControlledVocabularyException) do + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) + assert_raises(Seek::Samples::AttributeTypeHandlers::CVAttributeHandler::MissingControlledVocabularyException) do assert handler.validate_value?('Granny Smith') end end @@ -34,7 +34,7 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase test 'bypass validation for controlled vocabs together with allow_cv_free_text' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, allow_cv_free_text: true, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeTypeHandler.new(attr) + handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) assert handler.validate_value?('Granny Smith') assert handler.validate_value?('custom value') end diff --git a/test/unit/samples/cv_list_attribute_type_handler_test.rb b/test/unit/samples/cv_list_attribute_type_handler_test.rb index aa538ef564..d4f40b5d26 100644 --- a/test/unit/samples/cv_list_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_list_attribute_type_handler_test.rb @@ -5,7 +5,7 @@ class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase test 'test value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new(attr) + handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler.new(attr) assert handler.test_value(['Granny Smith']) assert handler.test_value(['Granny Smith','Bramley']) end @@ -13,7 +13,7 @@ class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase test 'validate value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new(attr) + handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler.new(attr) assert handler.validate_value?(['Granny Smith','Bramley']) refute handler.validate_value?(['Peter']) refute handler.validate_value?('Granny Smith') @@ -24,8 +24,8 @@ class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) assert_nil attr.sample_controlled_vocab - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler.new(attr) - assert_raises(Seek::Samples::AttributeTypeHandlers::CVListAttributeTypeHandler::MissingControlledVocabularyException) do + handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler.new(attr) + assert_raises(Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler::MissingControlledVocabularyException) do assert handler.validate_value?(['Granny Smith']) end end diff --git a/test/unit/samples/seek_resource_attribute_type_handler_test.rb b/test/unit/samples/seek_resource_attribute_type_handler_test.rb index 3c6fbebca1..7af64c8575 100644 --- a/test/unit/samples/seek_resource_attribute_type_handler_test.rb +++ b/test/unit/samples/seek_resource_attribute_type_handler_test.rb @@ -3,7 +3,7 @@ class SeekResourceAttributeTypeHandlerTest < ActiveSupport::TestCase test 'blank?' do - handler = Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeTypeHandler.new({}) + handler = Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeHandler.new({}) assert handler.test_blank?(nil) assert handler.test_blank?('') From 082d620d56aa80f291b5c8fa136b8dbd3ad95b03 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 27 Nov 2023 13:37:23 +0000 Subject: [PATCH 247/383] renamed AttributeTypeHandlers namespace #1648 --- lib/seek/json_metadata/attribute.rb | 4 ++-- .../attribute_handler_factory.rb | 4 ++-- .../base_attribute_handler.rb | 2 +- .../boolean_attribute_handler.rb | 2 +- .../cv_attribute_handler.rb | 2 +- .../cv_list_attribute_handler.rb | 2 +- .../date_attribute_handler.rb | 2 +- .../date_time_attribute_handler.rb | 2 +- .../float_attribute_handler.rb | 2 +- .../integer_attribute_handler.rb | 2 +- .../linked_extended_metadata_attribute_handler.rb | 2 +- ...linked_extended_metadata_multi_attribute_handler.rb | 2 +- .../seek_data_file_attribute_handler.rb | 2 +- .../seek_resource_attribute_handler.rb | 2 +- .../seek_sample_attribute_handler.rb | 2 +- .../seek_sample_multi_attribute_handler.rb | 2 +- .../seek_strain_attribute_handler.rb | 2 +- .../string_attribute_handler.rb | 2 +- .../text_attribute_handler.rb | 2 +- .../samples/attribute_type_handler_factory_test.rb | 6 +++--- .../samples/boolean_attribute_type_handler_test.rb | 2 +- test/unit/samples/cv_attribute_type_handler_test.rb | 10 +++++----- .../samples/cv_list_attribute_type_handler_test.rb | 8 ++++---- .../seek_resource_attribute_type_handler_test.rb | 2 +- 24 files changed, 35 insertions(+), 35 deletions(-) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/attribute_handler_factory.rb (73%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/base_attribute_handler.rb (95%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/boolean_attribute_handler.rb (95%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/cv_attribute_handler.rb (95%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/cv_list_attribute_handler.rb (94%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/date_attribute_handler.rb (86%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/date_time_attribute_handler.rb (87%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/float_attribute_handler.rb (84%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/integer_attribute_handler.rb (93%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/linked_extended_metadata_attribute_handler.rb (95%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/linked_extended_metadata_multi_attribute_handler.rb (94%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/seek_data_file_attribute_handler.rb (84%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/seek_resource_attribute_handler.rb (96%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/seek_sample_attribute_handler.rb (96%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/seek_sample_multi_attribute_handler.rb (97%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/seek_strain_attribute_handler.rb (83%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/string_attribute_handler.rb (86%) rename lib/seek/samples/{attribute_type_handlers => attribute_handlers}/text_attribute_handler.rb (77%) diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index aaa6dd644b..c2d4a9ead8 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -47,11 +47,11 @@ def resolve(value) end def seek_resource? - base_type_handler.is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeHandler) + base_type_handler.is_a?(Seek::Samples::AttributeHandlers::SeekResourceAttributeHandler) end def base_type_handler - Seek::Samples::AttributeTypeHandlers::AttributeHandlerFactory.instance.for_base_type(self) + Seek::Samples::AttributeHandlers::AttributeHandlerFactory.instance.for_base_type(self) end private diff --git a/lib/seek/samples/attribute_type_handlers/attribute_handler_factory.rb b/lib/seek/samples/attribute_handlers/attribute_handler_factory.rb similarity index 73% rename from lib/seek/samples/attribute_type_handlers/attribute_handler_factory.rb rename to lib/seek/samples/attribute_handlers/attribute_handler_factory.rb index fecee5b098..42ddfa8788 100644 --- a/lib/seek/samples/attribute_type_handlers/attribute_handler_factory.rb +++ b/lib/seek/samples/attribute_handlers/attribute_handler_factory.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class AttributeHandlerFactory include Singleton @@ -11,7 +11,7 @@ def initialize end def for_base_type(attribute) - "Seek::Samples::AttributeTypeHandlers::#{attribute.sample_attribute_type.base_type}AttributeHandler".constantize.new(attribute) + "Seek::Samples::AttributeHandlers::#{attribute.sample_attribute_type.base_type}AttributeHandler".constantize.new(attribute) rescue NameError raise UnrecognisedAttributeHandler, "unrecognised attribute base type '#{attribute.sample_attribute_type.base_type}'" end diff --git a/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb b/lib/seek/samples/attribute_handlers/base_attribute_handler.rb similarity index 95% rename from lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/base_attribute_handler.rb index a59fc24ece..10b3af475b 100644 --- a/lib/seek/samples/attribute_type_handlers/base_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/base_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class AttributeHandlerException < RuntimeError; end class BaseAttributeHandler diff --git a/lib/seek/samples/attribute_type_handlers/boolean_attribute_handler.rb b/lib/seek/samples/attribute_handlers/boolean_attribute_handler.rb similarity index 95% rename from lib/seek/samples/attribute_type_handlers/boolean_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/boolean_attribute_handler.rb index 3d647d0f7b..57b3d20bbd 100644 --- a/lib/seek/samples/attribute_type_handlers/boolean_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/boolean_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class BooleanAttributeHandler < BaseAttributeHandler def initialize(attribute) super(attribute) diff --git a/lib/seek/samples/attribute_type_handlers/cv_attribute_handler.rb b/lib/seek/samples/attribute_handlers/cv_attribute_handler.rb similarity index 95% rename from lib/seek/samples/attribute_type_handlers/cv_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/cv_attribute_handler.rb index 8586fbccfe..59a582b6f0 100644 --- a/lib/seek/samples/attribute_type_handlers/cv_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/cv_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class CVAttributeHandler < BaseAttributeHandler class MissingControlledVocabularyException < AttributeHandlerException; end diff --git a/lib/seek/samples/attribute_type_handlers/cv_list_attribute_handler.rb b/lib/seek/samples/attribute_handlers/cv_list_attribute_handler.rb similarity index 94% rename from lib/seek/samples/attribute_type_handlers/cv_list_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/cv_list_attribute_handler.rb index 588a805815..dcb4ce18e1 100644 --- a/lib/seek/samples/attribute_type_handlers/cv_list_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/cv_list_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class CVListAttributeHandler < CVAttributeHandler def test_value(array_value) array_value.each do |value| diff --git a/lib/seek/samples/attribute_type_handlers/date_attribute_handler.rb b/lib/seek/samples/attribute_handlers/date_attribute_handler.rb similarity index 86% rename from lib/seek/samples/attribute_type_handlers/date_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/date_attribute_handler.rb index 32e3982d58..a05b2cd282 100644 --- a/lib/seek/samples/attribute_type_handlers/date_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/date_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class DateAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not a date time' unless Date.parse(value.to_s) diff --git a/lib/seek/samples/attribute_type_handlers/date_time_attribute_handler.rb b/lib/seek/samples/attribute_handlers/date_time_attribute_handler.rb similarity index 87% rename from lib/seek/samples/attribute_type_handlers/date_time_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/date_time_attribute_handler.rb index 77a2729de4..3ede7b40b5 100644 --- a/lib/seek/samples/attribute_type_handlers/date_time_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/date_time_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class DateTimeAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not a date time' unless DateTime.parse(value.to_s) diff --git a/lib/seek/samples/attribute_type_handlers/float_attribute_handler.rb b/lib/seek/samples/attribute_handlers/float_attribute_handler.rb similarity index 84% rename from lib/seek/samples/attribute_type_handlers/float_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/float_attribute_handler.rb index 3c053b61be..bc0eb97495 100644 --- a/lib/seek/samples/attribute_type_handlers/float_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/float_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class FloatAttributeHandler < BaseAttributeHandler def test_value(value) Float(value) diff --git a/lib/seek/samples/attribute_type_handlers/integer_attribute_handler.rb b/lib/seek/samples/attribute_handlers/integer_attribute_handler.rb similarity index 93% rename from lib/seek/samples/attribute_type_handlers/integer_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/integer_attribute_handler.rb index 459cadee9d..a500ab106e 100644 --- a/lib/seek/samples/attribute_type_handlers/integer_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/integer_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class IntegerAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not an integer' unless Integer(value.to_f) # the to_f is to allow "1.0" type numbers diff --git a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_handler.rb b/lib/seek/samples/attribute_handlers/linked_extended_metadata_attribute_handler.rb similarity index 95% rename from lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/linked_extended_metadata_attribute_handler.rb index 2157c0b76c..6e3cc4804c 100644 --- a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/linked_extended_metadata_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class LinkedExtendedMetadataAttributeHandler < BaseAttributeHandler class MissingLinkedExtendedMetadataException < AttributeHandlerException; end diff --git a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_handler.rb b/lib/seek/samples/attribute_handlers/linked_extended_metadata_multi_attribute_handler.rb similarity index 94% rename from lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/linked_extended_metadata_multi_attribute_handler.rb index 652f3ea0d1..edf397ae3a 100644 --- a/lib/seek/samples/attribute_type_handlers/linked_extended_metadata_multi_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/linked_extended_metadata_multi_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class LinkedExtendedMetadataMultiAttributeHandler < LinkedExtendedMetadataAttributeHandler def test_value(value) diff --git a/lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_handler.rb b/lib/seek/samples/attribute_handlers/seek_data_file_attribute_handler.rb similarity index 84% rename from lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/seek_data_file_attribute_handler.rb index bb7d1ec542..9567d8f2a4 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_data_file_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/seek_data_file_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class SeekDataFileAttributeHandler < SeekResourceAttributeHandler def type DataFile diff --git a/lib/seek/samples/attribute_type_handlers/seek_resource_attribute_handler.rb b/lib/seek/samples/attribute_handlers/seek_resource_attribute_handler.rb similarity index 96% rename from lib/seek/samples/attribute_type_handlers/seek_resource_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/seek_resource_attribute_handler.rb index 5d5bbc345c..11b29a5d46 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_resource_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/seek_resource_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class SeekResourceAttributeHandler < BaseAttributeHandler def test_value(value) raise "Not a valid SEEK #{type.name.humanize} ID" unless value[:id].to_i.positive? diff --git a/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_handler.rb b/lib/seek/samples/attribute_handlers/seek_sample_attribute_handler.rb similarity index 96% rename from lib/seek/samples/attribute_type_handlers/seek_sample_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/seek_sample_attribute_handler.rb index 870cda0f21..be0c52b11a 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_sample_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/seek_sample_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class SeekSampleAttributeHandler < SeekResourceAttributeHandler class MissingLinkedSampleTypeException < AttributeHandlerException; end diff --git a/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_handler.rb b/lib/seek/samples/attribute_handlers/seek_sample_multi_attribute_handler.rb similarity index 97% rename from lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/seek_sample_multi_attribute_handler.rb index 436b1b26d9..df624b9ff6 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_sample_multi_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/seek_sample_multi_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class SeekSampleMultiAttributeHandler < SeekSampleAttributeHandler class MissingLinkedSampleTypeException < AttributeHandlerException; end diff --git a/lib/seek/samples/attribute_type_handlers/seek_strain_attribute_handler.rb b/lib/seek/samples/attribute_handlers/seek_strain_attribute_handler.rb similarity index 83% rename from lib/seek/samples/attribute_type_handlers/seek_strain_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/seek_strain_attribute_handler.rb index 38311f6710..958a4608dd 100644 --- a/lib/seek/samples/attribute_type_handlers/seek_strain_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/seek_strain_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class SeekStrainAttributeHandler < SeekResourceAttributeHandler def type Strain diff --git a/lib/seek/samples/attribute_type_handlers/string_attribute_handler.rb b/lib/seek/samples/attribute_handlers/string_attribute_handler.rb similarity index 86% rename from lib/seek/samples/attribute_type_handlers/string_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/string_attribute_handler.rb index 92788b030c..100d61ff52 100644 --- a/lib/seek/samples/attribute_type_handlers/string_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/string_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class StringAttributeHandler < BaseAttributeHandler def test_value(value) raise 'Not a string' unless value.is_a?(String) diff --git a/lib/seek/samples/attribute_type_handlers/text_attribute_handler.rb b/lib/seek/samples/attribute_handlers/text_attribute_handler.rb similarity index 77% rename from lib/seek/samples/attribute_type_handlers/text_attribute_handler.rb rename to lib/seek/samples/attribute_handlers/text_attribute_handler.rb index 40b790ba83..21b852679e 100644 --- a/lib/seek/samples/attribute_type_handlers/text_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/text_attribute_handler.rb @@ -1,6 +1,6 @@ module Seek module Samples - module AttributeTypeHandlers + module AttributeHandlers class TextAttributeHandler < StringAttributeHandler end end diff --git a/test/unit/samples/attribute_type_handler_factory_test.rb b/test/unit/samples/attribute_type_handler_factory_test.rb index c45c4f1597..fe1217df9e 100644 --- a/test/unit/samples/attribute_type_handler_factory_test.rb +++ b/test/unit/samples/attribute_type_handler_factory_test.rb @@ -2,7 +2,7 @@ class AttributeTypeHandlerFactoryTest < ActiveSupport::TestCase def setup - @factory = Seek::Samples::AttributeTypeHandlers::AttributeHandlerFactory.instance + @factory = Seek::Samples::AttributeHandlers::AttributeHandlerFactory.instance end test 'handlers to for base type' do @@ -12,7 +12,7 @@ def setup types = Seek::Samples::BaseType::ALL_TYPES + Seek::Samples::BaseType::ALL_TYPES types.each do |type| attr.sample_attribute_type.base_type = type - expected = "Seek::Samples::AttributeTypeHandlers::#{type}AttributeHandler".constantize + expected = "Seek::Samples::AttributeHandlers::#{type}AttributeHandler".constantize assert_kind_of expected, @factory.for_base_type(attr), "Expected #{expected.name} for #{type}" end end @@ -21,7 +21,7 @@ def setup st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) attr.sample_attribute_type.base_type = 'fish' - e = assert_raise(Seek::Samples::AttributeTypeHandlers::UnrecognisedAttributeHandler) do + e = assert_raise(Seek::Samples::AttributeHandlers::UnrecognisedAttributeHandler) do @factory.for_base_type(attr) end assert_equal "unrecognised attribute base type 'fish'", e.message diff --git a/test/unit/samples/boolean_attribute_type_handler_test.rb b/test/unit/samples/boolean_attribute_type_handler_test.rb index ad189634d7..96a3616dcf 100644 --- a/test/unit/samples/boolean_attribute_type_handler_test.rb +++ b/test/unit/samples/boolean_attribute_type_handler_test.rb @@ -3,7 +3,7 @@ class BooleanAttributeTypeHandlerTest < ActiveSupport::TestCase test 'blank?' do - handler = Seek::Samples::AttributeTypeHandlers::BooleanAttributeHandler.new({}) + handler = Seek::Samples::AttributeHandlers::BooleanAttributeHandler.new({}) assert handler.test_blank?(nil) assert handler.test_blank?('') diff --git a/test/unit/samples/cv_attribute_type_handler_test.rb b/test/unit/samples/cv_attribute_type_handler_test.rb index 19a97ce980..ed5efe728e 100644 --- a/test/unit/samples/cv_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_attribute_type_handler_test.rb @@ -5,7 +5,7 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase test 'test value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) + handler = Seek::Samples::AttributeHandlers::CVAttributeHandler.new(attr) handler.test_value('Granny Smith') assert_raises(RuntimeError) do @@ -16,7 +16,7 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase test 'validate value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, allow_cv_free_text: false, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) + handler = Seek::Samples::AttributeHandlers::CVAttributeHandler.new(attr) assert handler.validate_value?('Granny Smith') refute handler.validate_value?('Pear') end @@ -25,8 +25,8 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) assert_nil attr.sample_controlled_vocab - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) - assert_raises(Seek::Samples::AttributeTypeHandlers::CVAttributeHandler::MissingControlledVocabularyException) do + handler = Seek::Samples::AttributeHandlers::CVAttributeHandler.new(attr) + assert_raises(Seek::Samples::AttributeHandlers::CVAttributeHandler::MissingControlledVocabularyException) do assert handler.validate_value?('Granny Smith') end end @@ -34,7 +34,7 @@ class CVAttributeTypeHandlerTest < ActiveSupport::TestCase test 'bypass validation for controlled vocabs together with allow_cv_free_text' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, allow_cv_free_text: true, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVAttributeHandler.new(attr) + handler = Seek::Samples::AttributeHandlers::CVAttributeHandler.new(attr) assert handler.validate_value?('Granny Smith') assert handler.validate_value?('custom value') end diff --git a/test/unit/samples/cv_list_attribute_type_handler_test.rb b/test/unit/samples/cv_list_attribute_type_handler_test.rb index d4f40b5d26..3f46becd65 100644 --- a/test/unit/samples/cv_list_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_list_attribute_type_handler_test.rb @@ -5,7 +5,7 @@ class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase test 'test value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler.new(attr) + handler = Seek::Samples::AttributeHandlers::CVListAttributeHandler.new(attr) assert handler.test_value(['Granny Smith']) assert handler.test_value(['Granny Smith','Bramley']) end @@ -13,7 +13,7 @@ class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase test 'validate value' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:apples_controlled_vocab_attribute, sample_type: st) - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler.new(attr) + handler = Seek::Samples::AttributeHandlers::CVListAttributeHandler.new(attr) assert handler.validate_value?(['Granny Smith','Bramley']) refute handler.validate_value?(['Peter']) refute handler.validate_value?('Granny Smith') @@ -24,8 +24,8 @@ class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) assert_nil attr.sample_controlled_vocab - handler = Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler.new(attr) - assert_raises(Seek::Samples::AttributeTypeHandlers::CVListAttributeHandler::MissingControlledVocabularyException) do + handler = Seek::Samples::AttributeHandlers::CVListAttributeHandler.new(attr) + assert_raises(Seek::Samples::AttributeHandlers::CVListAttributeHandler::MissingControlledVocabularyException) do assert handler.validate_value?(['Granny Smith']) end end diff --git a/test/unit/samples/seek_resource_attribute_type_handler_test.rb b/test/unit/samples/seek_resource_attribute_type_handler_test.rb index 7af64c8575..d7e1aca7ae 100644 --- a/test/unit/samples/seek_resource_attribute_type_handler_test.rb +++ b/test/unit/samples/seek_resource_attribute_type_handler_test.rb @@ -3,7 +3,7 @@ class SeekResourceAttributeTypeHandlerTest < ActiveSupport::TestCase test 'blank?' do - handler = Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeHandler.new({}) + handler = Seek::Samples::AttributeHandlers::SeekResourceAttributeHandler.new({}) assert handler.test_blank?(nil) assert handler.test_blank?('') From d52bd81076cf47ec0e6646eaaa3ed8caf302d4ce Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 27 Nov 2023 13:51:47 +0000 Subject: [PATCH 248/383] renamed AttributeTypeHandlers tests #1648 --- ...andler_factory_test.rb => attribute_handler_factory_test.rb} | 2 +- ...e_type_handler_test.rb => boolean_attribute_handler_test.rb} | 2 +- ...ribute_type_handler_test.rb => cv_attribute_handler_test.rb} | 2 +- ...e_type_handler_test.rb => cv_list_attribute_handler_test.rb} | 2 +- ..._handler_test.rb => seek_resource_attribute_handler_test.rb} | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename test/unit/samples/{attribute_type_handler_factory_test.rb => attribute_handler_factory_test.rb} (94%) rename test/unit/samples/{boolean_attribute_type_handler_test.rb => boolean_attribute_handler_test.rb} (81%) rename test/unit/samples/{cv_attribute_type_handler_test.rb => cv_attribute_handler_test.rb} (96%) rename test/unit/samples/{cv_list_attribute_type_handler_test.rb => cv_list_attribute_handler_test.rb} (95%) rename test/unit/samples/{seek_resource_attribute_type_handler_test.rb => seek_resource_attribute_handler_test.rb} (86%) diff --git a/test/unit/samples/attribute_type_handler_factory_test.rb b/test/unit/samples/attribute_handler_factory_test.rb similarity index 94% rename from test/unit/samples/attribute_type_handler_factory_test.rb rename to test/unit/samples/attribute_handler_factory_test.rb index fe1217df9e..2b74104a73 100644 --- a/test/unit/samples/attribute_type_handler_factory_test.rb +++ b/test/unit/samples/attribute_handler_factory_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class AttributeTypeHandlerFactoryTest < ActiveSupport::TestCase +class AttributeHandlerFactoryTest < ActiveSupport::TestCase def setup @factory = Seek::Samples::AttributeHandlers::AttributeHandlerFactory.instance end diff --git a/test/unit/samples/boolean_attribute_type_handler_test.rb b/test/unit/samples/boolean_attribute_handler_test.rb similarity index 81% rename from test/unit/samples/boolean_attribute_type_handler_test.rb rename to test/unit/samples/boolean_attribute_handler_test.rb index 96a3616dcf..26cbc59206 100644 --- a/test/unit/samples/boolean_attribute_type_handler_test.rb +++ b/test/unit/samples/boolean_attribute_handler_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class BooleanAttributeTypeHandlerTest < ActiveSupport::TestCase +class BooleanAttributeHandlerTest < ActiveSupport::TestCase test 'blank?' do handler = Seek::Samples::AttributeHandlers::BooleanAttributeHandler.new({}) diff --git a/test/unit/samples/cv_attribute_type_handler_test.rb b/test/unit/samples/cv_attribute_handler_test.rb similarity index 96% rename from test/unit/samples/cv_attribute_type_handler_test.rb rename to test/unit/samples/cv_attribute_handler_test.rb index ed5efe728e..5661207375 100644 --- a/test/unit/samples/cv_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_attribute_handler_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class CVAttributeTypeHandlerTest < ActiveSupport::TestCase +class CVAttributeHandlerTest < ActiveSupport::TestCase test 'test value' do st = FactoryBot.create(:simple_sample_type) diff --git a/test/unit/samples/cv_list_attribute_type_handler_test.rb b/test/unit/samples/cv_list_attribute_handler_test.rb similarity index 95% rename from test/unit/samples/cv_list_attribute_type_handler_test.rb rename to test/unit/samples/cv_list_attribute_handler_test.rb index 3f46becd65..a444655487 100644 --- a/test/unit/samples/cv_list_attribute_type_handler_test.rb +++ b/test/unit/samples/cv_list_attribute_handler_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class CVListAttributeTypeHandlerTest < ActiveSupport::TestCase +class CVListAttributeHandlerTest < ActiveSupport::TestCase test 'test value' do st = FactoryBot.create(:simple_sample_type) diff --git a/test/unit/samples/seek_resource_attribute_type_handler_test.rb b/test/unit/samples/seek_resource_attribute_handler_test.rb similarity index 86% rename from test/unit/samples/seek_resource_attribute_type_handler_test.rb rename to test/unit/samples/seek_resource_attribute_handler_test.rb index d7e1aca7ae..1d90d59123 100644 --- a/test/unit/samples/seek_resource_attribute_type_handler_test.rb +++ b/test/unit/samples/seek_resource_attribute_handler_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class SeekResourceAttributeTypeHandlerTest < ActiveSupport::TestCase +class SeekResourceAttributeHandlerTest < ActiveSupport::TestCase test 'blank?' do handler = Seek::Samples::AttributeHandlers::SeekResourceAttributeHandler.new({}) From a9218e87b6e36e742de5e3671a07d9053f6d3094 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 28 Nov 2023 09:46:14 +0000 Subject: [PATCH 249/383] change factory for_base_type to for_attribute --- lib/seek/json_metadata/attribute.rb | 2 +- .../samples/attribute_handlers/attribute_handler_factory.rb | 2 +- test/unit/samples/attribute_handler_factory_test.rb | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index c2d4a9ead8..d6954c231e 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -51,7 +51,7 @@ def seek_resource? end def base_type_handler - Seek::Samples::AttributeHandlers::AttributeHandlerFactory.instance.for_base_type(self) + Seek::Samples::AttributeHandlers::AttributeHandlerFactory.instance.for_attribute(self) end private diff --git a/lib/seek/samples/attribute_handlers/attribute_handler_factory.rb b/lib/seek/samples/attribute_handlers/attribute_handler_factory.rb index 42ddfa8788..98ee2b4318 100644 --- a/lib/seek/samples/attribute_handlers/attribute_handler_factory.rb +++ b/lib/seek/samples/attribute_handlers/attribute_handler_factory.rb @@ -10,7 +10,7 @@ def initialize @handlers = {} end - def for_base_type(attribute) + def for_attribute(attribute) "Seek::Samples::AttributeHandlers::#{attribute.sample_attribute_type.base_type}AttributeHandler".constantize.new(attribute) rescue NameError raise UnrecognisedAttributeHandler, "unrecognised attribute base type '#{attribute.sample_attribute_type.base_type}'" diff --git a/test/unit/samples/attribute_handler_factory_test.rb b/test/unit/samples/attribute_handler_factory_test.rb index 2b74104a73..666b1e0c36 100644 --- a/test/unit/samples/attribute_handler_factory_test.rb +++ b/test/unit/samples/attribute_handler_factory_test.rb @@ -5,7 +5,7 @@ def setup @factory = Seek::Samples::AttributeHandlers::AttributeHandlerFactory.instance end - test 'handlers to for base type' do + test 'handlers to for attribute' do st = FactoryBot.create(:simple_sample_type) attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) # they are repeated twice to check for any caching issues @@ -13,7 +13,7 @@ def setup types.each do |type| attr.sample_attribute_type.base_type = type expected = "Seek::Samples::AttributeHandlers::#{type}AttributeHandler".constantize - assert_kind_of expected, @factory.for_base_type(attr), "Expected #{expected.name} for #{type}" + assert_kind_of expected, @factory.for_attribute(attr), "Expected #{expected.name} for #{type}" end end @@ -22,7 +22,7 @@ def setup attr = FactoryBot.create(:simple_string_sample_attribute, sample_type: st) attr.sample_attribute_type.base_type = 'fish' e = assert_raise(Seek::Samples::AttributeHandlers::UnrecognisedAttributeHandler) do - @factory.for_base_type(attr) + @factory.for_attribute(attr) end assert_equal "unrecognised attribute base type 'fish'", e.message end From f82ea51d33c06f1b1595f4ca4cedba793a162da8 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 28 Nov 2023 17:08:37 +0100 Subject: [PATCH 250/383] PR comments --- app/views/isa_assays/_assay_samples.html.erb | 6 +----- app/views/isa_assays/_assay_table.html.erb | 6 +----- app/views/isa_studies/_source_material.html.erb | 6 +----- app/views/isa_studies/_study_samples.html.erb | 6 +----- app/views/isa_studies/_study_table.html.erb | 6 +----- app/views/single_pages/download_samples_excel.axlsx | 2 +- 6 files changed, 6 insertions(+), 26 deletions(-) diff --git a/app/views/isa_assays/_assay_samples.html.erb b/app/views/isa_assays/_assay_samples.html.erb index 86bfe5e249..a9478ab376 100644 --- a/app/views/isa_assays/_assay_samples.html.erb +++ b/app/views/isa_assays/_assay_samples.html.erb @@ -1,8 +1,4 @@ -<% -=begin%> - The dynamic table where users can interact with -<% -=end%> +<%= # The dynamic table where users can interact with %> <% assay ||= nil diff --git a/app/views/isa_assays/_assay_table.html.erb b/app/views/isa_assays/_assay_table.html.erb index 7d87cec1ad..892a400d50 100644 --- a/app/views/isa_assays/_assay_table.html.erb +++ b/app/views/isa_assays/_assay_table.html.erb @@ -1,8 +1,4 @@ -<% -=begin%> - The read-only table in experiments overview -<% -=end%> +<% # The read-only table in experiments overview %> <% assay ||= nil %> <% valid_assay = assay&.sample_type.present? %> diff --git a/app/views/isa_studies/_source_material.html.erb b/app/views/isa_studies/_source_material.html.erb index ffeb169e49..8b9dd2426d 100644 --- a/app/views/isa_studies/_source_material.html.erb +++ b/app/views/isa_studies/_source_material.html.erb @@ -1,8 +1,4 @@ -<% -=begin%> - Dynamic table with the source samples where users interact with sources -<% -=end%> +<% # Dynamic table with the source samples where users interact with sources %> <% study ||= nil diff --git a/app/views/isa_studies/_study_samples.html.erb b/app/views/isa_studies/_study_samples.html.erb index 14c8f780b7..1d624860d5 100644 --- a/app/views/isa_studies/_study_samples.html.erb +++ b/app/views/isa_studies/_study_samples.html.erb @@ -1,8 +1,4 @@ -<% -=begin%> - Dynamic table of the Study samples -<% -=end%> +<% # Dynamic table of the Study samples %> <% study ||= nil diff --git a/app/views/isa_studies/_study_table.html.erb b/app/views/isa_studies/_study_table.html.erb index 4776365c9c..7c359806d3 100644 --- a/app/views/isa_studies/_study_table.html.erb +++ b/app/views/isa_studies/_study_table.html.erb @@ -1,8 +1,4 @@ -<% -=begin%> - The study experiment overview table -<% -=end%> +<% # The study experiment overview table %> <% study ||= nil %> <% valid_study = study&.sample_types&.second %> diff --git a/app/views/single_pages/download_samples_excel.axlsx b/app/views/single_pages/download_samples_excel.axlsx index f61f80dd80..76d266b90d 100644 --- a/app/views/single_pages/download_samples_excel.axlsx +++ b/app/views/single_pages/download_samples_excel.axlsx @@ -2,7 +2,7 @@ require 'caxlsx' require 'uuid' ##################################### -debug = true +debug = false ##################################### if debug From 0119def96d875a7ae5cc8a2ff4c25df02df90162 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 28 Nov 2023 17:26:47 +0100 Subject: [PATCH 251/383] Fixing comments --- app/views/isa_assays/_assay_samples.html.erb | 2 +- app/views/isa_assays/_assay_table.html.erb | 2 +- app/views/isa_studies/_source_material.html.erb | 2 +- app/views/isa_studies/_study_samples.html.erb | 2 +- app/views/isa_studies/_study_table.html.erb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/isa_assays/_assay_samples.html.erb b/app/views/isa_assays/_assay_samples.html.erb index a9478ab376..059b5a299f 100644 --- a/app/views/isa_assays/_assay_samples.html.erb +++ b/app/views/isa_assays/_assay_samples.html.erb @@ -1,4 +1,4 @@ -<%= # The dynamic table where users can interact with %> +<%# The dynamic table where users can interact with %> <% assay ||= nil diff --git a/app/views/isa_assays/_assay_table.html.erb b/app/views/isa_assays/_assay_table.html.erb index 892a400d50..a15e4d0233 100644 --- a/app/views/isa_assays/_assay_table.html.erb +++ b/app/views/isa_assays/_assay_table.html.erb @@ -1,4 +1,4 @@ -<% # The read-only table in experiments overview %> +<%# The read-only table in experiments overview %> <% assay ||= nil %> <% valid_assay = assay&.sample_type.present? %> diff --git a/app/views/isa_studies/_source_material.html.erb b/app/views/isa_studies/_source_material.html.erb index 8b9dd2426d..ee8b844ed7 100644 --- a/app/views/isa_studies/_source_material.html.erb +++ b/app/views/isa_studies/_source_material.html.erb @@ -1,4 +1,4 @@ -<% # Dynamic table with the source samples where users interact with sources %> +<%# Dynamic table with the source samples where users interact with sources %> <% study ||= nil diff --git a/app/views/isa_studies/_study_samples.html.erb b/app/views/isa_studies/_study_samples.html.erb index 1d624860d5..9f00405548 100644 --- a/app/views/isa_studies/_study_samples.html.erb +++ b/app/views/isa_studies/_study_samples.html.erb @@ -1,4 +1,4 @@ -<% # Dynamic table of the Study samples %> +<%# Dynamic table of the Study samples %> <% study ||= nil diff --git a/app/views/isa_studies/_study_table.html.erb b/app/views/isa_studies/_study_table.html.erb index 7c359806d3..89084dce9e 100644 --- a/app/views/isa_studies/_study_table.html.erb +++ b/app/views/isa_studies/_study_table.html.erb @@ -1,4 +1,4 @@ -<% # The study experiment overview table %> +<%# The study experiment overview table %> <% study ||= nil %> <% valid_study = study&.sample_types&.second %> From 78e55543901a924de0a6ea320ab5b9f2efe12321 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 28 Nov 2023 20:57:30 +0100 Subject: [PATCH 252/383] Move the instanceName to the single pages show view --- app/assets/javascripts/single_page/dynamic_table.js.erb | 1 - app/views/single_pages/show.html.erb | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index 94e33bd247..7bf238efcb 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -1,5 +1,4 @@ <% environment.context_class.instance_eval { include Seek::Util.routes } %> - const instanceName = "<%= Seek::Config.instance_name %>"; const rowStatus = { new: "new", diff --git a/app/views/single_pages/show.html.erb b/app/views/single_pages/show.html.erb index 10a1dd58a3..a0871b3550 100644 --- a/app/views/single_pages/show.html.erb +++ b/app/views/single_pages/show.html.erb @@ -67,6 +67,7 @@ diff --git a/app/views/isa_studies/_study_samples.html.erb b/app/views/isa_studies/_study_samples.html.erb index 9f00405548..299ec76edd 100644 --- a/app/views/isa_studies/_study_samples.html.erb +++ b/app/views/isa_studies/_study_samples.html.erb @@ -18,7 +18,7 @@ <% if sample_type %> From 36d8ad6d65ded83987977b234d19488ea8cdcc21 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 15:25:39 +0100 Subject: [PATCH 287/383] assays and studies should only be considered ISA-compliant if they have sample_types linked to them --- app/models/assay.rb | 2 +- app/models/study.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index ffd1d8f85b..14ecc15948 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -95,7 +95,7 @@ def state_allows_delete?(*args) end def is_ISA_JSON_compliant - investigation.is_ISA_JSON_compliant + investigation.is_ISA_JSON_compliant && !sample_type.nil? end # returns true if this is a modelling class of assay diff --git a/app/models/study.rb b/app/models/study.rb index 2a34052fff..0f0a0c691e 100644 --- a/app/models/study.rb +++ b/app/models/study.rb @@ -54,7 +54,7 @@ def associated_samples_through_sample_type end def is_ISA_JSON_compliant - investigation.is_ISA_JSON_compliant + investigation.is_ISA_JSON_compliant && sample_types.any? end def clone_with_associations From c887fc9764c18eaa00b2155785d4640080a8e26a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 17:29:14 +0100 Subject: [PATCH 288/383] Only allow to link deafult studies to default investigations --- app/views/studies/_investigation_list.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/studies/_investigation_list.html.erb b/app/views/studies/_investigation_list.html.erb index 21acfc63ca..8aef6f854f 100644 --- a/app/views/studies/_investigation_list.html.erb +++ b/app/views/studies/_investigation_list.html.erb @@ -2,7 +2,7 @@ study ||= @study study.investigation = nil unless study.investigation.try(:can_edit?) investigations = investigations | [study.investigation] if study.investigation - investigations = investigations.select &:can_edit? + investigations = investigations.select { |inv| inv.can_edit? && !inv.is_ISA_JSON_compliant } investigation_value=(study && study.investigation) ? study.investigation.id : 0 investigation_options = [["Select an #{t('investigation')}..",0]]|investigations.collect{|t| [h(t.title),t.id]} %> @@ -16,4 +16,4 @@ name: "study[investigation_id]", class: 'form-control' } --%> \ No newline at end of file +-%> From de35420420cb704b0ec22958e86e1c425c71618e Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 17:29:31 +0100 Subject: [PATCH 289/383] Only allow to link deafult assays to default studies --- app/helpers/assays_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/assays_helper.rb b/app/helpers/assays_helper.rb index 197dc08efb..cebe7e52da 100644 --- a/app/helpers/assays_helper.rb +++ b/app/helpers/assays_helper.rb @@ -90,7 +90,7 @@ def grouped_options_for_study_selection(current_study) # returns a map of the studies that can be selected, grouped by investigation # this includes the editable studies, plus the current associated study if it is not already included (i.e not edtiable) def selectable_studies_mapped_to_investigation(current_study) - studies = Study.authorized_for('edit').to_a + studies = Study.authorized_for('edit').to_a.select{ |study| !study.is_ISA_JSON_compliant } studies << current_study if current_study && !current_study.can_edit? investigation_map = {} studies.each do |study| From bc851afbc622cbe6233ca588ee382f24ab95b059 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 17:56:45 +0100 Subject: [PATCH 290/383] Add validation to the ISA exporter --- lib/isa_exporter.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index b984a3c5a0..6c3d81c932 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -8,6 +8,7 @@ def initialize(investigation, user) end def convert_investigation + raise "Only ISA-JSON compliant investigations can be exported to an ISA-JSON" unless @investigation.is_ISA_JSON_compliant isa_investigation = {} isa_investigation[:identifier] = '' # @investigation.id isa_investigation[:title] = @investigation.title @@ -36,6 +37,11 @@ def convert_investigation isa_investigation[:people] = people studies = [] + + unless @investigation.studies.all?(&:is_ISA_JSON_compliant) + raise "All studies in investigation '#{investigation.title}' should be ISA-JSON compliant" + end + @investigation.studies.each { |s| studies << convert_study(s) if s.can_view?(@current_user) } isa_investigation[:studies] = studies @@ -123,6 +129,11 @@ def convert_study(study) isa_study[:protocols] = protocols isa_study[:processSequence] = convert_process_sequence(study.sample_types.second, study.sops.map(&:id).join("_"), study.id) + + unless study.assays.all?(&:is_ISA_JSON_compliant) + raise "All assays in study `#{study.title}` should be ISA-JSON compliant." + end + assay_streams = study.assays.map { |assay| [assay] if assay&.position&.zero? } .compact .map do |assay_stream| From 4e8fba204e49b7c24359b67dfedf73b21cb33a7c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 17:57:02 +0100 Subject: [PATCH 291/383] Fix ISA exporter tests --- test/functional/investigations_controller_test.rb | 2 +- test/integration/isa_exporter_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/investigations_controller_test.rb b/test/functional/investigations_controller_test.rb index 5e690f545b..545f06f098 100644 --- a/test/functional/investigations_controller_test.rb +++ b/test/functional/investigations_controller_test.rb @@ -854,7 +854,7 @@ def test_title project = FactoryBot.create(:project) current_user.person.add_to_project_and_institution(project, current_user.person.institutions.first) other_user.person.add_to_project_and_institution(project, current_user.person.institutions.first) - investigation = FactoryBot.create(:investigation, projects: [project], contributor: current_user.person) + investigation = FactoryBot.create(:investigation, projects: [project], contributor: current_user.person, is_ISA_JSON_compliant: true) source_sample_type = FactoryBot.create(:isa_source_sample_type, template_id: FactoryBot.create(:isa_source_template).id) sample_collection_sample_type = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: source_sample_type, template_id: FactoryBot.create(:isa_sample_collection_template).id) diff --git a/test/integration/isa_exporter_test.rb b/test/integration/isa_exporter_test.rb index 07cb1d8809..4520da58d1 100644 --- a/test/integration/isa_exporter_test.rb +++ b/test/integration/isa_exporter_test.rb @@ -23,7 +23,7 @@ def before_all post '/session', params: { login: 'test', password: generate_user_password } @current_user = User.current_user @current_user.person.add_to_project_and_institution(@project, @current_user.person.institutions.first) - @investigation = FactoryBot.create(:investigation, projects: [@project], contributor: @current_user.person) + @investigation = FactoryBot.create(:investigation, projects: [@project], contributor: @current_user.person, is_ISA_JSON_compliant: true) isa_project_vars = create_basic_isa_project with_config_value(:project_single_page_enabled, true) do get export_isa_investigation_path(@investigation.id) From a9478e49aa4e8c34617ef7a554618552b430a121 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Sat, 16 Dec 2023 12:43:32 +0000 Subject: [PATCH 292/383] Fix user_content_actions overwriting --- app/controllers/application_controller.rb | 10 +++++++--- test/functional/workflows_controller_test.rb | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1c673d16ce..ef4e728293 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -129,11 +129,15 @@ def controller_model helper_method :controller_model def self.api_actions(*actions) - @api_actions ||= (superclass.respond_to?(:api_actions) ? superclass.api_actions.dup : []) + actions.map(&:to_sym) + @api_actions ||= [] + @api_actions |= actions.map(&:to_sym) + @api_actions end def self.user_content_actions(*actions) - @user_content_actions ||= (superclass.respond_to?(:user_content_actions) ? superclass.user_content_actions.dup : []) + actions.map(&:to_sym) + @user_content_actions ||= [] + @user_content_actions |= actions.map(&:to_sym) + @user_content_actions end private @@ -628,7 +632,7 @@ def recursive_determine_extended_metadata_keys(metadata_type) # Stop hosted user content from running scripts etc. def secure_user_content - if self.class.user_content_actions.include?(action_name.to_sym) + if self.class._user_content_actions.include?(action_name.to_sym) response.set_header('Content-Security-Policy', USER_CONTENT_CSP) end end diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index c354ced870..f6a6290391 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -463,6 +463,7 @@ def setup assert_response :success assert_equal 'image/svg+xml', response.headers['Content-Type'] + assert_equal ApplicationController::USER_CONTENT_CSP, @response.header['Content-Security-Policy'] assert wf.diagram_exists? end From 5359ce06b3364986bffce5488cecbe85f531f7c5 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 18 Dec 2023 09:23:43 +0000 Subject: [PATCH 293/383] SVG panning/zooming TODO: Fix CSP corrupting SVG --- app/assets/javascripts/application.js | 1 + .../javascripts/application_shared.js.erb | 13 +++++++++++ app/assets/stylesheets/styles.scss | 8 ++++++- app/views/workflows/show.html.erb | 8 ++++++- lib/seek/content_type_detection.rb | 4 ++++ lib/seek/renderers/renderer_factory.rb | 3 ++- lib/seek/renderers/svg_renderer.rb | 14 +++++++++++ .../javascripts/svg-pan-zoom-3.6.1/LICENSE | 23 +++++++++++++++++++ .../svg-pan-zoom-3.6.1/svg-pan-zoom.min.js | 3 +++ 9 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 lib/seek/renderers/svg_renderer.rb create mode 100644 vendor/assets/javascripts/svg-pan-zoom-3.6.1/LICENSE create mode 100644 vendor/assets/javascripts/svg-pan-zoom-3.6.1/svg-pan-zoom.min.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index eeb6c760ef..8eba4d1870 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -76,3 +76,4 @@ //= require jquery.splitter/jquery.splitter.min //= require select2.full.min //= require licenses +//= require svg-pan-zoom-3.6.1/svg-pan-zoom.min diff --git a/app/assets/javascripts/application_shared.js.erb b/app/assets/javascripts/application_shared.js.erb index 3b8c59fe21..8cc0bf939b 100644 --- a/app/assets/javascripts/application_shared.js.erb +++ b/app/assets/javascripts/application_shared.js.erb @@ -401,6 +401,19 @@ $j(document).ready(function () { $j(document).on('click', '.sidebar-backdrop', Sidebar.toggle); Licenses.init(); + + $j('.svg-pan-zoom').each(function () { + // Does not seem to work using jQuery load event, so doing it vanilla way + this.addEventListener('load', function () { + svgPanZoom(this, { + zoomEnabled: true, + dblClickZoomEnabled: false, + controlIconsEnabled: true, + fit: true + }); + }); + }); }); + var URL_ROOT = '<%= Rails.application.config.relative_url_root %>'; diff --git a/app/assets/stylesheets/styles.scss b/app/assets/stylesheets/styles.scss index 760d49ae4d..6026c47d18 100644 --- a/app/assets/stylesheets/styles.scss +++ b/app/assets/stylesheets/styles.scss @@ -793,4 +793,10 @@ div#super_tag_cloud { .lifemonitor-logo { height: 16px; } -} \ No newline at end of file +} + +.svg-pan-zoom { + width: 100%; + height: 100%; + border: 1px solid #ddd; +} diff --git a/app/views/workflows/show.html.erb b/app/views/workflows/show.html.erb index 39ef357533..bf0b5dd9a2 100644 --- a/app/views/workflows/show.html.erb +++ b/app/views/workflows/show.html.erb @@ -32,12 +32,18 @@
    - <%= image_tag(diagram_workflow_path(@workflow, version: @display_workflow.version)) %> + <% diagram_path = diagram_workflow_path(@workflow, version: @display_workflow.version) %> + <% if @display_workflow&.diagram.extension == 'svg' %> + <%= content_tag(:embed, '', type: 'image/svg+xml', src: diagram_path, class: 'svg-pan-zoom') %> + <% else %> + <%= image_tag(diagram_path) %> + <% end %>
    <% end %> <% rescue StandardError => e %> + <% raise e if Rails.env.development? %> <% Rails.logger.error(e.inspect) %> <% Rails.logger.error(e.backtrace.join("\n")) %>
    Could not render the workflow diagram.
    diff --git a/lib/seek/content_type_detection.rb b/lib/seek/content_type_detection.rb index 6364f02d6c..a43abbd45b 100644 --- a/lib/seek/content_type_detection.rb +++ b/lib/seek/content_type_detection.rb @@ -138,6 +138,10 @@ def is_jupyter_notebook?(blob = self) blob.content_type_file_extensions.include?('ipynb') end + def is_svg?(blob = self) + blob.content_type_file_extensions.include?('svg') + end + def unknown_file_type?(blob = self) blob.human_content_type == 'Unknown file type' end diff --git a/lib/seek/renderers/renderer_factory.rb b/lib/seek/renderers/renderer_factory.rb index 784117e257..66bf829981 100644 --- a/lib/seek/renderers/renderer_factory.rb +++ b/lib/seek/renderers/renderer_factory.rb @@ -23,7 +23,8 @@ def detect_renderer(blob) # Ordered list of Renderer classes. More generic renderers appear last. def renderer_instances - [SlideshareRenderer, YoutubeRenderer, MarkdownRenderer, NotebookRenderer, TextRenderer, PdfRenderer, ImageRenderer, BlankRenderer] + [SlideshareRenderer, YoutubeRenderer, MarkdownRenderer, NotebookRenderer, SvgRenderer, TextRenderer, PdfRenderer, + ImageRenderer, BlankRenderer] end end end diff --git a/lib/seek/renderers/svg_renderer.rb b/lib/seek/renderers/svg_renderer.rb new file mode 100644 index 0000000000..8bcf9848ec --- /dev/null +++ b/lib/seek/renderers/svg_renderer.rb @@ -0,0 +1,14 @@ +module Seek + module Renderers + class SvgRenderer < ImageRenderer + def can_render? + blob.is_svg? + end + + def render_content + path = blob.content_path + content_tag(:embed, '', type: 'image/svg+xml', src: path, class: 'svg-pan-zoom') + end + end + end +end diff --git a/vendor/assets/javascripts/svg-pan-zoom-3.6.1/LICENSE b/vendor/assets/javascripts/svg-pan-zoom-3.6.1/LICENSE new file mode 100644 index 0000000000..b414171560 --- /dev/null +++ b/vendor/assets/javascripts/svg-pan-zoom-3.6.1/LICENSE @@ -0,0 +1,23 @@ +Copyright 2009-2010 Andrea Leofreddi +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/assets/javascripts/svg-pan-zoom-3.6.1/svg-pan-zoom.min.js b/vendor/assets/javascripts/svg-pan-zoom-3.6.1/svg-pan-zoom.min.js new file mode 100644 index 0000000000..4904d12d3f --- /dev/null +++ b/vendor/assets/javascripts/svg-pan-zoom-3.6.1/svg-pan-zoom.min.js @@ -0,0 +1,3 @@ +// svg-pan-zoom v3.6.1 +// https://github.com/ariutta/svg-pan-zoom +!function s(r,a,l){function u(e,t){if(!a[e]){if(!r[e]){var o="function"==typeof require&&require;if(!t&&o)return o(e,!0);if(h)return h(e,!0);var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}var i=a[e]={exports:{}};r[e][0].call(i.exports,function(t){return u(r[e][1][t]||t)},i,i.exports,s,r,a,l)}return a[e].exports}for(var h="function"==typeof require&&require,t=0;tthis.options.maxZoom*n.zoom&&(t=this.options.maxZoom*n.zoom/this.getZoom());var i=this.viewport.getCTM(),s=e.matrixTransform(i.inverse()),r=this.svg.createSVGMatrix().translate(s.x,s.y).scale(t).translate(-s.x,-s.y),a=i.multiply(r);a.a!==i.a&&this.viewport.setCTM(a)},i.prototype.zoom=function(t,e){this.zoomAtPoint(t,a.getSvgCenterPoint(this.svg,this.width,this.height),e)},i.prototype.publicZoom=function(t,e){e&&(t=this.computeFromRelativeZoom(t)),this.zoom(t,e)},i.prototype.publicZoomAtPoint=function(t,e,o){if(o&&(t=this.computeFromRelativeZoom(t)),"SVGPoint"!==r.getType(e)){if(!("x"in e&&"y"in e))throw new Error("Given point is invalid");e=a.createSVGPoint(this.svg,e.x,e.y)}this.zoomAtPoint(t,e,o)},i.prototype.getZoom=function(){return this.viewport.getZoom()},i.prototype.getRelativeZoom=function(){return this.viewport.getRelativeZoom()},i.prototype.computeFromRelativeZoom=function(t){return t*this.viewport.getOriginalState().zoom},i.prototype.resetZoom=function(){var t=this.viewport.getOriginalState();this.zoom(t.zoom,!0)},i.prototype.resetPan=function(){this.pan(this.viewport.getOriginalState())},i.prototype.reset=function(){this.resetZoom(),this.resetPan()},i.prototype.handleDblClick=function(t){var e;if((this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),this.options.controlIconsEnabled)&&-1<(t.target.getAttribute("class")||"").indexOf("svg-pan-zoom-control"))return!1;e=t.shiftKey?1/(2*(1+this.options.zoomScaleSensitivity)):2*(1+this.options.zoomScaleSensitivity);var o=a.getEventPoint(t,this.svg).matrixTransform(this.svg.getScreenCTM().inverse());this.zoomAtPoint(e,o)},i.prototype.handleMouseDown=function(t,e){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),r.mouseAndTouchNormalize(t,this.svg),this.options.dblClickZoomEnabled&&r.isDblClick(t,e)?this.handleDblClick(t):(this.state="pan",this.firstEventCTM=this.viewport.getCTM(),this.stateOrigin=a.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()))},i.prototype.handleMouseMove=function(t){if(this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&this.options.panEnabled){var e=a.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()),o=this.firstEventCTM.translate(e.x-this.stateOrigin.x,e.y-this.stateOrigin.y);this.viewport.setCTM(o)}},i.prototype.handleMouseUp=function(t){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&(this.state="none")},i.prototype.fit=function(){var t=this.viewport.getViewBox(),e=Math.min(this.width/t.width,this.height/t.height);this.zoom(e,!0)},i.prototype.contain=function(){var t=this.viewport.getViewBox(),e=Math.max(this.width/t.width,this.height/t.height);this.zoom(e,!0)},i.prototype.center=function(){var t=this.viewport.getViewBox(),e=.5*(this.width-(t.width+2*t.x)*this.getZoom()),o=.5*(this.height-(t.height+2*t.y)*this.getZoom());this.getPublicInstance().pan({x:e,y:o})},i.prototype.updateBBox=function(){this.viewport.simpleViewBoxCache()},i.prototype.pan=function(t){var e=this.viewport.getCTM();e.e=t.x,e.f=t.y,this.viewport.setCTM(e)},i.prototype.panBy=function(t){var e=this.viewport.getCTM();e.e+=t.x,e.f+=t.y,this.viewport.setCTM(e)},i.prototype.getPan=function(){var t=this.viewport.getState();return{x:t.x,y:t.y}},i.prototype.resize=function(){var t=a.getBoundingClientRectNormalized(this.svg);this.width=t.width,this.height=t.height;var e=this.viewport;e.options.width=this.width,e.options.height=this.height,e.processCTM(),this.options.controlIconsEnabled&&(this.getPublicInstance().disableControlIcons(),this.getPublicInstance().enableControlIcons())},i.prototype.destroy=function(){var e=this;for(var t in this.beforeZoom=null,this.onZoom=null,this.beforePan=null,this.onPan=null,(this.onUpdatedCTM=null)!=this.options.customEventsHandler&&this.options.customEventsHandler.destroy({svgElement:this.svg,eventsListenerElement:this.options.eventsListenerElement,instance:this.getPublicInstance()}),this.eventListeners)(this.options.eventsListenerElement||this.svg).removeEventListener(t,this.eventListeners[t],!this.options.preventMouseEventsDefault&&h);this.disableMouseWheelZoom(),this.getPublicInstance().disableControlIcons(),this.reset(),c=c.filter(function(t){return t.svg!==e.svg}),delete this.options,delete this.viewport,delete this.publicInstance,delete this.pi,this.getPublicInstance=function(){return null}},i.prototype.getPublicInstance=function(){var o=this;return this.publicInstance||(this.publicInstance=this.pi={enablePan:function(){return o.options.panEnabled=!0,o.pi},disablePan:function(){return o.options.panEnabled=!1,o.pi},isPanEnabled:function(){return!!o.options.panEnabled},pan:function(t){return o.pan(t),o.pi},panBy:function(t){return o.panBy(t),o.pi},getPan:function(){return o.getPan()},setBeforePan:function(t){return o.options.beforePan=null===t?null:r.proxy(t,o.publicInstance),o.pi},setOnPan:function(t){return o.options.onPan=null===t?null:r.proxy(t,o.publicInstance),o.pi},enableZoom:function(){return o.options.zoomEnabled=!0,o.pi},disableZoom:function(){return o.options.zoomEnabled=!1,o.pi},isZoomEnabled:function(){return!!o.options.zoomEnabled},enableControlIcons:function(){return o.options.controlIconsEnabled||(o.options.controlIconsEnabled=!0,s.enable(o)),o.pi},disableControlIcons:function(){return o.options.controlIconsEnabled&&(o.options.controlIconsEnabled=!1,s.disable(o)),o.pi},isControlIconsEnabled:function(){return!!o.options.controlIconsEnabled},enableDblClickZoom:function(){return o.options.dblClickZoomEnabled=!0,o.pi},disableDblClickZoom:function(){return o.options.dblClickZoomEnabled=!1,o.pi},isDblClickZoomEnabled:function(){return!!o.options.dblClickZoomEnabled},enableMouseWheelZoom:function(){return o.enableMouseWheelZoom(),o.pi},disableMouseWheelZoom:function(){return o.disableMouseWheelZoom(),o.pi},isMouseWheelZoomEnabled:function(){return!!o.options.mouseWheelZoomEnabled},setZoomScaleSensitivity:function(t){return o.options.zoomScaleSensitivity=t,o.pi},setMinZoom:function(t){return o.options.minZoom=t,o.pi},setMaxZoom:function(t){return o.options.maxZoom=t,o.pi},setBeforeZoom:function(t){return o.options.beforeZoom=null===t?null:r.proxy(t,o.publicInstance),o.pi},setOnZoom:function(t){return o.options.onZoom=null===t?null:r.proxy(t,o.publicInstance),o.pi},zoom:function(t){return o.publicZoom(t,!0),o.pi},zoomBy:function(t){return o.publicZoom(t,!1),o.pi},zoomAtPoint:function(t,e){return o.publicZoomAtPoint(t,e,!0),o.pi},zoomAtPointBy:function(t,e){return o.publicZoomAtPoint(t,e,!1),o.pi},zoomIn:function(){return this.zoomBy(1+o.options.zoomScaleSensitivity),o.pi},zoomOut:function(){return this.zoomBy(1/(1+o.options.zoomScaleSensitivity)),o.pi},getZoom:function(){return o.getRelativeZoom()},setOnUpdatedCTM:function(t){return o.options.onUpdatedCTM=null===t?null:r.proxy(t,o.publicInstance),o.pi},resetZoom:function(){return o.resetZoom(),o.pi},resetPan:function(){return o.resetPan(),o.pi},reset:function(){return o.reset(),o.pi},fit:function(){return o.fit(),o.pi},contain:function(){return o.contain(),o.pi},center:function(){return o.center(),o.pi},updateBBox:function(){return o.updateBBox(),o.pi},resize:function(){return o.resize(),o.pi},getSizes:function(){return{width:o.width,height:o.height,realZoom:o.getZoom(),viewBox:o.viewport.getViewBox()}},destroy:function(){return o.destroy(),o.pi}}),this.publicInstance};var c=[];e.exports=function(t,e){var o=r.getSvg(t);if(null===o)return null;for(var n=c.length-1;0<=n;n--)if(c[n].svg===o)return c[n].instance.getPublicInstance();return c.push({svg:o,instance:new i(o,e)}),c[c.length-1].instance.getPublicInstance()}},{"./control-icons":1,"./shadow-viewport":2,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(t,e,o){var l=t("./utilities"),s="unknown";document.documentMode&&(s="ie"),e.exports={svgNS:"http://www.w3.org/2000/svg",xmlNS:"http://www.w3.org/XML/1998/namespace",xmlnsNS:"http://www.w3.org/2000/xmlns/",xlinkNS:"http://www.w3.org/1999/xlink",evNS:"http://www.w3.org/2001/xml-events",getBoundingClientRectNormalized:function(t){if(t.clientWidth&&t.clientHeight)return{width:t.clientWidth,height:t.clientHeight};if(t.getBoundingClientRect())return t.getBoundingClientRect();throw new Error("Cannot get BoundingClientRect for SVG.")},getOrCreateViewport:function(t,e){var o=null;if(!(o=l.isElement(e)?e:t.querySelector(e))){var n=Array.prototype.slice.call(t.childNodes||t.children).filter(function(t){return"defs"!==t.nodeName&&"#text"!==t.nodeName});1===n.length&&"g"===n[0].nodeName&&null===n[0].getAttribute("transform")&&(o=n[0])}if(!o){var i="viewport-"+(new Date).toISOString().replace(/\D/g,"");(o=document.createElementNS(this.svgNS,"g")).setAttribute("id",i);var s=t.childNodes||t.children;if(s&&0 Date: Mon, 18 Dec 2023 10:27:27 +0100 Subject: [PATCH 294/383] Add isa-json compliance validation to Sample Type model --- app/models/sample_type.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/sample_type.rb b/app/models/sample_type.rb index b2f4ece0da..01523e171d 100644 --- a/app/models/sample_type.rb +++ b/app/models/sample_type.rb @@ -60,6 +60,10 @@ class SampleType < ApplicationRecord has_annotation_type :sample_type_tag, method_name: :tags + def is_isa_json_compliant? + studies.any? || assays.any? + end + def validate_value?(attribute_name, value) attribute = sample_attributes.detect { |attr| attr.title == attribute_name } raise UnknownAttributeException, "Unknown attribute #{attribute_name}" unless attribute From 9c74321d908bffb1b7cc94f5ad08126a40881f7d Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 18 Dec 2023 10:41:25 +0100 Subject: [PATCH 295/383] Write tests for determining ISA-JSON compliant sample types --- test/unit/sample_type_test.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/unit/sample_type_test.rb b/test/unit/sample_type_test.rb index 4fc68f2e78..69e73420dc 100644 --- a/test/unit/sample_type_test.rb +++ b/test/unit/sample_type_test.rb @@ -1096,6 +1096,28 @@ def setup assert sample_type.errors.added?(:'sample_attributes.unit', 'cannot be changed (weight)') end + test 'determin whether sample types are considered ISA-JSON compliant' do + assay_sample_type = FactoryBot.create(:patient_sample_type) + refute assay_sample_type.is_isa_json_compliant? + + FactoryBot.create(:assay, sample_type: assay_sample_type) + assert assay_sample_type.is_isa_json_compliant? + + source_sample_type = FactoryBot.create(:patient_sample_type) + sample_collection_sample_type= FactoryBot.create(:patient_sample_type) + + [source_sample_type, sample_collection_sample_type].each do |st| + refute st.is_isa_json_compliant? + end + + FactoryBot.create(:study, sample_types: [source_sample_type, sample_collection_sample_type]) + + [source_sample_type, sample_collection_sample_type].each do |st| + assert st.is_isa_json_compliant? + end + + end + private # sample type with 3 samples From 80b24dc912ea4cc0070e6b229502da92d684232a Mon Sep 17 00:00:00 2001 From: Xiaoming Hu Date: Mon, 18 Dec 2023 16:14:10 +0100 Subject: [PATCH 296/383] Implementing select2 for sharing permission project list: enhance searchability and ease of selection in lengthy lists #1702 --- app/views/sharing/_group_permission_modal.html.erb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/views/sharing/_group_permission_modal.html.erb b/app/views/sharing/_group_permission_modal.html.erb index 3477e34638..818e14183d 100644 --- a/app/views/sharing/_group_permission_modal.html.erb +++ b/app/views/sharing/_group_permission_modal.html.erb @@ -5,8 +5,14 @@ <%= modal_body do %>
    - <%= select_tag('permission-project-id', projects_grouped_by_programme, name: nil, - prompt: "any #{t('project')}", class: 'form-control', autocomplete: 'off') -%> + <% + opts = {} + opts[:select_options] = projects_grouped_by_programme + opts[:multiple] = false + %> + + <%= objects_input('permission-project-id', [], opts) %> +
    From 7905e2759c5506ef74395b48e6db7c30bd65807b Mon Sep 17 00:00:00 2001 From: Xiaoming Hu Date: Mon, 18 Dec 2023 16:16:05 +0100 Subject: [PATCH 297/383] Ensure that the 'membering of' and 'working at' fields are consistently reset to empty values whenever a user intends to share the permission for the second time. --- app/views/sharing/_group_permission_modal.html.erb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/sharing/_group_permission_modal.html.erb b/app/views/sharing/_group_permission_modal.html.erb index 818e14183d..e31c09fb6f 100644 --- a/app/views/sharing/_group_permission_modal.html.erb +++ b/app/views/sharing/_group_permission_modal.html.erb @@ -40,8 +40,9 @@ // Display project/institution/workgroup modal $j('#add-project-permission-button').click(function () { - $j('#add-project-permission-modal').modal('show'); - + $j('#permission-institution-id').val(''); + $j('#permission-project-id').val('').trigger('change'); + $j('#add-project-permission-modal').modal('show'); return false; }); From c7a030b8a6c68679be8a42004c9e13b113fdddaa Mon Sep 17 00:00:00 2001 From: Sonja Mathias Date: Tue, 19 Dec 2023 10:26:32 +0100 Subject: [PATCH 298/383] Change column and method name to adhere to ruby style convention --- app/controllers/investigations_controller.rb | 2 +- app/helpers/assays_helper.rb | 2 +- app/models/assay.rb | 4 ++-- app/models/study.rb | 4 ++-- app/views/assays/_buttons.html.erb | 6 +++--- app/views/assays/show.html.erb | 4 ++-- app/views/general/_show_page_tab_definitions.html.erb | 2 +- app/views/investigations/_buttons.html.erb | 6 +++--- app/views/investigations/_form.html.erb | 4 ++-- app/views/investigations/show.html.erb | 2 +- app/views/studies/_buttons.html.erb | 6 +++--- app/views/studies/_investigation_list.html.erb | 2 +- app/views/studies/show.html.erb | 6 +++--- ...20231218113249_change_column_name_isajson_compliance.rb | 7 +++++++ db/schema.rb | 4 ++-- lib/isa_exporter.rb | 6 +++--- test/functional/investigations_controller_test.rb | 2 +- test/integration/isa_exporter_test.rb | 2 +- 18 files changed, 39 insertions(+), 32 deletions(-) create mode 100644 db/migrate/20231218113249_change_column_name_isajson_compliance.rb diff --git a/app/controllers/investigations_controller.rb b/app/controllers/investigations_controller.rb index 91274ea2d5..2d42e2e150 100644 --- a/app/controllers/investigations_controller.rb +++ b/app/controllers/investigations_controller.rb @@ -150,7 +150,7 @@ def update def investigation_params params.require(:investigation).permit(:title, :description, { project_ids: [] }, *creator_related_params, :position, { publication_ids: [] }, - :is_ISA_JSON_compliant, + :is_isa_json_compliant, { discussion_links_attributes:[:id, :url, :label, :_destroy] }, { extended_metadata_attributes: determine_extended_metadata_keys }) end diff --git a/app/helpers/assays_helper.rb b/app/helpers/assays_helper.rb index cebe7e52da..5c713eced1 100644 --- a/app/helpers/assays_helper.rb +++ b/app/helpers/assays_helper.rb @@ -90,7 +90,7 @@ def grouped_options_for_study_selection(current_study) # returns a map of the studies that can be selected, grouped by investigation # this includes the editable studies, plus the current associated study if it is not already included (i.e not edtiable) def selectable_studies_mapped_to_investigation(current_study) - studies = Study.authorized_for('edit').to_a.select{ |study| !study.is_ISA_JSON_compliant } + studies = Study.authorized_for('edit').to_a.select{ |study| !study.is_isa_json_compliant? } studies << current_study if current_study && !current_study.can_edit? investigation_map = {} studies.each do |study| diff --git a/app/models/assay.rb b/app/models/assay.rb index 14ecc15948..e2fe2430fa 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -94,8 +94,8 @@ def state_allows_delete?(*args) assets.empty? && publications.empty? && associated_samples_through_sample_type.empty? && super end - def is_ISA_JSON_compliant - investigation.is_ISA_JSON_compliant && !sample_type.nil? + def is_isa_json_compliant? + investigation.is_isa_json_compliant? && !sample_type.nil? end # returns true if this is a modelling class of assay diff --git a/app/models/study.rb b/app/models/study.rb index 0f0a0c691e..f7b6371c73 100644 --- a/app/models/study.rb +++ b/app/models/study.rb @@ -53,8 +53,8 @@ def associated_samples_through_sample_type st_samples end - def is_ISA_JSON_compliant - investigation.is_ISA_JSON_compliant && sample_types.any? + def is_isa_json_compliant? + investigation.is_isa_json_compliant? && sample_types.any? end def clone_with_associations diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 5e4e494ebc..ac5b58e309 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -14,13 +14,13 @@ <% end -%> <% if logged_in_and_member? %> - <% unless item.is_ISA_JSON_compliant%> + <% unless item.is_isa_json_compliant?%> <%= button_link_to("New #{assay_word} based on this one", 'new', new_object_based_on_existing_one_assay_path(item, :controller_name => "assays")) %> <% end %> <% end %> <% if item.can_edit? %> - <% if Seek::Config.isa_json_compliance_enabled && item.is_ISA_JSON_compliant %> + <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <% valid_study = item&.study&.sample_types.present? %> <% valid_assay = item&.sample_type.present? %> <% if valid_study && valid_assay %> @@ -37,7 +37,7 @@ <%= item_actions_dropdown do %> <% if item.can_edit? %> - <% if Seek::Config.isa_json_compliance_enabled && item.is_ISA_JSON_compliant %> + <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %>
  • <%= image_tag_for_key('edit', edit_isa_assay_path(item, source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page]), "Edit #{t('isa_assay')}", nil, "Edit #{t('isa_assay')}") -%>
  • <% else %>
  • <%= image_tag_for_key('edit', edit_assay_path(item), "Edit #{assay_word}", nil, "Edit #{assay_word}") -%>
  • diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 5d876d5977..7ca44d4f26 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -65,7 +65,7 @@ <% if Seek::Config.isa_json_compliance_enabled %>

    <%= "Is ISA-JSON compliant" %>: - <%= @assay.is_ISA_JSON_compliant %> + <%= @assay.is_isa_json_compliant? %>

    <%= render partial: 'isa_studies/applied_templates', locals: { resource: @assay } -%> <% end %> @@ -134,7 +134,7 @@ <%= tab_pane('related-items') do %> <%= render partial: 'general/items_related_to', object: @assay %> <% end %> - <% if Seek::Config.isa_json_compliance_enabled && @assay.is_ISA_JSON_compliant %> + <% if Seek::Config.isa_json_compliance_enabled && @assay.is_isa_json_compliant? %> <%= tab_pane('assay_design') do %> <%= render :partial=>"isa_assays/assay_design", locals: { assay: @assay} -%> <% end %> diff --git a/app/views/general/_show_page_tab_definitions.html.erb b/app/views/general/_show_page_tab_definitions.html.erb index 62fcbe2cf2..5ea7c76dc3 100644 --- a/app/views/general/_show_page_tab_definitions.html.erb +++ b/app/views/general/_show_page_tab_definitions.html.erb @@ -18,7 +18,7 @@ <% end %> <% if resource %> - <% if Seek::Config.isa_json_compliance_enabled && resource.is_ISA_JSON_compliant %> + <% if Seek::Config.isa_json_compliance_enabled && resource.is_isa_json_compliant? %> <%= tab(resource_name&.downcase + "_design") do %> <%= resource.model_name.human %> design <% end %> diff --git a/app/views/investigations/_buttons.html.erb b/app/views/investigations/_buttons.html.erb index 6d9ecde7ea..2b260a9965 100644 --- a/app/views/investigations/_buttons.html.erb +++ b/app/views/investigations/_buttons.html.erb @@ -13,21 +13,21 @@ <% end -%> <% if logged_in_and_member? %> - <% unless item.is_ISA_JSON_compliant %> + <% unless item.is_isa_json_compliant? %> <%= button_link_to("New #{t('investigation')} based on this one", 'new', new_object_based_on_existing_one_investigation_path(item, :controller_name => "investigations")) %> <% end %> <% end %> <% if item.can_edit? -%> - <% if Seek::Config.isa_json_compliance_enabled && item.is_ISA_JSON_compliant %> + <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <%= button_link_to("Design #{t('study')}", 'new', new_isa_study_path(investigation_id: item, single_page: params[:single_page])) %> <% else -%> <%= add_new_item_to_dropdown(item) %> <% end -%> <% end -%> -<% if item.can_view? && Seek::Config.isa_json_compliance_enabled && item.is_ISA_JSON_compliant%> +<% if item.can_view? && Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant?%> <%= button_link_to("Export ISA", 'download', export_isa_investigation_path(item)) %> <% end %> diff --git a/app/views/investigations/_form.html.erb b/app/views/investigations/_form.html.erb index 7fc0690d4d..693c10f80a 100644 --- a/app/views/investigations/_form.html.erb +++ b/app/views/investigations/_form.html.erb @@ -24,9 +24,9 @@
    diff --git a/app/views/investigations/show.html.erb b/app/views/investigations/show.html.erb index 5f94379f0f..2a20f868f8 100644 --- a/app/views/investigations/show.html.erb +++ b/app/views/investigations/show.html.erb @@ -21,7 +21,7 @@ <% if Seek::Config.isa_json_compliance_enabled %>

    <%= "Is ISA-JSON compliant" %>: - <%= @investigation.is_ISA_JSON_compliant %> + <%= @investigation.is_isa_json_compliant? %>

    <% end %> <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: {resource: @investigation} %> diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index e0f5bb59f7..b27edc214f 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -13,13 +13,13 @@ <% end -%> <% if logged_in_and_member? %> - <% unless item.is_ISA_JSON_compliant%> + <% unless item.is_isa_json_compliant?%> <%= button_link_to("New #{t('study')} based on this one", 'new', new_object_based_on_existing_one_study_path(item, :controller_name => "studies")) %> <% end %> <% end %> <% if item.can_edit? -%> - <% if Seek::Config.isa_json_compliance_enabled && item.is_ISA_JSON_compliant %> + <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <% if item&.sample_types.present? %> <%= button_link_to("Design #{t('assay')}", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_source: true)) %> <% end -%> @@ -30,7 +30,7 @@ <%= item_actions_dropdown do %> <% if item.can_edit? %> - <% if Seek::Config.isa_json_compliance_enabled && item.is_ISA_JSON_compliant %> + <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %>
  • <%= image_tag_for_key('edit', edit_isa_study_path(item, single_page: params[:single_page]), "Edit #{t('isa_study')}", nil, "Edit #{t('isa_study')}") -%>
  • <% else %>
  • <%= image_tag_for_key('edit', edit_study_path(item), "Edit #{t('study')}", nil, "Edit #{t('study')}") -%>
  • diff --git a/app/views/studies/_investigation_list.html.erb b/app/views/studies/_investigation_list.html.erb index 8aef6f854f..c568131397 100644 --- a/app/views/studies/_investigation_list.html.erb +++ b/app/views/studies/_investigation_list.html.erb @@ -2,7 +2,7 @@ study ||= @study study.investigation = nil unless study.investigation.try(:can_edit?) investigations = investigations | [study.investigation] if study.investigation - investigations = investigations.select { |inv| inv.can_edit? && !inv.is_ISA_JSON_compliant } + investigations = investigations.select { |inv| inv.can_edit? && !inv.is_isa_json_compliant? } investigation_value=(study && study.investigation) ? study.investigation.id : 0 investigation_options = [["Select an #{t('investigation')}..",0]]|investigations.collect{|t| [h(t.title),t.id]} %> diff --git a/app/views/studies/show.html.erb b/app/views/studies/show.html.erb index 81d01e7cec..e4d19b66c2 100644 --- a/app/views/studies/show.html.erb +++ b/app/views/studies/show.html.erb @@ -36,7 +36,7 @@ <% if Seek::Config.isa_json_compliance_enabled %>

    <%= "Is ISA-JSON compliant" %>: - <%= @study.is_ISA_JSON_compliant %> + <%= @study.is_isa_json_compliant? %>

    <%= render partial: 'isa_studies/applied_templates', locals: { resource: @study } -%> <% end %> @@ -68,7 +68,7 @@ <%= render partial: 'general/items_related_to', object: @study %> <% end %> - <% if Seek::Config.isa_json_compliance_enabled && @study.is_ISA_JSON_compliant%> + <% if Seek::Config.isa_json_compliance_enabled && @study.is_isa_json_compliant?%> <%= tab_pane('study_design') do %> <%= render :partial=>"isa_studies/study_design", locals: { study: @study} -%> <% end %> @@ -76,7 +76,7 @@
    ')) + git_blob = @git.get_blob('test.html') + renderer = Seek::Renderers::TextRenderer.new(git_blob) + assert renderer.can_render? + assert_equal "
    <script>Danger!</script>
    ", renderer.render + + git_blob.rewind + assert_equal "", renderer.render_standalone # Content-Security-Policy will prevent execution in standalone + blob = FactoryBot.create(:csv_content_blob, asset: @asset) renderer = Seek::Renderers::TextRenderer.new(blob) assert renderer.can_render? From 81b592bb6a29c798b1b371c930b8bc04c664f9b9 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 19 Dec 2023 15:17:13 +0000 Subject: [PATCH 302/383] Render raw SVG blobs as an image rather than text --- app/controllers/git_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/git_controller.rb b/app/controllers/git_controller.rb index b7d5fa1954..9a283e78af 100644 --- a/app/controllers/git_controller.rb +++ b/app/controllers/git_controller.rb @@ -52,7 +52,7 @@ def blob def raw if render_display? render_display(@blob) - elsif @blob.binary? + elsif @blob.binary? || @blob.is_image? # SVG is an image but not binary send_data(@blob.content, filename: path_param.split('/').last, disposition: 'inline') else # Set Content-Type if it's an image to allow use in img tags From 0d8cb97d92dceffc107474cad522da3fab50b4bd Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 19 Dec 2023 15:18:04 +0000 Subject: [PATCH 303/383] Permit inline styles in SVG diagrams, or they look broken --- app/controllers/application_controller.rb | 3 ++- app/controllers/workflows_controller.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ef4e728293..b9c458fc5c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base USER_CONTENT_CSP = "default-src 'self'" + USER_SVG_CSP = "#{USER_CONTENT_CSP}; style-src 'unsafe-inline';" include Seek::Errors::ControllerErrorHandling include Seek::EnabledFeaturesFilter @@ -632,7 +633,7 @@ def recursive_determine_extended_metadata_keys(metadata_type) # Stop hosted user content from running scripts etc. def secure_user_content - if self.class._user_content_actions.include?(action_name.to_sym) + if self.class.user_content_actions.include?(action_name.to_sym) response.set_header('Content-Security-Policy', USER_CONTENT_CSP) end end diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 4be79be5eb..7eae9c01e4 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -18,7 +18,6 @@ class WorkflowsController < ApplicationController include Legacy::WorkflowSupport api_actions :index, :show, :create, :update, :destroy, :ro_crate, :create_version - user_content_actions :diagram rescue_from ROCrate::ReadException do |e| logger.error("Error whilst attempting to read RO-Crate metadata for #{@workflow&.id}.") @@ -249,6 +248,7 @@ def create_version_metadata def diagram @diagram = @display_workflow.diagram if @diagram + response.set_header('Content-Security-Policy', USER_SVG_CSP) send_file(@diagram.path, filename: @diagram.filename, type: @diagram.content_type, From c36765df1c20b14a7d6740c85dcc2a5a3344f5f3 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 19 Dec 2023 15:19:41 +0000 Subject: [PATCH 304/383] Tweaks to pannable SVG appearance. * Make the container reasonable sized * Use double-click to zoom rather than mousewheel * Some help text --- app/assets/javascripts/application_shared.js.erb | 4 +++- app/assets/stylesheets/styles.scss | 3 +-- app/views/workflows/show.html.erb | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/application_shared.js.erb b/app/assets/javascripts/application_shared.js.erb index 8cc0bf939b..b57495c9fb 100644 --- a/app/assets/javascripts/application_shared.js.erb +++ b/app/assets/javascripts/application_shared.js.erb @@ -407,8 +407,10 @@ $j(document).ready(function () { this.addEventListener('load', function () { svgPanZoom(this, { zoomEnabled: true, - dblClickZoomEnabled: false, + dblClickZoomEnabled: true, + mouseWheelZoomEnabled: false, controlIconsEnabled: true, + contain: false, fit: true }); }); diff --git a/app/assets/stylesheets/styles.scss b/app/assets/stylesheets/styles.scss index 6026c47d18..01fee460b6 100644 --- a/app/assets/stylesheets/styles.scss +++ b/app/assets/stylesheets/styles.scss @@ -796,7 +796,6 @@ div#super_tag_cloud { } .svg-pan-zoom { - width: 100%; - height: 100%; + max-width: 100%; border: 1px solid #ddd; } diff --git a/app/views/workflows/show.html.erb b/app/views/workflows/show.html.erb index bf0b5dd9a2..a7cd771cfb 100644 --- a/app/views/workflows/show.html.erb +++ b/app/views/workflows/show.html.erb @@ -34,7 +34,8 @@
    <% diagram_path = diagram_workflow_path(@workflow, version: @display_workflow.version) %> <% if @display_workflow&.diagram.extension == 'svg' %> - <%= content_tag(:embed, '', type: 'image/svg+xml', src: diagram_path, class: 'svg-pan-zoom') %> + <%= content_tag(:embed, '', type: 'image/svg+xml', src: diagram_path, class: 'svg-pan-zoom', width: 1000, height: 500) %> +

    Click and drag the diagram to pan, double click or use the controls to zoom.

    <% else %> <%= image_tag(diagram_path) %> <% end %> From aa59d540c32da669abb44b88492951b9e514ca3a Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 19 Dec 2023 16:02:18 +0000 Subject: [PATCH 305/383] Test fix --- test/functional/workflows_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index f6a6290391..ce332e0750 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -463,7 +463,7 @@ def setup assert_response :success assert_equal 'image/svg+xml', response.headers['Content-Type'] - assert_equal ApplicationController::USER_CONTENT_CSP, @response.header['Content-Security-Policy'] + assert_equal ApplicationController::USER_SVG_CSP, @response.header['Content-Security-Policy'] assert wf.diagram_exists? end From 273c4fd5c7e8e32dc8bdb9c06e66d003f34acff7 Mon Sep 17 00:00:00 2001 From: Xiaoming Hu Date: Tue, 19 Dec 2023 23:49:43 +0100 Subject: [PATCH 306/383] Extended Metadata unfolded at start #1696 add "expand all" and "collapse all" buttons --- .../stylesheets/linked_extended_metadata.css | 12 +++++++++ app/helpers/extended_metadata_helper.rb | 2 +- ...xtended_metadata_attribute_values.html.erb | 26 +++++++++++++++++-- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/linked_extended_metadata.css b/app/assets/stylesheets/linked_extended_metadata.css index 138061afb7..fee60b92ba 100644 --- a/app/assets/stylesheets/linked_extended_metadata.css +++ b/app/assets/stylesheets/linked_extended_metadata.css @@ -55,3 +55,15 @@ padding: 0px 0px; border-radius: 5px; } + +#extended-metadata .panel-heading-button { + display: flex; + justify-content: space-between; + align-items: center; +} + +.extended_metadata-small-btn{ + font-size: 1rem; + padding: 0.2rem 0.5rem; +} + diff --git a/app/helpers/extended_metadata_helper.rb b/app/helpers/extended_metadata_helper.rb index 7361f78253..46738abb76 100644 --- a/app/helpers/extended_metadata_helper.rb +++ b/app/helpers/extended_metadata_helper.rb @@ -38,7 +38,7 @@ def render_extended_metadata_value(attribute, resource) content_tag(:div, class: 'extended_metadata') do if attribute.linked_extended_metadata? || attribute.linked_extended_metadata_multi? content_tag(:span, class: 'linked_extended_metdata_display') do - folding_panel(attribute.label, true, id: attribute.title) do + folding_panel(attribute.label, false, id: attribute.title) do display_attribute(resource.extended_metadata, attribute, link: true) end end diff --git a/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb b/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb index 77de064f8f..f2cf20d264 100644 --- a/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb +++ b/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb @@ -4,7 +4,15 @@ <% if resource.respond_to?(:extended_metadata) && resource.extended_metadata %>
    -
    <%= label %> (<%= resource.extended_metadata.extended_metadata_type.title %>)
    +
    +
    + <%= label %> (<%= resource.extended_metadata.extended_metadata_type.title %>) +
    + +
    <% resource.extended_metadata.extended_metadata_attributes.each do |attribute| %> <%= render_extended_metadata_value(attribute, resource) %> @@ -12,4 +20,18 @@
    <% else %> -<% end %> \ No newline at end of file +<% end %> + + From 1f35b3d6e2101174161645f1f47a1707c16511db Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 20 Dec 2023 19:11:30 +0100 Subject: [PATCH 307/383] fix bug in isa_assays_form --- app/views/isa_assays/_form.html.erb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 895043488b..35d5b38d2c 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -4,12 +4,14 @@ input_sample_type_id = params[:isa_assay][:input_sample_type_id] if params.dig(:isa_assay, :input_sample_type_id) source_assay = Assay.find(params[:source_assay_id]) if params[:source_assay_id] -if !@isa_assay.assay.new_record? - assay_position = source_assay.position -elsif params[:source_assay_id] - assay_position = source_assay.position + 1 +if @isa_assay.assay.new_record? + if params[:source_assay_id] + assay_position = source_assay.position + 1 + else + assay_position = 0 + end else - assay_position = 0 + assay_position = @isa_assay.assay.position end # assay_position = params[:source_assay_id] ? source_assay.position + 1 : 0 From 5cf41b05962bf516c6513253d46596d23b30e29e Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 20 Dec 2023 19:13:11 +0100 Subject: [PATCH 308/383] Fix bug in template attributes schema --- lib/seek/isa_templates/template_attributes_schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/seek/isa_templates/template_attributes_schema.json b/lib/seek/isa_templates/template_attributes_schema.json index d544e6f08d..ceed37ba58 100644 --- a/lib/seek/isa_templates/template_attributes_schema.json +++ b/lib/seek/isa_templates/template_attributes_schema.json @@ -62,7 +62,7 @@ "Controlled Vocabulary", "URI", "Registered Data file", - "ENA collection date" + "ENA custom date" ] }, "CVList": { From 152c7bf9278204f4a60cffce5effaedd13b7cf22 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 3 Jan 2024 11:51:44 +0100 Subject: [PATCH 309/383] Add isa json compliance test to studies --- test/unit/study_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit/study_test.rb b/test/unit/study_test.rb index 593a12b45e..8fb1b43b2f 100644 --- a/test/unit/study_test.rb +++ b/test/unit/study_test.rb @@ -225,4 +225,16 @@ class StudyTest < ActiveSupport::TestCase assert_equal [assay1, assay2, assay3, assay4], related_items_hash['Assay'][:items] end + test 'isa json compliance' do + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) + study = FactoryBot.create(:study, investigation:) + + refute study.is_isa_json_compliant? + + source_st = FactoryBot.create(:isa_source_sample_type, studies: [study]) + sample_collection_st = FactoryBot.create(:isa_sample_collection_sample_type, studies: [study], linked_sample_type: source_st) + + assert study.is_isa_json_compliant? + end + end From 00f2d2e247e242e2a665998cf5009b4bd6bf3613 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 3 Jan 2024 13:29:01 +0100 Subject: [PATCH 310/383] Add ISA JSON compliant factories --- test/factories/assays.rb | 9 +++++++++ test/factories/studies.rb | 19 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/test/factories/assays.rb b/test/factories/assays.rb index 738f06723f..c0ab8efcf5 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -95,6 +95,15 @@ assets_creators { [AssetsCreator.new(affiliation: 'University of Somewhere', creator: FactoryBot.create(:person, first_name: 'Some', last_name: 'One'))] } end + factory(:isa_json_compliant_assay, parent: :assay) do + title { 'ISA JSON compliant assay' } + description { 'An assay linked to an ISA JSON compliant study and a sample type' } + after(:build) do |assay| + assay.study = FactoryBot.create(:isa_json_compliant_study) + assay.sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: assay.study.sample_types.last) + end + end + # AssayAsset factory :assay_asset do association :assay diff --git a/test/factories/studies.rb b/test/factories/studies.rb index 130bc49b5e..470ed950e3 100644 --- a/test/factories/studies.rb +++ b/test/factories/studies.rb @@ -7,16 +7,16 @@ s.investigation ||= FactoryBot.create(:investigation, contributor: s.contributor, policy: s.policy.try(:deep_copy)) end end - + factory(:public_study, parent: :study) do policy { FactoryBot.create(:public_policy) } investigation { FactoryBot.create(:public_investigation) } end - + factory(:study_with_assay, parent: :study) do assays { [FactoryBot.create(:assay)] } end - + factory(:min_study, class: Study) do title { "A Minimal Study" } association :contributor, factory: :person, strategy: :create @@ -24,7 +24,7 @@ s.investigation ||= FactoryBot.create(:min_investigation, contributor: s.contributor, policy: s.policy.try(:deep_copy)) end end - + factory(:max_study, parent: :min_study) do title { "A Maximal Study" } description { "The Study of many things" } @@ -36,4 +36,15 @@ end assets_creators { [AssetsCreator.new(affiliation: 'University of Somewhere', creator: FactoryBot.create(:person, first_name: 'Some', last_name: 'One'))] } end + + factory(:isa_json_compliant_study, parent: :study) do + title { 'ISA JSON compliant study' } + description { 'A study which is linked to an ISA JSON compliant investigation and has two sample types linked to it.' } + after(:build) do |study| + study.investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) + source_st = FactoryBot.create(:isa_source_sample_type) + sample_collection_st = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: source_st) + study.sample_types = [source_st, sample_collection_st] + end + end end From 1bfe95e74bc5d120894deb47a1956e5c31e43eb9 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 3 Jan 2024 13:29:18 +0100 Subject: [PATCH 311/383] Add assay ISA JSON compliance test --- test/unit/assay_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index 1a948b9a21..8101e54d66 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -751,5 +751,13 @@ def new_valid_assay assert item2.has_jerm_contributor? end + test 'isa json compliance' do + isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study) + assert study.is_isa_json_compliant? + default_assay = FactoryBot.create(:assay, study: isa_json_compliant_study) + refute default_assay.is_isa_json_compliant? + isa_json_compliant_assay = FactoryBot.create(:isa_json_compliant_assay) + assert isa_json_compliant_assay.is_isa_json_compliant? + end end From 5d16598c19ca0dbe4f27f2e88c1512459b4f1e71 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 3 Jan 2024 21:24:37 +0100 Subject: [PATCH 312/383] Fix typo --- test/unit/assay_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index 8101e54d66..a8a9199528 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -753,7 +753,7 @@ def new_valid_assay test 'isa json compliance' do isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study) - assert study.is_isa_json_compliant? + assert isa_json_compliant_study.is_isa_json_compliant? default_assay = FactoryBot.create(:assay, study: isa_json_compliant_study) refute default_assay.is_isa_json_compliant? From a78470d89298cec6c69696002efa9892cab7b6bb Mon Sep 17 00:00:00 2001 From: whomingbird Date: Thu, 4 Jan 2024 14:48:22 +0100 Subject: [PATCH 313/383] rename the folder name to "extended_metadata_drafts" --- .../010_ena_custom_metadata.seeds.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename db/seeds/{custom_metadata_drafts => extended_metadata_drafts}/010_ena_custom_metadata.seeds.rb (100%) diff --git a/db/seeds/custom_metadata_drafts/010_ena_custom_metadata.seeds.rb b/db/seeds/extended_metadata_drafts/010_ena_custom_metadata.seeds.rb similarity index 100% rename from db/seeds/custom_metadata_drafts/010_ena_custom_metadata.seeds.rb rename to db/seeds/extended_metadata_drafts/010_ena_custom_metadata.seeds.rb From 37c76c664e93dedeaed9b57eecfb0143b7add597 Mon Sep 17 00:00:00 2001 From: Sonja Mathias Date: Tue, 9 Jan 2024 10:21:13 +0100 Subject: [PATCH 314/383] Functional button test for isa json compliant investigations --- test/functional/investigations_controller_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/functional/investigations_controller_test.rb b/test/functional/investigations_controller_test.rb index 8c148f6dd5..1c328a9804 100644 --- a/test/functional/investigations_controller_test.rb +++ b/test/functional/investigations_controller_test.rb @@ -1139,4 +1139,19 @@ def test_title refute assay_output_ids.include? "#data_file/#{assay_2_stream_2_hidden_sample.id}" end end + + test 'display adjusted buttons if isa json compliant' do + current_user = FactoryBot.create(:user) + login_as(current_user) + + inv = FactoryBot.create(:investigation, is_isa_json_compliant: true) + + get :show, params: { id: inv } + assert_response :success + + assert_select 'a', text: /Design #{I18n.t('study')}/i, count: 1 + assert_select 'a', text: 'Export ISA', count: 1 + + assert_select 'a', text: /Add a #{I18n.t('study')}/i, count: 0 + end end From bd685ad845223031438bdba3e8db8fff7f8b3c59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:33:37 +0000 Subject: [PATCH 315/383] Bump puma from 5.6.7 to 5.6.8 Bumps [puma](https://github.com/puma/puma) from 5.6.7 to 5.6.8. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v5.6.7...v5.6.8) --- updated-dependencies: - dependency-name: puma dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 36e59003a2..1f1b6cd9db 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -491,7 +491,7 @@ GEM net-smtp (0.3.3) net-protocol netrc (0.11.0) - nio4r (2.5.9) + nio4r (2.7.0) nokogiri (1.14.5) mini_portile2 (~> 2.8.0) racc (~> 1.4) @@ -563,7 +563,7 @@ GEM psych (5.1.0) stringio public_suffix (5.0.0) - puma (5.6.7) + puma (5.6.8) nio4r (~> 2.0) pyu-ruby-sasl (0.0.3.3) racc (1.7.1) From 5e54bb087a6696c64cb4109ace1f52ec5a5a52b4 Mon Sep 17 00:00:00 2001 From: Sonja Mathias Date: Tue, 9 Jan 2024 12:49:15 +0100 Subject: [PATCH 316/383] Split functional button tests for investigations --- .../investigations_controller_test.rb | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/test/functional/investigations_controller_test.rb b/test/functional/investigations_controller_test.rb index 1c328a9804..a564eb29a4 100644 --- a/test/functional/investigations_controller_test.rb +++ b/test/functional/investigations_controller_test.rb @@ -1140,18 +1140,32 @@ def test_title end end - test 'display adjusted buttons if isa json compliant' do - current_user = FactoryBot.create(:user) - login_as(current_user) + test 'display single page button if feature enabled' do + with_config_value(:project_single_page_enabled, true) do + current_user = FactoryBot.create(:user) + login_as(current_user) + inv = FactoryBot.create(:investigation, contributor: current_user.person) - inv = FactoryBot.create(:investigation, is_isa_json_compliant: true) + get :show, params: { id: inv } + assert_response :success - get :show, params: { id: inv } - assert_response :success + assert_select 'a', text: 'Single Page', count: 1 + end + end - assert_select 'a', text: /Design #{I18n.t('study')}/i, count: 1 - assert_select 'a', text: 'Export ISA', count: 1 + test 'display adjusted buttons if isa json compliant' do + with_config_value(:isa_json_compliance_enabled, true) do + current_user = FactoryBot.create(:user) + login_as(current_user) + inv = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: current_user.person) - assert_select 'a', text: /Add a #{I18n.t('study')}/i, count: 0 + get :show, params: { id: inv } + assert_response :success + + assert_select 'a', text: /Design #{I18n.t('study')}/i, count: 1 + assert_select 'a', text: 'Export ISA', count: 1 + + assert_select 'a', text: /Add a #{I18n.t('study')}/i, count: 0 + end end end From cc78c40016ac98b1217ed2886cdc64e216c82812 Mon Sep 17 00:00:00 2001 From: Sonja Mathias Date: Tue, 9 Jan 2024 13:01:12 +0100 Subject: [PATCH 317/383] Add functional test for Single Page button to Studies and Assays --- test/functional/assays_controller_test.rb | 13 +++++++++++++ test/functional/studies_controller_test.rb | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 7e8b2f918b..781358809e 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -1994,4 +1994,17 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links assert flash[:error].include?('disabled') end end + + test 'display single page button if feature enabled' do + with_config_value(:project_single_page_enabled, true) do + current_user = FactoryBot.create(:user) + login_as(current_user) + assay = FactoryBot.create(:assay, contributor: current_user.person) + + get :show, params: { id: assay } + assert_response :success + + assert_select 'a', text: 'Single Page', count: 1 + end + end end diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb index eed19ef139..be1a302c3d 100644 --- a/test/functional/studies_controller_test.rb +++ b/test/functional/studies_controller_test.rb @@ -1999,4 +1999,17 @@ def test_should_show_investigation_tab assert flash[:error].include?('disabled') end end + + test 'display single page button if feature enabled' do + with_config_value(:project_single_page_enabled, true) do + current_user = FactoryBot.create(:user) + login_as(current_user) + study = FactoryBot.create(:study, contributor: current_user.person) + + get :show, params: { id: study } + assert_response :success + + assert_select 'a', text: 'Single Page', count: 1 + end + end end From b643f175d1d8a92331fb05b4c84777e29f7f205f Mon Sep 17 00:00:00 2001 From: Sonja Mathias Date: Tue, 9 Jan 2024 13:13:59 +0100 Subject: [PATCH 318/383] Add functional test for adjusted buttons for isa json compliant Studies --- test/functional/studies_controller_test.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb index be1a302c3d..69a5ef4419 100644 --- a/test/functional/studies_controller_test.rb +++ b/test/functional/studies_controller_test.rb @@ -2012,4 +2012,18 @@ def test_should_show_investigation_tab assert_select 'a', text: 'Single Page', count: 1 end end + + test 'display adjusted buttons if isa json compliant' do + with_config_value(:isa_json_compliance_enabled, true) do + current_user = FactoryBot.create(:user) + login_as(current_user) + study = FactoryBot.create(:isa_json_compliant_study, contributor: current_user.person) + + get :show, params: { id: study } + assert_response :success + + assert_select 'a', text: /Design #{I18n.t('assay')}/i, count: 1 + assert_select 'a', text: /Add new #{I18n.t('assay')}/i, count: 0 + end + end end From 53d7a4688491f9b333fa4a58cf0eebf017d3a154 Mon Sep 17 00:00:00 2001 From: Sonja Mathias Date: Tue, 9 Jan 2024 13:19:18 +0100 Subject: [PATCH 319/383] Add functional test for adjusted buttons for isa json compliant Assays --- test/functional/assays_controller_test.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 781358809e..77253f7191 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -2007,4 +2007,17 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links assert_select 'a', text: 'Single Page', count: 1 end end + + test 'display adjusted buttons if isa json compliant' do + with_config_value(:isa_json_compliance_enabled, true) do + current_user = FactoryBot.create(:user) + login_as(current_user) + assay = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person) + + get :show, params: { id: assay } + assert_response :success + + assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1 + end + end end From 8b71b2a10de086e9b0cc8b27aabb6beb4d771cf1 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 9 Jan 2024 14:41:11 +0100 Subject: [PATCH 320/383] Add the dynamic table to the assay design tab in default view --- app/views/assays/show.html.erb | 12 +++++++++++ app/views/isa_assays/_assay_design.html.erb | 22 ++++++++++++++------ app/views/isa_assays/_assay_samples.html.erb | 7 ++++--- app/views/isa_assays/_assay_table.html.erb | 6 +++--- app/views/isa_studies/_study_design.html.erb | 1 - 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 7ca44d4f26..6f2c697b1b 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -1,3 +1,7 @@ +<%= javascript_include_tag "single_page/index" %> +<%= javascript_include_tag "single_page/dynamic_table" %> +<%= javascript_include_tag "project_folders" %> + <%= render partial: 'general/item_title', locals: { item: @assay, buttons_partial: 'assays/buttons' } -%> <%= render partial: 'general/show_page_tab_definitions', locals: { resource: @assay } %> @@ -140,3 +144,11 @@ <% end %> <% end %>
    + diff --git a/app/views/isa_assays/_assay_design.html.erb b/app/views/isa_assays/_assay_design.html.erb index 8e7aafce67..98415832d7 100644 --- a/app/views/isa_assays/_assay_design.html.erb +++ b/app/views/isa_assays/_assay_design.html.erb @@ -1,18 +1,23 @@ <% assay ||= nil %> <% valid_study = assay&.study&.sample_types.present? %> <% valid_assay = assay&.sample_type.present? %> +<% + assay_protocol_action = displaying_single_page? ? "highlightTreeviewItem('assay_protocol')" : "loadDynamicTableFromDefaultView('assay_protocol')" + assay_samples_table_action = displaying_single_page? ? "highlightTreeviewItem('assay_samples_table')" : "loadDynamicTableFromDefaultView('assay_samples_table')" + assay_experiment_overview_action = displaying_single_page? ? "highlightTreeviewItem('assay_experiment_overview')" : "loadDynamicTableFromDefaultView('assay_experiment_overview')" +%> <% if valid_study && valid_assay %> @@ -24,15 +29,20 @@ <%= render :partial=>"isa_assays/assay_samples", locals: { assay: assay} -%>
    - <%= render :partial=>"isa_assays/assay_table", locals: { assay: assay} -%> + <%= render :partial=>"isa_assays/assay_table", locals: { assay: assay} -%>
    <% else %>

    - This <%="#{t(:assay)}"%> has not been created in Single Page. + This <%="#{t(:assay)}"%> has not been created in Single Page. Please, create the <%="#{t(:assay)}"%> by using the Design <%="#{t(:assay)}"%> button at the <%="#{t(:study)}"%> level.

    -<% end %> \ No newline at end of file +<% end %> + diff --git a/app/views/isa_assays/_assay_samples.html.erb b/app/views/isa_assays/_assay_samples.html.erb index 059b5a299f..192536ec25 100644 --- a/app/views/isa_assays/_assay_samples.html.erb +++ b/app/views/isa_assays/_assay_samples.html.erb @@ -19,6 +19,7 @@ <% if valid_assay %> From fb692f721cda8a6ed646bb550c8133f13097864c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 9 Jan 2024 14:52:58 +0100 Subject: [PATCH 321/383] Fix study selection in single page --- app/helpers/assays_helper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/helpers/assays_helper.rb b/app/helpers/assays_helper.rb index 5c713eced1..4d928349af 100644 --- a/app/helpers/assays_helper.rb +++ b/app/helpers/assays_helper.rb @@ -90,7 +90,8 @@ def grouped_options_for_study_selection(current_study) # returns a map of the studies that can be selected, grouped by investigation # this includes the editable studies, plus the current associated study if it is not already included (i.e not edtiable) def selectable_studies_mapped_to_investigation(current_study) - studies = Study.authorized_for('edit').to_a.select{ |study| !study.is_isa_json_compliant? } + studies = Study.authorized_for('edit').to_a + studies.select!{ |study| !study.is_isa_json_compliant? } unless displaying_single_page? studies << current_study if current_study && !current_study.can_edit? investigation_map = {} studies.each do |study| From e4e8896ffe83568de10aca7a5cae77adaef25a5d Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Fri, 24 Nov 2023 15:40:32 +0000 Subject: [PATCH 322/383] Options to allow programmes and "site managed programme"'s projects to be auto-activated --- app/controllers/programmes_controller.rb | 7 +- app/controllers/projects_controller.rb | 139 ++++++++++-------- .../institutions/_select_or_define.html.erb | 16 +- app/views/projects/guided_create.html.erb | 10 +- lib/seek/config_setting_attributes.yml | 2 + 5 files changed, 101 insertions(+), 73 deletions(-) diff --git a/app/controllers/programmes_controller.rb b/app/controllers/programmes_controller.rb index 1ba340a937..0c40f6d320 100644 --- a/app/controllers/programmes_controller.rb +++ b/app/controllers/programmes_controller.rb @@ -29,8 +29,11 @@ def create # current person becomes the programme administrator, unless they are logged in # also activation email is sent unless User.admin_logged_in? - if Seek::Config.email_enabled - Mailer.programme_activation_required(@programme,current_person).deliver_later + if Seek::Config.auto_activate_programmes + @programe.activate + Mailer.programme_activated(@programme).deliver_later if Seek::Config.email_enabled + else + Mailer.programme_activation_required(@programme, current_person).deliver_later if Seek::Config.email_enabled end end format.html {respond_with(@programme)} diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 4c13a18d8d..7474bf22c3 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -75,6 +75,9 @@ def guided_join end def guided_create + @institution = Institution.new + @project = Project.new + respond_to do |format| format.html end @@ -177,7 +180,7 @@ def request_create @institution = Institution.find_by_id(params[:institution][:id]) if @institution.nil? - inst_params = params.require(:institution).permit([:id, :title, :web_page, :city, :country]) + inst_params = params.require(:institution).permit([:title, :web_page, :city, :country]) @institution = Institution.new(inst_params) end @@ -190,6 +193,17 @@ def request_create log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) elsif @programme.site_managed? log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) + if Seek::Config.auto_activate_site_managed_projects + @message_log = log + errors = confirm_project_create_request(skip_permissions: true) + if errors.present? + flash.now[:error] = errors + render action: :guided_create + else + flash[:notice] = "Thank you, your #{t('project')} has been created" + redirect_to(@project) + end + end if Seek::Config.email_enabled Mailer.request_create_project_for_programme(current_user, @programme, @project.to_json, @institution.to_json, log).deliver_later end @@ -539,15 +553,9 @@ def administer_create_project_request end def respond_create_project_request - requester = @message_log.sender - make_programme_admin = false if params['accept_request']=='1' - - # @programme already populated in before_filter when checking permissions - make_programme_admin = @programme&.new_record? - if params['institution']['id'] @institution = Institution.find(params['institution']['id']) else @@ -557,62 +565,13 @@ def respond_create_project_request @project = Project.new(params.require(:project).permit([:title, :web_page, :description])) @project.programme = @programme - validate_error_msg = [] - - unless @project.valid? - validate_error_msg << "The #{t('project')} is invalid, #{@project.errors.full_messages.join(', ')}" - end - unless @programme.nil? || @programme.valid? - validate_error_msg << "The #{t('programme')} is invalid, #{@programme.errors.full_messages.join(', ')}" - end - unless @institution.valid? - validate_error_msg << "The #{t('institution')} is invalid, #{@institution.errors.full_messages.join(', ')}" - end - - unless @programme&.allows_user_projects? || Institution.can_create? - validate_error_msg << "The #{t('institution')} cannot be created, as you do not have access rights" - end - - unless Project.can_create? - validate_error_msg << "The #{t('project')} cannot be created, as you do not have access rights" - end - - validate_error_msg = validate_error_msg.join('
    ').html_safe - - if validate_error_msg.blank? - @project.save! - - # they are soon to become a project administrator, with permission to create - disable_authorization_checks { @institution.save! } - - requester.add_to_project_and_institution(@project, @institution) - requester.is_project_administrator = true,@project - requester.is_programme_administrator = true, @programme if make_programme_admin - - disable_authorization_checks do - requester.save! - end - - if @message_log.sent_by_self? - @message_log.destroy - flash[:notice]="#{t('project')} created" - else - @message_log.respond('Accepted') - if Seek::Config.email_enabled - flash[:notice]="Request accepted and #{requester.name} added to #{t('project')} and notified" - Mailer.notify_user_projects_assigned(requester,[@project]).deliver_later - Mailer.notify_admins_project_creation_accepted(current_person, requester, @project).deliver_later - else - flash[:notice]="Request accepted and #{requester.name} added to #{t('project')}" - end - end - - redirect_to(@project) - else - flash.now[:error] = validate_error_msg + errors = confirm_project_create_request + if errors.present? + flash.now[:error] = errors render action: :administer_create_project_request + else + redirect_to(@project) end - else if @message_log.sent_by_self? || params['delete_request'] == '1' @message_log.destroy @@ -849,4 +808,62 @@ def check_message_log_programme_permissions return false end end + + def confirm_project_create_request(skip_permissions: false) + requester = @message_log.sender + validate_error_msg = [] + + unless @project.valid? + validate_error_msg << "The #{t('project')} is invalid, #{@project.errors.full_messages.join(', ')}" + end + + unless @programme.nil? || @programme.valid? + validate_error_msg << "The #{t('programme')} is invalid, #{@programme.errors.full_messages.join(', ')}" + end + + unless @institution.valid? + validate_error_msg << "The #{t('institution')} is invalid, #{@institution.errors.full_messages.join(', ')}" + end + + unless @programme&.allows_user_projects? || skip_permissions || Institution.can_create? + validate_error_msg << "The #{t('institution')} cannot be created, as you do not have access rights" + end + + unless skip_permissions || Project.can_create? + validate_error_msg << "The #{t('project')} cannot be created, as you do not have access rights" + end + + validate_error_msg = validate_error_msg.join('
    ').html_safe + return validate_error_msg unless validate_error_msg.blank? + + disable_authorization_checks { @project.save! } + + # they are soon to become a project administrator, with permission to create + disable_authorization_checks { @institution.save! } + + requester.add_to_project_and_institution(@project, @institution) + requester.is_project_administrator = true, @project + # @programme already populated in before_filter when checking permissions + requester.is_programme_administrator = true, @programme if @programme&.new_record? + + disable_authorization_checks do + requester.save! + end + + if @message_log.sent_by_self? + @message_log.destroy + flash[:notice]="#{t('project')} created" + else + @message_log.respond('Accepted') + if Seek::Config.email_enabled + flash[:notice]="Request accepted and #{requester.name} added to #{t('project')} and notified" + Mailer.notify_user_projects_assigned(requester,[@project]).deliver_later + Mailer.notify_admins_project_creation_accepted(current_person, requester, @project).deliver_later + else + flash[:notice]="Request accepted and #{requester.name} added to #{t('project')}" + end + end + + nil + end end diff --git a/app/views/institutions/_select_or_define.html.erb b/app/views/institutions/_select_or_define.html.erb index daf6b5b44c..5924d3f9e2 100644 --- a/app/views/institutions/_select_or_define.html.erb +++ b/app/views/institutions/_select_or_define.html.erb @@ -17,17 +17,23 @@
    * - <%= objects_input('institution[id]', [], typeahead: {query_url: typeahead_institutions_path, handlebars_template:'typeahead/institution'}, limit:1, allow_new: true, class:'form-control') -%> + + <%# I am setting the institution id to a junk value of `_` to get around a bug in select2 wherein an
    -<%= hidden_field_tag 'institution[title]' %> +<%= hidden_field_tag 'institution[title]', @institution.title %>
    - <%= text_field_tag('institution[web_page]','',class:'form-control institution-user-input') %> + <%= text_field_tag('institution[web_page]', @institution.web_page, class:'form-control institution-user-input') %> - <%= text_field_tag('institution[city]','',class:'form-control institution-user-input') %> + <%= text_field_tag('institution[city]', @institution.city, class:'form-control institution-user-input') %> <%= country_select('institution','country', @@ -42,7 +48,7 @@ } $j(document).ready(function () { - toggleUserInput(true); + toggleUserInput(<%= @institution.id.present? ? 'false' : 'true' -%>); $j('select#institution_country').on('change',function() { checkSubmitButtonEnabled(); diff --git a/app/views/projects/guided_create.html.erb b/app/views/projects/guided_create.html.erb index 79300434a7..48fe795c1d 100644 --- a/app/views/projects/guided_create.html.erb +++ b/app/views/projects/guided_create.html.erb @@ -16,11 +16,11 @@
    <%= label_tag :project_title, "Title" %>* - <%= text_field_tag 'project[title]', '', class: 'form-control' %> - <%= label_tag :project_title, "Description" %> - <%= text_area_tag 'project[description]', '', rows:2, class: 'form-control' %> - <%= label_tag :project_title, "Web page" %> - <%= text_field_tag 'project[web_page]', '', class: 'form-control' %> + <%= text_field_tag 'project[title]', @project.title, class: 'form-control' %> + <%= label_tag :project_description, "Description" %> + <%= text_area_tag 'project[description]', @project.description, rows:2, class: 'form-control' %> + <%= label_tag :project_web_page, "Web page" %> + <%= text_field_tag 'project[web_page]', @project.web_page, class: 'form-control' %> <% end %> diff --git a/lib/seek/config_setting_attributes.yml b/lib/seek/config_setting_attributes.yml index 9eb8eebc42..e23642e9df 100644 --- a/lib/seek/config_setting_attributes.yml +++ b/lib/seek/config_setting_attributes.yml @@ -255,3 +255,5 @@ galaxy_tool_sources: metadata_license: regular_job_offset: convert: :to_i +auto_activate_programmes: +auto_activate_site_managed_projects: From a2d2ab28658910a5cf9851588a317ee81f0157d2 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 27 Nov 2023 13:45:53 +0000 Subject: [PATCH 323/383] nil fix --- app/controllers/projects_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7474bf22c3..1046bb44d3 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -63,6 +63,7 @@ def project_creation_requests end def guided_join + @institution = Institution.new @project = Project.find(params[:id]) if params[:id] respond_to do |format| if @project && !@project.allow_request_membership? From c0d376f8a407a74f0865d42e6ab3a506a9c11325 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 27 Nov 2023 13:46:19 +0000 Subject: [PATCH 324/383] Fix intermittent test failure (only on postgres?) --- test/functional/projects_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 83c9f65a1e..0a0b488d5b 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -3679,7 +3679,7 @@ def test_admin_can_edit put :update, params: { id: project.id, project: { topic_annotations: ['Chemistry', 'Sample collections'] } } - assert_equal ['http://edamontology.org/topic_3314','http://edamontology.org/topic_3277'], assigns(:project).topic_annotations + assert_equal ['http://edamontology.org/topic_3314','http://edamontology.org/topic_3277'].sort, assigns(:project).topic_annotations.sort end From 93996459669fd091fd0b14c4c05a456ca5360c2c Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 27 Nov 2023 13:50:55 +0000 Subject: [PATCH 325/383] Fix programme admin regression --- app/controllers/projects_controller.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 1046bb44d3..b9f6d29119 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -813,6 +813,7 @@ def check_message_log_programme_permissions def confirm_project_create_request(skip_permissions: false) requester = @message_log.sender validate_error_msg = [] + make_programme_admin = @programme&.new_record? unless @project.valid? validate_error_msg << "The #{t('project')} is invalid, #{@project.errors.full_messages.join(', ')}" @@ -837,15 +838,17 @@ def confirm_project_create_request(skip_permissions: false) validate_error_msg = validate_error_msg.join('
    ').html_safe return validate_error_msg unless validate_error_msg.blank? - disable_authorization_checks { @project.save! } - - # they are soon to become a project administrator, with permission to create - disable_authorization_checks { @institution.save! } + # They are soon to become a project administrator, with permission to create institutions, + # and `Project.can_create?` is already checked + disable_authorization_checks do + @project.save! # Implicitly saves the @programme too + @institution.save! + end requester.add_to_project_and_institution(@project, @institution) requester.is_project_administrator = true, @project # @programme already populated in before_filter when checking permissions - requester.is_programme_administrator = true, @programme if @programme&.new_record? + requester.is_programme_administrator = true, @programme if make_programme_admin disable_authorization_checks do requester.save! From 34aae7faa69a99ac497ffb1a780e7ffc9829988c Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 27 Nov 2023 17:27:19 +0000 Subject: [PATCH 326/383] Fix typo. Tests. Don't check permission in `activate` method It is already checked in controller --- app/controllers/programmes_controller.rb | 2 +- app/models/programme.rb | 7 ++----- test/functional/programmes_controller_test.rb | 20 +++++++++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/controllers/programmes_controller.rb b/app/controllers/programmes_controller.rb index 0c40f6d320..745f29d4ac 100644 --- a/app/controllers/programmes_controller.rb +++ b/app/controllers/programmes_controller.rb @@ -30,7 +30,7 @@ def create # also activation email is sent unless User.admin_logged_in? if Seek::Config.auto_activate_programmes - @programe.activate + @programme.activate Mailer.programme_activated(@programme).deliver_later if Seek::Config.email_enabled else Mailer.programme_activation_required(@programme, current_person).deliver_later if Seek::Config.email_enabled diff --git a/app/models/programme.rb b/app/models/programme.rb index e5f0c16c25..5760a7a3e5 100644 --- a/app/models/programme.rb +++ b/app/models/programme.rb @@ -96,12 +96,9 @@ def rejected? !(activation_rejection_reason.nil? || is_activated?) end - # callback, activates if current user is an admin or nil, otherwise it needs activating def activate - if can_activate? - update_attribute(:is_activated, true) - update_attribute(:activation_rejection_reason, nil) - end + update_attribute(:is_activated, true) + update_attribute(:activation_rejection_reason, nil) end def can_activate?(user = User.current_user) diff --git a/test/functional/programmes_controller_test.rb b/test/functional/programmes_controller_test.rb index 608d012bbd..f5b6f55345 100644 --- a/test/functional/programmes_controller_test.rb +++ b/test/functional/programmes_controller_test.rb @@ -161,6 +161,7 @@ def rdf_test_object assert person2.is_programme_administrator?(prog) assert person2.is_programme_administrator_of_any_programme? assert person2.has_role?('programme_administrator') + refute assigns(:programme).is_activated? end test 'admin sets themself as programme administrator at creation' do @@ -937,4 +938,23 @@ def rdf_test_object assert flash[:error].include?('disabled') end end + + test 'automatically activate programme if setting enabled' do + with_config_value(:auto_activate_programmes, true) do + creator = FactoryBot.create(:person) + refute creator.is_admin? + login_as(creator) + assert_difference('Role.count', 1) do # Should include creator + assert_difference('Programme.count', 1) do + post :create, params: { programme: { title: 'programme xxxyxxx2' } } + end + end + + assert prog = assigns(:programme) + assert assigns(:programme).is_activated? + assert creator.is_programme_administrator?(prog) + assert creator.is_programme_administrator_of_any_programme? + assert creator.has_role?('programme_administrator') + end + end end From aa33939612a16bcaeef946e6e1b23f6308cde9c4 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 27 Nov 2023 18:00:04 +0000 Subject: [PATCH 327/383] Auto activation tests --- app/controllers/projects_controller.rb | 11 +- app/models/project_creation_message_log.rb | 8 +- .../institutions/_select_or_define.html.erb | 4 +- test/functional/projects_controller_test.rb | 106 +++++++++++++++++- 4 files changed, 120 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b9f6d29119..8ac9509070 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -193,17 +193,24 @@ def request_create if @programme.can_associate_projects? log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) elsif @programme.site_managed? - log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) + log = ProjectCreationMessageLog.prepare_request(sender:current_person, programme:@programme, project:@project, institution:@institution) if Seek::Config.auto_activate_site_managed_projects @message_log = log + @project.programme = @programme errors = confirm_project_create_request(skip_permissions: true) if errors.present? flash.now[:error] = errors - render action: :guided_create + render action: :guided_create, status: :unprocessable_entity else flash[:notice] = "Thank you, your #{t('project')} has been created" + if Seek::Config.email_enabled + Mailer.notify_admins_project_creation_accepted(current_person, current_person, @project).deliver_later + end redirect_to(@project) end + return + else + log.save! end if Seek::Config.email_enabled Mailer.request_create_project_for_programme(current_user, @programme, @project.to_json, @institution.to_json, log).deliver_later diff --git a/app/models/project_creation_message_log.rb b/app/models/project_creation_message_log.rb index 1aa7449ab1..346696eac3 100644 --- a/app/models/project_creation_message_log.rb +++ b/app/models/project_creation_message_log.rb @@ -7,10 +7,14 @@ class ProjectCreationMessageLog < MessageLog # project creation requests that haven't been responded to scope :pending_requests, -> { pending } - def self.log_request(sender:, project:, institution:, programme: nil) + def self.prepare_request(sender:, project:, institution:, programme: nil) details = details_json(programme: programme, project: project, institution: institution) # FIXME: needs a subject, but can't use programme as it will save it if it is new - ProjectCreationMessageLog.create(subject: sender, sender: sender, details: details) + ProjectCreationMessageLog.new(subject: sender, sender: sender, details: details) + end + + def self.log_request(sender:, project:, institution:, programme: nil) + prepare_request(sender: sender, project: project, institution: institution, programme: programme).tap(&:save) end # whether the person can respond to the creation request diff --git a/app/views/institutions/_select_or_define.html.erb b/app/views/institutions/_select_or_define.html.erb index 5924d3f9e2..6cf4c45313 100644 --- a/app/views/institutions/_select_or_define.html.erb +++ b/app/views/institutions/_select_or_define.html.erb @@ -18,9 +18,9 @@ * - <%# I am setting the institution id to a junk value of `_` to get around a bug in select2 wherein an
    <%= admin_checkbox_setting(:presentations_enabled, 1, Seek::Config.presentations_enabled, From 2e77d65ea7150174b903c1ce44516078dc1e678b Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 28 Nov 2023 16:38:03 +0000 Subject: [PATCH 331/383] Refactor flaky test assertion --- test/functional/projects_controller_test.rb | 29 +++++++-------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 9110b38758..2d18018e1d 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -3870,30 +3870,21 @@ def test_admin_can_edit private def check_project(project) - assert project.investigations.size == 2 - check_investigation_0(project.investigations[0]) - check_investigation_1(project.investigations[1]) - end + assert_equal 2, project.investigations.count - def check_investigation_0(investigation) - assert investigation.title == "Select host and product" - assert investigation.studies.size == 4 - end + investigation1 = project.investigations.detect { |i| i.title == 'Select host and product' } + assert investigation1 + assert 4, investigation1.studies.count - def check_investigation_1(investigation) - assert investigation.title == "Design" - assert investigation.studies.size == 1 - check_study_1_0(investigation.studies[0]) - end + investigation2 = project.investigations.detect { |i| i.title == 'Design' } + assert investigation2 + assert 1, investigation2.studies.count - def check_study_1_0(study) + study = investigation2.studies.first assert study.title == "Receive input from select host and products step" - assert study.assays.size == 2 - check_assay_1_0_1(study.assays[1]) - end + assert_equal 2, study.assays.count - def check_assay_1_0_1(assay) - assert assay.title == "Obtain SBML models for production hosts" + assert study.assays.detect { |a| a.title == 'Obtain SBML models for production hosts' } end def valid_project From f80e9f2546ab81e88043115161689b4f63c06895 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 28 Nov 2023 16:39:38 +0000 Subject: [PATCH 332/383] Rejig logic to avoid need for unsaved log object --- app/controllers/projects_controller.rb | 38 ++++++++++------------ app/models/project_creation_message_log.rb | 8 ++--- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a6e13dc868..ad0ac98a7f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -193,11 +193,9 @@ def request_create if @programme.can_associate_projects? log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) elsif @programme.site_managed? - log = ProjectCreationMessageLog.prepare_request(sender:current_person, programme:@programme, project:@project, institution:@institution) if Seek::Config.auto_activate_site_managed_projects - @message_log = log @project.programme = @programme - errors = confirm_project_create_request(skip_permissions: true) + errors = confirm_project_create_request(current_person, skip_permissions: true) if errors.present? flash.now[:error] = errors render action: :guided_create, status: :unprocessable_entity @@ -210,7 +208,7 @@ def request_create end return else - log.save! + log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) end if Seek::Config.email_enabled Mailer.request_create_project_for_programme(current_user, @programme, @project.to_json, @institution.to_json, log).deliver_later @@ -573,11 +571,24 @@ def respond_create_project_request @project = Project.new(params.require(:project).permit([:title, :web_page, :description])) @project.programme = @programme - errors = confirm_project_create_request + errors = confirm_project_create_request(requester) if errors.present? flash.now[:error] = errors render action: :administer_create_project_request else + if @message_log.sent_by_self? + @message_log.destroy + flash[:notice]="#{t('project')} created" + else + @message_log.respond('Accepted') + if Seek::Config.email_enabled + flash[:notice]="Request accepted and #{requester.name} added to #{t('project')} and notified" + Mailer.notify_user_projects_assigned(requester,[@project]).deliver_later + Mailer.notify_admins_project_creation_accepted(current_person, requester, @project).deliver_later + else + flash[:notice]="Request accepted and #{requester.name} added to #{t('project')}" + end + end redirect_to(@project) end else @@ -817,8 +828,7 @@ def check_message_log_programme_permissions end end - def confirm_project_create_request(skip_permissions: false) - requester = @message_log.sender + def confirm_project_create_request(requester, skip_permissions: false) validate_error_msg = [] make_programme_admin = @programme&.new_record? @@ -861,20 +871,6 @@ def confirm_project_create_request(skip_permissions: false) requester.save! end - if @message_log.sent_by_self? - @message_log.destroy - flash[:notice]="#{t('project')} created" - else - @message_log.respond('Accepted') - if Seek::Config.email_enabled - flash[:notice]="Request accepted and #{requester.name} added to #{t('project')} and notified" - Mailer.notify_user_projects_assigned(requester,[@project]).deliver_later - Mailer.notify_admins_project_creation_accepted(current_person, requester, @project).deliver_later - else - flash[:notice]="Request accepted and #{requester.name} added to #{t('project')}" - end - end - nil end end diff --git a/app/models/project_creation_message_log.rb b/app/models/project_creation_message_log.rb index 346696eac3..1aa7449ab1 100644 --- a/app/models/project_creation_message_log.rb +++ b/app/models/project_creation_message_log.rb @@ -7,14 +7,10 @@ class ProjectCreationMessageLog < MessageLog # project creation requests that haven't been responded to scope :pending_requests, -> { pending } - def self.prepare_request(sender:, project:, institution:, programme: nil) + def self.log_request(sender:, project:, institution:, programme: nil) details = details_json(programme: programme, project: project, institution: institution) # FIXME: needs a subject, but can't use programme as it will save it if it is new - ProjectCreationMessageLog.new(subject: sender, sender: sender, details: details) - end - - def self.log_request(sender:, project:, institution:, programme: nil) - prepare_request(sender: sender, project: project, institution: institution, programme: programme).tap(&:save) + ProjectCreationMessageLog.create(subject: sender, sender: sender, details: details) end # whether the person can respond to the creation request From 2e099c882db507fd815f7aafe4fc1663de1ff9c1 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 29 Nov 2023 13:59:12 +0000 Subject: [PATCH 333/383] Fix form UX issues * Remove text about activation requirement if auto approve setting is enabled. * Programme selection was reset after validation error. * Institution form was disabled for new institution after validation error. --- app/controllers/projects_controller.rb | 2 +- app/views/institutions/_select_or_define.html.erb | 7 +++---- .../projects/guided_create/_programme_dropdown.html.erb | 5 +++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ad0ac98a7f..2548e288b2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -213,7 +213,7 @@ def request_create if Seek::Config.email_enabled Mailer.request_create_project_for_programme(current_user, @programme, @project.to_json, @institution.to_json, log).deliver_later end - flash.now[:notice]="Thank you, your request for a new #{t('project')} has been sent" + flash.now[:notice] = "Thank you, your request for a new #{t('project')} has been sent" else raise 'Invalid Programme' end diff --git a/app/views/institutions/_select_or_define.html.erb b/app/views/institutions/_select_or_define.html.erb index 6cf4c45313..ee3b6bd25f 100644 --- a/app/views/institutions/_select_or_define.html.erb +++ b/app/views/institutions/_select_or_define.html.erb @@ -18,9 +18,9 @@ * - <%# I am setting the institution id to a junk value of `_` (if blank) to get around a bug in select2 wherein an
    <% if !admin_logged_in? && Programme.site_managed_programme.present? %>
    - You can also choose the <%= "#{Seek::Config.instance_admins_name} managed #{t('programme')}" %>, <%= link_to Programme.site_managed_programme.title, Programme.site_managed_programme %>, and if so you will need to wait for approval. + You can also choose the <%= "#{Seek::Config.instance_admins_name} managed #{t('programme')}" %>, + <%= link_to Programme.site_managed_programme.title, Programme.site_managed_programme %><%= ', and if so you will need to wait for approval' unless Seek::Config.auto_activate_site_managed_projects -%>.
    <% end %> @@ -16,7 +17,7 @@ end %> -<%= select_tag :programme_id,options_from_collection_for_select(programmes,:id, :title), class: 'form-control' %> +<%= select_tag :programme_id, options_from_collection_for_select(programmes, :id, :title, @programme&.id), class: 'form-control' %> <% if Seek::ProjectFormProgrammeOptions.creation_allowed? %>
    From 8768624d0ec5055f7723e152affe869d28fea16f Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 6 Dec 2023 17:31:49 +0000 Subject: [PATCH 334/383] Allow automatic creation/activation of programme & project together if setting is enabled --- app/controllers/projects_controller.rb | 25 ++++- app/views/projects/guided_create.html.erb | 3 + .../_managed_programme_checkbox.html.erb | 2 +- .../guided_create/_programme_details.html.erb | 17 ++-- .../_programme_dropdown.html.erb | 2 +- test/functional/projects_controller_test.rb | 95 +++++++++++++++++++ 6 files changed, 128 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2548e288b2..75215c4552 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -221,11 +221,28 @@ def request_create elsif Seek::ProjectFormProgrammeOptions.creation_allowed? prog_params = params.require(:programme).permit([:title]) @programme = Programme.new(prog_params) - log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) - if Seek::Config.email_enabled && !User.admin_logged_in? - Mailer.request_create_project_and_programme(current_user, @programme.to_json, @project.to_json, @institution.to_json, log).deliver_later + if Seek::Config.auto_activate_programmes + @project.programme = @programme + errors = confirm_project_create_request(current_person, skip_permissions: true) + if errors.present? + flash.now[:error] = errors + render action: :guided_create, status: :unprocessable_entity + else + @programme.activate + flash[:notice] = "Thank you, your #{t('programme')} and #{t('project')} have been created" + if Seek::Config.email_enabled + Mailer.notify_admins_project_creation_accepted(nil, current_person, @project).deliver_later + end + redirect_to(@project) + end + return + else + log = ProjectCreationMessageLog.log_request(sender:current_person, programme:@programme, project:@project, institution:@institution) + if Seek::Config.email_enabled && !User.admin_logged_in? + Mailer.request_create_project_and_programme(current_user, @programme.to_json, @project.to_json, @institution.to_json, log).deliver_later + end + flash.now[:notice] = "Thank you, your request for a new #{t('programme')} and #{t('project')} has been sent" end - flash.now[:notice] = "Thank you, your request for a new #{t('programme')} and #{t('project')} has been sent" # No Programme at all elsif !Seek::ProjectFormProgrammeOptions.show_programme_box? @programme=nil diff --git a/app/views/projects/guided_create.html.erb b/app/views/projects/guided_create.html.erb index 48fe795c1d..af697dac33 100644 --- a/app/views/projects/guided_create.html.erb +++ b/app/views/projects/guided_create.html.erb @@ -64,6 +64,8 @@ } checkSubmitButtonEnabled(); }); + + $j('input#managed_programme').change(); // Trigger initial state on page load <% end %> <% if Seek::ProjectFormProgrammeOptions.creation_allowed? && Seek::ProjectFormProgrammeOptions.programme_dropdown? %> @@ -83,6 +85,7 @@ } checkSubmitButtonEnabled(); }); + $j('input#new_programme').change(); // Trigger initial state on page load <% end %> $j('input#programme_title').on('input',function() { diff --git a/app/views/projects/guided_create/_managed_programme_checkbox.html.erb b/app/views/projects/guided_create/_managed_programme_checkbox.html.erb index c0faae891a..eb4cd95b1e 100644 --- a/app/views/projects/guided_create/_managed_programme_checkbox.html.erb +++ b/app/views/projects/guided_create/_managed_programme_checkbox.html.erb @@ -7,6 +7,6 @@ it. To have one created, then please provide the name. You will be able to provide additional details, if you wish, once it has been created.
    \ No newline at end of file diff --git a/app/views/projects/guided_create/_programme_details.html.erb b/app/views/projects/guided_create/_programme_details.html.erb index bd8dd84628..d6bddd91cf 100644 --- a/app/views/projects/guided_create/_programme_details.html.erb +++ b/app/views/projects/guided_create/_programme_details.html.erb @@ -1,11 +1,8 @@ -<% if Seek::ProjectFormProgrammeOptions.creation_allowed_only? %> -
    -<% else %> - <% end %> \ No newline at end of file diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 2d18018e1d..018c4f4416 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -2244,6 +2244,101 @@ def test_admin_can_edit end end + test 'request create project with new programme and institution with auto activation' do + with_config_value(:auto_activate_programmes, true) do + FactoryBot.create(:admin) + person = FactoryBot.create(:person_not_in_project) + managed_programme = FactoryBot.create(:programme) + assert Person.admins.count > 1 + login_as(person) + with_config_value(:managed_programme_id, managed_programme.id) do + params = { + project: { title: 'The Project', description: 'description', web_page: 'https://example.com' }, + institution: { id: ['the inst'], title: 'the inst', web_page: 'https://example.com/inst', city: 'London', country: 'GB' }, + programme_id: '', + programme: { title: 'the prog' } + } + project = nil + assert_enqueued_emails(1) do + assert_difference('Programme.count', 1) do + assert_difference('Project.count', 1) do + assert_difference('Institution.count', 1) do + assert_no_difference('ProjectCreationMessageLog.count') do + post :request_create, params: params + + project = Project.last + assert_redirected_to project_path(project) + end + end + end + end + end + + programme = Programme.last + institution = Institution.last + + assert flash[:notice] + gm = person.reload.group_memberships.last + assert_equal 'description', gm.project.description + assert_equal 'The Project', gm.project.title + assert_equal project, gm.project + + assert_equal programme, gm.project.programme + assert programme.is_activated? + assert_equal 'the prog', programme.title + + assert_equal institution, gm.institution + assert_equal 'the inst', institution.title + assert_equal 'https://example.com/inst', institution.web_page + assert_equal 'London', institution.city + assert_equal 'GB', institution.country + end + end + end + + test 'request create project with new programme and institution with auto activation and errors' do + with_config_value(:auto_activate_programmes, true) do + FactoryBot.create(:admin) + person = FactoryBot.create(:person_not_in_project) + existing_programme = FactoryBot.create(:programme, title: 'A Cool Programme') + managed_programme = FactoryBot.create(:programme) + assert Person.admins.count > 1 + login_as(person) + with_config_value(:managed_programme_id, managed_programme.id) do + params = { + project: { title: '', description: 'description', web_page: 'https://example.com' }, + institution: { id: ['the inst'], title: 'the inst', web_page: 'https://example.com/inst', city: 'London', country: 'XY' }, + programme_id: '', + programme: { title: 'A Cool Programme' } + } + project = nil + assert_no_enqueued_emails do + assert_no_difference('Programme.count') do + assert_no_difference('Project.count') do + assert_no_difference('Institution.count') do + assert_no_difference('ProjectCreationMessageLog.count') do + post :request_create, params: params + + assert_response :unprocessable_entity + end + end + end + end + end + + assert flash[:error].include?("The Project is invalid, Title can't be blank") + assert flash[:error].include?("The Programme is invalid, Title has already been taken") + assert flash[:error].include?("The Institution is invalid, Country isn't a valid country or code") + assert_equal 'the inst', assigns(:institution).title + assert_equal 'https://example.com/inst', assigns(:institution).web_page + assert_equal 'description', assigns(:project).description + assert_equal '', assigns(:project).title + assert_equal 'https://example.com', assigns(:project).web_page + assert_equal 'A Cool Programme', assigns(:programme).title + end + end + end + test 'request create project without programmes' do person = FactoryBot.create(:person_not_in_project) From feb3b781a444c2389098da149130b27bb6afa910 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Wed, 10 Jan 2024 09:17:28 +0100 Subject: [PATCH 335/383] bug fix : Select2 doesn't work when embedded in a bootstrap modal --- app/views/sharing/_group_permission_modal.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/sharing/_group_permission_modal.html.erb b/app/views/sharing/_group_permission_modal.html.erb index e31c09fb6f..266470b741 100644 --- a/app/views/sharing/_group_permission_modal.html.erb +++ b/app/views/sharing/_group_permission_modal.html.erb @@ -40,6 +40,7 @@ // Display project/institution/workgroup modal $j('#add-project-permission-button').click(function () { + $j.fn.modal.Constructor.prototype.enforceFocus = function() {}; $j('#permission-institution-id').val(''); $j('#permission-project-id').val('').trigger('change'); $j('#add-project-permission-modal').modal('show'); From ccb32beb4f8d8e5ae7cc64440d9fc44479165c39 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 9 Jan 2024 14:27:03 +0000 Subject: [PATCH 336/383] remember that the external search has been checked #1712 --- app/controllers/search_controller.rb | 1 + test/functional/search_controller_test.rb | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 30489b4b23..2b100795cb 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -37,6 +37,7 @@ def perform_search(sources) end if search_params[:include_external_search] == '1' + @include_external_search = true @external_results = Seek::ExternalSearch.instance.external_search(downcase_query, @search_type&.downcase) end diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb index e6f628b007..97cf320008 100644 --- a/test/functional/search_controller_test.rb +++ b/test/functional/search_controller_test.rb @@ -18,6 +18,9 @@ class SearchControllerTest < ActionController::TestCase assert_select '.list_item_title a[href=?]', document_path(doc) end end + + refute assigns(:include_external_search) + assert_select 'div#advanced-search input#include_external_search[type=checkbox]:not(checked)' end test 'search result order retained' do @@ -175,4 +178,22 @@ class SearchControllerTest < ActionController::TestCase assert_select '#more-results', count: 0 end + + test 'remember external search' do + FactoryBot.create(:model, policy: FactoryBot.create(:public_policy)) + + VCR.use_cassette('biomodels/search') do + with_config_value(:external_search_enabled, true) do + Model.stub(:solr_cache, -> (q) { Model.pluck(:id).last(3) }) do + get :index, params: { q: 'yeast', include_external_search: '1' } + end + end + end + + assert_equal 1, assigns(:results)['Model'].count + assert_equal 25, assigns(:external_results).count + + assert assigns(:include_external_search) + assert_select 'div#advanced-search input#include_external_search[type=checkbox][checked=checked]' + end end From 4dcc0b06aa90ae377632c28552237a2410121017 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 9 Jan 2024 15:04:24 +0000 Subject: [PATCH 337/383] tweak weirdly failing test to avoid unexpected failures #1712 --- test/functional/search_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb index 97cf320008..a490c5ae73 100644 --- a/test/functional/search_controller_test.rb +++ b/test/functional/search_controller_test.rb @@ -184,7 +184,7 @@ class SearchControllerTest < ActionController::TestCase VCR.use_cassette('biomodels/search') do with_config_value(:external_search_enabled, true) do - Model.stub(:solr_cache, -> (q) { Model.pluck(:id).last(3) }) do + Model.stub(:solr_cache, -> (q) { Model.pluck(:id).last(1) }) do get :index, params: { q: 'yeast', include_external_search: '1' } end end From 61bb837a5d649eb922c152cdcd7ffb8b0a78f438 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 9 Jan 2024 16:49:33 +0000 Subject: [PATCH 338/383] Fix for select2 inputs not working in bootstrap modals --- app/assets/javascripts/application_shared.js.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/application_shared.js.erb b/app/assets/javascripts/application_shared.js.erb index b57495c9fb..5da8e5733d 100644 --- a/app/assets/javascripts/application_shared.js.erb +++ b/app/assets/javascripts/application_shared.js.erb @@ -175,6 +175,9 @@ function digValue(object, path) { elements.removeClass(cssClass) }, 1100); }; + + // Fixes select2 in modals: https://github.com/select2/select2/issues/1436 + $j.fn.modal.Constructor.prototype.enforceFocus = function () {}; }(jQuery)); $j(document).ready(function () { From 5d7205597e882a1543fb1590fbb1ab6ede1ee6f4 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 10 Jan 2024 11:39:51 +0000 Subject: [PATCH 339/383] Revert "bug fix : Select2 doesn't work when embedded in a bootstrap modal" This reverts commit feb3b781a444c2389098da149130b27bb6afa910. --- app/views/sharing/_group_permission_modal.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/sharing/_group_permission_modal.html.erb b/app/views/sharing/_group_permission_modal.html.erb index 266470b741..e31c09fb6f 100644 --- a/app/views/sharing/_group_permission_modal.html.erb +++ b/app/views/sharing/_group_permission_modal.html.erb @@ -40,7 +40,6 @@ // Display project/institution/workgroup modal $j('#add-project-permission-button').click(function () { - $j.fn.modal.Constructor.prototype.enforceFocus = function() {}; $j('#permission-institution-id').val(''); $j('#permission-project-id').val('').trigger('change'); $j('#add-project-permission-modal').modal('show'); From a562a73ad89aaf1870704bb8426586ad0225b099 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 10 Jan 2024 13:06:59 +0100 Subject: [PATCH 340/383] Add rake upgrade task for recognising ISA JSON compliant investigations --- lib/tasks/seek_upgrades.rake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 3b2dfccb81..672b0d1d2c 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -15,6 +15,7 @@ namespace :seek do rename_registered_sample_multiple_attribute_type remove_ontology_attribute_type db:seed:007_sample_attribute_types + recognise_isa_json_compliant_items ] # these are the tasks that are executes for each upgrade as standard, and rarely change @@ -156,6 +157,21 @@ namespace :seek do puts " ... finished updating sample_resource_links of #{samples_updated.to_s} samples with data_file attributes" end + task(recognise_isa_json_compliant_items: [:environment]) do + puts '... searching for ISA compliant investigations' + investigations_updated = 0 + disable_authorization_checks do + isa_json_compliant_studies = Study.all.select { |study| study.sample_types.any? } + isa_json_compliant_studies.each do |study| + inv = study.investigation + inv.update(is_isa_json_compliant: true) + inv.save + investigations_updated += 1 + end + end + puts "...Updated #{investigations_updated} investigations" + end + private ## From e1bf41fb6c8e8f1b798c4a5aa0d1cf6f71bbfd17 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 14 Dec 2023 15:21:44 +0100 Subject: [PATCH 341/383] Add allow free text fields to template attributes --- .../templates/_template_attribute_form.html.erb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/templates/_template_attribute_form.html.erb b/app/views/templates/_template_attribute_form.html.erb index e5a7d3b6ec..ce835677d7 100644 --- a/app/views/templates/_template_attribute_form.html.erb +++ b/app/views/templates/_template_attribute_form.html.erb @@ -17,6 +17,7 @@ <% isa_tag_id = template_attribute&.isa_tag_id %> <% isa_tag_title = template_attribute&.isa_tag&.title %> <% is_parent_attribute ||= false %> +<% allow_cv_free_text = template_attribute&.allow_cv_free_text || false %> @@ -45,17 +46,25 @@ <%= select_tag "#{field_name_prefix}[sample_attribute_type_id]", options_for_select(SampleAttributeType.all.sort_by(&:title).sort_by { |t| t.default? ? 0 : 1 }.map do |t| - [t.title, t.id,{'data-is-cv': t.controlled_vocab?,'data-is-seek-sample': t.seek_sample? || t.seek_sample_multi? }] + [t.title, t.id,{'data-use-cv': t.controlled_vocab?,'data-is-seek-sample': t.seek_sample? || t.seek_sample_multi? }] end, attribute_type_id), class: 'form-control sample-type-attribute-type', data: {attr: "type"} %> -
    +

    <%= select_tag "#{field_name_prefix}[sample_controlled_vocab_id]", options_for_select(SampleControlledVocab.all.map { |scv| [scv.title, scv.id, {'data-editable': scv.can_edit?}] }, sample_controlled_vocab_id),include_blank: true, class: 'form-control controlled-vocab-selection', data: {attr: "cv_id"} %> +
    + +
    + <%= button_link_to('Edit', 'edit', '#', class:'cv-edit-button', disabled: true, target: :_blank) %> <%= create_sample_controlled_vocab_modal_button %> From 23f0f1adb7dcae01cbf68fe02b9155e55e1fdae3 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 14 Dec 2023 15:22:15 +0100 Subject: [PATCH 342/383] add allow_free_cv_text param to templates controller --- app/controllers/templates_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index b75b1d2743..b5e12e3b07 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -157,7 +157,7 @@ def template_params template_attributes_attributes: %i[id title pos required description sample_attribute_type_id isa_tag_id is_title sample_controlled_vocab_id pid - unit_id _destroy] }) + unit_id _destroy allow_cv_free_text] }) end def find_template From 230e15d38f79ecf1535385e6458d752ec8988ac9 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 14 Dec 2023 16:58:08 +0100 Subject: [PATCH 343/383] add allow_cv_free_text attribute --- app/helpers/templates_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/templates_helper.rb b/app/helpers/templates_helper.rb index 0e2b451828..cafdd9f49f 100644 --- a/app/helpers/templates_helper.rb +++ b/app/helpers/templates_helper.rb @@ -75,6 +75,7 @@ def map_template_attributes(attribute) attribute_type_id: attribute.sample_attribute_type_id, data_type: SampleAttributeType.find(attribute.sample_attribute_type_id)&.title, cv_id: attribute.sample_controlled_vocab_id, + allow_cv_free_text: attribute.allow_cv_free_text, title: attribute.title, is_title: attribute.is_title, short_name: attribute.short_name, From 1200d27e0ef481a426b7ec3f946e0e2013cd72b9 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 14 Dec 2023 17:53:41 +0100 Subject: [PATCH 344/383] add allow_cv_free_text in templates js --- app/assets/javascripts/templates.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/templates.js b/app/assets/javascripts/templates.js index 11bea4dea6..fd3c71d7e5 100644 --- a/app/assets/javascripts/templates.js +++ b/app/assets/javascripts/templates.js @@ -11,7 +11,7 @@ Templates.init = function (elem) { const columnDefs = [ { orderable: false, targets: [0, 7, 11] }, { - targets: [3, 4, 9, 10], + targets: [3, 4, 5, 10, 11], visible: false, searchable: false }, @@ -33,6 +33,7 @@ Templates.init = function (elem) { { title: "Description", width: "40%" }, { title: "attribute_type_id" }, { title: "cv_id" }, + { title: "allow_cv_free_text" }, { title: "Unit", width: "5%" }, { title: "Data type", width: "10%" }, { @@ -115,6 +116,7 @@ Templates.mapData = (data) => item.description, item.attribute_type_id, item.cv_id, + item.allow_cv_free_text, item.unit_id, item.data_type, item.is_title, @@ -232,17 +234,17 @@ const applyTemplate = () => { index++; newRow = $j(newRow.replace(/replace-me/g, index)); - $j(newRow).find('[data-attr="required"]').prop("checked", row[0]); $j(newRow).find('[data-attr="title"]').val(row[1]); $j(newRow).find('[data-attr="description"]').val(row[2]); $j(newRow).find('[data-attr="type"]').val(row[3]); $j(newRow).find('[data-attr="cv_id"]').val(row[4]); - $j(newRow).find('[data-attr="unit"]').val(row[5]); - $j(newRow).find(".sample-type-is-title").prop("checked", row[7]); - $j(newRow).find('[data-attr="pid"]').val(row[8]); - $j(newRow).find('[data-attr="isa_tag_id"]').val(row[10]); - $j(newRow).find('[data-attr="isa_tag_title"]').val(row[10]); + $j(newRow).find('[data-attr="allow_cv_free_text"]').prop("checked", row[5]); + $j(newRow).find('[data-attr="unit"]').val(row[6]); + $j(newRow).find(".sample-type-is-title").prop("checked", row[8]); + $j(newRow).find('[data-attr="pid"]').val(row[9]); + $j(newRow).find('[data-attr="isa_tag_id"]').val(row[11]); + $j(newRow).find('[data-attr="isa_tag_title"]').val(row[11]); $j(newRow).find('[data-attr="isa_tag_title"]').attr('disabled', true); // Show the CV block if cv_id is not empty From 6cff2e89eefc803fa7d3c5ea52c767e3623f3c25 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 14 Dec 2023 17:54:54 +0100 Subject: [PATCH 345/383] add allow_cv_free_text dat attribute --- app/views/sample_types/_sample_attribute_form.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/sample_types/_sample_attribute_form.html.erb b/app/views/sample_types/_sample_attribute_form.html.erb index 8aa5493470..e97eef7c7a 100644 --- a/app/views/sample_types/_sample_attribute_form.html.erb +++ b/app/views/sample_types/_sample_attribute_form.html.erb @@ -73,7 +73,7 @@
    From 6cf94836b690f6e4c539a19501a985b45e59566c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 11:03:11 +0100 Subject: [PATCH 346/383] Simplify filtering of ISA tags --- app/views/isa_studies/_sample_types_form.html.erb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/isa_studies/_sample_types_form.html.erb b/app/views/isa_studies/_sample_types_form.html.erb index 8889467eb3..d8f94b527b 100644 --- a/app/views/isa_studies/_sample_types_form.html.erb +++ b/app/views/isa_studies/_sample_types_form.html.erb @@ -4,6 +4,7 @@ <% add_attribute_row_id = 'add-attribute-row' + id_suffix %> <% main_field_name = id_suffix[1..-1] %> <% isa_element ||= "study" %> +<% sample_type_level = @isa_assay.sample_type&.isa_template&.level %> <%= f.fields_for main_field_name, sample_type do |field| %> @@ -139,10 +140,7 @@ $j('#attribute-table_sample_collection_sample_type').find('tr.sample-attribute [data-attr="isa_tag_title"]').attr('disabled', true); // Filter ISA tags for new rows - updateIsaTagSelect("study source", "#new-attribute-row_source_sample_type"); - updateIsaTagSelect("study sample", "#new-attribute-row_sample_collection_sample_type"); - updateIsaTagSelect("assay - material", "#new-attribute-row_sample_type"); - updateIsaTagSelect("assay - data file", "#new-attribute-row_sample_type"); + updateIsaTagSelect("<%= sample_type_level %>", ".sample-attribute"); }); $j(document).on('submit', function(){ // Remove the table from the form From eae11a5df788dbeae2c204977b86e17e0f12b1dc Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 11:03:34 +0100 Subject: [PATCH 347/383] Improve template error messages --- app/models/template.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/models/template.rb b/app/models/template.rb index 39dcc0ee6d..ede505d782 100644 --- a/app/models/template.rb +++ b/app/models/template.rb @@ -33,11 +33,17 @@ def resolve_inconsistencies end def validate_template_attributes - errors.add(:base, '[Template attribute]: Some attributes are missing ISA tags') unless none_empty_isa_tag + unless attributes_with_empty_isa_tag.none? + attributes_with_empty_isa_tag.map do |attribute| + errors.add("[#{:template_attributes}]:", "Attribute '#{attribute.title}' is missing an ISA tag") + end + end + if test_tag_occurences.any? test_tag_occurences.map do |tag| - errors.add(:base, - "[Template attribute]: The '#{tag}' ISA tag is not allowed to be used more then once".html_safe) + attributes_with_duplicate_tags = template_attributes.select { |tat| tat.isa_tag&.title == tag }.map(&:title) + errors.add("[#{:template_attributes}]:", + "The '#{tag}' ISA Tag was used in these attributes => #{attributes_with_duplicate_tags.inspect}. This ISA tag is not allowed to be used more then once!") end end @@ -56,8 +62,8 @@ def resolve_controlled_vocabs_inconsistencies end end - def none_empty_isa_tag - template_attributes.select { |ta| !ta.title.include?('Input') && ta.isa_tag_id.nil? }.none? + def attributes_with_empty_isa_tag + template_attributes.select { |ta| !ta.title.include?('Input') && ta.isa_tag_id.nil? } end def test_tag_occurences @@ -74,7 +80,7 @@ def test_input_occurence def test_attribute_title_uniqueness template_attribute_titles = template_attributes.map(&:title).uniq - duplicate_attributes = template_attribute_titles.map do |tat| + template_attribute_titles.map do |tat| if template_attributes.select { |ta| ta.title.downcase == tat.downcase }.map(&:title).count > 1 errors.add(:template_attributes, "Attribute names must be unique, there are duplicates of #{tat}") return tat From a4c840196adaca2830981b6ef8dc8d8ec048f53f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Dec 2023 11:06:04 +0100 Subject: [PATCH 348/383] filter ISA Tags for all attributes, not only new rows --- app/assets/javascripts/templates.js | 6 +++--- app/views/templates/edit.html.erb | 2 +- app/views/templates/new.html.erb | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/templates.js b/app/assets/javascripts/templates.js index fd3c71d7e5..3113e3084d 100644 --- a/app/assets/javascripts/templates.js +++ b/app/assets/javascripts/templates.js @@ -185,14 +185,14 @@ function get_filtered_isa_tags(level) { function updateIsaTagSelect(template_level, attribute_row) { const isa_tags = get_filtered_isa_tags(template_level); - // Remove all options first, except blank one - $j(attribute_row).find('select[data-attr="isa_tag_title"] option:not([value=""])').each(function() { + // Remove all options first from the select items that were not disabled, except blank one + $j(attribute_row).find('select[data-attr="isa_tag_title"]:not(:disabled) option:not([value=""])').each(function() { $j(this).remove(); }); // Append filtered option to a new attribute row $j.each(isa_tags, function (i, tag) { - $j(attribute_row).find('select[data-attr="isa_tag_title"]').append($j('