From eb94a7a6f0f965732fee41544fc134234c8a784c Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Fri, 6 Oct 2023 12:41:42 +0100 Subject: [PATCH 001/344] Endpoint to get workflow classes as JSON-LD In the format that should appear in a Workflow RO-Crate --- .../workflow_classes_controller.rb | 4 +++ .../workflow_classes_controller_test.rb | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/app/controllers/workflow_classes_controller.rb b/app/controllers/workflow_classes_controller.rb index 9097fd341e..2539a5617f 100644 --- a/app/controllers/workflow_classes_controller.rb +++ b/app/controllers/workflow_classes_controller.rb @@ -50,6 +50,10 @@ def edit def index @workflow_classes = WorkflowClass.order(extractor: :desc).all + respond_to do |format| + format.html + format.jsonld { render json: @workflow_classes.map(&:ro_crate_metadata), adapter: :attributes } + end end private diff --git a/test/functional/workflow_classes_controller_test.rb b/test/functional/workflow_classes_controller_test.rb index 9a115efc52..79519f0841 100644 --- a/test/functional/workflow_classes_controller_test.rb +++ b/test/functional/workflow_classes_controller_test.rb @@ -32,6 +32,40 @@ class WorkflowClassesControllerTest < ActionController::TestCase workflow_class_avatar_path(user_added_3, user_added_3.avatar, size: '32x32'), count: 1 end + test 'get index as json-ld' do + person = FactoryBot.create(:person) + disable_authorization_checks do + FactoryBot.create(:cwl_workflow_class) + FactoryBot.create(:galaxy_workflow_class) + FactoryBot.create(:nextflow_workflow_class) + WorkflowClass.create!(title: 'My Class', key: 'mine', contributor: person) + end + + login_as(person) + + get :index, format: :jsonld + + assert_response :success + classes = JSON.parse(response.body) + assert_equal 4, classes.length + + cwl = classes.detect { |c| c['@id'] == '#cwl' } + assert cwl + assert_equal 'Common Workflow Language', cwl['name'] + assert_equal 'ComputerLanguage', cwl['@type'] + assert_equal 'CWL', cwl['alternateName'] + assert_equal({ '@id' => 'https://w3id.org/cwl/v1.0/' }, cwl['identifier']) + assert_equal({ '@id' =>'https://www.commonwl.org/' }, cwl['url']) + + assert classes.detect { |c| c['@id'] == '#galaxy' } + assert classes.detect { |c| c['@id'] == '#nextflow' } + + user_added = classes.detect { |c| c['@id'] == '#mine' } + assert_equal 'My Class', user_added['name'] + refute user_added.key?('identifier') + refute user_added.key?('url') + end + test 'admin can edit any workflow class' do person = FactoryBot.create(:person) core_type, c1, c2 = nil From 86279fc1d5008927bfef146f5d1ae274318068cd Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 10:50:09 +0100 Subject: [PATCH 002/344] Add migration for assay streams --- db/migrate/20240112141513_add_assay_stream_id_to_assay.rb | 6 ++++++ db/schema.rb | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240112141513_add_assay_stream_id_to_assay.rb diff --git a/db/migrate/20240112141513_add_assay_stream_id_to_assay.rb b/db/migrate/20240112141513_add_assay_stream_id_to_assay.rb new file mode 100644 index 0000000000..6220df0185 --- /dev/null +++ b/db/migrate/20240112141513_add_assay_stream_id_to_assay.rb @@ -0,0 +1,6 @@ +class AddAssayStreamIdToAssay < ActiveRecord::Migration[6.1] + def change + add_column :assays, :assay_stream_id, :integer + add_index :assays, :assay_stream_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 22e12632eb..2f51e08778 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_12_18_133053) do +ActiveRecord::Schema.define(version: 2024_01_12_141513) do create_table "activity_logs", id: :integer, force: :cascade do |t| t.string "action" @@ -189,6 +189,8 @@ t.string "deleted_contributor" t.integer "sample_type_id" t.integer "position" + t.integer "assay_stream_id" + t.index ["assay_stream_id"], name: "index_assays_on_assay_stream_id" t.index ["sample_type_id"], name: "index_assays_on_sample_type_id" end From 50e5728cc1b50eefe199a0df345dc6480b801ae7 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 10:50:27 +0100 Subject: [PATCH 003/344] Add new Assay Class --- app/models/assay_class.rb | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index a7a72dc0fa..956488fbac 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -1,31 +1,38 @@ class AssayClass < ApplicationRecord - - #this returns an instance of AssayClass according to one of the types "experimental" or "modelling" - #if there is not a match nil is returned - def self.for_type type - keys={"experimental"=>"EXP","modelling"=>"MODEL"} - return AssayClass.find_by(key: keys[type]) + # this returns an instance of AssayClass according to one of the types "experimental" or "modelling" + # if there is not a match nil is returned + def self.for_type(type) + keys = { "experimental": 'EXP', "modelling": 'MODEL', 'assay_stream': 'ASS' } + AssayClass.find_by(key: keys[type]) end def self.experimental - self.for_type('experimental') + for_type('experimental') end def self.modelling - self.for_type('modelling') + for_type('modelling') + end + + def self.assay_stream + for_type('assay_stream') end def is_modelling? - key == "MODEL" + key == 'MODEL' end def is_experimental? key == 'EXP' end + def is_assay_stream? + key == 'ASS' + end + # for cases where a longer more descriptive key is useful, but can't rely on the title # which may have been changed over time def long_key - {'EXP'=>'Experimental Assay','MODEL'=>'Modelling Analysis'}[key] + { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'ASS': 'Assay Stream' }[key] end end From ada37ab192c1155f651ebd5bb73bc0f26cc19b06 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 10:50:59 +0100 Subject: [PATCH 004/344] Add self-referencing association to assays --- app/models/assay.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/assay.rb b/app/models/assay.rb index e2fe2430fa..cb84abdbf4 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -25,6 +25,9 @@ class Assay < ApplicationRecord belongs_to :sample_type + has_many :child_assays, class_name: 'Assay', foreign_key: 'assay_stream_id' + belongs_to :assay_stream, class_name: 'Assay', optional: true + belongs_to :assay_class has_many :assay_organisms, dependent: :destroy, inverse_of: :assay has_many :organisms, through: :assay_organisms, inverse_of: :assays @@ -65,6 +68,10 @@ class Assay < ApplicationRecord enforce_authorization_on_association :study, :view + def is_assay_stream? + child_assays.any? + end + def previous_linked_assay_sample_type sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.linked_sample_type end From 4109b53b1e5385cc9745ff59b4994a006ca14d7a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 13:26:33 +0100 Subject: [PATCH 005/344] Modify treeview to show extra assay stream level --- lib/treeview_builder.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/treeview_builder.rb b/lib/treeview_builder.rb index d64e6f4217..3996b2da2b 100644 --- a/lib/treeview_builder.rb +++ b/lib/treeview_builder.rb @@ -14,9 +14,22 @@ def build_tree_data investigation_items = [] @project.investigations.map do |investigation| - investigation.studies.map do |study| - assay_items = study.assays.map { |assay| build_assay_item(assay) } - study_items << build_study_item(study, assay_items) + if investigation.is_isa_json_compliant? + investigation.studies.map do |study| + assay_stream_items = study.assays.select { |assay| assay.is_assay_stream? }.map do |assay_stream| + assay_items = assay_stream.child_assays.map do |child_assay| + build_assay_item(child_assay) + end + build_assay_stream_item(assay_stream, assay_items) + end + + study_items << build_study_item(study, assay_stream_items) + end + else + investigation.studies.map do |study| + assay_items = study.assays.map { |assay| build_assay_item(assay) } + study_items << build_study_item(study, assay_items) + end end investigation_items << build_investigation_item(investigation, study_items) study_items = [] @@ -125,6 +138,11 @@ def build_study_item(study, assay_items) children: isa_study_elements(study) + assay_items, resource: study }) end + def build_assay_stream_item(assay_stream, child_assays) + create_node({ text: assay_stream.title, _type: 'assay', label: 'Assay Stream', _id: assay_stream.id, a_attr: BOLD, + children: isa_assay_elements(assay_stream) + child_assays, resource: assay_stream }) + end + def build_assay_item(assay) create_node({ text: assay.title, _type: 'assay', label: 'Assay', _id: assay.id, a_attr: BOLD, children: isa_assay_elements(assay), resource: assay }) From d37ef6f4a642f2a695f60cbc99d72fd775e9ed07 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 16 Jan 2024 14:38:59 +0100 Subject: [PATCH 006/344] Change button text --- app/views/assays/_buttons.html.erb | 10 +++++++--- app/views/studies/_buttons.html.erb | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index ac5b58e309..3d7659a054 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -21,10 +21,14 @@ <% if item.can_edit? %> <% 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? %> + <% valid_study = item&.study&.is_isa_json_compliant? %> + <% valid_assay = item&.is_isa_json_compliant? %> <% if valid_study && valid_assay %> - <%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page])) %> + <% if item&.is_assay_stream? %> + <%= button_link_to("Design #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page])) %> + <% else %> + <%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page])) %> + <% end %> <% end %> <% else %> <%= add_new_item_to_dropdown(item) %> diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index b27edc214f..50e728dca8 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -21,7 +21,7 @@ <% if item.can_edit? -%> <% 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)) %> + <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_source: true)) %> <% end -%> <% else -%> <%= add_new_item_to_dropdown(item) %> From ba27eb36490de5f56bc2ef20401c353d45e63cbf Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 16 Jan 2024 14:40:15 +0100 Subject: [PATCH 007/344] Add is_assay_stream function to assay model --- app/models/assay.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index cb84abdbf4..ece4e750a1 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -69,7 +69,7 @@ class Assay < ApplicationRecord enforce_authorization_on_association :study, :view def is_assay_stream? - child_assays.any? + assay_class.is_assay_stream? end def previous_linked_assay_sample_type @@ -102,7 +102,7 @@ def state_allows_delete?(*args) end def is_isa_json_compliant? - investigation.is_isa_json_compliant? && !sample_type.nil? + investigation.is_isa_json_compliant? && (!sample_type.nil? || is_assay_stream?) end # returns true if this is a modelling class of assay From f09cd0d6de6a9d17eab326e8df9f919ddbf90005 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 11:28:55 +0100 Subject: [PATCH 008/344] Make child assays depend on the essay stream for deletion --- app/models/assay.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index ece4e750a1..575afce5d5 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -25,7 +25,7 @@ class Assay < ApplicationRecord belongs_to :sample_type - has_many :child_assays, class_name: 'Assay', foreign_key: 'assay_stream_id' + has_many :child_assays, class_name: 'Assay', foreign_key: 'assay_stream_id', dependent: :destroy belongs_to :assay_stream, class_name: 'Assay', optional: true belongs_to :assay_class From 6d3e23287f6ed21771cb74c157d89c9a521ee384 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 11:38:02 +0100 Subject: [PATCH 009/344] Update the controller to handle assay streams --- app/controllers/isa_assays_controller.rb | 35 ++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index df0643ba43..8bdb451a7d 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -6,14 +6,18 @@ class IsaAssaysController < ApplicationController before_action :find_requested_item, only: %i[edit update] def new - @isa_assay = IsaAssay.new + if params[:is_assay_stream] + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'ASS').id } }) + else + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').id } }) + end end def create @isa_assay = IsaAssay.new(isa_assay_params) update_sharing_policies @isa_assay.assay @isa_assay.assay.contributor = current_person - @isa_assay.sample_type.contributor = User.current_user.person + @isa_assay.sample_type.contributor = User.current_user.person if isa_assay_params[:sample_type] if @isa_assay.save redirect_to single_page_path(id: @isa_assay.assay.projects.first, item_type: 'assay', item_id: @isa_assay.assay, notice: 'The ISA assay was created successfully!') @@ -27,7 +31,11 @@ def create def edit # let edit the assay if the sample_type is not authorized - @isa_assay.sample_type = nil unless requested_item_authorized?(@isa_assay.sample_type) + if @isa_assay.assay.is_assay_stream? + @isa_assay.sample_type = nil + else + @isa_assay.sample_type = nil unless requested_item_authorized?(@isa_assay.sample_type) + end respond_to do |format| format.html @@ -38,9 +46,11 @@ def update @isa_assay.assay.attributes = isa_assay_params[:assay] # update the sample_type - if requested_item_authorized?(@isa_assay.sample_type) - @isa_assay.sample_type.update(isa_assay_params[:sample_type]) - @isa_assay.sample_type.resolve_inconsistencies + unless @isa_assay.assay.is_assay_stream? + if requested_item_authorized?(@isa_assay.sample_type) + @isa_assay.sample_type.update(isa_assay_params[:sample_type]) + @isa_assay.sample_type.resolve_inconsistencies + end end if @isa_assay.save @@ -87,10 +97,12 @@ def assay_params { data_files_attributes: %i[asset_id direction relationship_type_id] }, { publication_ids: [] }, { extended_metadata_attributes: determine_extended_metadata_keys(:assay) }, - { discussion_links_attributes: %i[id url label _destroy] }] + { discussion_links_attributes: %i[id url label _destroy] }, :assay_stream_id] end def sample_type_params(params) + return [] unless params[:sample_type] + attributes = params[:sample_type][:sample_attributes] if attributes params[:sample_type][:sample_attributes_attributes] = [] @@ -128,9 +140,16 @@ def set_up_instance_variable end def find_requested_item - @isa_assay = IsaAssay.new + if params[:is_assay_stream] + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'ASS').id } }) + else + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').id } }) + end @isa_assay.populate(params[:id]) + # Should not deal with sample type if assay has assay_class assay stream + return if @isa_assay.assay.is_assay_stream? + if @isa_assay.sample_type.nil? || !requested_item_authorized?(@isa_assay.assay) flash[:error] = "You are not authorized to edit this #{t('isa_assay')}" flash[:error] = 'Resource not found.' if @isa_assay.sample_type.nil? From 79379c954f403428dd21e0e11be94b8f3924bf82 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 14:39:29 +0100 Subject: [PATCH 010/344] Modify helper function to accept alternative names --- app/helpers/images_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/images_helper.rb b/app/helpers/images_helper.rb index a126c4a054..bc23201ea8 100644 --- a/app/helpers/images_helper.rb +++ b/app/helpers/images_helper.rb @@ -70,8 +70,8 @@ def append_size_parameter(url, size) url end - def delete_icon(model_item, user, confirm_msg='Are you sure?') - item_name = text_for_resource model_item + def delete_icon(model_item, user, confirm_msg='Are you sure?', alternative_item_name=nil) + item_name = alternative_item_name.nil? ? (text_for_resource model_item) : alternative_item_name if model_item.can_delete?(user) fullURL = url_for(model_item) From 9cc63e3f71aa512f491115b8dc5060a8a3d68b42 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 14:40:01 +0100 Subject: [PATCH 011/344] Modify buttons for assay streams --- app/views/assays/_buttons.html.erb | 26 ++++++++++++++++++++------ app/views/studies/_buttons.html.erb | 9 +++++++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 3d7659a054..e247754156 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -1,4 +1,14 @@ -<% assay_word = item.is_modelling? ? t('assays.modelling_analysis') : t('assays.assay') %> +<% assay_word ||= + if item.is_modelling? + t('assays.modelling_analysis') + elsif item.is_assay_stream? + 'Assay Stream' + elsif Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? + t('isa_assay') + else + t('assays.assay') + end +%> <%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %> <% if Seek::Config.project_single_page_enabled %> @@ -25,9 +35,9 @@ <% valid_assay = item&.is_isa_json_compliant? %> <% if valid_study && valid_assay %> <% if item&.is_assay_stream? %> - <%= button_link_to("Design #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page])) %> + <%= button_link_to("Design #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.id)) %> <% else %> - <%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page])) %> + <%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id)) %> <% end %> <% end %> <% else %> @@ -42,7 +52,11 @@ <%= item_actions_dropdown do %> <% if item.can_edit? %> <% 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')}") -%>
  • + <% if item&.is_assay_stream? %> +
  • <%= 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], is_assay_stream: true), "Edit #{assay_word}", nil, "Edit #{assay_word}") -%>
  • + <% else %> +
  • <%= 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], assay_stream_id: item.assay_stream_id), "Edit #{assay_word}", nil, "Edit #{assay_word}") -%>
  • + <% end %> <% else %>
  • <%= image_tag_for_key('edit', edit_assay_path(item), "Edit #{assay_word}", nil, "Edit #{assay_word}") -%>
  • <% end %> @@ -50,8 +64,8 @@ <% if item.can_manage? -%>
  • <%= image_tag_for_key('manage', manage_assay_path(item), "Manage #{assay_word}", nil, "Manage #{assay_word}") -%>
  • - <%= render :partial => 'snapshots/new_snapshot_link', :locals => {:item => item} %> + <%= render partial: 'snapshots/new_snapshot_link', locals: {item: item} %> <% end -%> - <%= delete_icon(item, current_user) %> + <%= delete_icon(item, current_user, 'Are you sure?', assay_word) %> <% end %> diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index 50e728dca8..4b63c58c86 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -21,7 +21,7 @@ <% if item.can_edit? -%> <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <% if item&.sample_types.present? %> - <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_source: true)) %> + <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_assay_stream: true)) %> <% end -%> <% else -%> <%= add_new_item_to_dropdown(item) %> @@ -38,7 +38,12 @@ <% end %> <% if item.can_manage? -%> -
  • <%= image_tag_for_key('manage', manage_study_path(item), "Manage #{t('study')}", nil, "Manage #{t('study')}") -%>
  • + <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> +
  • <%= image_tag_for_key('manage', manage_study_path(item), "Manage #{t('study')}", nil, "Manage #{t('isa_study')}") -%>
  • + <% else %> +
  • <%= image_tag_for_key('manage', manage_study_path(item), "Manage #{t('study')}", nil, "Manage #{t('study')}") -%>
  • + <% end %> + <%= render :partial => 'snapshots/new_snapshot_link', :locals => { :item => item } %> <% end -%> From c6fdcb7f3b72bf58b66abbedf15bdb8c6362f439 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 14:43:02 +0100 Subject: [PATCH 012/344] Change model for assay streams --- app/forms/isa_assay.rb | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/app/forms/isa_assay.rb b/app/forms/isa_assay.rb index d5817e0051..a586c75851 100644 --- a/app/forms/isa_assay.rb +++ b/app/forms/isa_assay.rb @@ -3,29 +3,31 @@ class IsaAssay attr_accessor :assay, :sample_type, :input_sample_type_id - validates_presence_of :assay, :sample_type, :input_sample_type_id + validates_presence_of :assay validate :validate_objects def initialize(params = {}) @assay = Assay.new(params[:assay] || {}) - @sample_type = SampleType.new((params[:sample_type] || {}).merge({ project_ids: @assay.project_ids })) - @sample_type.sample_attributes.build(is_title: true, required: true) unless params[:sample_type] - @assay.sample_type = @sample_type - @assay.assay_class = AssayClass.for_type('experimental') + unless @assay.is_assay_stream? + @sample_type = SampleType.new((params[:sample_type] || {}).merge({ project_ids: @assay.project_ids })) + @sample_type.sample_attributes.build(is_title: true, required: true) unless params[:sample_type] + @assay.sample_type = @sample_type + end + @input_sample_type_id = params[:input_sample_type_id] end def save if valid? - if @assay.new_record? + if @assay.new_record? && !@assay.is_assay_stream? # connect the sample type multi link attribute to the last sample type of the assay's study input_attribute = @sample_type.sample_attributes.detect(&:seek_sample_multi?) input_attribute.linked_sample_type_id = @input_sample_type_id title = SampleType.find(@input_sample_type_id).sample_attributes.detect(&:is_title).title input_attribute.title = "Input (#{title})" end + @sample_type.save unless @assay.is_assay_stream? @assay.save - @sample_type.save else false end @@ -54,6 +56,12 @@ def populate(id) def validate_objects @assay.errors.each { |e| errors.add(:base, "[Assay]: #{e.full_message}") } unless @assay.valid? + return if @assay.is_assay_stream? + + errors.add(:base, '[Assay]: The assay is missing a sample type.') if @sample_type.nil? + + return unless @sample_type + @sample_type.errors.full_messages.each { |e| errors.add(:base, "[Sample type]: #{e}") } unless @sample_type.valid? unless @sample_type.sample_attributes.any?(&:seek_sample_multi?) @@ -66,22 +74,22 @@ def validate_objects unless @sample_type.sample_attributes.select { |a| a.title.include?('Input') && a.isa_tag.nil? }.one? errors.add(:base, - "[Sample type]: Should have exactly one attribute with the title 'Input' and no ISA tag".html_safe) + "[Sample type]: Should have exactly one attribute with the title 'Input' and no ISA tag".html_safe) end if @sample_type.sample_attributes.select { |a| !a.title.include?('Input') && a.isa_tag.nil? }.any? errors.add(:base, - "[Sample type]: All attributes should have an ISA Tag except for the 'Input' attribute (hidden)".html_safe) + "[Sample type]: All attributes should have an ISA Tag except for the 'Input' attribute (hidden)".html_safe) end assay_sample_or_datafile_attributes = @sample_type.sample_attributes.select do |a| a.isa_tag&.isa_other_material? || a.isa_tag&.isa_data_file? end + unless assay_sample_or_datafile_attributes.one? errors.add(:base, - "[Sample type]: Should have exactly one attribute with the 'data_file' or 'other_material' ISA tag selected".html_safe) + "[Sample type]: Should have exactly one attribute with the 'data_file' or 'other_material' ISA tag selected".html_safe) end - errors.add(:base, '[Input Assay]: Input Assay is not provided') if @input_sample_type_id.blank? end end From b196d09101de201da47b4e0d932fefaff550a95f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 15:03:34 +0100 Subject: [PATCH 013/344] Modify form for handling assay streams --- app/views/isa_assays/_form.html.erb | 62 ++++++++++++++++------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 35d5b38d2c..13a77cced1 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -1,35 +1,40 @@ <% assay = params[:isa_assay][:assay] if params.dig(:isa_assay, :assay) %> <% study = Study.find(params[:study_id] || assay[:study_id]) %> <% -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? - if params[:source_assay_id] - assay_position = source_assay.position + 1 - else - assay_position = 0 - end -else - assay_position = @isa_assay.assay.position -end - -# assay_position = params[:source_assay_id] ? source_assay.position + 1 : 0 -input_sample_type_id ||= - if params[:is_source] - study.sample_types.second.id + source_assay = Assay.find(params[:source_assay_id]) if params[:source_assay_id] + assay_stream_id = params[:assay_stream_id] if params[:assay_stream_id] + + if @isa_assay.assay.new_record? + if params[:is_assay_stream] + assay_position = 0 + assay_class_id = AssayClass.find_by(key: 'ASS')&.id + is_assay_stream = true + else + assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 + assay_class_id = AssayClass.find_by(key: 'EXP')&.id + is_assay_stream = false + end else - source_assay.sample_type.id if params[:source_assay_id] + assay_position = @isa_assay.assay.position + assay_class_id = @isa_assay.assay.assay_class_id + is_assay_stream = @isa_assay.assay.is_assay_stream? end -show_extended_metadata = - if params[:is_source] - true - elsif source_assay&.position&.zero? && !@isa_assay.assay.new_record? - true # Custom metadata should be shown in edit as well if assay position is 0. - else - false - end + input_sample_type_id ||= + if is_assay_stream || source_assay&.assay_class&.is_assay_stream? + study.sample_types.second.id + else + source_assay.sample_type.id if source_assay + end + + show_extended_metadata = + if is_assay_stream + true + elsif source_assay&.position&.zero? && !@isa_assay.assay.new_record? + true # Custom metadata should be shown in edit as well if assay position is 0. + else + false + end %> <%= error_messages_for :isa_assay %> @@ -60,7 +65,8 @@ show_extended_metadata = <%= assay_fields.number_field :position, value: assay_position || study.assays.length -%> - <%= assay_fields.hidden_field :assay_class_id -%> + <%= assay_fields.hidden_field :assay_stream_id, value: assay_stream_id -%> + <%= assay_fields.hidden_field :assay_class_id, value: assay_class_id -%> <% if User.current_user -%> <%= render partial: 'assets/manage_specific_attributes', locals:{f:assay_fields} if show_form_manage_specific_attributes? %> @@ -75,7 +81,7 @@ show_extended_metadata = <%= f.hidden_field :input_sample_type_id, value: input_sample_type_id -%> -<% if @isa_assay.sample_type %> +<% unless is_assay_stream %> <%= folding_panel("Define #{t(:sample_type)} for #{t(:assay)}") do %> <%= render partial: 'isa_studies/sample_types_form', locals: {f: f, sample_type: @isa_assay.sample_type, id_suffix: "_sample_type", isa_element: "assay", action: action} %> <% end %> From dcb5794812e3fd4e2d08e53951a483bcf7e6b78b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 15:23:52 +0100 Subject: [PATCH 014/344] Seed Assay Stream class --- db/seeds/017_minimal_starter_isa_templates.seeds.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index e78e477916..f44069c7e7 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -200,3 +200,8 @@ end puts 'Seeded minimal templates for organizing ISA JSON compliant experiments.' + +disable_authorization_checks do + AssayClass.find_or_create_by(title: 'Assay Stream', key: 'ASS', + description: 'Special type of class that is user in Single Page, specifying this is a container for a stream of assays') +end From 2c583d0235c8fd3c9eebd0d4651881b7cd465e10 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 16:34:33 +0100 Subject: [PATCH 015/344] Adapt isa exporter to handle assay streams --- app/models/assay.rb | 7 ---- app/models/study.rb | 4 ++ lib/isa_exporter.rb | 89 +++++++++++++++++++++------------------------ 3 files changed, 45 insertions(+), 55 deletions(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index 575afce5d5..f7210d3235 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -80,13 +80,6 @@ def has_linked_child_assay? sample_type&.linked_sample_attributes&.any? end - # Fetches the assay which is linked through linked_sample_attributes (Single Page specific method) - def linked_assay - sample_type.linked_sample_attributes - .select { |lsa| lsa.isa_tag.nil? && lsa.title.include?('Input') } - .first&.sample_type&.assays&.first - end - def default_contributor User.current_user.try :person end diff --git a/app/models/study.rb b/app/models/study.rb index f7b6371c73..467a550083 100644 --- a/app/models/study.rb +++ b/app/models/study.rb @@ -36,6 +36,10 @@ class Study < ApplicationRecord has_many "related_#{type.pluralize}".to_sym, -> { distinct }, through: :assays, source: type.pluralize.to_sym end + def assay_streams + assays.select(&:is_assay_stream?) + end + def assets related_data_files + related_sops + related_models + related_publications + related_documents end diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 108144752a..f015bc5d8d 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -117,7 +117,7 @@ def convert_study(study) # raise "The Study with the title '#{study.title}' does not have any SOP" if study.sops.blank? protocols << convert_protocol(study.sops, study.id, with_tag_protocol_study, with_tag_parameter_value_study) - study.assays.each do |a| + study.assay_streams.map(&:child_assays).flatten.each do |a| # There should be only one attribute with isa_tag == protocol protocol_attribute = a.sample_type.sample_attributes.detect { |sa| sa.isa_tag&.isa_protocol? } with_tag_parameter_value = a.sample_type.sample_attributes.select { |sa| sa.isa_tag&.isa_parameter_value? } @@ -134,20 +134,7 @@ def convert_study(study) 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| - last_assay = assay_stream.first - until last_assay.linked_assay.nil? - linked_assay = last_assay.linked_assay - assay_stream.push(linked_assay) - last_assay = linked_assay - end - assay_stream - end - - isa_study[:assays] = assay_streams.map { |assay_stream| convert_assays(assay_stream) }.compact - + isa_study[:assays] = study.assay_streams.map { |assay_stream| convert_assays(assay_stream) }.compact isa_study[:factors] = [] isa_study[:unitCategories] = [] @@ -163,28 +150,25 @@ def convert_annotation(term_uri) isa_annotation end - def convert_assay_comments(assays) + def convert_assay_comments(assay_stream) assay_comments = [] - assay_streams = assays.select { |a| a.position.zero? } - assay_stream_id = assays.pluck(:id).join('_') - - linked_assays = assays.map { |assay| { 'id': assay.id, 'title': assay.title } }.to_json - - assay_streams.map do |assay| - study_id = assay.study_id - next if assay.extended_metadata.nil? - - json = JSON.parse(assay.extended_metadata&.json_metadata) - cm_attributes = assay.extended_metadata.extended_metadata_attributes - cm_id = assay.extended_metadata&.id - json.map do |key, val| - cma_id = cm_attributes.detect { |cma| cma.title == key }&.id - assay_comments.push({ - '@id': "#assay_comment/#{[study_id, assay_stream_id, cm_id, cma_id].join('_')}", - 'name': key, - 'value': val - }) - end + assay_stream_id = assay_stream&.id + + linked_assays = assay_stream.child_assays.map { |assay| { 'id': assay.id, 'title': assay.title } }.to_json + + study_id = assay_stream.study_id + return [] if assay_stream.extended_metadata.nil? + + json = JSON.parse(assay_stream.extended_metadata&.json_metadata) + cm_attributes = assay_stream.extended_metadata.extended_metadata_attributes + cm_id = assay_stream.extended_metadata&.id + json.map do |key, val| + cma_id = cm_attributes.detect { |cma| cma.title == key }&.id + assay_comments.push({ + '@id': "#assay_comment/#{[study_id, assay_stream_id, cm_id, cma_id].join('_')}", + 'name': key, + 'value': val + }) end assay_comments.push({ @@ -195,35 +179,37 @@ def convert_assay_comments(assays) assay_comments.compact end - def convert_assays(assays) - return unless assays.all? { |a| a.can_view?(@current_user) } + def convert_assays(assay_stream) + child_assays = assay_stream.child_assays + return unless assay_stream.can_view?(@current_user) + return unless child_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 + all_sample_types = child_assays.map(&:sample_type).compact + # first_assay = assays.detect { |s| s.position.zero? } + # raise 'No assay could be found!' unless first_assay - stream_name = "assays_#{assays.pluck(:id).join('_')}" - assay_comments = convert_assay_comments(assays) + stream_name = "assays_#{child_assays.pluck(:id).join('_')}" + assay_comments = convert_assay_comments(assay_stream) # Retrieve assay_stream if stream_name_comment = assay_comments.detect { |ac| ac[:name] == 'assay_stream' } stream_name = stream_name_comment[:value] unless stream_name_comment.nil? isa_assay = {} - isa_assay['@id'] = "#assay/#{assays.pluck(:id).join('_')}" + isa_assay['@id'] = "#assay/#{child_assays.pluck(:id).join('_')}" isa_assay[:filename] = "a_#{stream_name.downcase.tr(" ", "_")}.txt" isa_assay[:measurementType] = { annotationValue: '', termSource: '', termAccession: '' } isa_assay[:technologyType] = { annotationValue: '', termSource: '', termAccession: '' } isa_assay[:comments] = assay_comments isa_assay[:technologyPlatform] = '' - isa_assay[:characteristicCategories] = convert_characteristic_categories(nil, assays) + isa_assay[:characteristicCategories] = convert_characteristic_categories(nil, child_assays) isa_assay[:materials] = { # Here, the first assay's samples will be enough - samples: assay_samples(first_assay), # the samples from study level that are referenced in this assay's samples, + samples: assay_samples(child_assays.first), # the samples from study level that are referenced in this assay's samples, otherMaterials: convert_other_materials(all_sample_types) } isa_assay[:processSequence] = - assays.map { |a| convert_process_sequence(a.sample_type, a.sops.map(&:id).join("_"), a.id) }.flatten + child_assays.map { |a| convert_process_sequence(a.sample_type, a.sops.map(&:id).join("_"), a.id) }.flatten isa_assay[:dataFiles] = convert_data_files(all_sample_types) isa_assay[:unitCategories] = [] isa_assay @@ -278,7 +264,14 @@ def convert_publication(publication) def convert_ontologies source_ontologies = [] - sample_types = @investigation.studies.map(&:sample_types) + @investigation.assays.map(&:sample_type) + sample_types = @investigation.studies.map(&:sample_types) + @investigation.assays + .select(&:is_assay_stream?) + .map(&:child_assays) + .compact + .flatten + .map(&:sample_type) + .compact + sample_types.flatten.each do |sa| sa.sample_attributes.each do |atr| source_ontologies << atr.sample_controlled_vocab.source_ontology if atr.ontology_based? From 158cdd40d2b4a9d6c8ed923026a44e2f9412cb84 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 17:22:34 +0100 Subject: [PATCH 016/344] Add rake upgrade task to add Assay Stream AssayClass --- db/seeds/017_minimal_starter_isa_templates.seeds.rb | 2 ++ lib/tasks/seek_upgrades.rake | 1 + 2 files changed, 3 insertions(+) diff --git a/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index f44069c7e7..2b1929fbfb 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -205,3 +205,5 @@ AssayClass.find_or_create_by(title: 'Assay Stream', key: 'ASS', description: 'Special type of class that is user in Single Page, specifying this is a container for a stream of assays') end + +puts 'Seeded Extra Assay Class' diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index fb779e184a..6497f980b5 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 + db:seed:017_minimal_starter_isa_templates recognise_isa_json_compliant_items ] From 0da942e4ae223f4350fe9cd38ab46727974080b0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 17:22:57 +0100 Subject: [PATCH 017/344] Add unit test --- test/factories/assays.rb | 15 +++++++++++++++ test/fixtures/assay_classes.yml | 10 +++++++--- test/unit/assay_test.rb | 9 +++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/test/factories/assays.rb b/test/factories/assays.rb index c0ab8efcf5..eab8a81d18 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -13,6 +13,12 @@ description { "An experimental assay class description" } end + factory(:assay_stream_class, class: AssayClass) do + title { 'Assay Stream' } + key { 'ASS' } + description { "An assay stream class description" } + end + # SuggestedTechnologyType factory(:suggested_technology_type) do sequence(:label) { | n | "A TechnologyType#{n}" } @@ -104,6 +110,15 @@ end end + factory(:assay_stream, parent: :assay_base) do + title { 'Assay Stream' } + description { 'A holder assay holding multiple child assays' } + association :assay_class, factory: :assay_stream_class + after(:build) do |assay| + assay.study = FactoryBot.create(:isa_json_compliant_study) + end + end + # AssayAsset factory :assay_asset do association :assay diff --git a/test/fixtures/assay_classes.yml b/test/fixtures/assay_classes.yml index 6811c4bdb0..fc730abd78 100644 --- a/test/fixtures/assay_classes.yml +++ b/test/fixtures/assay_classes.yml @@ -1,7 +1,11 @@ experimental_assay_class: title: <%= I18n.t('assays.experimental_assay') %> key: EXP - -modelling_assay_class: + +modelling_assay_class: title: <%= I18n.t('assays.modelling_analysis') %> - key: MODEL \ No newline at end of file + key: MODEL + +assay_stream_class: + title: 'Assay Stream' + key: 'ASS' diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index a8a9199528..72907b408a 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -760,4 +760,13 @@ def new_valid_assay isa_json_compliant_assay = FactoryBot.create(:isa_json_compliant_assay) assert isa_json_compliant_assay.is_isa_json_compliant? end + + test 'is assay stream' do + isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study) + assay_stream = FactoryBot.create(:assay_stream, study: isa_json_compliant_study) + assert assay_stream.is_assay_stream? + + default_assay = FactoryBot.create(:assay) + refute default_assay.is_assay_stream? + end end From 9407039c4fa457ea6182152a0d6cc8b2506406b2 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 17:44:53 +0100 Subject: [PATCH 018/344] Test the button text on the assays controller --- app/models/assay_class.rb | 2 +- test/functional/assays_controller_test.rb | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index 956488fbac..bc2a45728f 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -1,5 +1,5 @@ class AssayClass < ApplicationRecord - # this returns an instance of AssayClass according to one of the types "experimental" or "modelling" + # this returns an instance of AssayClass according to one of the types "experimental", "modelling" or "assay_stream" # if there is not a match nil is returned def self.for_type(type) keys = { "experimental": 'EXP', "modelling": 'MODEL', 'assay_stream': 'ASS' } diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 77253f7191..ede6bcfa51 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -2012,9 +2012,21 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links 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) + assay_stream = FactoryBot.create(:assay_stream, contributor: current_user.person) + assay1 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: assay_stream.study, assay_stream:) + assay2 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: assay_stream.study, assay_stream:) - get :show, params: { id: assay } + get :show, params: { id: assay_stream } + assert_response :success + + assert_select 'a', text: /Design #{I18n.t('assay')}/i, count: 1 + + get :show, params: { id: assay1 } + assert_response :success + + assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1 + + get :show, params: { id: assay2 } assert_response :success assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1 From 299dfefd0c1f350edce7b1e5f52447ffff6c0597 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 18:08:12 +0100 Subject: [PATCH 019/344] Fix studiescontroller test --- test/functional/studies_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb index 69a5ef4419..a29d1c8891 100644 --- a/test/functional/studies_controller_test.rb +++ b/test/functional/studies_controller_test.rb @@ -2022,7 +2022,7 @@ def test_should_show_investigation_tab get :show, params: { id: study } assert_response :success - assert_select 'a', text: /Design #{I18n.t('assay')}/i, count: 1 + assert_select 'a', text: /Design #{I18n.t('assay')} Stream/i, count: 1 assert_select 'a', text: /Add new #{I18n.t('assay')}/i, count: 0 end end From bc6cb12830756670b0d955d13fb2c2d33c80e494 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 21:41:01 +0100 Subject: [PATCH 020/344] Fix for_type function in AssayClass --- app/controllers/isa_assays_controller.rb | 8 ++++---- app/models/assay_class.rb | 6 +++--- app/views/isa_assays/_form.html.erb | 4 ++-- config/default_data/assay_classes.yml | 7 ++++++- config/locales/en.yml | 1 + db/seeds/017_minimal_starter_isa_templates.seeds.rb | 7 ------- lib/tasks/seek_upgrades.rake | 2 +- test/fixtures/assay_classes.yml | 4 ++-- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 8bdb451a7d..0a44ce8d18 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -7,9 +7,9 @@ class IsaAssaysController < ApplicationController def new if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'ASS').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) end end @@ -141,9 +141,9 @@ def set_up_instance_variable def find_requested_item if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'ASS').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) end @isa_assay.populate(params[:id]) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index bc2a45728f..37a148de50 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -2,8 +2,8 @@ class AssayClass < ApplicationRecord # this returns an instance of AssayClass according to one of the types "experimental", "modelling" or "assay_stream" # if there is not a match nil is returned def self.for_type(type) - keys = { "experimental": 'EXP', "modelling": 'MODEL', 'assay_stream': 'ASS' } - AssayClass.find_by(key: keys[type]) + keys = { "experimental": 'EXP', "modelling": 'MODEL', "assay_stream": 'ASS' } + AssayClass.find_by(key: keys[type.to_sym]) end def self.experimental @@ -15,7 +15,7 @@ def self.modelling end def self.assay_stream - for_type('assay_stream') + for_type('assaystream') end def is_modelling? diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 13a77cced1..13460bd080 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -7,11 +7,11 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] assay_position = 0 - assay_class_id = AssayClass.find_by(key: 'ASS')&.id + assay_class_id = AssayClass.for_type('assay_stream').id is_assay_stream = true else assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 - assay_class_id = AssayClass.find_by(key: 'EXP')&.id + assay_class_id = AssayClass.for_type('experimental').id is_assay_stream = false end else diff --git a/config/default_data/assay_classes.yml b/config/default_data/assay_classes.yml index 94a08a2a99..56c2c6790e 100644 --- a/config/default_data/assay_classes.yml +++ b/config/default_data/assay_classes.yml @@ -6,4 +6,9 @@ experimental_assay_class: modelling_assay_class: id: 2 title: <%= I18n.t('assays.modelling_analysis') %> - key: MODEL \ No newline at end of file + key: MODEL + +assay_stream_class: + id: 3 + title: <%= I18n.t('assays.assay_stream') %> + key: ASS diff --git a/config/locales/en.yml b/config/locales/en.yml index ced43e1a21..8880ff79e8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -16,6 +16,7 @@ en: assay: "Assay" experimental_assay: "Experimental assay" modelling_analysis: "Modelling analysis" + assay_stream: "Assay Stream" isa_study: "ISA Study" study_design: "Study Design" diff --git a/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index 2b1929fbfb..e78e477916 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -200,10 +200,3 @@ end puts 'Seeded minimal templates for organizing ISA JSON compliant experiments.' - -disable_authorization_checks do - AssayClass.find_or_create_by(title: 'Assay Stream', key: 'ASS', - description: 'Special type of class that is user in Single Page, specifying this is a container for a stream of assays') -end - -puts 'Seeded Extra Assay Class' diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 6497f980b5..38990a2e7d 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -15,7 +15,7 @@ namespace :seek do rename_registered_sample_multiple_attribute_type remove_ontology_attribute_type db:seed:007_sample_attribute_types - db:seed:017_minimal_starter_isa_templates + db:seed:001_create_controlled_vocabs recognise_isa_json_compliant_items ] diff --git a/test/fixtures/assay_classes.yml b/test/fixtures/assay_classes.yml index fc730abd78..4a90f2e308 100644 --- a/test/fixtures/assay_classes.yml +++ b/test/fixtures/assay_classes.yml @@ -7,5 +7,5 @@ modelling_assay_class: key: MODEL assay_stream_class: - title: 'Assay Stream' - key: 'ASS' + title: <%= I18n.t('assays.assay_stream') %> + key: ASS From 0dc8b6c12b8390799ca6bdfa853afc50742e9659 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 18 Jan 2024 15:40:23 +0100 Subject: [PATCH 021/344] Fix functional tests about the isa json exporter --- lib/isa_exporter.rb | 11 +++++-- test/factories/assays.rb | 4 +-- .../investigations_controller_test.rb | 29 +++++++++++++------ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index f015bc5d8d..5cc20eeda1 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -184,9 +184,14 @@ def convert_assays(assay_stream) return unless assay_stream.can_view?(@current_user) return unless child_assays.all? { |a| a.can_view?(@current_user) } + child_assays.map do |ca| + unless ca.sample_type.present? + raise "No Sample type was found in Assay '#{ca.id} - #{ca.title}'," \ + " part of Assay Stream '#{assay_stream.id - assay_stream.title}'" + end + end + all_sample_types = child_assays.map(&:sample_type).compact - # first_assay = assays.detect { |s| s.position.zero? } - # raise 'No assay could be found!' unless first_assay stream_name = "assays_#{child_assays.pluck(:id).join('_')}" assay_comments = convert_assay_comments(assay_stream) @@ -745,7 +750,7 @@ def random_string(len) end def get_derived_from_type(sample_type) - raise 'There is no sample!' if sample_type.samples.length == 0 + raise "There are no samples in '#{sample_type.title}'!" if sample_type.samples.blank? prev_sample_type = sample_type.samples[0]&.linked_samples[0]&.sample_type return nil if prev_sample_type.blank? diff --git a/test/factories/assays.rb b/test/factories/assays.rb index eab8a81d18..ffa20fd9ee 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -14,7 +14,7 @@ end factory(:assay_stream_class, class: AssayClass) do - title { 'Assay Stream' } + title { I18n.t('assays.assay_stream') } key { 'ASS' } description { "An assay stream class description" } end @@ -115,7 +115,7 @@ description { 'A holder assay holding multiple child assays' } association :assay_class, factory: :assay_stream_class after(:build) do |assay| - assay.study = FactoryBot.create(:isa_json_compliant_study) + assay.study ||= FactoryBot.create(:isa_json_compliant_study, contributor: assay.contributor) end end diff --git a/test/functional/investigations_controller_test.rb b/test/functional/investigations_controller_test.rb index a564eb29a4..b38571a183 100644 --- a/test/functional/investigations_controller_test.rb +++ b/test/functional/investigations_controller_test.rb @@ -911,16 +911,22 @@ def test_title 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) + stream_1 = FactoryBot.create(:assay_stream, title: 'Assay Stream 1', study: accessible_study, contributor: other_user.person) + assert_equal(stream_1.study, accessible_study) + assert(stream_1.is_assay_stream?) + assay_1_stream_1_sample_type = FactoryBot.create(:isa_assay_material_sample_type, contributor: other_user.person, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id) + assay_1_stream_1 = FactoryBot.create(:assay, position: 1, sample_type: assay_1_stream_1_sample_type, study: accessible_study, contributor: other_user.person, assay_stream_id: stream_1.id) + assay_2_stream_1_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, contributor: other_user.person, 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: 2, sample_type: assay_2_stream_1_sample_type, study: accessible_study, contributor: other_user.person, assay_stream_id: stream_1.id) # 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) + stream_2 = FactoryBot.create(:assay_stream, title: 'Assay Stream 2', study: accessible_study, contributor: current_user.person) + assert_equal(stream_2.study, accessible_study) + assert(stream_2.is_assay_stream?) + assay_1_stream_2_sample_type = FactoryBot.create(:isa_assay_material_sample_type, contributor: current_user.person, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id) + assay_1_stream_2 = FactoryBot.create(:assay, position: 1, sample_type: assay_1_stream_2_sample_type, study: accessible_study, contributor: current_user.person, assay_stream_id: stream_2.id) + assay_2_stream_2_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, contributor: current_user.person, 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: 2, sample_type: assay_2_stream_2_sample_type, study: accessible_study, contributor: current_user.person, assay_stream_id: stream_2.id) # create samples in second assay stream with viewing permission @@ -1096,8 +1102,13 @@ def test_title assert json_investigation['studies'].map { |s| s['title'] }.include? accessible_study.title study_json = json_investigation['studies'].first + # Total assays + assert_equal accessible_study.assays.count, 6 + # Assay_streams + assert_equal accessible_study.assay_streams.count, 2 + # Child assays + assert_equal accessible_study.assay_streams.map(&:child_assays).compact.flatten.count, 4 # 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'] } From 1cc9b45403597198fc1c7df9b44f3b0d68c76ab5 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 19 Jan 2024 10:19:34 +0100 Subject: [PATCH 022/344] Move instanceName to the window-level and declare it in both assay_design and study_design --- app/assets/javascripts/single_page/dynamic_table.js.erb | 2 +- app/views/isa_assays/_assay_design.html.erb | 1 + app/views/isa_studies/_study_design.html.erb | 1 + app/views/single_pages/show.html.erb | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index c344d5e762..e3032c5cfe 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -83,7 +83,7 @@ const handleSelect = (e) => { if (cellData == "#HIDDEN") $j(td).addClass("disabled"); }; // Changes the id header to an instance id - if (c.title == "id") c.title = instanceName + " id"; + if (c.title == "id") c.title = window.instanceName + " id"; }); // Retrieve the column index of the multi-input cells (select2 items) // if column has a multi-input cell, it adds the index to the t array (=accumulator) diff --git a/app/views/isa_assays/_assay_design.html.erb b/app/views/isa_assays/_assay_design.html.erb index 98415832d7..9240363691 100644 --- a/app/views/isa_assays/_assay_design.html.erb +++ b/app/views/isa_assays/_assay_design.html.erb @@ -42,6 +42,7 @@

    <% end %> From e3fb6c46ae7e79cca8fefb84eca49be489a3fd4a Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 23 Jan 2024 11:28:25 +0000 Subject: [PATCH 031/344] Fix SVG blobs being previewed as text --- lib/seek/renderers/renderer_factory.rb | 2 +- test/unit/renderers_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/seek/renderers/renderer_factory.rb b/lib/seek/renderers/renderer_factory.rb index 784117e257..164e61db09 100644 --- a/lib/seek/renderers/renderer_factory.rb +++ b/lib/seek/renderers/renderer_factory.rb @@ -23,7 +23,7 @@ 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, PdfRenderer, ImageRenderer, TextRenderer, BlankRenderer] end end end diff --git a/test/unit/renderers_test.rb b/test/unit/renderers_test.rb index 74c3a193ac..6ca23903a8 100644 --- a/test/unit/renderers_test.rb +++ b/test/unit/renderers_test.rb @@ -27,6 +27,7 @@ class RenderersTest < ActiveSupport::TestCase assert_equal Seek::Renderers::NotebookRenderer, factory.renderer(FactoryBot.create(:jupyter_notebook_content_blob)).class assert_equal Seek::Renderers::TextRenderer, factory.renderer(FactoryBot.create(:txt_content_blob)).class assert_equal Seek::Renderers::ImageRenderer, factory.renderer(FactoryBot.create(:image_content_blob)).class + assert_equal Seek::Renderers::ImageRenderer, factory.renderer(FactoryBot.create(:svg_content_blob)).class assert_equal Seek::Renderers::BlankRenderer, factory.renderer(FactoryBot.create(:binary_content_blob)).class end From 49873214398fe8a06380dd68ac202c4741f7deb9 Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 23 Jan 2024 12:31:44 +0000 Subject: [PATCH 032/344] More SVG preview tests --- test/functional/git_controller_test.rb | 10 ++++++++++ test/unit/renderers_test.rb | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/test/functional/git_controller_test.rb b/test/functional/git_controller_test.rb index a21c7364a6..5a43c947ee 100644 --- a/test/functional/git_controller_test.rb +++ b/test/functional/git_controller_test.rb @@ -200,6 +200,16 @@ def setup assert_select 'img.git-image-preview[src=?]', workflow_git_raw_path(@workflow, version: @git_version.version, path: 'diagram.png') end + test 'get svg file blob' do + @git_version.add_file('test.svg', open_fixture_file('transparent-fairdom-logo-square.svg')) + @git_version.save! + get :blob, params: { workflow_id: @workflow.id, version: @git_version.version, path: 'test.svg', format: 'html' } # Not sure why this is needed + + assert_response :success + assert_select 'a.btn[href=?]', workflow_git_remove_file_path(@workflow, version: @git_version.version, path: 'test.svg') + assert_select 'img.git-image-preview[src=?]', workflow_git_raw_path(@workflow, version: @git_version.version, path: 'test.svg') + end + test 'get raw binary file' do get :raw, params: { workflow_id: @workflow.id, version: @git_version.version, path: 'diagram.png' } diff --git a/test/unit/renderers_test.rb b/test/unit/renderers_test.rb index 6ca23903a8..0f80fc7f4d 100644 --- a/test/unit/renderers_test.rb +++ b/test/unit/renderers_test.rb @@ -388,9 +388,22 @@ class RenderersTest < ActiveSupport::TestCase assert_select 'iframe', count: 0 assert_select '#navbar', count: 0 assert_select 'img.git-image-preview' + blob = FactoryBot.create(:txt_content_blob, asset: @asset) renderer = Seek::Renderers::ImageRenderer.new(blob) refute renderer.can_render? + + @git.add_file('test.svg', open_fixture_file('transparent-fairdom-logo-square.svg')) + git_blob = @git.get_blob('test.svg') + renderer = Seek::Renderers::ImageRenderer.new(git_blob) + assert renderer.can_render? + @html = Nokogiri::HTML.parse(renderer.render) + assert_select 'img.git-image-preview' + + @html = Nokogiri::HTML.parse(renderer.render_standalone) + assert_select 'iframe', count: 0 + assert_select '#navbar', count: 0 + assert_select 'img.git-image-preview' end def document_root_element From 9493fef09c4cfe01770bd6df554bbc05d8f00eaf Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 14:15:39 +0100 Subject: [PATCH 033/344] move cv_modal back from the sample_types form to the new page --- app/views/isa_assays/new.html.erb | 3 +++ app/views/isa_studies/_sample_types_form.html.erb | 3 --- app/views/isa_studies/new.html.erb | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/views/isa_assays/new.html.erb b/app/views/isa_assays/new.html.erb index 4af1f56a50..07da9cf1e0 100644 --- a/app/views/isa_assays/new.html.erb +++ b/app/views/isa_assays/new.html.erb @@ -1,4 +1,7 @@
    + + <%= sample_controlled_vocab_model_dialog('cv-modal') %> + <% if params[:is_assay_stream] %>

    New <%=t("assays.assay_stream")%>

    <% else %> diff --git a/app/views/isa_studies/_sample_types_form.html.erb b/app/views/isa_studies/_sample_types_form.html.erb index b1d7bf1ebd..8889467eb3 100644 --- a/app/views/isa_studies/_sample_types_form.html.erb +++ b/app/views/isa_studies/_sample_types_form.html.erb @@ -5,9 +5,6 @@ <% main_field_name = id_suffix[1..-1] %> <% isa_element ||= "study" %> - - <%= sample_controlled_vocab_model_dialog('cv-modal') %> - <%= f.fields_for main_field_name, sample_type do |field| %> <% unless action == :edit %> diff --git a/app/views/isa_studies/new.html.erb b/app/views/isa_studies/new.html.erb index 38f08dc616..021e3e17f4 100644 --- a/app/views/isa_studies/new.html.erb +++ b/app/views/isa_studies/new.html.erb @@ -1,6 +1,10 @@ <% if Investigation.authorized_for('view').none? %> <%= button_link_to("New #{t('investigation')}", 'arrow_right', new_investigation_path) -%> <% else %> + + <%= sample_controlled_vocab_model_dialog('cv-modal') %> + +

    New <%=t('isa_study')%>

    <%= render :partial => "templates/template_modal" -%> From 9edc7f6801c173fc2bfa53f3f2f2cf99fbace6a0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 14:43:05 +0100 Subject: [PATCH 034/344] Hide assay type and technology type in case of isa json compliant assay --- app/views/assays/show.html.erb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 6f2c697b1b..6c31f71d60 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -47,16 +47,18 @@ <%= "Assay position" %>: <%= @assay.position %>

    -

    - <%= assay_type_text -%>: - <%= link_to_assay_type(@assay) -%> -

    - <% unless @assay.is_modelling? -%> -

    - Technology type: - <%= link_to_technology_type(@assay) -%> + <% unless @assay.is_isa_json_compliant? %> +

    + <%= assay_type_text -%>: + <%= link_to_assay_type(@assay) -%>

    - <% end -%> + <% unless @assay.is_modelling? -%> +

    + Technology type: + <%= link_to_technology_type(@assay) -%> +

    + <% end -%> + <% end %> <% if Seek::Config.organisms_enabled %> <%= list_assay_organisms("Organisms", @assay.assay_organisms, { :id => "organism" }) %> From 67c1778aaf330ffb6e70bcbcc0c2d9257d1677fe Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 14:59:41 +0100 Subject: [PATCH 035/344] Add assay stream and related child assays to show --- app/views/assays/show.html.erb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 6c31f71d60..192f49da35 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -70,12 +70,30 @@ <% if Seek::Config.isa_json_compliance_enabled %>

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

    <%= render partial: 'isa_studies/applied_templates', locals: { resource: @assay } -%> <% end %> + <% if @assay.is_assay_stream? %> +

    + Child assays: +

      + <% @assay.child_assays.map do |ca| %> +
    • + <%= link_to ca.title, ca %> +
    • + <% end %> +
    +

    + <% else %> +

    + <%= t('assays.assay_stream') %>: + <%= link_to @assay.assay_stream.title, @assay.assay_stream %> +

    + <% end %> +
    From 37dbaaef37418036261c68f7fec1a71604d35cb1 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 15:31:18 +0100 Subject: [PATCH 036/344] Set assay filename in ISA JSON to the assay stream name --- 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 5cc20eeda1..5d5a3ff288 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -193,12 +193,13 @@ def convert_assays(assay_stream) all_sample_types = child_assays.map(&:sample_type).compact - stream_name = "assays_#{child_assays.pluck(:id).join('_')}" + # stream_name = "assays_#{child_assays.pluck(:id).join('_')}" + stream_name = "#{ assay_stream.title }_#{assay_stream.id}_#{child_assays.pluck(:id).join('_')}" assay_comments = convert_assay_comments(assay_stream) # Retrieve assay_stream if - stream_name_comment = assay_comments.detect { |ac| ac[:name] == 'assay_stream' } - stream_name = stream_name_comment[:value] unless stream_name_comment.nil? + # stream_name_comment = assay_comments.detect { |ac| ac[:name] == 'assay_stream' } + # stream_name = stream_name_comment[:value] unless stream_name_comment.nil? isa_assay = {} isa_assay['@id'] = "#assay/#{child_assays.pluck(:id).join('_')}" From 31a5ade77c4efdeef923fe12e6c5b60bb6410d2b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 16:16:51 +0100 Subject: [PATCH 037/344] Fix failing integration test --- app/views/assays/show.html.erb | 36 +++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 192f49da35..9f85fc1b39 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -76,22 +76,26 @@ <%= render partial: 'isa_studies/applied_templates', locals: { resource: @assay } -%> <% end %> - <% if @assay.is_assay_stream? %> -

    - Child assays: -

      - <% @assay.child_assays.map do |ca| %> -
    • - <%= link_to ca.title, ca %> -
    • - <% end %> -
    -

    - <% else %> -

    - <%= t('assays.assay_stream') %>: - <%= link_to @assay.assay_stream.title, @assay.assay_stream %> -

    + <% if @assay.is_isa_json_compliant? %> + <% if @assay.is_assay_stream? %> +

    + Child assays: +

      + <% @assay.child_assays.map do |ca| %> + <% unless @assay.child_assays.blank? %> +
    • + <%= link_to ca.title, ca %> +
    • + <% end %> + <% end %> +
    +

    + <% else %> +

    + <%= t('assays.assay_stream') %>: + <%= link_to @assay.assay_stream.title, @assay.assay_stream %> +

    + <% end %> <% end %> From 8855f09b9b812fe65a7df118eb2374c96098099c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 16:55:42 +0100 Subject: [PATCH 038/344] Abstact AssayClass keys to constants --- app/controllers/assays_controller.rb | 2 +- app/controllers/isa_assays_controller.rb | 8 ++++---- app/models/assay_class.rb | 20 +++++++++---------- app/views/isa_assays/_form.html.erb | 4 ++-- config/default_data/assay_classes.yml | 2 +- lib/seek/isa/assay_class.rb | 17 ++++++++++++++++ lib/seek/ontologies/synchronize.rb | 2 +- lib/seek/openbis/seek_util.rb | 6 +++--- lib/seek/projects/population.rb | 10 +++++----- test/factories/assays.rb | 6 +++--- test/fixtures/assay_classes.yml | 2 +- test/functional/isa_assays_controller_test.rb | 4 ++-- test/unit/assay_class_test.rb | 8 ++++---- .../ontology_synchronization_test.rb | 4 ++-- 14 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 lib/seek/isa/assay_class.rb diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index e2a552fd4f..900ddef19e 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -82,7 +82,7 @@ def edit end def create - params[:assay_class_id] ||= AssayClass.for_type('experimental').id + params[:assay_class_id] ||= AssayClass.for_type(Seek::ISA::AssayClass::EXP).id @assay = Assay.new(assay_params) update_assay_organisms @assay, params diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 0a44ce8d18..7771d17b3e 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -7,9 +7,9 @@ class IsaAssaysController < ApplicationController def new if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) end end @@ -141,9 +141,9 @@ def set_up_instance_variable def find_requested_item if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) end @isa_assay.populate(params[:id]) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index bd1dee611c..bb53cdec8e 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -1,38 +1,38 @@ class AssayClass < ApplicationRecord - # this returns an instance of AssayClass according to one of the types "experimental", "modelling" or "assay_stream" + # this returns an instance of AssayClass according to one of the constants defined in seek/isa/assay_class.rb # if there is not a match nil is returned def self.for_type(type) - keys = { "experimental": 'EXP', "modelling": 'MODEL', "assay_stream": 'ASS' } - AssayClass.find_by(key: keys[type.to_sym]) + AssayClass.find_by(key: type) end def self.experimental - for_type('experimental') + for_type(Seek::ISA::AssayClass::EXP) end def self.modelling - for_type('modelling') + for_type(Seek::ISA::AssayClass::MODEL) + end def self.assay_stream - for_type('assaystream') + for_type(Seek::ISA::AssayClass::STREAM) end def is_modelling? - key == 'MODEL' + key == Seek::ISA::AssayClass::MODEL end def is_experimental? - key == 'EXP' + key == Seek::ISA::AssayClass::EXP end def is_assay_stream? - key == 'ASS' + key == Seek::ISA::AssayClass::STREAM end # for cases where a longer more descriptive key is useful, but can't rely on the title # which may have been changed over time def long_key - { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'ASS': 'Assay Stream' }[key.to_sym] + { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }[key.to_sym] end end diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index eabfdf1abf..06b9a057ad 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -7,11 +7,11 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] assay_position = 0 - assay_class_id = AssayClass.for_type('assay_stream').id + assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id is_assay_stream = true else assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 - assay_class_id = AssayClass.for_type('experimental').id + assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::EXP).id is_assay_stream = false end else diff --git a/config/default_data/assay_classes.yml b/config/default_data/assay_classes.yml index 56c2c6790e..8b01bc6ad3 100644 --- a/config/default_data/assay_classes.yml +++ b/config/default_data/assay_classes.yml @@ -11,4 +11,4 @@ modelling_assay_class: assay_stream_class: id: 3 title: <%= I18n.t('assays.assay_stream') %> - key: ASS + key: STREAM diff --git a/lib/seek/isa/assay_class.rb b/lib/seek/isa/assay_class.rb new file mode 100644 index 0000000000..9be0b02dce --- /dev/null +++ b/lib/seek/isa/assay_class.rb @@ -0,0 +1,17 @@ +module Seek + module ISA + module AssayClass + # Creates constants based on the AssayClass key attributes + # Example: AssayClass key 'EXP' can be represented by Seek::ISA:AssayClass::EXP + ALL_TYPES = %w[EXP MODEL STREAM] + + ALL_TYPES.each do |type| + AssayClass.const_set(type.underscore.upcase, type) + end + + def self.valid?(value) + ALL_TYPES.include?(value) + end + end + end +end diff --git a/lib/seek/ontologies/synchronize.rb b/lib/seek/ontologies/synchronize.rb index e15f0119e8..fd3b83d4c4 100644 --- a/lib/seek/ontologies/synchronize.rb +++ b/lib/seek/ontologies/synchronize.rb @@ -99,7 +99,7 @@ def assay_changes_class?(assays, ontology_uri) def determine_assay_class_from_uri(uri) ontology_class = Seek::Ontologies::AssayTypeReader.instance.class_for_uri(uri) - ontology_class.nil? ? AssayClass.for_type('modelling') : AssayClass.for_type('experimental') + ontology_class.nil? ? AssayClass.for_type(Seek::ISA::AssayClass::MODEL) : AssayClass.for_type(Seek::ISA::AssayClass::EXP) end def get_suggested_types_found_in_ontology(type) diff --git a/lib/seek/openbis/seek_util.rb b/lib/seek/openbis/seek_util.rb index 6476676d54..f6303e2e18 100644 --- a/lib/seek/openbis/seek_util.rb +++ b/lib/seek/openbis/seek_util.rb @@ -27,7 +27,7 @@ def createObisAssay(assay_params, creator, obis_asset) zample = obis_asset.content openbis_endpoint = obis_asset.seek_service - assay_params[:assay_class_id] ||= AssayClass.for_type('experimental').id + assay_params[:assay_class_id] ||= AssayClass.for_type(Seek::ISA::AssayClass::EXP).id assay_params[:title] ||= extract_title(zample) ## "OpenBIS #{zample.perm_id}" assay = Assay.new(assay_params) @@ -88,7 +88,7 @@ def fake_file_assay(study) assay = study.assays.where(title: FAKE_FILE_ASSAY_NAME).first return assay if assay - assay_params = {assay_class_id: AssayClass.for_type('experimental').id, + assay_params = {assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, title: FAKE_FILE_ASSAY_NAME, description: 'Automatically generated assay to host openbis files that are linked to the original OpenBIS experiment. Its content and linked data files will be updated by the system @@ -446,7 +446,7 @@ def study_types(openbis_endpoint) Seek::Openbis::EntityType.ExperimentType(openbis_endpoint).find_by_codes(study_codes) end - + end end end diff --git a/lib/seek/projects/population.rb b/lib/seek/projects/population.rb index 2ab008c1e4..f0fa3d1e20 100644 --- a/lib/seek/projects/population.rb +++ b/lib/seek/projects/population.rb @@ -21,7 +21,7 @@ def populate_from_spreadsheet_impl r = sheet.rows[1] if r.cell(1).value.blank? - flash[:error]= "Unable to find header cells in #{datafile.title}" + flash[:error]= "Unable to find header cells in #{datafile.title}" return end @@ -78,7 +78,7 @@ def populate_from_spreadsheet_impl assay_position = 1 investigation.save! end - + if r.cell(study_index)&.value.present? if investigation.blank? flash[:error]= "Study specified without Investigation in #{datafile.title} at row #{r.index}" @@ -111,7 +111,7 @@ def populate_from_spreadsheet_impl set_description(assay, r, description_index) assay.position = assay_position assay_position += 1 - assay.assay_class = AssayClass.for_type('experimental') + assay.assay_class = AssayClass.for_type(Seek::ISA::AssayClass::EXP) set_assignees(assay, r, assignee_indices) @@ -134,7 +134,7 @@ def set_description(object, r, description_index) end object.description = description end - + def set_assignees(assay, r, assignee_indices) assignees = [] assignee_indices.each do |x| @@ -154,7 +154,7 @@ def set_assignees(assay, r, assignee_indices) end assay.creators = known_creators assay.other_creators = other_creators.join(';') - end + end def set_protocol(assay, r, protocol_index) protocol_string = r.cell(protocol_index)&.value&.to_s.strip diff --git a/test/factories/assays.rb b/test/factories/assays.rb index dfc298b213..ad88ee763f 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -4,18 +4,18 @@ factory(:modelling_assay_class, class: AssayClass) do title { I18n.t('assays.modelling_analysis') } - key { 'MODEL' } + key { Seek::ISA::AssayClass::MODEL } end factory(:experimental_assay_class, class: AssayClass) do title { I18n.t('assays.experimental_assay') } - key { 'EXP' } + key { Seek::ISA::AssayClass::EXP } description { "An experimental assay class description" } end factory(:assay_stream_class, class: AssayClass) do title { I18n.t('assays.assay_stream') } - key { 'ASS' } + key { Seek::ISA::AssayClass::STREAM } description { "An assay stream class description" } end diff --git a/test/fixtures/assay_classes.yml b/test/fixtures/assay_classes.yml index 4a90f2e308..cf87a8f7d1 100644 --- a/test/fixtures/assay_classes.yml +++ b/test/fixtures/assay_classes.yml @@ -8,4 +8,4 @@ modelling_assay_class: assay_stream_class: title: <%= I18n.t('assays.assay_stream') %> - key: ASS + key: STREAM diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 1cf8c6b35f..665aa54dcc 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -45,7 +45,7 @@ def setup sop_ids: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy)).id], creator_ids: [this_person.id, other_creator.id], other_creators: 'other collaborators', - assay_class_id: AssayClass.for_type('experimental').id, + assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, position: 0, policy_attributes: }, input_sample_type_id: sample_collection_sample_type.id, sample_type: { title: 'assay sample_type', project_ids: [projects.first.id], template_id: 1, @@ -189,7 +189,7 @@ def setup sop_ids: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy)).id], creator_ids: [this_person.id, other_creator.id], other_creators: 'other collaborators', - position: 0, assay_class_id: AssayClass.for_type('experimental').id, policy_attributes: } + position: 0, assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, policy_attributes: } isa_assay_attributes = { assay: assay_attributes.merge(emt_attributes), input_sample_type_id: sample_collection_sample_type.id, diff --git a/test/unit/assay_class_test.rb b/test/unit/assay_class_test.rb index 3a3d002f6d..c78b3ec6c1 100644 --- a/test/unit/assay_class_test.rb +++ b/test/unit/assay_class_test.rb @@ -4,12 +4,12 @@ class AssayClassTest < ActiveSupport::TestCase # Replace this with your real tests. fixtures :assay_classes test 'for_type' do - assert_equal 'EXP', AssayClass.for_type('experimental').key - assert_equal 'MODEL', AssayClass.for_type('modelling').key + assert_equal 'EXP', AssayClass.for_type(Seek::ISA::AssayClass::EXP).key + assert_equal 'MODEL', AssayClass.for_type(Seek::ISA::AssayClass::MODEL).key end test 'is_modelling?' do - assert AssayClass.for_type('modelling').is_modelling? - refute AssayClass.for_type('experimental').is_modelling? + assert AssayClass.for_type(Seek::ISA::AssayClass::MODEL).is_modelling? + refute AssayClass.for_type(Seek::ISA::AssayClass::EXP).is_modelling? end end diff --git a/test/unit/ontologies/ontology_synchronization_test.rb b/test/unit/ontologies/ontology_synchronization_test.rb index 50aeb1c446..c8e5836f14 100644 --- a/test/unit/ontologies/ontology_synchronization_test.rb +++ b/test/unit/ontologies/ontology_synchronization_test.rb @@ -3,10 +3,10 @@ class OntologySynchronizationTest < ActiveSupport::TestCase def setup - unless AssayClass.for_type('modelling') + unless AssayClass.for_type(Seek::ISA::AssayClass::MODEL) FactoryBot.create(:modelling_assay_class) end - unless AssayClass.for_type('experimental') + unless AssayClass.for_type(Seek::ISA::AssayClass::EXP) FactoryBot.create(:experimental_assay_class) end end From 518ebc2656070576a0a1261890ac899c5bdca519 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 16:56:26 +0100 Subject: [PATCH 039/344] Let the database do the job instead ruby in memory --- lib/tasks/seek_upgrades.rake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 0797d29613..b936eda04b 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -186,15 +186,15 @@ namespace :seek do # Should be isa json compliant # Shouldn't already have an assay stream (don't update assays that have been updated already) # Previous ST should be second ST of study - first_assays_in_stream = Assay.joins(:sample_type) - .select(&:is_isa_json_compliant?) - .select { |a| a.assay_stream_id.nil? && (a.previous_linked_sample_type == a.study.sample_types.second) } + first_assays_in_stream = Assay.joins(:sample_type, study: :investigation) + .where(assay_stream_id: nil, investigation: { is_isa_json_compliant: true }) + .select { |a| a.previous_linked_sample_type == a.study.sample_types.second } first_assays_in_stream.map do |fas| stream_name = "Assay Stream - #{UUID.generate}" assay_stream = Assay.create(title: stream_name, study_id: fas.study_id, - assay_class_id: AssayClass.for_type('assay_stream').id, + assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id, contributor: fas.contributor, position: 0) From a6089802363ea89d2ff03078e38f7431c3c8bca5 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 17:23:08 +0100 Subject: [PATCH 040/344] Fix failing tests --- test/functional/assays_controller_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 4ee3790a91..c4e6bc2f7e 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -587,14 +587,14 @@ def test_title test 'get new with class doesnt present options for class' do login_as(:model_owner) - get :new, params: { class: 'experimental' } + get :new, params: { class: Seek::ISA::AssayClass::EXP } assert_response :success assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0 assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0 assert_select 'a[href=?]', new_assay_path(class: :modelling), count: 0 assert_select 'a', text: /A #{I18n.t('assays.modelling_analysis')}/i, count: 0 - get :new, params: { class: 'modelling' } + get :new, params: { class: Seek::ISA::AssayClass::MODEL } assert_response :success assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0 assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0 @@ -1065,7 +1065,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links end test 'new should include tags element' do - get :new, params: { class: :experimental } + get :new, params: { class: Seek::ISA::AssayClass::EXP } assert_response :success assert_select 'div.panel-heading', text: /Tags/, count: 1 assert_select 'select#tag_list', count: 1 @@ -1121,7 +1121,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links end test 'should show experimental assay types for new experimental assay' do - get :new, params: { class: :experimental } + get :new, params: { class: Seek::ISA::AssayClass::EXP } assert_response :success assert_select 'label', text: /assay type/i assert_select 'select#assay_assay_type_uri' do @@ -1131,7 +1131,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links end test 'should show modelling assay types for new modelling assay' do - get :new, params: { class: :modelling } + get :new, params: { class: Seek::ISA::AssayClass::MODEL } assert_response :success assert_select 'label', text: /Biological problem addressed/i assert_select 'select#assay_assay_type_uri' do From 4c8c9cd0a7b088195b40d65ff1a4d2efb890ee1d Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 25 Jan 2024 08:57:04 +0100 Subject: [PATCH 041/344] Use constants instead of keys as string --- app/models/assay_class.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index bb53cdec8e..fb98ad0bb3 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -33,6 +33,6 @@ def is_assay_stream? # for cases where a longer more descriptive key is useful, but can't rely on the title # which may have been changed over time def long_key - { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }[key.to_sym] + { "#{Seek::ISA::AssayClass::EXP}": 'Experimental Assay', "#{Seek::ISA::AssayClass::MODEL}": 'Modelling Analysis', "#{Seek::ISA::AssayClass::STREAM}": 'Assay Stream' }[key.to_sym] end end From f3a0251eadd2351c1d4a4016454af73805c55993 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 25 Jan 2024 09:04:15 +0100 Subject: [PATCH 042/344] Use the assay class's long_key method to determin the button text --- app/views/assays/_buttons.html.erb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 6c5b795cc5..72c59241fe 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -1,12 +1,10 @@ <% assay_word ||= - if item.is_modelling? - t('assays.modelling_analysis') - elsif item.is_assay_stream? + if item.is_assay_stream? t('assays.assay_stream') elsif Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? t('isa_assay') else - t('assays.assay') + t("assays.#{item.assay_class.long_key.delete(' ').underscore}") end %> <%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %> From 1cae5565024d92edfbba1baf48e151ec70fa4462 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 12 Jan 2024 14:49:11 +0000 Subject: [PATCH 043/344] Fix the select2 in the controlled vocab modal --- app/assets/javascripts/controlled_vocabs.js.erb | 8 +++++++- app/views/isa_studies/_sample_types_form.html.erb | 2 +- app/views/sample_types/_form.html.erb | 2 +- app/views/templates/_form.html.erb | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/controlled_vocabs.js.erb b/app/assets/javascripts/controlled_vocabs.js.erb index 4d1d0ddd1b..db2a303dd3 100644 --- a/app/assets/javascripts/controlled_vocabs.js.erb +++ b/app/assets/javascripts/controlled_vocabs.js.erb @@ -30,14 +30,20 @@ var SampleTypeControlledVocab = { initialise_rich_text_editors(".rich-text-edit"); }, + copyBlankModalForm: function() { + // first destroy any select2 elements, which won't survive being cloned. They will be initialised again when reset + $j('#cv-modal select[data-role="seek-objectsinput"]').select2('destroy'); + SampleTypeControlledVocab.blankControlledVocabModelForm=$j('#cv-modal').clone(); + }, + //resets the modal resetModalControlledVocabForm: function () { $j('#cv-modal').remove(); $j('#modal-dialogues').append(SampleTypeControlledVocab.blankControlledVocabModelForm.clone()); CVTerms.init(); + ObjectsInput.init(); SampleTypeControlledVocab.bindNewControlledVocabShowEvent(); SampleTypeControlledVocab.initialise_deferred_rich_editor_modal(); - }, //selected CV item changed diff --git a/app/views/isa_studies/_sample_types_form.html.erb b/app/views/isa_studies/_sample_types_form.html.erb index b1d7bf1ebd..1e78428601 100644 --- a/app/views/isa_studies/_sample_types_form.html.erb +++ b/app/views/isa_studies/_sample_types_form.html.erb @@ -80,7 +80,7 @@ + + + <% end %> <% if Seek::Config.google_analytics_enabled %> diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index a478073237..0ed2f3fd2b 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -177,6 +177,13 @@ class ConfigTest < ActiveSupport::TestCase assert_equal 'localhost/piwik/', Seek::Config.piwik_analytics_url end + test 'custom_analytics_enabled' do + assert !Seek::Config.custom_analytics_snippet_enabled + end + test 'custom analytics name' do + assert_equal 'Custom name', Seek::Config.custom_analytics_name + end + # homepage settings test 'project_news_enabled' do assert !Seek::Config.project_news_enabled From aa3742f4400e4f5f2d5e4e0472f49579849963ce Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 31 Jan 2024 17:24:07 +0100 Subject: [PATCH 051/344] Consolidate analytics constants in a method --- app/views/cookies/consent.html.erb | 2 +- app/views/layouts/_cookies_banner.html.erb | 2 +- lib/seek/config.rb | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/views/cookies/consent.html.erb b/app/views/cookies/consent.html.erb index 02555c8cec..09c3f507bf 100644 --- a/app/views/cookies/consent.html.erb +++ b/app/views/cookies/consent.html.erb @@ -37,7 +37,7 @@ <%= link_to t('cookies.buttons.embedding'), cookies_consent_path(allow: 'necessary,embedding'), method: :post, class: 'btn btn-default' %> <%= link_to t('cookies.buttons.all'), cookies_consent_path(allow: CookieConsent::OPTIONS.join(',')), - method: :post, class: 'btn btn-default' if (Seek::Config.google_analytics_enabled || Seek::Config.piwik_analytics_enabled || Seek::Config.custom_analytics_snippet_enabled) %> + method: :post, class: 'btn btn-default' if Seek::Config.analytics_enabled %> diff --git a/app/views/layouts/_cookies_banner.html.erb b/app/views/layouts/_cookies_banner.html.erb index dbefc1a3d1..1b68cfab6e 100644 --- a/app/views/layouts/_cookies_banner.html.erb +++ b/app/views/layouts/_cookies_banner.html.erb @@ -15,7 +15,7 @@ <%= link_to t('cookies.buttons.embedding'), cookies_consent_path(allow: 'necessary,embedding'), method: :post, class: 'btn btn-default' %> <%= link_to t('cookies.buttons.all'), cookies_consent_path(allow: CookieConsent::OPTIONS.join(',')), - method: :post, class: 'btn btn-primary' if (Seek::Config.google_analytics_enabled || Seek::Config.piwik_analytics_enabled || Seek::Config.custom_analytics_snippet_enabled) %> + method: :post, class: 'btn btn-primary' if Seek::Config.analytics_enabled %> diff --git a/lib/seek/config.rb b/lib/seek/config.rb index b216834f0b..ffcc4484ca 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -7,7 +7,7 @@ module Fallbacks def instance_admins_name_fallback instance_name end - + def instance_admins_link_fallback instance_link end @@ -529,6 +529,10 @@ def self.read_project_setting_attributes register_encrypted_setting(method) if opts && opts[:encrypt] end + def self.analytics_enabled + google_analytics_enabled || piwik_analytics_enabled || custom_analytics_snippet_enabled + end + def self.schema_org_supported? true end From 767d2afb7d08247d2f15569fa8f06ad7fe475cca Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 31 Jan 2024 17:33:47 +0100 Subject: [PATCH 052/344] Change the default value to an empty script tag --- config/initializers/seek_configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/seek_configuration.rb b/config/initializers/seek_configuration.rb index 977ac74073..a3078a1309 100644 --- a/config/initializers/seek_configuration.rb +++ b/config/initializers/seek_configuration.rb @@ -35,7 +35,7 @@ def load_seek_config_defaults! Seek::Config.default :piwik_analytics_tracking_notice, true Seek::Config.default :custom_analytics_snippet_enabled, false Seek::Config.default :custom_analytics_name, 'Custom name' - Seek::Config.default :custom_analytics_snippet, '' + Seek::Config.default :custom_analytics_snippet, '' Seek::Config.default :custom_analytics_tracking_notice, true Seek::Config.default :bioportal_api_key,'' Seek::Config.default :project_news_enabled,false From 1d7e925a955930937f3ab986513b11ae72fb4c13 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 31 Jan 2024 17:34:52 +0100 Subject: [PATCH 053/344] Wrap `custom_analytics_snippet` in a div instead of a script tag. Escape special characters using raw instead of sanitize. --- app/views/layouts/application.html.erb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 47066b9283..fcf1138e77 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -49,11 +49,9 @@ <%= render partial: 'layouts/piwik' if Seek::Config.piwik_analytics_enabled %> <% if Seek::Config.custom_analytics_snippet_enabled %> - - - +
    + <%= raw Seek::Config.custom_analytics_snippet %> +
    <% end %> <% if Seek::Config.google_analytics_enabled %> From 334988409cbae0b9a7ac0daddc2d8bb05106be0b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 31 Jan 2024 17:39:59 +0100 Subject: [PATCH 054/344] Add test for `custom_analytics_snippet` --- test/unit/config_test.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index 0ed2f3fd2b..5edd0ed1b7 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -180,9 +180,12 @@ class ConfigTest < ActiveSupport::TestCase test 'custom_analytics_enabled' do assert !Seek::Config.custom_analytics_snippet_enabled end - test 'custom analytics name' do + test 'custom analytics name' do assert_equal 'Custom name', Seek::Config.custom_analytics_name end + test 'custom analytics snippet' do + assert_equal '', Seek::Config.custom_analytics_snippet + end # homepage settings test 'project_news_enabled' do From a8316e2226c4e2a02f69765ae52e72378bc2d1dd Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 09:20:53 +0100 Subject: [PATCH 055/344] Remove the AssayClass constants --- app/controllers/assays_controller.rb | 4 ++-- app/controllers/isa_assays_controller.rb | 8 ++++---- app/models/assay_class.rb | 14 +++++++------- app/views/isa_assays/_form.html.erb | 4 ++-- lib/seek/isa/assay_class.rb | 17 ----------------- lib/seek/ontologies/synchronize.rb | 2 +- lib/seek/openbis/seek_util.rb | 4 ++-- lib/seek/projects/population.rb | 2 +- lib/tasks/seek_upgrades.rake | 2 +- test/factories/assays.rb | 6 +++--- test/functional/assays_controller_test.rb | 10 +++++----- test/functional/isa_assays_controller_test.rb | 4 ++-- test/unit/assay_class_test.rb | 8 ++++---- .../ontologies/ontology_synchronization_test.rb | 4 ++-- 14 files changed, 36 insertions(+), 53 deletions(-) delete mode 100644 lib/seek/isa/assay_class.rb diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 900ddef19e..007e3f7da9 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -70,7 +70,7 @@ def new @permitted_params = assay_params if params[:assay] # jump straight to experimental if modelling analysis is disabled - @assay_class ||= 'experimental' unless Seek::Config.modelling_analysis_enabled + @assay_class ||= 'EXP' unless Seek::Config.modelling_analysis_enabled @assay.assay_class = AssayClass.for_type(@assay_class) unless @assay_class.nil? @@ -82,7 +82,7 @@ def edit end def create - params[:assay_class_id] ||= AssayClass.for_type(Seek::ISA::AssayClass::EXP).id + params[:assay_class_id] ||= AssayClass.experimental.id @assay = Assay.new(assay_params) update_assay_organisms @assay, params diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 7771d17b3e..cc0c8128ed 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -7,9 +7,9 @@ class IsaAssaysController < ApplicationController def new if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.assay_stream.id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.experimental.id } }) end end @@ -141,9 +141,9 @@ def set_up_instance_variable def find_requested_item if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.assay_stream.id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.experimental.id } }) end @isa_assay.populate(params[:id]) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index fb98ad0bb3..42298a17dd 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -6,33 +6,33 @@ def self.for_type(type) end def self.experimental - for_type(Seek::ISA::AssayClass::EXP) + for_type('EXP') end def self.modelling - for_type(Seek::ISA::AssayClass::MODEL) + for_type('MODEL') end def self.assay_stream - for_type(Seek::ISA::AssayClass::STREAM) + for_type('STREAM') end def is_modelling? - key == Seek::ISA::AssayClass::MODEL + key == 'MODEL' end def is_experimental? - key == Seek::ISA::AssayClass::EXP + key == 'EXP' end def is_assay_stream? - key == Seek::ISA::AssayClass::STREAM + key == 'STREAM' end # for cases where a longer more descriptive key is useful, but can't rely on the title # which may have been changed over time def long_key - { "#{Seek::ISA::AssayClass::EXP}": 'Experimental Assay', "#{Seek::ISA::AssayClass::MODEL}": 'Modelling Analysis', "#{Seek::ISA::AssayClass::STREAM}": 'Assay Stream' }[key.to_sym] + { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }[key.to_sym] end end diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 06b9a057ad..cc707ccf20 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -7,11 +7,11 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] assay_position = 0 - assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id + assay_class_id = AssayClass.assay_stream.id is_assay_stream = true else assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 - assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::EXP).id + assay_class_id = AssayClass.experimental.id is_assay_stream = false end else diff --git a/lib/seek/isa/assay_class.rb b/lib/seek/isa/assay_class.rb deleted file mode 100644 index 9be0b02dce..0000000000 --- a/lib/seek/isa/assay_class.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Seek - module ISA - module AssayClass - # Creates constants based on the AssayClass key attributes - # Example: AssayClass key 'EXP' can be represented by Seek::ISA:AssayClass::EXP - ALL_TYPES = %w[EXP MODEL STREAM] - - ALL_TYPES.each do |type| - AssayClass.const_set(type.underscore.upcase, type) - end - - def self.valid?(value) - ALL_TYPES.include?(value) - end - end - end -end diff --git a/lib/seek/ontologies/synchronize.rb b/lib/seek/ontologies/synchronize.rb index fd3b83d4c4..cfce103be3 100644 --- a/lib/seek/ontologies/synchronize.rb +++ b/lib/seek/ontologies/synchronize.rb @@ -99,7 +99,7 @@ def assay_changes_class?(assays, ontology_uri) def determine_assay_class_from_uri(uri) ontology_class = Seek::Ontologies::AssayTypeReader.instance.class_for_uri(uri) - ontology_class.nil? ? AssayClass.for_type(Seek::ISA::AssayClass::MODEL) : AssayClass.for_type(Seek::ISA::AssayClass::EXP) + ontology_class.nil? ? AssayClass.modelling : AssayClass.experimental end def get_suggested_types_found_in_ontology(type) diff --git a/lib/seek/openbis/seek_util.rb b/lib/seek/openbis/seek_util.rb index f6303e2e18..a99b15949e 100644 --- a/lib/seek/openbis/seek_util.rb +++ b/lib/seek/openbis/seek_util.rb @@ -27,7 +27,7 @@ def createObisAssay(assay_params, creator, obis_asset) zample = obis_asset.content openbis_endpoint = obis_asset.seek_service - assay_params[:assay_class_id] ||= AssayClass.for_type(Seek::ISA::AssayClass::EXP).id + assay_params[:assay_class_id] ||= AssayClass.experimental.id assay_params[:title] ||= extract_title(zample) ## "OpenBIS #{zample.perm_id}" assay = Assay.new(assay_params) @@ -88,7 +88,7 @@ def fake_file_assay(study) assay = study.assays.where(title: FAKE_FILE_ASSAY_NAME).first return assay if assay - assay_params = {assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, + assay_params = {assay_class_id: AssayClass.experimental.id, title: FAKE_FILE_ASSAY_NAME, description: 'Automatically generated assay to host openbis files that are linked to the original OpenBIS experiment. Its content and linked data files will be updated by the system diff --git a/lib/seek/projects/population.rb b/lib/seek/projects/population.rb index f0fa3d1e20..faeb03241b 100644 --- a/lib/seek/projects/population.rb +++ b/lib/seek/projects/population.rb @@ -111,7 +111,7 @@ def populate_from_spreadsheet_impl set_description(assay, r, description_index) assay.position = assay_position assay_position += 1 - assay.assay_class = AssayClass.for_type(Seek::ISA::AssayClass::EXP) + assay.assay_class = AssayClass.experimental set_assignees(assay, r, assignee_indices) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index b936eda04b..e9b1ae3c15 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -194,7 +194,7 @@ namespace :seek do stream_name = "Assay Stream - #{UUID.generate}" assay_stream = Assay.create(title: stream_name, study_id: fas.study_id, - assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id, + assay_class_id: AssayClass.assay_stream.id, contributor: fas.contributor, position: 0) diff --git a/test/factories/assays.rb b/test/factories/assays.rb index ad88ee763f..24f95f1ae0 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -4,18 +4,18 @@ factory(:modelling_assay_class, class: AssayClass) do title { I18n.t('assays.modelling_analysis') } - key { Seek::ISA::AssayClass::MODEL } + key { 'MODEL' } end factory(:experimental_assay_class, class: AssayClass) do title { I18n.t('assays.experimental_assay') } - key { Seek::ISA::AssayClass::EXP } + key { 'EXP' } description { "An experimental assay class description" } end factory(:assay_stream_class, class: AssayClass) do title { I18n.t('assays.assay_stream') } - key { Seek::ISA::AssayClass::STREAM } + key { 'STREAM' } description { "An assay stream class description" } end diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index c4e6bc2f7e..1996d36918 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -587,14 +587,14 @@ def test_title test 'get new with class doesnt present options for class' do login_as(:model_owner) - get :new, params: { class: Seek::ISA::AssayClass::EXP } + get :new, params: { class: 'EXP' } assert_response :success assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0 assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0 assert_select 'a[href=?]', new_assay_path(class: :modelling), count: 0 assert_select 'a', text: /A #{I18n.t('assays.modelling_analysis')}/i, count: 0 - get :new, params: { class: Seek::ISA::AssayClass::MODEL } + get :new, params: { class: 'MODEL' } assert_response :success assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0 assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0 @@ -1065,7 +1065,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links end test 'new should include tags element' do - get :new, params: { class: Seek::ISA::AssayClass::EXP } + get :new, params: { class: 'EXP' } assert_response :success assert_select 'div.panel-heading', text: /Tags/, count: 1 assert_select 'select#tag_list', count: 1 @@ -1121,7 +1121,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links end test 'should show experimental assay types for new experimental assay' do - get :new, params: { class: Seek::ISA::AssayClass::EXP } + get :new, params: { class: 'EXP' } assert_response :success assert_select 'label', text: /assay type/i assert_select 'select#assay_assay_type_uri' do @@ -1131,7 +1131,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links end test 'should show modelling assay types for new modelling assay' do - get :new, params: { class: Seek::ISA::AssayClass::MODEL } + get :new, params: { class: 'MODEL' } assert_response :success assert_select 'label', text: /Biological problem addressed/i assert_select 'select#assay_assay_type_uri' do diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 665aa54dcc..1212dd21d1 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -45,7 +45,7 @@ def setup sop_ids: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy)).id], creator_ids: [this_person.id, other_creator.id], other_creators: 'other collaborators', - assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, + assay_class_id: AssayClass.experimental.id, position: 0, policy_attributes: }, input_sample_type_id: sample_collection_sample_type.id, sample_type: { title: 'assay sample_type', project_ids: [projects.first.id], template_id: 1, @@ -189,7 +189,7 @@ def setup sop_ids: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy)).id], creator_ids: [this_person.id, other_creator.id], other_creators: 'other collaborators', - position: 0, assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, policy_attributes: } + position: 0, assay_class_id: AssayClass.experimental.id, policy_attributes: } isa_assay_attributes = { assay: assay_attributes.merge(emt_attributes), input_sample_type_id: sample_collection_sample_type.id, diff --git a/test/unit/assay_class_test.rb b/test/unit/assay_class_test.rb index c78b3ec6c1..3ef0593df8 100644 --- a/test/unit/assay_class_test.rb +++ b/test/unit/assay_class_test.rb @@ -4,12 +4,12 @@ class AssayClassTest < ActiveSupport::TestCase # Replace this with your real tests. fixtures :assay_classes test 'for_type' do - assert_equal 'EXP', AssayClass.for_type(Seek::ISA::AssayClass::EXP).key - assert_equal 'MODEL', AssayClass.for_type(Seek::ISA::AssayClass::MODEL).key + assert_equal 'EXP', AssayClass.experimental.key + assert_equal 'MODEL', AssayClass.modelling.key end test 'is_modelling?' do - assert AssayClass.for_type(Seek::ISA::AssayClass::MODEL).is_modelling? - refute AssayClass.for_type(Seek::ISA::AssayClass::EXP).is_modelling? + assert AssayClass.modelling.is_modelling? + refute AssayClass.experimental.is_modelling? end end diff --git a/test/unit/ontologies/ontology_synchronization_test.rb b/test/unit/ontologies/ontology_synchronization_test.rb index c8e5836f14..06ba1d31df 100644 --- a/test/unit/ontologies/ontology_synchronization_test.rb +++ b/test/unit/ontologies/ontology_synchronization_test.rb @@ -3,10 +3,10 @@ class OntologySynchronizationTest < ActiveSupport::TestCase def setup - unless AssayClass.for_type(Seek::ISA::AssayClass::MODEL) + unless AssayClass.modelling FactoryBot.create(:modelling_assay_class) end - unless AssayClass.for_type(Seek::ISA::AssayClass::EXP) + unless AssayClass.experimental FactoryBot.create(:experimental_assay_class) end end From cd06baa6b8cceedbc928f8dac2892586a225ac87 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 09:36:07 +0100 Subject: [PATCH 056/344] Move long key mapping to a constant outside --- app/models/assay_class.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index 42298a17dd..5e8940f9ee 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -30,9 +30,11 @@ def is_assay_stream? key == 'STREAM' end + LONG_KEYS = { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }.freeze + # for cases where a longer more descriptive key is useful, but can't rely on the title # which may have been changed over time def long_key - { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }[key.to_sym] + LONG_KEYS[key.to_sym] end end From 14089f6ed19079e01c3b0af4f79fcea2d5948aa1 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 10:09:34 +0100 Subject: [PATCH 057/344] Use the definition instead of manual text setting --- app/views/studies/_buttons.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index 4b63c58c86..a308561224 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -21,7 +21,7 @@ <% if item.can_edit? -%> <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <% if item&.sample_types.present? %> - <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_assay_stream: true)) %> + <%= button_link_to("Design #{t('assays.assay_stream')}", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_assay_stream: true)) %> <% end -%> <% else -%> <%= add_new_item_to_dropdown(item) %> From 6fdd31fee6dcd2095187536a0d6204b035943065 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 10:12:05 +0100 Subject: [PATCH 058/344] Use assay_streams method instead of select statement --- lib/treeview_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/treeview_builder.rb b/lib/treeview_builder.rb index 3996b2da2b..6f89ddf2d3 100644 --- a/lib/treeview_builder.rb +++ b/lib/treeview_builder.rb @@ -16,7 +16,7 @@ def build_tree_data @project.investigations.map do |investigation| if investigation.is_isa_json_compliant? investigation.studies.map do |study| - assay_stream_items = study.assays.select { |assay| assay.is_assay_stream? }.map do |assay_stream| + assay_stream_items = study.assay_streams.map do |assay_stream| assay_items = assay_stream.child_assays.map do |child_assay| build_assay_item(child_assay) end From f994a3620311fb2d546e20b81a123c297d4b76a6 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 16:36:07 +0100 Subject: [PATCH 059/344] Replace `is_single_page_assay?` by `is_isa_json_compliant?` --- app/controllers/assays_controller.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 007e3f7da9..a61deef54b 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -107,14 +107,14 @@ def create end def delete_linked_sample_types - return unless is_single_page_assay? + return unless @assay.is_isa_json_compliant? return if @assay.sample_type.nil? @assay.sample_type.destroy end def fix_assay_linkage - return unless is_single_page_assay? + return unless @assay.is_isa_json_compliant? return unless @assay.has_linked_child_assay? previous_assay_linked_st_id = @assay.previous_linked_sample_type&.id @@ -194,10 +194,4 @@ def assay_params assay_params[:model_ids].select! { |id| Model.find_by_id(id).try(:can_view?) } if assay_params.key?(:model_ids) end end - - def is_single_page_assay? - return false unless params.key?(:return_to) - - params[:return_to].start_with? '/single_pages/' - end end From a1faa5d8426b9fb67ce9ffb305555b14ba8462a4 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 14:33:12 +0100 Subject: [PATCH 060/344] Add fix linkage to isa assays after a new one is created. --- app/controllers/isa_assays_controller.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index cc0c8128ed..29df51f069 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -4,6 +4,7 @@ class IsaAssaysController < ApplicationController before_action :set_up_instance_variable before_action :find_requested_item, only: %i[edit update] + after_action :fix_assay_linkage_for_new_assays, only: :create def new if params[:is_assay_stream] @@ -64,6 +65,20 @@ def update end end + def fix_assay_linkage_for_new_assays + return unless @isa_assay.assay.is_isa_json_compliant? + + previous_assay_st = @isa_assay.assay.previous_linked_sample_type + previous_assay = previous_assay_st.assays.first + next_assay = previous_assay.next_linked_child_assay + next_assay.update(position: @isa_assay.assay.position + 1) + return unless next_assay + + current_assay_st = @isa_assay.assay.sample_type + previous_assay_st.update(linked_sample_attribute_ids: [@isa_assay.assay.sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }.id]) + current_assay_st.update(linked_sample_attribute_ids: [next_assay.sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }.id]) + end + private def isa_assay_params From f8c22007e0579f938de51b9137014912868d4813 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 14:56:41 +0100 Subject: [PATCH 061/344] Order assays by position in assay_stream --- lib/treeview_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/treeview_builder.rb b/lib/treeview_builder.rb index 6f89ddf2d3..b810759ebb 100644 --- a/lib/treeview_builder.rb +++ b/lib/treeview_builder.rb @@ -17,7 +17,7 @@ def build_tree_data if investigation.is_isa_json_compliant? investigation.studies.map do |study| assay_stream_items = study.assay_streams.map do |assay_stream| - assay_items = assay_stream.child_assays.map do |child_assay| + assay_items = assay_stream.child_assays.order(:position).map do |child_assay| build_assay_item(child_assay) end build_assay_stream_item(assay_stream, assay_items) From 3e0237bdae936be963de3d142f1f0b0637c82b9f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 25 Jan 2024 15:27:26 +0100 Subject: [PATCH 062/344] Create function to find the input attribute --- app/models/sample_attribute.rb | 4 ++++ test/unit/sample_attribute_test.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/app/models/sample_attribute.rb b/app/models/sample_attribute.rb index bf117fe9cc..97707ea259 100644 --- a/app/models/sample_attribute.rb +++ b/app/models/sample_attribute.rb @@ -25,6 +25,10 @@ class SampleAttribute < ApplicationRecord # whether this attribute is tied to a controlled vocab which is based on an ontology delegate :ontology_based?, to: :sample_controlled_vocab, allow_nil: true + def input_attribute? + isa_tag.nil? && title.downcase.include?('input') && seek_sample_multi? + end + def title=(title) super store_accessor_name diff --git a/test/unit/sample_attribute_test.rb b/test/unit/sample_attribute_test.rb index 8951913607..a9a045473f 100644 --- a/test/unit/sample_attribute_test.rb +++ b/test/unit/sample_attribute_test.rb @@ -513,6 +513,18 @@ class SampleAttributeTest < ActiveSupport::TestCase refute attribute.validate_value?('moonbeam') end + test 'is input attribute?' do + correct_input_attribute = FactoryBot.create(:sample_multi_sample_attribute, title: 'Input from previous sample type', isa_tag: nil, is_title: true, sample_type: FactoryBot.create(:simple_sample_type)) + assert correct_input_attribute.input_attribute? + + incorrect_input_attribute = FactoryBot.create(:sample_multi_sample_attribute, title: 'Input from previous sample type', isa_tag: FactoryBot.create(:default_isa_tag), is_title: true, sample_type: FactoryBot.create(:simple_sample_type)) + refute incorrect_input_attribute.input_attribute? + second_incorrect_input_attribute = FactoryBot.create(:sample_multi_sample_attribute, title: 'Ingoing material', isa_tag: nil, is_title: true, sample_type: FactoryBot.create(:simple_sample_type)) + refute second_incorrect_input_attribute.input_attribute? + third_incorrect_input_attribute = FactoryBot.create(:sample_sample_attribute, title: 'Input from previous sample type', isa_tag: nil, is_title: true, sample_type: FactoryBot.create(:simple_sample_type)) + refute third_incorrect_input_attribute.input_attribute? + end + private def valid_value?(attribute, value) From 8b5db61c6b161f5ae61edaab7b0cb50113d1cc5c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 25 Jan 2024 15:28:47 +0100 Subject: [PATCH 063/344] Add functions to find the next and previous linked sample type --- app/models/sample_type.rb | 8 ++++++++ test/unit/sample_type_test.rb | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/app/models/sample_type.rb b/app/models/sample_type.rb index 01523e171d..729db7b338 100644 --- a/app/models/sample_type.rb +++ b/app/models/sample_type.rb @@ -60,6 +60,14 @@ class SampleType < ApplicationRecord has_annotation_type :sample_type_tag, method_name: :tags + def previous_linked_sample_type + sample_attributes.detect(&:input_attribute?)&.linked_sample_type + end + + def next_linked_sample_types + linked_sample_attributes.select(&:input_attribute?).map(&:sample_type).compact + end + def is_isa_json_compliant? studies.any? || assays.any? end diff --git a/test/unit/sample_type_test.rb b/test/unit/sample_type_test.rb index 69e73420dc..89ce2b81d6 100644 --- a/test/unit/sample_type_test.rb +++ b/test/unit/sample_type_test.rb @@ -1118,6 +1118,26 @@ def setup end + test 'previous linked sample type' do + first_sample_type = FactoryBot.create(:isa_source_sample_type) + second_sample_type = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: first_sample_type) + third_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: second_sample_type) + + assert_equal second_sample_type.previous_linked_sample_type, first_sample_type + refute_equal third_sample_type.previous_linked_sample_type, first_sample_type + refute_equal first_sample_type.previous_linked_sample_type, second_sample_type + end + + test 'next linked sample types' do + first_sample_type = FactoryBot.create(:isa_source_sample_type) + second_sample_type = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: first_sample_type) + third_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: second_sample_type) + + assert_equal first_sample_type.next_linked_sample_types, [second_sample_type] + assert_equal second_sample_type.next_linked_sample_types, [third_sample_type] + assert third_sample_type.next_linked_sample_types.blank? + end + private # sample type with 3 samples From 6b064775ed82d843c1ad23a67e0c11c4bd8d0128 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 26 Jan 2024 10:48:20 +0100 Subject: [PATCH 064/344] Simplify dynamic table helper --- app/helpers/dynamic_table_helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/dynamic_table_helper.rb b/app/helpers/dynamic_table_helper.rb index 6bdacab953..5a547108bd 100644 --- a/app/helpers/dynamic_table_helper.rb +++ b/app/helpers/dynamic_table_helper.rb @@ -22,7 +22,7 @@ def dt_aggregated(study, assay = nil) # Links all sample_types in a sequence of sample_types def link_sequence(sample_type) sequence = [sample_type] - while link = sample_type.sample_attributes.detect(&:seek_sample_multi?)&.linked_sample_type + while link = sample_type.previous_linked_sample_type sequence << link sample_type = link end @@ -96,8 +96,8 @@ def dt_cols(sample_type) 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!({ multi_link: false, linked_sample_type: a.linked_sample_type.id }) if is_seek_sample + attribute.merge!({ multi_link: true, linked_sample_type: a.linked_sample_type_id }) if is_seek_multi_sample + attribute.merge!({ multi_link: false, linked_sample_type: a.linked_sample_type_id }) if is_seek_sample attribute.merge!({is_cv_list: , cv_allows_free_text:}) if is_cv_list attribute end From ce06b7c27443018841e4f8da12a6779353b74d0b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 26 Jan 2024 11:24:27 +0100 Subject: [PATCH 065/344] Fix sample type linkage when inserting new assays --- app/controllers/assays_controller.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index a61deef54b..2252496db8 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -13,6 +13,9 @@ class AssaysController < ApplicationController # defined in the application controller before_action :project_membership_required_appended, only: [:new_object_based_on_existing_one] + # Only for ISA JSON compliant assays => Fix sample type linkage + before_action :fix_assay_linkage_for_new_assays, only: :create + include Seek::Publishing::PublishingCommon include Seek::IsaGraphExtensions @@ -178,6 +181,21 @@ def show private + def fix_assay_linkage_for_new_assays + return unless @isa_assay.assay.is_isa_json_compliant? + + previous_assay_st = @isa_assay.assay.sample_type.previous_linked_sample_type + previous_assay = previous_assay_st.assays.first + next_assay = previous_assay.next_linked_child_assay + + # In case no next assay (an assay was appended to the end of the stream), assay linkage does not have to be fixed. + return unless next_assay + + @isa_assay.assay.sample_type.linked_sample_attribute_ids = [next_assay.sample_type.sample_attributes.detect(&:input_attribute?).id] + previous_assay_st.update(linked_sample_attribute_ids: [@isa_assay.assay.sample_type.sample_attributes.detect(&:input_attribute?).id]) + end + + def assay_params params.require(:assay).permit(:title, :description, :study_id, :assay_class_id, :assay_type_uri, :technology_type_uri, :license, *creator_related_params, :position, { document_ids: [] }, From 69a4ef698e3d07f56857e8675d89ff711dd9be18 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 26 Jan 2024 11:25:27 +0100 Subject: [PATCH 066/344] Rearrange assay positions when inserting or deleting assays --- app/controllers/assays_controller.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 2252496db8..7a88327cbe 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -15,6 +15,7 @@ class AssaysController < ApplicationController # Only for ISA JSON compliant assays => Fix sample type linkage before_action :fix_assay_linkage_for_new_assays, only: :create + after_action :rearrange_assay_positions, only: [:create, :destroy] include Seek::Publishing::PublishingCommon @@ -195,6 +196,18 @@ def fix_assay_linkage_for_new_assays previous_assay_st.update(linked_sample_attribute_ids: [@isa_assay.assay.sample_type.sample_attributes.detect(&:input_attribute?).id]) end + def rearrange_assay_positions + current_assay_stream = @assay.assay_stream + next_assay = current_assay_stream.next_linked_child_assay + assay_position = 0 + + # While there is a next assay, increment position by + while next_assay + next_assay.update(position: assay_position) + next_assay = next_assay.next_linked_child_assay + assay_position += 1 + end + end def assay_params params.require(:assay).permit(:title, :description, :study_id, :assay_class_id, :assay_type_uri, :technology_type_uri, From 61c374f2c0ed60e535d25069e33ad4624b233c19 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 26 Jan 2024 11:30:01 +0100 Subject: [PATCH 067/344] Move code to private section --- app/controllers/assays_controller.rb | 61 +++++++++++++++------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 7a88327cbe..482852abca 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -6,16 +6,19 @@ class AssaysController < ApplicationController before_action :find_assets, only: [:index] before_action :find_and_authorize_requested_item, only: %i[edit update destroy manage manage_update show new_object_based_on_existing_one] - before_action :fix_assay_linkage, only: [:destroy] - before_action :delete_linked_sample_types, only: [:destroy] # project_membership_required_appended is an alias to project_membership_required, but is necessary to include the actions # defined in the application controller before_action :project_membership_required_appended, only: [:new_object_based_on_existing_one] - # Only for ISA JSON compliant assays => Fix sample type linkage + # Only for ISA JSON compliant assays + # => Delete sample type of deleted assay + before_action :fix_assay_linkage_when_deleting_assays, only: [:destroy] + # => Fix sample type linkage + before_action :delete_linked_sample_types, only: [:destroy] before_action :fix_assay_linkage_for_new_assays, only: :create - after_action :rearrange_assay_positions, only: [:create, :destroy] + # => Rearrange positions + after_action :rearrange_assay_positions, only: %i[create destroy] include Seek::Publishing::PublishingCommon @@ -110,30 +113,6 @@ def create end end - def delete_linked_sample_types - return unless @assay.is_isa_json_compliant? - return if @assay.sample_type.nil? - - @assay.sample_type.destroy - end - - def fix_assay_linkage - return unless @assay.is_isa_json_compliant? - return unless @assay.has_linked_child_assay? - - previous_assay_linked_st_id = @assay.previous_linked_sample_type&.id - - next_assay = Assay.all.detect do |a| - a.sample_type&.sample_attributes&.first&.linked_sample_type_id == @assay.sample_type_id - end - - next_assay_st_attr = next_assay.sample_type&.sample_attributes&.first - - return unless next_assay && previous_assay_linked_st_id && next_assay_st_attr - - next_assay_st_attr.update(linked_sample_type_id: previous_assay_linked_st_id) - end - def update update_assay_organisms @assay, params update_assay_human_diseases @assay, params @@ -182,7 +161,31 @@ def show private - def fix_assay_linkage_for_new_assays + def delete_linked_sample_types + return unless @assay.is_isa_json_compliant? + return if @assay.sample_type.nil? + + @assay.sample_type.destroy + end + + def fix_assay_linkage_when_deleting_assays + return unless @assay.is_isa_json_compliant? + return unless @assay.has_linked_child_assay? + + previous_assay_linked_st_id = @assay.previous_linked_sample_type&.id + + next_assay = Assay.all.detect do |a| + a.sample_type&.sample_attributes&.first&.linked_sample_type_id == @assay.sample_type_id + end + + next_assay_st_attr = next_assay.sample_type&.sample_attributes&.first + + return unless next_assay && previous_assay_linked_st_id && next_assay_st_attr + + next_assay_st_attr.update(linked_sample_type_id: previous_assay_linked_st_id) + end + +def fix_assay_linkage_for_new_assays return unless @isa_assay.assay.is_isa_json_compliant? previous_assay_st = @isa_assay.assay.sample_type.previous_linked_sample_type From bdd97c499effa46a72d874e7af38dc66e20a4d26 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 26 Jan 2024 11:54:17 +0100 Subject: [PATCH 068/344] simplify assay linkage when deleting --- app/controllers/assays_controller.rb | 4 +--- app/models/assay.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 482852abca..0157bcfb12 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -174,9 +174,7 @@ def fix_assay_linkage_when_deleting_assays previous_assay_linked_st_id = @assay.previous_linked_sample_type&.id - next_assay = Assay.all.detect do |a| - a.sample_type&.sample_attributes&.first&.linked_sample_type_id == @assay.sample_type_id - end + next_assay = @assay.next_linked_child_assay next_assay_st_attr = next_assay.sample_type&.sample_attributes&.first diff --git a/app/models/assay.rb b/app/models/assay.rb index ab9e5b95e6..16efa73edb 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -78,7 +78,7 @@ def previous_linked_sample_type if is_assay_stream? study.sample_types.second else - sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.linked_sample_type + sample_type.previous_linked_sample_type end end From af66b5396975fe7c2b62986d2da8863debefc93f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 26 Jan 2024 13:16:23 +0100 Subject: [PATCH 069/344] Split functionality over assay and isa assay --- app/controllers/assays_controller.rb | 34 ++++------------------- app/controllers/isa_assays_controller.rb | 35 ++++++++++++++++-------- lib/seek/assets_common.rb | 16 ++++++++++- 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 0157bcfb12..5c0d89f294 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -13,12 +13,11 @@ class AssaysController < ApplicationController # Only for ISA JSON compliant assays # => Delete sample type of deleted assay - before_action :fix_assay_linkage_when_deleting_assays, only: [:destroy] + before_action :delete_linked_sample_types, only: :destroy # => Fix sample type linkage - before_action :delete_linked_sample_types, only: [:destroy] - before_action :fix_assay_linkage_for_new_assays, only: :create + before_action :fix_assay_linkage_when_deleting_assays, only: :destroy # => Rearrange positions - after_action :rearrange_assay_positions, only: %i[create destroy] + after_action :rearrange_assay_positions_at_destroy, only: :destroy include Seek::Publishing::PublishingCommon @@ -183,31 +182,8 @@ def fix_assay_linkage_when_deleting_assays next_assay_st_attr.update(linked_sample_type_id: previous_assay_linked_st_id) end -def fix_assay_linkage_for_new_assays - return unless @isa_assay.assay.is_isa_json_compliant? - - previous_assay_st = @isa_assay.assay.sample_type.previous_linked_sample_type - previous_assay = previous_assay_st.assays.first - next_assay = previous_assay.next_linked_child_assay - - # In case no next assay (an assay was appended to the end of the stream), assay linkage does not have to be fixed. - return unless next_assay - - @isa_assay.assay.sample_type.linked_sample_attribute_ids = [next_assay.sample_type.sample_attributes.detect(&:input_attribute?).id] - previous_assay_st.update(linked_sample_attribute_ids: [@isa_assay.assay.sample_type.sample_attributes.detect(&:input_attribute?).id]) - end - - def rearrange_assay_positions - current_assay_stream = @assay.assay_stream - next_assay = current_assay_stream.next_linked_child_assay - assay_position = 0 - - # While there is a next assay, increment position by - while next_assay - next_assay.update(position: assay_position) - next_assay = next_assay.next_linked_child_assay - assay_position += 1 - end + def rearrange_assay_positions_at_destroy + rearrange_assay_positions(@assay.assay_stream) end def assay_params diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 29df51f069..b1af5583a7 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -4,7 +4,9 @@ class IsaAssaysController < ApplicationController before_action :set_up_instance_variable before_action :find_requested_item, only: %i[edit update] - after_action :fix_assay_linkage_for_new_assays, only: :create + before_action :initialize_isa_assay, only: :create + before_action :fix_assay_linkage_for_new_assays, only: :create + after_action :rearrange_assay_positions_create_isa_assay, only: :create def new if params[:is_assay_stream] @@ -15,10 +17,6 @@ def new end def create - @isa_assay = IsaAssay.new(isa_assay_params) - update_sharing_policies @isa_assay.assay - @isa_assay.assay.contributor = current_person - @isa_assay.sample_type.contributor = User.current_user.person if isa_assay_params[:sample_type] if @isa_assay.save redirect_to single_page_path(id: @isa_assay.assay.projects.first, item_type: 'assay', item_id: @isa_assay.assay, notice: 'The ISA assay was created successfully!') @@ -65,21 +63,34 @@ def update end end + private + def fix_assay_linkage_for_new_assays return unless @isa_assay.assay.is_isa_json_compliant? - previous_assay_st = @isa_assay.assay.previous_linked_sample_type + previous_assay_st = @isa_assay.assay.sample_type.previous_linked_sample_type previous_assay = previous_assay_st.assays.first - next_assay = previous_assay.next_linked_child_assay - next_assay.update(position: @isa_assay.assay.position + 1) + # In case an assay is inserted right at the beginning of an assay stream, + # the next assay is the current first one in the assay stream. + next_assay = previous_assay.nil? ? @isa_assay.assay.assay_stream.next_linked_child_assay : previous_assay.next_linked_child_assay + + # In case no next assay (an assay was appended to the end of the stream), assay linkage does not have to be fixed. return unless next_assay - current_assay_st = @isa_assay.assay.sample_type - previous_assay_st.update(linked_sample_attribute_ids: [@isa_assay.assay.sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }.id]) - current_assay_st.update(linked_sample_attribute_ids: [next_assay.sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }.id]) + @isa_assay.assay.sample_type.linked_sample_attribute_ids = [next_assay.sample_type.sample_attributes.detect(&:input_attribute?).id] + previous_assay_st.update(linked_sample_attribute_ids: [@isa_assay.assay.sample_type.sample_attributes.detect(&:input_attribute?).id]) end - private + def rearrange_assay_positions_create_isa_assay + rearrange_assay_positions(@isa_assay.assay.assay_stream) + end + + def initialize_isa_assay + @isa_assay = IsaAssay.new(isa_assay_params) + update_sharing_policies @isa_assay.assay + @isa_assay.assay.contributor = current_person + @isa_assay.sample_type.contributor = User.current_user.person if isa_assay_params[:sample_type] + end def isa_assay_params # TODO: get the params from a shared module diff --git a/lib/seek/assets_common.rb b/lib/seek/assets_common.rb index e72fbf3859..b528b19ac4 100644 --- a/lib/seek/assets_common.rb +++ b/lib/seek/assets_common.rb @@ -29,7 +29,7 @@ def find_display_asset(asset = instance_variable_get("@#{controller_name.singula def update_relationships(asset, params) Relationship.set_attributions(asset, params[:attributions]) end - + def request_contact resource = class_for_controller_name.find(params[:id]) details = params[:details] @@ -74,5 +74,19 @@ def params_for_controller method = "#{name}_params" send(method) end + + def rearrange_assay_positions(assay_stream) + disable_authorization_checks do + next_assay = assay_stream.next_linked_child_assay + assay_position = 0 + + # While there is a next assay, increment position by + while next_assay + next_assay.update(position: assay_position) + next_assay = next_assay.next_linked_child_assay + assay_position += 1 + end + end + end end end From e1c790357f38af2eeea3271b59b2728c767d929f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 29 Jan 2024 07:57:45 +0100 Subject: [PATCH 070/344] Delete ST first, fix linkage later --- app/controllers/assays_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 5c0d89f294..140b5c0dd6 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -12,10 +12,10 @@ class AssaysController < ApplicationController before_action :project_membership_required_appended, only: [:new_object_based_on_existing_one] # Only for ISA JSON compliant assays - # => Delete sample type of deleted assay - before_action :delete_linked_sample_types, only: :destroy # => Fix sample type linkage before_action :fix_assay_linkage_when_deleting_assays, only: :destroy + # => Delete sample type of deleted assay + before_action :delete_linked_sample_types, only: :destroy # => Rearrange positions after_action :rearrange_assay_positions_at_destroy, only: :destroy @@ -175,7 +175,7 @@ def fix_assay_linkage_when_deleting_assays next_assay = @assay.next_linked_child_assay - next_assay_st_attr = next_assay.sample_type&.sample_attributes&.first + next_assay_st_attr = next_assay.sample_type&.sample_attributes&.detect(&:input_attribute?) return unless next_assay && previous_assay_linked_st_id && next_assay_st_attr From f4b6783055a2a72fd9643ce03f189540edfaedad Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 29 Jan 2024 07:58:33 +0100 Subject: [PATCH 071/344] Simplify deletion of linked sampletypes in studies --- app/controllers/studies_controller.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index 2849b2f1a8..642494ae57 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -89,7 +89,7 @@ def update end def delete_linked_sample_types - return unless is_single_page_study? + return unless @study.is_isa_json_compliant? return if @study.sample_types.empty? # The study sample types must be destroyed in reversed order @@ -361,9 +361,3 @@ def study_params { extended_metadata_attributes: determine_extended_metadata_keys }) end end - -def is_single_page_study? - return false unless params.key?(:return_to) - - params[:return_to].start_with? '/single_pages/' -end From 532c9f3a7de07c6a8900050b33a15b8d75490796 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 29 Jan 2024 08:00:06 +0100 Subject: [PATCH 072/344] Fix existing assays controller tests --- app/models/assay.rb | 6 +++- app/models/sample_type.rb | 16 ++++++--- lib/seek/assets_common.rb | 2 ++ test/functional/assays_controller_test.rb | 41 +++++++++++++++++------ 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index 16efa73edb..76153b0729 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -75,7 +75,7 @@ def is_assay_stream? def previous_linked_sample_type return unless is_isa_json_compliant? - if is_assay_stream? + if is_assay_stream? || first_assay_in_stream? study.sample_types.second else sample_type.previous_linked_sample_type @@ -102,6 +102,10 @@ def next_linked_child_assay end end + def first_assay_in_stream? + self == assay_stream.child_assays.first + end + def default_contributor User.current_user.try :person end diff --git a/app/models/sample_type.rb b/app/models/sample_type.rb index 729db7b338..36648afa04 100644 --- a/app/models/sample_type.rb +++ b/app/models/sample_type.rb @@ -128,11 +128,17 @@ def can_edit?(user = User.current_user) end def can_delete?(user = User.current_user) - can_edit?(user) && samples.empty? && - linked_sample_attributes.detect do |attr| - attr.sample_type && - attr.sample_type != self - end.nil? + # Users should be able to delete an ISA JSON compliant sample type that has linked sample attributes, + # as long as it's ISA JSON compliant. + if is_isa_json_compliant? + can_edit?(user) && samples.empty? + else + can_edit?(user) && samples.empty? && + linked_sample_attributes.detect do |attr| + attr.sample_type && + attr.sample_type != self + end.nil? + end end def can_view?(user = User.current_user, referring_sample = nil, view_in_single_page = false) diff --git a/lib/seek/assets_common.rb b/lib/seek/assets_common.rb index b528b19ac4..226ad2407f 100644 --- a/lib/seek/assets_common.rb +++ b/lib/seek/assets_common.rb @@ -76,6 +76,8 @@ def params_for_controller end def rearrange_assay_positions(assay_stream) + return unless assay_stream + disable_authorization_checks do next_assay = assay_stream.next_linked_child_assay assay_position = 0 diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 1996d36918..dae23793ae 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -1924,21 +1924,35 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links test 'should delete empty assay with linked sample type' do person = FactoryBot.create(:person) project = person.projects.first + investigation = FactoryBot.create(:investigation, projects: [project], is_isa_json_compliant: true, contributor: person) source_st = FactoryBot.create(:isa_source_sample_type, contributor: person, projects: [project]) sample_collection_st = FactoryBot.create(:isa_sample_collection_sample_type, contributor: person, projects: [project], linked_sample_type: source_st) - assay_sample_type = FactoryBot.create :isa_assay_material_sample_type, linked_sample_type: sample_collection_st, contributor: person, isa_template: FactoryBot.build(:isa_assay_material_template) + study = FactoryBot.create(:study, investigation: , contributor: person, + policy: FactoryBot.create(:private_policy, permissions: [FactoryBot.create(:permission, contributor: person, access_type: Policy::MANAGING)]), + sops: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy))], + sample_types: [source_st, sample_collection_st]) + assay_stream = FactoryBot.create(:assay_stream, study: , contributor: person) + assay_sample_type = FactoryBot.create :isa_assay_material_sample_type, linked_sample_type: sample_collection_st, + contributor: person, isa_template: FactoryBot.build(:isa_assay_material_template) assay = FactoryBot.create(:assay, - policy:FactoryBot.create(:private_policy, permissions:[FactoryBot.create(:permission,contributor: person, access_type:Policy::EDITING)]), + study: , + policy: FactoryBot.create(:private_policy, permissions:[FactoryBot.create(:permission,contributor: person, access_type:Policy::EDITING)]), sample_type: assay_sample_type, - contributor: person) + contributor: person, + assay_stream: ) + login_as(person) + assert assay.is_isa_json_compliant? + assert assay.sample_type.is_isa_json_compliant? + assert assay.sample_type.can_delete? + assert_difference('SampleType.count', -1) do assert_difference('Assay.count', -1) do - delete :destroy, params: { id: assay.id, return_to: '/single_pages/' } + delete :destroy, params: { id: assay.id } end end end @@ -1969,22 +1983,29 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links sops: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy))], sample_types: [source_st, sample_collection_st]) - assay1 = FactoryBot.create(:assay, study: study, contributor: person, sample_type: assay_st1, - policy: FactoryBot.create(:private_policy, permissions: [FactoryBot.create(:permission, contributor: person, access_type: Policy::MANAGING)])) + assay_stream = FactoryBot.create(:assay_stream, study: , contributor: person) + assay1 = FactoryBot.create(:assay, study: , contributor: person, sample_type: assay_st1, + policy: FactoryBot.create(:private_policy, permissions: [FactoryBot.create(:permission, contributor: person, access_type: Policy::MANAGING)]), + position: 0, assay_stream: ) assay2 = FactoryBot.create(:assay, study: study, contributor: person, sample_type: assay_st2, - policy: FactoryBot.create(:private_policy, permissions: [FactoryBot.create(:permission, contributor: person, access_type: Policy::MANAGING)])) + policy: FactoryBot.create(:private_policy, permissions: [FactoryBot.create(:permission, contributor: person, access_type: Policy::MANAGING)]), + position: 1, assay_stream: ) assay3 = FactoryBot.create(:assay, study: study, contributor: person, sample_type: assay_st3, - policy: FactoryBot.create(:private_policy, permissions: [FactoryBot.create(:permission, contributor: person, access_type: Policy::MANAGING)])) + policy: FactoryBot.create(:private_policy, permissions: [FactoryBot.create(:permission, contributor: person, access_type: Policy::MANAGING)]), + position: 2, assay_stream: ) login_as(person) - assert_difference "Assay.count", -1 do - delete :destroy, params: { id: assay2.id, return_to: '/single_pages/' } + assert_difference("SampleType.count", -1) do + assert_difference("Assay.count", -1) do + delete :destroy, params: { id: assay2.id } + end end assay3.reload assert_equal(assay3.previous_linked_sample_type&.id, assay1.sample_type&.id) + assert_equal assay3.position, 1 end test 'do not get index if feature disabled' do From 88c205389d2e4b2311384a0e37886eaaeddae795 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 29 Jan 2024 08:09:38 +0100 Subject: [PATCH 073/344] Fix existing studies_controller tests --- test/functional/studies_controller_test.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb index a29d1c8891..c022e8aa04 100644 --- a/test/functional/studies_controller_test.rb +++ b/test/functional/studies_controller_test.rb @@ -1976,15 +1976,19 @@ def test_should_show_investigation_tab test 'should delete empty study with linked sample type' do person = FactoryBot.create(:person) - study_source_sample_type = FactoryBot.create :linked_sample_type, contributor: person - study_sample_sample_type = FactoryBot.create :linked_sample_type, contributor: person + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) + study_source_sample_type = FactoryBot.create :isa_source_sample_type, contributor: person + study_sample_sample_type = FactoryBot.create :isa_sample_collection_sample_type, linked_sample_type: study_source_sample_type, contributor: person study = FactoryBot.create(:study, + investigation: , policy:FactoryBot.create(:private_policy, permissions:[FactoryBot.create(:permission,contributor: person, access_type:Policy::EDITING)]), sample_types: [study_source_sample_type, study_sample_sample_type], contributor: person) login_as(person) + assert study.is_isa_json_compliant? + assert_difference('SampleType.count', -2) do assert_difference('Study.count', -1) do delete :destroy, params: { id: study.id, return_to: '/single_pages/' } From 50dfe2ac2ec62445ac07a6580874e5c1d5626e43 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 13:58:12 +0100 Subject: [PATCH 074/344] Correct sample type linkage --- app/controllers/isa_assays_controller.rb | 16 +++++++++++----- app/models/assay.rb | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index b1af5583a7..fd7b9d278b 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -5,7 +5,7 @@ class IsaAssaysController < ApplicationController before_action :set_up_instance_variable before_action :find_requested_item, only: %i[edit update] before_action :initialize_isa_assay, only: :create - before_action :fix_assay_linkage_for_new_assays, only: :create + after_action :fix_assay_linkage_for_new_assays, only: :create after_action :rearrange_assay_positions_create_isa_assay, only: :create def new @@ -67,9 +67,11 @@ def update def fix_assay_linkage_for_new_assays return unless @isa_assay.assay.is_isa_json_compliant? + return if @isa_assay.assay.is_assay_stream? # Should not fix anything when creating an assay stream + + previous_sample_type = SampleType.find(params[:isa_assay][:input_sample_type_id]) + previous_assay = previous_sample_type.assays.first - previous_assay_st = @isa_assay.assay.sample_type.previous_linked_sample_type - previous_assay = previous_assay_st.assays.first # In case an assay is inserted right at the beginning of an assay stream, # the next assay is the current first one in the assay stream. next_assay = previous_assay.nil? ? @isa_assay.assay.assay_stream.next_linked_child_assay : previous_assay.next_linked_child_assay @@ -77,8 +79,12 @@ def fix_assay_linkage_for_new_assays # In case no next assay (an assay was appended to the end of the stream), assay linkage does not have to be fixed. return unless next_assay - @isa_assay.assay.sample_type.linked_sample_attribute_ids = [next_assay.sample_type.sample_attributes.detect(&:input_attribute?).id] - previous_assay_st.update(linked_sample_attribute_ids: [@isa_assay.assay.sample_type.sample_attributes.detect(&:input_attribute?).id]) + next_assay_input_attribute_id = next_assay.sample_type.sample_attributes.detect(&:input_attribute?).id + return unless next_assay_input_attribute_id + + # Add link of next assay sample type to currently created assay sample type + updated_lsai = @isa_assay.assay.sample_type.linked_sample_attribute_ids.push(next_assay_input_attribute_id) + @isa_assay.assay.sample_type.update(linked_sample_attribute_ids: updated_lsai) end def rearrange_assay_positions_create_isa_assay diff --git a/app/models/assay.rb b/app/models/assay.rb index 76153b0729..ce591f9534 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -96,9 +96,9 @@ def next_linked_child_assay return unless has_linked_child_assay? if is_assay_stream? - previous_linked_sample_type&.linked_sample_attributes&.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.sample_type&.assays&.first + child_assays.first else - sample_type.linked_sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.sample_type&.assays&.first + sample_type.next_linked_sample_types.map(&:assays).flatten.detect { |a| a.assay_stream_id == assay_stream_id } end end From 4a6c0eb53ef67414ddec3fd63cbcc4cd99145e1d Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 13:58:46 +0100 Subject: [PATCH 075/344] Fix existing tests --- test/factories/studies.rb | 2 +- test/unit/assay_test.rb | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/test/factories/studies.rb b/test/factories/studies.rb index 470ed950e3..afb01507f0 100644 --- a/test/factories/studies.rb +++ b/test/factories/studies.rb @@ -41,7 +41,7 @@ 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) + 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] diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index f72a1e7d50..653d618db4 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -752,7 +752,8 @@ def new_valid_assay end test 'isa json compliance' do - isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) + isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) assert isa_json_compliant_study.is_isa_json_compliant? default_assay = FactoryBot.create(:assay, study: isa_json_compliant_study) @@ -775,10 +776,13 @@ def new_valid_assay end test 'previous linked sample type' do - isa_study = FactoryBot.create(:isa_json_compliant_study) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) + isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) def_study = FactoryBot.create(:study) assay_stream = FactoryBot.create(:assay_stream, study: isa_study) + assert assay_stream.is_assay_stream? + assert assay_stream.is_isa_json_compliant? assert_equal assay_stream.previous_linked_sample_type, isa_study.sample_types.second def_assay = FactoryBot.create(:assay, study:def_study) @@ -800,7 +804,8 @@ def new_valid_assay end test 'has_linked_child_assay?' do - isa_study = FactoryBot.create(:isa_json_compliant_study) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) + isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) def_study = FactoryBot.create(:study) def_assay = FactoryBot.create(:assay, study:def_study) @@ -820,12 +825,13 @@ def new_valid_assay end test 'next_linked_child_assay' do - isa_study = FactoryBot.create(:isa_json_compliant_study) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) + isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) def_study = FactoryBot.create(:study) def_assay = FactoryBot.create(:assay, study:def_study) assay_stream = FactoryBot.create(:assay_stream, study: isa_study) - first_isa_assay = FactoryBot.create(:isa_json_compliant_assay, study: isa_study) + first_isa_assay = FactoryBot.create(:isa_json_compliant_assay, study: isa_study, assay_stream: ) data_file_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: first_isa_assay.sample_type) second_isa_assay = FactoryBot.create(:assay, From 8366b9efac46ca2d6c56f526f0f55061943ca6fb Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 14:00:46 +0100 Subject: [PATCH 076/344] Write test for inserting new assay between assay stream and first assay --- test/functional/isa_assays_controller_test.rb | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 1212dd21d1..a138244df5 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -281,4 +281,86 @@ def setup assert_select 'div.panel-heading', text: /Discussion Channels/i, count: 1 assert_select 'div.panel-heading', text: /Define Sample type for Assay/i, count: 1 end + + test 'insert assay between assay stream and experimental assay' do + # TODO: Test button text + person = FactoryBot.create(:person) + project = person.projects.first + login_as(person) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: person) + study = FactoryBot.create(:isa_json_compliant_study, investigation: , contributor: person ) + + ## Create an assay stream + assay_stream = FactoryBot.create(:assay_stream, contributor: person, study: ) + assert assay_stream.is_assay_stream? + assert_equal assay_stream.previous_linked_sample_type, study.sample_types.second + assert_nil assay_stream.next_linked_child_assay + + ## Create an assay at the end of the stream + end_assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, + linked_sample_type: study.sample_types.second, + projects: [project], + contributor: person) + end_assay = FactoryBot.create(:assay, contributor: person, study: , sample_type: end_assay_sample_type, assay_stream: ) + + refute end_assay.is_assay_stream? + assert_equal end_assay.previous_linked_sample_type, assay_stream.previous_linked_sample_type, study.sample_types.second + assert_nil end_assay.next_linked_child_assay + + # Test assay linkage + ## Post intermediate assay + policy_attributes = { access_type: Policy::ACCESSIBLE, + permissions_attributes: project_permissions([projects.first], Policy::ACCESSIBLE) } + + intermediate_assay_attributes1 = { title: 'First intermediate assay', + study_id: study.id, + assay_class_id: AssayClass.for_type(Seek:: ISA:: AssayClass::EXP).id, + creator_ids: [person.id], + policy_attributes: , + assay_stream_id: assay_stream.id} + + intermediate_assay_sample_type_attributes1 = { title: "Intermediate Assay Sample type 1", + project_ids: [project.id], + sample_attributes_attributes: { + '0': { + pos: '1', title: 'a string', required: '1', is_title: '1', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, _destroy: '0', + isa_tag_id: FactoryBot.create(:other_material_isa_tag).id + }, + '1': { + pos: '2', title: 'protocol', required: '1', is_title: '0', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, + isa_tag_id: FactoryBot.create(:protocol_isa_tag).id, _destroy: '0' + }, + '2': { + pos: '3', title: 'Input sample', required: '1', + sample_attribute_type_id: FactoryBot.create(:sample_multi_sample_attribute_type).id, + linked_sample_type_id: study.sample_types.second.id, _destroy: '0' + }, + '3': { + pos: '4', title: 'Some material characteristic', required: '1', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, + _destroy: '0', + isa_tag_id: FactoryBot.create(:other_material_characteristic_isa_tag).id + } + } + } + + intermediate_isa_assay_attributes1 = { assay: intermediate_assay_attributes1, + input_sample_type_id: study.sample_types.second.id, + sample_type: intermediate_assay_sample_type_attributes1 } + + assert_difference "Assay.count", 1 do + assert_difference "SampleType.count", 1 do + post :create, params: { isa_assay: intermediate_isa_assay_attributes1 } + end + end + + isa_assay = assigns(:isa_assay) + assert_redirected_to single_page_path(id: project, item_type: 'assay', item_id: isa_assay.assay.id, notice: 'The ISA assay was created successfully!') + + assert_equal isa_assay.assay.sample_type.previous_linked_sample_type, study.sample_types.second + assert_equal isa_assay.assay.next_linked_child_assay, end_assay + end + end From 78c3a7e30ea0048e3183c8b90a6609882d132559 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 14:01:55 +0100 Subject: [PATCH 077/344] Write test for inserting an assay between two experimental assays --- test/functional/isa_assays_controller_test.rb | 96 ++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index a138244df5..435e974c88 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -248,7 +248,7 @@ def setup assert_select 'div#add_documents_form', text: /Documents/i, count: 0 assert_select 'div.panel-heading', text: /Discussion Channels/i, count: 0 assert_select 'div.panel-heading', text: /Define Sample type for Assay/i, count: 0 -end + end test 'show sops, publications, documents, and discussion channels if experimental assay' do person = FactoryBot.create(:person) @@ -363,4 +363,98 @@ def setup assert_equal isa_assay.assay.next_linked_child_assay, end_assay end + test 'insert assay between two experimental assays' do + # TODO: Test button text + person = FactoryBot.create(:person) + project = person.projects.first + login_as(person) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: person) + study = FactoryBot.create(:isa_json_compliant_study, investigation: , contributor: person ) + + ## Create an assay stream + assay_stream = FactoryBot.create(:assay_stream, contributor: person, study: ) + assert assay_stream.is_assay_stream? + assert_equal assay_stream.previous_linked_sample_type, study.sample_types.second + assert_nil assay_stream.next_linked_child_assay + + ## Create an assay at the begin of the stream + begin_assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, + linked_sample_type: study.sample_types.second, + projects: [project], + contributor: person) + begin_assay = FactoryBot.create(:assay, title: 'Begin Assay', contributor: person, study: , sample_type: begin_assay_sample_type, assay_stream: ) + + ## Create an assay at the end of the stream + end_assay_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, + linked_sample_type: begin_assay_sample_type, + projects: [project], + contributor: person) + end_assay = FactoryBot.create(:assay, title: 'End Assay', contributor: person, study: , sample_type: end_assay_sample_type, assay_stream: ) + + refute end_assay.is_assay_stream? + assert_equal begin_assay.previous_linked_sample_type, assay_stream.previous_linked_sample_type, study.sample_types.second + assert_nil end_assay.next_linked_child_assay + + # Test assay linkage + ## Post intermediate assay + policy_attributes = { access_type: Policy::ACCESSIBLE, + permissions_attributes: project_permissions([projects.first], Policy::ACCESSIBLE) } + + intermediate_assay_attributes2 = { title: 'Second intermediate assay', + study_id: study.id, + assay_class_id: AssayClass.for_type(Seek:: ISA:: AssayClass::EXP).id, + creator_ids: [person.id], + policy_attributes: , + assay_stream_id: assay_stream.id} + + intermediate_assay_sample_type_attributes2 = { title: "Intermediate Assay Sample type 2", + project_ids: [project.id], + sample_attributes_attributes: { + '0': { + pos: '1', title: 'a string', required: '1', is_title: '1', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, _destroy: '0', + isa_tag_id: FactoryBot.create(:other_material_isa_tag).id + }, + '1': { + pos: '2', title: 'protocol', required: '1', is_title: '0', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, + isa_tag_id: FactoryBot.create(:protocol_isa_tag).id, _destroy: '0' + }, + '2': { + pos: '3', title: 'Input sample', required: '1', + sample_attribute_type_id: FactoryBot.create(:sample_multi_sample_attribute_type).id, + linked_sample_type_id: study.sample_types.second.id, _destroy: '0' + }, + '3': { + pos: '4', title: 'Some material characteristic', required: '1', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, + _destroy: '0', + isa_tag_id: FactoryBot.create(:other_material_characteristic_isa_tag).id + } + } + } + + intermediate_isa_assay_attributes2 = { assay: intermediate_assay_attributes2, + input_sample_type_id: begin_assay_sample_type.id, + sample_type: intermediate_assay_sample_type_attributes2 } + + + assert_difference "Assay.count", 1 do + assert_difference "SampleType.count", 1 do + post :create, params: { isa_assay: intermediate_isa_assay_attributes2 } + end + end + + isa_assay = assigns(:isa_assay) + assert_redirected_to single_page_path(id: project, item_type: 'assay', item_id: isa_assay.assay.id, notice: 'The ISA assay was created successfully!') + + puts "Assay added: #{isa_assay.assay.inspect}" + puts "Assay ST added: #{isa_assay.assay.sample_type.inspect}" + puts "Assay from DB added: #{Assay.last.inspect}" + + assert_equal begin_assay.previous_linked_sample_type, study.sample_types.second + assert_equal isa_assay.assay.sample_type.previous_linked_sample_type, begin_assay.sample_type + assert_equal isa_assay.assay.next_linked_child_assay, end_assay + end + end From fc4c991756b5ba6562596589468e44173598eb90 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 14:08:10 +0100 Subject: [PATCH 078/344] Actually better as a before_action since the sample_type is created before the assay --- app/controllers/isa_assays_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index fd7b9d278b..178ec8bd55 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -5,7 +5,7 @@ class IsaAssaysController < ApplicationController before_action :set_up_instance_variable before_action :find_requested_item, only: %i[edit update] before_action :initialize_isa_assay, only: :create - after_action :fix_assay_linkage_for_new_assays, only: :create + before_action :fix_assay_linkage_for_new_assays, only: :create after_action :rearrange_assay_positions_create_isa_assay, only: :create def new From d2d1a90ec4fa9e2b0445855ee5518bd7b6ccea10 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 14:34:36 +0100 Subject: [PATCH 079/344] Change the button text when inserting an assay between existing assays --- app/views/assays/_buttons.html.erb | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 72c59241fe..38e9e5bf18 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -6,6 +6,22 @@ else t("assays.#{item.assay_class.long_key.delete(' ').underscore}") end + + isa_assay_verb ||= + if item&.is_assay_stream? + if item.next_linked_child_assay + "Insert a new" + else + "Design" + end + else + if item.next_linked_child_assay + "Insert a new" + else + "Design the next" + end + end + %> <%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %> @@ -33,9 +49,9 @@ <% valid_assay = item&.is_isa_json_compliant? %> <% if valid_study && valid_assay %> <% if item&.is_assay_stream? %> - <%= button_link_to("Design #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.id)) %> + <%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.id)) %> <% else %> - <%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id)) %> + <%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id)) %> <% end %> <% end %> <% else %> From 3f75302a60650fc914d55ca3b2e9e072ee3e2891 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 14:36:25 +0100 Subject: [PATCH 080/344] Modify existing tests for inserting assays --- test/functional/assays_controller_test.rb | 27 ++++++++++++++++--- test/functional/isa_assays_controller_test.rb | 2 -- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index dae23793ae..08bd48b4dd 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -2033,26 +2033,45 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links 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) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: current_user.person) + study = FactoryBot.create(:isa_json_compliant_study, investigation: ) assay_stream = FactoryBot.create(:assay_stream, study: , contributor: current_user.person) - assay1 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: , assay_stream:) - assay2 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: , assay_stream:) + get :show, params: { id: assay_stream } + assert_response :success + + # If stream has no assays, it should say 'Design Assay' + assert_select 'a', text: /Design #{I18n.t('assay')}/i, count: 1 + + assay_sample_type1 = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: study.sample_types.second) + assay1 = FactoryBot.create(:assay, contributor: current_user.person, study: , assay_stream:, sample_type: assay_sample_type1) assert_equal assay_stream.study, assay1.study get :show, params: { id: assay_stream } assert_response :success - assert_select 'a', text: /Design #{I18n.t('assay')}/i, count: 1 + # If stream has child assays, it should say 'Insert a new Assay' + assert_select 'a', text: /Insert a new #{I18n.t('assay')}/i, count: 1 get :show, params: { id: assay1 } assert_response :success + # If current assay doesn't have a next assay in the same stream, it should say 'Design the next Assay' assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1 + assay_sample_type2 = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: assay_sample_type1) + assay2 = FactoryBot.create(:assay, contributor: current_user.person, study: , assay_stream:, sample_type: assay_sample_type2) + + get :show, params: { id: assay1 } + assert_response :success + + # If current assay has a next assay in the same stream, it should say 'Insert a new Assay' + assert_select 'a', text: /Insert a new #{I18n.t('assay')}/i, count: 1 + get :show, params: { id: assay2 } assert_response :success + # If current assay is at the end of the stream, it should say 'Design the next Assay' again assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1 end end diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 435e974c88..2bafd4cb92 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -283,7 +283,6 @@ def setup end test 'insert assay between assay stream and experimental assay' do - # TODO: Test button text person = FactoryBot.create(:person) project = person.projects.first login_as(person) @@ -364,7 +363,6 @@ def setup end test 'insert assay between two experimental assays' do - # TODO: Test button text person = FactoryBot.create(:person) project = person.projects.first login_as(person) From 183c8ed5fc2a013426f333bd78c89c2eeff09b57 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 15:30:51 +0100 Subject: [PATCH 081/344] Disable button when inserting an assay before an assay with samples in its sample type --- app/views/assays/_buttons.html.erb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 38e9e5bf18..747e72addc 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -22,6 +22,7 @@ end end + hide_new_assays_button = item.next_linked_child_assay&.sample_type&.samples&.any? %> <%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %> @@ -48,10 +49,14 @@ <% valid_study = item&.study&.is_isa_json_compliant? %> <% valid_assay = item&.is_isa_json_compliant? %> <% if valid_study && valid_assay %> - <% if item&.is_assay_stream? %> - <%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.id)) %> + <% if hide_new_assays_button %> + <%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', nil, disabled_reason: 'The next linked assay has samples. Cannot insert new assay here.') %> <% else %> - <%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id)) %> + <% if item&.is_assay_stream? %> + <%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.id)) %> + <% else %> + <%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id)) %> + <% end %> <% end %> <% end %> <% else %> From a09434bee04dff1fcb8feb4ef3d86e60e041733a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 16:02:23 +0100 Subject: [PATCH 082/344] Add test for disabled button --- test/functional/assays_controller_test.rb | 54 +++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 08bd48b4dd..6bde112931 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -2032,6 +2032,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links test 'display adjusted buttons if isa json compliant' do with_config_value(:isa_json_compliance_enabled, true) do current_user = FactoryBot.create(:user) + project = current_user.person.projects.first login_as(current_user) investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: current_user.person) study = FactoryBot.create(:isa_json_compliant_study, investigation: ) @@ -2073,6 +2074,59 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links # If current assay is at the end of the stream, it should say 'Design the next Assay' again assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1 + + source_sample = + FactoryBot.create :sample, + title: 'source 1', + sample_type: study.sample_types.first, + project_ids: [project.id], + data: { + 'Source Name': 'Source Name', + 'Source Characteristic 1': 'Source Characteristic 1', + 'Source Characteristic 2': + study.sample_types.first + .sample_attributes + .find_by_title('Source Characteristic 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label + }, + contributor: current_user.person + + sample_sample = + FactoryBot.create :sample, + title: 'sample 1', + sample_type: study.sample_types.second, + 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 + + FactoryBot.create :sample, + title: 'assay 1 - sample 1', + sample_type: assay_sample_type1, + project_ids: [project.id], + data: { + Input: [sample_sample.id], + 'Protocol Assay 1': 'Protocol Assay 1', + 'Assay 1 parameter value 1': 'Assay 1 parameter value 1', + 'Extract Name': 'Extract Name', + 'other material characteristic 1': 'other material characteristic 1' + }, + contributor: current_user.person + + get :show, params: { id: assay_stream } + assert_response :success + + # If the next assay's sample type has samples, the 'new assay' button should be disabled' + assert_select 'a', text: /Insert a new #{I18n.t('assay')}/i, class: 'disabled', count: 1 + end end end From 518b8ce6b48246840268e5d743128ef7f2624cdc Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 17:34:09 +0100 Subject: [PATCH 083/344] Test prevent inserting new assay if next assay's sample type has samples --- test/functional/isa_assays_controller_test.rb | 138 +++++++++++++++++- 1 file changed, 134 insertions(+), 4 deletions(-) diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 2bafd4cb92..b554085818 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -360,6 +360,8 @@ def setup assert_equal isa_assay.assay.sample_type.previous_linked_sample_type, study.sample_types.second assert_equal isa_assay.assay.next_linked_child_assay, end_assay + + # TODO: Test the assay positions after reorganising end test 'insert assay between two experimental assays' do @@ -446,13 +448,141 @@ def setup isa_assay = assigns(:isa_assay) assert_redirected_to single_page_path(id: project, item_type: 'assay', item_id: isa_assay.assay.id, notice: 'The ISA assay was created successfully!') - puts "Assay added: #{isa_assay.assay.inspect}" - puts "Assay ST added: #{isa_assay.assay.sample_type.inspect}" - puts "Assay from DB added: #{Assay.last.inspect}" - assert_equal begin_assay.previous_linked_sample_type, study.sample_types.second assert_equal isa_assay.assay.sample_type.previous_linked_sample_type, begin_assay.sample_type assert_equal isa_assay.assay.next_linked_child_assay, end_assay + + # TODO: Test the assay positions after reorganising end + test 'should not insert assay if next assay has samples' do + person = FactoryBot.create(:person) + project = person.projects.first + login_as(person) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: person) + study = FactoryBot.create(:isa_json_compliant_study, investigation: , contributor: person ) + + ## Create an assay stream + assay_stream = FactoryBot.create(:assay_stream, contributor: person, study: ) + assert assay_stream.is_assay_stream? + assert_equal assay_stream.previous_linked_sample_type, study.sample_types.second + assert_nil assay_stream.next_linked_child_assay + + ## Create an assay at the begin of the stream + begin_assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, + linked_sample_type: study.sample_types.second, + projects: [project], + contributor: person) + begin_assay = FactoryBot.create(:assay, title: 'Begin Assay', contributor: person, study: , sample_type: begin_assay_sample_type, assay_stream: ) + + ## Create an assay at the end of the stream + end_assay_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, + linked_sample_type: begin_assay_sample_type, + projects: [project], + contributor: person) + end_assay = FactoryBot.create(:assay, title: 'End Assay', contributor: person, study: , sample_type: end_assay_sample_type, assay_stream: ) + + refute end_assay.is_assay_stream? + assert_equal begin_assay.previous_linked_sample_type, assay_stream.previous_linked_sample_type, study.sample_types.second + assert_nil end_assay.next_linked_child_assay + + source_sample = + FactoryBot.create :sample, + title: 'source 1', + sample_type: study.sample_types.first, + project_ids: [project.id], + data: { + 'Source Name': 'Source Name', + 'Source Characteristic 1': 'Source Characteristic 1', + 'Source Characteristic 2': + study.sample_types.first + .sample_attributes + .find_by_title('Source Characteristic 2') + .sample_controlled_vocab + .sample_controlled_vocab_terms + .first + .label + }, + contributor: person + + sample_sample = + FactoryBot.create :sample, + title: 'sample 1', + sample_type: study.sample_types.second, + 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: person + + FactoryBot.create :sample, + title: 'Begin Material 1', + sample_type: begin_assay_sample_type, + project_ids: [project.id], + data: { + Input: [sample_sample.id], + 'Protocol Assay 1': 'Protocol Assay 1', + 'Assay 1 parameter value 1': 'Assay 1 parameter value 1', + 'Extract Name': 'Extract Name', + 'other material characteristic 1': 'other material characteristic 1' + }, + contributor: person + + + assert assay_stream.next_linked_child_assay.sample_type.samples.any? + + # Test assay linkage + ## Post intermediate assay + policy_attributes = { access_type: Policy::ACCESSIBLE, + permissions_attributes: project_permissions([projects.first], Policy::ACCESSIBLE) } + + intermediate_assay_attributes3 = { title: 'Third intermediate assay', + study_id: study.id, + assay_class_id: AssayClass.for_type(Seek:: ISA:: AssayClass::EXP).id, + creator_ids: [person.id], + policy_attributes: , + assay_stream_id: assay_stream.id} + + intermediate_assay_sample_type_attributes3 = { title: "Intermediate Assay Sample type 3", + project_ids: [project.id], + sample_attributes_attributes: { + '0': { + pos: '1', title: 'a string', required: '1', is_title: '1', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, _destroy: '0', + isa_tag_id: FactoryBot.create(:other_material_isa_tag).id + }, + '1': { + pos: '2', title: 'protocol', required: '1', is_title: '0', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, + isa_tag_id: FactoryBot.create(:protocol_isa_tag).id, _destroy: '0' + }, + '2': { + pos: '3', title: 'Input sample', required: '1', + sample_attribute_type_id: FactoryBot.create(:sample_multi_sample_attribute_type).id, + linked_sample_type_id: study.sample_types.second.id, _destroy: '0' + }, + '3': { + pos: '4', title: 'Some material characteristic', required: '1', + sample_attribute_type_id: FactoryBot.create(:string_sample_attribute_type).id, + _destroy: '0', + isa_tag_id: FactoryBot.create(:other_material_characteristic_isa_tag).id + } + } + } + + intermediate_isa_assay_attributes3 = { assay: intermediate_assay_attributes3, + input_sample_type_id: assay_stream.id, + sample_type: intermediate_assay_sample_type_attributes3 } + + assert_no_difference "Assay.count" do + assert_no_difference "SampleType.count" do + post :create, params: { isa_assay: intermediate_isa_assay_attributes3 } + end + end + assert_response :not_found + end end From d8aec7d8b3ba820c2cfa34c3f63a54e40f1c91a0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 20:30:18 +0100 Subject: [PATCH 084/344] do not rearrange position if not isa json compliant or if assay stream --- app/controllers/isa_assays_controller.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 178ec8bd55..417ff0da4f 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -88,6 +88,9 @@ def fix_assay_linkage_for_new_assays end def rearrange_assay_positions_create_isa_assay + return if @isa_assay.assay.is_assay_stream? + return unless @isa_assay.assay.is_isa_json_compliant? + rearrange_assay_positions(@isa_assay.assay.assay_stream) end From 735896e745b3bb3ecba24a5d1e0d1fb075e1a283 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 30 Jan 2024 20:31:38 +0100 Subject: [PATCH 085/344] Fix tests --- app/forms/isa_assay.rb | 6 ++++++ app/models/assay.rb | 12 ++++++++++-- lib/seek/assets_common.rb | 17 ++++++++--------- test/functional/isa_assays_controller_test.rb | 8 ++++---- test/unit/assay_test.rb | 3 +++ 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/app/forms/isa_assay.rb b/app/forms/isa_assay.rb index a586c75851..7c6679d4b3 100644 --- a/app/forms/isa_assay.rb +++ b/app/forms/isa_assay.rb @@ -56,6 +56,12 @@ def populate(id) def validate_objects @assay.errors.each { |e| errors.add(:base, "[Assay]: #{e.full_message}") } unless @assay.valid? + if @assay.new_record? && @assay.next_linked_child_assay&.sample_type&.samples&.any? + next_assay_id = @assay.next_linked_child_assay.id + next_assay_title = @assay.next_linked_child_assay.title + errors.add(:base, "[Assay]: Not allowed to create an assay before assay '#{next_assay_id} - #{next_assay_title}'. It has samples linked to it.") + end + return if @assay.is_assay_stream? errors.add(:base, '[Assay]: The assay is missing a sample type.') if @sample_type.nil? diff --git a/app/models/assay.rb b/app/models/assay.rb index ce591f9534..1a6044e35d 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -96,14 +96,22 @@ def next_linked_child_assay return unless has_linked_child_assay? if is_assay_stream? - child_assays.first + first_assay_in_stream else sample_type.next_linked_sample_types.map(&:assays).flatten.detect { |a| a.assay_stream_id == assay_stream_id } end end + def first_assay_in_stream + if is_assay_stream? + child_assays.detect { |a| a.sample_type.previous_linked_sample_type == a.study.sample_types.second } + else + assay_stream.child_assays.detect { |a| a.sample_type.previous_linked_sample_type == a.study.sample_types.second } + end + end + def first_assay_in_stream? - self == assay_stream.child_assays.first + self == first_assay_in_stream end def default_contributor diff --git a/lib/seek/assets_common.rb b/lib/seek/assets_common.rb index 226ad2407f..5c36e2912b 100644 --- a/lib/seek/assets_common.rb +++ b/lib/seek/assets_common.rb @@ -78,16 +78,15 @@ def params_for_controller def rearrange_assay_positions(assay_stream) return unless assay_stream - disable_authorization_checks do - next_assay = assay_stream.next_linked_child_assay - assay_position = 0 + # updating the position should happen whether or not the user has the right permissions + next_assay = assay_stream.next_linked_child_assay + assay_position = 0 - # While there is a next assay, increment position by - while next_assay - next_assay.update(position: assay_position) - next_assay = next_assay.next_linked_child_assay - assay_position += 1 - end + # While there is a next assay, increment position by + while next_assay + Assay.find(next_assay.id).update_column(:position, assay_position) unless assay_position == next_assay.position + next_assay = next_assay.next_linked_child_assay + assay_position += 1 end end end diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index b554085818..7ad99d5b33 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -300,7 +300,7 @@ def setup linked_sample_type: study.sample_types.second, projects: [project], contributor: person) - end_assay = FactoryBot.create(:assay, contributor: person, study: , sample_type: end_assay_sample_type, assay_stream: ) + end_assay = FactoryBot.create(:assay, position: 0, contributor: person, study: , sample_type: end_assay_sample_type, assay_stream: ) refute end_assay.is_assay_stream? assert_equal end_assay.previous_linked_sample_type, assay_stream.previous_linked_sample_type, study.sample_types.second @@ -316,7 +316,7 @@ def setup assay_class_id: AssayClass.for_type(Seek:: ISA:: AssayClass::EXP).id, creator_ids: [person.id], policy_attributes: , - assay_stream_id: assay_stream.id} + assay_stream_id: assay_stream.id, position: 0} intermediate_assay_sample_type_attributes1 = { title: "Intermediate Assay Sample type 1", project_ids: [project.id], @@ -382,14 +382,14 @@ def setup linked_sample_type: study.sample_types.second, projects: [project], contributor: person) - begin_assay = FactoryBot.create(:assay, title: 'Begin Assay', contributor: person, study: , sample_type: begin_assay_sample_type, assay_stream: ) + begin_assay = FactoryBot.create(:assay, title: 'Begin Assay', position: 0, contributor: person, study: , sample_type: begin_assay_sample_type, assay_stream: ) ## Create an assay at the end of the stream end_assay_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: begin_assay_sample_type, projects: [project], contributor: person) - end_assay = FactoryBot.create(:assay, title: 'End Assay', contributor: person, study: , sample_type: end_assay_sample_type, assay_stream: ) + end_assay = FactoryBot.create(:assay, title: 'End Assay', position: 1, contributor: person, study: , sample_type: end_assay_sample_type, assay_stream: ) refute end_assay.is_assay_stream? assert_equal begin_assay.previous_linked_sample_type, assay_stream.previous_linked_sample_type, study.sample_types.second diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index 653d618db4..f16c9a2509 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -801,6 +801,7 @@ def new_valid_assay sample_type: data_file_sample_type) assert_equal second_isa_assay.previous_linked_sample_type, first_isa_assay.sample_type + assert_equal first_isa_assay.previous_linked_sample_type, isa_study.sample_types.second end test 'has_linked_child_assay?' do @@ -839,6 +840,8 @@ def new_valid_assay assay_stream: , sample_type: data_file_sample_type) + assert_equal assay_stream.first_assay_in_stream, first_isa_assay + assert first_isa_assay.first_assay_in_stream? assert_equal assay_stream.next_linked_child_assay, first_isa_assay assert_nil def_assay.next_linked_child_assay assert_equal first_isa_assay.next_linked_child_assay, second_isa_assay From 1ef73ff5c1a2355ca5bcbd39d601facbec3b66ca Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 15:56:48 +0100 Subject: [PATCH 086/344] test assay position after deletion --- test/functional/assays_controller_test.rb | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 6bde112931..a46455b36c 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -2129,4 +2129,34 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links end end + + test 'assay position after deletion' do + with_config_value(:isa_json_compliance_enabled, true) do + person = FactoryBot.create(:person) + project = person.projects.first + login_as(person) + investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: person) + study = FactoryBot.create(:isa_json_compliant_study, investigation: ) + assay_stream = FactoryBot.create(:assay_stream, study: , contributor: person, position: 0) + + begin_assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: study.sample_types.second, projects: [project], contributor: person) + begin_assay = FactoryBot.create(:assay, contributor: person, study: , assay_stream:, sample_type: begin_assay_sample_type, position: 0) + + middle_assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: begin_assay_sample_type, projects: [project], contributor: person) + middle_assay = FactoryBot.create(:assay, contributor: person, study: , assay_stream:, sample_type: middle_assay_sample_type, position: 1) + + end_assay_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: middle_assay_sample_type, projects: [project], contributor: person) + end_assay = FactoryBot.create(:assay, contributor: person, study: , assay_stream:, sample_type: end_assay_sample_type, position: 2) + + assert_difference('Assay.count', -1) do + assert_difference('SampleType.count', -1) do + delete :destroy, params: {id: middle_assay} + end + end + + end_assay.reload + refute_equal end_assay.position, 2 + assert_equal end_assay.position, 1 + end + end end From 755b5537963eafdd920677744ef149ce553c3e89 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Thu, 1 Feb 2024 16:13:46 +0100 Subject: [PATCH 087/344] seed examples for creating extended metadata type for SEEK resources --- .../extended_study_metadata_example.seeds.rb | 210 ++++++++++++++++++ .../family_example.seeds.rb | 48 ++++ 2 files changed, 258 insertions(+) create mode 100644 db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb create mode 100644 db/seeds/extended_metadata_drafts/family_example.seeds.rb diff --git a/db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb b/db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb new file mode 100644 index 0000000000..6ea0dd597c --- /dev/null +++ b/db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb @@ -0,0 +1,210 @@ +puts 'Seeded My Study Metadata Example' + + +string_type = SampleAttributeType.find_or_initialize_by(title: 'String') +string_type.update(base_type: Seek::Samples::BaseType::STRING) + +text_type = SampleAttributeType.find_or_initialize_by(title: 'Text') +text_type.update(base_type: Seek::Samples::BaseType::TEXT) + +date_type = SampleAttributeType.find_or_initialize_by(title: 'Date') +date_type.update(base_type: Seek::Samples::BaseType::DATE) + + +date_time_type = SampleAttributeType.find_or_initialize_by(title: 'Date time') +date_time_type.update(base_type: Seek::Samples::BaseType::DATE_TIME) + +int_type = SampleAttributeType.find_or_initialize_by(title: 'Integer') +int_type.update(base_type: Seek::Samples::BaseType::INTEGER, placeholder: '1') + +float_type = SampleAttributeType.find_or_initialize_by(title: 'Real number') +float_type.update(base_type: Seek::Samples::BaseType::FLOAT, placeholder: '0.5') + +boolean_type = SampleAttributeType.find_or_initialize_by(title: 'Boolean') +boolean_type.update(base_type: Seek::Samples::BaseType::BOOLEAN) + +cv_type = SampleAttributeType.find_or_initialize_by(title: 'Controlled Vocabulary') +cv_type.update(base_type: Seek::Samples::BaseType::CV) + +cv_type_list = SampleAttributeType.find_or_initialize_by(title: 'Controlled Vocabulary List') +cv_type_list.update(base_type: Seek::Samples::BaseType::CV_LIST) + +linked_extended_metadata_type = SampleAttributeType.find_or_initialize_by(title: 'Linked Extended Metadata') +linked_extended_metadata_type.update(base_type: Seek::Samples::BaseType::LINKED_EXTENDED_METADATA) + +linked_extended_metadata_type_list = SampleAttributeType.find_or_initialize_by(title: 'Linked Extended Metadata (multiple)') +linked_extended_metadata_type_list.update(base_type: Seek::Samples::BaseType::LINKED_EXTENDED_METADATA_MULTI) + + +def create_sample_controlled_vocab_terms_attributes(array) + attributes = [] + array.each do |type| + attributes << { label: type } + end + attributes +end + + +disable_authorization_checks do + + + resource_use_rights_label_cv = SampleControlledVocab.where(title: 'Study Use Rights Label').first_or_create!( + sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(['CC0 1.0 (Creative Commons Zero v1.0 Universal)', + 'CC BY 4.0 (Creative Commons Attribution 4.0 International)', + 'CC BY-NC 4.0 (Creative Commons Attribution Non Commercial 4.0 International)', + 'CC BY-SA 4.0 (Creative Commons Attribution Share Alike 4.0 International)', + 'CC BY-NC-SA 4.0 (Creative Commons Attribution Non Commercial Share Alike 4.0 International)', + 'All rights reserved', + 'Other', + 'Not applicable'])) + + resource_use_rights_label = ExtendedMetadataAttribute.new(title: 'resource_use_rights_label', required: true, sample_attribute_type: cv_type, sample_controlled_vocab: resource_use_rights_label_cv) + + resource_use_rights_description = ExtendedMetadataAttribute.new(title: 'resource_use_rights_description', required: false, sample_attribute_type: text_type) + + resource_use_rights_authors_confirmation = ExtendedMetadataAttribute.new(title: 'resource_use_rights_authors_confirmation', required: true, sample_attribute_type: boolean_type) + + + + + # Define role + role_type_cv = SampleControlledVocab.where(title: 'Role Type').first_or_create!( + sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(['Contact', 'Principal investigator', 'Creator/Author', 'Funder (public)', 'Funder (private)', + 'Sponsor (primary)', 'Sponsor (secondary)', 'Sponsor-Investigator', 'Data collector', 'Data curator', + 'Data manager', 'Distributor', 'Editor', 'Hosting institution', 'Producer', 'Project leader', 'Project manager', + 'Project member', 'Publisher', 'Registration agency', 'Registration authority', 'Related person', 'Researcher', + 'Research group', 'Rights holder', 'Supervisor', 'Work package leader', 'Other'])) + + + role_name_personal_title_cv = SampleControlledVocab.where(title: 'Role Name Personal Title').first_or_create!( + sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(['Mr.', 'Ms.', 'Dr.', 'Prof. Dr.', 'Other'])) + + role_name_identifier_scheme_cv = SampleControlledVocab.where(title: 'Role Name Identifier Scheme').first_or_create!( + sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(%w[ORCID ROR GRID ISNI])) + + + + unless ExtendedMetadataType.where(title:'role_name_identifiers', supported_type:'ExtendedMetadata').any? + emt = ExtendedMetadataType.new(title: 'role_name_identifiers', supported_type:'ExtendedMetadata') + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'scheme', required:true, + sample_attribute_type: cv_type, sample_controlled_vocab: role_name_identifier_scheme_cv, + description: "scheme") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'identifier', sample_attribute_type: SampleAttributeType.where(title:'String').first) + emt.save! + end + + unless ExtendedMetadataType.where(title:'role_emt', supported_type:'ExtendedMetadata').any? + emt = ExtendedMetadataType.new(title: 'role_emt', supported_type:'ExtendedMetadata') + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'role_name_personal_title', required:true, + sample_attribute_type: cv_type, sample_controlled_vocab: role_name_personal_title_cv, + description: "role_name_personal_title", label: "personal title") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'first_name', sample_attribute_type: SampleAttributeType.where(title:'String').first, label: "first name", description: "First name of the role") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'last_name', sample_attribute_type: SampleAttributeType.where(title:'String').first, label: "last name", description: "Last name of the role") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'role_type', required:true, + sample_attribute_type: cv_type, sample_controlled_vocab: role_type_cv, description: "role type", label: "Role type") + + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'role_name_identifiers', + sample_attribute_type: SampleAttributeType.where(title:'Linked Extended Metadata (multiple)').first, linked_extended_metadata_type: ExtendedMetadataType.where(title:'role_name_identifiers', supported_type:'ExtendedMetadata').first ) + + + emt.save! + end + + + # ************************ role related end ********************************* + + study_country_cv = SampleControlledVocab.where(title: 'European Study Country').first_or_create!( + sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(["Albania","Austria","Belgium","Bosnia and Herzegovina","Bulgaria","Croatia","Cyprus", + "Czech Republic","Denmark","Estonia","Finland","France","Germany","Greece","Hungary","Iceland","Ireland","Italy","Latvia","Lithuania","Luxembourg","Malta","Montenegro","Netherlands", + "North Macedonia","Norway","Poland","Portugal","Romania","Serbia","Slovakia","Slovenia","Spain","Sweden","Switzerland","United Kingdom",])) + + + + resource_type_general_cv = SampleControlledVocab.where(title: 'resource_type_general').first_or_create!( + sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(['Audiovisual', 'Book', 'Book chapter', 'Collection', 'Computational notebook', + 'Conference paper', 'Conference proceeding', 'Data paper', 'Dataset', + 'Dissertation', 'Event', 'Image', 'Interactive resource', 'Journal', 'Journal article', + 'Model', 'Output management plan', 'Peer review', 'Physical object', 'Preprint', + 'Report', 'Service', 'Software', 'Sound', 'Standard', 'Text', 'Workflow', 'Other']) + ) + + study_primary_design_cv = SampleControlledVocab.where(title: 'study_primary_design').first_or_create!( + sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(['Interventional','Non-Interventional']) + ) + + + + unless ExtendedMetadataType.where(title:'resource_use_rights_emt', supported_type:'ExtendedMetadata').any? + emt = ExtendedMetadataType.new(title: 'resource_use_rights_emt', supported_type:'ExtendedMetadata') + emt.extended_metadata_attributes << resource_use_rights_label + emt.extended_metadata_attributes << resource_use_rights_description + emt.extended_metadata_attributes << resource_use_rights_authors_confirmation + + emt.save! + end + + + unless ExtendedMetadataType.where(title:'study_conditions_emt', supported_type:'ExtendedMetadata').any? + emt = ExtendedMetadataType.new(title: 'study_conditions_emt', supported_type:'ExtendedMetadata') + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions', sample_attribute_type: SampleAttributeType.where(title:'String').first,description: "study_conditions", label: "study_conditions", required:true) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions_classification',sample_attribute_type: SampleAttributeType.where(title:'String').first,description: "study_conditions_classification", label: "study_conditions_classification") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions_classification_code',sample_attribute_type: SampleAttributeType.where(title:'String').first,description: "study_conditions_classification_code", label: "study_conditions_classification_code") + emt.save! + end + + unless ExtendedMetadataType.where(title:'study_design_emt', supported_type:'ExtendedMetadata').any? + emt = ExtendedMetadataType.new(title: 'study_design_emt', supported_type:'ExtendedMetadata') + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_primary_design', required:true, + sample_attribute_type: cv_type, sample_controlled_vocab: study_primary_design_cv) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions', + sample_attribute_type: SampleAttributeType.where(title:'Linked Extended Metadata (multiple)').first, linked_extended_metadata_type: ExtendedMetadataType.where(title:'study_conditions_emt', supported_type:'ExtendedMetadata').first ) + emt.save! + end + + + + unless ExtendedMetadataType.where(title:'My Study Metadata Example', supported_type:'Study').any? + + emt = ExtendedMetadataType.new(title: 'My Study Metadata Example', supported_type:'Study') + ################################################################### + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new( + title: 'title', # The attribute's identifier or name . + required: true, # Indicates whether this attribute is mandatory for the associated metadata. + sample_attribute_type: SampleAttributeType.where(title: 'String').first, # Specifies the attribute type, here set to 'String'. + description: 'the title of your study', # A brief description providing additional details about the attribute. By default, it is set to the empty string. + label: 'study title' # The label to be displayed in the user interface, conveying the purpose of the attribute. By default, it is set to the value of the 'title' attribute." + ) + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'description', required:true, sample_attribute_type: SampleAttributeType.where(title:'Text').first) + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_age', required:true, sample_attribute_type: SampleAttributeType.where(title:'Integer').first) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'cholesterol_level', required:true, sample_attribute_type: SampleAttributeType.where(title:'Real number').first) + + ################################################################### + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'resource_type_general', required:true, + sample_attribute_type: cv_type, sample_controlled_vocab: resource_type_general_cv) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_start_date', required:true, sample_attribute_type: date_type) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_start_time', required:true, sample_attribute_type: date_time_type) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_end_date', sample_attribute_type: date_type) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_end_time', sample_attribute_type: date_time_type) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_country', required:true, sample_attribute_type: cv_type_list, sample_controlled_vocab: study_country_cv) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'resource_use_rights', + sample_attribute_type: linked_extended_metadata_type, linked_extended_metadata_type: ExtendedMetadataType.where(title:'resource_use_rights_emt', supported_type:'ExtendedMetadata').first ) + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'resource_role', + sample_attribute_type: linked_extended_metadata_type_list, linked_extended_metadata_type: ExtendedMetadataType.where(title:'role_emt', supported_type:'ExtendedMetadata').first ) + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_design', + sample_attribute_type: linked_extended_metadata_type, linked_extended_metadata_type: ExtendedMetadataType.where(title:'study_design_emt', supported_type:'ExtendedMetadata').first ) + + emt.save! + puts 'My study metadata is created' + end + + + + + +end \ No newline at end of file diff --git a/db/seeds/extended_metadata_drafts/family_example.seeds.rb b/db/seeds/extended_metadata_drafts/family_example.seeds.rb new file mode 100644 index 0000000000..3ea2cc76b3 --- /dev/null +++ b/db/seeds/extended_metadata_drafts/family_example.seeds.rb @@ -0,0 +1,48 @@ +cv_type = SampleAttributeType.find_or_initialize_by(title: 'Controlled Vocabulary') +cv_type.update(base_type: Seek::Samples::BaseType::CV) + +cv_type_list = SampleAttributeType.find_or_initialize_by(title: 'Controlled Vocabulary List') +cv_type_list.update(base_type: Seek::Samples::BaseType::CV_LIST) + + +def create_sample_controlled_vocab_terms_attributes(array) + attributes = [] + array.each do |type| + attributes << { label: type } + end + attributes +end + + +disable_authorization_checks do + + # Define the inner extended metadata type 'person' with attributes 'first_name' and 'last_name'. + # The 'supported_type' is set to 'ExtendedMetadata' to denote it as the inner extended metadata type. + # + unless ExtendedMetadataType.where(title:'person', supported_type:'ExtendedMetadata').any? + emt = ExtendedMetadataType.new(title: 'person', supported_type:'ExtendedMetadata') + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'first_name', sample_attribute_type: SampleAttributeType.where(title:'String').first) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'last_name', sample_attribute_type: SampleAttributeType.where(title:'String').first) + emt.save! + end + + # Define the extended metadata type 'family', which contains the nested extended metadata attributes + unless ExtendedMetadataType.where(title:'family', supported_type:'Investigation').any? + emt = ExtendedMetadataType.new(title: 'family', supported_type:'Investigation') + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'dad', + sample_attribute_type: SampleAttributeType.where(title:'Linked Extended Metadata').first, linked_extended_metadata_type: ExtendedMetadataType.where(title:'person', supported_type:'ExtendedMetadata').first ) + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'mom', + sample_attribute_type: SampleAttributeType.where(title:'Linked Extended Metadata').first, linked_extended_metadata_type: ExtendedMetadataType.where(title:'person', supported_type:'ExtendedMetadata').first ) + + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'child', + sample_attribute_type: SampleAttributeType.where(title:'Linked Extended Metadata (multiple)').first, linked_extended_metadata_type: ExtendedMetadataType.where(title:'person', supported_type:'ExtendedMetadata').first ) + + + emt.save! + puts 'Family metadata' + end + +end + From 279695e3c45d18f4ef991f3e63c64cf910d59372 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Thu, 1 Feb 2024 16:41:26 +0100 Subject: [PATCH 088/344] clean up seed file --- .../extended_study_metadata_example.seeds.rb | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb b/db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb index 6ea0dd597c..a62477a134 100644 --- a/db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb +++ b/db/seeds/extended_metadata_drafts/extended_study_metadata_example.seeds.rb @@ -1,6 +1,5 @@ puts 'Seeded My Study Metadata Example' - string_type = SampleAttributeType.find_or_initialize_by(title: 'String') string_type.update(base_type: Seek::Samples::BaseType::STRING) @@ -90,7 +89,7 @@ def create_sample_controlled_vocab_terms_attributes(array) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'scheme', required:true, sample_attribute_type: cv_type, sample_controlled_vocab: role_name_identifier_scheme_cv, description: "scheme") - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'identifier', sample_attribute_type: SampleAttributeType.where(title:'String').first) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'identifier', sample_attribute_type: string_type ) emt.save! end @@ -99,22 +98,20 @@ def create_sample_controlled_vocab_terms_attributes(array) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'role_name_personal_title', required:true, sample_attribute_type: cv_type, sample_controlled_vocab: role_name_personal_title_cv, description: "role_name_personal_title", label: "personal title") - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'first_name', sample_attribute_type: SampleAttributeType.where(title:'String').first, label: "first name", description: "First name of the role") - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'last_name', sample_attribute_type: SampleAttributeType.where(title:'String').first, label: "last name", description: "Last name of the role") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'first_name', sample_attribute_type: string_type, label: "first name", description: "First name of the role") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'last_name', sample_attribute_type: string_type, label: "last name", description: "Last name of the role") emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'role_type', required:true, sample_attribute_type: cv_type, sample_controlled_vocab: role_type_cv, description: "role type", label: "Role type") emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'role_name_identifiers', - sample_attribute_type: SampleAttributeType.where(title:'Linked Extended Metadata (multiple)').first, linked_extended_metadata_type: ExtendedMetadataType.where(title:'role_name_identifiers', supported_type:'ExtendedMetadata').first ) + sample_attribute_type: linked_extended_metadata_type_list, linked_extended_metadata_type: ExtendedMetadataType.where(title:'role_name_identifiers', supported_type:'ExtendedMetadata').first ) emt.save! end - # ************************ role related end ********************************* - study_country_cv = SampleControlledVocab.where(title: 'European Study Country').first_or_create!( sample_controlled_vocab_terms_attributes: create_sample_controlled_vocab_terms_attributes(["Albania","Austria","Belgium","Bosnia and Herzegovina","Bulgaria","Croatia","Cyprus", "Czech Republic","Denmark","Estonia","Finland","France","Germany","Greece","Hungary","Iceland","Ireland","Italy","Latvia","Lithuania","Luxembourg","Malta","Montenegro","Netherlands", @@ -148,9 +145,9 @@ def create_sample_controlled_vocab_terms_attributes(array) unless ExtendedMetadataType.where(title:'study_conditions_emt', supported_type:'ExtendedMetadata').any? emt = ExtendedMetadataType.new(title: 'study_conditions_emt', supported_type:'ExtendedMetadata') - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions', sample_attribute_type: SampleAttributeType.where(title:'String').first,description: "study_conditions", label: "study_conditions", required:true) - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions_classification',sample_attribute_type: SampleAttributeType.where(title:'String').first,description: "study_conditions_classification", label: "study_conditions_classification") - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions_classification_code',sample_attribute_type: SampleAttributeType.where(title:'String').first,description: "study_conditions_classification_code", label: "study_conditions_classification_code") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions', sample_attribute_type: string_type, description: "study_conditions", label: "study_conditions", required:true) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions_classification',sample_attribute_type: string_type, description: "study_conditions_classification", label: "study_conditions_classification") + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions_classification_code',sample_attribute_type: string_type, description: "study_conditions_classification_code", label: "study_conditions_classification_code") emt.save! end @@ -159,7 +156,7 @@ def create_sample_controlled_vocab_terms_attributes(array) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_primary_design', required:true, sample_attribute_type: cv_type, sample_controlled_vocab: study_primary_design_cv) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_conditions', - sample_attribute_type: SampleAttributeType.where(title:'Linked Extended Metadata (multiple)').first, linked_extended_metadata_type: ExtendedMetadataType.where(title:'study_conditions_emt', supported_type:'ExtendedMetadata').first ) + sample_attribute_type:linked_extended_metadata_type_list, linked_extended_metadata_type: ExtendedMetadataType.where(title:'study_conditions_emt', supported_type:'ExtendedMetadata').first ) emt.save! end @@ -168,21 +165,19 @@ def create_sample_controlled_vocab_terms_attributes(array) unless ExtendedMetadataType.where(title:'My Study Metadata Example', supported_type:'Study').any? emt = ExtendedMetadataType.new(title: 'My Study Metadata Example', supported_type:'Study') - ################################################################### + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new( title: 'title', # The attribute's identifier or name . required: true, # Indicates whether this attribute is mandatory for the associated metadata. - sample_attribute_type: SampleAttributeType.where(title: 'String').first, # Specifies the attribute type, here set to 'String'. + sample_attribute_type: string_type, # Specifies the attribute type, here set to 'String'. description: 'the title of your study', # A brief description providing additional details about the attribute. By default, it is set to the empty string. label: 'study title' # The label to be displayed in the user interface, conveying the purpose of the attribute. By default, it is set to the value of the 'title' attribute." ) - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'description', required:true, sample_attribute_type: SampleAttributeType.where(title:'Text').first) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'description', required:true, sample_attribute_type: text_type ) - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_age', required:true, sample_attribute_type: SampleAttributeType.where(title:'Integer').first) - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'cholesterol_level', required:true, sample_attribute_type: SampleAttributeType.where(title:'Real number').first) - - ################################################################### + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_age', required:true, sample_attribute_type: int_type) + emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'cholesterol_level', required:true, sample_attribute_type: float_type) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'resource_type_general', required:true, sample_attribute_type: cv_type, sample_controlled_vocab: resource_type_general_cv) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_start_date', required:true, sample_attribute_type: date_type) @@ -192,19 +187,12 @@ def create_sample_controlled_vocab_terms_attributes(array) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_country', required:true, sample_attribute_type: cv_type_list, sample_controlled_vocab: study_country_cv) emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'resource_use_rights', sample_attribute_type: linked_extended_metadata_type, linked_extended_metadata_type: ExtendedMetadataType.where(title:'resource_use_rights_emt', supported_type:'ExtendedMetadata').first ) - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'resource_role', sample_attribute_type: linked_extended_metadata_type_list, linked_extended_metadata_type: ExtendedMetadataType.where(title:'role_emt', supported_type:'ExtendedMetadata').first ) - emt.extended_metadata_attributes << ExtendedMetadataAttribute.new(title: 'study_design', sample_attribute_type: linked_extended_metadata_type, linked_extended_metadata_type: ExtendedMetadataType.where(title:'study_design_emt', supported_type:'ExtendedMetadata').first ) emt.save! puts 'My study metadata is created' end - - - - - end \ No newline at end of file From e255e68021dfd3793865d3cbf14549262d0bc4ac Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 17:01:37 +0100 Subject: [PATCH 089/344] Order assay streams by position --- app/views/isa_assays/_form.html.erb | 18 +++++++++++++----- lib/treeview_builder.rb | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index cc707ccf20..a6e55c458d 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -6,7 +6,7 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] - assay_position = 0 + assay_position = study.assay_streams.map(&:position).max + 1 assay_class_id = AssayClass.assay_stream.id is_assay_stream = true else @@ -48,7 +48,7 @@
    <%= assay_fields.label :description -%>
    - <%= assay_fields.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%> + <%= assay_fields.text_area :description, rows: 5, class: "form-control rich-text-edit" -%>
    <% if show_extended_metadata %> @@ -61,9 +61,17 @@ <%= assay_study_selection('isa_assay[assay][study_id]',@isa_assay.assay.study) %> - + <% if is_assay_stream %> +
    + <%= assay_fields.label "Assay position" -%>
    + <%= assay_fields.number_field :position, rows: 5, class: "form-control", value: assay_position %> +
    + <% else %> + + <% end %> + <%= assay_fields.hidden_field :assay_stream_id, value: assay_stream_id -%> <%= assay_fields.hidden_field :assay_class_id, value: assay_class_id -%> diff --git a/lib/treeview_builder.rb b/lib/treeview_builder.rb index b810759ebb..abb3211bce 100644 --- a/lib/treeview_builder.rb +++ b/lib/treeview_builder.rb @@ -16,7 +16,7 @@ def build_tree_data @project.investigations.map do |investigation| if investigation.is_isa_json_compliant? investigation.studies.map do |study| - assay_stream_items = study.assay_streams.map do |assay_stream| + assay_stream_items = study.assay_streams.sort_by{ |stream| stream.position }.map do |assay_stream| assay_items = assay_stream.child_assays.order(:position).map do |child_assay| build_assay_item(child_assay) end From d9c8af523e3604208adf806067af3a430738e81c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 17:06:17 +0100 Subject: [PATCH 090/344] Test positions after creating intermediate assays --- lib/seek/assets_common.rb | 3 +++ test/functional/isa_assays_controller_test.rb | 22 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/seek/assets_common.rb b/lib/seek/assets_common.rb index 5c36e2912b..0a53cec0ce 100644 --- a/lib/seek/assets_common.rb +++ b/lib/seek/assets_common.rb @@ -78,6 +78,9 @@ def params_for_controller def rearrange_assay_positions(assay_stream) return unless assay_stream + # Reload the assay stream to also include newly created assays + assay_stream.reload + # updating the position should happen whether or not the user has the right permissions next_assay = assay_stream.next_linked_child_assay assay_position = 0 diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 7ad99d5b33..bc7edf4d79 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -313,7 +313,7 @@ def setup intermediate_assay_attributes1 = { title: 'First intermediate assay', study_id: study.id, - assay_class_id: AssayClass.for_type(Seek:: ISA:: AssayClass::EXP).id, + assay_class_id: AssayClass.experimental.id, creator_ids: [person.id], policy_attributes: , assay_stream_id: assay_stream.id, position: 0} @@ -361,7 +361,13 @@ def setup assert_equal isa_assay.assay.sample_type.previous_linked_sample_type, study.sample_types.second assert_equal isa_assay.assay.next_linked_child_assay, end_assay - # TODO: Test the assay positions after reorganising + # Test the assay positions after reorganising + end_assay.reload + refute_equal end_assay.position, 0 + assert_equal end_assay.position, 1 + + isa_assay.assay.reload + assert_equal isa_assay.assay.position, 0 end test 'insert assay between two experimental assays' do @@ -402,7 +408,7 @@ def setup intermediate_assay_attributes2 = { title: 'Second intermediate assay', study_id: study.id, - assay_class_id: AssayClass.for_type(Seek:: ISA:: AssayClass::EXP).id, + assay_class_id: AssayClass.experimental.id, creator_ids: [person.id], policy_attributes: , assay_stream_id: assay_stream.id} @@ -452,7 +458,13 @@ def setup assert_equal isa_assay.assay.sample_type.previous_linked_sample_type, begin_assay.sample_type assert_equal isa_assay.assay.next_linked_child_assay, end_assay - # TODO: Test the assay positions after reorganising + # Test the assay positions after reorganising + end_assay.reload + refute_equal end_assay.position, 1 + assert_equal end_assay.position, 2 + + isa_assay.assay.reload + assert_equal isa_assay.assay.position, 1 end test 'should not insert assay if next assay has samples' do @@ -542,7 +554,7 @@ def setup intermediate_assay_attributes3 = { title: 'Third intermediate assay', study_id: study.id, - assay_class_id: AssayClass.for_type(Seek:: ISA:: AssayClass::EXP).id, + assay_class_id: AssayClass.experimental.id, creator_ids: [person.id], policy_attributes: , assay_stream_id: assay_stream.id} From eb5ef8f9f51f7025b0734aa1a0622baba2e1195d Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 31 Jan 2024 11:48:20 +0000 Subject: [PATCH 091/344] fix for handling missing filename in biomodels result #1729 also added some more general resilience --- .../_biomodels_resource_list_item.html.erb | 2 +- .../search_biomodels_adaptor.rb | 15 +- test/unit/search_biomodels_adaptor_test.rb | 9 + test/vcr_cassettes/biomodels/search-2024.yml | 1608 +++++++++++++++++ 4 files changed, 1628 insertions(+), 6 deletions(-) create mode 100644 test/vcr_cassettes/biomodels/search-2024.yml diff --git a/app/views/search/partials/_biomodels_resource_list_item.html.erb b/app/views/search/partials/_biomodels_resource_list_item.html.erb index 10fd2a13f0..eac9264930 100644 --- a/app/views/search/partials/_biomodels_resource_list_item.html.erb +++ b/app/views/search/partials/_biomodels_resource_list_item.html.erb @@ -25,7 +25,7 @@ <% end %> - <% unless resource.unreleased %> + <% unless resource.unreleased || resource.main_filename.blank? %>
    <% biomodel_download_link = resource.download_link diff --git a/lib/seek/biomodels_search/search_biomodels_adaptor.rb b/lib/seek/biomodels_search/search_biomodels_adaptor.rb index d0553df4c0..e3ebe002a9 100644 --- a/lib/seek/biomodels_search/search_biomodels_adaptor.rb +++ b/lib/seek/biomodels_search/search_biomodels_adaptor.rb @@ -12,7 +12,11 @@ def perform_search(query) json = JSON.parse(json) json['models'].collect do |result| - BiomodelsSearchResult.new result['id'] + begin + BiomodelsSearchResult.new result['id'] + rescue NoMethodError=>e + Seek::Errors::ExceptionForwarder.send_notification(exception, data: { error: 'error reading response from BioModels', item_id: result['id'], query: query }) + end end.compact.reject do |biomodels_result| biomodels_result.title.blank? end @@ -62,14 +66,15 @@ def populate self.publication_title = json.dig('publication', 'title') self.authors = (json.dig('publication', 'authors') || []).collect { |author| author['name'] } self.published_date = Time.at(json['firstPublished'] / 1000) - latest_version = json['history']['revisions'].sort { |rev| rev['version'] }.first - self.last_modification_date = Time.at(latest_version['submitted'] / 1000) - self.main_filename = json['files']['main'].first['name'] + latest_version = (json.dig('history','revisions') || []).sort { |rev| rev['version'] }&.first + if latest_version&.fetch('submitted') + self.last_modification_date = Time.at(latest_version['submitted'] / 1000) + end + self.main_filename = (json.dig('files','main') || []).first&.fetch('name') self.unreleased = false else self.unreleased = true end - end end end diff --git a/test/unit/search_biomodels_adaptor_test.rb b/test/unit/search_biomodels_adaptor_test.rb index d617eb9532..5907a7829c 100644 --- a/test/unit/search_biomodels_adaptor_test.rb +++ b/test/unit/search_biomodels_adaptor_test.rb @@ -52,6 +52,7 @@ class SearchBiomodelsAdaptorTest < ActiveSupport::TestCase assert_equal DateTime.parse('2012-12-14 14:24:40 +0000'), result.last_modification_date assert_equal 'search/partials/test_partial', result.partial_path assert_equal 'EBI Biomodels', result.tab + assert_equal 'BIOMD0000000429_url.xml', result.main_filename end end @@ -64,5 +65,13 @@ class SearchBiomodelsAdaptorTest < ActiveSupport::TestCase end end + test 'search does not files' do + VCR.use_cassette('biomodels/search-2024') do + adaptor = Seek::BiomodelsSearch::SearchBiomodelsAdaptor.new('partial_path' => 'search/partials/test_partial') + results = adaptor.search('2024') + assert_equal 25, results.count + end + end + end diff --git a/test/vcr_cassettes/biomodels/search-2024.yml b/test/vcr_cassettes/biomodels/search-2024.yml new file mode 100644 index 0000000000..caa64da168 --- /dev/null +++ b/test/vcr_cassettes/biomodels/search-2024.yml @@ -0,0 +1,1608 @@ +--- +http_interactions: +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/search?numResults=25&query=2024 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=UTF-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=f0ca479f-f910-47bd-86f5-6648fd781221; Path=/biomodels/; HttpOnly + - biomodels-session=1706699240.904.16430.861308; Expires=Wed, 31-Jan-24 12:07:19 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + eyJmYWNldFN0YXRzIjoiW3tcImZhY2V0VmFsdWVzXCI6W3tcInZhbHVlXCI6XCJOb24tY3VyYXRlZFwiLFwibGFiZWxcIjpcIk5vbi1jdXJhdGVkXCIsXCJjb3VudFwiOlwiMjRcIn0se1widmFsdWVcIjpcIk1hbnVhbGx5IGN1cmF0ZWRcIixcImxhYmVsXCI6XCJNYW51YWxseSBjdXJhdGVkXCIsXCJjb3VudFwiOlwiNFwifV0sXCJpZFwiOlwiY3VyYXRpb25zdGF0dXNcIixcInRvdGFsXCI6MixcImxhYmVsXCI6XCJDdXJhdGlvbiBzdGF0dXNcIn0se1wiZmFjZXRWYWx1ZXNcIjpbe1widmFsdWVcIjpcIlNCTUxcIixcImxhYmVsXCI6XCJTQk1MXCIsXCJjb3VudFwiOlwiMjBcIn0se1widmFsdWVcIjpcIkNPTUJJTkUgYXJjaGl2ZVwiLFwibGFiZWxcIjpcIkNPTUJJTkUgYXJjaGl2ZVwiLFwiY291bnRcIjpcIjdcIn0se1widmFsdWVcIjpcIk90aGVyXCIsXCJsYWJlbFwiOlwiT3RoZXJcIixcImNvdW50XCI6XCIxXCJ9XSxcImlkXCI6XCJtb2RlbGZvcm1hdFwiLFwidG90YWxcIjozLFwibGFiZWxcIjpcIk1vZGVsIGZvcm1hdFwifSx7XCJmYWNldFZhbHVlc1wiOlt7XCJ2YWx1ZVwiOlwiY29uc3RyYWludC1iYXNlZCBtb2RlbFwiLFwibGFiZWxcIjpcIkNvbnN0cmFpbnQtYmFzZWQgbW9kZWxcIixcImNvdW50XCI6XCIxNFwifSx7XCJ2YWx1ZVwiOlwib3JkaW5hcnkgZGlmZmVyZW50aWFsIGVxdWF0aW9uIG1vZGVsXCIsXCJsYWJlbFwiOlwiT3JkaW5hcnkgZGlmZmVyZW50aWFsIGVxdWF0aW9uIG1vZGVsXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiZmluaXRlIGVsZW1lbnQgc3BhdGlhbCBtb2RlbFwiLFwibGFiZWxcIjpcIkZpbml0ZSBlbGVtZW50IHNwYXRpYWwgbW9kZWxcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJsb2dpY2FsIG1vZGVsXCIsXCJsYWJlbFwiOlwiTG9naWNhbCBtb2RlbFwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIm1hdGhlbWF0aWNhbCBtb2RlbFwiLFwibGFiZWxcIjpcIk1hdGhlbWF0aWNhbCBtb2RlbFwiLFwiY291bnRcIjpcIjFcIn1dLFwiaWRcIjpcIm1vZGVsbGluZ2FwcHJvYWNoXCIsXCJ0b3RhbFwiOjUsXCJsYWJlbFwiOlwiTW9kZWxsaW5nIEFwcHJvYWNoXCJ9LHtcImZhY2V0VmFsdWVzXCI6W3tcInZhbHVlXCI6XCI5NjA2XCIsXCJsYWJlbFwiOlwiSG9tbyBzYXBpZW5zXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiMTQ1MjYyXCIsXCJsYWJlbFwiOlwiTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXNcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCIxODc0MjBcIixcImxhYmVsXCI6XCJNZXRoYW5vdGhlcm1vYmFjdGVyIHRoZXJtYXV0b3Ryb3BoaWN1cyBzdHIuIERlbHRhIEhcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCI3OTkyOVwiLFwibGFiZWxcIjpcIk1ldGhhbm90aGVybW9iYWN0ZXIgbWFyYnVyZ2Vuc2lzIHN0ci4gTWFyYnVyZ1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIjEwMTE2XCIsXCJsYWJlbFwiOlwiUmF0dHVzIG5vcnZlZ2ljdXNcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCIxMDA5MFwiLFwibGFiZWxcIjpcIk11cyBtdXNjdWx1c1wiLFwiY291bnRcIjpcIjFcIn1dLFwiaWRcIjpcIlRBWE9OT01ZXCIsXCJ0b3RhbFwiOjYsXCJsYWJlbFwiOlwiT3JnYW5pc21zXCJ9LHtcImZhY2V0VmFsdWVzXCI6W3tcInZhbHVlXCI6XCJHTzowMDA3MDQ5XCIsXCJsYWJlbFwiOlwiY2VsbCBjeWNsZVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkdPOjAwNTEzMDFcIixcImxhYmVsXCI6XCJjZWxsIGRpdmlzaW9uXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiR086MDAwNTY4MFwiLFwibGFiZWxcIjpcImFuYXBoYXNlLXByb21vdGluZyBjb21wbGV4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiR086MTkwMzMyMVwiLFwibGFiZWxcIjpcIm5lZ2F0aXZlIHJlZ3VsYXRpb24gb2YgcHJvdGVpbiBtb2RpZmljYXRpb24gYnkgc21hbGwgcHJvdGVpbiBjb25qdWdhdGlvbiBvciByZW1vdmFsXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiR086MDAzNjIxMVwiLFwibGFiZWxcIjpcInByb3RlaW4gbW9kaWZpY2F0aW9uIHByb2Nlc3NcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJHTzowMDQ2NDI3XCIsXCJsYWJlbFwiOlwicG9zaXRpdmUgcmVndWxhdGlvbiBvZiByZWNlcHRvciBzaWduYWxpbmcgcGF0aHdheSB2aWEgSkFLLVNUQVRcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJHTzowMDE2NTU2XCIsXCJsYWJlbFwiOlwibVJOQSBtb2RpZmljYXRpb25cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJHTzowMDk3MTI1XCIsXCJsYWJlbFwiOlwiY3ljbGluIEIxLUNESzEgY29tcGxleFwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkdPOjAwNzA4NDhcIixcImxhYmVsXCI6XCJyZXNwb25zZSB0byBncm93dGggZmFjdG9yXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiR086MDAzODE2MlwiLFwibGFiZWxcIjpcImVyeXRocm9wb2lldGluLW1lZGlhdGVkIHNpZ25hbGluZyBwYXRod2F5XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiR086MDAzMDA3M1wiLFwibGFiZWxcIjpcImluc3VsaW4gc2VjcmV0aW9uXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiR086MDAxNjMxMVwiLFwibGFiZWxcIjpcImRlcGhvc3Bob3J5bGF0aW9uXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiR086MDAxNjMxMFwiLFwibGFiZWxcIjpcInBob3NwaG9yeWxhdGlvblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkdPOjAwMTA0NjdcIixcImxhYmVsXCI6XCJnZW5lIGV4cHJlc3Npb25cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJHTzowMDA1NjM0XCIsXCJsYWJlbFwiOlwibnVjbGV1c1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkdPOjAwMDY0MTJcIixcImxhYmVsXCI6XCJ0cmFuc2xhdGlvblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkdPOjAwMDU4MjlcIixcImxhYmVsXCI6XCJjeXRvc29sXCIsXCJjb3VudFwiOlwiMVwifV0sXCJpZFwiOlwiR09cIixcInRvdGFsXCI6MTcsXCJsYWJlbFwiOlwiR09cIn0se1wiZmFjZXRWYWx1ZXNcIjpbe1widmFsdWVcIjpcIkEwQTUxNkdITTlcIixcImxhYmVsXCI6XCJNZXRhbCBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHR0s4XCIsXCJsYWJlbFwiOlwiQVRQLWJpbmRpbmcgY2Fzc2V0dGUgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKMDdcIixcImxhYmVsXCI6XCJVRFAtTi1hY2V0eWxtdXJhbWF0ZS0tTC1hbGFuaW5lIGxpZ2FzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEs3VzlcIixcImxhYmVsXCI6XCJHVFAgZGlwaG9zcGhva2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tTN1wiLFwibGFiZWxcIjpcIlJpYm9udWNsZW9zaWRlLWRpcGhvc3BoYXRlIHJlZHVjdGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHSzRcIixcImxhYmVsXCI6XCJHbHV0YW1hdGUtLXRSTkEgbGlnYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hNNVwiLFwibGFiZWxcIjpcIkNhdGlvbi10cmFuc2xvY2F0aW5nIFAtdHlwZSBBVFBhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUDM0XCIsXCJsYWJlbFwiOlwiVXJhY2lsIHBlcm1lYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S0xMMVwiLFwibGFiZWxcIjpcIkd1YW55bGF0ZSBraW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSkEzXCIsXCJsYWJlbFwiOlwiTW5oQiBkb21haW4tY29udGFpbmluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tDMVwiLFwibGFiZWxcIjpcIkFjZXR5bG9ybml0aGluZSBkZWFjZXR5bGFzZTMuNS4xLjE2XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0xFMFwiLFwibGFiZWxcIjpcIkRpaHlkcm94eWFjZXRvbmUga2luYXNlIHN1YnVuaXQgTFwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdMRTFcIixcImxhYmVsXCI6XCJBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtEUDNcIixcImxhYmVsXCI6XCJQcm9iYWJsZSBtYW5nYW5lc2UtZGVwZW5kZW50IGlub3JnYW5pYyBweXJvcGhvc3BoYXRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLMFkwXCIsXCJsYWJlbFwiOlwiTkFEIGtpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtVQTRcIixcImxhYmVsXCI6XCJPc21vcHJvdGVjdGFudCBBQkMgdHJhbnNwb3J0ZXIgc3Vic3RyYXRlLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdMRDhcIixcImxhYmVsXCI6XCJJcm9uLWNvbnRhaW5pbmcgYWxjb2hvbCBkZWh5ZHJvZ2VuYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1BYNlwiLFwibGFiZWxcIjpcIkFUUC1kZXBlbmRlbnQgNi1waG9zcGhvZnJ1Y3Rva2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0pRM1wiLFwibGFiZWxcIjpcIkFsY29ob2wgZGVoeWRyb2dlbmFzZSBBZGhQMS4xLjEuMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtVMDFcIixcImxhYmVsXCI6XCJJb24tdHJhbnNsb2NhdGluZyBveGlkb3JlZHVjdGFzZSBjb21wbGV4IHN1YnVuaXQgQ1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJTjlcIixcImxhYmVsXCI6XCJIeWRyb3h5YWN5bGdsdXRhdGhpb25lIGh5ZHJvbGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtQUzlcIixcImxhYmVsXCI6XCJQVFMgc3lzdGVtIG1hbm5vc2Utc3BlY2lmaWMgRUlJQUIgY29tcG9uZW50XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0VBM1wiLFwibGFiZWxcIjpcIkJpZnVuY3Rpb25hbCBwcm90ZWluIEZvbERcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUDIyXCIsXCJsYWJlbFwiOlwiU29kaXVtL3NvbHV0ZSBzeW1wb3J0ZXJTb2RpdW06c29sdXRlIHN5bXBvcnRlclwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHSjVcIixcImxhYmVsXCI6XCJkVVRQIGRpcGhvc3BoYXRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSEw1XCIsXCJsYWJlbFwiOlwiT3JuaXRoaW5lIGNhcmJhbW95bHRyYW5zZmVyYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0sxOFwiLFwibGFiZWxcIjpcIk5BRFAtZGVwZW5kZW50IG1hbGljIGVuenltZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtRNDVcIixcImxhYmVsXCI6XCJNYWduZXNpdW0gdHJhbnNwb3J0ZXIgTWd0RVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKUDZcIixcImxhYmVsXCI6XCJSaWJva2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tCMlwiLFwibGFiZWxcIjpcIk9yb3RhdGUgcGhvc3Bob3JpYm9zeWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtQMjBcIixcImxhYmVsXCI6XCJOLWFjZXR5bG5ldXJhbWluYXRlIGx5YXNlNC4xLjMuM1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtNQTRcIixcImxhYmVsXCI6XCJDeXN0ZWluZSBoeWRyb2xhc2VJc29jaG9yaXNtYXRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTEQxXCIsXCJsYWJlbFwiOlwiUHlydXZhdGUgY2FyYm94eWxhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTEQwXCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1dEM1wiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZUFtaW5vIGFjaWQgQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S04yMFwiLFwibGFiZWxcIjpcIk1ldGh5bGVuZXRldHJhaHlkcm9mb2xhdGUgcmVkdWN0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tSNVwiLFwibGFiZWxcIjpcIlJlc3BvbnNlIHJlZ3VsYXRvclwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJTjBcIixcImxhYmVsXCI6XCJJc29wcmVueWwgdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLR0QxXCIsXCJsYWJlbFwiOlwiUGFudG90aGVuYXRlIGtpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJTTdcIixcImxhYmVsXCI6XCJTb2RpdW06YWxhbmluZSBzeW1wb3J0ZXIgZmFtaWx5IHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUVQ4XCIsXCJsYWJlbFwiOlwiQUFBIGZhbWlseSBBVFBhc2VEZW94eW51Y2xlb3NpZGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0dENVwiLFwibGFiZWxcIjpcIlRoaW9yZWRveGluIHJlZHVjdGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtRVDNcIixcImxhYmVsXCI6XCJTSVMgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpblRhZ2F0b3NlLTYtcGhvc3BoYXRlIGtldG9zZSBpc29tZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUFI0XCIsXCJsYWJlbFwiOlwiR1RQIGN5Y2xvaHlkcm9sYXNlIDFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzA0XCIsXCJsYWJlbFwiOlwiVGFnYXRvc2UtNi1waG9zcGhhdGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1BGMVwiLFwibGFiZWxcIjpcIkNhcmJvaHlkcmF0ZSBBQkMgdHJhbnNwb3J0ZXIgc3Vic3RyYXRlLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEszTTZcIixcImxhYmVsXCI6XCJNZXRhbCBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUjUxXCIsXCJsYWJlbFwiOlwiVHJpb3NlcGhvc3BoYXRlIGlzb21lcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtJWDRcIixcImxhYmVsXCI6XCJQaG9zcGhvZ2x1Y29zYW1pbmUgbXV0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dZN1wiLFwibGFiZWxcIjpcIlRoaW9sYXNlIGZhbWlseSBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S1FEOVwiLFwibGFiZWxcIjpcIjMtaHlkcm94eWFjeWwtW2FjeWwtY2Fycmllci1wcm90ZWluXSBkZWh5ZHJhdGFzZSBGYWJaXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dJMFwiLFwibGFiZWxcIjpcIkZ1bWFyYXRlIGh5ZHJhdGFzZSBjbGFzcyBJSVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLMDJcIixcImxhYmVsXCI6XCJJcm9uIEFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdISzBcIixcImxhYmVsXCI6XCJDaHJvbWF0ZSB0cmFuc3BvcnRlclwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtRNzVcIixcImxhYmVsXCI6XCJMYWN0b3lsZ2x1dGF0aGlvbmUgbHlhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTFMwXCIsXCJsYWJlbFwiOlwiSEQgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEsxWTlcIixcImxhYmVsXCI6XCJQeXJ1dmF0ZSBmb3JtYXRlLWx5YXNlLWFjdGl2YXRpbmcgZW56eW1lXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S1AxOFwiLFwibGFiZWxcIjpcIlRoaW9sIHJlZHVjdGFzZSB0aGlvcmVkb3hpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJTDhcIixcImxhYmVsXCI6XCJBbGRlaHlkZSBkZWh5ZHJvZ2VuYXNlIGZhbWlseSBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S0xJMlwiLFwibGFiZWxcIjpcIlBob3NwaG9nbHljZXJhdGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4Szg1NlwiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVN1Z2FyIEFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKTjNcIixcImxhYmVsXCI6XCJDYXRpb246ZGljYXJib3h5bGFzZSBzeW1wb3J0ZXIgZmFtaWx5IHRyYW5zcG9ydGVyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dIMFwiLFwibGFiZWxcIjpcIlJpYm9zb21lLWJpbmRpbmcgQVRQYXNlIFljaEZcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLVlE3XCIsXCJsYWJlbFwiOlwiR2FsYWN0b3NlLTEtcGhvc3BoYXRlIHVyaWR5bHlsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTEIyXCIsXCJsYWJlbFwiOlwiQWNldHlsLUNvQSBjYXJib3h5dHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSFo4XCIsXCJsYWJlbFwiOlwiUHJvYmFibGUgRC1zZXJpbmUgZGVoeWRyYXRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHR1g1XCIsXCJsYWJlbFwiOlwiRW5lcmd5LWNvdXBsaW5nIGZhY3RvciB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluIEVjZkEyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0g5NVwiLFwibGFiZWxcIjpcIkJpb3RpbiBjYXJib3h5bGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdMQTlcIixcImxhYmVsXCI6XCJBbmFlcm9iaWMgcmlib251Y2xlb3NpZGUtdHJpcGhvc3BoYXRlIHJlZHVjdGFzZTEuMTcuNC4yXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0haNVwiLFwibGFiZWxcIjpcIk51Y2xlb3RpZGUgcHlyb3Bob3NwaG9oeWRyb2xhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLQkkwXCIsXCJsYWJlbFwiOlwiR01QIHJlZHVjdGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHNzBcIixcImxhYmVsXCI6XCJBVFAtZGVwZW5kZW50IHppbmMgbWV0YWxsb3Byb3RlYXNlIEZ0c0hcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLRjA0XCIsXCJsYWJlbFwiOlwiUFRTIE4tYWNldHlsZ2x1Y29zYW1pbmUgdHJhbnNwb3J0ZXIgc3VidW5pdCBJSUFCQ1BUUyBnbHVjb3NlIHRyYW5zcG9ydGVyIHN1YnVuaXQgSUlBXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0g5MVwiLFwibGFiZWxcIjpcIlN1Z2FyLWJpbmRpbmcgdHJhbnNjcmlwdGlvbmFsIHJlZ3VsYXRvclwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIOTBcIixcImxhYmVsXCI6XCJSaWJvbnVjbGVvc2lkZSBoeWRyb2xhc2UgUmloQ1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKTjFcIixcImxhYmVsXCI6XCJEaWh5ZHJvbGlwb2FtaWRlIGFjZXR5bHRyYW5zZmVyYXNlIGNvbXBvbmVudCBvZiBweXJ1dmF0ZSBkZWh5ZHJvZ2VuYXNlIGNvbXBsZXhcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLRzI5XCIsXCJsYWJlbFwiOlwiRGlhY3lsZ2x5Y2Vyb2wga2luYXNlWWVnUy9SdjIyNTIvQm1yVSBmYW1pbHkgbGlwaWQga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S1EyOVwiLFwibGFiZWxcIjpcIlBUUyBtYW5ub3NlL2ZydWN0b3NlL3NvcmJvc2UgdHJhbnNwb3J0ZXIgc3VidW5pdCBJSUNcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSUs3XCIsXCJsYWJlbFwiOlwiUHJvYmFibGUgZ2x5Y2luZSBkZWh5ZHJvZ2VuYXNlIChkZWNhcmJveHlsYXRpbmcpIHN1YnVuaXQgMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHRzVcIixcImxhYmVsXCI6XCJGb3JtYXRlLS10ZXRyYWh5ZHJvZm9sYXRlIGxpZ2FzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHRzRcIixcImxhYmVsXCI6XCJGQUQtYmluZGluZyBveGlkb3JlZHVjdGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJSzVcIixcImxhYmVsXCI6XCJBQkMtRiBmYW1pbHkgQVRQLWJpbmRpbmcgY2Fzc2V0dGUgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdISTNcIixcImxhYmVsXCI6XCJCaWZ1bmN0aW9uYWwgcHJvdGVpbiBHbG1VXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lLNFwiLFwibGFiZWxcIjpcIkRpaHlkcm9mb2xhdGUgcmVkdWN0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S0xQM1wiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluQmFjaXRyYWNpbiBBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdISTJcIixcImxhYmVsXCI6XCJELWFsYW5pbmUtLUQtYWxhbmluZSBsaWdhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUDc5XCIsXCJsYWJlbFwiOlwiQWRoZXNpb24gcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtOMzFcIixcImxhYmVsXCI6XCJBcm9tYXRpYyByaW5nIGh5ZHJveHlsYXNlTWV0YWwtc3VsZnVyIGNsdXN0ZXIgYXNzZW1ibHkgZmFjdG9yXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0haMFwiLFwibGFiZWxcIjpcIkRpaHlkcm94eWFjZXRvbmUga2luYXNlIHN1YnVuaXQgRGhhSzIuNy4xLjEyMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtQWDFcIixcImxhYmVsXCI6XCJMLWxhY3RhdGUgZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIODVcIixcImxhYmVsXCI6XCJBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTkQ5XCIsXCJsYWJlbFwiOlwiQWNldGF0ZSBraW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSFkzXCIsXCJsYWJlbFwiOlwiUy1hZGVub3N5bG1ldGhpb25pbmUgc3ludGhhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSDgxXCIsXCJsYWJlbFwiOlwiTkFEKFApSC1kZXBlbmRlbnQgb3hpZG9yZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLREwyXCIsXCJsYWJlbFwiOlwiSG9sby1bYWN5bC1jYXJyaWVyLXByb3RlaW5dIHN5bnRoYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dXMFwiLFwibGFiZWxcIjpcIkVuZXJneS1jb3VwbGluZyBmYWN0b3IgQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLRzE1XCIsXCJsYWJlbFwiOlwiUHJvYmFibGUgc3VjY2lueWwtZGlhbWlub3BpbWVsYXRlIGRlc3VjY2lueWxhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUEg0XCIsXCJsYWJlbFwiOlwiWGFudGhpbmUgcGhvc3Bob3JpYm9zeWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtGUDdcIixcImxhYmVsXCI6XCJVbmRlY2FwcmVueWwtZGlwaG9zcGhhdGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEs5NTFcIixcImxhYmVsXCI6XCJVcmlkeWxhdGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S04yN1wiLFwibGFiZWxcIjpcIlBob3NwaG9jYXJyaWVyIHByb3RlaW4gSFByXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0pMNlwiLFwibGFiZWxcIjpcIlBob3NwaG9wYW50ZXRoZWluZSBhZGVueWx5bHRyYW5zZmVyYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0pMM1wiLFwibGFiZWxcIjpcIlByb3Rvbl9hbnRpcG9fTSBkb21haW4tY29udGFpbmluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dGMFwiLFwibGFiZWxcIjpcIlNEUiBmYW1pbHkgb3hpZG9yZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLM0oxXCIsXCJsYWJlbFwiOlwiR2x1Y29zZS02LXBob3NwaGF0ZSBpc29tZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSkwyXCIsXCJsYWJlbFwiOlwiUHJvYmFibGUgbmljb3RpbmF0ZS1udWNsZW90aWRlIGFkZW55bHlsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLOTU3XCIsXCJsYWJlbFwiOlwiQ2FyYm9oeWRyYXRlIEFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVN1Z2FyIEFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHRjlcIixcImxhYmVsXCI6XCJBbmFlcm9iaWMgcmlib251Y2xlb3NpZGUtdHJpcGhvc3BoYXRlIHJlZHVjdGFzZS1hY3RpdmF0aW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLUkc1XCIsXCJsYWJlbFwiOlwiQXJnaW5pbmUgZGVpbWluYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0I3N1wiLFwibGFiZWxcIjpcIk5BRChQKUgtZGVwZW5kZW50IG94aWRvcmVkdWN0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0s4MlwiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluUG9seWFtaW5lIEFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0laNVwiLFwibGFiZWxcIjpcIkFzcGFyYWdpbmUgc3ludGhhc2UgKEdsdXRhbWluZS1oeWRyb2x5emluZyk2LjMuNS40XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0laMFwiLFwibGFiZWxcIjpcIjEsNC1hbHBoYS1nbHVjYW4gYnJhbmNoaW5nIGVuenltZSBHbGdCXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hHNVwiLFwibGFiZWxcIjpcIjUtbWV0aHlsdGV0cmFoeWRyb3B0ZXJveWx0cmlnbHV0YW1hdGUtLWhvbW9jeXN0ZWluZSBtZXRoeWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIRzNcIixcImxhYmVsXCI6XCJHbHljZXJhdGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dFMlwiLFwibGFiZWxcIjpcIlBUUyBnbHVjb3NlIHRyYW5zcG9ydGVyIHN1YnVuaXQgSUlBXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4SzgyMFwiLFwibGFiZWxcIjpcIlVyaWRpbmUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lJMFwiLFwibGFiZWxcIjpcIkFtaW5vbWV0aHlsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHS00yXCIsXCJsYWJlbFwiOlwiQW1pZG9oeWRyb2xhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLUkYyXCIsXCJsYWJlbFwiOlwiZElUUC9YVFAgcHlyb3Bob3NwaGF0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4SzcwOFwiLFwibGFiZWxcIjpcIkdsdXRhdGhpb25lIGJpb3N5bnRoZXNpcyBiaWZ1bmN0aW9uYWwgcHJvdGVpbiBHc2hBQlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEs1TTdcIixcImxhYmVsXCI6XCJSaWJvZmxhdmluIGJpb3N5bnRoZXNpcyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hHOFwiLFwibGFiZWxcIjpcIkNhcmJvaHlkcmF0ZSBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLQ1k4XCIsXCJsYWJlbFwiOlwiSGVhdnkgbWV0YWwtYmluZGluZyBwcm90ZWluSGVhdnktbWV0YWwtYXNzb2NpYXRlZCBkb21haW4tY29udGFpbmluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dFNlwiLFwibGFiZWxcIjpcIlRya0ggZmFtaWx5IHBvdGFzc2l1bSB1cHRha2UgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIRzZcIixcImxhYmVsXCI6XCJPbGlnb3BlcHRpZGUgQUJDIHRyYW5zcG9ydGVyIHN1YnN0cmF0ZS1iaW5kaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLTFo0XCIsXCJsYWJlbFwiOlwiR2x1dGFtYXRlIHJhY2VtYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0k4MFwiLFwibGFiZWxcIjpcIkdsdXRhbWluZS0tZnJ1Y3Rvc2UtNi1waG9zcGhhdGUgYW1pbm90cmFuc2ZlcmFzZSBbaXNvbWVyaXppbmddXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hXOVwiLFwibGFiZWxcIjpcIkFyc2VuaWNhbCBwdW1wLWRyaXZpbmcgQVRQYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0ZOOVwiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hGNFwiLFwibGFiZWxcIjpcIlRyaW9zZS1waG9zcGhhdGUgaXNvbWVyYXNlNS4zLjEuMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJSDVcIixcImxhYmVsXCI6XCJQcm9iYWJsZSBnbHljaW5lIGRlaHlkcm9nZW5hc2UgKGRlY2FyYm94eWxhdGluZykgc3VidW5pdCAyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0pKNlwiLFwibGFiZWxcIjpcIlBlbmljaWxsaW4tYmluZGluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S0xNNFwiLFwibGFiZWxcIjpcIkFjeWwgY2FycmllciBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0pKNFwiLFwibGFiZWxcIjpcIlJpYm9mbGF2aW4gdHJhbnNwb3J0ZXJcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLNk4wXCIsXCJsYWJlbFwiOlwiT3JvdGlkaW5lIDUnLXBob3NwaGF0ZSBkZWNhcmJveHlsYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S0xNMVwiLFwibGFiZWxcIjpcIkVub2xhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSkoyXCIsXCJsYWJlbFwiOlwiUGhvc3Boby1OLWFjZXR5bG11cmFtb3lsLXBlbnRhcGVwdGlkZS10cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtOMDNcIixcImxhYmVsXCI6XCJDb2YtdHlwZSBIQUQtSUlCIGZhbWlseSBoeWRyb2xhc2VIYWxvYWNpZCBkZWhhbG9nZW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLM0gwXCIsXCJsYWJlbFwiOlwiUHV0YXRpdmUgTi1hY2V0eWxtYW5ub3NhbWluZS02LXBob3NwaGF0ZSAyLWVwaW1lcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIRjhcIixcImxhYmVsXCI6XCJUcmFuc2NyaXB0aW9uIHRlcm1pbmF0aW9uIGZhY3RvciBSaG9cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSUg5XCIsXCJsYWJlbFwiOlwiRGlwZXB0aWRhc2UgUGVwVlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIRjZcIixcImxhYmVsXCI6XCJUcmFuc2FsZG9sYXNlMi4yLjEuMlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtEOTdcIixcImxhYmVsXCI6XCJQeXJ1dmF0ZSBraW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSko3XCIsXCJsYWJlbFwiOlwiVURQLU4tYWNldHlsZ2x1Y29zYW1pbmUtLU4tYWNldHlsbXVyYW15bC0ocGVudGFwZXB0aWRlKSBweXJvcGhvc3Bob3J5bC11bmRlY2FwcmVub2wgTi1hY2V0eWxnbHVjb3NhbWluZSB0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHVDJcIixcImxhYmVsXCI6XCJBZGVueWxvc3VjY2luYXRlIGx5YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hWMlwiLFwibGFiZWxcIjpcIkFscGhhLWdseWNlcm9waG9zcGhhdGUgb3hpZGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJNzFcIixcImxhYmVsXCI6XCJHbHV0YXRoaW9uZSBwZXJveGlkYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dUOVwiLFwibGFiZWxcIjpcIjMtb3hvYWN5bC1bYWN5bC1jYXJyaWVyLXByb3RlaW5dIHN5bnRoYXNlIDJcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSVg4XCIsXCJsYWJlbFwiOlwiR2x5Y29nZW4gc3ludGhhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSDU1XCIsXCJsYWJlbFwiOlwiVGh5bWlkeWxhdGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0o5N1wiLFwibGFiZWxcIjpcIkFscGhhLTEsNCBnbHVjYW4gcGhvc3Bob3J5bGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJWDdcIixcImxhYmVsXCI6XCJBVFAtYmluZGluZyBjYXNzZXR0ZSBkb21haW4tY29udGFpbmluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lHNFwiLFwibGFiZWxcIjpcIlVEUC1nbHVjb3NlIDYtZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLSzVcIixcImxhYmVsXCI6XCJDYXRpb24tdHJhbnNsb2NhdGluZyBQLXR5cGUgQVRQYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0xNN1wiLFwibGFiZWxcIjpcIkdmby9JZGgvTW9jQSBmYW1pbHkgb3hpZG9yZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUEwxXCIsXCJsYWJlbFwiOlwiTWV0aHlsZ2x5b3hhbCBzeW50aGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJRzFcIixcImxhYmVsXCI6XCJEZW94eXJpYm9zZS1waG9zcGhhdGUgYWxkb2xhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLVUowXCIsXCJsYWJlbFwiOlwiTWV0aGlvbmluZSBBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtEODNcIixcImxhYmVsXCI6XCJHbHljZXJvbCBraW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSUc4XCIsXCJsYWJlbFwiOlwiUGhvc3BoYXRlIGFjeWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIRTVcIixcImxhYmVsXCI6XCJBbWlub3B5cmltaWRpbmUgYW1pbm9oeWRyb2xhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLTFkwXCIsXCJsYWJlbFwiOlwiRm9ybWF0ZSBhY2V0eWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtEODdcIixcImxhYmVsXCI6XCJNZXZhbG9uYXRlIGtpbmFzZTIuNy4xLjM2XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dTMlwiLFwibGFiZWxcIjpcIkVub3lsLVthY3lsLWNhcnJpZXItcHJvdGVpbl0gcmVkdWN0YXNlIEZhYktcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSjgzXCIsXCJsYWJlbFwiOlwiU2VyaW5lIGh5ZHJveHltZXRoeWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJNjJcIixcImxhYmVsXCI6XCJGcnVjdG9zZS02LXBob3NwaGF0ZSBhbGRvbGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdINDBcIixcImxhYmVsXCI6XCJCcmFuY2hlZC1jaGFpbiBhbWlubyBhY2lkIHRyYW5zcG9ydCBzeXN0ZW0gY2FycmllciBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0ozNlwiLFwibGFiZWxcIjpcIkVuZXJneS1jb3VwbGluZyBmYWN0b3IgdHJhbnNwb3J0ZXIgdHJhbnNtZW1icmFuZSBwcm90ZWluIEVjZlRcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLREgzXCIsXCJsYWJlbFwiOlwiMiwzLWJpc3Bob3NwaG9nbHljZXJhdGUtaW5kZXBlbmRlbnQgcGhvc3Bob2dseWNlcmF0ZSBtdXRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSVc1XCIsXCJsYWJlbFwiOlwiSHlkcm94eWV0aHlsdGhpYXpvbGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lXNlwiLFwibGFiZWxcIjpcIlRoeW1pZHlsYXRlIHN5bnRoYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0dTM1wiLFwibGFiZWxcIjpcIjMtb3hvYWN5bC1bYWN5bC1jYXJyaWVyLXByb3RlaW5dIHJlZHVjdGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKSDNcIixcImxhYmVsXCI6XCJBVFAtZGVwZW5kZW50IEROQSBoZWxpY2FzZSBSZWNRXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0xMNVwiLFwibGFiZWxcIjpcIkFzcGFydGF0ZSBjYXJiYW1veWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIRDFcIixcImxhYmVsXCI6XCJDVFAgc3ludGhhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHS0oyXCIsXCJsYWJlbFwiOlwiQ29wcGVyLWV4cG9ydGluZyBQLXR5cGUgQVRQYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lGMFwiLFwibGFiZWxcIjpcIkFUUC1iaW5kaW5nIGNhc3NldHRlIGRvbWFpbi1jb250YWluaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTTQ2XCIsXCJsYWJlbFwiOlwiQ2F0YWJvbGl0ZSBjb250cm9sIHByb3RlaW4gQVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJRjlcIixcImxhYmVsXCI6XCJBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEs4UDZcIixcImxhYmVsXCI6XCJBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSEQ3XCIsXCJsYWJlbFwiOlwiU2FwZXAgZmFtaWx5IE1uKDIrKS1kZXBlbmRlbnQgZGlwZXB0aWRhc2UzLjQuMTMuLVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJRjhcIixcImxhYmVsXCI6XCJVRFAtTi1hY2V0eWxnbHVjb3NhbWluZSAyLWVwaW1lcmFzZSAoTm9uLWh5ZHJvbHl6aW5nKTUuMS4zLjE0XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tKOFwiLFwibGFiZWxcIjpcIlNvZGl1bTphbGFuaW5lIHN5bXBvcnRlciBmYW1pbHkgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIRDRcIixcImxhYmVsXCI6XCJPbGlnb3BlcHRpZGUgQUJDIHRyYW5zcG9ydGVyIHN1YnN0cmF0ZS1iaW5kaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTVUxXCIsXCJsYWJlbFwiOlwiVGhpb3JlZG94aW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTTQwXCIsXCJsYWJlbFwiOlwiUGVwdGlkZSBtZXRoaW9uaW5lIHN1bGZveGlkZSByZWR1Y3Rhc2UgTXNyQlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIMzBcIixcImxhYmVsXCI6XCJBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJVjBcIixcImxhYmVsXCI6XCJBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKNzFcIixcImxhYmVsXCI6XCJETkEgbGlnYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0pTMVwiLFwibGFiZWxcIjpcIlRoaW9yZWRveGluIHJlZHVjdGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLOTBcIixcImxhYmVsXCI6XCJCcmFuY2hlZC1jaGFpbi1hbWluby1hY2lkIGFtaW5vdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLVVkwXCIsXCJsYWJlbFwiOlwiR2x5Y2luZSBDLWFjZXR5bHRyYW5zZmVyYXNlMi4zLjEuMjlcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSFQ2XCIsXCJsYWJlbFwiOlwiQUFBIGZhbWlseSBBVFBhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzk4XCIsXCJsYWJlbFwiOlwiMy1oeWRyb3h5LTMtbWV0aHlsZ2x1dGFyeWwgY29lbnp5bWUgQSByZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSDM0XCIsXCJsYWJlbFwiOlwiMi1hbWluby00LWh5ZHJveHktNi1oeWRyb3h5bWV0aHlsZGloeWRyb3B0ZXJpZGluZSBkaXBob3NwaG9raW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUEs1XCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW5Tb2RpdW0gQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSDMyXCIsXCJsYWJlbFwiOlwiTGFjdG9uYXNlIGZhbWlseSBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lWNVwiLFwibGFiZWxcIjpcIkdseWNlcm9sLTMtcGhvc3BoYXRlIGFjeWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdMSzRcIixcImxhYmVsXCI6XCJVRFAtTi1hY2V0eWxlbm9scHlydXZveWxnbHVjb3NhbWluZSByZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSkcyXCIsXCJsYWJlbFwiOlwiVHJhbnNrZXRvbGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJRTFcIixcImxhYmVsXCI6XCJCcmFuY2hlZC1jaGFpbiBhbWlubyBhY2lkIHRyYW5zcG9ydCBzeXN0ZW0gY2FycmllciBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S0xSOVwiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tJMVwiLFwibGFiZWxcIjpcIlBob3NwaGF0ZSBhY2V0eWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKRzBcIixcImxhYmVsXCI6XCJHbHV0YW1hdGUgZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdMSzJcIixcImxhYmVsXCI6XCJBY2V0eWwtQ29BIEMtYWNldHlsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTEszXCIsXCJsYWJlbFwiOlwiR01QIHN5bnRoYXNlIFtnbHV0YW1pbmUtaHlkcm9seXppbmddXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1JSN1wiLFwibGFiZWxcIjpcIkRpaHlkcm9saXBveWwgZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIQzhcIixcImxhYmVsXCI6XCJDbGFzcyBJSSBmcnVjdG9zZS1iaXNwaG9zcGhhdGUgYWxkb2xhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLUFo1XCIsXCJsYWJlbFwiOlwiQWRlbnlsYXRlIGtpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtONTVcIixcImxhYmVsXCI6XCJHbHVjb3NlLTEtcGhvc3BoYXRlIGFkZW55bHlsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHS0k5XCIsXCJsYWJlbFwiOlwiQW1pbm90cmFuc2ZlcmFzZSBjbGFzcyBWLWZvbGQgUExQLWRlcGVuZGVudCBlbnp5bWVcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTVQ0XCIsXCJsYWJlbFwiOlwiSGVhdnkgbWV0YWwgdHJhbnNwb3J0ZXJcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHS0k3XCIsXCJsYWJlbFwiOlwiQVRQLWJpbmRpbmcgY2Fzc2V0dGUgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJRTVcIixcImxhYmVsXCI6XCJEZXBob3NwaG8tQ29BIGtpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdMSzlcIixcImxhYmVsXCI6XCJEaWh5ZHJvb3JvdGF0ZSBkZWh5ZHJvZ2VuYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0IyM1wiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIQzJcIixcImxhYmVsXCI6XCJHbHljb3NpZGUgaHlkcm9sYXNlIGZhbWlseSAxIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzgzXCIsXCJsYWJlbFwiOlwiRGloeWRyb29yb3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzgwXCIsXCJsYWJlbFwiOlwiVURQLU4tYWNldHlsZ2x1Y29zYW1pbmUgMS1jYXJib3h5dmlueWx0cmFuc2ZlcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtMVjBcIixcImxhYmVsXCI6XCJDeXRpZHlsYXRlIGtpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtMQjVcIixcImxhYmVsXCI6XCJSaWJvc2UtcGhvc3BoYXRlIHB5cm9waG9zcGhva2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0hOMlwiLFwibGFiZWxcIjpcIkFzcGFyYWdpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIUzhcIixcImxhYmVsXCI6XCJTRFIgZmFtaWx5IE5BRChQKS1kZXBlbmRlbnQgb3hpZG9yZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSlc5XCIsXCJsYWJlbFwiOlwiVElHUjAxNDU3IGZhbWlseSBIQUQtdHlwZSBoeWRyb2xhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSTQ1XCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1Q1MlwiLFwibGFiZWxcIjpcIlBlcHRpZGUgY2hhaW4gcmVsZWFzZSBmYWN0b3IgMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdIMjJcIixcImxhYmVsXCI6XCJOYS9QaSBjb3RyYW5zcG9ydGVyIGZhbWlseSBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0s4NFwiLFwibGFiZWxcIjpcIkh5ZHJveHltZXRoeWxnbHV0YXJ5bC1Db0Egc3ludGhhc2UyLjMuMy4xMFwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKNjRcIixcImxhYmVsXCI6XCJDYWRtaXVtLXRyYW5zbG9jYXRpbmcgUC10eXBlIEFUUGFzZTMuNi4zLjNcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTVM1XCIsXCJsYWJlbFwiOlwiU29kaXVtOnByb3RvbiBhbnRpcG9ydGVyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1BNOFwiLFwibGFiZWxcIjpcIjItZGVoeWRyb3BhbnRvYXRlIDItcmVkdWN0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4SzlQMlwiLFwibGFiZWxcIjpcIkFtaW5vdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSkY3XCIsXCJsYWJlbFwiOlwiNSctbWV0aHlsdGhpb2FkZW5vc2luZS9TLWFkZW5vc3lsaG9tb2N5c3RlaW5lIG51Y2xlb3NpZGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtOVTRcIixcImxhYmVsXCI6XCJBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VTdWdhciBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLSkE2XCIsXCJsYWJlbFwiOlwiUFRTIG1hbm5vc2UgZmFtaWx5IHRyYW5zcG9ydGVyIHN1YnVuaXQgSUlEUFRTIG1hbm5vc2UvZnJ1Y3Rvc2Uvc29yYm9zZSB0cmFuc3BvcnRlciBmYW1pbHkgc3VidW5pdCBJSURcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLRlozXCIsXCJsYWJlbFwiOlwiSFByIGtpbmFzZS9waG9zcGhvcnlsYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0xKOFwiLFwibGFiZWxcIjpcIk4oNCktKEJldGEtTi1hY2V0eWxnbHVjb3NhbWlueWwpLUwtYXNwYXJhZ2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S1A4MlwiLFwibGFiZWxcIjpcIkVsb25nYXRpb24gZmFjdG9yIEdcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSlYxXCIsXCJsYWJlbFwiOlwiVURQLU4tYWNldHlsbXVyYW1veWxhbGFuaW5lLS1ELWdsdXRhbWF0ZSBsaWdhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLQzI2XCIsXCJsYWJlbFwiOlwiUGVwdGlkZSBtZXRoaW9uaW5lIHN1bGZveGlkZSByZWR1Y3Rhc2UgTXNyQVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtCMDlcIixcImxhYmVsXCI6XCJSbmZBQkNER0UgdHlwZSBlbGVjdHJvbiB0cmFuc3BvcnQgY29tcGxleCBzdWJ1bml0IERcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLTVc4XCIsXCJsYWJlbFwiOlwiRmVycmVkb3hpbi0tTkFEUCByZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLNjkyXCIsXCJsYWJlbFwiOlwiTWV0YWwgQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlWmluYyBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSjU3XCIsXCJsYWJlbFwiOlwiQ2FkbWl1bS10cmFuc2xvY2F0aW5nIFAtdHlwZSBBVFBhc2UzLjYuMy4zXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0kzNFwiLFwibGFiZWxcIjpcIlJpYnVsb3NlLXBob3NwaGF0ZSAzLWVwaW1lcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKNTRcIixcImxhYmVsXCI6XCI2LXBob3NwaG9nbHVjb25hdGUgZGVoeWRyb2dlbmFzZSwgZGVjYXJib3h5bGF0aW5nXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0xaOFwiLFwibGFiZWxcIjpcIkV4dHJhY2VsbHVsYXIgc29sdXRlLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtQSTNcIixcImxhYmVsXCI6XCJSaWJvbnVjbGVvdGlkZSByZWR1Y3Rhc2Ugc3RpbXVsYXRvcnkgcHJvdGVpblJpYm9udWNsZW90aWRlIHJlZHVjdGFzZSBzdGltdWxhdG9yeSBwcm90ZWluIE5yZElcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTDk1XCIsXCJsYWJlbFwiOlwiTWFuZ2FuZXNlIHRyYW5zcG9ydGVyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0xJMVwiLFwibGFiZWxcIjpcInBIIHJlZ3VsYXRpb24gcHJvdGVpbiBGXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0VGOFwiLFwibGFiZWxcIjpcIlB1ciBvcGVyb24gcmVwcmVzc29yXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S1FSMVwiLFwibGFiZWxcIjpcIkh5cG94YW50aGluZSBwaG9zcGhvcmlib3N5bHRyYW5zZmVyYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0kyOVwiLFwibGFiZWxcIjpcIlNtYWxsIHJpYm9zb21hbCBzdWJ1bml0IGJpb2dlbmVzaXMgR1RQYXNlIFJzZ0FcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSFE5XCIsXCJsYWJlbFwiOlwiUHlyaWRpbmUgbnVjbGVvdGlkZS1kaXN1bGZpZGUgb3hpZG9yZWR1Y3Rhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLTkg1XCIsXCJsYWJlbFwiOlwiVXJhY2lsIHBob3NwaG9yaWJvc3lsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTTk3XCIsXCJsYWJlbFwiOlwiR2x5Y2luZSBjbGVhdmFnZSBzeXN0ZW0gSCBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0xJOFwiLFwibGFiZWxcIjpcIkRVRjU2MSBkb21haW4tY29udGFpbmluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tHOFwiLFwibGFiZWxcIjpcIk4tYWNldHlsZ2x1Y29zYW1pbmUtNi1waG9zcGhhdGUgZGVhY2V0eWxhc2UzLjUuMS4yNVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLRzVcIixcImxhYmVsXCI6XCJHZm8vSWRoL01vY0EgZmFtaWx5IG94aWRvcmVkdWN0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hBMlwiLFwibGFiZWxcIjpcIkdseWNvc2lkZSBoeWRyb2xhc2UgZmFtaWx5IDEgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtCUTJcIixcImxhYmVsXCI6XCJETkEgbWlzbWF0Y2ggcmVwYWlyIHByb3RlaW4gTXV0VE5VRElYIGh5ZHJvbGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLNjBcIixcImxhYmVsXCI6XCJSaWJvc2UtNS1waG9zcGhhdGUgaXNvbWVyYXNlIEFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzY2XCIsXCJsYWJlbFwiOlwidFJOQS1zcGVjaWZpYyBhZGVub3NpbmUgZGVhbWluYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0gwMlwiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lTM1wiLFwibGFiZWxcIjpcIkdUUGFzZSBFcmFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSTIwXCIsXCJsYWJlbFwiOlwiQ29lbnp5bWUgQSBiaW9zeW50aGVzaXMgYmlmdW5jdGlvbmFsIHByb3RlaW4gQ29hQkNcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTDg0XCIsXCJsYWJlbFwiOlwiSGlzdGlkaW5lIGtpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtGSDVcIixcImxhYmVsXCI6XCJJc29jaG9yaXNtYXRhc2VJc29jaG9yaXNtYXRhc2UgZmFtaWx5IHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSlUzXCIsXCJsYWJlbFwiOlwiUk9LIGZhbWlseSBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0s2M1wiLFwibGFiZWxcIjpcIlRoeW1pZGluZSBraW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSkQwXCIsXCJsYWJlbFwiOlwiQWN5bC1Db0EgZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdMSDBcIixcImxhYmVsXCI6XCJBY3lscGhvc3BoYXRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzU5XCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIHN1YnN0cmF0ZS1iaW5kaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSjM5XCIsXCJsYWJlbFwiOlwiQ3l0aWRpbmUgZGVhbWluYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0tGNFwiLFwibGFiZWxcIjpcIkFkZW55bG9zdWNjaW5hdGUgc3ludGhldGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKRDFcIixcImxhYmVsXCI6XCJCaWZ1bmN0aW9uYWwgb2xpZ29yaWJvbnVjbGVhc2UvUEFQIHBob3NwaGF0YXNlIE5ybkFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTVk1XCIsXCJsYWJlbFwiOlwiMzBTIHJpYm9zb21hbCBwcm90ZWluIFMxXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0s1MFwiLFwibGFiZWxcIjpcIkJpZnVuY3Rpb25hbCBoeWRyb3h5bWV0aHlscHlyaW1pZGluZSBraW5hc2UvcGhvc3Bob21ldGh5bHB5cmltaWRpbmUga2luYXNlMi43LjEuNDkyLjcuNC43XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S1FBNVwiLFwibGFiZWxcIjpcIkNsYXNzIElJIGZydWN0b3NlLTEsNi1iaXNwaG9zcGhhdGUgYWxkb2xhc2U0LjEuMi4xM0ZydWN0b3NlLTEsNi1iaXNwaG9zcGhhdGUgYWxkb2xhc2UsIGNsYXNzIElJXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S0RTOFwiLFwibGFiZWxcIjpcIkNhZG1pdW0tdHJhbnNsb2NhdGluZyBQLXR5cGUgQVRQYXNlMy42LjMuM1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEtNVTRcIixcImxhYmVsXCI6XCJBTVAtYmluZGluZyBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0pUN1wiLFwibGFiZWxcIjpcIkFsYW5pbmUgZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLNThcIixcImxhYmVsXCI6XCJJc29wZW50ZW55bC1kaXBob3NwaGF0ZSBkZWx0YS1pc29tZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTEc2XCIsXCJsYWJlbFwiOlwiR2x5Y2VyYWxkZWh5ZGUtMy1waG9zcGhhdGUgZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLNTZcIixcImxhYmVsXCI6XCJEaXBob3NwaG9tZXZhbG9uYXRlIGRlY2FyYm94eWxhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSjM1XCIsXCJsYWJlbFwiOlwiUG9seXByZW55bCBzeW50aGV0YXNlIGZhbWlseSBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMVM4S1IyOFwiLFwibGFiZWxcIjpcIlVEUC1OLWFjZXR5bGdsdWNvc2FtaW5lIDEtY2FyYm94eXZpbnlsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLTFM4XCIsXCJsYWJlbFwiOlwiUGhvc3Bob3JpYm9zeWxmb3JteWxnbHljaW5hbWlkaW5lIHN5bnRoYXNlIElcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSTExXCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1RTMVwiLFwibGFiZWxcIjpcIk5BREggb3hpZGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKVDFcIixcImxhYmVsXCI6XCJQaG9zcGhvcGVudG9tdXRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTUk1XCIsXCJsYWJlbFwiOlwiRmxhdm9kb3hpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKVDJcIixcImxhYmVsXCI6XCJDeXN0ZWluZSBkZXN1bGZ1cmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtQTjFcIixcImxhYmVsXCI6XCJFbG9uZ2F0aW9uIGZhY3RvciBUdVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdHTThcIixcImxhYmVsXCI6XCJIeWRyb3h5YWNpZCBkZWh5ZHJvZ2VuYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0pTOFwiLFwibGFiZWxcIjpcIkxhbnRpYmlvdGljIHByb3RlY3Rpb24gQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHN1YnVuaXRcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLUEo0XCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLTEIxXCIsXCJsYWJlbFwiOlwiR2x5Y2Vyb2wtMy1waG9zcGhhdGUgZGVoeWRyb2dlbmFzZSBbTkFEKFApK11cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzQ5XCIsXCJsYWJlbFwiOlwiUHlyaW1pZGluZS1udWNsZW9zaWRlIHBob3NwaG9yeWxhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTEc3XCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSUEzXCIsXCJsYWJlbFwiOlwiVXZyQUJDIHN5c3RlbSBwcm90ZWluIEFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSkMwXCIsXCJsYWJlbFwiOlwiTWFnbmVzaXVtIHRyYW5zcG9ydGVyIE1ndEVcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTjk1XCIsXCJsYWJlbFwiOlwiSW5vc2luZS01Jy1tb25vcGhvc3BoYXRlIGRlaHlkcm9nZW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLRlY1XCIsXCJsYWJlbFwiOlwiUmlib251Y2xlb3NpZGUtZGlwaG9zcGhhdGUgcmVkdWN0YXNlIHN1YnVuaXQgYmV0YVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtNSDdcIixcImxhYmVsXCI6XCJQdXJpbmUgbnVjbGVvc2lkZSBwaG9zcGhvcnlsYXNlIERlb0QtdHlwZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJUTZcIixcImxhYmVsXCI6XCJQZW5pY2lsbGluLWJpbmRpbmcgcHJvdGVpbiAyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0s0N1wiLFwibGFiZWxcIjpcIkdsdWNvc2FtaW5lLTYtcGhvc3BoYXRlIGRlYW1pbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdJMDJcIixcImxhYmVsXCI6XCJJbnN1bGluYXNlIGZhbWlseSBwcm90ZWluXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0oyMlwiLFwibGFiZWxcIjpcIlBCUDFBIGZhbWlseSBwZW5pY2lsbGluLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTFTOEtSMDFcIixcImxhYmVsXCI6XCJDYXJiYW1hdGUga2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0hOOFwiLFwibGFiZWxcIjpcIlBUUyBnbHVjb3NlIHRyYW5zcG9ydGVyIHN1YnVuaXQgSUlCQ1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTMyOEszUDdcIixcImxhYmVsXCI6XCJHbHVjb2tpbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKMTZcIixcImxhYmVsXCI6XCJHbHVjb3NlLTYtcGhvc3BoYXRlIDEtZGVoeWRyb2dlbmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdLMzhcIixcImxhYmVsXCI6XCJOaWNvdGluYXRlIHBob3NwaG9yaWJvc3lsdHJhbnNmZXJhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHR0w0XCIsXCJsYWJlbFwiOlwiTGlwb3Byb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHS0Q2XCIsXCJsYWJlbFwiOlwiQUJDLUYgZmFtaWx5IEFUUC1iaW5kaW5nIGNhc3NldHRlIGRvbWFpbi1jb250YWluaW5nIHByb3RlaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHTEY3XCIsXCJsYWJlbFwiOlwiQ0NBLWFkZGluZyBlbnp5bWVcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEExUzhLTFU3XCIsXCJsYWJlbFwiOlwiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlUGVwdGlkZSBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEEzMjhLOEk5XCIsXCJsYWJlbFwiOlwiRGloeWRyb3B0ZXJvYXRlIHN5bnRoYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1A4MlwiLFwibGFiZWxcIjpcIkdsdXRhbWluZSBzeW50aGV0YXNlIEkgYWxwaGFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHSzM2XCIsXCJsYWJlbFwiOlwiTkgoMyktZGVwZW5kZW50IE5BRCgrKSBzeW50aGV0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S040M1wiLFwibGFiZWxcIjpcIkFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVBlcHRpZGUgQUJDIHRyYW5zcG9ydGVyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBNTE2R0lQMlwiLFwibGFiZWxcIjpcIkxELWNhcmJveHlwZXB0aWRhc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJBMEE1MTZHR0wwXCIsXCJsYWJlbFwiOlwiUy1yaWJvc3lsaG9tb2N5c3RlaW5lIGx5YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiQTBBMzI4S1BZOFwiLFwibGFiZWxcIjpcIkJldGFpbmUvcHJvbGluZS9jaG9saW5lIGZhbWlseSBBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIkEwQTUxNkdKUjJcIixcImxhYmVsXCI6XCJCaWZ1bmN0aW9uYWwgaHlkcm94eW1ldGh5bHB5cmltaWRpbmUga2luYXNlL3Bob3NwaG9tZXRoeWxweXJpbWlkaW5lIGtpbmFzZTIuNy4xLjQ5Mi43LjQuN1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAxOTIzNVwiLFwibGFiZWxcIjpcIlAxOTIzNVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwMTU4OFwiLFwibGFiZWxcIjpcIlAwMTU4OFwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlE5TlNFMlwiLFwibGFiZWxcIjpcIlE5TlNFMlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlE5VU1SMFwiLFwibGFiZWxcIjpcIlE5VU1SMFwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlEwMTgxM1wiLFwibGFiZWxcIjpcIlEwMTgxM1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlE5VUo3M1wiLFwibGFiZWxcIjpcIlE5VUo3M1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlE5VU1ROVwiLFwibGFiZWxcIjpcIlE5VU1ROVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlE5TlE4OFwiLFwibGFiZWxcIjpcIlE5TlE4OFwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwNzczOFwiLFwibGFiZWxcIjpcIkJpc3Bob3NwaG9nbHljZXJhdGUgbXV0YXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUTE2ODc3XCIsXCJsYWJlbFwiOlwiNi1waG9zcGhvZnJ1Y3RvLTIta2luYXNlL2ZydWN0b3NlLTIsNi1iaXNwaG9zcGhhdGFzZSA0XCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUTE2ODc1XCIsXCJsYWJlbFwiOlwiNi1waG9zcGhvZnJ1Y3RvLTIta2luYXNlL2ZydWN0b3NlLTIsNi1iaXNwaG9zcGhhdGFzZSAzXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiTzYwODI1XCIsXCJsYWJlbFwiOlwiNi1waG9zcGhvZnJ1Y3RvLTIta2luYXNlL2ZydWN0b3NlLTIsNi1iaXNwaG9zcGhhdGFzZSAyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDE1MjU5XCIsXCJsYWJlbFwiOlwiUGhvc3Bob2dseWNlcmF0ZSBtdXRhc2UgMlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwNjQ5M1wiLFwibGFiZWxcIjpcIkN5Y2xpbi1kZXBlbmRlbnQga2luYXNlIDFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJQMTQ2MzVcIixcImxhYmVsXCI6XCJHMi9taXRvdGljLXNwZWNpZmljIGN5Y2xpbi1CMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwODIzN1wiLFwibGFiZWxcIjpcIkFUUC1kZXBlbmRlbnQgNi1waG9zcGhvZnJ1Y3Rva2luYXNlLCBtdXNjbGUgdHlwZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIk8xNDU0M1wiLFwibGFiZWxcIjpcIk8xNDU0M1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlA0MjIyOVwiLFwibGFiZWxcIjpcIlNpZ25hbCB0cmFuc2R1Y2VyIGFuZCBhY3RpdmF0b3Igb2YgdHJhbnNjcmlwdGlvbiA1QVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAyOTM1MFwiLFwibGFiZWxcIjpcIlR5cm9zaW5lLXByb3RlaW4gcGhvc3BoYXRhc2Ugbm9uLXJlY2VwdG9yIHR5cGUgNlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIk82MDY3NFwiLFwibGFiZWxcIjpcIlR5cm9zaW5lLXByb3RlaW4ga2luYXNlIEpBSzJcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJQMDcyMDVcIixcImxhYmVsXCI6XCJQaG9zcGhvZ2x5Y2VyYXRlIGtpbmFzZSAyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiTzE0NTU2XCIsXCJsYWJlbFwiOlwiR2x5Y2VyYWxkZWh5ZGUtMy1waG9zcGhhdGUgZGVoeWRyb2dlbmFzZSwgdGVzdGlzLXNwZWNpZmljXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDA5NDY3XCIsXCJsYWJlbFwiOlwiRnJ1Y3Rvc2UtMSw2LWJpc3Bob3NwaGF0YXNlIDFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJQMDkxMDRcIixcImxhYmVsXCI6XCJHYW1tYS1lbm9sYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDAwNTU4XCIsXCJsYWJlbFwiOlwiUGhvc3Bob2dseWNlcmF0ZSBraW5hc2UgMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAxNzg1OFwiLFwibGFiZWxcIjpcIkFUUC1kZXBlbmRlbnQgNi1waG9zcGhvZnJ1Y3Rva2luYXNlLCBsaXZlciB0eXBlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDE4NjY5XCIsXCJsYWJlbFwiOlwiUGhvc3Bob2dseWNlcmF0ZSBtdXRhc2UgMVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwNDQwNlwiLFwibGFiZWxcIjpcIkdseWNlcmFsZGVoeWRlLTMtcGhvc3BoYXRlIGRlaHlkcm9nZW5hc2VcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJPMDA3NTdcIixcImxhYmVsXCI6XCJGcnVjdG9zZS0xLDYtYmlzcGhvc3BoYXRhc2UgaXNvenltZSAyXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDYwMTc0XCIsXCJsYWJlbFwiOlwiVHJpb3NlcGhvc3BoYXRlIGlzb21lcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAzNTU1N1wiLFwibGFiZWxcIjpcIkdsdWNva2luYXNlXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDE2MTE4XCIsXCJsYWJlbFwiOlwiNi1waG9zcGhvZnJ1Y3RvLTIta2luYXNlL2ZydWN0b3NlLTIsNi1iaXNwaG9zcGhhdGFzZSAxXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDA2NzMzXCIsXCJsYWJlbFwiOlwiQWxwaGEtZW5vbGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAxMzkyOVwiLFwibGFiZWxcIjpcIkJldGEtZW5vbGFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlEwMTA5NFwiLFwibGFiZWxcIjpcIlRyYW5zY3JpcHRpb24gZmFjdG9yIEUyRjFcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJQMDUwNjJcIixcImxhYmVsXCI6XCJGcnVjdG9zZS1iaXNwaG9zcGhhdGUgYWxkb2xhc2UgQlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAxNDYxOFwiLFwibGFiZWxcIjpcIlB5cnV2YXRlIGtpbmFzZSBQS01cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJQMDk5NzJcIixcImxhYmVsXCI6XCJGcnVjdG9zZS1iaXNwaG9zcGhhdGUgYWxkb2xhc2UgQ1wiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwNDA3NVwiLFwibGFiZWxcIjpcIkZydWN0b3NlLWJpc3Bob3NwaGF0ZSBhbGRvbGFzZSBBXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUTlCWVoyXCIsXCJsYWJlbFwiOlwiTC1sYWN0YXRlIGRlaHlkcm9nZW5hc2UgQS1saWtlIDZCXCIsXCJjb3VudFwiOlwiMVwifSx7XCJ2YWx1ZVwiOlwiUDAwMzM4XCIsXCJsYWJlbFwiOlwiTC1sYWN0YXRlIGRlaHlkcm9nZW5hc2UgQSBjaGFpblwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwNzE5NVwiLFwibGFiZWxcIjpcIkwtbGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlIEIgY2hhaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJQMDY3NDRcIixcImxhYmVsXCI6XCJHbHVjb3NlLTYtcGhvc3BoYXRlIGlzb21lcmFzZVwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwNzg2NFwiLFwibGFiZWxcIjpcIkwtbGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlIEMgY2hhaW5cIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJQMzA2MTNcIixcImxhYmVsXCI6XCJQeXJ1dmF0ZSBraW5hc2UgUEtMUlwiLFwiY291bnRcIjpcIjFcIn0se1widmFsdWVcIjpcIlAwMTMwOFwiLFwibGFiZWxcIjpcIkluc3VsaW5cIixcImNvdW50XCI6XCIxXCJ9XSxcImlkXCI6XCJVTklQUk9UXCIsXCJ0b3RhbFwiOjM3MyxcImxhYmVsXCI6XCJVTklQUk9UXCJ9LHtcImZhY2V0VmFsdWVzXCI6W3tcInZhbHVlXCI6XCJDSEVCSToxMTg3OVwiLFwibGFiZWxcIjpcIkNIRUJJOjExODc5XCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUzNzhcIixcImxhYmVsXCI6XCJoeWRyb25cIixcImNvdW50XCI6XCI2XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjY1MVwiLFwibGFiZWxcIjpcIihTKS1sYWN0YXRlXCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc1NDBcIixcImxhYmVsXCI6XCJOQUQoMS0pXCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDU2MjE2XCIsXCJsYWJlbFwiOlwiQURQKDMtKVwiLFwiY291bnRcIjpcIjZcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3OTQ1XCIsXCJsYWJlbFwiOlwiTkFESCgyLSlcIixcImNvdW50XCI6XCI2XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzY0MlwiLFwibGFiZWxcIjpcImdseWNlcm9uZSBwaG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzA2MTZcIixcImxhYmVsXCI6XCJBVFAoNC0pXCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDM0NzRcIixcImxhYmVsXCI6XCJoeWRyb2dlbnBob3NwaGF0ZVwiLFwiY291bnRcIjpcIjZcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NzAyXCIsXCJsYWJlbFwiOlwicGhvc3Bob25hdG9lbm9scHlydXZhdGVcIixcImNvdW50XCI6XCI2XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODI4OVwiLFwibGFiZWxcIjpcIjItcGhvc3Bob25hdG8tRC1nbHljZXJhdGUoMy0pXCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUzNzdcIixcImxhYmVsXCI6XCJ3YXRlclwiLFwiY291bnRcIjpcIjZcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0MzE0XCIsXCJsYWJlbFwiOlwiRC1nbHVjb3NlIDYtcGhvc3BoYXRlXCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUzNjFcIixcImxhYmVsXCI6XCJweXJ1dmF0ZVwiLFwiY291bnRcIjpcIjZcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MDg0XCIsXCJsYWJlbFwiOlwiYmV0YS1ELWZydWN0b2Z1cmFub3NlIDYtcGhvc3BoYXRlXCIsXCJjb3VudFwiOlwiNlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTYwMDFcIixcImxhYmVsXCI6XCIzLXBob3NwaG8tRC1nbHljZXJveWwgZGloeWRyb2dlbiBwaG9zcGhhdGVcIixcImNvdW50XCI6XCI2XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTA1MlwiLFwibGFiZWxcIjpcIkQtZ2x5Y2VyYWxkZWh5ZGUgMy1waG9zcGhhdGVcIixcImNvdW50XCI6XCI2XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTI4OFwiLFwibGFiZWxcIjpcIkNIRUJJOjE1Mjg4XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgwOTVcIixcImxhYmVsXCI6XCJDSEVCSTo1ODA5NVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MjkyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUyOTJcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTI5NlwiLFwibGFiZWxcIjpcIkNIRUJJOjE1Mjk2XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA5NjZcIixcImxhYmVsXCI6XCJDSEVCSToxMDk2NlwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMDk0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIwOTRcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTI0NFwiLFwibGFiZWxcIjpcIkNIRUJJOjE1MjQ0XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTg2NDZcIixcImxhYmVsXCI6XCJDSEVCSToxODY0NlwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMDgzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMwODNcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzA4NVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMDg1XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA5MjVcIixcImxhYmVsXCI6XCJDSEVCSToxMDkyNVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0MDYzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQwNjNcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzA5NlwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMDk2XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTIwNTdcIixcImxhYmVsXCI6XCJDSEVCSToxMjA1N1wiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMDE4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMwMThcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzI3NFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMjc0XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjI0N1wiLFwibGFiZWxcIjpcIkNIRUJJOjIyNDdcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDM3MVwiLFwibGFiZWxcIjpcIkNIRUJJOjE0MzcxXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQzNzVcIixcImxhYmVsXCI6XCJDSEVCSToxNDM3NVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NzMwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg3MzBcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0ODMxM1wiLFwibGFiZWxcIjpcIkNIRUJJOjQ4MzEzXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzU2NDE2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzU2NDE2XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ0MzFcIixcImxhYmVsXCI6XCJDSEVCSToxNDQzMVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMTI1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIxMjVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2NjkxNFwiLFwibGFiZWxcIjpcIkNIRUJJOjY2OTE0XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxMTRcIixcImxhYmVsXCI6XCJDSEVCSToxMzExNFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0NDQ0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQ0NDRcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzAwMFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMDAwXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTExODZcIixcImxhYmVsXCI6XCJDSEVCSToxMTE4NlwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0NDU2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQ0NTZcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDY5OFwiLFwibGFiZWxcIjpcIkNIRUJJOjE0Njk4XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxMjNcIixcImxhYmVsXCI6XCJDSEVCSToxMzEyM1wiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjExMDYzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTEwNjNcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzEyMlwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTIyXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDgwMDBcIixcImxhYmVsXCI6XCJDSEVCSTo0ODAwMFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjExMTkyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTExOTJcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzM3NFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMzc0XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTExOTVcIixcImxhYmVsXCI6XCJDSEVCSToxMTE5NVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0Njk5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQ2OTlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjY5M1wiLFwibGFiZWxcIjpcIkNIRUJJOjEyNjkzXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzI5MFwiLFwibGFiZWxcIjpcIkNIRUJJOjMyOTBcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjEwMlwiLFwibGFiZWxcIjpcIkNIRUJJOjEyMTAyXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTIxMDFcIixcImxhYmVsXCI6XCJDSEVCSToxMjEwMVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMTAzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIxMDNcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODg2NlwiLFwibGFiZWxcIjpcIkNIRUJJOjU4ODY2XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDg4MjhcIixcImxhYmVsXCI6XCJDSEVCSTo0ODgyOFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NzkxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTc3OTFcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODg4NVwiLFwibGFiZWxcIjpcIkNIRUJJOjU4ODg1XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTEwMzNcIixcImxhYmVsXCI6XCJDSEVCSToxMTAzM1wiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwMDY2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTAwNjZcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDA2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjEwMDY3XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTEzOThcIixcImxhYmVsXCI6XCJDSEVCSToxMTM5OFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0Nzc5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQ3NzlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzQ0NVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzNDQ1XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODk0NFwiLFwibGFiZWxcIjpcIkNIRUJJOjg5NDRcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzczM1wiLFwibGFiZWxcIjpcIkNIRUJJOjEzNzMzXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI4ODhcIixcImxhYmVsXCI6XCJDSEVCSToxMjg4OFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzNTEwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTM1MTBcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjY1NlwiLFwibGFiZWxcIjpcIkNIRUJJOjEyNjU2XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTM3NDdcIixcImxhYmVsXCI6XCJDSEVCSToxMzc0N1wiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzNTA4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTM1MDhcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDQ5M1wiLFwibGFiZWxcIjpcIkNIRUJJOjEwNDkzXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA0OTRcIixcImxhYmVsXCI6XCJDSEVCSToxMDQ5NFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNDk1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA0OTVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDk3MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE0OTcyXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI0MzFcIixcImxhYmVsXCI6XCJDSEVCSToxMjQzMVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNDkxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA0OTFcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDQ5MlwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNDkyXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTAyNDZcIixcImxhYmVsXCI6XCJDSEVCSToxMDI0NlwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNDg5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA0ODlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MTcyM1wiLFwibGFiZWxcIjpcIkNIRUJJOjYxNzIzXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE0NzRcIixcImxhYmVsXCI6XCJDSEVCSToxMTQ3NFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNjg0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI2ODRcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDQ5N1wiLFwibGFiZWxcIjpcIkNIRUJJOjEwNDk3XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA0OTlcIixcImxhYmVsXCI6XCJDSEVCSToxMDQ5OVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMzE2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIzMTZcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMTYzNlwiLFwibGFiZWxcIjpcIkNIRUJJOjExNjM2XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ5MDRcIixcImxhYmVsXCI6XCJDSEVCSToxNDkwNFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE5MjUwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTkyNTBcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzYwOVwiLFwibGFiZWxcIjpcIkNIRUJJOjU3NjA5XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE4ODZcIixcImxhYmVsXCI6XCJDSEVCSToxMTg4NlwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNDA4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE0MDhcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjYyM1wiLFwibGFiZWxcIjpcIkNIRUJJOjEyNjIzXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI2MzVcIixcImxhYmVsXCI6XCJDSEVCSToxMjYzNVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MDgzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUwODNcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDc0NVwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNzQ1XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA4NjZcIixcImxhYmVsXCI6XCJDSEVCSToxMDg2NlwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNTAwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA1MDBcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2ODYyM1wiLFwibGFiZWxcIjpcIkNIRUJJOjY4NjIzXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA4ODhcIixcImxhYmVsXCI6XCJDSEVCSToxMDg4OFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNzM2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE3MzZcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDUyOVwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNTI5XCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA1MzBcIixcImxhYmVsXCI6XCJDSEVCSToxMDUzMFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNTMxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA1MzFcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDUzMlwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNTMyXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA1MzNcIixcImxhYmVsXCI6XCJDSEVCSToxMDUzM1wiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NTAxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTc1MDFcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MDA1MlwiLFwibGFiZWxcIjpcInNpcm9oZW1lKDgtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NjkyXCIsXCJsYWJlbFwiOlwiRkFEKDMtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5MDM2XCIsXCJsYWJlbFwiOlwiY29wcGVyKDIrKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ2OTExXCIsXCJsYWJlbFwiOlwiTC1vcm5pdGhpbml1bSgxKylcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODM1OVwiLFwibGFiZWxcIjpcIkwtZ2x1dGFtaW5lIHp3aXR0ZXJpb25cIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1OTc4OVwiLFwibGFiZWxcIjpcIlMtYWRlbm9zeWwtTC1tZXRoaW9uaW5lIHp3aXR0ZXJpb25cIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1Nzk3MlwiLFwibGFiZWxcIjpcIkwtYWxhbmluZSB6d2l0dGVyaW9uXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzI1NDRcIixcImxhYmVsXCI6XCJuaWNvdGluYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc3NjJcIixcImxhYmVsXCI6XCJMLXZhbGluZSB6d2l0dGVyaW9uXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc4NTZcIixcImxhYmVsXCI6XCJTLWFkZW5vc3lsLUwtaG9tb2N5c3RlaW5lIHp3aXR0ZXJpb25cIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjg5MlwiLFwibGFiZWxcIjpcIjQtYW1pbm8tNS1oeWRyb3h5bWV0aHlsLTItbWV0aHlscHlyaW1pZGluZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMzU2XCIsXCJsYWJlbFwiOlwiQ0hFQklfMTMzNTZcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzUzNVwiLFwibGFiZWxcIjpcIjQtcGhvc3Bob25hdG8tTC1hc3BhcnRpYyBhY2lkKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NzYyXCIsXCJsYWJlbFwiOlwiNyw4LWRpaHlkcm9uZW9wdGVyaW4gMyctcGhvc3BoYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NDM3XCIsXCJsYWJlbFwiOlwiZGVhbWlkby1OQUQoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc4NDFcIixcImxhYmVsXCI6XCI0LWFtaW5vLTItbWV0aHlsLTUtZGlwaG9zcGhvbmF0b294eW1ldGh5bHB5cmltaWRpbmUoMy0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg5MzdcIixcImxhYmVsXCI6XCJ0aGlhbWluZSgxKykgZGlwaG9zcGhhdGUoMy0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE3OFwiLFwibGFiZWxcIjpcIigyUyktMi1pc29wcm9weWxtYWxhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzYyNjRcIixcImxhYmVsXCI6XCJtb2x5YmRhdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDA0OVwiLFwibGFiZWxcIjpcIlhUUFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3NDk3XCIsXCJsYWJlbFwiOlwiZ2x5Y29saWMgYWNpZFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1NTM3XCIsXCJsYWJlbFwiOlwicGhlbnlsYWNldHlsLUNvQVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ5Nzg2XCIsXCJsYWJlbFwiOlwibmlja2VsKDIrKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5ODg4XCIsXCJsYWJlbFwiOlwiZGlwaG9zcGhvcmljIGFjaWRcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzgzNlwiLFwibGFiZWxcIjpcIjQtYW1pbm9iZW56b2F0ZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MDMzXCIsXCJsYWJlbFwiOlwidGhpb3JlZG94aW5cIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1Nzk2N1wiLFwibGFiZWxcIjpcIkFEUC1ELXJpYm9zZSgyLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1OTczMjZcIixcImxhYmVsXCI6XCJweXJpZG94YWwgNSctcGhvc3BoYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3MTU0XCIsXCJsYWJlbFwiOlwibmljb3RpbmFtaWRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcwMDFcIixcImxhYmVsXCI6XCI3LDgtZGloeWRyb25lb3B0ZXJpblwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5MTA1XCIsXCJsYWJlbFwiOlwiemluYygyKylcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNzczN1wiLFwibGFiZWxcIjpcIjUtcGhvc3Boby1iZXRhLUQtcmlib3N5bGFtaW5lXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTgzNzJcIixcImxhYmVsXCI6XCI3LDgtZGloeWRyb25lb3B0ZXJpbiAzJy10cmlwaG9zcGhhdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjk2MlwiLFwibGFiZWxcIjpcIkQtZ2x1Y29zYW1pbmUgNi1waG9zcGhhdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDk4NlwiLFwibGFiZWxcIjpcIihSKS00Jy1waG9zcGhvbmF0b3BhbnRvdGhlbmF0ZSgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTEwM1wiLFwibGFiZWxcIjpcInBvdGFzc2l1bSgxKylcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNzU3NVwiLFwibGFiZWxcIjpcInRoaWFtaW5lKDErKSBtb25vcGhvc3BoYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU5NDU4XCIsXCJsYWJlbFwiOlwiTi1bKFIpLTQtcGhvc3Bob25hdG9wYW50b3RoZW5veWxdLUwtY3lzdGVpbmF0ZSgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNzU2NVwiLFwibGFiZWxcIjpcIkdUUCg0LSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjY1OVwiLFwibGFiZWxcIjpcIkQtZ2x5Y2VyYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTczMjhcIixcImxhYmVsXCI6XCIzJy1kZXBob3NwaG8tQ29BKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0NjQ5XCIsXCJsYWJlbFwiOlwiTk1OKC0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY4NDJcIixcImxhYmVsXCI6XCJmb3JtYWxkZWh5ZGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODQyMFwiLFwibGFiZWxcIjpcIm1hZ25lc2l1bSgyKylcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODIxMFwiLFwibGFiZWxcIjpcIkZNTigzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzU4NlwiLFwibGFiZWxcIjpcImJpb3RpbmF0ZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1ODExXCIsXCJsYWJlbFwiOlwiTy1waG9zcGhvLUwtc2VyaW5lXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTczMzhcIixcImxhYmVsXCI6XCJpc29idXR5cnlsLUNvQSg0LSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1Nzg0NVwiLFwibGFiZWxcIjpcInByZXVyb3BvcnBoeXJpbm9nZW4oOC0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc3MDVcIixcImxhYmVsXCI6XCJVRFAtTi1hY2V0eWwtYWxwaGEtRC1nbHVjb3NhbWluZSgyLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzE1OFwiLFwibGFiZWxcIjpcIm1ldGh5bGdseW94YWxcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3MTI1XCIsXCJsYWJlbFwiOlwiMi1hY2V0YW1pZG8tMi1kZW94eS1ELWdsdWNvcHlyYW5vc2UgMS1waG9zcGhhdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTAzM1wiLFwibGFiZWxcIjpcImlyb24oMispXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc1MDJcIixcImxhYmVsXCI6XCJuaWNvdGluYXRlIEQtcmlib251Y2xlb3RpZGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzM1MzlcIixcImxhYmVsXCI6XCJoeWRyb3hpZG9kaW94aWRvc3VsZmlkb3N1bGZhdGUoMS0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTgwMTJcIixcImxhYmVsXCI6XCJmdW1hcmljIGFjaWRcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTc0MVwiLFwibGFiZWxcIjpcInN1Y2NpbmljIGFjaWRcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTEwMVwiLFwibGFiZWxcIjpcInNvZGl1bSgxKylcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjgxMFwiLFwibGFiZWxcIjpcIjItb3hvZ2x1dGFyYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NTI1XCIsXCJsYWJlbFwiOlwiNS1bKDUtcGhvc3Boby0xLWRlb3h5LUQtcmlidWxvcy0xLXlsaW1pbm8pbWV0aHlsYW1pbm9dLTEtKDUtcGhvc3Boby1iZXRhLUQtcmlib3N5bClpbWlkYXpvbGUtNC1jYXJib3hhbWlkZSg0LSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzQ1MVwiLFwibGFiZWxcIjpcImRpaHlkcm9mb2xhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg0MjFcIixcImxhYmVsXCI6XCI1LWFtaW5vLTYtKDUtcGhvc3Bob3JpYml0eWxhbWlubyl1cmFjaWwoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc3ODNcIixcImxhYmVsXCI6XCJOQURQSCg0LSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzY3M1wiLFwibGFiZWxcIjpcIjInLWRlb3h5Z3Vhbm9zaW5lIDUnLW1vbm9waG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDU2MjE1XCIsXCJsYWJlbFwiOlwiYWRlbm9zaW5lIDUnLW1vbm9waG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgwODVcIixcImxhYmVsXCI6XCIyLWlzb3Byb3B5bG1hbGVhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTA2MDZcIixcImxhYmVsXCI6XCIyLWh5ZHJveHktMy1veG9idXR5bCBwaG9zcGhhdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODE5MVwiLFwibGFiZWxcIjpcInRoaW9yZWRveGluIGRpc3VsZmlkZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3NTc2M1wiLFwibGFiZWxcIjpcIjItdHJhbnMsNi10cmFucy1mYXJuZXN5bCBkaXBob3NwaGF0ZSgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODQzM1wiLFwibGFiZWxcIjpcIjFELW15by1pbm9zaXRvbCAxLXBob3NwaGF0ZSgyLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTkzNFwiLFwibGFiZWxcIjpcIjUtYW1pbm8tNi0oRC1yaWJpdHlsYW1pbm8pdXJhY2lsXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc0NzJcIixcImxhYmVsXCI6XCIoTihvbWVnYSktTC1hcmdpbmlubylzdWNjaW5hdGUoMS0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg0NDNcIixcImxhYmVsXCI6XCJTQUlDQVIoNC0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgyMDFcIixcImxhYmVsXCI6XCI2LDctZGltZXRoeWwtOC0oMS1ELXJpYml0eWwpbHVtYXppbmUoMS0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc1OTdcIixcImxhYmVsXCI6XCJzbi1nbHljZXJvbCAzLXBob3NwaGF0ZSgyLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODQzNVwiLFwibGFiZWxcIjpcIjEtKDUtcGhvc3Boby1iZXRhLUQtcmlib3N5bCktNS1bKDUtcGhvc3Boby1iZXRhLUQtcmlib3N5bGFtaW5vKW1ldGh5bGlkZW5lYW1pbm9daW1pZGF6b2xlLTQtY2FyYm94YW1pZGUoNC0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTcyODZcIixcImxhYmVsXCI6XCJhY2V0b2FjZXR5bC1Db0EoNC0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTcyODhcIixcImxhYmVsXCI6XCJhY2V0eWwtQ29BKDQtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3Mjg3XCIsXCJsYWJlbFwiOlwiY29lbnp5bWUgQSg0LSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTE2N1wiLFwibGFiZWxcIjpcInRSTkEoQ3lzKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjM3NTYzXCIsXCJsYWJlbFwiOlwiQ1RQKDQtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5MTc3XCIsXCJsYWJlbFwiOlwidFJOQShQcm8pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgwMTdcIixcImxhYmVsXCI6XCI1LU8tcGhvc3Bob25hdG8tYWxwaGEtRC1yaWJvZnVyYW5vc3lsIGRpcGhvc3BoYXRlKDUtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MTE1XCIsXCJsYWJlbFwiOlwiZ3Vhbm9zaW5lIDUnLW1vbm9waG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTU1ODlcIixcImxhYmVsXCI6XCIoUyktbWFsYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyODc2OVwiLFwibGFiZWxcIjpcImlzb3BlbnRlbnlsIGRpcGhvc3BoYXRlKDMtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MzQ5XCIsXCJsYWJlbFwiOlwiTkFEUCgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODIyOFwiLFwibGFiZWxcIjpcImNhcmJhbW95bCBwaG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc5MzZcIixcImxhYmVsXCI6XCJOLWFjZXR5bC1MLWdhbW1hLWdsdXRhbXlsIHBob3NwaGF0ZSgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODI0NVwiLFwibGFiZWxcIjpcIjInLWRlb3h5YWRlbm9zaW5lIDUnLW1vbm9waG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzUxMjFcIixcImxhYmVsXCI6XCIoMlIsM1MpLTMtaXNvcHJvcHlsbWFsYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MDU3XCIsXCJsYWJlbFwiOlwiZ2VyYW55bCBkaXBob3NwaGF0ZSgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODExMFwiLFwibGFiZWxcIjpcIjMtcGhvc3Bob25hdG9veHlweXJ1dmF0ZSgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODE4OVwiLFwibGFiZWxcIjpcIkdEUCgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODA2OVwiLFwibGFiZWxcIjpcIkNEUCgzLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMTg1MVwiLFwibGFiZWxcIjpcIjMtbWV0aHlsLTItb3hvYnV0YW5vYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjkxNTRcIixcImxhYmVsXCI6XCJQcm8tdFJOQShQcm8pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc5ODZcIixcImxhYmVsXCI6XCJyaWJvZmxhdmluKDEtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NjIzXCIsXCJsYWJlbFwiOlwicHJlbnlsIGRpcGhvc3BoYXRlKDMtKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MjU1XCIsXCJsYWJlbFwiOlwiTC1oaXN0aWRpbm9sXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTYxMzRcIixcImxhYmVsXCI6XCJhbW1vbmlhXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcxMTVcIixcImxhYmVsXCI6XCJMLXNlcmluZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1OTcxXCIsXCJsYWJlbFwiOlwiTC1oaXN0aWRpbmVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTk4MFwiLFwibGFiZWxcIjpcIihSKS1wYW50b2F0ZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2ODI4XCIsXCJsYWJlbFwiOlwiTC10cnlwdG9waGFuXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzI4MTRcIixcImxhYmVsXCI6XCJOLWNhcmJhbW95bC1MLWFzcGFydGF0ZSgyLSlcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzI1NlwiLFwibGFiZWxcIjpcIjInLWRlb3h5YWRlbm9zaW5lXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY5NThcIixcImxhYmVsXCI6XCJiZXRhLWFsYW5pbmVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzgwOFwiLFwibGFiZWxcIjpcIklEUFwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2NjkxXCIsXCJsYWJlbFwiOlwiZGV0aGlvYmlvdGluXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE1NjFcIixcImxhYmVsXCI6XCIyLWRlaHlkcm9wYW50b2F0ZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2NDUyXCIsXCJsYWJlbFwiOlwib3hhbG9hY2V0YXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MzM1XCIsXCJsYWJlbFwiOlwiYWRlbm9zaW5lXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY0NjdcIixcImxhYmVsXCI6XCJMLWFyZ2luaW5lXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUzODBcIixcImxhYmVsXCI6XCJzdWNjaW55bC1Db0FcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzA4NlwiLFwibGFiZWxcIjpcIkwtYXNwYXJ0YXRlIDQtc2VtaWFsZGVoeWRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcyMDNcIixcImxhYmVsXCI6XCJMLXByb2xpbmVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjM0OVwiLFwibGFiZWxcIjpcIkwtY2l0cnVsbGluZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3ODY1XCIsXCJsYWJlbFwiOlwiNC1tZXRoeWwtMi1veG9wZW50YW5vYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzYyNDJcIixcImxhYmVsXCI6XCIzLSg0LWh5ZHJveHlwaGVueWwpcHlydXZhdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjUyNlwiLFwibGFiZWxcIjpcImNhcmJvbiBkaW94aWRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcxOTFcIixcImxhYmVsXCI6XCJMLWlzb2xldWNpbmVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjMwMFwiLFwibGFiZWxcIjpcImFscGhhLUQtcmlib3NlIDEtcGhvc3BoYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTU2OThcIixcImxhYmVsXCI6XCIyJy1kZW94eWN5dGlkaW5lXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY1NDNcIixcImxhYmVsXCI6XCJOKDIpLWFjZXR5bC1MLW9ybml0aGluZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5OTg1XCIsXCJsYWJlbFwiOlwiTC1nbHV0YW1hdGUoMS0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Mjk3NDhcIixcImxhYmVsXCI6XCJjaG9yaXNtYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3ODk1XCIsXCJsYWJlbFwiOlwiTC10eXJvc2luZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2NTY3XCIsXCJsYWJlbFwiOlwiYW50aHJhbmlsYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzIzNjRcIixcImxhYmVsXCI6XCIzLWRlaHlkcm9xdWluYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcyNjhcIixcImxhYmVsXCI6XCJteW8taW5vc2l0b2xcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDMzN1wiLFwibGFiZWxcIjpcIk4tYWNldHlsLUwtZ2x1dGFtYXRlKDItKVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5MTIzXCIsXCJsYWJlbFwiOlwiMi1hY2V0YW1pZG8tNS1veG9wZW50YW5vYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTIyNjZcIixcImxhYmVsXCI6XCI4LWFtaW5vLTctb3hvbm9uYW5vYXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Mjk5MzRcIixcImxhYmVsXCI6XCIoMXMsNHMpLXByZXBoZW5hdGUoMi0pXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY2MzBcIixcImxhYmVsXCI6XCIzLWRlaHlkcm9zaGlraW1hdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjk5NlwiLFwibGFiZWxcIjpcIkwtaGlzdGlkaW5vbCBwaG9zcGhhdGVcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODAxOVwiLFwibGFiZWxcIjpcIkwtbHlzaW5lXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcxNzJcIixcImxhYmVsXCI6XCIyJy1kZW94eWd1YW5vc2luZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1NDI4XCIsXCJsYWJlbFwiOlwiZ2x5Y2luZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5MTA4XCIsXCJsYWJlbFwiOlwiY2FsY2l1bSgyKylcIixcImNvdW50XCI6XCI1XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTM0M1wiLFwibGFiZWxcIjpcImFjZXRhbGRlaHlkZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2NTUxXCIsXCJsYWJlbFwiOlwiYWxwaGEsYWxwaGEtdHJlaGFsb3NlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzAwODlcIixcImxhYmVsXCI6XCJhY2V0YXRlXCIsXCJjb3VudFwiOlwiNVwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTYyNDBcIixcImxhYmVsXCI6XCJoeWRyb2dlbiBwZXJveGlkZVwiLFwiY291bnRcIjpcIjVcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxMDM2XCIsXCJsYWJlbFwiOlwiNy1jYXJib3h5bGF0by03LWRlYXphZ3VhbmluZVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxMDMyXCIsXCJsYWJlbFwiOlwiNSw2LDcsOC10ZXRyYWh5ZHJvcHRlcmluLTYtY2FyYm94eWxhdGVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NTA3NVwiLFwibGFiZWxcIjpcIjctY3lhbm8tNy1kZWF6YWd1YW5pbmVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjc4MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE2NzgyXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI3MjdcIixcImxhYmVsXCI6XCJDSEVCSTo2MjcyN1wiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjYwNDg3XCIsXCJsYWJlbFwiOlwiYWRlbm9zeWxjb2JpbmFtaWRlIGd1YW5vc3lsIGRpcGhvc3BoYXRlKDEtKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjI3OTM3XCIsXCJsYWJlbFwiOlwiY29iKElJKXlyaW5pYyBhY2lkIGEsYyBkaWFtaWRlXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg1MDJcIixcImxhYmVsXCI6XCJhZGVub3N5bGNvYmluYW1pZGUgcGhvc3BoYXRlKDEtKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NTA0XCIsXCJsYWJlbFwiOlwiYWRlbm9zeWxjb2J5cmF0ZVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMDAzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIwMDNcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzMyOVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMzI5XCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMzMjhcIixcImxhYmVsXCI6XCJDSEVCSToxMzMyOFwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4Njk4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTg2OThcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjc2OFwiLFwibGFiZWxcIjpcIkNIRUJJXzEyNzY4XCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI4MzBcIixcImxhYmVsXCI6XCJDSEVCSTo2MjgzMFwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjIzMzI5XCIsXCJsYWJlbFwiOlwiQ0hFQklfMjMzMjlcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODU2M1wiLFwibGFiZWxcIjpcIihSKS0xLWFtbW9uaW9wcm9wYW4tMi15bCBwaG9zcGhhdGUoMS0pXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTAyNjFcIixcImxhYmVsXCI6XCJDSEVCSToxMDI2MVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjYyMjg2XCIsXCJsYWJlbFwiOlwiMictZGVveHlpbm9zaW5lIDUnLWRpcGhvc3BoYXRlKDMtKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNjUyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE2NTJcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3MzA4M1wiLFwibGFiZWxcIjpcIig3LDgtZGloeWRyb3B0ZXJpbi02LXlsKW1ldGh5bCBkaXBob3NwaGF0ZVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNjczXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE2NzNcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NjUwMlwiLFwibGFiZWxcIjpcInR1bmdzdGF0ZVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MzUxXCIsXCJsYWJlbFwiOlwic2lyb2h5ZHJvY2hsb3Jpbig4LSlcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzM2MFwiLFwibGFiZWxcIjpcInBpbWVsb3lsLUNvQSg1LSlcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODc1NlwiLFwibGFiZWxcIjpcIjItdHJhbnMsNi10cmFucywxMC10cmFucy1nZXJhbnlsZ2VyYW55bCBkaXBob3NwaGF0ZSgzLSlcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzQ3N1wiLFwibGFiZWxcIjpcImRURFAtYWxwaGEtRC1nbHVjb3NlKDItKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NjQ5XCIsXCJsYWJlbFwiOlwiZFREUC00LWRlaHlkcm8tNi1kZW94eS1hbHBoYS1ELWdsdWNvc2UoMi0pXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc1MTBcIixcImxhYmVsXCI6XCJkVERQLTYtZGVveHktYmV0YS1MLW1hbm5vc2UoMi0pXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY0MTFcIixcImxhYmVsXCI6XCJpbmRvbGUtMy1hY2V0aWMgYWNpZFwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MzU0XCIsXCJsYWJlbFwiOlwiNC1hbWluby0yLW1ldGh5bC01LXBob3NwaG9uYXRvb3h5bWV0aHlscHlyaW1pZGluZSgyLSlcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODgyN1wiLFwibGFiZWxcIjpcInByZWNvcnJpbi0yKDctKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4ODMxXCIsXCJsYWJlbFwiOlwiaW1pbm9hc3BhcnRhdGVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozMDg1MVwiLFwibGFiZWxcIjpcImtldG8tcGhlbnlscHlydXZpYyBhY2lkXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjkxMTRcIixcImxhYmVsXCI6XCIyLDUtZGlhbWluby00LWh5ZHJveHktNi0oNS1waG9zcGhvcmlib3N5bGFtaW5vKXB5cmltaWRpbmVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTk1OVwiLFwibGFiZWxcIjpcInF1aW5vbGluYXRlKDItKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NTI3XCIsXCJsYWJlbFwiOlwiRC1yaWJvc3lsbmljb3RpbmF0ZVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1OTI3XCIsXCJsYWJlbFwiOlwiTi1yaWJvc3lsbmljb3RpbmFtaWRlXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTYxMzJcIixcImxhYmVsXCI6XCIyLWRlb3h5LUQtcmlib3NlIDUtcGhvc3BoYXRlXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcyNDVcIixcImxhYmVsXCI6XCJjYXJib24gbW9ub3hpZGVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODQwN1wiLFwibGFiZWxcIjpcImh5ZHJvZ2VuIGN5YW5pZGVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODUzM1wiLFwibGFiZWxcIjpcIlMtbWV0aHlsLTUtdGhpby1hbHBoYS1ELXJpYm9zZSAxLXBob3NwaGF0ZSgyLSlcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODAyMlwiLFwibGFiZWxcIjpcInRoaW9jeWFuYXRlXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc0OTlcIixcImxhYmVsXCI6XCIyLW94b2FkaXBhdGUoMi0pXCIsXCJjb3VudFwiOlwiNFwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcyMTRcIixcImxhYmVsXCI6XCIoMlMpLTItaXNvcHJvcHlsLTMtb3hvc3VjY2luYXRlKDItKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MTQ2XCIsXCJsYWJlbFwiOlwiKFIpLTUtcGhvc3Bob25hdG9tZXZhbG9uYXRlKDMtKVwiLFwiY291bnRcIjpcIjRcIn0se1widmFsdWVcIjpcIkNIRUJJOjM2NDY0XCIsXCJsYWJlbFwiOlwiKFIpLW1ldmFsb25hdGVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODI3NlwiLFwibGFiZWxcIjpcImRpaHlkcm9nZW5cIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODQyMVwiLFwibGFiZWxcIjpcInN1cGVyb3hpZGVcIixcImNvdW50XCI6XCI0XCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODMyNFwiLFwibGFiZWxcIjpcIkNIRUJJOjE4MzI0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA4NDFcIixcImxhYmVsXCI6XCJDSEVCSToxMDg0MVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NzgzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg3ODNcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDMzM1wiLFwibGFiZWxcIjpcIkNIRUJJOjE0MzMzXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjM1OVwiLFwibGFiZWxcIjpcIkNIRUJJOjIzNTlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDM0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE0MzQyXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQzNDFcIixcImxhYmVsXCI6XCJDSEVCSToxNDM0MVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU5Nzc2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTk3NzZcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDgxMlwiLFwibGFiZWxcIjpcIkNIRUJJOjE0ODEyXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjYwNTRcIixcImxhYmVsXCI6XCJDSEVCSToyNjA1NFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjI2MDU1XCIsXCJsYWJlbFwiOlwiQ0hFQklfMjYwNTVcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4MTQ3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODE0N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjI2NDY2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjY0NjZcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNjQ2MlwiLFwibGFiZWxcIjpcIkNIRUJJOjI2NDYyXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ5ODdcIixcImxhYmVsXCI6XCJDSEVCSToxNDk4N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMzUyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIzNTJcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NDQ2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTQ0NlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU0NTRcIixcImxhYmVsXCI6XCJDSEVCSTo1NDU0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDUyNTNcIixcImxhYmVsXCI6XCJDSEVCSTo0NTI1M1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjExODgwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE4ODBcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMTg4MVwiLFwibGFiZWxcIjpcIkNIRUJJOjExODgxXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY1OFwiLFwibGFiZWxcIjpcIkNIRUJJOjE2NThcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjU3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTY1N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjM5ODY4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6Mzk4NjhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNDM1NVwiLFwibGFiZWxcIjpcIkNIRUJJOjI0MzU1XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTEwNjVcIixcImxhYmVsXCI6XCJDSEVCSToxMTA2NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMjM2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMyMzZcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjY3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI2N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyMjQ5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjIyNDlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MDkzOFwiLFwibGFiZWxcIjpcIkNIRUJJOjQwOTM4XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI0MTFcIixcImxhYmVsXCI6XCJDSEVCSToxMjQxMVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjM3NzM2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6Mzc3MzZcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDg5NFwiLFwibGFiZWxcIjpcIkNIRUJJOjQ0ODk0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODY4NVwiLFwibGFiZWxcIjpcIkNIRUJJOjg2ODVcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMzc5NThcIixcImxhYmVsXCI6XCJDSEVCSToyMzc5NThcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDk5OVwiLFwibGFiZWxcIjpcIkNIRUJJOjIwOTk5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Mzk1NzFcIixcImxhYmVsXCI6XCJDSEVCSTozOTU3MVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMzUyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMzNTJcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDE4OVwiLFwibGFiZWxcIjpcIkNIRUJJOjIwMTg5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTgxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTgxXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTAzNzVcIixcImxhYmVsXCI6XCJDSEVCSToxMDM3NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjIxMDI4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjEwMjhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMTAyOVwiLFwibGFiZWxcIjpcIkNIRUJJOjIxMDI5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjEwMjZcIixcImxhYmVsXCI6XCJDSEVCSToyMTAyNlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyNzY4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjI3NjhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDc4OVwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNzg5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI5NjVcIixcImxhYmVsXCI6XCJDSEVCSToxMjk2NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNjUxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE2NTFcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjk4NlwiLFwibGFiZWxcIjpcIkNIRUJJOjEyOTg2XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI5ODRcIixcImxhYmVsXCI6XCJDSEVCSToxMjk4NFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyOTgzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI5ODNcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjk4N1wiLFwibGFiZWxcIjpcIkNIRUJJOjEyOTg3XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA3NDNcIixcImxhYmVsXCI6XCJDSEVCSToxMDc0M1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjQyMzc4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDIzNzhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MDA0OVwiLFwibGFiZWxcIjpcIkNIRUJJOjYwMDQ5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTM3NDA0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTM3NDA0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcxMTRcIixcImxhYmVsXCI6XCJDSEVCSToxNzExNFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjYwMDYxXCIsXCJsYWJlbFwiOlwiY29iYWx0LXByZWNvcnJpbi00KDctKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjYwMDYyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjAwNjJcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MDA2MFwiLFwibGFiZWxcIjpcImNvYmFsdC1wcmVjb3JyaW4tMyg4LSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4NTEwNlwiLFwibGFiZWxcIjpcIkNIRUJJOjg1MTA2XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzUwOThcIixcImxhYmVsXCI6XCJDSEVCSTozNTA5OFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjMzOTA3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzM5MDdcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzAxMFwiLFwibGFiZWxcIjpcIkNIRUJJOjE3MDEwXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjgyNjVcIixcImxhYmVsXCI6XCJDSEVCSToyODI2NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjc1NDM0XCIsXCJsYWJlbFwiOlwicGh5dHlsIGRpcGhvc3BoYXRlKDMtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3OTIyXCIsXCJsYWJlbFwiOlwiY29lbnp5bWUgZ2FtbWEtRjQyMC0yKDUtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjYwMDYzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjAwNjNcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MDM1NlwiLFwibGFiZWxcIjpcIkNIRUJJOjQwMzU2XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjAwNjRcIixcImxhYmVsXCI6XCJDSEVCSTo2MDA2NFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxMjk0XCIsXCJsYWJlbFwiOlwiKFIpLTQtcGhvc3Bob25hdG9wYW50b2F0ZSgzLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3NDAwN1wiLFwibGFiZWxcIjpcIkNIRUJJOjc0MDA3XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NzQwMDRcIixcImxhYmVsXCI6XCJDSEVCSTo3NDAwNFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjY0ODAyXCIsXCJsYWJlbFwiOlwiTC1oaXN0aWRpbmFsKDErKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMzg4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMzODhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMjUxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjI1MVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjMwOTM0XCIsXCJsYWJlbFwiOlwiRC1jaXRyYW1hbGF0ZSgyLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3Mjc3OFwiLFwibGFiZWxcIjpcIkNIRUJJOjcyNzc4XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NzI3NzdcIixcImxhYmVsXCI6XCJDSEVCSTo3Mjc3N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjI3Nzg3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6Mjc3ODdcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3MTMxMFwiLFwibGFiZWxcIjpcIkNIRUJJOjcxMzEwXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg1OTZcIixcImxhYmVsXCI6XCJDSEVCSTo1ODU5NlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwOTI0XCIsXCJsYWJlbFwiOlwiQ0hFQklfMjA5MjRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3NDg1M1wiLFwibGFiZWxcIjpcIkNIRUJJOjc0ODUzXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Mjg3NzNcIixcImxhYmVsXCI6XCJDSEVCSToyODc3M1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3MjU4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTcyNThcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODI0NlwiLFwibGFiZWxcIjpcIkNIRUJJOjU4MjQ2XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODM0MzFcIixcImxhYmVsXCI6XCJDSEVCSTo4MzQzMVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MTE2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTgxMTZcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2Mjc4NFwiLFwibGFiZWxcIjpcIkNIRUJJOjYyNzg0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTM0MTc2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTM0MTc2XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI3ODZcIixcImxhYmVsXCI6XCJDSEVCSTo2Mjc4NlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjcyNjk4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzI2OThcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNzg5XCIsXCJsYWJlbFwiOlwiY29iYWx0LXByZWNvcnJpbi02QlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjcyNjk0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzI2OTRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2NjkxNVwiLFwibGFiZWxcIjpcIlVEUC1hbHBoYS1ELWdhbGFjdG9mdXJhbm9zZSgyLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzc2OTZcIixcImxhYmVsXCI6XCJDSEVCSToxMzc2OTZcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MDQ2NFwiLFwibGFiZWxcIjpcIkNIRUJJOjYwNDY0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgxNTFcIixcImxhYmVsXCI6XCJDSEVCSTo1ODE1MVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjMwNzE5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzA3MTlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3MzY4MlwiLFwibGFiZWxcIjpcIkNIRUJJOjczNjgyXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgyODZcIixcImxhYmVsXCI6XCJDSEVCSTo1ODI4NlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjYyODkwXCIsXCJsYWJlbFwiOlwiMi0oMi1jYXJib3h5LTQtbWV0aHlsdGhpYXpvbC01LXlsKWV0aHlsIHBob3NwaGF0ZSgzLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzc2NzhcIixcImxhYmVsXCI6XCJDSEVCSToxMzc2NzhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNzk1XCIsXCJsYWJlbFwiOlwiY29iYWx0LXByZWNvcnJpbi04XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTU0MjlcIixcImxhYmVsXCI6XCJoeWRyb3h5bGFtaW5lXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI1Njg0XCIsXCJsYWJlbFwiOlwiKDJTLDRSLDVTLDZSKS02LShoeWRyb3h5bWV0aHlsKW94YW5lLTIsNCw1LXRyaW9sXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDg1OTVcIixcImxhYmVsXCI6XCJDSEVCSTo0ODU5NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4ODkwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTg4OTBcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODQxMVwiLFwibGFiZWxcIjpcIkNIRUJJOjU4NDExXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc2NzdcIixcImxhYmVsXCI6XCJDSEVCSTo1NzY3N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0ODg1XCIsXCJsYWJlbFwiOlwicHJlcGh5dG9lbmUgZGlwaG9zcGhhdGVcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzkxNTFcIixcImxhYmVsXCI6XCJDSEVCSToxMzkxNTFcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2NTA3OFwiLFwibGFiZWxcIjpcIkNIRUJJOjY1MDc4XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjQwNzRcIixcImxhYmVsXCI6XCIoUyktTkFESFgoMi0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjQwNzVcIixcImxhYmVsXCI6XCIoUiktTkFESFgoMi0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjQwNzZcIixcImxhYmVsXCI6XCJDSEVCSTo2NDA3NlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjY0MDc3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjQwNzdcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3MjcxM1wiLFwibGFiZWxcIjpcIkNIRUJJOjcyNzEzXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NzI3MTJcIixcImxhYmVsXCI6XCJDSEVCSTo3MjcxMlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjcyNzEwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzI3MTBcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3Mjk1MVwiLFwibGFiZWxcIjpcIkNIRUJJOjcyOTUxXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg1MDNcIixcImxhYmVsXCI6XCJDSEVCSTo1ODUwM1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4ODg0XCIsXCJsYWJlbFwiOlwiKDJSKS1ob21vY2l0cmF0ZSgzLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4ODA1NFwiLFwibGFiZWxcIjpcIkNIRUJJOjg4MDU0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODgwNTVcIixcImxhYmVsXCI6XCJDSEVCSTo4ODA1NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjcyNzAxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzI3MDFcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNzk3M1wiLFwibGFiZWxcIjpcIkNIRUJJOjI3OTczXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgxMDNcIixcImxhYmVsXCI6XCI1LDYsNyw4LXRldHJhaHlkcm9tZXRoYW5vcHRlcmluKDMtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU5NDM1XCIsXCJsYWJlbFwiOlwiTC1sYWN0eWwtMi1kaXBob3NwaG8tNSctZ3Vhbm9zaW5lKDMtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjcxNTE3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzE1MTdcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODMzN1wiLFwibGFiZWxcIjpcIjUsMTAtKG1ldGhhbnlseWxpZGVuZSl0ZXRyYWh5ZHJvbWV0aGFub3B0ZXJpbigyLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4Mjc2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjgyNzY3XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODA1ODdcIixcImxhYmVsXCI6XCJDSEVCSTo4MDU4N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjY3MTM5XCIsXCJsYWJlbFwiOlwiKDJTLDRTKS00LWh5ZHJveHktMiwzLDQsNS10ZXRyYWh5ZHJvZGlwaWNvbGluYXRlKDItKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjY3MTM4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjcxMzhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMzQ0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjIzNDQyXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODM0MDdcIixcImxhYmVsXCI6XCJDSEVCSTo4MzQwN1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjcwNzkxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzA3OTFcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODU1NlwiLFwibGFiZWxcIjpcIkNIRUJJOjU4NTU2XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgzMTlcIixcImxhYmVsXCI6XCJDSEVCSTo1ODMxOVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjc5OTcyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6Nzk5NzJcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NTUwNlwiLFwibGFiZWxcIjpcImFscGhhLUQtcmlib3NlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTk5MDZcIixcImxhYmVsXCI6XCIyLXBob3NwaG9uYXRvLUwtbGFjdGF0ZSgzLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1OTkwN1wiLFwibGFiZWxcIjpcIkY0MjAtMCgzLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1Nzg0NlwiLFwibGFiZWxcIjpcIkwtZnVjdWxvc2UgMS1waG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzQ2MDZcIixcImxhYmVsXCI6XCJDSEVCSTozNDYwNlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU5OTA0XCIsXCJsYWJlbFwiOlwiNyw4LWRpZGVtZXRoeWwtOC1oeWRyb3h5LTUtZGVhemFyaWJvZmxhdmluKDEtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NzI3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTc3MjdcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NzgxOFwiLFwibGFiZWxcIjpcIjUsMTAtbWV0aHlsZW5ldGV0cmFoeWRyb21ldGhhbm9wdGVyaW4oMy0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg4NjBcIixcImxhYmVsXCI6XCJDSEVCSTo1ODg2MFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NTMwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTc1MzBcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODg2MVwiLFwibGFiZWxcIjpcIkNIRUJJOjU4ODYxXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTYxODNcIixcImxhYmVsXCI6XCJtZXRoYW5lXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NzA3MzFcIixcImxhYmVsXCI6XCJDSEVCSTo3MDczMVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjM0NzczXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzQ3NzNcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzEzNTBcIixcImxhYmVsXCI6XCJDSEVCSToxMzEzNTBcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODg1NFwiLFwibGFiZWxcIjpcIkNIRUJJOjU4ODU0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg4NTlcIixcImxhYmVsXCI6XCJDSEVCSTo1ODg1OVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NzM4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTg3MzhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozMTEyOFwiLFwibGFiZWxcIjpcIkNIRUJJOjMxMTI4XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTk5MjBcIixcImxhYmVsXCI6XCJjb2VuenltZSBGNDIwLTEoNC0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg4MzdcIixcImxhYmVsXCI6XCJDSEVCSTo1ODgzN1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4ODM4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTg4MzhcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDgxOVwiLFwibGFiZWxcIjpcIkNIRUJJOjQ0ODE5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTU5NFwiLFwibGFiZWxcIjpcIkNIRUJJOjU1OTRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzQxOVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzNDE5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzA0OTBcIixcImxhYmVsXCI6XCJDSEVCSTozMDQ5MFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMzk2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMzOTZcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDY0MVwiLFwibGFiZWxcIjpcIkNIRUJJOjQ0NjQxXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMzOTVcIixcImxhYmVsXCI6XCJDSEVCSToxMzM5NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMzk0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMzOTRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozOTc0NVwiLFwibGFiZWxcIjpcIkNIRUJJOjM5NzQ1XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMzOTNcIixcImxhYmVsXCI6XCJDSEVCSToxMzM5M1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjc0MjJcIixcImxhYmVsXCI6XCJDSEVCSTo3NDIyXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NzQyM1wiLFwibGFiZWxcIjpcIkNIRUJJOjc0MjNcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozOTczOVwiLFwibGFiZWxcIjpcIkNIRUJJOjM5NzM5XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjczMTNcIixcImxhYmVsXCI6XCJDSEVCSToyNzMxM1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjQyODU3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDI4NTdcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MDU1M1wiLFwibGFiZWxcIjpcIkNIRUJJOjQwNTUzXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Nzc5M1wiLFwibGFiZWxcIjpcIkNIRUJJOjc3OTNcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTg2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjI5ODY3XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjYwNzhcIixcImxhYmVsXCI6XCJwaG9zcGhvcmljIGFjaWRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4NzUxOFwiLFwibGFiZWxcIjpcIkNIRUJJOjg3NTE4XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjE5MDFcIixcImxhYmVsXCI6XCJDSEVCSToyMTkwMVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjIxOTAyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjE5MDJcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDIxNFwiLFwibGFiZWxcIjpcIkNIRUJJOjQ0MjE0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ3OTFcIixcImxhYmVsXCI6XCJDSEVCSToxNDc5MVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ0MjE1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDQyMTVcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDIxNlwiLFwibGFiZWxcIjpcIkNIRUJJOjQ0MjE2XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMyMjJcIixcImxhYmVsXCI6XCJDSEVCSToxMzIyMlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNzQ0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA3NDRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MjA0M1wiLFwibGFiZWxcIjpcIkNIRUJJOjQyMDQzXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDQyOTJcIixcImxhYmVsXCI6XCJDSEVCSTo0NDI5MlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ1MDI0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDUwMjRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTEzN1wiLFwibGFiZWxcIjpcIkNIRUJJOjI5MTM3XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Mjk0MTJcIixcImxhYmVsXCI6XCJDSEVCSToyOTQxMlwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5MTM5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjkxMzlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzM2NVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMzY1XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDMyMjhcIixcImxhYmVsXCI6XCJDSEVCSTo0MzIyOFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ0MjgxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDQyODFcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MzQ3MFwiLFwibGFiZWxcIjpcIkNIRUJJOjQzNDcwXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMzNTdcIixcImxhYmVsXCI6XCJDSEVCSToxMzM1N1wiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ0NzAxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDQ3MDFcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1NTg1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTU4NVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU1ODRcIixcImxhYmVsXCI6XCJDSEVCSTo1NTg0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjM0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjIzNDJcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMjI0NFwiLFwibGFiZWxcIjpcIkNIRUJJOjIyMjQ0XCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI0MTRcIixcImxhYmVsXCI6XCJDSEVCSTo2MjQxNFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjExODEyXCIsXCJsYWJlbFwiOlwiMy1oeWRyb3h5LTMtbWV0aHlsLTItb3hvYnV0YW5vYXRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjMxOTM1XCIsXCJsYWJlbFwiOlwiZ2x5Y2Vyb2wgMS1waG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODgzNTBcIixcImxhYmVsXCI6XCIyLXBob3NwaG9nbHljZXJhdGUoMy0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjkzNTZcIixcImxhYmVsXCI6XCJveGlkZSgyLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNDM0NFwiLFwibGFiZWxcIjpcIjItcGhvc3Bob2dseWNlcmljIGFjaWRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0OTI5OVwiLFwibGFiZWxcIjpcIkQtZnJ1Y3RvZnVyYW5vc2UgMSw2LWJpc3Bob3NwaGF0ZSg0LSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MzA3NFwiLFwibGFiZWxcIjpcIigzUyktMy1oeWRyb3h5LTMtbWV0aHlsZ2x1dGFyeWwtQ29BKDUtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1NDA0XCIsXCJsYWJlbFwiOlwiKC0pLWhvbW9pc29jaXRyYXRlKDMtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjUzNjY0XCIsXCJsYWJlbFwiOlwiZGVoeWRyb2dseWNpbmF0ZVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NjM0XCIsXCJsYWJlbFwiOlwiYmV0YS1ELWZydWN0b2Z1cmFub3NlIDYtcGhvc3BoYXRlKDItKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjM1NzgwXCIsXCJsYWJlbFwiOlwicGhvc3BoYXRlIGlvblwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjI5NzUwXCIsXCJsYWJlbFwiOlwiMy0oaW5kb2wtMy15bClweXJ1dmljIGFjaWRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzcyMFwiLFwibGFiZWxcIjpcIjIsMy1iaXNwaG9zcGhvLUQtZ2x5Y2VyaWMgYWNpZFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjI2MDIwXCIsXCJsYWJlbFwiOlwicGhvc3BoYXRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc5NDBcIixcImxhYmVsXCI6XCIzLXN1bGZvbmF0b3B5cnV2YXRlKDItKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MjM0XCIsXCJsYWJlbFwiOlwiaHlkcm94aWRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTc2MzRcIixcImxhYmVsXCI6XCJELWdsdWNvc2VcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozMzgxM1wiLFwibGFiZWxcIjpcIigoMTgpTyl3YXRlclwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3Mjk5XCIsXCJsYWJlbFwiOlwiQVRQKDMtKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxNTQ4XCIsXCJsYWJlbFwiOlwiRC1nbHVjb3B5cmFub3NlIDYtcGhvc3BoYXRlKDItKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3NTA5XCIsXCJsYWJlbFwiOlwiNSctUy1tZXRoeWwtNSctdGhpb2FkZW5vc2luZVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3Njc4XCIsXCJsYWJlbFwiOlwiMy1tZXJjYXB0b3B5cnV2YXRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTcyNzJcIixcImxhYmVsXCI6XCJwcm9waW9uYXRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTgyNzNcIixcImxhYmVsXCI6XCJhbGRlaHlkby1ELXJpYm9zZSA1LXBob3NwaGF0ZSgyLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MTcwXCIsXCJsYWJlbFwiOlwiRC1nbHVjb3B5cmFub3NlIDYtcGhvc3BoYXRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDQ4OTdcIixcImxhYmVsXCI6XCJwaG9zcGhvZW5vbHB5cnV2aWMgYWNpZFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3MTM4XCIsXCJsYWJlbFwiOlwiZ2x5Y2VyYWxkZWh5ZGUgMy1waG9zcGhhdGVcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MjJcIixcImxhYmVsXCI6XCIoUyktbGFjdGljIGFjaWRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozMjgxNlwiLFwibGFiZWxcIjpcInB5cnV2aWMgYWNpZFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NDI5XCIsXCJsYWJlbFwiOlwiYWxwaGEsYWxwaGEtdHJlaGFsb3NlIDYtcGhvc3BoYXRlKDItKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MDI3XCIsXCJsYWJlbFwiOlwiZ2x5Y2VyYWxkZWh5ZGUgMy1waG9zcGhhdGUoMi0pXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc2MDRcIixcImxhYmVsXCI6XCIzLXBob3NwaG9uYXRvLUQtZ2x5Y2Vyb3lsIHBob3NwaGF0ZSg0LSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODE3NFwiLFwibGFiZWxcIjpcImJ1dC0xLWVuZS0xLDIsNC10cmljYXJib3h5bGF0ZVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4MjcyXCIsXCJsYWJlbFwiOlwiMy1waG9zcGhvbmF0by1ELWdseWNlcmF0ZSgzLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODM2N1wiLFwibGFiZWxcIjpcInBob3NwaGF0ZSgzLSlcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTQyMlwiLFwibGFiZWxcIjpcIkFUUFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3ODM1XCIsXCJsYWJlbFwiOlwiMi1waG9zcGhvLUQtZ2x5Y2VyaWMgYWNpZFwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MTA4XCIsXCJsYWJlbFwiOlwiZGloeWRyb3h5YWNldG9uZSBwaG9zcGhhdGVcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyODAxM1wiLFwibGFiZWxcIjpcImJldGEtRC1mcnVjdG9mdXJhbm9zZSAxLDYtYmlzcGhvc3BoYXRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY5MDhcIixcImxhYmVsXCI6XCJOQURIXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTgwMjFcIixcImxhYmVsXCI6XCJwaG9zcGhvZW5vbHB5cnV2YXRlXCIsXCJjb3VudFwiOlwiM1wifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY3NjFcIixcImxhYmVsXCI6XCJBRFBcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzc5NFwiLFwibGFiZWxcIjpcIjMtcGhvc3Boby1ELWdseWNlcmljIGFjaWRcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTg0NlwiLFwibGFiZWxcIjpcIk5BRCgrKVwiLFwiY291bnRcIjpcIjNcIn0se1widmFsdWVcIjpcIkNIRUJJOjQxNjdcIixcImxhYmVsXCI6XCJELWdsdWNvcHlyYW5vc2VcIixcImNvdW50XCI6XCIzXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNjE0NlwiLFwibGFiZWxcIjpcIkNIRUJJOjE2MTQ2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTYxNjhcIixcImxhYmVsXCI6XCJDSEVCSToxNjE2OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2OTY3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTY5NjdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0OTA4MlwiLFwibGFiZWxcIjpcIkNIRUJJOjQ5MDgyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTMxNDZcIixcImxhYmVsXCI6XCJDSEVCSTo1MzE0NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQzODM2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDM4MzZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MzgyOVwiLFwibGFiZWxcIjpcIkNIRUJJOjQzODI5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzcxNTZcIixcImxhYmVsXCI6XCJDSEVCSTozNzE1NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI1MTE2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjUxMTZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNTExN1wiLFwibGFiZWxcIjpcIkNIRUJJOjI1MTE3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjUxMThcIixcImxhYmVsXCI6XCJDSEVCSToyNTExOFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI1MTE5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjUxMTlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNjQ2MFwiLFwibGFiZWxcIjpcIkNIRUJJOjI2NDYwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjIwNzVcIixcImxhYmVsXCI6XCJDSEVCSToyMjA3NVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4ODE4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg4MThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODgxN1wiLFwibGFiZWxcIjpcIkNIRUJJOjE4ODE3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTc2NjRcIixcImxhYmVsXCI6XCJDSEVCSTo1NzY2NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMjk1MVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMjk1MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNDUzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE0NTNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4OTc0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODk3NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE5MzgzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTkzODNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNzI1NFwiLFwibGFiZWxcIjpcIkNIRUJJOjE3MjU0XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzI4MDhcIixcImxhYmVsXCI6XCJDSEVCSTozMjgwOFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjkzN1wiLFwibGFiZWxcIjpcIkNIRUJJOjkzN1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNDE2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI0MTZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1OTkxMVwiLFwibGFiZWxcIjpcIkNIRUJJOjU5OTExXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTk5MzBcIixcImxhYmVsXCI6XCJDSEVCSTo1OTkzMFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjg4N1wiLFwibGFiZWxcIjpcIkNIRUJJOjg4N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyNzE3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjI3MTdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyODAyN1wiLFwibGFiZWxcIjpcIkNIRUJJOjI4MDI3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzAyOVwiLFwibGFiZWxcIjpcIkNIRUJJOjMwMjlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzg3OVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzODc5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjgwODBcIixcImxhYmVsXCI6XCJDSEVCSToyODA4MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwMTI3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjAxMjdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMjcyMlwiLFwibGFiZWxcIjpcIkNIRUJJOjIyNzIyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDEwNTFcIixcImxhYmVsXCI6XCJDSEVCSTo0MTA1MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4MzE1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTgzMTVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxOTY1MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE5NjUyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTk2MTRcIixcImxhYmVsXCI6XCJDSEVCSToxOTYxNFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4MzAwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTgzMDBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODk3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg5N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMjE5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIyMTlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2NjUzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjY1M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjY2NTJcIixcImxhYmVsXCI6XCJDSEVCSTo2NjUyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTg0NTlcIixcImxhYmVsXCI6XCJDSEVCSTo1ODQ1OVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjU4NDQyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTg0NDJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTA0NVwiLFwibGFiZWxcIjpcIkNIRUJJOjI5MDQ1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTgzNTZcIixcImxhYmVsXCI6XCJDSEVCSToxODM1NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NDA0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg0MDRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDczMVwiLFwibGFiZWxcIjpcIkNIRUJJOjIwNzMxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjExNjdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMTMxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTEzMVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjExMzJcIixcImxhYmVsXCI6XCJDSEVCSToxMTMyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ5ODZcIixcImxhYmVsXCI6XCJDSEVCSToxNDk4NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ1MjUxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDUyNTFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3NzY2MFwiLFwibGFiZWxcIjpcIkNIRUJJOjc3NjYwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE4NTdcIixcImxhYmVsXCI6XCJDSEVCSToxMTg1N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQzOTYyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDM5NjJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjIyNVwiLFwibGFiZWxcIjpcIkNIRUJJOjYyMjI1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUwMzBcIixcImxhYmVsXCI6XCJDSEVCSToxNTAzMFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MzY0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTYzNjRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNjUyNlwiLFwibGFiZWxcIjpcIkNIRUJJOjI2NTI2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ1NThcIixcImxhYmVsXCI6XCJDSEVCSToxNDU1OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0NTU5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQ1NTlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDU1N1wiLFwibGFiZWxcIjpcIkNIRUJJOjE0NTU3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjQwMTBcIixcImxhYmVsXCI6XCJDSEVCSTo2NDAxMFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjY0MDE2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjQwMTZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NTVcIixcImxhYmVsXCI6XCJDSEVCSTo0NTVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDQ0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjIwNDQyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjQwMzdcIixcImxhYmVsXCI6XCJDSEVCSTo2NDAzN1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjMwNzgwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzA3ODBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjAyNVwiLFwibGFiZWxcIjpcIkNIRUJJOjEyMDI1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjIwMFwiLFwibGFiZWxcIjpcIkNIRUJJOjIyMDBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjAzOFwiLFwibGFiZWxcIjpcIkNIRUJJOjEyMDM4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Nzg4MlwiLFwibGFiZWxcIjpcIkNIRUJJOjc4ODJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3ODgxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6Nzg4MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYyNTZcIixcImxhYmVsXCI6XCJDSEVCSTo2MjU2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTYyODdcIixcImxhYmVsXCI6XCJDSEVCSToxNjI4N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMTI4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMxMjhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMjExNlwiLFwibGFiZWxcIjpcIkNIRUJJOjIyMTE2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA2ODNcIixcImxhYmVsXCI6XCJDSEVCSToxMDY4M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzNDU3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTM0NTdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzQ3NFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzNDc0XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Njg2NzhcIixcImxhYmVsXCI6XCJDSEVCSTo2ODY3OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNTdcIixcImxhYmVsXCI6XCJDSEVCSToxMTU3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6OTgyNFwiLFwibGFiZWxcIjpcIkNIRUJJOjk4MjRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTE3OVwiLFwibGFiZWxcIjpcIkNIRUJJOjE1MTc5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjI4MzZcIixcImxhYmVsXCI6XCJDSEVCSToyMjgzNlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjM2MjE3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzYyMTdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNTQ2MlwiLFwibGFiZWxcIjpcIkNIRUJJOjM1NDYyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDEzNTNcIixcImxhYmVsXCI6XCJDSEVCSTo0MTM1M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwMzU4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTAzNThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzA3MVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMDcxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMwNzBcIixcImxhYmVsXCI6XCJDSEVCSToxMzA3MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMDc5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMwNzlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzA4MFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMDgwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDA2MTVcIixcImxhYmVsXCI6XCJDSEVCSTo0MDYxNVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0MzYzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQzNjNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNTIzNlwiLFwibGFiZWxcIjpcIkNIRUJJOjM1MjM2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjE1NjVcIixcImxhYmVsXCI6XCJMLWFsYW55bC1MLWdsdXRhbWljIGFjaWRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjA4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjIwOFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyMDMzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjIwMzNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2Mjk1OFwiLFwibGFiZWxcIjpcIkNIRUJJOjYyOTU4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI5NTlcIixcImxhYmVsXCI6XCJDSEVCSTo2Mjk1OVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYyODdcIixcImxhYmVsXCI6XCJDSEVCSTo2Mjg3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI4M1wiLFwibGFiZWxcIjpcIkNIRUJJOjYyODNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjQzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjI0M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NTMyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTc1MzJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3NTg1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzU4NVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjc1ODBcIixcImxhYmVsXCI6XCJDSEVCSTo3NTgwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjYyNjdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjYyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjI2MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjMzNTQwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzM1NDBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzE4N1wiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTg3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxODVcIixcImxhYmVsXCI6XCJDSEVCSToxMzE4NVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMTM4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMxMzhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzEzN1wiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTM3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxNTJcIixcImxhYmVsXCI6XCJDSEVCSToxMzE1MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMTU1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMxNTVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzE3NlwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTc2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxNzlcIixcImxhYmVsXCI6XCJDSEVCSToxMzE3OVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMTEyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMxMTJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzEyMFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTIwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDA3NTJcIixcImxhYmVsXCI6XCJDSEVCSTo0MDc1MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMTM0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMxMzRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzEzM1wiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTMzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjMyMlwiLFwibGFiZWxcIjpcIkNIRUJJOjYzMjJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MzA5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjMwOVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYzMThcIixcImxhYmVsXCI6XCJDSEVCSTo2MzE4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjMxMlwiLFwibGFiZWxcIjpcIkNIRUJJOjYzMTJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDM5NlwiLFwibGFiZWxcIjpcIkNIRUJJOjQ0Mzk2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI3NThcIixcImxhYmVsXCI6XCJDSEVCSToxMjc1OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwODQ2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjA4NDZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDg1MFwiLFwibGFiZWxcIjpcIkNIRUJJOjIwODUwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Njk2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjY5NjdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDgwOFwiLFwibGFiZWxcIjpcIkNIRUJJOjIwODA4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTM3NjdcIixcImxhYmVsXCI6XCJDSEVCSToxMzc2N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNjcyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA2NzJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDY4MVwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNjgxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA2ODJcIixcImxhYmVsXCI6XCJDSEVCSToxMDY4MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNjc4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA2NzhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDY3OVwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNjc5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA2NzNcIixcImxhYmVsXCI6XCJDSEVCSToxMDY3M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNjc1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA2NzVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDY5MVwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNjkxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTkwMjVcIixcImxhYmVsXCI6XCJDSEVCSToxOTAyNVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNjkyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA2OTJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDY5NFwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNjk0XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA2OTBcIixcImxhYmVsXCI6XCJDSEVCSToxMDY5MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNjg4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA2ODhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDY4OVwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNjg5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA2ODRcIixcImxhYmVsXCI6XCJDSEVCSToxMDY4NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNjg1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA2ODVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDY4NlwiLFwibGFiZWxcIjpcIkNIRUJJOjEwNjg2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA2ODdcIixcImxhYmVsXCI6XCJDSEVCSToxMDY4N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjMyODg5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzI4ODlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTI3OVwiLFwibGFiZWxcIjpcIkNIRUJJOjI5Mjc5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjY0OFwiLFwibGFiZWxcIjpcIkNIRUJJOjI2NDhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MTg4N1wiLFwibGFiZWxcIjpcIkNIRUJJOjQxODg3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjA5MjFcIixcImxhYmVsXCI6XCJDSEVCSToyMDkyMVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ0MDgwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDQwODBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDkzMFwiLFwibGFiZWxcIjpcIkNIRUJJOjIwOTMwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NzQ3NjRcIixcImxhYmVsXCI6XCJDSEVCSTo3NDc2NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxNzJcIixcImxhYmVsXCI6XCJDSEVCSTo2MTcyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjE4OVwiLFwibGFiZWxcIjpcIkNIRUJJOjYxODlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0Mjg0OVwiLFwibGFiZWxcIjpcIkNIRUJJOjQyODQ5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjMwOFwiLFwibGFiZWxcIjpcIkNIRUJJOjIzMDhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTI0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE1MjQyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDU5MjJcIixcImxhYmVsXCI6XCJDSEVCSTo0NTkyMlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIzNTFcIixcImxhYmVsXCI6XCJDSEVCSToyMzUxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDgxMDdcIixcImxhYmVsXCI6XCJDSEVCSTo0ODEwN1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIzNDlcIixcImxhYmVsXCI6XCJDSEVCSToyMzQ5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjU1NDVcIixcImxhYmVsXCI6XCJDSEVCSToyNTU0NVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyMjQyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTIyNDJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNzUxNVwiLFwibGFiZWxcIjpcIkNIRUJJOjM3NTE1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODQ3OFwiLFwibGFiZWxcIjpcIkNIRUJJOjg0NzhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNDA3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjQwN1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwNzY1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjA3NjVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNTU2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjI1NTY3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjgxNlwiLFwibGFiZWxcIjpcIkNIRUJJOjY4MTZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNjI2M1wiLFwibGFiZWxcIjpcIkNIRUJJOjM2MjYzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTUwM1wiLFwibGFiZWxcIjpcIkNIRUJJOjU1MDNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNjk3N1wiLFwibGFiZWxcIjpcIkNIRUJJOjI2OTc3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTQzM1wiLFwibGFiZWxcIjpcIkNIRUJJOjU0MzNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MTExXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDExMVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2Mzc1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTYzNzVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzIzMVwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMjMxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMyMzBcIixcImxhYmVsXCI6XCJDSEVCSToxMzIzMFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI1MjI3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjUyMjdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDU4OFwiLFwibGFiZWxcIjpcIkNIRUJJOjE0NTg4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDQ0ODdcIixcImxhYmVsXCI6XCJDSEVCSTo0NDQ4N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjUyMDkwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTIwOTBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDY1NFwiLFwibGFiZWxcIjpcIkNIRUJJOjE0NjU0XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ2NThcIixcImxhYmVsXCI6XCJDSEVCSToxNDY1OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjk1NjlcIixcImxhYmVsXCI6XCJDSEVCSTo5NTY5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTE3NFwiLFwibGFiZWxcIjpcIkNIRUJJOjUxNzRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozMjQ1MVwiLFwibGFiZWxcIjpcIkNIRUJJOjMyNDUxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzI0NTBcIixcImxhYmVsXCI6XCJDSEVCSTozMjQ1MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjMyNDQ5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzI0NDlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTE2NlwiLFwibGFiZWxcIjpcIkNIRUJJOjE1MTY2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUxNjdcIixcImxhYmVsXCI6XCJDSEVCSToxNTE2N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MTY5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUxNjlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0Mjc0OVwiLFwibGFiZWxcIjpcIkNIRUJJOjQyNzQ5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUxNzNcIixcImxhYmVsXCI6XCJDSEVCSToxNTE3M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MTc0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUxNzRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTE3N1wiLFwibGFiZWxcIjpcIkNIRUJJOjE1MTc3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUxNzhcIixcImxhYmVsXCI6XCJDSEVCSToxNTE3OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MTgwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUxODBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTE4MVwiLFwibGFiZWxcIjpcIkNIRUJJOjE1MTgxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUxODRcIixcImxhYmVsXCI6XCJDSEVCSToxNTE4NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MTgyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUxODJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTE4M1wiLFwibGFiZWxcIjpcIkNIRUJJOjE1MTgzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUxODhcIixcImxhYmVsXCI6XCJDSEVCSToxNTE4OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MTg5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUxODlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNTE4NlwiLFwibGFiZWxcIjpcIkNIRUJJOjE1MTg2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTUxODdcIixcImxhYmVsXCI6XCJDSEVCSToxNTE4N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE1MTkxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTUxOTFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjkxOVwiLFwibGFiZWxcIjpcIkNIRUJJOjEyOTE5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjUzNjhcIixcImxhYmVsXCI6XCJDSEVCSToyNTM2OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI1MzcxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjUzNzFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NjI2MlwiLFwibGFiZWxcIjpcIkNIRUJJOjQ2MjYyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMzNDRcIixcImxhYmVsXCI6XCJDSEVCSToxMzM0NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjcxMjYzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzEyNjNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDU1M1wiLFwibGFiZWxcIjpcIkNIRUJJOjQ0NTUzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDA2OThcIixcImxhYmVsXCI6XCJDSEVCSTo0MDY5OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ5MDMyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDkwMzJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1ODA5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTgwOVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MTQxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTYxNDFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDM3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQzN1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjM5Njk4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6Mzk2OThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MzcwMVwiLFwibGFiZWxcIjpcIkNIRUJJOjQzNzAxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA4NDBcIixcImxhYmVsXCI6XCJDSEVCSToxMDg0MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQxOTk4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDE5OThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MDY2OFwiLFwibGFiZWxcIjpcIkNIRUJJOjQwNjY4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Mjk0NzdcIixcImxhYmVsXCI6XCJDSEVCSToyOTQ3N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ5MDAzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDkwMDNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzA2MlwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMDYyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTg3NTFcIixcImxhYmVsXCI6XCJDSEVCSToxODc1MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NzUwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg3NTBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODc1MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE4NzUyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTg3NTRcIixcImxhYmVsXCI6XCJDSEVCSToxODc1NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NzU2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg3NTZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDM5OFwiLFwibGFiZWxcIjpcIkNIRUJJOjE0Mzk4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODQyNjZcIixcImxhYmVsXCI6XCJDSEVCSTo4NDI2NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMDg3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMwODdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODc3OVwiLFwibGFiZWxcIjpcIkNIRUJJOjE4Nzc5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDkwNTVcIixcImxhYmVsXCI6XCJDSEVCSTo0OTA1NVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMDkxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMwOTFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODc4MFwiLFwibGFiZWxcIjpcIkNIRUJJOjE4NzgwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTg3ODJcIixcImxhYmVsXCI6XCJDSEVCSToxODc4MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NzgxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg3ODFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjAxMlwiLFwibGFiZWxcIjpcIkNIRUJJOjYyMDEyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMwMTlcIixcImxhYmVsXCI6XCJDSEVCSToxMzAxOVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0MzQ4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQzNDhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDM0N1wiLFwibGFiZWxcIjpcIkNIRUJJOjE0MzQ3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzUyNjZcIixcImxhYmVsXCI6XCJDSEVCSTozNTI2NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxNTQzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjE1NDNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4Mzc3NFwiLFwibGFiZWxcIjpcIkNIRUJJOjgzNzc0XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjUwMTRcIixcImxhYmVsXCI6XCJDSEVCSToyNTAxNFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQxOTI2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDE5MjZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2Mjg0MFwiLFwibGFiZWxcIjpcIkNIRUJJOjYyODQwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMwNDJcIixcImxhYmVsXCI6XCJDSEVCSToxMzA0MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NzMxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg3MzFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzA0OFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMDQ4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQzNzhcIixcImxhYmVsXCI6XCJDSEVCSToxNDM3OFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ1NzdcIixcImxhYmVsXCI6XCJDSEVCSTo0NTc3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6Mjc2ODFcIixcImxhYmVsXCI6XCJDSEVCSToyNzY4MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI2MzU4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjYzNThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDM4MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE0MzgyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjYzNTlcIixcImxhYmVsXCI6XCJDSEVCSToyNjM1OVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI2MzYwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjYzNjBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2NDYwNVwiLFwibGFiZWxcIjpcIkNIRUJJOjY0NjA1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTY5NzBcIixcImxhYmVsXCI6XCJDSEVCSToxNjk3MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0ODdcIixcImxhYmVsXCI6XCJDSEVCSToxNDg3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ4OFwiLFwibGFiZWxcIjpcIkNIRUJJOjE0ODhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNTg4NDhcIixcImxhYmVsXCI6XCJDSEVCSTozNTg4NDhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDk0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQ5NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIzMzM3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjMzMzdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjAyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjIwMlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjU3NTczXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTc1NzNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3NTU5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzU1OVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMTUzM1wiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTUzM1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMTUzMFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTUzMFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE0NDIzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTQ0MjNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjMyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjIzMlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjc1NjFcIixcImxhYmVsXCI6XCJDSEVCSTo3NTYxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjMzMDZcIixcImxhYmVsXCI6XCJDSEVCSToyMzMwNlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIzMzA4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjMzMDhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNDYzNVwiLFwibGFiZWxcIjpcIkNIRUJJOjI0NjM1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTM5OTNcIixcImxhYmVsXCI6XCJDSEVCSToxMzk5M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwMjgzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjAyODNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0OTk4MlwiLFwibGFiZWxcIjpcIkNIRUJJOjQ5OTgyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MzQwMDJcIixcImxhYmVsXCI6XCJDSEVCSTozNDAwMlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ5OTcyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDk5NzJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMzMyOFwiLFwibGFiZWxcIjpcIkNIRUJJOjIzMzI4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTEzNTRcIixcImxhYmVsXCI6XCJDSEVCSToxMTM1NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNjg1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI2ODVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMTU1MlwiLFwibGFiZWxcIjpcIkNIRUJJOjIxNTUyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTkyNTFcIixcImxhYmVsXCI6XCJDSEVCSToxOTI1MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYyMDg1XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjIwODVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjYxNlwiLFwibGFiZWxcIjpcIkNIRUJJOjEyNjE2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTM5NDdcIixcImxhYmVsXCI6XCJDSEVCSToxMzk0N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNjE4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI2MThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMjYxN1wiLFwibGFiZWxcIjpcIkNIRUJJOjEyNjE3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTM5NzBcIixcImxhYmVsXCI6XCJDSEVCSToxMzk3MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNjM0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI2MzRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxOTIwN1wiLFwibGFiZWxcIjpcIkNIRUJJOjE5MjA3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI0NlwiLFwibGFiZWxcIjpcIkNIRUJJOjYyNDZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0NDIxOVwiLFwibGFiZWxcIjpcIkNIRUJJOjQ0MjE5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjI0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjYyNDJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MjQxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjI0MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyODUxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjI4NTFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MjQ5NlwiLFwibGFiZWxcIjpcIkNIRUJJOjQyNDk2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjE1NDlcIixcImxhYmVsXCI6XCJDSEVCSToyMTU0OVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYyNzJcIixcImxhYmVsXCI6XCJDSEVCSTo2MjcyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjE1NTBcIixcImxhYmVsXCI6XCJDSEVCSToyMTU1MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIxNTUxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjE1NTFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MjUxN1wiLFwibGFiZWxcIjpcIkNIRUJJOjQyNTE3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODc4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODc4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTU2NVwiLFwibGFiZWxcIjpcIkNIRUJJOjE1NjVcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1OTI2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTkyNlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwOTUyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA5NTJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3MTAzOFwiLFwibGFiZWxcIjpcIkNIRUJJOjcxMDM4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA5NjBcIixcImxhYmVsXCI6XCJDSEVCSToxMDk2MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQ2MTZcIixcImxhYmVsXCI6XCJDSEVCSTo0NjE2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTA5NjdcIixcImxhYmVsXCI6XCJDSEVCSToxMDk2N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwOTY4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA5NjhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxOTMxMlwiLFwibGFiZWxcIjpcIkNIRUJJOjE5MzEyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NTI3NzJcIixcImxhYmVsXCI6XCJDSEVCSTo1Mjc3MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjk0NzVcIixcImxhYmVsXCI6XCJDSEVCSTo5NDc1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODM3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODM3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxOTJcIixcImxhYmVsXCI6XCJDSEVCSToxMzE5MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjg0Mzg3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODQzODdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozMDU2N1wiLFwibGFiZWxcIjpcIkNIRUJJOjMwNTY3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjMxNTg4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjMxNTg4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6ODE2NlwiLFwibGFiZWxcIjpcIkNIRUJJOjgxNjZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4MTY3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODE2N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjgxOFwiLFwibGFiZWxcIjpcIkNIRUJJOjgxOFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE2MjQxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTYyNDFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1OTAwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NTkwMFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI1MTk5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjUxOTlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzE0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTQyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTQ0NzNcIixcImxhYmVsXCI6XCJDSEVCSToxNDQ3M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjUzNzUxOVwiLFwibGFiZWxcIjpcIkNIRUJJOjUzNzUxOVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjgzODk4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODM4OThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDMxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjAzMVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwMzJcIixcImxhYmVsXCI6XCJDSEVCSToyMDMyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjAzNFwiLFwibGFiZWxcIjpcIkNIRUJJOjIwMzRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3NzI0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzcyNFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE3NTMzXCIsXCJsYWJlbFwiOlwiTi1hY2V0eWwtTC1nbHV0YW1pYyBhY2lkXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjAyMFwiLFwibGFiZWxcIjpcIkNIRUJJOjIwMjBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNTE1NlwiLFwibGFiZWxcIjpcIkNIRUJJOjI1MTU2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDA3NzFcIixcImxhYmVsXCI6XCJDSEVCSTo0MDc3MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxNjk0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjE2OTRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMzM4MFwiLFwibGFiZWxcIjpcIkNIRUJJOjIzMzgwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjMzODZcIixcImxhYmVsXCI6XCJDSEVCSToyMzM4NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjMzMDhcIixcImxhYmVsXCI6XCJDSEVCSTozMzA4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjcyMDY2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjcyMDY2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjY0MjNcIixcImxhYmVsXCI6XCJDSEVCSToyNjQyM1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI2NDI0XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjY0MjRcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNjQyOVwiLFwibGFiZWxcIjpcIkNIRUJJOjI2NDI5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjY0MjZcIixcImxhYmVsXCI6XCJDSEVCSToyNjQyNlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI2NDI3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjY0MjdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMzExOFwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMTE4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxMTlcIixcImxhYmVsXCI6XCJDSEVCSToxMzExOVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyMDczXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjIwNzNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyNjQzMFwiLFwibGFiZWxcIjpcIkNIRUJJOjI2NDMwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMxMzBcIixcImxhYmVsXCI6XCJDSEVCSToxMzEzMFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI1MTEyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjUxMTJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMjEyNVwiLFwibGFiZWxcIjpcIkNIRUJJOjIyMTI1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjIxMjRcIixcImxhYmVsXCI6XCJDSEVCSToyMjEyNFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyMTI2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjIxMjZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMTQ4M1wiLFwibGFiZWxcIjpcIkNIRUJJOjExNDgzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE0ODFcIixcImxhYmVsXCI6XCJDSEVCSToxMTQ4MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNDgyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE0ODJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxNDUwMVwiLFwibGFiZWxcIjpcIkNIRUJJOjE0NTAxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjQ3OTRcIixcImxhYmVsXCI6XCJDSEVCSToyNDc5NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjc2NjhcIixcImxhYmVsXCI6XCJDSEVCSTo3NjY4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE0OTFcIixcImxhYmVsXCI6XCJDSEVCSToxMTQ5MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjc2NjdcIixcImxhYmVsXCI6XCJDSEVCSTo3NjY3XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE0OTNcIixcImxhYmVsXCI6XCJDSEVCSToxMTQ5M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjExNDg4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE0ODhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMTQ4OVwiLFwibGFiZWxcIjpcIkNIRUJJOjExNDg5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTMyOTcwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTMyOTcwXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjM0OVwiLFwibGFiZWxcIjpcIkNIRUJJOjYzNDlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0MzAyNlwiLFwibGFiZWxcIjpcIkNIRUJJOjQzMDI2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NjM0MVwiLFwibGFiZWxcIjpcIkNIRUJJOjYzNDFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozNTQwNFwiLFwibGFiZWxcIjpcIkNIRUJJOjM1NDA0XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI3ODFcIixcImxhYmVsXCI6XCJDSEVCSToxMjc4MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwMTEzXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTAxMTNcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo0ODc3NVwiLFwibGFiZWxcIjpcImNhZG1pdW0oMispXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDg3NzNcIixcImxhYmVsXCI6XCJDSEVCSTo0ODc3M1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyMTEwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjIxMTBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MTcxOVwiLFwibGFiZWxcIjpcIkNIRUJJOjYxNzE5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDg3NjBcIixcImxhYmVsXCI6XCJDSEVCSTo0ODc2MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEzMjkyNlwiLFwibGFiZWxcIjpcIkNIRUJJOjEzMjkyNlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI3ODAwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6Mjc4MDBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo4OTcxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODk3MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNzk4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI3OThcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozMjc3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MzI3N1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyMTIwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjIxMjBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMjEyM1wiLFwibGFiZWxcIjpcIkNIRUJJOjIyMTIzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI3MjRcIixcImxhYmVsXCI6XCJDSEVCSToxMjcyNFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjI0NzE2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjQ3MTZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODA0MlwiLFwibGFiZWxcIjpcIkNIRUJJOjE4MDQyXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTgwNTVcIixcImxhYmVsXCI6XCJDSEVCSToxODA1NVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNzUxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI3NTFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODA2OVwiLFwibGFiZWxcIjpcIkNIRUJJOjE4MDY5XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDQzMzVcIixcImxhYmVsXCI6XCJDSEVCSTo0NDMzNVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwMzA4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjAzMDhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo3NjkxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NzY5MVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjgzOTAwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6ODM5MDBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyMDMxNVwiLFwibGFiZWxcIjpcIkNIRUJJOjIwMzE1XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjAzMTRcIixcImxhYmVsXCI6XCJDSEVCSToyMDMxNFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIwMzEyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjAzMTJcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxMDk5NlwiLFwibGFiZWxcIjpcIkNIRUJJOjEwOTk2XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6NDQzMTlcIixcImxhYmVsXCI6XCJDSEVCSTo0NDMxOVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjIyOTg4XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjI5ODhcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToxODAxNFwiLFwibGFiZWxcIjpcIkNIRUJJOjE4MDE0XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjM0NTcxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MjM0NTcxXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTI3MDlcIixcImxhYmVsXCI6XCJDSEVCSToxMjcwOVwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjM0MTkzN1wiLFwibGFiZWxcIjpcIkNIRUJJOjM0MTkzN1wiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNzEwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI3MTBcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MzkyXCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjM5MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEyNzE3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTI3MTdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSToyOTIwM1wiLFwibGFiZWxcIjpcIkNIRUJJOjI5MjAzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjA4NzRcIixcImxhYmVsXCI6XCJDSEVCSToyMDg3NFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjYxMzg3XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NjEzODdcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo5MTY2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6OTE2NlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjExOTI5XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTE5MjlcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo2MDAyOFwiLFwibGFiZWxcIjpcIkNIRUJJOjYwMDI4XCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MjA4ODJcIixcImxhYmVsXCI6XCJDSEVCSToyMDg4MlwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjE4NTc2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTg1NzZcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTozODgwXCIsXCJsYWJlbFwiOlwiQ0hFQkk6Mzg4MFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjEwNjAxXCIsXCJsYWJlbFwiOlwiQ0hFQkk6MTA2MDFcIixcImNvdW50XCI6XCIyXCJ9LHtcInZhbHVlXCI6XCJDSEVCSTo1MjQwM1wiLFwibGFiZWxcIjpcIkNIRUJJOjUyNDAzXCIsXCJjb3VudFwiOlwiMlwifSx7XCJ2YWx1ZVwiOlwiQ0hFQkk6MTE5MzhcIixcImxhYmVsXCI6XCJDSEVCSToxMTkzOFwiLFwiY291bnRcIjpcIjJcIn0se1widmFsdWVcIjpcIkNIRUJJOjQxNzU2XCIsXCJsYWJlbFwiOlwiQ0hFQkk6NDE3NTZcIixcImNvdW50XCI6XCIyXCJ9XSxcImlkXCI6XCJDSEVCSVwiLFwidG90YWxcIjo1Nzg4LFwibGFiZWxcIjpcIkNIRUJJXCJ9LHtcImZhY2V0VmFsdWVzXCI6W3tcInZhbHVlXCI6XCJFTlNHMDAwMDAxNjM3MzlcIixcImxhYmVsXCI6XCJFTlNHMDAwMDAxNjM3MzlcIixcImNvdW50XCI6XCIxXCJ9LHtcInZhbHVlXCI6XCJFTlNHMDAwMDAxMjA2NTlcIixcImxhYmVsXCI6XCJFTlNHMDAwMDAxMjA2NTlcIixcImNvdW50XCI6XCIxXCJ9XSxcImlkXCI6XCJFTlNFTUJMXCIsXCJ0b3RhbFwiOjIsXCJsYWJlbFwiOlwiRU5TRU1CTFwifV0iLCJmYWNldHMiOlt7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldCIsImZhY2V0VmFsdWVzIjpbeyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMjQiLCJsYWJlbCI6Ik5vbi1jdXJhdGVkIiwidmFsdWUiOiJOb24tY3VyYXRlZCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6Ik1hbnVhbGx5IGN1cmF0ZWQiLCJ2YWx1ZSI6Ik1hbnVhbGx5IGN1cmF0ZWQifV0sImlkIjoiY3VyYXRpb25zdGF0dXMiLCJsYWJlbCI6IkN1cmF0aW9uIHN0YXR1cyIsInRvdGFsIjoyfSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldCIsImZhY2V0VmFsdWVzIjpbeyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMjAiLCJsYWJlbCI6IlNCTUwiLCJ2YWx1ZSI6IlNCTUwifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI3IiwibGFiZWwiOiJDT01CSU5FIGFyY2hpdmUiLCJ2YWx1ZSI6IkNPTUJJTkUgYXJjaGl2ZSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik90aGVyIiwidmFsdWUiOiJPdGhlciJ9XSwiaWQiOiJtb2RlbGZvcm1hdCIsImxhYmVsIjoiTW9kZWwgZm9ybWF0IiwidG90YWwiOjN9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0IiwiZmFjZXRWYWx1ZXMiOlt7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxNCIsImxhYmVsIjoiQ29uc3RyYWludC1iYXNlZCBtb2RlbCIsInZhbHVlIjoiY29uc3RyYWludC1iYXNlZCBtb2RlbCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Ik9yZGluYXJ5IGRpZmZlcmVudGlhbCBlcXVhdGlvbiBtb2RlbCIsInZhbHVlIjoib3JkaW5hcnkgZGlmZmVyZW50aWFsIGVxdWF0aW9uIG1vZGVsIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRmluaXRlIGVsZW1lbnQgc3BhdGlhbCBtb2RlbCIsInZhbHVlIjoiZmluaXRlIGVsZW1lbnQgc3BhdGlhbCBtb2RlbCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkxvZ2ljYWwgbW9kZWwiLCJ2YWx1ZSI6ImxvZ2ljYWwgbW9kZWwifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJNYXRoZW1hdGljYWwgbW9kZWwiLCJ2YWx1ZSI6Im1hdGhlbWF0aWNhbCBtb2RlbCJ9XSwiaWQiOiJtb2RlbGxpbmdhcHByb2FjaCIsImxhYmVsIjoiTW9kZWxsaW5nIEFwcHJvYWNoIiwidG90YWwiOjV9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0IiwiZmFjZXRWYWx1ZXMiOlt7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJIb21vIHNhcGllbnMiLCJ2YWx1ZSI6Ijk2MDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJNZXRoYW5vdGhlcm1vYmFjdGVyIHRoZXJtYXV0b3Ryb3BoaWN1cyIsInZhbHVlIjoiMTQ1MjYyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgc3RyLiBEZWx0YSBIIiwidmFsdWUiOiIxODc0MjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJNZXRoYW5vdGhlcm1vYmFjdGVyIG1hcmJ1cmdlbnNpcyBzdHIuIE1hcmJ1cmciLCJ2YWx1ZSI6Ijc5OTI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUmF0dHVzIG5vcnZlZ2ljdXMiLCJ2YWx1ZSI6IjEwMTE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTXVzIG11c2N1bHVzIiwidmFsdWUiOiIxMDA5MCJ9XSwiaWQiOiJUQVhPTk9NWSIsImxhYmVsIjoiT3JnYW5pc21zIiwidG90YWwiOjZ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0IiwiZmFjZXRWYWx1ZXMiOlt7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJjZWxsIGN5Y2xlIiwidmFsdWUiOiJHTzowMDA3MDQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiY2VsbCBkaXZpc2lvbiIsInZhbHVlIjoiR086MDA1MTMwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6ImFuYXBoYXNlLXByb21vdGluZyBjb21wbGV4IiwidmFsdWUiOiJHTzowMDA1NjgwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoibmVnYXRpdmUgcmVndWxhdGlvbiBvZiBwcm90ZWluIG1vZGlmaWNhdGlvbiBieSBzbWFsbCBwcm90ZWluIGNvbmp1Z2F0aW9uIG9yIHJlbW92YWwiLCJ2YWx1ZSI6IkdPOjE5MDMzMjEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJwcm90ZWluIG1vZGlmaWNhdGlvbiBwcm9jZXNzIiwidmFsdWUiOiJHTzowMDM2MjExIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoicG9zaXRpdmUgcmVndWxhdGlvbiBvZiByZWNlcHRvciBzaWduYWxpbmcgcGF0aHdheSB2aWEgSkFLLVNUQVQiLCJ2YWx1ZSI6IkdPOjAwNDY0MjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJtUk5BIG1vZGlmaWNhdGlvbiIsInZhbHVlIjoiR086MDAxNjU1NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6ImN5Y2xpbiBCMS1DREsxIGNvbXBsZXgiLCJ2YWx1ZSI6IkdPOjAwOTcxMjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJyZXNwb25zZSB0byBncm93dGggZmFjdG9yIiwidmFsdWUiOiJHTzowMDcwODQ4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiZXJ5dGhyb3BvaWV0aW4tbWVkaWF0ZWQgc2lnbmFsaW5nIHBhdGh3YXkiLCJ2YWx1ZSI6IkdPOjAwMzgxNjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJpbnN1bGluIHNlY3JldGlvbiIsInZhbHVlIjoiR086MDAzMDA3MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6ImRlcGhvc3Bob3J5bGF0aW9uIiwidmFsdWUiOiJHTzowMDE2MzExIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoicGhvc3Bob3J5bGF0aW9uIiwidmFsdWUiOiJHTzowMDE2MzEwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiZ2VuZSBleHByZXNzaW9uIiwidmFsdWUiOiJHTzowMDEwNDY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoibnVjbGV1cyIsInZhbHVlIjoiR086MDAwNTYzNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6InRyYW5zbGF0aW9uIiwidmFsdWUiOiJHTzowMDA2NDEyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiY3l0b3NvbCIsInZhbHVlIjoiR086MDAwNTgyOSJ9XSwiaWQiOiJHTyIsImxhYmVsIjoiR08iLCJ0b3RhbCI6MTd9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0IiwiZmFjZXRWYWx1ZXMiOlt7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJNZXRhbCBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdITTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBVFAtYmluZGluZyBjYXNzZXR0ZSBkb21haW4tY29udGFpbmluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHR0s4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVURQLU4tYWNldHlsbXVyYW1hdGUtLUwtYWxhbmluZSBsaWdhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKMDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHVFAgZGlwaG9zcGhva2luYXNlIiwidmFsdWUiOiJBMEEzMjhLN1c5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUmlib251Y2xlb3NpZGUtZGlwaG9zcGhhdGUgcmVkdWN0YXNlIiwidmFsdWUiOiJBMEE1MTZHS1M3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR2x1dGFtYXRlLS10Uk5BIGxpZ2FzZSIsInZhbHVlIjoiQTBBNTE2R0dLNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNhdGlvbi10cmFuc2xvY2F0aW5nIFAtdHlwZSBBVFBhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdITTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJVcmFjaWwgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTFTOEtQMzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHdWFueWxhdGUga2luYXNlIiwidmFsdWUiOiJBMEExUzhLTEwxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTW5oQiBkb21haW4tY29udGFpbmluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHSkEzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQWNldHlsb3JuaXRoaW5lIGRlYWNldHlsYXNlMy41LjEuMTYiLCJ2YWx1ZSI6IkEwQTUxNkdLQzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJEaWh5ZHJveHlhY2V0b25lIGtpbmFzZSBzdWJ1bml0IEwiLCJ2YWx1ZSI6IkEwQTUxNkdMRTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0xFMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlByb2JhYmxlIG1hbmdhbmVzZS1kZXBlbmRlbnQgaW5vcmdhbmljIHB5cm9waG9zcGhhdGFzZSIsInZhbHVlIjoiQTBBMzI4S0RQMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik5BRCBraW5hc2UiLCJ2YWx1ZSI6IkEwQTMyOEswWTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJPc21vcHJvdGVjdGFudCBBQkMgdHJhbnNwb3J0ZXIgc3Vic3RyYXRlLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBMzI4S1VBNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Iklyb24tY29udGFpbmluZyBhbGNvaG9sIGRlaHlkcm9nZW5hc2UiLCJ2YWx1ZSI6IkEwQTUxNkdMRDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBVFAtZGVwZW5kZW50IDYtcGhvc3Bob2ZydWN0b2tpbmFzZSIsInZhbHVlIjoiQTBBMzI4S1BYNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFsY29ob2wgZGVoeWRyb2dlbmFzZSBBZGhQMS4xLjEuMSIsInZhbHVlIjoiQTBBNTE2R0pRMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Iklvbi10cmFuc2xvY2F0aW5nIG94aWRvcmVkdWN0YXNlIGNvbXBsZXggc3VidW5pdCBDIiwidmFsdWUiOiJBMEEzMjhLVTAxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiSHlkcm94eWFjeWxnbHV0YXRoaW9uZSBoeWRyb2xhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdJTjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQVFMgc3lzdGVtIG1hbm5vc2Utc3BlY2lmaWMgRUlJQUIgY29tcG9uZW50IiwidmFsdWUiOiJBMEExUzhLUFM5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQmlmdW5jdGlvbmFsIHByb3RlaW4gRm9sRCIsInZhbHVlIjoiQTBBMzI4S0VBMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlNvZGl1bS9zb2x1dGUgc3ltcG9ydGVyU29kaXVtOnNvbHV0ZSBzeW1wb3J0ZXIiLCJ2YWx1ZSI6IkEwQTFTOEtQMjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJkVVRQIGRpcGhvc3BoYXRhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdHSjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJPcm5pdGhpbmUgY2FyYmFtb3lsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdITDUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJOQURQLWRlcGVuZGVudCBtYWxpYyBlbnp5bWUiLCJ2YWx1ZSI6IkEwQTUxNkdLMTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJNYWduZXNpdW0gdHJhbnNwb3J0ZXIgTWd0RSIsInZhbHVlIjoiQTBBMVM4S1E0NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlJpYm9raW5hc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKUDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJPcm90YXRlIHBob3NwaG9yaWJvc3lsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdLQjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJOLWFjZXR5bG5ldXJhbWluYXRlIGx5YXNlNC4xLjMuMyIsInZhbHVlIjoiQTBBMVM4S1AyMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkN5c3RlaW5lIGh5ZHJvbGFzZUlzb2Nob3Jpc21hdGFzZSIsInZhbHVlIjoiQTBBMzI4S01BNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlB5cnV2YXRlIGNhcmJveHlsYXNlIiwidmFsdWUiOiJBMEE1MTZHTEQxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlIiwidmFsdWUiOiJBMEE1MTZHTEQwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlQW1pbm8gYWNpZCBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtXRDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJNZXRoeWxlbmV0ZXRyYWh5ZHJvZm9sYXRlIHJlZHVjdGFzZSIsInZhbHVlIjoiQTBBMzI4S04yMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlJlc3BvbnNlIHJlZ3VsYXRvciIsInZhbHVlIjoiQTBBNTE2R0tSNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Iklzb3ByZW55bCB0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0lOMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlBhbnRvdGhlbmF0ZSBraW5hc2UiLCJ2YWx1ZSI6IkEwQTMyOEtHRDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJTb2RpdW06YWxhbmluZSBzeW1wb3J0ZXIgZmFtaWx5IHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdJTTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQUEgZmFtaWx5IEFUUGFzZURlb3h5bnVjbGVvc2lkZSBraW5hc2UiLCJ2YWx1ZSI6IkEwQTFTOEtRVDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJUaGlvcmVkb3hpbiByZWR1Y3Rhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtHRDUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJTSVMgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpblRhZ2F0b3NlLTYtcGhvc3BoYXRlIGtldG9zZSBpc29tZXJhc2UiLCJ2YWx1ZSI6IkEwQTFTOEtRVDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHVFAgY3ljbG9oeWRyb2xhc2UgMSIsInZhbHVlIjoiQTBBMVM4S1BSNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRhZ2F0b3NlLTYtcGhvc3BoYXRlIGtpbmFzZSIsInZhbHVlIjoiQTBBNTE2R0swNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNhcmJvaHlkcmF0ZSBBQkMgdHJhbnNwb3J0ZXIgc3Vic3RyYXRlLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBMzI4S1BGMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik1ldGFsIEFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZSIsInZhbHVlIjoiQTBBMzI4SzNNNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRyaW9zZXBob3NwaGF0ZSBpc29tZXJhc2UiLCJ2YWx1ZSI6IkEwQTFTOEtSNTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQaG9zcGhvZ2x1Y29zYW1pbmUgbXV0YXNlIiwidmFsdWUiOiJBMEEzMjhLSVg0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVGhpb2xhc2UgZmFtaWx5IHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdHWTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiIzLWh5ZHJveHlhY3lsLVthY3lsLWNhcnJpZXItcHJvdGVpbl0gZGVoeWRyYXRhc2UgRmFiWiIsInZhbHVlIjoiQTBBMVM4S1FEOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkZ1bWFyYXRlIGh5ZHJhdGFzZSBjbGFzcyBJSSIsInZhbHVlIjoiQTBBNTE2R0dJMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Iklyb24gQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlIiwidmFsdWUiOiJBMEE1MTZHSzAyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQ2hyb21hdGUgdHJhbnNwb3J0ZXIiLCJ2YWx1ZSI6IkEwQTUxNkdISzAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJMYWN0b3lsZ2x1dGF0aGlvbmUgbHlhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtRNzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJIRCBkb21haW4tY29udGFpbmluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHTFMwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUHlydXZhdGUgZm9ybWF0ZS1seWFzZS1hY3RpdmF0aW5nIGVuenltZSIsInZhbHVlIjoiQTBBMzI4SzFZOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRoaW9sIHJlZHVjdGFzZSB0aGlvcmVkb3hpbiIsInZhbHVlIjoiQTBBMVM4S1AxOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFsZGVoeWRlIGRlaHlkcm9nZW5hc2UgZmFtaWx5IHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdJTDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQaG9zcGhvZ2x5Y2VyYXRlIGtpbmFzZSIsInZhbHVlIjoiQTBBMVM4S0xJMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZVN1Z2FyIEFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZSIsInZhbHVlIjoiQTBBMzI4Szg1NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNhdGlvbjpkaWNhcmJveHlsYXNlIHN5bXBvcnRlciBmYW1pbHkgdHJhbnNwb3J0ZXIiLCJ2YWx1ZSI6IkEwQTUxNkdKTjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJSaWJvc29tZS1iaW5kaW5nIEFUUGFzZSBZY2hGIiwidmFsdWUiOiJBMEE1MTZHR0gwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR2FsYWN0b3NlLTEtcGhvc3BoYXRlIHVyaWR5bHlsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtWUTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBY2V0eWwtQ29BIGNhcmJveHl0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0xCMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlByb2JhYmxlIEQtc2VyaW5lIGRlaHlkcmF0YXNlIiwidmFsdWUiOiJBMEE1MTZHSFo4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRW5lcmd5LWNvdXBsaW5nIGZhY3RvciB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluIEVjZkEyIiwidmFsdWUiOiJBMEE1MTZHR1g1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQmlvdGluIGNhcmJveHlsYXNlIiwidmFsdWUiOiJBMEE1MTZHSDk1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQW5hZXJvYmljIHJpYm9udWNsZW9zaWRlLXRyaXBob3NwaGF0ZSByZWR1Y3Rhc2UxLjE3LjQuMiIsInZhbHVlIjoiQTBBNTE2R0xBOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik51Y2xlb3RpZGUgcHlyb3Bob3NwaG9oeWRyb2xhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdIWjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHTVAgcmVkdWN0YXNlIiwidmFsdWUiOiJBMEEzMjhLQkkwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQVRQLWRlcGVuZGVudCB6aW5jIG1ldGFsbG9wcm90ZWFzZSBGdHNIIiwidmFsdWUiOiJBMEE1MTZHRzcwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUFRTIE4tYWNldHlsZ2x1Y29zYW1pbmUgdHJhbnNwb3J0ZXIgc3VidW5pdCBJSUFCQ1BUUyBnbHVjb3NlIHRyYW5zcG9ydGVyIHN1YnVuaXQgSUlBIiwidmFsdWUiOiJBMEEzMjhLRjA0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiU3VnYXItYmluZGluZyB0cmFuc2NyaXB0aW9uYWwgcmVndWxhdG9yIiwidmFsdWUiOiJBMEE1MTZHSDkxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUmlib251Y2xlb3NpZGUgaHlkcm9sYXNlIFJpaEMiLCJ2YWx1ZSI6IkEwQTUxNkdIOTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJEaWh5ZHJvbGlwb2FtaWRlIGFjZXR5bHRyYW5zZmVyYXNlIGNvbXBvbmVudCBvZiBweXJ1dmF0ZSBkZWh5ZHJvZ2VuYXNlIGNvbXBsZXgiLCJ2YWx1ZSI6IkEwQTUxNkdKTjEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJEaWFjeWxnbHljZXJvbCBraW5hc2VZZWdTL1J2MjI1Mi9CbXJVIGZhbWlseSBsaXBpZCBraW5hc2UiLCJ2YWx1ZSI6IkEwQTMyOEtHMjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQVFMgbWFubm9zZS9mcnVjdG9zZS9zb3Jib3NlIHRyYW5zcG9ydGVyIHN1YnVuaXQgSUlDIiwidmFsdWUiOiJBMEExUzhLUTI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUHJvYmFibGUgZ2x5Y2luZSBkZWh5ZHJvZ2VuYXNlIChkZWNhcmJveHlsYXRpbmcpIHN1YnVuaXQgMSIsInZhbHVlIjoiQTBBNTE2R0lLNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkZvcm1hdGUtLXRldHJhaHlkcm9mb2xhdGUgbGlnYXNlIiwidmFsdWUiOiJBMEE1MTZHR0c1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRkFELWJpbmRpbmcgb3hpZG9yZWR1Y3Rhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdHRzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMtRiBmYW1pbHkgQVRQLWJpbmRpbmcgY2Fzc2V0dGUgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0lLNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkJpZnVuY3Rpb25hbCBwcm90ZWluIEdsbVUiLCJ2YWx1ZSI6IkEwQTUxNkdISTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJEaWh5ZHJvZm9sYXRlIHJlZHVjdGFzZSIsInZhbHVlIjoiQTBBNTE2R0lLNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluQmFjaXRyYWNpbiBBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBMVM4S0xQMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkQtYWxhbmluZS0tRC1hbGFuaW5lIGxpZ2FzZSIsInZhbHVlIjoiQTBBNTE2R0hJMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFkaGVzaW9uIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTFTOEtQNzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBcm9tYXRpYyByaW5nIGh5ZHJveHlsYXNlTWV0YWwtc3VsZnVyIGNsdXN0ZXIgYXNzZW1ibHkgZmFjdG9yIiwidmFsdWUiOiJBMEExUzhLTjMxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRGloeWRyb3h5YWNldG9uZSBraW5hc2Ugc3VidW5pdCBEaGFLMi43LjEuMTIxIiwidmFsdWUiOiJBMEE1MTZHSFowIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTC1sYWN0YXRlIGRlaHlkcm9nZW5hc2UiLCJ2YWx1ZSI6IkEwQTFTOEtQWDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdIODUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBY2V0YXRlIGtpbmFzZSIsInZhbHVlIjoiQTBBMVM4S05EOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlMtYWRlbm9zeWxtZXRoaW9uaW5lIHN5bnRoYXNlIiwidmFsdWUiOiJBMEE1MTZHSFkzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTkFEKFApSC1kZXBlbmRlbnQgb3hpZG9yZWR1Y3Rhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdIODEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJIb2xvLVthY3lsLWNhcnJpZXItcHJvdGVpbl0gc3ludGhhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtETDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJFbmVyZ3ktY291cGxpbmcgZmFjdG9yIEFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHR1cwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUHJvYmFibGUgc3VjY2lueWwtZGlhbWlub3BpbWVsYXRlIGRlc3VjY2lueWxhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtHMTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJYYW50aGluZSBwaG9zcGhvcmlib3N5bHRyYW5zZmVyYXNlIiwidmFsdWUiOiJBMEExUzhLUEg0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVW5kZWNhcHJlbnlsLWRpcGhvc3BoYXRhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtGUDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJVcmlkeWxhdGUga2luYXNlIiwidmFsdWUiOiJBMEEzMjhLOTUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUGhvc3Bob2NhcnJpZXIgcHJvdGVpbiBIUHIiLCJ2YWx1ZSI6IkEwQTFTOEtOMjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQaG9zcGhvcGFudGV0aGVpbmUgYWRlbnlseWx0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0pMNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlByb3Rvbl9hbnRpcG9fTSBkb21haW4tY29udGFpbmluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHSkwzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiU0RSIGZhbWlseSBveGlkb3JlZHVjdGFzZSIsInZhbHVlIjoiQTBBNTE2R0dGMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdsdWNvc2UtNi1waG9zcGhhdGUgaXNvbWVyYXNlIiwidmFsdWUiOiJBMEEzMjhLM0oxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUHJvYmFibGUgbmljb3RpbmF0ZS1udWNsZW90aWRlIGFkZW55bHlsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKTDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJDYXJib2h5ZHJhdGUgQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlU3VnYXIgQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlIiwidmFsdWUiOiJBMEEzMjhLOTU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQW5hZXJvYmljIHJpYm9udWNsZW9zaWRlLXRyaXBob3NwaGF0ZSByZWR1Y3Rhc2UtYWN0aXZhdGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHR0Y5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQXJnaW5pbmUgZGVpbWluYXNlIiwidmFsdWUiOiJBMEEzMjhLUkc1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTkFEKFApSC1kZXBlbmRlbnQgb3hpZG9yZWR1Y3Rhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtCNzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblBvbHlhbWluZSBBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBMzI4S0s4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFzcGFyYWdpbmUgc3ludGhhc2UgKEdsdXRhbWluZS1oeWRyb2x5emluZyk2LjMuNS40IiwidmFsdWUiOiJBMEE1MTZHSVo1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiMSw0LWFscGhhLWdsdWNhbiBicmFuY2hpbmcgZW56eW1lIEdsZ0IiLCJ2YWx1ZSI6IkEwQTUxNkdJWjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiI1LW1ldGh5bHRldHJhaHlkcm9wdGVyb3lsdHJpZ2x1dGFtYXRlLS1ob21vY3lzdGVpbmUgbWV0aHlsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdIRzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHljZXJhdGUga2luYXNlIiwidmFsdWUiOiJBMEE1MTZHSEczIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUFRTIGdsdWNvc2UgdHJhbnNwb3J0ZXIgc3VidW5pdCBJSUEiLCJ2YWx1ZSI6IkEwQTUxNkdHRTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJVcmlkaW5lIGtpbmFzZSIsInZhbHVlIjoiQTBBMzI4SzgyMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFtaW5vbWV0aHlsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdJSTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBbWlkb2h5ZHJvbGFzZSIsInZhbHVlIjoiQTBBNTE2R0tNMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6ImRJVFAvWFRQIHB5cm9waG9zcGhhdGFzZSIsInZhbHVlIjoiQTBBMzI4S1JGMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdsdXRhdGhpb25lIGJpb3N5bnRoZXNpcyBiaWZ1bmN0aW9uYWwgcHJvdGVpbiBHc2hBQiIsInZhbHVlIjoiQTBBMzI4SzcwOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlJpYm9mbGF2aW4gYmlvc3ludGhlc2lzIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTMyOEs1TTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJDYXJib2h5ZHJhdGUgQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlIiwidmFsdWUiOiJBMEE1MTZHSEc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiSGVhdnkgbWV0YWwtYmluZGluZyBwcm90ZWluSGVhdnktbWV0YWwtYXNzb2NpYXRlZCBkb21haW4tY29udGFpbmluZyBwcm90ZWluIiwidmFsdWUiOiJBMEEzMjhLQ1k4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVHJrSCBmYW1pbHkgcG90YXNzaXVtIHVwdGFrZSBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHR0U2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiT2xpZ29wZXB0aWRlIEFCQyB0cmFuc3BvcnRlciBzdWJzdHJhdGUtYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHSEc2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR2x1dGFtYXRlIHJhY2VtYXNlIiwidmFsdWUiOiJBMEEzMjhLTFo0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR2x1dGFtaW5lLS1mcnVjdG9zZS02LXBob3NwaGF0ZSBhbWlub3RyYW5zZmVyYXNlIFtpc29tZXJpemluZ10iLCJ2YWx1ZSI6IkEwQTUxNkdJODAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBcnNlbmljYWwgcHVtcC1kcml2aW5nIEFUUGFzZSIsInZhbHVlIjoiQTBBNTE2R0hXOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEEzMjhLRk45In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVHJpb3NlLXBob3NwaGF0ZSBpc29tZXJhc2U1LjMuMS4xIiwidmFsdWUiOiJBMEE1MTZHSEY0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUHJvYmFibGUgZ2x5Y2luZSBkZWh5ZHJvZ2VuYXNlIChkZWNhcmJveHlsYXRpbmcpIHN1YnVuaXQgMiIsInZhbHVlIjoiQTBBNTE2R0lINSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlBlbmljaWxsaW4tYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHSko2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQWN5bCBjYXJyaWVyIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTFTOEtMTTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJSaWJvZmxhdmluIHRyYW5zcG9ydGVyIiwidmFsdWUiOiJBMEE1MTZHSko0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiT3JvdGlkaW5lIDUnLXBob3NwaGF0ZSBkZWNhcmJveHlsYXNlIiwidmFsdWUiOiJBMEEzMjhLNk4wIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRW5vbGFzZSIsInZhbHVlIjoiQTBBMVM4S0xNMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlBob3NwaG8tTi1hY2V0eWxtdXJhbW95bC1wZW50YXBlcHRpZGUtdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKSjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJDb2YtdHlwZSBIQUQtSUlCIGZhbWlseSBoeWRyb2xhc2VIYWxvYWNpZCBkZWhhbG9nZW5hc2UiLCJ2YWx1ZSI6IkEwQTFTOEtOMDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQdXRhdGl2ZSBOLWFjZXR5bG1hbm5vc2FtaW5lLTYtcGhvc3BoYXRlIDItZXBpbWVyYXNlIiwidmFsdWUiOiJBMEEzMjhLM0gwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVHJhbnNjcmlwdGlvbiB0ZXJtaW5hdGlvbiBmYWN0b3IgUmhvIiwidmFsdWUiOiJBMEE1MTZHSEY4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRGlwZXB0aWRhc2UgUGVwViIsInZhbHVlIjoiQTBBNTE2R0lIOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRyYW5zYWxkb2xhc2UyLjIuMS4yIiwidmFsdWUiOiJBMEE1MTZHSEY2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUHlydXZhdGUga2luYXNlIiwidmFsdWUiOiJBMEEzMjhLRDk3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVURQLU4tYWNldHlsZ2x1Y29zYW1pbmUtLU4tYWNldHlsbXVyYW15bC0ocGVudGFwZXB0aWRlKSBweXJvcGhvc3Bob3J5bC11bmRlY2FwcmVub2wgTi1hY2V0eWxnbHVjb3NhbWluZSB0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0pKNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFkZW55bG9zdWNjaW5hdGUgbHlhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdHVDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBbHBoYS1nbHljZXJvcGhvc3BoYXRlIG94aWRhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdIVjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHV0YXRoaW9uZSBwZXJveGlkYXNlIiwidmFsdWUiOiJBMEE1MTZHSTcxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiMy1veG9hY3lsLVthY3lsLWNhcnJpZXItcHJvdGVpbl0gc3ludGhhc2UgMiIsInZhbHVlIjoiQTBBNTE2R0dUOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdseWNvZ2VuIHN5bnRoYXNlIiwidmFsdWUiOiJBMEE1MTZHSVg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVGh5bWlkeWxhdGUga2luYXNlIiwidmFsdWUiOiJBMEE1MTZHSDU1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQWxwaGEtMSw0IGdsdWNhbiBwaG9zcGhvcnlsYXNlIiwidmFsdWUiOiJBMEE1MTZHSjk3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQVRQLWJpbmRpbmcgY2Fzc2V0dGUgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0lYNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlVEUC1nbHVjb3NlIDYtZGVoeWRyb2dlbmFzZSIsInZhbHVlIjoiQTBBNTE2R0lHNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNhdGlvbi10cmFuc2xvY2F0aW5nIFAtdHlwZSBBVFBhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdLSzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHZm8vSWRoL01vY0EgZmFtaWx5IG94aWRvcmVkdWN0YXNlIiwidmFsdWUiOiJBMEE1MTZHTE03In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTWV0aHlsZ2x5b3hhbCBzeW50aGFzZSIsInZhbHVlIjoiQTBBMVM4S1BMMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkRlb3h5cmlib3NlLXBob3NwaGF0ZSBhbGRvbGFzZSIsInZhbHVlIjoiQTBBNTE2R0lHMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik1ldGhpb25pbmUgQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTMyOEtVSjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHljZXJvbCBraW5hc2UiLCJ2YWx1ZSI6IkEwQTMyOEtEODMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQaG9zcGhhdGUgYWN5bHRyYW5zZmVyYXNlIiwidmFsdWUiOiJBMEE1MTZHSUc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQW1pbm9weXJpbWlkaW5lIGFtaW5vaHlkcm9sYXNlIiwidmFsdWUiOiJBMEE1MTZHSEU1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRm9ybWF0ZSBhY2V0eWx0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBMzI4S0xZMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik1ldmFsb25hdGUga2luYXNlMi43LjEuMzYiLCJ2YWx1ZSI6IkEwQTMyOEtEODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJFbm95bC1bYWN5bC1jYXJyaWVyLXByb3RlaW5dIHJlZHVjdGFzZSBGYWJLIiwidmFsdWUiOiJBMEE1MTZHR1MyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiU2VyaW5lIGh5ZHJveHltZXRoeWx0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0o4MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkZydWN0b3NlLTYtcGhvc3BoYXRlIGFsZG9sYXNlIiwidmFsdWUiOiJBMEE1MTZHSTYyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQnJhbmNoZWQtY2hhaW4gYW1pbm8gYWNpZCB0cmFuc3BvcnQgc3lzdGVtIGNhcnJpZXIgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0g0MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkVuZXJneS1jb3VwbGluZyBmYWN0b3IgdHJhbnNwb3J0ZXIgdHJhbnNtZW1icmFuZSBwcm90ZWluIEVjZlQiLCJ2YWx1ZSI6IkEwQTMyOEtKMzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiIyLDMtYmlzcGhvc3Bob2dseWNlcmF0ZS1pbmRlcGVuZGVudCBwaG9zcGhvZ2x5Y2VyYXRlIG11dGFzZSIsInZhbHVlIjoiQTBBMzI4S0RIMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ikh5ZHJveHlldGh5bHRoaWF6b2xlIGtpbmFzZSIsInZhbHVlIjoiQTBBNTE2R0lXNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRoeW1pZHlsYXRlIHN5bnRoYXNlIiwidmFsdWUiOiJBMEE1MTZHSVc2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiMy1veG9hY3lsLVthY3lsLWNhcnJpZXItcHJvdGVpbl0gcmVkdWN0YXNlIiwidmFsdWUiOiJBMEE1MTZHR1MzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQVRQLWRlcGVuZGVudCBETkEgaGVsaWNhc2UgUmVjUSIsInZhbHVlIjoiQTBBNTE2R0pIMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFzcGFydGF0ZSBjYXJiYW1veWx0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0xMNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNUUCBzeW50aGFzZSIsInZhbHVlIjoiQTBBNTE2R0hEMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNvcHBlci1leHBvcnRpbmcgUC10eXBlIEFUUGFzZSIsInZhbHVlIjoiQTBBNTE2R0tKMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFUUC1iaW5kaW5nIGNhc3NldHRlIGRvbWFpbi1jb250YWluaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdJRjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJDYXRhYm9saXRlIGNvbnRyb2wgcHJvdGVpbiBBIiwidmFsdWUiOiJBMEExUzhLTTQ2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdJRjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTMyOEs4UDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJTYXBlcCBmYW1pbHkgTW4oMispLWRlcGVuZGVudCBkaXBlcHRpZGFzZTMuNC4xMy4tIiwidmFsdWUiOiJBMEE1MTZHSEQ3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVURQLU4tYWNldHlsZ2x1Y29zYW1pbmUgMi1lcGltZXJhc2UgKE5vbi1oeWRyb2x5emluZyk1LjEuMy4xNCIsInZhbHVlIjoiQTBBNTE2R0lGOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlNvZGl1bTphbGFuaW5lIHN5bXBvcnRlciBmYW1pbHkgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0tKOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik9saWdvcGVwdGlkZSBBQkMgdHJhbnNwb3J0ZXIgc3Vic3RyYXRlLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0hENCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRoaW9yZWRveGluIiwidmFsdWUiOiJBMEExUzhLTVUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUGVwdGlkZSBtZXRoaW9uaW5lIHN1bGZveGlkZSByZWR1Y3Rhc2UgTXNyQiIsInZhbHVlIjoiQTBBMVM4S000MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHSDMwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdJVjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJETkEgbGlnYXNlIiwidmFsdWUiOiJBMEE1MTZHSjcxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVGhpb3JlZG94aW4gcmVkdWN0YXNlIiwidmFsdWUiOiJBMEEzMjhLSlMxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQnJhbmNoZWQtY2hhaW4tYW1pbm8tYWNpZCBhbWlub3RyYW5zZmVyYXNlIiwidmFsdWUiOiJBMEE1MTZHSzkwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR2x5Y2luZSBDLWFjZXR5bHRyYW5zZmVyYXNlMi4zLjEuMjkiLCJ2YWx1ZSI6IkEwQTMyOEtVWTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQUEgZmFtaWx5IEFUUGFzZSIsInZhbHVlIjoiQTBBNTE2R0hUNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IjMtaHlkcm94eS0zLW1ldGh5bGdsdXRhcnlsIGNvZW56eW1lIEEgcmVkdWN0YXNlIiwidmFsdWUiOiJBMEE1MTZHSzk4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiMi1hbWluby00LWh5ZHJveHktNi1oeWRyb3h5bWV0aHlsZGloeWRyb3B0ZXJpZGluZSBkaXBob3NwaG9raW5hc2UiLCJ2YWx1ZSI6IkEwQTUxNkdIMzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpblNvZGl1bSBBQkMgdHJhbnNwb3J0ZXIgQVRQLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBMVM4S1BLNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkxhY3RvbmFzZSBmYW1pbHkgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0gzMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdseWNlcm9sLTMtcGhvc3BoYXRlIGFjeWx0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0lWNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlVEUC1OLWFjZXR5bGVub2xweXJ1dm95bGdsdWNvc2FtaW5lIHJlZHVjdGFzZSIsInZhbHVlIjoiQTBBNTE2R0xLNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRyYW5za2V0b2xhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKRzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJCcmFuY2hlZC1jaGFpbiBhbWlubyBhY2lkIHRyYW5zcG9ydCBzeXN0ZW0gY2FycmllciBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHSUUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTFTOEtMUjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQaG9zcGhhdGUgYWNldHlsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdLSTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHV0YW1hdGUgZGVoeWRyb2dlbmFzZSIsInZhbHVlIjoiQTBBNTE2R0pHMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFjZXR5bC1Db0EgQy1hY2V0eWx0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0xLMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdNUCBzeW50aGFzZSBbZ2x1dGFtaW5lLWh5ZHJvbHl6aW5nXSIsInZhbHVlIjoiQTBBNTE2R0xLMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkRpaHlkcm9saXBveWwgZGVoeWRyb2dlbmFzZSIsInZhbHVlIjoiQTBBMzI4S1JSNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNsYXNzIElJIGZydWN0b3NlLWJpc3Bob3NwaGF0ZSBhbGRvbGFzZSIsInZhbHVlIjoiQTBBNTE2R0hDOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFkZW55bGF0ZSBraW5hc2UiLCJ2YWx1ZSI6IkEwQTFTOEtQWjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHVjb3NlLTEtcGhvc3BoYXRlIGFkZW55bHlsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTFTOEtONTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBbWlub3RyYW5zZmVyYXNlIGNsYXNzIFYtZm9sZCBQTFAtZGVwZW5kZW50IGVuenltZSIsInZhbHVlIjoiQTBBNTE2R0tJOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkhlYXZ5IG1ldGFsIHRyYW5zcG9ydGVyIiwidmFsdWUiOiJBMEExUzhLTVQ0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQVRQLWJpbmRpbmcgY2Fzc2V0dGUgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0tJNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkRlcGhvc3Boby1Db0Ega2luYXNlIiwidmFsdWUiOiJBMEE1MTZHSUU1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRGloeWRyb29yb3RhdGUgZGVoeWRyb2dlbmFzZSIsInZhbHVlIjoiQTBBNTE2R0xLOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFCQyB0cmFuc3BvcnRlciBwZXJtZWFzZSIsInZhbHVlIjoiQTBBMzI4S0IyMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdseWNvc2lkZSBoeWRyb2xhc2UgZmFtaWx5IDEgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0hDMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkRpaHlkcm9vcm90YXNlIiwidmFsdWUiOiJBMEE1MTZHSzgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVURQLU4tYWNldHlsZ2x1Y29zYW1pbmUgMS1jYXJib3h5dmlueWx0cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBNTE2R0s4MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkN5dGlkeWxhdGUga2luYXNlIiwidmFsdWUiOiJBMEEzMjhLTFYwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUmlib3NlLXBob3NwaGF0ZSBweXJvcGhvc3Bob2tpbmFzZSIsInZhbHVlIjoiQTBBMVM4S0xCNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFzcGFyYWdpbmFzZSIsInZhbHVlIjoiQTBBMzI4S0hOMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlNEUiBmYW1pbHkgTkFEKFApLWRlcGVuZGVudCBveGlkb3JlZHVjdGFzZSIsInZhbHVlIjoiQTBBNTE2R0hTOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRJR1IwMTQ1NyBmYW1pbHkgSEFELXR5cGUgaHlkcm9sYXNlIiwidmFsdWUiOiJBMEE1MTZHSlc5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlIiwidmFsdWUiOiJBMEE1MTZHSTQ1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUGVwdGlkZSBjaGFpbiByZWxlYXNlIGZhY3RvciAxIiwidmFsdWUiOiJBMEEzMjhLVDUyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTmEvUGkgY290cmFuc3BvcnRlciBmYW1pbHkgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0gyMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ikh5ZHJveHltZXRoeWxnbHV0YXJ5bC1Db0Egc3ludGhhc2UyLjMuMy4xMCIsInZhbHVlIjoiQTBBNTE2R0s4NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNhZG1pdW0tdHJhbnNsb2NhdGluZyBQLXR5cGUgQVRQYXNlMy42LjMuMyIsInZhbHVlIjoiQTBBNTE2R0o2NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlNvZGl1bTpwcm90b24gYW50aXBvcnRlciIsInZhbHVlIjoiQTBBMVM4S01TNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IjItZGVoeWRyb3BhbnRvYXRlIDItcmVkdWN0YXNlIiwidmFsdWUiOiJBMEEzMjhLUE04In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQW1pbm90cmFuc2ZlcmFzZSIsInZhbHVlIjoiQTBBMzI4SzlQMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IjUnLW1ldGh5bHRoaW9hZGVub3NpbmUvUy1hZGVub3N5bGhvbW9jeXN0ZWluZSBudWNsZW9zaWRhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKRjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2VTdWdhciBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTFTOEtOVTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQVFMgbWFubm9zZSBmYW1pbHkgdHJhbnNwb3J0ZXIgc3VidW5pdCBJSURQVFMgbWFubm9zZS9mcnVjdG9zZS9zb3Jib3NlIHRyYW5zcG9ydGVyIGZhbWlseSBzdWJ1bml0IElJRCIsInZhbHVlIjoiQTBBMzI4S0pBNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkhQciBraW5hc2UvcGhvc3Bob3J5bGFzZSIsInZhbHVlIjoiQTBBMzI4S0ZaMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik4oNCktKEJldGEtTi1hY2V0eWxnbHVjb3NhbWlueWwpLUwtYXNwYXJhZ2luYXNlIiwidmFsdWUiOiJBMEE1MTZHTEo4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRWxvbmdhdGlvbiBmYWN0b3IgRyIsInZhbHVlIjoiQTBBMVM4S1A4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlVEUC1OLWFjZXR5bG11cmFtb3lsYWxhbmluZS0tRC1nbHV0YW1hdGUgbGlnYXNlIiwidmFsdWUiOiJBMEE1MTZHSlYxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUGVwdGlkZSBtZXRoaW9uaW5lIHN1bGZveGlkZSByZWR1Y3Rhc2UgTXNyQSIsInZhbHVlIjoiQTBBMzI4S0MyNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlJuZkFCQ0RHRSB0eXBlIGVsZWN0cm9uIHRyYW5zcG9ydCBjb21wbGV4IHN1YnVuaXQgRCIsInZhbHVlIjoiQTBBMzI4S0IwOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkZlcnJlZG94aW4tLU5BRFAgcmVkdWN0YXNlIiwidmFsdWUiOiJBMEEzMjhLTVc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTWV0YWwgQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlWmluYyBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTMyOEs2OTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJDYWRtaXVtLXRyYW5zbG9jYXRpbmcgUC10eXBlIEFUUGFzZTMuNi4zLjMiLCJ2YWx1ZSI6IkEwQTUxNkdKNTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJSaWJ1bG9zZS1waG9zcGhhdGUgMy1lcGltZXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdJMzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiI2LXBob3NwaG9nbHVjb25hdGUgZGVoeWRyb2dlbmFzZSwgZGVjYXJib3h5bGF0aW5nIiwidmFsdWUiOiJBMEE1MTZHSjU0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRXh0cmFjZWxsdWxhciBzb2x1dGUtYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHTFo4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUmlib251Y2xlb3RpZGUgcmVkdWN0YXNlIHN0aW11bGF0b3J5IHByb3RlaW5SaWJvbnVjbGVvdGlkZSByZWR1Y3Rhc2Ugc3RpbXVsYXRvcnkgcHJvdGVpbiBOcmRJIiwidmFsdWUiOiJBMEExUzhLUEkzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTWFuZ2FuZXNlIHRyYW5zcG9ydGVyIiwidmFsdWUiOiJBMEE1MTZHTDk1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoicEggcmVndWxhdGlvbiBwcm90ZWluIEYiLCJ2YWx1ZSI6IkEwQTUxNkdMSTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQdXIgb3Blcm9uIHJlcHJlc3NvciIsInZhbHVlIjoiQTBBMzI4S0VGOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ikh5cG94YW50aGluZSBwaG9zcGhvcmlib3N5bHRyYW5zZmVyYXNlIiwidmFsdWUiOiJBMEExUzhLUVIxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiU21hbGwgcmlib3NvbWFsIHN1YnVuaXQgYmlvZ2VuZXNpcyBHVFBhc2UgUnNnQSIsInZhbHVlIjoiQTBBNTE2R0kyOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlB5cmlkaW5lIG51Y2xlb3RpZGUtZGlzdWxmaWRlIG94aWRvcmVkdWN0YXNlIiwidmFsdWUiOiJBMEE1MTZHSFE5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiVXJhY2lsIHBob3NwaG9yaWJvc3lsdHJhbnNmZXJhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtOSDUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHljaW5lIGNsZWF2YWdlIHN5c3RlbSBIIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTFTOEtNOTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJEVUY1NjEgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0xJOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik4tYWNldHlsZ2x1Y29zYW1pbmUtNi1waG9zcGhhdGUgZGVhY2V0eWxhc2UzLjUuMS4yNSIsInZhbHVlIjoiQTBBNTE2R0tHOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ikdmby9JZGgvTW9jQSBmYW1pbHkgb3hpZG9yZWR1Y3Rhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdLRzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHljb3NpZGUgaHlkcm9sYXNlIGZhbWlseSAxIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdIQTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJETkEgbWlzbWF0Y2ggcmVwYWlyIHByb3RlaW4gTXV0VE5VRElYIGh5ZHJvbGFzZSIsInZhbHVlIjoiQTBBMzI4S0JRMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlJpYm9zZS01LXBob3NwaGF0ZSBpc29tZXJhc2UgQSIsInZhbHVlIjoiQTBBNTE2R0s2MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6InRSTkEtc3BlY2lmaWMgYWRlbm9zaW5lIGRlYW1pbmFzZSIsInZhbHVlIjoiQTBBNTE2R0s2NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEE1MTZHSDAyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR1RQYXNlIEVyYSIsInZhbHVlIjoiQTBBNTE2R0lTMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNvZW56eW1lIEEgYmlvc3ludGhlc2lzIGJpZnVuY3Rpb25hbCBwcm90ZWluIENvYUJDIiwidmFsdWUiOiJBMEE1MTZHSTIwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiSGlzdGlkaW5lIGtpbmFzZSIsInZhbHVlIjoiQTBBNTE2R0w4NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Iklzb2Nob3Jpc21hdGFzZUlzb2Nob3Jpc21hdGFzZSBmYW1pbHkgcHJvdGVpbiIsInZhbHVlIjoiQTBBMzI4S0ZINSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlJPSyBmYW1pbHkgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0pVMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRoeW1pZGluZSBraW5hc2UiLCJ2YWx1ZSI6IkEwQTUxNkdLNjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBY3lsLUNvQSBkZWh5ZHJvZ2VuYXNlIiwidmFsdWUiOiJBMEE1MTZHSkQwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQWN5bHBob3NwaGF0YXNlIiwidmFsdWUiOiJBMEE1MTZHTEgwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIHN1YnN0cmF0ZS1iaW5kaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdLNTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJDeXRpZGluZSBkZWFtaW5hc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKMzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBZGVueWxvc3VjY2luYXRlIHN5bnRoZXRhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdLRjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJCaWZ1bmN0aW9uYWwgb2xpZ29yaWJvbnVjbGVhc2UvUEFQIHBob3NwaGF0YXNlIE5ybkEiLCJ2YWx1ZSI6IkEwQTUxNkdKRDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiIzMFMgcmlib3NvbWFsIHByb3RlaW4gUzEiLCJ2YWx1ZSI6IkEwQTFTOEtNWTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJCaWZ1bmN0aW9uYWwgaHlkcm94eW1ldGh5bHB5cmltaWRpbmUga2luYXNlL3Bob3NwaG9tZXRoeWxweXJpbWlkaW5lIGtpbmFzZTIuNy4xLjQ5Mi43LjQuNyIsInZhbHVlIjoiQTBBNTE2R0s1MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNsYXNzIElJIGZydWN0b3NlLTEsNi1iaXNwaG9zcGhhdGUgYWxkb2xhc2U0LjEuMi4xM0ZydWN0b3NlLTEsNi1iaXNwaG9zcGhhdGUgYWxkb2xhc2UsIGNsYXNzIElJIiwidmFsdWUiOiJBMEExUzhLUUE1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQ2FkbWl1bS10cmFuc2xvY2F0aW5nIFAtdHlwZSBBVFBhc2UzLjYuMy4zIiwidmFsdWUiOiJBMEEzMjhLRFM4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQU1QLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBMzI4S01VNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFsYW5pbmUgZGVoeWRyb2dlbmFzZSIsInZhbHVlIjoiQTBBNTE2R0pUNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Iklzb3BlbnRlbnlsLWRpcGhvc3BoYXRlIGRlbHRhLWlzb21lcmFzZSIsInZhbHVlIjoiQTBBNTE2R0s1OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdseWNlcmFsZGVoeWRlLTMtcGhvc3BoYXRlIGRlaHlkcm9nZW5hc2UiLCJ2YWx1ZSI6IkEwQTFTOEtMRzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJEaXBob3NwaG9tZXZhbG9uYXRlIGRlY2FyYm94eWxhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdLNTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQb2x5cHJlbnlsIHN5bnRoZXRhc2UgZmFtaWx5IHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdKMzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJVRFAtTi1hY2V0eWxnbHVjb3NhbWluZSAxLWNhcmJveHl2aW55bHRyYW5zZmVyYXNlIiwidmFsdWUiOiJBMEExUzhLUjI4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUGhvc3Bob3JpYm9zeWxmb3JteWxnbHljaW5hbWlkaW5lIHN5bnRoYXNlIEkiLCJ2YWx1ZSI6IkEwQTMyOEtMUzgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdJMTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJOQURIIG94aWRhc2UiLCJ2YWx1ZSI6IkEwQTMyOEtUUzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQaG9zcGhvcGVudG9tdXRhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKVDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJGbGF2b2RveGluIiwidmFsdWUiOiJBMEExUzhLTUk1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQ3lzdGVpbmUgZGVzdWxmdXJhc2UiLCJ2YWx1ZSI6IkEwQTUxNkdKVDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJFbG9uZ2F0aW9uIGZhY3RvciBUdSIsInZhbHVlIjoiQTBBMVM4S1BOMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ikh5ZHJveHlhY2lkIGRlaHlkcm9nZW5hc2UiLCJ2YWx1ZSI6IkEwQTUxNkdHTTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJMYW50aWJpb3RpYyBwcm90ZWN0aW9uIEFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBzdWJ1bml0IiwidmFsdWUiOiJBMEE1MTZHSlM4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTMyOEtQSjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHljZXJvbC0zLXBob3NwaGF0ZSBkZWh5ZHJvZ2VuYXNlIFtOQUQoUCkrXSIsInZhbHVlIjoiQTBBMzI4S0xCMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlB5cmltaWRpbmUtbnVjbGVvc2lkZSBwaG9zcGhvcnlsYXNlIiwidmFsdWUiOiJBMEE1MTZHSzQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIEFUUC1iaW5kaW5nIHByb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdMRzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJVdnJBQkMgc3lzdGVtIHByb3RlaW4gQSIsInZhbHVlIjoiQTBBNTE2R0lBMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik1hZ25lc2l1bSB0cmFuc3BvcnRlciBNZ3RFIiwidmFsdWUiOiJBMEE1MTZHSkMwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiSW5vc2luZS01Jy1tb25vcGhvc3BoYXRlIGRlaHlkcm9nZW5hc2UiLCJ2YWx1ZSI6IkEwQTFTOEtOOTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJSaWJvbnVjbGVvc2lkZS1kaXBob3NwaGF0ZSByZWR1Y3Rhc2Ugc3VidW5pdCBiZXRhIiwidmFsdWUiOiJBMEEzMjhLRlY1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUHVyaW5lIG51Y2xlb3NpZGUgcGhvc3Bob3J5bGFzZSBEZW9ELXR5cGUiLCJ2YWx1ZSI6IkEwQTFTOEtNSDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQZW5pY2lsbGluLWJpbmRpbmcgcHJvdGVpbiAyIiwidmFsdWUiOiJBMEE1MTZHSVE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR2x1Y29zYW1pbmUtNi1waG9zcGhhdGUgZGVhbWluYXNlIiwidmFsdWUiOiJBMEE1MTZHSzQ3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiSW5zdWxpbmFzZSBmYW1pbHkgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0kwMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlBCUDFBIGZhbWlseSBwZW5pY2lsbGluLWJpbmRpbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0oyMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNhcmJhbWF0ZSBraW5hc2UiLCJ2YWx1ZSI6IkEwQTFTOEtSMDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQVFMgZ2x1Y29zZSB0cmFuc3BvcnRlciBzdWJ1bml0IElJQkMiLCJ2YWx1ZSI6IkEwQTUxNkdITjgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHVjb2tpbmFzZSIsInZhbHVlIjoiQTBBMzI4SzNQNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdsdWNvc2UtNi1waG9zcGhhdGUgMS1kZWh5ZHJvZ2VuYXNlIiwidmFsdWUiOiJBMEE1MTZHSjE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTmljb3RpbmF0ZSBwaG9zcGhvcmlib3N5bHRyYW5zZmVyYXNlIiwidmFsdWUiOiJBMEE1MTZHSzM4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTGlwb3Byb3RlaW4iLCJ2YWx1ZSI6IkEwQTUxNkdHTDQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBQkMtRiBmYW1pbHkgQVRQLWJpbmRpbmcgY2Fzc2V0dGUgZG9tYWluLWNvbnRhaW5pbmcgcHJvdGVpbiIsInZhbHVlIjoiQTBBNTE2R0tENiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkNDQS1hZGRpbmcgZW56eW1lIiwidmFsdWUiOiJBMEE1MTZHTEY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlUGVwdGlkZSBBQkMgdHJhbnNwb3J0ZXIgcGVybWVhc2UiLCJ2YWx1ZSI6IkEwQTFTOEtMVTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJEaWh5ZHJvcHRlcm9hdGUgc3ludGhhc2UiLCJ2YWx1ZSI6IkEwQTMyOEs4STkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHV0YW1pbmUgc3ludGhldGFzZSBJIGFscGhhIiwidmFsdWUiOiJBMEEzMjhLUDgyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTkgoMyktZGVwZW5kZW50IE5BRCgrKSBzeW50aGV0YXNlIiwidmFsdWUiOiJBMEE1MTZHSzM2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQUJDIHRyYW5zcG9ydGVyIHBlcm1lYXNlUGVwdGlkZSBBQkMgdHJhbnNwb3J0ZXIiLCJ2YWx1ZSI6IkEwQTMyOEtONDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJMRC1jYXJib3h5cGVwdGlkYXNlIiwidmFsdWUiOiJBMEE1MTZHSVAyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUy1yaWJvc3lsaG9tb2N5c3RlaW5lIGx5YXNlIiwidmFsdWUiOiJBMEE1MTZHR0wwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQmV0YWluZS9wcm9saW5lL2Nob2xpbmUgZmFtaWx5IEFCQyB0cmFuc3BvcnRlciBBVFAtYmluZGluZyBwcm90ZWluIiwidmFsdWUiOiJBMEEzMjhLUFk4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQmlmdW5jdGlvbmFsIGh5ZHJveHltZXRoeWxweXJpbWlkaW5lIGtpbmFzZS9waG9zcGhvbWV0aHlscHlyaW1pZGluZSBraW5hc2UyLjcuMS40OTIuNy40LjciLCJ2YWx1ZSI6IkEwQTUxNkdKUjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQMTkyMzUiLCJ2YWx1ZSI6IlAxOTIzNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlAwMTU4OCIsInZhbHVlIjoiUDAxNTg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUTlOU0UyIiwidmFsdWUiOiJROU5TRTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJROVVNUjAiLCJ2YWx1ZSI6IlE5VU1SMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlEwMTgxMyIsInZhbHVlIjoiUTAxODEzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiUTlVSjczIiwidmFsdWUiOiJROVVKNzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJROVVNUTkiLCJ2YWx1ZSI6IlE5VU1ROSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlE5TlE4OCIsInZhbHVlIjoiUTlOUTg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiQmlzcGhvc3Bob2dseWNlcmF0ZSBtdXRhc2UiLCJ2YWx1ZSI6IlAwNzczOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IjYtcGhvc3Bob2ZydWN0by0yLWtpbmFzZS9mcnVjdG9zZS0yLDYtYmlzcGhvc3BoYXRhc2UgNCIsInZhbHVlIjoiUTE2ODc3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiNi1waG9zcGhvZnJ1Y3RvLTIta2luYXNlL2ZydWN0b3NlLTIsNi1iaXNwaG9zcGhhdGFzZSAzIiwidmFsdWUiOiJRMTY4NzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiI2LXBob3NwaG9mcnVjdG8tMi1raW5hc2UvZnJ1Y3Rvc2UtMiw2LWJpc3Bob3NwaGF0YXNlIDIiLCJ2YWx1ZSI6Ik82MDgyNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlBob3NwaG9nbHljZXJhdGUgbXV0YXNlIDIiLCJ2YWx1ZSI6IlAxNTI1OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkN5Y2xpbi1kZXBlbmRlbnQga2luYXNlIDEiLCJ2YWx1ZSI6IlAwNjQ5MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkcyL21pdG90aWMtc3BlY2lmaWMgY3ljbGluLUIxIiwidmFsdWUiOiJQMTQ2MzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBVFAtZGVwZW5kZW50IDYtcGhvc3Bob2ZydWN0b2tpbmFzZSwgbXVzY2xlIHR5cGUiLCJ2YWx1ZSI6IlAwODIzNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6Ik8xNDU0MyIsInZhbHVlIjoiTzE0NTQzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiU2lnbmFsIHRyYW5zZHVjZXIgYW5kIGFjdGl2YXRvciBvZiB0cmFuc2NyaXB0aW9uIDVBIiwidmFsdWUiOiJQNDIyMjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJUeXJvc2luZS1wcm90ZWluIHBob3NwaGF0YXNlIG5vbi1yZWNlcHRvciB0eXBlIDYiLCJ2YWx1ZSI6IlAyOTM1MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlR5cm9zaW5lLXByb3RlaW4ga2luYXNlIEpBSzIiLCJ2YWx1ZSI6Ik82MDY3NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlBob3NwaG9nbHljZXJhdGUga2luYXNlIDIiLCJ2YWx1ZSI6IlAwNzIwNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdseWNlcmFsZGVoeWRlLTMtcGhvc3BoYXRlIGRlaHlkcm9nZW5hc2UsIHRlc3Rpcy1zcGVjaWZpYyIsInZhbHVlIjoiTzE0NTU2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRnJ1Y3Rvc2UtMSw2LWJpc3Bob3NwaGF0YXNlIDEiLCJ2YWx1ZSI6IlAwOTQ2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkdhbW1hLWVub2xhc2UiLCJ2YWx1ZSI6IlAwOTEwNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlBob3NwaG9nbHljZXJhdGUga2luYXNlIDEiLCJ2YWx1ZSI6IlAwMDU1OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkFUUC1kZXBlbmRlbnQgNi1waG9zcGhvZnJ1Y3Rva2luYXNlLCBsaXZlciB0eXBlIiwidmFsdWUiOiJQMTc4NTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQaG9zcGhvZ2x5Y2VyYXRlIG11dGFzZSAxIiwidmFsdWUiOiJQMTg2NjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHljZXJhbGRlaHlkZS0zLXBob3NwaGF0ZSBkZWh5ZHJvZ2VuYXNlIiwidmFsdWUiOiJQMDQ0MDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJGcnVjdG9zZS0xLDYtYmlzcGhvc3BoYXRhc2UgaXNvenltZSAyIiwidmFsdWUiOiJPMDA3NTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJUcmlvc2VwaG9zcGhhdGUgaXNvbWVyYXNlIiwidmFsdWUiOiJQNjAxNzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJHbHVjb2tpbmFzZSIsInZhbHVlIjoiUDM1NTU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiNi1waG9zcGhvZnJ1Y3RvLTIta2luYXNlL2ZydWN0b3NlLTIsNi1iaXNwaG9zcGhhdGFzZSAxIiwidmFsdWUiOiJQMTYxMTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJBbHBoYS1lbm9sYXNlIiwidmFsdWUiOiJQMDY3MzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJCZXRhLWVub2xhc2UiLCJ2YWx1ZSI6IlAxMzkyOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlRyYW5zY3JpcHRpb24gZmFjdG9yIEUyRjEiLCJ2YWx1ZSI6IlEwMTA5NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkZydWN0b3NlLWJpc3Bob3NwaGF0ZSBhbGRvbGFzZSBCIiwidmFsdWUiOiJQMDUwNjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJQeXJ1dmF0ZSBraW5hc2UgUEtNIiwidmFsdWUiOiJQMTQ2MTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJGcnVjdG9zZS1iaXNwaG9zcGhhdGUgYWxkb2xhc2UgQyIsInZhbHVlIjoiUDA5OTcyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRnJ1Y3Rvc2UtYmlzcGhvc3BoYXRlIGFsZG9sYXNlIEEiLCJ2YWx1ZSI6IlAwNDA3NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkwtbGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlIEEtbGlrZSA2QiIsInZhbHVlIjoiUTlCWVoyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTC1sYWN0YXRlIGRlaHlkcm9nZW5hc2UgQSBjaGFpbiIsInZhbHVlIjoiUDAwMzM4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiTC1sYWN0YXRlIGRlaHlkcm9nZW5hc2UgQiBjaGFpbiIsInZhbHVlIjoiUDA3MTk1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiR2x1Y29zZS02LXBob3NwaGF0ZSBpc29tZXJhc2UiLCJ2YWx1ZSI6IlAwNjc0NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkwtbGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlIEMgY2hhaW4iLCJ2YWx1ZSI6IlAwNzg2NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IlB5cnV2YXRlIGtpbmFzZSBQS0xSIiwidmFsdWUiOiJQMzA2MTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIxIiwibGFiZWwiOiJJbnN1bGluIiwidmFsdWUiOiJQMDEzMDgifV0sImlkIjoiVU5JUFJPVCIsImxhYmVsIjoiVU5JUFJPVCIsInRvdGFsIjozNzN9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0IiwiZmFjZXRWYWx1ZXMiOlt7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJDSEVCSToxMTg3OSIsInZhbHVlIjoiQ0hFQkk6MTE4NzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJoeWRyb24iLCJ2YWx1ZSI6IkNIRUJJOjE1Mzc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNiIsImxhYmVsIjoiKFMpLWxhY3RhdGUiLCJ2YWx1ZSI6IkNIRUJJOjE2NjUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNiIsImxhYmVsIjoiTkFEKDEtKSIsInZhbHVlIjoiQ0hFQkk6NTc1NDAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJBRFAoMy0pIiwidmFsdWUiOiJDSEVCSTo0NTYyMTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJOQURIKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc5NDUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJnbHljZXJvbmUgcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc2NDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJBVFAoNC0pIiwidmFsdWUiOiJDSEVCSTozMDYxNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjYiLCJsYWJlbCI6Imh5ZHJvZ2VucGhvc3BoYXRlIiwidmFsdWUiOiJDSEVCSTo0MzQ3NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjYiLCJsYWJlbCI6InBob3NwaG9uYXRvZW5vbHB5cnV2YXRlIiwidmFsdWUiOiJDSEVCSTo1ODcwMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjYiLCJsYWJlbCI6IjItcGhvc3Bob25hdG8tRC1nbHljZXJhdGUoMy0pIiwidmFsdWUiOiJDSEVCSTo1ODI4OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjYiLCJsYWJlbCI6IndhdGVyIiwidmFsdWUiOiJDSEVCSToxNTM3NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjYiLCJsYWJlbCI6IkQtZ2x1Y29zZSA2LXBob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6MTQzMTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJweXJ1dmF0ZSIsInZhbHVlIjoiQ0hFQkk6MTUzNjEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI2IiwibGFiZWwiOiJiZXRhLUQtZnJ1Y3RvZnVyYW5vc2UgNi1waG9zcGhhdGUiLCJ2YWx1ZSI6IkNIRUJJOjE2MDg0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNiIsImxhYmVsIjoiMy1waG9zcGhvLUQtZ2x5Y2Vyb3lsIGRpaHlkcm9nZW4gcGhvc3BoYXRlIiwidmFsdWUiOiJDSEVCSToxNjAwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjYiLCJsYWJlbCI6IkQtZ2x5Y2VyYWxkZWh5ZGUgMy1waG9zcGhhdGUiLCJ2YWx1ZSI6IkNIRUJJOjI5MDUyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTUyODgiLCJ2YWx1ZSI6IkNIRUJJOjE1Mjg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6NTgwOTUiLCJ2YWx1ZSI6IkNIRUJJOjU4MDk1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTUyOTIiLCJ2YWx1ZSI6IkNIRUJJOjE1MjkyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTUyOTYiLCJ2YWx1ZSI6IkNIRUJJOjE1Mjk2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTA5NjYiLCJ2YWx1ZSI6IkNIRUJJOjEwOTY2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTIwOTQiLCJ2YWx1ZSI6IkNIRUJJOjEyMDk0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTUyNDQiLCJ2YWx1ZSI6IkNIRUJJOjE1MjQ0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTg2NDYiLCJ2YWx1ZSI6IkNIRUJJOjE4NjQ2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMwODMiLCJ2YWx1ZSI6IkNIRUJJOjEzMDgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMwODUiLCJ2YWx1ZSI6IkNIRUJJOjEzMDg1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTA5MjUiLCJ2YWx1ZSI6IkNIRUJJOjEwOTI1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTQwNjMiLCJ2YWx1ZSI6IkNIRUJJOjE0MDYzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMwOTYiLCJ2YWx1ZSI6IkNIRUJJOjEzMDk2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTIwNTciLCJ2YWx1ZSI6IkNIRUJJOjEyMDU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMwMTgiLCJ2YWx1ZSI6IkNIRUJJOjEzMDE4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMyNzQiLCJ2YWx1ZSI6IkNIRUJJOjEzMjc0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MjI0NyIsInZhbHVlIjoiQ0hFQkk6MjI0NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjE0MzcxIiwidmFsdWUiOiJDSEVCSToxNDM3MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjE0Mzc1IiwidmFsdWUiOiJDSEVCSToxNDM3NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjE4NzMwIiwidmFsdWUiOiJDSEVCSToxODczMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjQ4MzEzIiwidmFsdWUiOiJDSEVCSTo0ODMxMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjM1NjQxNiIsInZhbHVlIjoiQ0hFQkk6MzU2NDE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTQ0MzEiLCJ2YWx1ZSI6IkNIRUJJOjE0NDMxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTIxMjUiLCJ2YWx1ZSI6IkNIRUJJOjEyMTI1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6NjY5MTQiLCJ2YWx1ZSI6IkNIRUJJOjY2OTE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMxMTQiLCJ2YWx1ZSI6IkNIRUJJOjEzMTE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTQ0NDQiLCJ2YWx1ZSI6IkNIRUJJOjE0NDQ0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMwMDAiLCJ2YWx1ZSI6IkNIRUJJOjEzMDAwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTExODYiLCJ2YWx1ZSI6IkNIRUJJOjExMTg2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTQ0NTYiLCJ2YWx1ZSI6IkNIRUJJOjE0NDU2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTQ2OTgiLCJ2YWx1ZSI6IkNIRUJJOjE0Njk4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMxMjMiLCJ2YWx1ZSI6IkNIRUJJOjEzMTIzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTEwNjMiLCJ2YWx1ZSI6IkNIRUJJOjExMDYzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMxMjIiLCJ2YWx1ZSI6IkNIRUJJOjEzMTIyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6NDgwMDAiLCJ2YWx1ZSI6IkNIRUJJOjQ4MDAwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTExOTIiLCJ2YWx1ZSI6IkNIRUJJOjExMTkyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTMzNzQiLCJ2YWx1ZSI6IkNIRUJJOjEzMzc0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTExOTUiLCJ2YWx1ZSI6IkNIRUJJOjExMTk1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTQ2OTkiLCJ2YWx1ZSI6IkNIRUJJOjE0Njk5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MTI2OTMiLCJ2YWx1ZSI6IkNIRUJJOjEyNjkzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQ0hFQkk6MzI5MCIsInZhbHVlIjoiQ0hFQkk6MzI5MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjEyMTAyIiwidmFsdWUiOiJDSEVCSToxMjEwMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjEyMTAxIiwidmFsdWUiOiJDSEVCSToxMjEwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjEyMTAzIiwidmFsdWUiOiJDSEVCSToxMjEwMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjU4ODY2IiwidmFsdWUiOiJDSEVCSTo1ODg2NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjQ4ODI4IiwidmFsdWUiOiJDSEVCSTo0ODgyOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjU3NzkxIiwidmFsdWUiOiJDSEVCSTo1Nzc5MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjU4ODg1IiwidmFsdWUiOiJDSEVCSTo1ODg4NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjExMDMzIiwidmFsdWUiOiJDSEVCSToxMTAzMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjEwMDY2IiwidmFsdWUiOiJDSEVCSToxMDA2NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjEwMDY3IiwidmFsdWUiOiJDSEVCSToxMDA2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjExMzk4IiwidmFsdWUiOiJDSEVCSToxMTM5OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjE0Nzc5IiwidmFsdWUiOiJDSEVCSToxNDc3OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjEzNDQ1IiwidmFsdWUiOiJDSEVCSToxMzQ0NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNIRUJJOjg5NDQiLCJ2YWx1ZSI6IkNIRUJJOjg5NDQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMzczMyIsInZhbHVlIjoiQ0hFQkk6MTM3MzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMjg4OCIsInZhbHVlIjoiQ0hFQkk6MTI4ODgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMzUxMCIsInZhbHVlIjoiQ0hFQkk6MTM1MTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMjY1NiIsInZhbHVlIjoiQ0hFQkk6MTI2NTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMzc0NyIsInZhbHVlIjoiQ0hFQkk6MTM3NDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMzUwOCIsInZhbHVlIjoiQ0hFQkk6MTM1MDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ5MyIsInZhbHVlIjoiQ0hFQkk6MTA0OTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ5NCIsInZhbHVlIjoiQ0hFQkk6MTA0OTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ5NSIsInZhbHVlIjoiQ0hFQkk6MTA0OTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxNDk3MiIsInZhbHVlIjoiQ0hFQkk6MTQ5NzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMjQzMSIsInZhbHVlIjoiQ0hFQkk6MTI0MzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ5MSIsInZhbHVlIjoiQ0hFQkk6MTA0OTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ5MiIsInZhbHVlIjoiQ0hFQkk6MTA0OTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDI0NiIsInZhbHVlIjoiQ0hFQkk6MTAyNDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ4OSIsInZhbHVlIjoiQ0hFQkk6MTA0ODkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSTo2MTcyMyIsInZhbHVlIjoiQ0hFQkk6NjE3MjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMTQ3NCIsInZhbHVlIjoiQ0hFQkk6MTE0NzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMjY4NCIsInZhbHVlIjoiQ0hFQkk6MTI2ODQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ5NyIsInZhbHVlIjoiQ0hFQkk6MTA0OTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDQ5OSIsInZhbHVlIjoiQ0hFQkk6MTA0OTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMjMxNiIsInZhbHVlIjoiQ0hFQkk6MTIzMTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMTYzNiIsInZhbHVlIjoiQ0hFQkk6MTE2MzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxNDkwNCIsInZhbHVlIjoiQ0hFQkk6MTQ5MDQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxOTI1MCIsInZhbHVlIjoiQ0hFQkk6MTkyNTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSTo1NzYwOSIsInZhbHVlIjoiQ0hFQkk6NTc2MDkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMTg4NiIsInZhbHVlIjoiQ0hFQkk6MTE4ODYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMTQwOCIsInZhbHVlIjoiQ0hFQkk6MTE0MDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMjYyMyIsInZhbHVlIjoiQ0hFQkk6MTI2MjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMjYzNSIsInZhbHVlIjoiQ0hFQkk6MTI2MzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxNTA4MyIsInZhbHVlIjoiQ0hFQkk6MTUwODMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDc0NSIsInZhbHVlIjoiQ0hFQkk6MTA3NDUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDg2NiIsInZhbHVlIjoiQ0hFQkk6MTA4NjYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDUwMCIsInZhbHVlIjoiQ0hFQkk6MTA1MDAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSTo2ODYyMyIsInZhbHVlIjoiQ0hFQkk6Njg2MjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDg4OCIsInZhbHVlIjoiQ0hFQkk6MTA4ODgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMTczNiIsInZhbHVlIjoiQ0hFQkk6MTE3MzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDUyOSIsInZhbHVlIjoiQ0hFQkk6MTA1MjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDUzMCIsInZhbHVlIjoiQ0hFQkk6MTA1MzAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDUzMSIsInZhbHVlIjoiQ0hFQkk6MTA1MzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDUzMiIsInZhbHVlIjoiQ0hFQkk6MTA1MzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSToxMDUzMyIsInZhbHVlIjoiQ0hFQkk6MTA1MzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSTo1NzUwMSIsInZhbHVlIjoiQ0hFQkk6NTc1MDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJzaXJvaGVtZSg4LSkiLCJ2YWx1ZSI6IkNIRUJJOjYwMDUyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiRkFEKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTc2OTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJjb3BwZXIoMispIiwidmFsdWUiOiJDSEVCSToyOTAzNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Ikwtb3JuaXRoaW5pdW0oMSspIiwidmFsdWUiOiJDSEVCSTo0NjkxMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtZ2x1dGFtaW5lIHp3aXR0ZXJpb24iLCJ2YWx1ZSI6IkNIRUJJOjU4MzU5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiUy1hZGVub3N5bC1MLW1ldGhpb25pbmUgendpdHRlcmlvbiIsInZhbHVlIjoiQ0hFQkk6NTk3ODkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJMLWFsYW5pbmUgendpdHRlcmlvbiIsInZhbHVlIjoiQ0hFQkk6NTc5NzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJuaWNvdGluYXRlIiwidmFsdWUiOiJDSEVCSTozMjU0NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtdmFsaW5lIHp3aXR0ZXJpb24iLCJ2YWx1ZSI6IkNIRUJJOjU3NzYyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiUy1hZGVub3N5bC1MLWhvbW9jeXN0ZWluZSB6d2l0dGVyaW9uIiwidmFsdWUiOiJDSEVCSTo1Nzg1NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjQtYW1pbm8tNS1oeWRyb3h5bWV0aHlsLTItbWV0aHlscHlyaW1pZGluZSIsInZhbHVlIjoiQ0hFQkk6MTY4OTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDSEVCSV8xMzM1NiIsInZhbHVlIjoiQ0hFQkk6MTMzNTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiI0LXBob3NwaG9uYXRvLUwtYXNwYXJ0aWMgYWNpZCgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3NTM1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiNyw4LWRpaHlkcm9uZW9wdGVyaW4gMyctcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTg3NjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJkZWFtaWRvLU5BRCgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4NDM3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiNC1hbWluby0yLW1ldGh5bC01LWRpcGhvc3Bob25hdG9veHltZXRoeWxweXJpbWlkaW5lKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTc4NDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJ0aGlhbWluZSgxKykgZGlwaG9zcGhhdGUoMy0pIiwidmFsdWUiOiJDSEVCSTo1ODkzNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IigyUyktMi1pc29wcm9weWxtYWxhdGUoMi0pIiwidmFsdWUiOiJDSEVCSToxMTc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoibW9seWJkYXRlIiwidmFsdWUiOiJDSEVCSTozNjI2NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IlhUUCIsInZhbHVlIjoiQ0hFQkk6MTAwNDkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJnbHljb2xpYyBhY2lkIiwidmFsdWUiOiJDSEVCSToxNzQ5NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6InBoZW55bGFjZXR5bC1Db0EiLCJ2YWx1ZSI6IkNIRUJJOjE1NTM3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoibmlja2VsKDIrKSIsInZhbHVlIjoiQ0hFQkk6NDk3ODYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJkaXBob3NwaG9yaWMgYWNpZCIsInZhbHVlIjoiQ0hFQkk6Mjk4ODgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiI0LWFtaW5vYmVuem9hdGUiLCJ2YWx1ZSI6IkNIRUJJOjE3ODM2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoidGhpb3JlZG94aW4iLCJ2YWx1ZSI6IkNIRUJJOjE1MDMzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiQURQLUQtcmlib3NlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc5NjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJweXJpZG94YWwgNSctcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTk3MzI2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoibmljb3RpbmFtaWRlIiwidmFsdWUiOiJDSEVCSToxNzE1NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjcsOC1kaWh5ZHJvbmVvcHRlcmluIiwidmFsdWUiOiJDSEVCSToxNzAwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6InppbmMoMispIiwidmFsdWUiOiJDSEVCSToyOTEwNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjUtcGhvc3Boby1iZXRhLUQtcmlib3N5bGFtaW5lIiwidmFsdWUiOiJDSEVCSTozNzczNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjcsOC1kaWh5ZHJvbmVvcHRlcmluIDMnLXRyaXBob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6MTgzNzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJELWdsdWNvc2FtaW5lIDYtcGhvc3BoYXRlIiwidmFsdWUiOiJDSEVCSToxMjk2MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IihSKS00Jy1waG9zcGhvbmF0b3BhbnRvdGhlbmF0ZSgzLSkiLCJ2YWx1ZSI6IkNIRUJJOjEwOTg2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoicG90YXNzaXVtKDErKSIsInZhbHVlIjoiQ0hFQkk6MjkxMDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJ0aGlhbWluZSgxKykgbW9ub3Bob3NwaGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjM3NTc1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiTi1bKFIpLTQtcGhvc3Bob25hdG9wYW50b3RoZW5veWxdLUwtY3lzdGVpbmF0ZSgzLSkiLCJ2YWx1ZSI6IkNIRUJJOjU5NDU4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiR1RQKDQtKSIsInZhbHVlIjoiQ0hFQkk6Mzc1NjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJELWdseWNlcmF0ZSIsInZhbHVlIjoiQ0hFQkk6MTY2NTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiIzJy1kZXBob3NwaG8tQ29BKDItKSIsInZhbHVlIjoiQ0hFQkk6NTczMjgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJOTU4oLSkiLCJ2YWx1ZSI6IkNIRUJJOjE0NjQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiZm9ybWFsZGVoeWRlIiwidmFsdWUiOiJDSEVCSToxNjg0MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Im1hZ25lc2l1bSgyKykiLCJ2YWx1ZSI6IkNIRUJJOjE4NDIwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiRk1OKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTgyMTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJiaW90aW5hdGUiLCJ2YWx1ZSI6IkNIRUJJOjU3NTg2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiTy1waG9zcGhvLUwtc2VyaW5lIiwidmFsdWUiOiJDSEVCSToxNTgxMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Imlzb2J1dHlyeWwtQ29BKDQtKSIsInZhbHVlIjoiQ0hFQkk6NTczMzgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJwcmV1cm9wb3JwaHlyaW5vZ2VuKDgtKSIsInZhbHVlIjoiQ0hFQkk6NTc4NDUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJVRFAtTi1hY2V0eWwtYWxwaGEtRC1nbHVjb3NhbWluZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3NzA1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoibWV0aHlsZ2x5b3hhbCIsInZhbHVlIjoiQ0hFQkk6MTcxNTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiIyLWFjZXRhbWlkby0yLWRlb3h5LUQtZ2x1Y29weXJhbm9zZSAxLXBob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6NzEyNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Imlyb24oMispIiwidmFsdWUiOiJDSEVCSToyOTAzMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Im5pY290aW5hdGUgRC1yaWJvbnVjbGVvdGlkZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3NTAyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiaHlkcm94aWRvZGlveGlkb3N1bGZpZG9zdWxmYXRlKDEtKSIsInZhbHVlIjoiQ0hFQkk6MzM1MzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJmdW1hcmljIGFjaWQiLCJ2YWx1ZSI6IkNIRUJJOjE4MDEyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoic3VjY2luaWMgYWNpZCIsInZhbHVlIjoiQ0hFQkk6MTU3NDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJzb2RpdW0oMSspIiwidmFsdWUiOiJDSEVCSToyOTEwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjItb3hvZ2x1dGFyYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6MTY4MTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiI1LVsoNS1waG9zcGhvLTEtZGVveHktRC1yaWJ1bG9zLTEteWxpbWlubyltZXRoeWxhbWlub10tMS0oNS1waG9zcGhvLWJldGEtRC1yaWJvc3lsKWltaWRhem9sZS00LWNhcmJveGFtaWRlKDQtKSIsInZhbHVlIjoiQ0hFQkk6NTg1MjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJkaWh5ZHJvZm9sYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc0NTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiI1LWFtaW5vLTYtKDUtcGhvc3Bob3JpYml0eWxhbWlubyl1cmFjaWwoMi0pIiwidmFsdWUiOiJDSEVCSTo1ODQyMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Ik5BRFBIKDQtKSIsInZhbHVlIjoiQ0hFQkk6NTc3ODMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiIyJy1kZW94eWd1YW5vc2luZSA1Jy1tb25vcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc2NzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJhZGVub3NpbmUgNSctbW9ub3Bob3NwaGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjQ1NjIxNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjItaXNvcHJvcHlsbWFsZWF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MDg1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMi1oeWRyb3h5LTMtb3hvYnV0eWwgcGhvc3BoYXRlIiwidmFsdWUiOiJDSEVCSTo1MDYwNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6InRoaW9yZWRveGluIGRpc3VsZmlkZSIsInZhbHVlIjoiQ0hFQkk6MTgxOTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiIyLXRyYW5zLDYtdHJhbnMtZmFybmVzeWwgZGlwaG9zcGhhdGUoMy0pIiwidmFsdWUiOiJDSEVCSToxNzU3NjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiIxRC1teW8taW5vc2l0b2wgMS1waG9zcGhhdGUoMi0pIiwidmFsdWUiOiJDSEVCSTo1ODQzMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjUtYW1pbm8tNi0oRC1yaWJpdHlsYW1pbm8pdXJhY2lsIiwidmFsdWUiOiJDSEVCSToxNTkzNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IihOKG9tZWdhKS1MLWFyZ2luaW5vKXN1Y2NpbmF0ZSgxLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3NDcyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiU0FJQ0FSKDQtKSIsInZhbHVlIjoiQ0hFQkk6NTg0NDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiI2LDctZGltZXRoeWwtOC0oMS1ELXJpYml0eWwpbHVtYXppbmUoMS0pIiwidmFsdWUiOiJDSEVCSTo1ODIwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6InNuLWdseWNlcm9sIDMtcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc1OTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiIxLSg1LXBob3NwaG8tYmV0YS1ELXJpYm9zeWwpLTUtWyg1LXBob3NwaG8tYmV0YS1ELXJpYm9zeWxhbWlubyltZXRoeWxpZGVuZWFtaW5vXWltaWRhem9sZS00LWNhcmJveGFtaWRlKDQtKSIsInZhbHVlIjoiQ0hFQkk6NTg0MzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJhY2V0b2FjZXR5bC1Db0EoNC0pIiwidmFsdWUiOiJDSEVCSTo1NzI4NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6ImFjZXR5bC1Db0EoNC0pIiwidmFsdWUiOiJDSEVCSTo1NzI4OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6ImNvZW56eW1lIEEoNC0pIiwidmFsdWUiOiJDSEVCSTo1NzI4NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6InRSTkEoQ3lzKSIsInZhbHVlIjoiQ0hFQkk6MjkxNjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJDVFAoNC0pIiwidmFsdWUiOiJDSEVCSTozNzU2MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6InRSTkEoUHJvKSIsInZhbHVlIjoiQ0hFQkk6MjkxNzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiI1LU8tcGhvc3Bob25hdG8tYWxwaGEtRC1yaWJvZnVyYW5vc3lsIGRpcGhvc3BoYXRlKDUtKSIsInZhbHVlIjoiQ0hFQkk6NTgwMTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJndWFub3NpbmUgNSctbW9ub3Bob3NwaGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MTE1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiKFMpLW1hbGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjE1NTg5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiaXNvcGVudGVueWwgZGlwaG9zcGhhdGUoMy0pIiwidmFsdWUiOiJDSEVCSToxMjg3NjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJOQURQKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTgzNDkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJjYXJiYW1veWwgcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTgyMjgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJOLWFjZXR5bC1MLWdhbW1hLWdsdXRhbXlsIHBob3NwaGF0ZSgzLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3OTM2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMictZGVveHlhZGVub3NpbmUgNSctbW9ub3Bob3NwaGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MjQ1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiKDJSLDNTKS0zLWlzb3Byb3B5bG1hbGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjM1MTIxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiZ2VyYW55bCBkaXBob3NwaGF0ZSgzLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MDU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMy1waG9zcGhvbmF0b294eXB5cnV2YXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6MTgxMTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJHRFAoMy0pIiwidmFsdWUiOiJDSEVCSTo1ODE4OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkNEUCgzLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MDY5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMy1tZXRoeWwtMi1veG9idXRhbm9hdGUiLCJ2YWx1ZSI6IkNIRUJJOjExODUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiUHJvLXRSTkEoUHJvKSIsInZhbHVlIjoiQ0hFQkk6MjkxNTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJyaWJvZmxhdmluKDEtKSIsInZhbHVlIjoiQ0hFQkk6NTc5ODYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJwcmVueWwgZGlwaG9zcGhhdGUoMy0pIiwidmFsdWUiOiJDSEVCSTo1NzYyMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtaGlzdGlkaW5vbCIsInZhbHVlIjoiQ0hFQkk6MTYyNTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJhbW1vbmlhIiwidmFsdWUiOiJDSEVCSToxNjEzNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Ikwtc2VyaW5lIiwidmFsdWUiOiJDSEVCSToxNzExNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtaGlzdGlkaW5lIiwidmFsdWUiOiJDSEVCSToxNTk3MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IihSKS1wYW50b2F0ZSIsInZhbHVlIjoiQ0hFQkk6MTU5ODAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJMLXRyeXB0b3BoYW4iLCJ2YWx1ZSI6IkNIRUJJOjE2ODI4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiTi1jYXJiYW1veWwtTC1hc3BhcnRhdGUoMi0pIiwidmFsdWUiOiJDSEVCSTozMjgxNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjInLWRlb3h5YWRlbm9zaW5lIiwidmFsdWUiOiJDSEVCSToxNzI1NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6ImJldGEtYWxhbmluZSIsInZhbHVlIjoiQ0hFQkk6MTY5NTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJJRFAiLCJ2YWx1ZSI6IkNIRUJJOjE3ODA4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiZGV0aGlvYmlvdGluIiwidmFsdWUiOiJDSEVCSToxNjY5MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjItZGVoeWRyb3BhbnRvYXRlIiwidmFsdWUiOiJDSEVCSToxMTU2MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Im94YWxvYWNldGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjE2NDUyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiYWRlbm9zaW5lIiwidmFsdWUiOiJDSEVCSToxNjMzNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtYXJnaW5pbmUiLCJ2YWx1ZSI6IkNIRUJJOjE2NDY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoic3VjY2lueWwtQ29BIiwidmFsdWUiOiJDSEVCSToxNTM4MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtYXNwYXJ0YXRlIDQtc2VtaWFsZGVoeWRlIiwidmFsdWUiOiJDSEVCSToxMzA4NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtcHJvbGluZSIsInZhbHVlIjoiQ0hFQkk6MTcyMDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJMLWNpdHJ1bGxpbmUiLCJ2YWx1ZSI6IkNIRUJJOjE2MzQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiNC1tZXRoeWwtMi1veG9wZW50YW5vYXRlIiwidmFsdWUiOiJDSEVCSToxNzg2NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjMtKDQtaHlkcm94eXBoZW55bClweXJ1dmF0ZSIsInZhbHVlIjoiQ0hFQkk6MzYyNDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJjYXJib24gZGlveGlkZSIsInZhbHVlIjoiQ0hFQkk6MTY1MjYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJMLWlzb2xldWNpbmUiLCJ2YWx1ZSI6IkNIRUJJOjE3MTkxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiYWxwaGEtRC1yaWJvc2UgMS1waG9zcGhhdGUiLCJ2YWx1ZSI6IkNIRUJJOjE2MzAwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMictZGVveHljeXRpZGluZSIsInZhbHVlIjoiQ0hFQkk6MTU2OTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJOKDIpLWFjZXR5bC1MLW9ybml0aGluZSIsInZhbHVlIjoiQ0hFQkk6MTY1NDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJMLWdsdXRhbWF0ZSgxLSkiLCJ2YWx1ZSI6IkNIRUJJOjI5OTg1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiY2hvcmlzbWF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjI5NzQ4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiTC10eXJvc2luZSIsInZhbHVlIjoiQ0hFQkk6MTc4OTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJhbnRocmFuaWxhdGUiLCJ2YWx1ZSI6IkNIRUJJOjE2NTY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMy1kZWh5ZHJvcXVpbmF0ZSIsInZhbHVlIjoiQ0hFQkk6MzIzNjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJteW8taW5vc2l0b2wiLCJ2YWx1ZSI6IkNIRUJJOjE3MjY4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiTi1hY2V0eWwtTC1nbHV0YW1hdGUoMi0pIiwidmFsdWUiOiJDSEVCSTo0NDMzNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IjItYWNldGFtaWRvLTUtb3hvcGVudGFub2F0ZSIsInZhbHVlIjoiQ0hFQkk6MjkxMjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiI4LWFtaW5vLTctb3hvbm9uYW5vYXRlIiwidmFsdWUiOiJDSEVCSToxMjI2NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6Iigxcyw0cyktcHJlcGhlbmF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjI5OTM0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMy1kZWh5ZHJvc2hpa2ltYXRlIiwidmFsdWUiOiJDSEVCSToxNjYzMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6IkwtaGlzdGlkaW5vbCBwaG9zcGhhdGUiLCJ2YWx1ZSI6IkNIRUJJOjE2OTk2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiTC1seXNpbmUiLCJ2YWx1ZSI6IkNIRUJJOjE4MDE5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiMictZGVveHlndWFub3NpbmUiLCJ2YWx1ZSI6IkNIRUJJOjE3MTcyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiZ2x5Y2luZSIsInZhbHVlIjoiQ0hFQkk6MTU0MjgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJjYWxjaXVtKDIrKSIsInZhbHVlIjoiQ0hFQkk6MjkxMDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI1IiwibGFiZWwiOiJhY2V0YWxkZWh5ZGUiLCJ2YWx1ZSI6IkNIRUJJOjE1MzQzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiYWxwaGEsYWxwaGEtdHJlaGFsb3NlIiwidmFsdWUiOiJDSEVCSToxNjU1MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjUiLCJsYWJlbCI6ImFjZXRhdGUiLCJ2YWx1ZSI6IkNIRUJJOjMwMDg5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNSIsImxhYmVsIjoiaHlkcm9nZW4gcGVyb3hpZGUiLCJ2YWx1ZSI6IkNIRUJJOjE2MjQwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiNy1jYXJib3h5bGF0by03LWRlYXphZ3VhbmluZSIsInZhbHVlIjoiQ0hFQkk6NjEwMzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiI1LDYsNyw4LXRldHJhaHlkcm9wdGVyaW4tNi1jYXJib3h5bGF0ZSIsInZhbHVlIjoiQ0hFQkk6NjEwMzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiI3LWN5YW5vLTctZGVhemFndWFuaW5lIiwidmFsdWUiOiJDSEVCSTo0NTA3NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6IkNIRUJJOjE2NzgyIiwidmFsdWUiOiJDSEVCSToxNjc4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6IkNIRUJJOjYyNzI3IiwidmFsdWUiOiJDSEVCSTo2MjcyNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6ImFkZW5vc3lsY29iaW5hbWlkZSBndWFub3N5bCBkaXBob3NwaGF0ZSgxLSkiLCJ2YWx1ZSI6IkNIRUJJOjYwNDg3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiY29iKElJKXlyaW5pYyBhY2lkIGEsYyBkaWFtaWRlIiwidmFsdWUiOiJDSEVCSToyNzkzNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6ImFkZW5vc3lsY29iaW5hbWlkZSBwaG9zcGhhdGUoMS0pIiwidmFsdWUiOiJDSEVCSTo1ODUwMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6ImFkZW5vc3lsY29ieXJhdGUiLCJ2YWx1ZSI6IkNIRUJJOjU4NTA0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQkk6MTIwMDMiLCJ2YWx1ZSI6IkNIRUJJOjEyMDAzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQkk6MTMzMjkiLCJ2YWx1ZSI6IkNIRUJJOjEzMzI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQkk6MTMzMjgiLCJ2YWx1ZSI6IkNIRUJJOjEzMzI4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQkk6NTg2OTgiLCJ2YWx1ZSI6IkNIRUJJOjU4Njk4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQklfMTI3NjgiLCJ2YWx1ZSI6IkNIRUJJOjEyNzY4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQkk6NjI4MzAiLCJ2YWx1ZSI6IkNIRUJJOjYyODMwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQklfMjMzMjkiLCJ2YWx1ZSI6IkNIRUJJOjIzMzI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiKFIpLTEtYW1tb25pb3Byb3Bhbi0yLXlsIHBob3NwaGF0ZSgxLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4NTYzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQkk6MTAyNjEiLCJ2YWx1ZSI6IkNIRUJJOjEwMjYxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiMictZGVveHlpbm9zaW5lIDUnLWRpcGhvc3BoYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6NjIyODYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiJDSEVCSToxMTY1MiIsInZhbHVlIjoiQ0hFQkk6MTE2NTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiIoNyw4LWRpaHlkcm9wdGVyaW4tNi15bCltZXRoeWwgZGlwaG9zcGhhdGUiLCJ2YWx1ZSI6IkNIRUJJOjczMDgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiQ0hFQkk6MTE2NzMiLCJ2YWx1ZSI6IkNIRUJJOjExNjczIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoidHVuZ3N0YXRlIiwidmFsdWUiOiJDSEVCSTo0NjUwMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6InNpcm9oeWRyb2NobG9yaW4oOC0pIiwidmFsdWUiOiJDSEVCSTo1ODM1MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6InBpbWVsb3lsLUNvQSg1LSkiLCJ2YWx1ZSI6IkNIRUJJOjU3MzYwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiMi10cmFucyw2LXRyYW5zLDEwLXRyYW5zLWdlcmFueWxnZXJhbnlsIGRpcGhvc3BoYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTg3NTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiJkVERQLWFscGhhLUQtZ2x1Y29zZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3NDc3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiZFREUC00LWRlaHlkcm8tNi1kZW94eS1hbHBoYS1ELWdsdWNvc2UoMi0pIiwidmFsdWUiOiJDSEVCSTo1NzY0OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6ImRURFAtNi1kZW94eS1iZXRhLUwtbWFubm9zZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3NTEwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiaW5kb2xlLTMtYWNldGljIGFjaWQiLCJ2YWx1ZSI6IkNIRUJJOjE2NDExIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiNC1hbWluby0yLW1ldGh5bC01LXBob3NwaG9uYXRvb3h5bWV0aHlscHlyaW1pZGluZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MzU0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoicHJlY29ycmluLTIoNy0pIiwidmFsdWUiOiJDSEVCSTo1ODgyNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6ImltaW5vYXNwYXJ0YXRlIiwidmFsdWUiOiJDSEVCSTo1ODgzMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6ImtldG8tcGhlbnlscHlydXZpYyBhY2lkIiwidmFsdWUiOiJDSEVCSTozMDg1MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6IjIsNS1kaWFtaW5vLTQtaHlkcm94eS02LSg1LXBob3NwaG9yaWJvc3lsYW1pbm8pcHlyaW1pZGluZSIsInZhbHVlIjoiQ0hFQkk6MjkxMTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiJxdWlub2xpbmF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjI5OTU5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiRC1yaWJvc3lsbmljb3RpbmF0ZSIsInZhbHVlIjoiQ0hFQkk6NTg1MjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiJOLXJpYm9zeWxuaWNvdGluYW1pZGUiLCJ2YWx1ZSI6IkNIRUJJOjE1OTI3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiMi1kZW94eS1ELXJpYm9zZSA1LXBob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6MTYxMzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiJjYXJib24gbW9ub3hpZGUiLCJ2YWx1ZSI6IkNIRUJJOjE3MjQ1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiaHlkcm9nZW4gY3lhbmlkZSIsInZhbHVlIjoiQ0hFQkk6MTg0MDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiJTLW1ldGh5bC01LXRoaW8tYWxwaGEtRC1yaWJvc2UgMS1waG9zcGhhdGUoMi0pIiwidmFsdWUiOiJDSEVCSTo1ODUzMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6InRoaW9jeWFuYXRlIiwidmFsdWUiOiJDSEVCSToxODAyMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6IjItb3hvYWRpcGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3NDk5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiKDJTKS0yLWlzb3Byb3B5bC0zLW94b3N1Y2NpbmF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjE3MjE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiNCIsImxhYmVsIjoiKFIpLTUtcGhvc3Bob25hdG9tZXZhbG9uYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTgxNDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiIoUiktbWV2YWxvbmF0ZSIsInZhbHVlIjoiQ0hFQkk6MzY0NjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiI0IiwibGFiZWwiOiJkaWh5ZHJvZ2VuIiwidmFsdWUiOiJDSEVCSToxODI3NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjQiLCJsYWJlbCI6InN1cGVyb3hpZGUiLCJ2YWx1ZSI6IkNIRUJJOjE4NDIxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTgzMjQiLCJ2YWx1ZSI6IkNIRUJJOjE4MzI0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTA4NDEiLCJ2YWx1ZSI6IkNIRUJJOjEwODQxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTg3ODMiLCJ2YWx1ZSI6IkNIRUJJOjE4NzgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTQzMzMiLCJ2YWx1ZSI6IkNIRUJJOjE0MzMzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjM1OSIsInZhbHVlIjoiQ0hFQkk6MjM1OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjE0MzQyIiwidmFsdWUiOiJDSEVCSToxNDM0MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjE0MzQxIiwidmFsdWUiOiJDSEVCSToxNDM0MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU5Nzc2IiwidmFsdWUiOiJDSEVCSTo1OTc3NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjE0ODEyIiwidmFsdWUiOiJDSEVCSToxNDgxMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjI2MDU0IiwidmFsdWUiOiJDSEVCSToyNjA1NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJXzI2MDU1IiwidmFsdWUiOiJDSEVCSToyNjA1NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjgxNDciLCJ2YWx1ZSI6IkNIRUJJOjgxNDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToyNjQ2NiIsInZhbHVlIjoiQ0hFQkk6MjY0NjYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToyNjQ2MiIsInZhbHVlIjoiQ0hFQkk6MjY0NjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxNDk4NyIsInZhbHVlIjoiQ0hFQkk6MTQ5ODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMjM1MiIsInZhbHVlIjoiQ0hFQkk6MTIzNTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1NDQ2IiwidmFsdWUiOiJDSEVCSTo1NDQ2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NTQ1NCIsInZhbHVlIjoiQ0hFQkk6NTQ1NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjQ1MjUzIiwidmFsdWUiOiJDSEVCSTo0NTI1MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjExODgwIiwidmFsdWUiOiJDSEVCSToxMTg4MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjExODgxIiwidmFsdWUiOiJDSEVCSToxMTg4MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjE2NTgiLCJ2YWx1ZSI6IkNIRUJJOjE2NTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxNjU3IiwidmFsdWUiOiJDSEVCSToxNjU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6Mzk4NjgiLCJ2YWx1ZSI6IkNIRUJJOjM5ODY4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjQzNTUiLCJ2YWx1ZSI6IkNIRUJJOjI0MzU1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTEwNjUiLCJ2YWx1ZSI6IkNIRUJJOjExMDY1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTMyMzYiLCJ2YWx1ZSI6IkNIRUJJOjEzMjM2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTI2NyIsInZhbHVlIjoiQ0hFQkk6MTI2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjIyMjQ5IiwidmFsdWUiOiJDSEVCSToyMjI0OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjQwOTM4IiwidmFsdWUiOiJDSEVCSTo0MDkzOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjEyNDExIiwidmFsdWUiOiJDSEVCSToxMjQxMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjM3NzM2IiwidmFsdWUiOiJDSEVCSTozNzczNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjQ0ODk0IiwidmFsdWUiOiJDSEVCSTo0NDg5NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjg2ODUiLCJ2YWx1ZSI6IkNIRUJJOjg2ODUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToyMzc5NTgiLCJ2YWx1ZSI6IkNIRUJJOjIzNzk1OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjIwOTk5IiwidmFsdWUiOiJDSEVCSToyMDk5OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjM5NTcxIiwidmFsdWUiOiJDSEVCSTozOTU3MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjEzMzUyIiwidmFsdWUiOiJDSEVCSToxMzM1MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjIwMTg5IiwidmFsdWUiOiJDSEVCSToyMDE4OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjE4MSIsInZhbHVlIjoiQ0hFQkk6MTgxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTAzNzUiLCJ2YWx1ZSI6IkNIRUJJOjEwMzc1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjEwMjgiLCJ2YWx1ZSI6IkNIRUJJOjIxMDI4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjEwMjkiLCJ2YWx1ZSI6IkNIRUJJOjIxMDI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjEwMjYiLCJ2YWx1ZSI6IkNIRUJJOjIxMDI2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjI3NjgiLCJ2YWx1ZSI6IkNIRUJJOjIyNzY4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTA3ODkiLCJ2YWx1ZSI6IkNIRUJJOjEwNzg5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTI5NjUiLCJ2YWx1ZSI6IkNIRUJJOjEyOTY1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTE2NTEiLCJ2YWx1ZSI6IkNIRUJJOjExNjUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTI5ODYiLCJ2YWx1ZSI6IkNIRUJJOjEyOTg2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTI5ODQiLCJ2YWx1ZSI6IkNIRUJJOjEyOTg0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTI5ODMiLCJ2YWx1ZSI6IkNIRUJJOjEyOTgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTI5ODciLCJ2YWx1ZSI6IkNIRUJJOjEyOTg3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTA3NDMiLCJ2YWx1ZSI6IkNIRUJJOjEwNzQzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDIzNzgiLCJ2YWx1ZSI6IkNIRUJJOjQyMzc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NjAwNDkiLCJ2YWx1ZSI6IkNIRUJJOjYwMDQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTM3NDA0IiwidmFsdWUiOiJDSEVCSToxMzc0MDQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxNzExNCIsInZhbHVlIjoiQ0hFQkk6MTcxMTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJjb2JhbHQtcHJlY29ycmluLTQoNy0pIiwidmFsdWUiOiJDSEVCSTo2MDA2MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjYwMDYyIiwidmFsdWUiOiJDSEVCSTo2MDA2MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6ImNvYmFsdC1wcmVjb3JyaW4tMyg4LSkiLCJ2YWx1ZSI6IkNIRUJJOjYwMDYwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6ODUxMDYiLCJ2YWx1ZSI6IkNIRUJJOjg1MTA2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MzUwOTgiLCJ2YWx1ZSI6IkNIRUJJOjM1MDk4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MzM5MDciLCJ2YWx1ZSI6IkNIRUJJOjMzOTA3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTcwMTAiLCJ2YWx1ZSI6IkNIRUJJOjE3MDEwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjgyNjUiLCJ2YWx1ZSI6IkNIRUJJOjI4MjY1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoicGh5dHlsIGRpcGhvc3BoYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6NzU0MzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJjb2VuenltZSBnYW1tYS1GNDIwLTIoNS0pIiwidmFsdWUiOiJDSEVCSTo1NzkyMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjYwMDYzIiwidmFsdWUiOiJDSEVCSTo2MDA2MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjQwMzU2IiwidmFsdWUiOiJDSEVCSTo0MDM1NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjYwMDY0IiwidmFsdWUiOiJDSEVCSTo2MDA2NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IihSKS00LXBob3NwaG9uYXRvcGFudG9hdGUoMy0pIiwidmFsdWUiOiJDSEVCSTo2MTI5NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjc0MDA3IiwidmFsdWUiOiJDSEVCSTo3NDAwNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjc0MDA0IiwidmFsdWUiOiJDSEVCSTo3NDAwNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkwtaGlzdGlkaW5hbCgxKykiLCJ2YWx1ZSI6IkNIRUJJOjY0ODAyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTMzODgiLCJ2YWx1ZSI6IkNIRUJJOjEzMzg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjI1MSIsInZhbHVlIjoiQ0hFQkk6MjI1MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkQtY2l0cmFtYWxhdGUoMi0pIiwidmFsdWUiOiJDSEVCSTozMDkzNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjcyNzc4IiwidmFsdWUiOiJDSEVCSTo3Mjc3OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjcyNzc3IiwidmFsdWUiOiJDSEVCSTo3Mjc3NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjI3Nzg3IiwidmFsdWUiOiJDSEVCSToyNzc4NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjcxMzEwIiwidmFsdWUiOiJDSEVCSTo3MTMxMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU4NTk2IiwidmFsdWUiOiJDSEVCSTo1ODU5NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJXzIwOTI0IiwidmFsdWUiOiJDSEVCSToyMDkyNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjc0ODUzIiwidmFsdWUiOiJDSEVCSTo3NDg1MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjI4NzczIiwidmFsdWUiOiJDSEVCSToyODc3MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU3MjU4IiwidmFsdWUiOiJDSEVCSTo1NzI1OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU4MjQ2IiwidmFsdWUiOiJDSEVCSTo1ODI0NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjgzNDMxIiwidmFsdWUiOiJDSEVCSTo4MzQzMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU4MTE2IiwidmFsdWUiOiJDSEVCSTo1ODExNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjYyNzg0IiwidmFsdWUiOiJDSEVCSTo2Mjc4NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjEzNDE3NiIsInZhbHVlIjoiQ0hFQkk6MTM0MTc2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NjI3ODYiLCJ2YWx1ZSI6IkNIRUJJOjYyNzg2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NzI2OTgiLCJ2YWx1ZSI6IkNIRUJJOjcyNjk4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiY29iYWx0LXByZWNvcnJpbi02QiIsInZhbHVlIjoiQ0hFQkk6Mzc4OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjcyNjk0IiwidmFsdWUiOiJDSEVCSTo3MjY5NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IlVEUC1hbHBoYS1ELWdhbGFjdG9mdXJhbm9zZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjY2OTE1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTM3Njk2IiwidmFsdWUiOiJDSEVCSToxMzc2OTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo2MDQ2NCIsInZhbHVlIjoiQ0hFQkk6NjA0NjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODE1MSIsInZhbHVlIjoiQ0hFQkk6NTgxNTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTozMDcxOSIsInZhbHVlIjoiQ0hFQkk6MzA3MTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3MzY4MiIsInZhbHVlIjoiQ0hFQkk6NzM2ODIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODI4NiIsInZhbHVlIjoiQ0hFQkk6NTgyODYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiIyLSgyLWNhcmJveHktNC1tZXRoeWx0aGlhem9sLTUteWwpZXRoeWwgcGhvc3BoYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6NjI4OTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMzc2NzgiLCJ2YWx1ZSI6IkNIRUJJOjEzNzY3OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6ImNvYmFsdC1wcmVjb3JyaW4tOCIsInZhbHVlIjoiQ0hFQkk6Mzc5NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6Imh5ZHJveHlsYW1pbmUiLCJ2YWx1ZSI6IkNIRUJJOjE1NDI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiKDJTLDRSLDVTLDZSKS02LShoeWRyb3h5bWV0aHlsKW94YW5lLTIsNCw1LXRyaW9sIiwidmFsdWUiOiJDSEVCSToxMjU2ODQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo0ODU5NSIsInZhbHVlIjoiQ0hFQkk6NDg1OTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODg5MCIsInZhbHVlIjoiQ0hFQkk6NTg4OTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODQxMSIsInZhbHVlIjoiQ0hFQkk6NTg0MTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1NzY3NyIsInZhbHVlIjoiQ0hFQkk6NTc2NzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJwcmVwaHl0b2VuZSBkaXBob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6MTQ4ODUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMzkxNTEiLCJ2YWx1ZSI6IkNIRUJJOjEzOTE1MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjY1MDc4IiwidmFsdWUiOiJDSEVCSTo2NTA3OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IihTKS1OQURIWCgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjY0MDc0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiKFIpLU5BREhYKDItKSIsInZhbHVlIjoiQ0hFQkk6NjQwNzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo2NDA3NiIsInZhbHVlIjoiQ0hFQkk6NjQwNzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo2NDA3NyIsInZhbHVlIjoiQ0hFQkk6NjQwNzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3MjcxMyIsInZhbHVlIjoiQ0hFQkk6NzI3MTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3MjcxMiIsInZhbHVlIjoiQ0hFQkk6NzI3MTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3MjcxMCIsInZhbHVlIjoiQ0hFQkk6NzI3MTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3Mjk1MSIsInZhbHVlIjoiQ0hFQkk6NzI5NTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODUwMyIsInZhbHVlIjoiQ0hFQkk6NTg1MDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiIoMlIpLWhvbW9jaXRyYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTg4ODQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo4ODA1NCIsInZhbHVlIjoiQ0hFQkk6ODgwNTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo4ODA1NSIsInZhbHVlIjoiQ0hFQkk6ODgwNTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3MjcwMSIsInZhbHVlIjoiQ0hFQkk6NzI3MDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToyNzk3MyIsInZhbHVlIjoiQ0hFQkk6Mjc5NzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiI1LDYsNyw4LXRldHJhaHlkcm9tZXRoYW5vcHRlcmluKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTgxMDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJMLWxhY3R5bC0yLWRpcGhvc3Boby01Jy1ndWFub3NpbmUoMy0pIiwidmFsdWUiOiJDSEVCSTo1OTQzNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjcxNTE3IiwidmFsdWUiOiJDSEVCSTo3MTUxNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IjUsMTAtKG1ldGhhbnlseWxpZGVuZSl0ZXRyYWh5ZHJvbWV0aGFub3B0ZXJpbigyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MzM3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6ODI3NjciLCJ2YWx1ZSI6IkNIRUJJOjgyNzY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6ODA1ODciLCJ2YWx1ZSI6IkNIRUJJOjgwNTg3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiKDJTLDRTKS00LWh5ZHJveHktMiwzLDQsNS10ZXRyYWh5ZHJvZGlwaWNvbGluYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NjcxMzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo2NzEzOCIsInZhbHVlIjoiQ0hFQkk6NjcxMzgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToyMzQ0MiIsInZhbHVlIjoiQ0hFQkk6MjM0NDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo4MzQwNyIsInZhbHVlIjoiQ0hFQkk6ODM0MDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3MDc5MSIsInZhbHVlIjoiQ0hFQkk6NzA3OTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODU1NiIsInZhbHVlIjoiQ0hFQkk6NTg1NTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODMxOSIsInZhbHVlIjoiQ0hFQkk6NTgzMTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3OTk3MiIsInZhbHVlIjoiQ0hFQkk6Nzk5NzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJhbHBoYS1ELXJpYm9zZSIsInZhbHVlIjoiQ0hFQkk6NDU1MDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiIyLXBob3NwaG9uYXRvLUwtbGFjdGF0ZSgzLSkiLCJ2YWx1ZSI6IkNIRUJJOjU5OTA2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiRjQyMC0wKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTk5MDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJMLWZ1Y3Vsb3NlIDEtcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc4NDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTozNDYwNiIsInZhbHVlIjoiQ0hFQkk6MzQ2MDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiI3LDgtZGlkZW1ldGh5bC04LWh5ZHJveHktNS1kZWF6YXJpYm9mbGF2aW4oMS0pIiwidmFsdWUiOiJDSEVCSTo1OTkwNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU3NzI3IiwidmFsdWUiOiJDSEVCSTo1NzcyNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IjUsMTAtbWV0aHlsZW5ldGV0cmFoeWRyb21ldGhhbm9wdGVyaW4oMy0pIiwidmFsdWUiOiJDSEVCSTo1NzgxOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU4ODYwIiwidmFsdWUiOiJDSEVCSTo1ODg2MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU3NTMwIiwidmFsdWUiOiJDSEVCSTo1NzUzMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU4ODYxIiwidmFsdWUiOiJDSEVCSTo1ODg2MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6Im1ldGhhbmUiLCJ2YWx1ZSI6IkNIRUJJOjE2MTgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NzA3MzEiLCJ2YWx1ZSI6IkNIRUJJOjcwNzMxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MzQ3NzMiLCJ2YWx1ZSI6IkNIRUJJOjM0NzczIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTMxMzUwIiwidmFsdWUiOiJDSEVCSToxMzEzNTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODg1NCIsInZhbHVlIjoiQ0hFQkk6NTg4NTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODg1OSIsInZhbHVlIjoiQ0hFQkk6NTg4NTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo1ODczOCIsInZhbHVlIjoiQ0hFQkk6NTg3MzgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTozMTEyOCIsInZhbHVlIjoiQ0hFQkk6MzExMjgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJjb2VuenltZSBGNDIwLTEoNC0pIiwidmFsdWUiOiJDSEVCSTo1OTkyMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU4ODM3IiwidmFsdWUiOiJDSEVCSTo1ODgzNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU4ODM4IiwidmFsdWUiOiJDSEVCSTo1ODgzOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjQ0ODE5IiwidmFsdWUiOiJDSEVCSTo0NDgxOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU1OTQiLCJ2YWx1ZSI6IkNIRUJJOjU1OTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMzQxOSIsInZhbHVlIjoiQ0hFQkk6MTM0MTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTozMDQ5MCIsInZhbHVlIjoiQ0hFQkk6MzA0OTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMzM5NiIsInZhbHVlIjoiQ0hFQkk6MTMzOTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo0NDY0MSIsInZhbHVlIjoiQ0hFQkk6NDQ2NDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMzM5NSIsInZhbHVlIjoiQ0hFQkk6MTMzOTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMzM5NCIsInZhbHVlIjoiQ0hFQkk6MTMzOTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTozOTc0NSIsInZhbHVlIjoiQ0hFQkk6Mzk3NDUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToxMzM5MyIsInZhbHVlIjoiQ0hFQkk6MTMzOTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSTo3NDIyIiwidmFsdWUiOiJDSEVCSTo3NDIyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NzQyMyIsInZhbHVlIjoiQ0hFQkk6NzQyMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjM5NzM5IiwidmFsdWUiOiJDSEVCSTozOTczOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjI3MzEzIiwidmFsdWUiOiJDSEVCSToyNzMxMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjQyODU3IiwidmFsdWUiOiJDSEVCSTo0Mjg1NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjQwNTUzIiwidmFsdWUiOiJDSEVCSTo0MDU1MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjc3OTMiLCJ2YWx1ZSI6IkNIRUJJOjc3OTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToyOTg2NyIsInZhbHVlIjoiQ0hFQkk6Mjk4NjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJwaG9zcGhvcmljIGFjaWQiLCJ2YWx1ZSI6IkNIRUJJOjI2MDc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6ODc1MTgiLCJ2YWx1ZSI6IkNIRUJJOjg3NTE4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjE5MDEiLCJ2YWx1ZSI6IkNIRUJJOjIxOTAxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjE5MDIiLCJ2YWx1ZSI6IkNIRUJJOjIxOTAyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDQyMTQiLCJ2YWx1ZSI6IkNIRUJJOjQ0MjE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTQ3OTEiLCJ2YWx1ZSI6IkNIRUJJOjE0NzkxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDQyMTUiLCJ2YWx1ZSI6IkNIRUJJOjQ0MjE1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDQyMTYiLCJ2YWx1ZSI6IkNIRUJJOjQ0MjE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTMyMjIiLCJ2YWx1ZSI6IkNIRUJJOjEzMjIyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTA3NDQiLCJ2YWx1ZSI6IkNIRUJJOjEwNzQ0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDIwNDMiLCJ2YWx1ZSI6IkNIRUJJOjQyMDQzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDQyOTIiLCJ2YWx1ZSI6IkNIRUJJOjQ0MjkyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDUwMjQiLCJ2YWx1ZSI6IkNIRUJJOjQ1MDI0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjkxMzciLCJ2YWx1ZSI6IkNIRUJJOjI5MTM3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6Mjk0MTIiLCJ2YWx1ZSI6IkNIRUJJOjI5NDEyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjkxMzkiLCJ2YWx1ZSI6IkNIRUJJOjI5MTM5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTMzNjUiLCJ2YWx1ZSI6IkNIRUJJOjEzMzY1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDMyMjgiLCJ2YWx1ZSI6IkNIRUJJOjQzMjI4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDQyODEiLCJ2YWx1ZSI6IkNIRUJJOjQ0MjgxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDM0NzAiLCJ2YWx1ZSI6IkNIRUJJOjQzNDcwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MTMzNTciLCJ2YWx1ZSI6IkNIRUJJOjEzMzU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NDQ3MDEiLCJ2YWx1ZSI6IkNIRUJJOjQ0NzAxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NTU4NSIsInZhbHVlIjoiQ0hFQkk6NTU4NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkNIRUJJOjU1ODQiLCJ2YWx1ZSI6IkNIRUJJOjU1ODQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJDSEVCSToyMzQyIiwidmFsdWUiOiJDSEVCSToyMzQyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6MjIyNDQiLCJ2YWx1ZSI6IkNIRUJJOjIyMjQ0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQ0hFQkk6NjI0MTQiLCJ2YWx1ZSI6IkNIRUJJOjYyNDE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiMy1oeWRyb3h5LTMtbWV0aHlsLTItb3hvYnV0YW5vYXRlIiwidmFsdWUiOiJDSEVCSToxMTgxMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6ImdseWNlcm9sIDEtcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6MjMxOTM1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiMi1waG9zcGhvZ2x5Y2VyYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6ODgzNTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJveGlkZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjI5MzU2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiMi1waG9zcGhvZ2x5Y2VyaWMgYWNpZCIsInZhbHVlIjoiQ0hFQkk6MjQzNDQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJELWZydWN0b2Z1cmFub3NlIDEsNi1iaXNwaG9zcGhhdGUoNC0pIiwidmFsdWUiOiJDSEVCSTo0OTI5OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IigzUyktMy1oeWRyb3h5LTMtbWV0aHlsZ2x1dGFyeWwtQ29BKDUtKSIsInZhbHVlIjoiQ0hFQkk6NDMwNzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiIoLSktaG9tb2lzb2NpdHJhdGUoMy0pIiwidmFsdWUiOiJDSEVCSToxNTQwNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6ImRlaHlkcm9nbHljaW5hdGUiLCJ2YWx1ZSI6IkNIRUJJOjUzNjY0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiYmV0YS1ELWZydWN0b2Z1cmFub3NlIDYtcGhvc3BoYXRlKDItKSIsInZhbHVlIjoiQ0hFQkk6NTc2MzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJwaG9zcGhhdGUgaW9uIiwidmFsdWUiOiJDSEVCSTozNTc4MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IjMtKGluZG9sLTMteWwpcHlydXZpYyBhY2lkIiwidmFsdWUiOiJDSEVCSToyOTc1MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IjIsMy1iaXNwaG9zcGhvLUQtZ2x5Y2VyaWMgYWNpZCIsInZhbHVlIjoiQ0hFQkk6MTc3MjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJwaG9zcGhhdGUiLCJ2YWx1ZSI6IkNIRUJJOjI2MDIwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiMy1zdWxmb25hdG9weXJ1dmF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU3OTQwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiaHlkcm94aWRlIiwidmFsdWUiOiJDSEVCSToxNjIzNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkQtZ2x1Y29zZSIsInZhbHVlIjoiQ0hFQkk6MTc2MzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiIoKDE4KU8pd2F0ZXIiLCJ2YWx1ZSI6IkNIRUJJOjMzODEzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQVRQKDMtKSIsInZhbHVlIjoiQ0hFQkk6NTcyOTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJELWdsdWNvcHlyYW5vc2UgNi1waG9zcGhhdGUoMi0pIiwidmFsdWUiOiJDSEVCSTo2MTU0OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IjUnLVMtbWV0aHlsLTUnLXRoaW9hZGVub3NpbmUiLCJ2YWx1ZSI6IkNIRUJJOjE3NTA5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiMy1tZXJjYXB0b3B5cnV2YXRlIiwidmFsdWUiOiJDSEVCSTo1NzY3OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6InByb3Bpb25hdGUiLCJ2YWx1ZSI6IkNIRUJJOjE3MjcyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiYWxkZWh5ZG8tRC1yaWJvc2UgNS1waG9zcGhhdGUoMi0pIiwidmFsdWUiOiJDSEVCSTo1ODI3MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkQtZ2x1Y29weXJhbm9zZSA2LXBob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6NDE3MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6InBob3NwaG9lbm9scHlydXZpYyBhY2lkIiwidmFsdWUiOiJDSEVCSTo0NDg5NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6ImdseWNlcmFsZGVoeWRlIDMtcGhvc3BoYXRlIiwidmFsdWUiOiJDSEVCSToxNzEzOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IihTKS1sYWN0aWMgYWNpZCIsInZhbHVlIjoiQ0hFQkk6NDIyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoicHlydXZpYyBhY2lkIiwidmFsdWUiOiJDSEVCSTozMjgxNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6ImFscGhhLGFscGhhLXRyZWhhbG9zZSA2LXBob3NwaGF0ZSgyLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4NDI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiZ2x5Y2VyYWxkZWh5ZGUgMy1waG9zcGhhdGUoMi0pIiwidmFsdWUiOiJDSEVCSTo1ODAyNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IjMtcGhvc3Bob25hdG8tRC1nbHljZXJveWwgcGhvc3BoYXRlKDQtKSIsInZhbHVlIjoiQ0hFQkk6NTc2MDQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJidXQtMS1lbmUtMSwyLDQtdHJpY2FyYm94eWxhdGUiLCJ2YWx1ZSI6IkNIRUJJOjU4MTc0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiMy1waG9zcGhvbmF0by1ELWdseWNlcmF0ZSgzLSkiLCJ2YWx1ZSI6IkNIRUJJOjU4MjcyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoicGhvc3BoYXRlKDMtKSIsInZhbHVlIjoiQ0hFQkk6MTgzNjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJBVFAiLCJ2YWx1ZSI6IkNIRUJJOjE1NDIyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiMi1waG9zcGhvLUQtZ2x5Y2VyaWMgYWNpZCIsInZhbHVlIjoiQ0hFQkk6MTc4MzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJkaWh5ZHJveHlhY2V0b25lIHBob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6MTYxMDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJiZXRhLUQtZnJ1Y3RvZnVyYW5vc2UgMSw2LWJpc3Bob3NwaGF0ZSIsInZhbHVlIjoiQ0hFQkk6MjgwMTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIzIiwibGFiZWwiOiJOQURIIiwidmFsdWUiOiJDSEVCSToxNjkwOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6InBob3NwaG9lbm9scHlydXZhdGUiLCJ2YWx1ZSI6IkNIRUJJOjE4MDIxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiQURQIiwidmFsdWUiOiJDSEVCSToxNjc2MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IjMtcGhvc3Boby1ELWdseWNlcmljIGFjaWQiLCJ2YWx1ZSI6IkNIRUJJOjE3Nzk0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMyIsImxhYmVsIjoiTkFEKCspIiwidmFsdWUiOiJDSEVCSToxNTg0NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjMiLCJsYWJlbCI6IkQtZ2x1Y29weXJhbm9zZSIsInZhbHVlIjoiQ0hFQkk6NDE2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE2MTQ2IiwidmFsdWUiOiJDSEVCSToxNjE0NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE2MTY4IiwidmFsdWUiOiJDSEVCSToxNjE2OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE2OTY3IiwidmFsdWUiOiJDSEVCSToxNjk2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQ5MDgyIiwidmFsdWUiOiJDSEVCSTo0OTA4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjUzMTQ2IiwidmFsdWUiOiJDSEVCSTo1MzE0NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQzODM2IiwidmFsdWUiOiJDSEVCSTo0MzgzNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQzODI5IiwidmFsdWUiOiJDSEVCSTo0MzgyOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjM3MTU2IiwidmFsdWUiOiJDSEVCSTozNzE1NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1MTE2IiwidmFsdWUiOiJDSEVCSToyNTExNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1MTE3IiwidmFsdWUiOiJDSEVCSToyNTExNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1MTE4IiwidmFsdWUiOiJDSEVCSToyNTExOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1MTE5IiwidmFsdWUiOiJDSEVCSToyNTExOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDYwIiwidmFsdWUiOiJDSEVCSToyNjQ2MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyMDc1IiwidmFsdWUiOiJDSEVCSToyMjA3NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE4ODE4IiwidmFsdWUiOiJDSEVCSToxODgxOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE4ODE3IiwidmFsdWUiOiJDSEVCSToxODgxNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjU3NjY0IiwidmFsdWUiOiJDSEVCSTo1NzY2NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMjk1MSIsInZhbHVlIjoiQ0hFQkk6MTMyOTUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE0NTMiLCJ2YWx1ZSI6IkNIRUJJOjExNDUzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6ODk3NCIsInZhbHVlIjoiQ0hFQkk6ODk3NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE5MzgzIiwidmFsdWUiOiJDSEVCSToxOTM4MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE3MjU0IiwidmFsdWUiOiJDSEVCSToxNzI1NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjMyODA4IiwidmFsdWUiOiJDSEVCSTozMjgwOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjkzNyIsInZhbHVlIjoiQ0hFQkk6OTM3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTI0MTYiLCJ2YWx1ZSI6IkNIRUJJOjEyNDE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NTk5MTEiLCJ2YWx1ZSI6IkNIRUJJOjU5OTExIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NTk5MzAiLCJ2YWx1ZSI6IkNIRUJJOjU5OTMwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6ODg3IiwidmFsdWUiOiJDSEVCSTo4ODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMjcxNyIsInZhbHVlIjoiQ0hFQkk6MjI3MTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyODAyNyIsInZhbHVlIjoiQ0hFQkk6MjgwMjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozMDI5IiwidmFsdWUiOiJDSEVCSTozMDI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTM4NzkiLCJ2YWx1ZSI6IkNIRUJJOjEzODc5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjgwODAiLCJ2YWx1ZSI6IkNIRUJJOjI4MDgwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjAxMjciLCJ2YWx1ZSI6IkNIRUJJOjIwMTI3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjI3MjIiLCJ2YWx1ZSI6IkNIRUJJOjIyNzIyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDEwNTEiLCJ2YWx1ZSI6IkNIRUJJOjQxMDUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTgzMTUiLCJ2YWx1ZSI6IkNIRUJJOjE4MzE1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTk2NTIiLCJ2YWx1ZSI6IkNIRUJJOjE5NjUyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTk2MTQiLCJ2YWx1ZSI6IkNIRUJJOjE5NjE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTgzMDAiLCJ2YWx1ZSI6IkNIRUJJOjE4MzAwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTg5NyIsInZhbHVlIjoiQ0hFQkk6MTg5NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyMjE5IiwidmFsdWUiOiJDSEVCSToxMjIxOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjY2NTMiLCJ2YWx1ZSI6IkNIRUJJOjY2NTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2NjUyIiwidmFsdWUiOiJDSEVCSTo2NjUyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NTg0NTkiLCJ2YWx1ZSI6IkNIRUJJOjU4NDU5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NTg0NDIiLCJ2YWx1ZSI6IkNIRUJJOjU4NDQyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjkwNDUiLCJ2YWx1ZSI6IkNIRUJJOjI5MDQ1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTgzNTYiLCJ2YWx1ZSI6IkNIRUJJOjE4MzU2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTg0MDQiLCJ2YWx1ZSI6IkNIRUJJOjE4NDA0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjA3MzEiLCJ2YWx1ZSI6IkNIRUJJOjIwNzMxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE2NyIsInZhbHVlIjoiQ0hFQkk6MTE2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjExMzEiLCJ2YWx1ZSI6IkNIRUJJOjExMzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMTMyIiwidmFsdWUiOiJDSEVCSToxMTMyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTQ5ODYiLCJ2YWx1ZSI6IkNIRUJJOjE0OTg2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDUyNTEiLCJ2YWx1ZSI6IkNIRUJJOjQ1MjUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6Nzc2NjAiLCJ2YWx1ZSI6IkNIRUJJOjc3NjYwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE4NTciLCJ2YWx1ZSI6IkNIRUJJOjExODU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDM5NjIiLCJ2YWx1ZSI6IkNIRUJJOjQzOTYyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjIyMjUiLCJ2YWx1ZSI6IkNIRUJJOjYyMjI1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUwMzAiLCJ2YWx1ZSI6IkNIRUJJOjE1MDMwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTYzNjQiLCJ2YWx1ZSI6IkNIRUJJOjE2MzY0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjY1MjYiLCJ2YWx1ZSI6IkNIRUJJOjI2NTI2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTQ1NTgiLCJ2YWx1ZSI6IkNIRUJJOjE0NTU4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTQ1NTkiLCJ2YWx1ZSI6IkNIRUJJOjE0NTU5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTQ1NTciLCJ2YWx1ZSI6IkNIRUJJOjE0NTU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjQwMTAiLCJ2YWx1ZSI6IkNIRUJJOjY0MDEwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjQwMTYiLCJ2YWx1ZSI6IkNIRUJJOjY0MDE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDU1IiwidmFsdWUiOiJDSEVCSTo0NTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMDQ0MiIsInZhbHVlIjoiQ0hFQkk6MjA0NDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2NDAzNyIsInZhbHVlIjoiQ0hFQkk6NjQwMzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozMDc4MCIsInZhbHVlIjoiQ0hFQkk6MzA3ODAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMjAyNSIsInZhbHVlIjoiQ0hFQkk6MTIwMjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMjAwIiwidmFsdWUiOiJDSEVCSToyMjAwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTIwMzgiLCJ2YWx1ZSI6IkNIRUJJOjEyMDM4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6Nzg4MiIsInZhbHVlIjoiQ0hFQkk6Nzg4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjc4ODEiLCJ2YWx1ZSI6IkNIRUJJOjc4ODEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MjU2IiwidmFsdWUiOiJDSEVCSTo2MjU2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTYyODciLCJ2YWx1ZSI6IkNIRUJJOjE2Mjg3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTMxMjgiLCJ2YWx1ZSI6IkNIRUJJOjEzMTI4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjIxMTYiLCJ2YWx1ZSI6IkNIRUJJOjIyMTE2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTA2ODMiLCJ2YWx1ZSI6IkNIRUJJOjEwNjgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTM0NTciLCJ2YWx1ZSI6IkNIRUJJOjEzNDU3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTM0NzQiLCJ2YWx1ZSI6IkNIRUJJOjEzNDc0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6Njg2NzgiLCJ2YWx1ZSI6IkNIRUJJOjY4Njc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE1NyIsInZhbHVlIjoiQ0hFQkk6MTE1NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjk4MjQiLCJ2YWx1ZSI6IkNIRUJJOjk4MjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNTE3OSIsInZhbHVlIjoiQ0hFQkk6MTUxNzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMjgzNiIsInZhbHVlIjoiQ0hFQkk6MjI4MzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozNjIxNyIsInZhbHVlIjoiQ0hFQkk6MzYyMTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozNTQ2MiIsInZhbHVlIjoiQ0hFQkk6MzU0NjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MTM1MyIsInZhbHVlIjoiQ0hFQkk6NDEzNTMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMDM1OCIsInZhbHVlIjoiQ0hFQkk6MTAzNTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA3MSIsInZhbHVlIjoiQ0hFQkk6MTMwNzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA3MCIsInZhbHVlIjoiQ0hFQkk6MTMwNzAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA3OSIsInZhbHVlIjoiQ0hFQkk6MTMwNzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA4MCIsInZhbHVlIjoiQ0hFQkk6MTMwODAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MDYxNSIsInZhbHVlIjoiQ0hFQkk6NDA2MTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNDM2MyIsInZhbHVlIjoiQ0hFQkk6MTQzNjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozNTIzNiIsInZhbHVlIjoiQ0hFQkk6MzUyMzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJMLWFsYW55bC1MLWdsdXRhbWljIGFjaWQiLCJ2YWx1ZSI6IkNIRUJJOjYxNTY1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjIwOCIsInZhbHVlIjoiQ0hFQkk6NjIwOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyMDMzIiwidmFsdWUiOiJDSEVCSToyMjAzMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYyOTU4IiwidmFsdWUiOiJDSEVCSTo2Mjk1OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYyOTU5IiwidmFsdWUiOiJDSEVCSTo2Mjk1OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYyODciLCJ2YWx1ZSI6IkNIRUJJOjYyODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MjgzIiwidmFsdWUiOiJDSEVCSTo2MjgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjI0MyIsInZhbHVlIjoiQ0hFQkk6NjI0MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjU3NTMyIiwidmFsdWUiOiJDSEVCSTo1NzUzMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjc1ODUiLCJ2YWx1ZSI6IkNIRUJJOjc1ODUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo3NTgwIiwidmFsdWUiOiJDSEVCSTo3NTgwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjI2NyIsInZhbHVlIjoiQ0hFQkk6NjI2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYyNjIiLCJ2YWx1ZSI6IkNIRUJJOjYyNjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozMzU0MCIsInZhbHVlIjoiQ0hFQkk6MzM1NDAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzE4NyIsInZhbHVlIjoiQ0hFQkk6MTMxODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzE4NSIsInZhbHVlIjoiQ0hFQkk6MTMxODUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzEzOCIsInZhbHVlIjoiQ0hFQkk6MTMxMzgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzEzNyIsInZhbHVlIjoiQ0hFQkk6MTMxMzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzE1MiIsInZhbHVlIjoiQ0hFQkk6MTMxNTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzE1NSIsInZhbHVlIjoiQ0hFQkk6MTMxNTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzE3NiIsInZhbHVlIjoiQ0hFQkk6MTMxNzYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzE3OSIsInZhbHVlIjoiQ0hFQkk6MTMxNzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzExMiIsInZhbHVlIjoiQ0hFQkk6MTMxMTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzEyMCIsInZhbHVlIjoiQ0hFQkk6MTMxMjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MDc1MiIsInZhbHVlIjoiQ0hFQkk6NDA3NTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzEzNCIsInZhbHVlIjoiQ0hFQkk6MTMxMzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzEzMyIsInZhbHVlIjoiQ0hFQkk6MTMxMzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MzIyIiwidmFsdWUiOiJDSEVCSTo2MzIyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjMwOSIsInZhbHVlIjoiQ0hFQkk6NjMwOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYzMTgiLCJ2YWx1ZSI6IkNIRUJJOjYzMTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MzEyIiwidmFsdWUiOiJDSEVCSTo2MzEyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDQzOTYiLCJ2YWx1ZSI6IkNIRUJJOjQ0Mzk2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTI3NTgiLCJ2YWx1ZSI6IkNIRUJJOjEyNzU4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjA4NDYiLCJ2YWx1ZSI6IkNIRUJJOjIwODQ2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjA4NTAiLCJ2YWx1ZSI6IkNIRUJJOjIwODUwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6Njk2NyIsInZhbHVlIjoiQ0hFQkk6Njk2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIwODA4IiwidmFsdWUiOiJDSEVCSToyMDgwOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzNzY3IiwidmFsdWUiOiJDSEVCSToxMzc2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjcyIiwidmFsdWUiOiJDSEVCSToxMDY3MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjgxIiwidmFsdWUiOiJDSEVCSToxMDY4MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjgyIiwidmFsdWUiOiJDSEVCSToxMDY4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjc4IiwidmFsdWUiOiJDSEVCSToxMDY3OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjc5IiwidmFsdWUiOiJDSEVCSToxMDY3OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjczIiwidmFsdWUiOiJDSEVCSToxMDY3MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjc1IiwidmFsdWUiOiJDSEVCSToxMDY3NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjkxIiwidmFsdWUiOiJDSEVCSToxMDY5MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE5MDI1IiwidmFsdWUiOiJDSEVCSToxOTAyNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjkyIiwidmFsdWUiOiJDSEVCSToxMDY5MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjk0IiwidmFsdWUiOiJDSEVCSToxMDY5NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjkwIiwidmFsdWUiOiJDSEVCSToxMDY5MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjg4IiwidmFsdWUiOiJDSEVCSToxMDY4OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjg5IiwidmFsdWUiOiJDSEVCSToxMDY4OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjg0IiwidmFsdWUiOiJDSEVCSToxMDY4NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjg1IiwidmFsdWUiOiJDSEVCSToxMDY4NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjg2IiwidmFsdWUiOiJDSEVCSToxMDY4NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjg3IiwidmFsdWUiOiJDSEVCSToxMDY4NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjMyODg5IiwidmFsdWUiOiJDSEVCSTozMjg4OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI5Mjc5IiwidmFsdWUiOiJDSEVCSToyOTI3OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDgiLCJ2YWx1ZSI6IkNIRUJJOjI2NDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MTg4NyIsInZhbHVlIjoiQ0hFQkk6NDE4ODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMDkyMSIsInZhbHVlIjoiQ0hFQkk6MjA5MjEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0NDA4MCIsInZhbHVlIjoiQ0hFQkk6NDQwODAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMDkzMCIsInZhbHVlIjoiQ0hFQkk6MjA5MzAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo3NDc2NCIsInZhbHVlIjoiQ0hFQkk6NzQ3NjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MTcyIiwidmFsdWUiOiJDSEVCSTo2MTcyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjE4OSIsInZhbHVlIjoiQ0hFQkk6NjE4OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQyODQ5IiwidmFsdWUiOiJDSEVCSTo0Mjg0OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIzMDgiLCJ2YWx1ZSI6IkNIRUJJOjIzMDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNTI0MiIsInZhbHVlIjoiQ0hFQkk6MTUyNDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0NTkyMiIsInZhbHVlIjoiQ0hFQkk6NDU5MjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMzUxIiwidmFsdWUiOiJDSEVCSToyMzUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDgxMDciLCJ2YWx1ZSI6IkNIRUJJOjQ4MTA3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjM0OSIsInZhbHVlIjoiQ0hFQkk6MjM0OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1NTQ1IiwidmFsdWUiOiJDSEVCSToyNTU0NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyMjQyIiwidmFsdWUiOiJDSEVCSToxMjI0MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjM3NTE1IiwidmFsdWUiOiJDSEVCSTozNzUxNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjg0NzgiLCJ2YWx1ZSI6IkNIRUJJOjg0NzgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyNDA3IiwidmFsdWUiOiJDSEVCSToyNDA3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjA3NjUiLCJ2YWx1ZSI6IkNIRUJJOjIwNzY1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjU1NjciLCJ2YWx1ZSI6IkNIRUJJOjI1NTY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjgxNiIsInZhbHVlIjoiQ0hFQkk6NjgxNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjM2MjYzIiwidmFsdWUiOiJDSEVCSTozNjI2MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjU1MDMiLCJ2YWx1ZSI6IkNIRUJJOjU1MDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyNjk3NyIsInZhbHVlIjoiQ0hFQkk6MjY5NzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo1NDMzIiwidmFsdWUiOiJDSEVCSTo1NDMzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDExMSIsInZhbHVlIjoiQ0hFQkk6NDExMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE2Mzc1IiwidmFsdWUiOiJDSEVCSToxNjM3NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMjMxIiwidmFsdWUiOiJDSEVCSToxMzIzMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMjMwIiwidmFsdWUiOiJDSEVCSToxMzIzMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1MjI3IiwidmFsdWUiOiJDSEVCSToyNTIyNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE0NTg4IiwidmFsdWUiOiJDSEVCSToxNDU4OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQ0NDg3IiwidmFsdWUiOiJDSEVCSTo0NDQ4NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjUyMDkwIiwidmFsdWUiOiJDSEVCSTo1MjA5MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE0NjU0IiwidmFsdWUiOiJDSEVCSToxNDY1NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE0NjU4IiwidmFsdWUiOiJDSEVCSToxNDY1OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjk1NjkiLCJ2YWx1ZSI6IkNIRUJJOjk1NjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo1MTc0IiwidmFsdWUiOiJDSEVCSTo1MTc0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MzI0NTEiLCJ2YWx1ZSI6IkNIRUJJOjMyNDUxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MzI0NTAiLCJ2YWx1ZSI6IkNIRUJJOjMyNDUwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MzI0NDkiLCJ2YWx1ZSI6IkNIRUJJOjMyNDQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxNjYiLCJ2YWx1ZSI6IkNIRUJJOjE1MTY2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxNjciLCJ2YWx1ZSI6IkNIRUJJOjE1MTY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxNjkiLCJ2YWx1ZSI6IkNIRUJJOjE1MTY5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDI3NDkiLCJ2YWx1ZSI6IkNIRUJJOjQyNzQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxNzMiLCJ2YWx1ZSI6IkNIRUJJOjE1MTczIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxNzQiLCJ2YWx1ZSI6IkNIRUJJOjE1MTc0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxNzciLCJ2YWx1ZSI6IkNIRUJJOjE1MTc3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxNzgiLCJ2YWx1ZSI6IkNIRUJJOjE1MTc4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODAiLCJ2YWx1ZSI6IkNIRUJJOjE1MTgwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODEiLCJ2YWx1ZSI6IkNIRUJJOjE1MTgxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODQiLCJ2YWx1ZSI6IkNIRUJJOjE1MTg0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODIiLCJ2YWx1ZSI6IkNIRUJJOjE1MTgyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODMiLCJ2YWx1ZSI6IkNIRUJJOjE1MTgzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODgiLCJ2YWx1ZSI6IkNIRUJJOjE1MTg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODkiLCJ2YWx1ZSI6IkNIRUJJOjE1MTg5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODYiLCJ2YWx1ZSI6IkNIRUJJOjE1MTg2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxODciLCJ2YWx1ZSI6IkNIRUJJOjE1MTg3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTUxOTEiLCJ2YWx1ZSI6IkNIRUJJOjE1MTkxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTI5MTkiLCJ2YWx1ZSI6IkNIRUJJOjEyOTE5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjUzNjgiLCJ2YWx1ZSI6IkNIRUJJOjI1MzY4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjUzNzEiLCJ2YWx1ZSI6IkNIRUJJOjI1MzcxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDYyNjIiLCJ2YWx1ZSI6IkNIRUJJOjQ2MjYyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTMzNDQiLCJ2YWx1ZSI6IkNIRUJJOjEzMzQ0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NzEyNjMiLCJ2YWx1ZSI6IkNIRUJJOjcxMjYzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDQ1NTMiLCJ2YWx1ZSI6IkNIRUJJOjQ0NTUzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDA2OTgiLCJ2YWx1ZSI6IkNIRUJJOjQwNjk4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDkwMzIiLCJ2YWx1ZSI6IkNIRUJJOjQ5MDMyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NTgwOSIsInZhbHVlIjoiQ0hFQkk6NTgwOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE2MTQxIiwidmFsdWUiOiJDSEVCSToxNjE0MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE0MzciLCJ2YWx1ZSI6IkNIRUJJOjE0MzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozOTY5OCIsInZhbHVlIjoiQ0hFQkk6Mzk2OTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MzcwMSIsInZhbHVlIjoiQ0hFQkk6NDM3MDEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMDg0MCIsInZhbHVlIjoiQ0hFQkk6MTA4NDAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MTk5OCIsInZhbHVlIjoiQ0hFQkk6NDE5OTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MDY2OCIsInZhbHVlIjoiQ0hFQkk6NDA2NjgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyOTQ3NyIsInZhbHVlIjoiQ0hFQkk6Mjk0NzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0OTAwMyIsInZhbHVlIjoiQ0hFQkk6NDkwMDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA2MiIsInZhbHVlIjoiQ0hFQkk6MTMwNjIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc1MSIsInZhbHVlIjoiQ0hFQkk6MTg3NTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc1MCIsInZhbHVlIjoiQ0hFQkk6MTg3NTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc1MiIsInZhbHVlIjoiQ0hFQkk6MTg3NTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc1NCIsInZhbHVlIjoiQ0hFQkk6MTg3NTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc1NiIsInZhbHVlIjoiQ0hFQkk6MTg3NTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNDM5OCIsInZhbHVlIjoiQ0hFQkk6MTQzOTgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo4NDI2NiIsInZhbHVlIjoiQ0hFQkk6ODQyNjYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA4NyIsInZhbHVlIjoiQ0hFQkk6MTMwODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc3OSIsInZhbHVlIjoiQ0hFQkk6MTg3NzkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0OTA1NSIsInZhbHVlIjoiQ0hFQkk6NDkwNTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA5MSIsInZhbHVlIjoiQ0hFQkk6MTMwOTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc4MCIsInZhbHVlIjoiQ0hFQkk6MTg3ODAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc4MiIsInZhbHVlIjoiQ0hFQkk6MTg3ODIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODc4MSIsInZhbHVlIjoiQ0hFQkk6MTg3ODEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MjAxMiIsInZhbHVlIjoiQ0hFQkk6NjIwMTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzAxOSIsInZhbHVlIjoiQ0hFQkk6MTMwMTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNDM0OCIsInZhbHVlIjoiQ0hFQkk6MTQzNDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNDM0NyIsInZhbHVlIjoiQ0hFQkk6MTQzNDcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozNTI2NiIsInZhbHVlIjoiQ0hFQkk6MzUyNjYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MTU0MyIsInZhbHVlIjoiQ0hFQkk6NjE1NDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo4Mzc3NCIsInZhbHVlIjoiQ0hFQkk6ODM3NzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyNTAxNCIsInZhbHVlIjoiQ0hFQkk6MjUwMTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MTkyNiIsInZhbHVlIjoiQ0hFQkk6NDE5MjYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2Mjg0MCIsInZhbHVlIjoiQ0hFQkk6NjI4NDAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA0MiIsInZhbHVlIjoiQ0hFQkk6MTMwNDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODczMSIsInZhbHVlIjoiQ0hFQkk6MTg3MzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzA0OCIsInZhbHVlIjoiQ0hFQkk6MTMwNDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNDM3OCIsInZhbHVlIjoiQ0hFQkk6MTQzNzgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0NTc3IiwidmFsdWUiOiJDSEVCSTo0NTc3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6Mjc2ODEiLCJ2YWx1ZSI6IkNIRUJJOjI3NjgxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjYzNTgiLCJ2YWx1ZSI6IkNIRUJJOjI2MzU4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTQzODIiLCJ2YWx1ZSI6IkNIRUJJOjE0MzgyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjYzNTkiLCJ2YWx1ZSI6IkNIRUJJOjI2MzU5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjYzNjAiLCJ2YWx1ZSI6IkNIRUJJOjI2MzYwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjQ2MDUiLCJ2YWx1ZSI6IkNIRUJJOjY0NjA1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTY5NzAiLCJ2YWx1ZSI6IkNIRUJJOjE2OTcwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTQ4NyIsInZhbHVlIjoiQ0hFQkk6MTQ4NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE0ODgiLCJ2YWx1ZSI6IkNIRUJJOjE0ODgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozNTg4NDgiLCJ2YWx1ZSI6IkNIRUJJOjM1ODg0OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE0OTQiLCJ2YWx1ZSI6IkNIRUJJOjE0OTQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMzMzNyIsInZhbHVlIjoiQ0hFQkk6MjMzMzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MjAyIiwidmFsdWUiOiJDSEVCSTo2MjAyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NTc1NzMiLCJ2YWx1ZSI6IkNIRUJJOjU3NTczIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NzU1OSIsInZhbHVlIjoiQ0hFQkk6NzU1OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMTUzMyIsInZhbHVlIjoiQ0hFQkk6MTMxNTMzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTMxNTMwIiwidmFsdWUiOiJDSEVCSToxMzE1MzAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNDQyMyIsInZhbHVlIjoiQ0hFQkk6MTQ0MjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MjMyIiwidmFsdWUiOiJDSEVCSTo2MjMyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NzU2MSIsInZhbHVlIjoiQ0hFQkk6NzU2MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIzMzA2IiwidmFsdWUiOiJDSEVCSToyMzMwNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIzMzA4IiwidmFsdWUiOiJDSEVCSToyMzMwOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI0NjM1IiwidmFsdWUiOiJDSEVCSToyNDYzNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzOTkzIiwidmFsdWUiOiJDSEVCSToxMzk5MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIwMjgzIiwidmFsdWUiOiJDSEVCSToyMDI4MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQ5OTgyIiwidmFsdWUiOiJDSEVCSTo0OTk4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjM0MDAyIiwidmFsdWUiOiJDSEVCSTozNDAwMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQ5OTcyIiwidmFsdWUiOiJDSEVCSTo0OTk3MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIzMzI4IiwidmFsdWUiOiJDSEVCSToyMzMyOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjExMzU0IiwidmFsdWUiOiJDSEVCSToxMTM1NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNjg1IiwidmFsdWUiOiJDSEVCSToxMjY4NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIxNTUyIiwidmFsdWUiOiJDSEVCSToyMTU1MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE5MjUxIiwidmFsdWUiOiJDSEVCSToxOTI1MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYyMDg1IiwidmFsdWUiOiJDSEVCSTo2MjA4NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNjE2IiwidmFsdWUiOiJDSEVCSToxMjYxNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzOTQ3IiwidmFsdWUiOiJDSEVCSToxMzk0NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNjE4IiwidmFsdWUiOiJDSEVCSToxMjYxOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNjE3IiwidmFsdWUiOiJDSEVCSToxMjYxNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzOTcwIiwidmFsdWUiOiJDSEVCSToxMzk3MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNjM0IiwidmFsdWUiOiJDSEVCSToxMjYzNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE5MjA3IiwidmFsdWUiOiJDSEVCSToxOTIwNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYyNDYiLCJ2YWx1ZSI6IkNIRUJJOjYyNDYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0NDIxOSIsInZhbHVlIjoiQ0hFQkk6NDQyMTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MjQyIiwidmFsdWUiOiJDSEVCSTo2MjQyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjI0MSIsInZhbHVlIjoiQ0hFQkk6NjI0MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyODUxIiwidmFsdWUiOiJDSEVCSToyMjg1MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQyNDk2IiwidmFsdWUiOiJDSEVCSTo0MjQ5NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIxNTQ5IiwidmFsdWUiOiJDSEVCSToyMTU0OSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYyNzIiLCJ2YWx1ZSI6IkNIRUJJOjYyNzIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMTU1MCIsInZhbHVlIjoiQ0hFQkk6MjE1NTAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMTU1MSIsInZhbHVlIjoiQ0hFQkk6MjE1NTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0MjUxNyIsInZhbHVlIjoiQ0hFQkk6NDI1MTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo4NzgiLCJ2YWx1ZSI6IkNIRUJJOjg3OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE1NjUiLCJ2YWx1ZSI6IkNIRUJJOjE1NjUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo1OTI2IiwidmFsdWUiOiJDSEVCSTo1OTI2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTA5NTIiLCJ2YWx1ZSI6IkNIRUJJOjEwOTUyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NzEwMzgiLCJ2YWx1ZSI6IkNIRUJJOjcxMDM4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTA5NjAiLCJ2YWx1ZSI6IkNIRUJJOjEwOTYwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDYxNiIsInZhbHVlIjoiQ0hFQkk6NDYxNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwOTY3IiwidmFsdWUiOiJDSEVCSToxMDk2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwOTY4IiwidmFsdWUiOiJDSEVCSToxMDk2OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE5MzEyIiwidmFsdWUiOiJDSEVCSToxOTMxMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjUyNzcyIiwidmFsdWUiOiJDSEVCSTo1Mjc3MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjk0NzUiLCJ2YWx1ZSI6IkNIRUJJOjk0NzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo4MzciLCJ2YWx1ZSI6IkNIRUJJOjgzNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMTkyIiwidmFsdWUiOiJDSEVCSToxMzE5MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjg0Mzg3IiwidmFsdWUiOiJDSEVCSTo4NDM4NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjMwNTY3IiwidmFsdWUiOiJDSEVCSTozMDU2NyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIzMTU4OCIsInZhbHVlIjoiQ0hFQkk6MjMxNTg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6ODE2NiIsInZhbHVlIjoiQ0hFQkk6ODE2NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjgxNjciLCJ2YWx1ZSI6IkNIRUJJOjgxNjcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo4MTgiLCJ2YWx1ZSI6IkNIRUJJOjgxOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE2MjQxIiwidmFsdWUiOiJDSEVCSToxNjI0MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjU5MDAiLCJ2YWx1ZSI6IkNIRUJJOjU5MDAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyNTE5OSIsInZhbHVlIjoiQ0hFQkk6MjUxOTkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMzE0MiIsInZhbHVlIjoiQ0hFQkk6MTMxNDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxNDQ3MyIsInZhbHVlIjoiQ0hFQkk6MTQ0NzMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo1Mzc1MTkiLCJ2YWx1ZSI6IkNIRUJJOjUzNzUxOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjgzODk4IiwidmFsdWUiOiJDSEVCSTo4Mzg5OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIwMzEiLCJ2YWx1ZSI6IkNIRUJJOjIwMzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMDMyIiwidmFsdWUiOiJDSEVCSToyMDMyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjAzNCIsInZhbHVlIjoiQ0hFQkk6MjAzNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjc3MjQiLCJ2YWx1ZSI6IkNIRUJJOjc3MjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJOLWFjZXR5bC1MLWdsdXRhbWljIGFjaWQiLCJ2YWx1ZSI6IkNIRUJJOjE3NTMzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjAyMCIsInZhbHVlIjoiQ0hFQkk6MjAyMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1MTU2IiwidmFsdWUiOiJDSEVCSToyNTE1NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQwNzcxIiwidmFsdWUiOiJDSEVCSTo0MDc3MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYxNjk0IiwidmFsdWUiOiJDSEVCSTo2MTY5NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIzMzgwIiwidmFsdWUiOiJDSEVCSToyMzM4MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIzMzg2IiwidmFsdWUiOiJDSEVCSToyMzM4NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjMzMDgiLCJ2YWx1ZSI6IkNIRUJJOjMzMDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyNzIwNjYiLCJ2YWx1ZSI6IkNIRUJJOjI3MjA2NiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDIzIiwidmFsdWUiOiJDSEVCSToyNjQyMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDI0IiwidmFsdWUiOiJDSEVCSToyNjQyNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDI5IiwidmFsdWUiOiJDSEVCSToyNjQyOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDI2IiwidmFsdWUiOiJDSEVCSToyNjQyNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDI3IiwidmFsdWUiOiJDSEVCSToyNjQyNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMTE4IiwidmFsdWUiOiJDSEVCSToxMzExOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMTE5IiwidmFsdWUiOiJDSEVCSToxMzExOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyMDczIiwidmFsdWUiOiJDSEVCSToyMjA3MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI2NDMwIiwidmFsdWUiOiJDSEVCSToyNjQzMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMTMwIiwidmFsdWUiOiJDSEVCSToxMzEzMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI1MTEyIiwidmFsdWUiOiJDSEVCSToyNTExMiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyMTI1IiwidmFsdWUiOiJDSEVCSToyMjEyNSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyMTI0IiwidmFsdWUiOiJDSEVCSToyMjEyNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyMTI2IiwidmFsdWUiOiJDSEVCSToyMjEyNiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjExNDgzIiwidmFsdWUiOiJDSEVCSToxMTQ4MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjExNDgxIiwidmFsdWUiOiJDSEVCSToxMTQ4MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjExNDgyIiwidmFsdWUiOiJDSEVCSToxMTQ4MiJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjE0NTAxIiwidmFsdWUiOiJDSEVCSToxNDUwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjI0Nzk0IiwidmFsdWUiOiJDSEVCSToyNDc5NCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjc2NjgiLCJ2YWx1ZSI6IkNIRUJJOjc2NjgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMTQ5MSIsInZhbHVlIjoiQ0hFQkk6MTE0OTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo3NjY3IiwidmFsdWUiOiJDSEVCSTo3NjY3In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE0OTMiLCJ2YWx1ZSI6IkNIRUJJOjExNDkzIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE0ODgiLCJ2YWx1ZSI6IkNIRUJJOjExNDg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE0ODkiLCJ2YWx1ZSI6IkNIRUJJOjExNDg5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTMyOTcwIiwidmFsdWUiOiJDSEVCSToxMzI5NzAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MzQ5IiwidmFsdWUiOiJDSEVCSTo2MzQ5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDMwMjYiLCJ2YWx1ZSI6IkNIRUJJOjQzMDI2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjM0MSIsInZhbHVlIjoiQ0hFQkk6NjM0MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjM1NDA0IiwidmFsdWUiOiJDSEVCSTozNTQwNCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNzgxIiwidmFsdWUiOiJDSEVCSToxMjc4MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwMTEzIiwidmFsdWUiOiJDSEVCSToxMDExMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6ImNhZG1pdW0oMispIiwidmFsdWUiOiJDSEVCSTo0ODc3NSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQ4NzczIiwidmFsdWUiOiJDSEVCSTo0ODc3MyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjIyMTEwIiwidmFsdWUiOiJDSEVCSToyMjExMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYxNzE5IiwidmFsdWUiOiJDSEVCSTo2MTcxOSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQ4NzYwIiwidmFsdWUiOiJDSEVCSTo0ODc2MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEzMjkyNiIsInZhbHVlIjoiQ0hFQkk6MTMyOTI2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6Mjc4MDAiLCJ2YWx1ZSI6IkNIRUJJOjI3ODAwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6ODk3MSIsInZhbHVlIjoiQ0hFQkk6ODk3MSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNzk4IiwidmFsdWUiOiJDSEVCSToxMjc5OCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjMyNzciLCJ2YWx1ZSI6IkNIRUJJOjMyNzcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMjEyMCIsInZhbHVlIjoiQ0hFQkk6MjIxMjAifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMjEyMyIsInZhbHVlIjoiQ0hFQkk6MjIxMjMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMjcyNCIsInZhbHVlIjoiQ0hFQkk6MTI3MjQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyNDcxNiIsInZhbHVlIjoiQ0hFQkk6MjQ3MTYifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODA0MiIsInZhbHVlIjoiQ0hFQkk6MTgwNDIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODA1NSIsInZhbHVlIjoiQ0hFQkk6MTgwNTUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMjc1MSIsInZhbHVlIjoiQ0hFQkk6MTI3NTEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxODA2OSIsInZhbHVlIjoiQ0hFQkk6MTgwNjkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo0NDMzNSIsInZhbHVlIjoiQ0hFQkk6NDQzMzUifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMDMwOCIsInZhbHVlIjoiQ0hFQkk6MjAzMDgifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo3NjkxIiwidmFsdWUiOiJDSEVCSTo3NjkxIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6ODM5MDAiLCJ2YWx1ZSI6IkNIRUJJOjgzOTAwIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjAzMTUiLCJ2YWx1ZSI6IkNIRUJJOjIwMzE1In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjAzMTQiLCJ2YWx1ZSI6IkNIRUJJOjIwMzE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjAzMTIiLCJ2YWx1ZSI6IkNIRUJJOjIwMzEyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTA5OTYiLCJ2YWx1ZSI6IkNIRUJJOjEwOTk2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NDQzMTkiLCJ2YWx1ZSI6IkNIRUJJOjQ0MzE5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjI5ODgiLCJ2YWx1ZSI6IkNIRUJJOjIyOTg4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTgwMTQiLCJ2YWx1ZSI6IkNIRUJJOjE4MDE0In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjM0NTcxIiwidmFsdWUiOiJDSEVCSToyMzQ1NzEifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMjcwOSIsInZhbHVlIjoiQ0hFQkk6MTI3MDkifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTozNDE5MzciLCJ2YWx1ZSI6IkNIRUJJOjM0MTkzNyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEyNzEwIiwidmFsdWUiOiJDSEVCSToxMjcxMCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjYzOTIiLCJ2YWx1ZSI6IkNIRUJJOjYzOTIifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToxMjcxNyIsInZhbHVlIjoiQ0hFQkk6MTI3MTcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyOTIwMyIsInZhbHVlIjoiQ0hFQkk6MjkyMDMifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSToyMDg3NCIsInZhbHVlIjoiQ0hFQkk6MjA4NzQifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo2MTM4NyIsInZhbHVlIjoiQ0hFQkk6NjEzODcifSx7ImNsYXNzIjoidWsuYWMuZWJpLmRkaS5lYmUud3MuZGFvLm1vZGVsLmNvbW1vbi5GYWNldFZhbHVlIiwiY291bnQiOiIyIiwibGFiZWwiOiJDSEVCSTo5MTY2IiwidmFsdWUiOiJDSEVCSTo5MTY2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTE5MjkiLCJ2YWx1ZSI6IkNIRUJJOjExOTI5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6NjAwMjgiLCJ2YWx1ZSI6IkNIRUJJOjYwMDI4In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MjA4ODIiLCJ2YWx1ZSI6IkNIRUJJOjIwODgyIn0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6MTg1NzYiLCJ2YWx1ZSI6IkNIRUJJOjE4NTc2In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMiIsImxhYmVsIjoiQ0hFQkk6Mzg4MCIsInZhbHVlIjoiQ0hFQkk6Mzg4MCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjEwNjAxIiwidmFsdWUiOiJDSEVCSToxMDYwMSJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjUyNDAzIiwidmFsdWUiOiJDSEVCSTo1MjQwMyJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjExOTM4IiwidmFsdWUiOiJDSEVCSToxMTkzOCJ9LHsiY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjIiLCJsYWJlbCI6IkNIRUJJOjQxNzU2IiwidmFsdWUiOiJDSEVCSTo0MTc1NiJ9XSwiaWQiOiJDSEVCSSIsImxhYmVsIjoiQ0hFQkkiLCJ0b3RhbCI6NTc4OH0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXQiLCJmYWNldFZhbHVlcyI6W3siY2xhc3MiOiJ1ay5hYy5lYmkuZGRpLmViZS53cy5kYW8ubW9kZWwuY29tbW9uLkZhY2V0VmFsdWUiLCJjb3VudCI6IjEiLCJsYWJlbCI6IkVOU0cwMDAwMDE2MzczOSIsInZhbHVlIjoiRU5TRzAwMDAwMTYzNzM5In0seyJjbGFzcyI6InVrLmFjLmViaS5kZGkuZWJlLndzLmRhby5tb2RlbC5jb21tb24uRmFjZXRWYWx1ZSIsImNvdW50IjoiMSIsImxhYmVsIjoiRU5TRzAwMDAwMTIwNjU5IiwidmFsdWUiOiJFTlNHMDAwMDAxMjA2NTkifV0sImlkIjoiRU5TRU1CTCIsImxhYmVsIjoiRU5TRU1CTCIsInRvdGFsIjoyfV0sIm1hdGNoZXMiOjMyLCJtb2RlbHMiOlt7ImZvcm1hdCI6IlNCTUwiLCJpZCI6Ik1PREVMMjQwMTA1MDAwMSIsImxhc3RNb2RpZmllZCI6IjIwMjQtMDEtMTVUMDA6MDA6MDBaIiwibmFtZSI6IkxhbmcyMDI0IC0gQ2VsbCBjeWNsZSBtb2RlbCBleHBsYWlucyBjb21wYXJ0bWVudC1yZXNvbHZlZCBkeW5hbWljcyBvZiAxNiBvYnNlcnZhYmxlcyBpbiBSUEUxIGNlbGxzIiwic3VibWlzc2lvbkRhdGUiOiIyMDI0LTAxLTA1VDAwOjAwOjAwWiIsInN1Ym1pdHRlciI6IlBhdWwgRiBMYW5nIiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDI0MDEwNTAwMDEifSx7ImZvcm1hdCI6Ik90aGVyIiwiaWQiOiJNT0RFTDI0MDExNzAwMDEiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTE3VDAwOjAwOjAwWiIsIm5hbWUiOiJQcmFkZWVwMjAyNC1BIE1lY2hhbmljYWwgbW9kZWxsaW5nIGZyYW1ld29yayBmb3Igc3R1ZHlpbmcgZW5kb3RoZWxpYWwgcGVybWVhYmlsaXR5Iiwic3VibWlzc2lvbkRhdGUiOiIyMDI0LTAxLTE3VDAwOjAwOjAwWiIsInN1Ym1pdHRlciI6IlByYWRlZXAgS2VzaGF2YW5hcmF5YW5hIiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDI0MDExNzAwMDEifSx7ImZvcm1hdCI6IlNCTUwiLCJpZCI6Ik1PREVMMjMwNjI4MDAwMiIsImxhc3RNb2RpZmllZCI6IjIwMjQtMDEtMTVUMDA6MDA6MDBaIiwibmFtZSI6IkJ1cmJhbm8yMDIzX0hHRnNpZ25hbGluZ19pbl9GYXR0eUxpdmVyRGlzZWFzZSIsInN1Ym1pc3Npb25EYXRlIjoiMjAyNC0wMS0xNFQwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJTdmVuamEgS2VtbWVyIiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDIzMDYyODAwMDIifSx7ImZvcm1hdCI6IlNCTUwiLCJpZCI6Ik1PREVMMjQwMTExMDAwMSIsImxhc3RNb2RpZmllZCI6IjIwMjQtMDEtMTFUMDA6MDA6MDBaIiwibmFtZSI6InBhbmNyZWFzIGdsdWNvc2UgbW9kZWwiLCJzdWJtaXNzaW9uRGF0ZSI6IjIwMjQtMDEtMTFUMDA6MDA6MDBaIiwic3VibWl0dGVyIjoiRGVlcGEgTWFoZXNodmFyZSBNLiIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyNDAxMTEwMDAxIn0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJCSU9NRDAwMDAwMDEwNzciLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTI2VDAwOjAwOjAwWiIsIm5hbWUiOiJBZGx1bmcyMDIxIC0gQ2VsbC10by1jZWxsIHZhcmlhYmlsaXR5IGluIEpBSzIvU1RBVDUgcGF0aHdheSIsInN1Ym1pc3Npb25EYXRlIjoiMjAyNC0wMS0yNlQwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJLcmlzaG5hIEt1bWFyIFRpd2FyaSIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvQklPTUQwMDAwMDAxMDc3In0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJNT0RFTDIzMDcxODAwMDEiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTI2VDAwOjAwOjAwWiIsIm5hbWUiOiJaZXJyb3VrMjAyMyAtIExhcmdlIHNjYWxlIGNvbXB1dGF0aW9uYWwgbW9kZWxpbmcgb2YgdGhlIE0xIHN5bm92aWFsIG1hY3JvcGhhZ2UgaW4gcmhldW1hdG9pZCBhcnRocml0aXMiLCJzdWJtaXNzaW9uRGF0ZSI6IjIwMjQtMDEtMjZUMDA6MDA6MDBaIiwic3VibWl0dGVyIjoibmFvdWVsIHplcnJvdWsiLCJ1cmwiOiJodHRwczovL3d3dy5lYmkuYWMudWsvYmlvbW9kZWxzL01PREVMMjMwNzE4MDAwMSJ9LHsiZm9ybWF0IjoiU0JNTCIsImlkIjoiQklPTUQwMDAwMDAxMDc4IiwibGFzdE1vZGlmaWVkIjoiMjAyNC0wMS0yOVQwMDowMDowMFoiLCJuYW1lIjoiSGFtbWFyZW4tR2Vpc3NlbjIwMjJfUFBUb1BfTW9kZWwxMiIsInN1Ym1pc3Npb25EYXRlIjoiMjAyNC0wMS0yOVQwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJLcmlzaG5hIEt1bWFyIFRpd2FyaSIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvQklPTUQwMDAwMDAxMDc4In0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJCSU9NRDAwMDAwMDEwODAiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTI5VDAwOjAwOjAwWiIsIm5hbWUiOiJEZUJvZWNrMjAyMSAtIE1vZHVsYXIgYXBwcm9hY2ggdG8gbW9kZWxpbmcgdGhlIGNlbGwgY3ljbGUsIDUgT0RFIG1vZGVsIHdpdGggMyBiaXN0YWJsZSBzd2l0Y2hlcyIsInN1Ym1pc3Npb25EYXRlIjoiMjAyNC0wMS0yOVQwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJLcmlzaG5hIEt1bWFyIFRpd2FyaSIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvQklPTUQwMDAwMDAxMDgwIn0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJNT0RFTDIyMTEyOTAwMDIiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTExVDAwOjAwOjAwWiIsIm5hbWUiOiJDYXNpbmkyMDIyIC0gTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgWi0yNDUiLCJzdWJtaXNzaW9uRGF0ZSI6IjIwMjQtMDEtMTFUMDA6MDA6MDBaIiwic3VibWl0dGVyIjoiVHVuZyBOZ3V5ZW4iLCJ1cmwiOiJodHRwczovL3d3dy5lYmkuYWMudWsvYmlvbW9kZWxzL01PREVMMjIxMTI5MDAwMiJ9LHsiZm9ybWF0IjoiU0JNTCIsImlkIjoiTU9ERUwxODEyMjEwMDAyIiwibGFzdE1vZGlmaWVkIjoiMjAyNC0wMS0yNFQwMDowMDowMFoiLCJuYW1lIjoiSGVuemUyMDE3IC0gQSBEeW5hbWljYWwgTW9kZWwgZm9yIEFjdGl2YXRpbmcgYW5kIFNpbGVuY2luZyB0aGUgTWl0b3RpYyBDaGVja3BvaW50Iiwic3VibWlzc2lvbkRhdGUiOiIyMDI0LTAxLTI0VDAwOjAwOjAwWiIsInN1Ym1pdHRlciI6IlR1bmcgTmd1eWVuIiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDE4MTIyMTAwMDIifSx7ImZvcm1hdCI6IkNPTUJJTkUgYXJjaGl2ZSIsImlkIjoiTU9ERUwyMzA0MjcwMDAzIiwibGFzdE1vZGlmaWVkIjoiMjAyNC0wMS0wOVQwMDowMDowMFoiLCJuYW1lIjoiaUNzdHIxMTk3RkIyMzogR2Vub21lLXNjYWxlIG1ldGFib2xpYyBtb2RlbCBvZiBDb3J5bmViYWN0ZXJpdW0gc3RyaWF0dW0gc3RyYWluIEZEQUFSR09TXzExOTciLCJzdWJtaXNzaW9uRGF0ZSI6IjIwMjQtMDEtMDlUMDA6MDA6MDBaIiwic3VibWl0dGVyIjoiQW5kcmVhcyBEcsOkZ2VyIiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDIzMDQyNzAwMDMifSx7ImZvcm1hdCI6IkNPTUJJTkUgYXJjaGl2ZSIsImlkIjoiTU9ERUwyMDEyMjIwMDAzIiwibGFzdE1vZGlmaWVkIjoiMjAyNC0wMS0wOFQwMDowMDowMFoiLCJuYW1lIjoiUmVuejIwMjEgLSBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIChpRFBNMjFSVykgb2YgRG9sb3NpZ3JhbnVsdW0gcGlncnVtIDgzVlBzIEtCNSIsInN1Ym1pc3Npb25EYXRlIjoiMjAyNC0wMS0wOFQwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJBbmRyZWFzIERyw6RnZXIiLCJ1cmwiOiJodHRwczovL3d3dy5lYmkuYWMudWsvYmlvbW9kZWxzL01PREVMMjAxMjIyMDAwMyJ9LHsiZm9ybWF0IjoiU0JNTCIsImlkIjoiTU9ERUwyMjEyMDYwMDAyIiwibGFzdE1vZGlmaWVkIjoiMjAyNC0wMS0yMlQwMDowMDowMFoiLCJuYW1lIjoiRGVCb2VjazIwMjEgLSBNb2R1bGFyIGFwcHJvYWNoIHRvIG1vZGVsaW5nIHRoZSBjZWxsIGN5Y2xlLCA1IE9ERSBtb2RlbCB3aXRoIDMgYmlzdGFibGUgc3dpdGNoZXMiLCJzdWJtaXNzaW9uRGF0ZSI6IjIwMjItMTItMDdUMDA6MDA6MDBaIiwic3VibWl0dGVyIjoiSmFuIFJvbWJvdXRzIiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDIyMTIwNjAwMDIifSx7ImZvcm1hdCI6IlNCTUwiLCJpZCI6Ik1PREVMMjIxMTI5MDAwMyIsImxhc3RNb2RpZmllZCI6IjIwMjQtMDEtMTFUMDA6MDA6MDBaIiwibmFtZSI6IkNhc2luaTIwMjIgLSBNZXRoYW5vdGhlcm1vYmFjdGVyIG1hcmJ1cmdlbnNpcyBNYXJidXJnIiwic3VibWlzc2lvbkRhdGUiOiIyMDIyLTExLTI5VDAwOjAwOjAwWiIsInN1Ym1pdHRlciI6IklzYWJlbGxhIENhc2luaSIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyMjExMjkwMDAzIn0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJNT0RFTDIyMTIwNjAwMDEiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTIyVDAwOjAwOjAwWiIsIm5hbWUiOiJEZUJvZWNrMjAyMSAtIE1vZHVsYXIgYXBwcm9hY2ggdG8gbW9kZWxpbmcgdGhlIGNlbGwgY3ljbGUsIHNpbXBsZSBjZWxsIGN5Y2xlIG1vZGVsIiwic3VibWlzc2lvbkRhdGUiOiIyMDIyLTEyLTA2VDAwOjAwOjAwWiIsInN1Ym1pdHRlciI6IkphbiBSb21ib3V0cyIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyMjEyMDYwMDAxIn0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJNT0RFTDIwMDYwODAwMDEiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTI1VDAwOjAwOjAwWiIsIm5hbWUiOiJOb3ZhazIwMDQgLSBBIE1vZGVsIGZvciBSZXN0cmljdGlvbiBQb2ludCBDb250cm9sIG9mIHRoZSBNYW1tYWxpYW4gQ2VsbCBDeWNsZSIsInN1Ym1pc3Npb25EYXRlIjoiMjAyMC0wNi0wN1QyMzowMDowMFoiLCJzdWJtaXR0ZXIiOiJBaG1hZCBaeW91ZCIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyMDA2MDgwMDAxIn0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJCSU9NRDAwMDAwMDEwNzkiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTI5VDAwOjAwOjAwWiIsIm5hbWUiOiJEZUJvZWNrMjAyMSAtIE1vZHVsYXIgYXBwcm9hY2ggdG8gbW9kZWxpbmcgdGhlIGNlbGwgY3ljbGUsIHNpbXBsZSBjZWxsIGN5Y2xlIG1vZGVsIiwic3VibWlzc2lvbkRhdGUiOiIyMDIyLTEyLTA2VDAwOjAwOjAwWiIsInN1Ym1pdHRlciI6IkphbiBSb21ib3V0cyIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvQklPTUQwMDAwMDAxMDc5In0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJNT0RFTDIyMDMyNTAwMDEiLCJsYXN0TW9kaWZpZWQiOiIyMDIyLTA0LTAzVDIzOjAwOjAwWiIsIm5hbWUiOiJVc3RpbGFnbyBtYXlkaXMgTWV0YWJvbGljIENoYXJhY3Rlcml6YXRpb24gYW5kIEdyb3d0aCBRdWFudGlmaWNhdGlvbiB3aXRoIGEgR2Vub21lLVNjYWxlIE1ldGFib2xpYyBNb2RlbCIsInN1Ym1pc3Npb25EYXRlIjoiMjAyNC0wMS0xMFQwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJVbGYgTGllYmFsIiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDIyMDMyNTAwMDEifSx7ImZvcm1hdCI6IlNCTUwiLCJpZCI6Ik1PREVMMjIxMTI5MDAwMSIsImxhc3RNb2RpZmllZCI6IjIwMjMtMTItMzFUMDA6MDA6MDBaIiwibmFtZSI6IkNhc2luaTIwMjIgLSBnZW5vbWUgc2NhbGUgbWV0YWJvbGljIG1vZGVsIChpTVREMjJJQykgb2YgTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgRGVsdGEgSCIsInN1Ym1pc3Npb25EYXRlIjoiMjAyMy0xMi0xM1QwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJUdW5nIE5ndXllbiIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyMjExMjkwMDAxIn0seyJmb3JtYXQiOiJTQk1MIiwiaWQiOiJNT0RFTDIzMTIyMjAwMDEiLCJsYXN0TW9kaWZpZWQiOiIyMDIzLTEyLTIyVDAwOjAwOjAwWiIsIm5hbWUiOiJOaXRpbjIwMjMtIEtpbmV0aWMgbW9kZWwgb2YgY2VsbHVsYXIgbWV0YWJvbGlzbSIsInN1Ym1pc3Npb25EYXRlIjoiMjAyMy0xMi0yMlQwMDowMDowMFoiLCJzdWJtaXR0ZXIiOiJOaXRpbiBQYXRpbCIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyMzEyMjIwMDAxIn0seyJmb3JtYXQiOiJDT01CSU5FIGFyY2hpdmUiLCJpZCI6Ik1PREVMMjMwNDI3MDAwNCIsImxhc3RNb2RpZmllZCI6IjIwMjQtMDEtMDlUMDA6MDA6MDBaIiwibmFtZSI6ImlDc3RyMTExNkZCMjM6IEdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWwgb2YgQ29yeW5lYmFjdGVyaXVtIHN0cmlhdHVtIHN0cmFpbiBGREFBUkdPU18xMTE2Iiwic3VibWlzc2lvbkRhdGUiOiIyMDIzLTA0LTI2VDIzOjAwOjAwWiIsInN1Ym1pdHRlciI6IkFuZHJlYXMgRHLDpGdlciIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyMzA0MjcwMDA0In0seyJmb3JtYXQiOiJDT01CSU5FIGFyY2hpdmUiLCJpZCI6Ik1PREVMMjMwNDI3MDAwNSIsImxhc3RNb2RpZmllZCI6IjIwMjQtMDEtMDlUMDA6MDA6MDBaIiwibmFtZSI6ImlDc3RyS0NOYTAxRkIyMzogR2Vub21lLXNjYWxlIG1ldGFib2xpYyBtb2RlbCBvZiBDb3J5bmViYWN0ZXJpdW0gc3RyaWF0dW0gc3RyYWluIEtDLU5hLTAxIiwic3VibWlzc2lvbkRhdGUiOiIyMDIzLTA0LTI2VDIzOjAwOjAwWiIsInN1Ym1pdHRlciI6IkFuZHJlYXMgRHLDpGdlciIsInVybCI6Imh0dHBzOi8vd3d3LmViaS5hYy51ay9iaW9tb2RlbHMvTU9ERUwyMzA0MjcwMDA1In0seyJmb3JtYXQiOiJDT01CSU5FIGFyY2hpdmUiLCJpZCI6Ik1PREVMMjMwNDI3MDAwMiIsImxhc3RNb2RpZmllZCI6IjIwMjQtMDEtMDlUMDA6MDA6MDBaIiwibmFtZSI6ImlDc3RyMTExNUZCMjM6IEdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWwgb2YgQ29yeW5lYmFjdGVyaXVtIHN0cmlhdHVtIHN0cmFpbiBGREEtQVJHT1NfMTExNSIsInN1Ym1pc3Npb25EYXRlIjoiMjAyMy0wNC0yNlQyMzowMDowMFoiLCJzdWJtaXR0ZXIiOiJBbmRyZWFzIERyw6RnZXIiLCJ1cmwiOiJodHRwczovL3d3dy5lYmkuYWMudWsvYmlvbW9kZWxzL01PREVMMjMwNDI3MDAwMiJ9LHsiZm9ybWF0IjoiQ09NQklORSBhcmNoaXZlIiwiaWQiOiJNT0RFTDIxMTAwMTAwMDEiLCJsYXN0TW9kaWZpZWQiOiIyMDI0LTAxLTA5VDAwOjAwOjAwWiIsIm5hbWUiOiJaaGFuZzIwMTcgLSBNb2RlbCBpQ1c3NzMgb2YgQ29yeW5lYmFjdGVyaXVtIGdsdXRhbWljdW0gQVRDQyAxMzAzMiAoQmllbGVmZWxkKSIsInN1Ym1pc3Npb25EYXRlIjoiMjAyMS0wOS0zMFQyMzowMDowMFoiLCJzdWJtaXR0ZXIiOiJBbmRyZWFzIERyw6RnZXIiLCJ1cmwiOiJodHRwczovL3d3dy5lYmkuYWMudWsvYmlvbW9kZWxzL01PREVMMjExMDAxMDAwMSJ9LHsiZm9ybWF0IjoiU0JNTCIsImlkIjoiTU9ERUwyMjA0MjAwMDAzIiwibGFzdE1vZGlmaWVkIjoiMjAyMi0wOC0xMVQyMzowMDowMFoiLCJuYW1lIjoiTGl1MjAxOSAtIENsb3N0cmlkaXVtIGxqdW5nZGFobGlpIG1ldGFib2xpc20gbW9kZWwiLCJzdWJtaXNzaW9uRGF0ZSI6IjIwMjItMDQtMTlUMjM6MDA6MDBaIiwic3VibWl0dGVyIjoiUm9kcmlnbyBTYW50aWJhbmV6IiwidXJsIjoiaHR0cHM6Ly93d3cuZWJpLmFjLnVrL2Jpb21vZGVscy9NT0RFTDIyMDQyMDAwMDMifV0sInF1ZXJ5UGFyYW1ldGVycyI6eyJudW1SZXN1bHRzIjoyNSwib2Zmc2V0IjowLCJzb3J0QnkiOiJyZWxldmFuY2UiLCJzb3J0RGlyZWN0aW9uIjoiZGVzYyJ9fQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2401050001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=5e879748-c411-4a8d-9072-4bacbacc4879; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.132.16427.390356; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJMYW5nMjAyNCAtIENlbGwgY3ljbGUgbW9kZWwgZXhwbGFpbnMgY29tcGFydG1lbnQtcmVzb2x2ZWQgZHluYW1pY3Mgb2YgMTYgb2JzZXJ2YWJsZXMgaW4gUlBFMSBjZWxscyIsCiAgImRlc2NyaXB0aW9uIiA6ICJUaGlzIG1vZGVsIHByb3ZpZGVzIGEgbW9sZWN1bGFybHkgZGV0YWlsZWQgZGVzY3JpcHRpb24gb2YgdGhlIGJpb2NoZW1pY2FsIHJlYWN0aW9uIG5ldHdvcmtzIGdvdmVybmluZyB0aGUgbWFtbWFsaWFuIGNlbGwgY3ljbGUsIGluY2x1ZGluZyB0aGUgcmVzdHJpY3Rpb24gcG9pbnQsIHRoZSBHMVMsIEcyL00gYW5kIG1ldGFwaGFzZS9hbmFwaGFzZSB0cmFuc2l0aW9ucy4gS2luZXRpYyBsYXdzIGFyZSBkZXNjcmliZWQgd2l0aCBtYXNzIGFjdGlvbiBraW5ldGljcy4gVGhlIHJlYWN0aW9uIG5ldHdvcmsgaW5jbHVkZXMgRE5BLCAocGhvc3Bobylwcm90ZWlucyBhbmQgY29tcGxleGVzLCBidXQgb21pdHMgbVJOQXMgZm9yIHNpbXBsaWNpdHkuIFRvIGVzdGltYXRlIHRoZSBtb2RlbCBwYXJhbWV0ZXJzLCB0aW1lIGNvdXJzZXMgb2YgZWlnaHQgY2VsbCBjeWNsZSByZWd1bGF0b3JzIGluIHR3byBjb21wYXJ0bWVudHMgd2VyZSByZWNvbnN0cnVjdGVkIGZyb20gc2luZ2xlIGNlbGwgc25hcHNob3QgbWVhc3VyZW1lbnRzLiBUaGUgcmVzdWx0aW5nIG9wdGltaXphdGlvbiBwcm9ibGVtIHdhcyBkZWZpbmVkIGluIFBFdGFiIChzZWUgaHR0cHM6Ly9naXRodWIuY29tL3BhdWxmbGFuZy9jZWxsX2N5Y2xlX3BldGFiKSBhbmQgc29sdmVkIHdpdGggdGhlIHBhcmFsbGVsIGdsb2JhbCBvcHRpbWlzYXRpb24gbWV0YWhldXJpc3RpYyBzYUNlU1MuIiwKICAiZm9ybWF0IiA6IHsKICAgICJuYW1lIiA6ICJTQk1MIiwKICAgICJ2ZXJzaW9uIiA6ICJMM1YxIgogIH0sCiAgInB1YmxpY2F0aW9uIiA6IHsKICAgICJqb3VybmFsIiA6ICJQTG9TIGNvbXB1dGF0aW9uYWwgYmlvbG9neSIsCiAgICAidGl0bGUiIDogIlJldXNhYmxlIHJ1bGUtYmFzZWQgY2VsbCBjeWNsZSBtb2RlbCBleHBsYWlucyBjb21wYXJ0bWVudC1yZXNvbHZlZCBkeW5hbWljcyBvZiAxNiBvYnNlcnZhYmxlcyBpbiBSUEUtMSBjZWxscy4iLAogICAgImFmZmlsaWF0aW9uIiA6ICJEZXBhcnRtZW50IG9mIEJpb2NoZW1pc3RyeSwgVW5pdmVyc2l0eSBvZiBPeGZvcmQsIE94Zm9yZCwgVW5pdGVkIEtpbmdkb20uIiwKICAgICJzeW5vcHNpcyIgOiAiVGhlIG1hbW1hbGlhbiBjZWxsIGN5Y2xlIGlzIHJlZ3VsYXRlZCBieSBhIHdlbGwtc3R1ZGllZCBidXQgY29tcGxleCBiaW9jaGVtaWNhbCByZWFjdGlvbiBzeXN0ZW0uIENvbXB1dGF0aW9uYWwgbW9kZWxzIHByb3ZpZGUgYSBwYXJ0aWN1bGFybHkgc3lzdGVtYXRpYyBhbmQgc3lzdGVtaWMgZGVzY3JpcHRpb24gb2YgdGhlIG1lY2hhbmlzbXMgZ292ZXJuaW5nIG1hbW1hbGlhbiBjZWxsIGN5Y2xlIGNvbnRyb2wuIEJ5IGNvbWJpbmluZyBib3RoIHN0YXRlLW9mLXRoZS1hcnQgbXVsdGlwbGV4ZWQgZXhwZXJpbWVudGFsIG1ldGhvZHMgYW5kIHBvd2VyZnVsIGNvbXB1dGF0aW9uYWwgdG9vbHMsIHRoaXMgd29yayBhaW1zIGF0IGltcHJvdmluZyBvbiB0aGVzZSBtb2RlbHMgYWxvbmcgZm91ciBkaW1lbnNpb25zOiBtb2RlbCBzdHJ1Y3R1cmUsIHZhbGlkYXRpb24gZGF0YSwgdmFsaWRhdGlvbiBtZXRob2RvbG9neSBhbmQgbW9kZWwgcmV1c2FiaWxpdHkuIFdlIGRldmVsb3BlZCBhIGNvbXByZWhlbnNpdmUgbW9kZWwgc3RydWN0dXJlIG9mIHRoZSBmdWxsIGNlbGwgY3ljbGUgdGhhdCBxdWFsaXRhdGl2ZWx5IGV4cGxhaW5zIHRoZSBiZWhhdmlvdXIgb2YgaHVtYW4gcmV0aW5hbCBwaWdtZW50IGVwaXRoZWxpYWwtMSBjZWxscy4gVG8gZXN0aW1hdGUgdGhlIG1vZGVsIHBhcmFtZXRlcnMsIHRpbWUgY291cnNlcyBvZiBlaWdodCBjZWxsIGN5Y2xlIHJlZ3VsYXRvcnMgaW4gdHdvIGNvbXBhcnRtZW50cyB3ZXJlIHJlY29uc3RydWN0ZWQgZnJvbSBzaW5nbGUgY2VsbCBzbmFwc2hvdCBtZWFzdXJlbWVudHMuIEFmdGVyIG9wdGltaXNhdGlvbiB3aXRoIGEgcGFyYWxsZWwgZ2xvYmFsIG9wdGltaXNhdGlvbiBtZXRhaGV1cmlzdGljIHdlIG9idGFpbmVkIGV4Y2VsbGVudCBhZ3JlZW1lbnRzIGJldHdlZW4gc2ltdWxhdGlvbnMgYW5kIG1lYXN1cmVtZW50cy4gVGhlIFBFdGFiIHNwZWNpZmljYXRpb24gb2YgdGhlIG9wdGltaXNhdGlvbiBwcm9ibGVtIGZhY2lsaXRhdGVzIHJldXNlIG9mIG1vZGVsLCBkYXRhIGFuZC9vciBvcHRpbWlzYXRpb24gcmVzdWx0cy4gRnV0dXJlIHBlcnR1cmJhdGlvbiBleHBlcmltZW50cyB3aWxsIGltcHJvdmUgcGFyYW1ldGVyIGlkZW50aWZpYWJpbGl0eSBhbmQgYWxsb3cgZm9yIHRlc3RpbmcgbW9kZWwgcHJlZGljdGl2ZSBwb3dlci4gU3VjaCBhIHByZWRpY3RpdmUgbW9kZWwgbWF5IGFpZCBpbiBkcnVnIGRpc2NvdmVyeSBmb3IgY2VsbCBjeWNsZS1yZWxhdGVkIGRpc29yZGVycy4iLAogICAgInllYXIiIDogMjAyNCwKICAgICJtb250aCIgOiAiMSIsCiAgICAidm9sdW1lIiA6ICIyMCIsCiAgICAiaXNzdWUiIDogIjEiLAogICAgInBhZ2VzIiA6ICJlMTAxMTE1MSIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9wdWJtZWQvMzgxOTAzOTgiLAogICAgImF1dGhvcnMiIDogWyB7CiAgICAgICJuYW1lIiA6ICJQYXVsIEYgTGFuZyIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiQmlvc2ltQUkiLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi02Mzg4LTI0MDUiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJEYXZpZCBSIFBlbmFzIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJDb21wdXRhdGlvbmFsIEJpb2xvZ3kgTGFiLCBNQkctQ1NJQyAoU3BhbmlzaCBOYXRpb25hbCBSZXNlYXJjaCBDb3VuY2lsKSwgUG9udGV2ZWRyYSwgU3BhaW4iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJKdWxpbyBSIEJhbmdhIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJDb21wdXRhdGlvbmFsIEJpb2xvZ3kgTGFiLCBNQkctQ1NJQyAoU3BhbmlzaCBOYXRpb25hbCBSZXNlYXJjaCBDb3VuY2lsKSwgUG9udGV2ZWRyYSwgU3BhaW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi00MjQ1LTAzMjAiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJEYW5pZWwgV2VpbmRsIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJDb21wdXRhdGlvbmFsIEhlYWx0aCBDZW50ZXIsIEhlbG1ob2x0eiBaZW50cnVtIE3DvG5jaGVuIERldXRzY2hlcyBGb3JzY2h1bmdzemVudHJ1bSBmw7xyIEdlc3VuZGhlaXQgdW5kIFVtd2VsdCAoR21iSCksIE5ldWhlcmJlcmcsIEdlcm1hbnkiLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMS05OTYzLTYwNTciCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJCZWxhIE5vdmFrIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJVbml2ZXJzaXR5IG9mIE94Zm9yZCIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAyLTY5NjEtMTM2NiIKICAgIH0gXQogIH0sCiAgImZpbGVzIiA6IHsKICAgICJtYWluIiA6IFsgewogICAgICAibmFtZSIgOiAidjQuMC4wX29wdGltaXplZC54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjY4ODYyNiIKICAgIH0gXQogIH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogMywKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA1MzIwMzU4MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJQYXVsIEYgTGFuZyIsCiAgICAgICJjb21tZW50IiA6ICJBZGRlZCBwdWJsaWNhdGlvbiIKICAgIH0gXQogIH0sCiAgImZpcnN0UHVibGlzaGVkIiA6IDE3MDUzMjA1ODMwMDAsCiAgInN1Ym1pc3Npb25JZCIgOiAiTU9ERUwyNDAxMDUwMDAxIgp9 + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2401170001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=ae60803c-665b-4527-97aa-79f414301a94; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.251.16423.667765; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: |- + { + "name" : "Pradeep2024-A Mechanical modelling framework for studying endothelial permeability", + "format" : { + "name" : "Other", + "version" : "*" + }, + "publication" : { + "journal" : "Biophysical journal", + "title" : "A mechanical modelling framework to study endothelial permeability.", + "affiliation" : "School of Mathematics, University of Birmingham, Birmingham, B15 2TS, UK. Electronic address: p.keshavanarayana@bham.ac.uk.", + "synopsis" : "The inner lining of blood vessels, the endothelium, is made up of endothelial cells. Vascular endothelial (VE)-cadherin protein forms a bond with VE-cadherin from neighbouring cells to determine the size of gaps between the cells and thereby regulate the size of particles that can cross the endothelium. Chemical cues such as thrombin, along with mechanical properties of the cell and extracellular matrix (ECM) are known to affect the permeability of endothelial cells. Abnormal permeability is found in patients suffering from diseases including cardiovascular diseases, cancer, and COVID-19. Even though some of the regulatory mechanisms affecting endothelial permeability are well studied, details of how several mechanical and chemical stimuli acting simultaneously affect endothelial permeability are not yet understood. In this article, we present a continuum-level mechanical modelling framework to study the highly dynamic nature of the VE-cadherin bonds. Taking inspiration from the catch-slip behaviour that VE-cadherin complexes are known to exhibit, we model VE-cadherin homophilic bond as cohesive contact with damage following a traction-separation law. We explicitly model the actin-cytoskeleton, and substrate to study their role in permeability. Our studies show that mechano-chemical coupling is necessary to simulate the influence of the mechanical properties of the substrate on permeability. Simulations show that shear between cells is responsible for the variation in permeability between bi-cellular and tri-cellular junctions, explaining the phenotypic differences observed in experiments. An increase in the magnitude of traction force due to disturbed flow that endothelial cells experience results in increased permeability, and it is found that the effect is higher on stiffer ECM. Finally, we show that the cylindrical monolayer exhibits higher permeability than the planar monolayer under unconstrained cases. Thus, we present a contact mechanics-based mechano-chemical model to investigate the variation in permeability of endothelial monolayer due to multiple loads acting simultaneously.", + "year" : 2024, + "month" : "1", + "pages" : "S0006-3495(23)04179-6", + "link" : "http://identifiers.org/doi/10.1016/j.bpj.2023.12.026", + "authors" : [ { + "name" : "Pradeep Keshavanarayana" + }, { + "name" : "Fabian Spill" + } ] + }, + "files" : { + "main" : [ { + "name" : "Planar_Monolayer_With_ECM.inp", + "fileSize" : "0" + } ], + "additional" : [ { + "name" : "UAMP.f", + "fileSize" : "0", + "description" : "Random intercellular pressure acting on cell" + }, { + "name" : "Cell_UMAT_Small_Strain.f", + "fileSize" : "0", + "description" : "Material definition for stress fibre growth" + } ] + }, + "history" : { + "revisions" : [ { + "version" : 1, + "submitted" : 1705508674000, + "submitter" : "Pradeep Keshavanarayana", + "comment" : "Import of Pradeep2024-A Mechanical modelling framework for studying endothelial permeability" + } ] + }, + "firstPublished" : 1705573062000, + "submissionId" : "MODEL2401170001" + } + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2306280002 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=f6d5b117-8d21-437c-83a8-e4746a940cde; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.355.16427.385970; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJCdXJiYW5vMjAyM19IR0ZzaWduYWxpbmdfaW5fRmF0dHlMaXZlckRpc2Vhc2UiLAogICJkZXNjcmlwdGlvbiIgOiAiQ2hyb25pYyBsaXZlciBkaXNlYXNlcyBhcmUgd29ybGR3aWRlIG9uIHRoZSByaXNlLiBEdWUgdG8gdGhlIHJhcGlkbHkgaW5jcmVhc2luZyBpbmNpZGVuY2UsIGluIHBhcnRpY3VsYXIgaW4gV2VzdGVybiBjb3VudHJpZXMsIG5vbi1hbGNvaG9saWMgZmF0dHkgbGl2ZXIgZGlzZWFzZSAoTkFGTEQpIGlzIGdhaW5pbmcgaW1wb3J0YW5jZS4gQXMgdGhlIGRpc2Vhc2UgcHJvZ3Jlc3NlcyBpdCBjYW4gZGV2ZWxvcCBpbnRvIGhlcGF0b2NlbGx1bGFyIGNhcmNpbm9tYS4gTGlwaWQgYWNjdW11bGF0aW9uIGluIGhlcGF0b2N5dGVzIGhhcyBiZWVuIGlkZW50aWZpZWQgYXMgdGhlIGNoYXJhY3RlcmlzdGljIHN0cnVjdHVyYWwgY2hhbmdlIGluIE5BRkxEIGRldmVsb3BtZW50LCBidXQgdGhlIG1vbGVjdWxhciBtZWNoYW5pc21zIHJlc3BvbnNpYmxlIGZvciBkaXNlYXNlIGRldmVsb3BtZW50IHJlbWFpbmVkIHVucmVzb2x2ZWQuIEhlcmUsIHdlIHVuY292ZXIgYSBzdHJvbmcgZG93bnJlZ3VsYXRpb24gb2YgdGhlIFBJM0stQUtUIHBhdGh3YXkgYW5kIGFuIHVwcmVndWxhdGlvbiBvZiB0aGUgTUFQSyBwYXRod2F5IGluIHByaW1hcnkgaGVwYXRvY3l0ZXMgZnJvbSBhIHByZWNsaW5pY2FsIG1vZGVsIGZlZCB3aXRoIGEgV2VzdGVybiBkaWV0IChXRCkuIER5bmFtaWMgcGF0aHdheSBtb2RlbGluZyBvZiBoZXBhdG9jeXRlIGdyb3d0aCBmYWN0b3IgKEhHRikgc2lnbmFsIHRyYW5zZHVjdGlvbiBjb21iaW5lZCB3aXRoIGdsb2JhbCBwcm90ZW9taWNzIGlkZW50aWZpZXMgdGhhdCBhbiBlbGV2YXRlZCBiYXNhbCBNRVQgcGhvc3Bob3J5bGF0aW9uIHJhdGUgaXMgdGhlIG1haW4gZHJpdmVyIG9mIGFsdGVyZWQgc2lnbmFsaW5nIGxlYWRpbmcgdG8gaW5jcmVhc2VkIHByb2xpZmVyYXRpb24gb2YgV0QtaGVwYXRvY3l0ZXMuIE1vZGVsLWFkYXB0YXRpb24gdG8gcGF0aWVudC1kZXJpdmVkIGhlcGF0b2N5dGVzIHJldmVhbHMgYSBwYXRpZW50LXNwZWNpZmljIHZhcmlhYmlsaXR5IGluIGJhc2FsIE1FVCBwaG9zcGhvcnlsYXRpb24sIHdoaWNoIGNvcnJlbGF0ZXMgd2l0aCB0aGUgb3V0Y29tZSBvZiBwYXRpZW50cyBhZnRlciBsaXZlciBzdXJnZXJ5LiBUaHVzLCBkeXNyZWd1bGF0ZWQgYmFzYWwgTUVUIHBob3NwaG9yeWxhdGlvbiBjb3VsZCBiZSBhbiBpbmRpY2F0b3IgZm9yIHRoZSBoZWFsdGggc3RhdHVzIG9mIHRoZSBsaXZlciBhbmQgdGhlcmVieSBpbmZvcm0gb24gdGhlIHJpc2sgb2YgYSBwYXRpZW50IHRvIHN1ZmZlciBmcm9tIGxpdmVyIGZhaWx1cmUgYWZ0ZXIgc3VyZ2VyeS4iLAogICJmb3JtYXQiIDogewogICAgIm5hbWUiIDogIlNCTUwiLAogICAgInZlcnNpb24iIDogIkwyVjQiCiAgfSwKICAicHVibGljYXRpb24iIDogewogICAgImpvdXJuYWwiIDogIk1vbGVjdWxhciBzeXN0ZW1zIGJpb2xvZ3kiLAogICAgInRpdGxlIiA6ICJCYXNhbCBNRVQgcGhvc3Bob3J5bGF0aW9uIGlzIGFuIGluZGljYXRvciBvZiBoZXBhdG9jeXRlIGR5c3JlZ3VsYXRpb24gaW4gbGl2ZXIgZGlzZWFzZS4iLAogICAgImFmZmlsaWF0aW9uIiA6ICJEaXZpc2lvbiBTeXN0ZW1zIEJpb2xvZ3kgb2YgU2lnbmFsIFRyYW5zZHVjdGlvbiwgR2VybWFuIENhbmNlciBSZXNlYXJjaCBDZW50ZXIgKERLRlopLCBIZWlkZWxiZXJnLCBHZXJtYW55LlxuSW5zdGl0dXRlIG9mIFBoeXNpY3MsIFVuaXZlcnNpdHkgb2YgRnJlaWJ1cmcsIEZyZWlidXJnLCBHZXJtYW55XG5MaXZlciBTeXN0ZW1zIE1lZGljaW5lIGFnYWluc3QgQ2FuY2VyIChMaVN5TS1LcmVicyksIEhlaWRlbGJlcmcsIEdlcm1hbnkiLAogICAgInN5bm9wc2lzIiA6ICJDaHJvbmljIGxpdmVyIGRpc2Vhc2VzIGFyZSB3b3JsZHdpZGUgb24gdGhlIHJpc2UuIER1ZSB0byB0aGUgcmFwaWRseSBpbmNyZWFzaW5nIGluY2lkZW5jZSwgaW4gcGFydGljdWxhciBpbiBXZXN0ZXJuIGNvdW50cmllcywgbWV0YWJvbGljIGR5c2Z1bmN0aW9uLWFzc29jaWF0ZWQgc3RlYXRvdGljIGxpdmVyIGRpc2Vhc2UgKE1BU0xEKSBpcyBnYWluaW5nIGltcG9ydGFuY2UgYXMgdGhlIGRpc2Vhc2UgY2FuIGRldmVsb3AgaW50byBoZXBhdG9jZWxsdWxhciBjYXJjaW5vbWEuIExpcGlkIGFjY3VtdWxhdGlvbiBpbiBoZXBhdG9jeXRlcyBoYXMgYmVlbiBpZGVudGlmaWVkIGFzIHRoZSBjaGFyYWN0ZXJpc3RpYyBzdHJ1Y3R1cmFsIGNoYW5nZSBpbiBNQVNMRCBkZXZlbG9wbWVudCwgYnV0IG1vbGVjdWxhciBtZWNoYW5pc21zIHJlc3BvbnNpYmxlIGZvciBkaXNlYXNlIHByb2dyZXNzaW9uIHJlbWFpbmVkIHVucmVzb2x2ZWQuIEhlcmUsIHdlIHVuY292ZXIgaW4gcHJpbWFyeSBoZXBhdG9jeXRlcyBmcm9tIGEgcHJlY2xpbmljYWwgbW9kZWwgZmVkIHdpdGggYSBXZXN0ZXJuIGRpZXQgKFdEKSBhbiBpbmNyZWFzZWQgYmFzYWwgTUVUIHBob3NwaG9yeWxhdGlvbiBhbmQgYSBzdHJvbmcgZG93bnJlZ3VsYXRpb24gb2YgdGhlIFBJM0stQUtUIHBhdGh3YXkuIER5bmFtaWMgcGF0aHdheSBtb2RlbGluZyBvZiBoZXBhdG9jeXRlIGdyb3d0aCBmYWN0b3IgKEhHRikgc2lnbmFsIHRyYW5zZHVjdGlvbiBjb21iaW5lZCB3aXRoIGdsb2JhbCBwcm90ZW9taWNzIGlkZW50aWZpZXMgdGhhdCBhbiBlbGV2YXRlZCBiYXNhbCBNRVQgcGhvc3Bob3J5bGF0aW9uIHJhdGUgaXMgdGhlIG1haW4gZHJpdmVyIG9mIGFsdGVyZWQgc2lnbmFsaW5nIGxlYWRpbmcgdG8gaW5jcmVhc2VkIHByb2xpZmVyYXRpb24gb2YgV0QtaGVwYXRvY3l0ZXMuIE1vZGVsLWFkYXB0YXRpb24gdG8gcGF0aWVudC1kZXJpdmVkIGhlcGF0b2N5dGVzIHJldmVhbCBwYXRpZW50LXNwZWNpZmljIHZhcmlhYmlsaXR5IGluIGJhc2FsIE1FVCBwaG9zcGhvcnlsYXRpb24sIHdoaWNoIGNvcnJlbGF0ZXMgd2l0aCBwYXRpZW50IG91dGNvbWUgYWZ0ZXIgbGl2ZXIgc3VyZ2VyeS4gVGh1cywgZHlzcmVndWxhdGVkIGJhc2FsIE1FVCBwaG9zcGhvcnlsYXRpb24gY291bGQgYmUgYW4gaW5kaWNhdG9yIGZvciB0aGUgaGVhbHRoIHN0YXR1cyBvZiB0aGUgbGl2ZXIgYW5kIHRoZXJlYnkgaW5mb3JtIG9uIHRoZSByaXNrIG9mIGEgcGF0aWVudCB0byBzdWZmZXIgZnJvbSBsaXZlciBmYWlsdXJlIGFmdGVyIHN1cmdlcnkuIiwKICAgICJ5ZWFyIiA6IDIwMjQsCiAgICAibW9udGgiIDogIjEiLAogICAgImxpbmsiIDogImh0dHA6Ly9pZGVudGlmaWVycy5vcmcvZG9pLzEwLjEwMzgvczQ0MzIwLTAyMy0wMDAwNy00IiwKICAgICJhdXRob3JzIiA6IFsgewogICAgICAibmFtZSIgOiAiU2ViYXN0aWFuIEJ1cmJhbm8gZGUgTGFyYSIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAyLTUzNjQtMjY4NiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlN2ZW5qYSBLZW1tZXIiLAogICAgICAiaW5zdGl0dXRpb24iIDogIlVuaXZlcnNpdHkgb2YgRnJlaWJ1cmciLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMS03NzA4LTA1NDUiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJJbmEgQmllcm1heWVyIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU3ZlbmphIEZlaWxlciIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFydHlvbSBWbGFzb3YiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJMb3JlbnphIEEgRCdBbGVzc2FuZHJvIgogICAgfSwgewogICAgICAibmFtZSIgOiAiQmFyYmFyYSBIZWxtIgogICAgfSwgewogICAgICAibmFtZSIgOiAiQ2hyaXN0aW5hIE3DtmxkZXJzIgogICAgfSwgewogICAgICAibmFtZSIgOiAiWWFubmlrIERpZXRlciIsCiAgICAgICJvcmNpZCIgOiAiMDAwOS0wMDA0LTI1OTktNzg3OSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFobWVkIEdoYWxsYWIiLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMy0wNjk1LTM0MDMiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJKYW4gRyBIZW5nc3RsZXIiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJSZW5uZXJ0IEMiLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMS04NzUzLTg0NDEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJNYWRsZW4gTWF0ei1Tb2phIgogICAgfSwgewogICAgICAibmFtZSIgOiAiQ2hyaXN0aW5hIEfDtnR6IiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDItODA2MC0xNTk5IgogICAgfSwgewogICAgICAibmFtZSIgOiAiR2VvcmcgRGFtbSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkthdHJpbiBIb2ZmbWFubiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkRhbmllbCBTZWVob2ZlciIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlRob21hcyBCZXJnIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU2NoaWxsaW5nIE0iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi05NTE3LTUxNjYiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJUaW1tZXIgSiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAzLTQ1MTctMTM4MyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIktsaW5nbcO8bGxlciBVIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDEtOTg0NS0zMDk5IgogICAgfSBdCiAgfSwKICAiZmlsZXMiIDogewogICAgIm1haW4iIDogWyB7CiAgICAgICJuYW1lIiA6ICJtb2RlbF9CdXJiYW5vMjAyMy54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjU3NzQ4IgogICAgfSBdLAogICAgImFkZGl0aW9uYWwiIDogWyB7CiAgICAgICJuYW1lIiA6ICJQRXRhYl9CdXJiYW5vMjAyM19IdW1hbi56aXAiLAogICAgICAiZmlsZVNpemUiIDogIjI4NTQ4IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJQRXRhYiBmaWxlcyBmb3IgdGhlIEhHRiBzaWduYWxpbmcgbW9kZWwgY2FsaWJyYXRlZCBvbiBwYXRpZW50IGRhdGEgKGMuZi4gaHR0cHM6Ly9wZXRhYi5yZWFkdGhlZG9jcy5pbykiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJQRXRhYl9CdXJiYW5vMjAyM19Nb3VzZS56aXAiLAogICAgICAiZmlsZVNpemUiIDogIjE5MzgyIiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJQRXRhYiBmaWxlcyBmb3IgdGhlIEhHRiBzaWduYWxpbmcgbW9kZWwgY2FsaWJyYXRlZCBvbiBXZXN0ZXJuIGRpZXQgbW91c2UgZGF0YSAoYy5mLiBodHRwczovL3BldGFiLnJlYWR0aGVkb2NzLmlvKSIKICAgIH0gXQogIH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogMywKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA1MzU1MTgxMDAwLAogICAgICAic3VibWl0dGVyIiA6ICJTdmVuamEgS2VtbWVyIiwKICAgICAgImNvbW1lbnQiIDogImFkZCBwdWJsaWNhdGlvbiBhZ2FpbiIKICAgIH0gXQogIH0sCiAgImZpcnN0UHVibGlzaGVkIiA6IDE3MDU0MjIwNjEwMDAsCiAgInN1Ym1pc3Npb25JZCIgOiAiTU9ERUwyMzA2MjgwMDAyIgp9 + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2401110001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=e91ed913-0c70-4cf1-9780-aaf16ac45de5; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.474.16429.257222; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJwYW5jcmVhcyBnbHVjb3NlIG1vZGVsIiwKICAiZGVzY3JpcHRpb24iIDogIlRoZSBwYW5jcmVhcyBwbGF5cyBhIGNyaXRpY2FsIHJvbGUgaW4gbWFpbnRhaW5pbmcgZ2x1Y29zZSBob21lb3N0YXNpcyB0aHJvdWdoIHRoZSBzZWNyZXRpb24gb2YgaG9ybW9uZXMgZnJvbSB0aGUgaXNsZXRzIG9mIExhbmdlcmhhbnMuIEdsdWNvc2Utc3RpbXVsYXRlZCBpbnN1bGluIHNlY3JldGlvbiAoR1NJUykgYnkgdGhlIHBhbmNyZWF0aWMgYmV0YS1jZWxsIGlzIHRoZSBtYWluIG1lY2hhbmlzbSBmb3IgcmVkdWNpbmcgZWxldmF0ZWQgcGxhc21hIGdsdWNvc2UuXG5IZXJlIHdlIHByZXNlbnQgYSBzeXN0ZW1hdGljIG1vZGVsaW5nIHdvcmtmbG93IGZvciB0aGUgZGV2ZWxvcG1lbnQgb2Yga2luZXRpYyBwYXRod2F5IG1vZGVscyB1c2luZyB0aGUgU3lzdGVtcyBCaW9sb2d5IE1hcmt1cCBMYW5ndWFnZSAoU0JNTCkuIEFuIGltcG9ydGFudCBmYWN0b3Igd2FzIHRoZSByZXByb2R1Y2liaWxpdHkgYW5kIGV4Y2hhbmdlYWJpbGl0eSBvZiB0aGUgbW9kZWwsIHdoaWNoIGFsbG93ZWQgdGhlIHVzZSBvZiB2YXJpb3VzIGV4aXN0aW5nIHRvb2xzLlxuVGhlIHdvcmtmbG93IHdhcyBhcHBsaWVkIHRvIGNvbnN0cnVjdCBhIG5vdmVsIGRhdGEtZHJpdmVuIGtpbmV0aWMgbW9kZWwgb2YgR1NJUyBpbiB0aGUgcGFuY3JlYXRpYyBiZXRhLWNlbGwgYmFzZWQgb24gZXhwZXJpbWVudGFsIGFuZCBjbGluaWNhbCBkYXRhIGZyb20gMzkgc3R1ZGllcyBzcGFubmluZyA1MCB5ZWFycyBvZiBwYW5jcmVhdGljLCBpc2xldCwgYW5kIGJldGEtY2VsbCByZXNlYXJjaCBpbiBodW1hbnMsIHJhdHMsIG1pY2UsIGFuZCBjZWxsIGxpbmVzLiBUaGUgbW9kZWwgY29uc2lzdHMgb2YgZGV0YWlsZWQgZ2x5Y29seXNpcyBhbmQgcGhlbm9tZW5vbG9naWNhbCBlcXVhdGlvbnMgZm9yIGluc3VsaW4gc2VjcmV0aW9uIGNvdXBsZWQgdG8gY2VsbHVsYXIgZW5lcmd5IHN0YXRlLCBBVFAgZHluYW1pY3MgYW5kIChBVFAvQURQIHJhdGlvKS5cbktleSBmaW5kaW5ncyBvZiBvdXIgd29yayBhcmUgdGhhdCBpbiBHU0lTIHRoZXJlIGlzIGEgZ2x1Y29zZS1kZXBlbmRlbnQgaW5jcmVhc2UgaW4gYWxtb3N0IGFsbCBpbnRlcm1lZGlhdGVzIG9mIGdseWNvbHlzaXMuIFRoaXMgaW5jcmVhc2UgaW4gZ2x5Y29seXRpYyBtZXRhYm9saXRlcyBpcyBhY2NvbXBhbmllZCBieSBhbiBpbmNyZWFzZSBpbiBlbmVyZ3kgbWV0YWJvbGl0ZXMsIGVzcGVjaWFsbHkgQVRQIGFuZCBOQURILiBPbmUgb2YgdGhlIGZldyBkZWNyZWFzaW5nIG1ldGFib2xpdGVzIGlzIEFEUCwgd2hpY2gsIGluIGNvbWJpbmF0aW9uIHdpdGggdGhlIGluY3JlYXNlIGluIEFUUCwgcmVzdWx0cyBpbiBhIGxhcmdlIGluY3JlYXNlIGluIEFUUC9BRFAgcmF0aW9zIGluIHRoZSBiZXRhLWNlbGwgd2l0aCBpbmNyZWFzaW5nIGdsdWNvc2UuIEluc3VsaW4gc2VjcmV0aW9uIGlzIGRlcGVuZGVudCBvbiBBVFAvQURQLCByZXN1bHRpbmcgaW4gZ2x1Y29zZS1zdGltdWxhdGVkIGluc3VsaW4gc2VjcmV0aW9uLiIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiU0JNTCIsCiAgICAidmVyc2lvbiIgOiAiTDNWMiIKICB9LAogICJwdWJsaWNhdGlvbiIgOiB7CiAgICAiam91cm5hbCIgOiAiRnJvbnRpZXJzIGluIGVuZG9jcmlub2xvZ3kiLAogICAgInRpdGxlIiA6ICJBIHBhdGh3YXkgbW9kZWwgb2YgZ2x1Y29zZS1zdGltdWxhdGVkIGluc3VsaW4gc2VjcmV0aW9uIGluIHRoZSBwYW5jcmVhdGljIDxpPs6yPC9pPi1jZWxsLiIsCiAgICAiYWZmaWxpYXRpb24iIDogIkRlcGFydG1lbnQgb2YgQ29tcHV0YXRpb25hbCBhbmQgRGF0YSBTY2llbmNlcywgSW5kaWFuIEluc3RpdHV0ZSBvZiBTY2llbmNlLCBCYW5nYWxvcmUsIEluZGlhLiIsCiAgICAic3lub3BzaXMiIDogIlRoZSBwYW5jcmVhcyBwbGF5cyBhIGNyaXRpY2FsIHJvbGUgaW4gbWFpbnRhaW5pbmcgZ2x1Y29zZSBob21lb3N0YXNpcyB0aHJvdWdoIHRoZSBzZWNyZXRpb24gb2YgaG9ybW9uZXMgZnJvbSB0aGUgaXNsZXRzIG9mIExhbmdlcmhhbnMuIEdsdWNvc2Utc3RpbXVsYXRlZCBpbnN1bGluIHNlY3JldGlvbiAoR1NJUykgYnkgdGhlIHBhbmNyZWF0aWMgPGk+zrI8L2k+LWNlbGwgaXMgdGhlIG1haW4gbWVjaGFuaXNtIGZvciByZWR1Y2luZyBlbGV2YXRlZCBwbGFzbWEgZ2x1Y29zZS4gSGVyZSB3ZSBwcmVzZW50IGEgc3lzdGVtYXRpYyBtb2RlbGluZyB3b3JrZmxvdyBmb3IgdGhlIGRldmVsb3BtZW50IG9mIGtpbmV0aWMgcGF0aHdheSBtb2RlbHMgdXNpbmcgdGhlIFN5c3RlbXMgQmlvbG9neSBNYXJrdXAgTGFuZ3VhZ2UgKFNCTUwpLiBTdGVwcyBpbmNsdWRlIHJldHJpZXZhbCBvZiBpbmZvcm1hdGlvbiBmcm9tIGRhdGFiYXNlcywgY3VyYXRpb24gb2YgZXhwZXJpbWVudGFsIGFuZCBjbGluaWNhbCBkYXRhIGZvciBtb2RlbCBjYWxpYnJhdGlvbiBhbmQgdmFsaWRhdGlvbiwgaW50ZWdyYXRpb24gb2YgaGV0ZXJvZ2VuZW91cyBkYXRhIGluY2x1ZGluZyBhYnNvbHV0ZSBhbmQgcmVsYXRpdmUgbWVhc3VyZW1lbnRzLCB1bml0IG5vcm1hbGl6YXRpb24sIGRhdGEgbm9ybWFsaXphdGlvbiwgYW5kIG1vZGVsIGFubm90YXRpb24uIEFuIGltcG9ydGFudCBmYWN0b3Igd2FzIHRoZSByZXByb2R1Y2liaWxpdHkgYW5kIGV4Y2hhbmdlYWJpbGl0eSBvZiB0aGUgbW9kZWwsIHdoaWNoIGFsbG93ZWQgdGhlIHVzZSBvZiB2YXJpb3VzIGV4aXN0aW5nIHRvb2xzLiBUaGUgd29ya2Zsb3cgd2FzIGFwcGxpZWQgdG8gY29uc3RydWN0IGEgbm92ZWwgZGF0YS1kcml2ZW4ga2luZXRpYyBtb2RlbCBvZiBHU0lTIGluIHRoZSBwYW5jcmVhdGljIDxpPs6yPC9pPi1jZWxsIGJhc2VkIG9uIGV4cGVyaW1lbnRhbCBhbmQgY2xpbmljYWwgZGF0YSBmcm9tIDM5IHN0dWRpZXMgc3Bhbm5pbmcgNTAgeWVhcnMgb2YgcGFuY3JlYXRpYywgaXNsZXQsIGFuZCA8aT7OsjwvaT4tY2VsbCByZXNlYXJjaCBpbiBodW1hbnMsIHJhdHMsIG1pY2UsIGFuZCBjZWxsIGxpbmVzLiBUaGUgbW9kZWwgY29uc2lzdHMgb2YgZGV0YWlsZWQgZ2x5Y29seXNpcyBhbmQgcGhlbm9tZW5vbG9naWNhbCBlcXVhdGlvbnMgZm9yIGluc3VsaW4gc2VjcmV0aW9uIGNvdXBsZWQgdG8gY2VsbHVsYXIgZW5lcmd5IHN0YXRlLCBBVFAgZHluYW1pY3MgYW5kIChBVFAvQURQIHJhdGlvKS4gS2V5IGZpbmRpbmdzIG9mIG91ciB3b3JrIGFyZSB0aGF0IGluIEdTSVMgdGhlcmUgaXMgYSBnbHVjb3NlLWRlcGVuZGVudCBpbmNyZWFzZSBpbiBhbG1vc3QgYWxsIGludGVybWVkaWF0ZXMgb2YgZ2x5Y29seXNpcy4gVGhpcyBpbmNyZWFzZSBpbiBnbHljb2x5dGljIG1ldGFib2xpdGVzIGlzIGFjY29tcGFuaWVkIGJ5IGFuIGluY3JlYXNlIGluIGVuZXJneSBtZXRhYm9saXRlcywgZXNwZWNpYWxseSBBVFAgYW5kIE5BREguIE9uZSBvZiB0aGUgZmV3IGRlY3JlYXNpbmcgbWV0YWJvbGl0ZXMgaXMgQURQLCB3aGljaCwgaW4gY29tYmluYXRpb24gd2l0aCB0aGUgaW5jcmVhc2UgaW4gQVRQLCByZXN1bHRzIGluIGEgbGFyZ2UgaW5jcmVhc2UgaW4gQVRQL0FEUCByYXRpb3MgaW4gdGhlIDxpPs6yPC9pPi1jZWxsIHdpdGggaW5jcmVhc2luZyBnbHVjb3NlLiBJbnN1bGluIHNlY3JldGlvbiBpcyBkZXBlbmRlbnQgb24gQVRQL0FEUCwgcmVzdWx0aW5nIGluIGdsdWNvc2Utc3RpbXVsYXRlZCBpbnN1bGluIHNlY3JldGlvbi4gVGhlIG9ic2VydmVkIGdsdWNvc2UtZGVwZW5kZW50IGluY3JlYXNlIGluIGdseWNvbHl0aWMgaW50ZXJtZWRpYXRlcyBhbmQgdGhlIHJlc3VsdGluZyBjaGFuZ2UgaW4gQVRQL0FEUCByYXRpb3MgYW5kIGluc3VsaW4gc2VjcmV0aW9uIGlzIGEgcm9idXN0IHBoZW5vbWVub24gb2JzZXJ2ZWQgYWNyb3NzIGRhdGEgc2V0cywgZXhwZXJpbWVudGFsIHN5c3RlbXMgYW5kIHNwZWNpZXMuIE1vZGVsIHByZWRpY3Rpb25zIG9mIHRoZSBnbHVjb3NlLWRlcGVuZGVudCByZXNwb25zZSBvZiBnbHljb2x5dGljIGludGVybWVkaWF0ZXMgYW5kIGJpcGhhc2ljIGluc3VsaW4gc2VjcmV0aW9uIGFyZSBpbiBnb29kIGFncmVlbWVudCB3aXRoIGV4cGVyaW1lbnRhbCBtZWFzdXJlbWVudHMuIE91ciBtb2RlbCBwcmVkaWN0cyB0aGF0IGZhY3RvcnMgYWZmZWN0aW5nIEFUUCBjb25zdW1wdGlvbiwgQVRQIGZvcm1hdGlvbiwgaGV4b2tpbmFzZSwgcGhvc3Bob2ZydWN0b2tpbmFzZSwgYW5kIEFUUC9BRFAtZGVwZW5kZW50IGluc3VsaW4gc2VjcmV0aW9uIGhhdmUgYSBtYWpvciBlZmZlY3Qgb24gR1NJUy4gSW4gY29uY2x1c2lvbiwgd2UgaGF2ZSBkZXZlbG9wZWQgYW5kIGFwcGxpZWQgYSBzeXN0ZW1hdGljIG1vZGVsaW5nIHdvcmtmbG93IGZvciBwYXRod2F5IG1vZGVscyB0aGF0IGFsbG93ZWQgdXMgdG8gZ2FpbiBpbnNpZ2h0IGludG8ga2V5IG1lY2hhbmlzbXMgaW4gR1NJUyBpbiB0aGUgcGFuY3JlYXRpYyA8aT7OsjwvaT4tY2VsbC4iLAogICAgInllYXIiIDogMjAyMywKICAgICJ2b2x1bWUiIDogIjE0IiwKICAgICJwYWdlcyIgOiAiMTE4NTY1NiIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9kb2kvMTAuMzM4OS9mZW5kby4yMDIzLjExODU2NTYiLAogICAgImF1dGhvcnMiIDogWyB7CiAgICAgICJuYW1lIiA6ICJNIERlZXBhIE1haGVzaHZhcmUiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJTb3VteWVuZHUgUmFoYSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIk1hdHRoaWFzIEvDtm5pZyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkRlYm5hdGggUGFsIgogICAgfSBdCiAgfSwKICAiZmlsZXMiIDogewogICAgIm1haW4iIDogWyB7CiAgICAgICJuYW1lIiA6ICJwYW5jcmVhc19kZWVwYS54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjIyNDkzMiIKICAgIH0gXQogIH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogMSwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA0OTYyMDQzMDAwLAogICAgICAic3VibWl0dGVyIiA6ICJEZWVwYSBNYWhlc2h2YXJlIE0uIiwKICAgICAgImNvbW1lbnQiIDogIkltcG9ydCBvZiBwYW5jcmVhcyBnbHVjb3NlIG1vZGVsIgogICAgfSBdCiAgfSwKICAiZmlyc3RQdWJsaXNoZWQiIDogMTcwNTYwMjE2NzAwMCwKICAic3VibWlzc2lvbklkIiA6ICJNT0RFTDI0MDExMTAwMDEiCn0= + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/BIOMD0000001077 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=78676f60-e1fb-4d33-b041-2495d9036566; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.58.16427.911888; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJBZGx1bmcyMDIxIC0gQ2VsbC10by1jZWxsIHZhcmlhYmlsaXR5IGluIEpBSzIvU1RBVDUgcGF0aHdheSIsCiAgImRlc2NyaXB0aW9uIiA6ICI8bm90ZXMgeG1sbnM9XCJodHRwOi8vd3d3LnNibWwub3JnL3NibWwvbGV2ZWwyL3ZlcnNpb240XCI+XG4gICAgICA8Ym9keSB4bWxucz1cImh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWxcIj5cbiAgICAgICAgPHA+QSBtYXRoZW1hdGljYWwgbW9kZWwgZm9yIGNlbGwtdG8tY2VsbCB2YXJpYWJpbGl0eSBpbiBKQUsyL1NUQVQ1IHBhdGh3YXkgY29tcG9uZW50cyBhbmQgY3l0b3BsYXNtaWMgdm9sdW1lcyBkZWZpbmVzIHN1cnZpdmFsIHRocmVzaG9sZCBpbiBlcnl0aHJvaWQgcHJvZ2VuaXRvciBjZWxsczwvcD5cbiAgICAgIDwvYm9keT5cbiAgICA8L25vdGVzPiIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiU0JNTCIsCiAgICAidmVyc2lvbiIgOiAiTDJWNCIKICB9LAogICJwdWJsaWNhdGlvbiIgOiB7CiAgICAiam91cm5hbCIgOiAiQ2VsbCByZXBvcnRzIiwKICAgICJ0aXRsZSIgOiAiQ2VsbC10by1jZWxsIHZhcmlhYmlsaXR5IGluIEpBSzIvU1RBVDUgcGF0aHdheSBjb21wb25lbnRzIGFuZCBjeXRvcGxhc21pYyB2b2x1bWVzIGRlZmluZXMgc3Vydml2YWwgdGhyZXNob2xkIGluIGVyeXRocm9pZCBwcm9nZW5pdG9yIGNlbGxzLiIsCiAgICAiYWZmaWxpYXRpb24iIDogIkRpdmlzaW9uIFN5c3RlbXMgQmlvbG9neSBvZiBTaWduYWwgVHJhbnNkdWN0aW9uLCBHZXJtYW4gQ2FuY2VyIFJlc2VhcmNoIENlbnRlciAoREtGWiksIDY5MTIwIEhlaWRlbGJlcmcsIEdlcm1hbnk7IERlcGFydG1lbnQgb2YgTWVkaWNpbmUsIFVuaXZlcnNpdHkgTWVkaWNhbCBDZW50ZXIgSGFtYnVyZy1FcHBlbmRvcmYsIDIwMjQ2IEhhbWJ1cmcsIEdlcm1hbnk7IEhhbWJ1cmcgQ2VudGVyIGZvciBUcmFuc2xhdGlvbmFsIEltbXVub2xvZ3kgKEhDVEkpLCBVbml2ZXJzaXR5IE1lZGljYWwgQ2VudGVyIEhhbWJ1cmctRXBwZW5kb3JmLCAyMDI0NiBIYW1idXJnLCBHZXJtYW55LiIsCiAgICAic3lub3BzaXMiIDogIlN1cnZpdmFsIG9yIGFwb3B0b3NpcyBpcyBhIGJpbmFyeSBkZWNpc2lvbiBpbiBpbmRpdmlkdWFsIGNlbGxzLiBIb3dldmVyLCBhdCB0aGUgY2VsbC1wb3B1bGF0aW9uIGxldmVsLCBhIGdyYWRlZCBpbmNyZWFzZSBpbiBzdXJ2aXZhbCBvZiBjb2xvbnktZm9ybWluZyB1bml0LWVyeXRocm9pZCAoQ0ZVLUUpIGNlbGxzIGlzIG9ic2VydmVkIHVwb24gc3RpbXVsYXRpb24gd2l0aCBlcnl0aHJvcG9pZXRpbiAoRXBvKS4gVG8gaWRlbnRpZnkgY29tcG9uZW50cyBvZiBKYW51cyBraW5hc2UgMi9zaWduYWwgdHJhbnNkdWNlciBhbmQgYWN0aXZhdG9yIG9mIHRyYW5zY3JpcHRpb24gNSAoSkFLMi9TVEFUNSkgc2lnbmFsIHRyYW5zZHVjdGlvbiB0aGF0IGNvbnRyaWJ1dGUgdG8gdGhlIGdyYWRlZCBwb3B1bGF0aW9uIHJlc3BvbnNlLCB3ZSBleHRlbmRlZCBhIGNlbGwtcG9wdWxhdGlvbi1sZXZlbCBtb2RlbCBjYWxpYnJhdGVkIHdpdGggZXhwZXJpbWVudGFsIGRhdGEgdG8gc3R1ZHkgdGhlIGJlaGF2aW9yIGluIHNpbmdsZSBjZWxscy4gVGhlIHNpbmdsZS1jZWxsIG1vZGVsIHNob3dzIHRoYXQgdGhlIGhpZ2ggY2VsbC10by1jZWxsIHZhcmlhYmlsaXR5IGluIG51Y2xlYXIgcGhvc3Bob3J5bGF0ZWQgU1RBVDUgaXMgY2F1c2VkIGJ5IHZhcmlhYmlsaXR5IGluIHRoZSBhbW91bnQgb2YgRXBvIHJlY2VwdG9yIChFcG9SKTpKQUsyIGNvbXBsZXhlcyBhbmQgb2YgU0hQMSwgYXMgd2VsbCBhcyB0aGUgZXh0ZW50IG9mIG51Y2xlYXIgaW1wb3J0IGJlY2F1c2Ugb2YgdGhlIGxhcmdlIHZhcmlhbmNlIGluIHRoZSBjeXRvcGxhc21pYyB2b2x1bWUgb2YgQ0ZVLUUgY2VsbHMuIDI0LTExOCBwU1RBVDUgbW9sZWN1bGVzIGluIHRoZSBudWNsZXVzIGZvciAxMjDCoG1pbiBhcmUgc3VmZmljaWVudCB0byBlbnN1cmUgY2VsbCBzdXJ2aXZhbC4gVGh1cywgdmFyaWFiaWxpdHkgaW4gbWVtYnJhbmUtYXNzb2NpYXRlZCBwcm9jZXNzZXMgaXMgc3VmZmljaWVudCB0byBjb252ZXJ0IGEgc3dpdGNoLWxpa2UgYmVoYXZpb3IgYXQgdGhlIHNpbmdsZS1jZWxsIGxldmVsIHRvIGEgZ3JhZGVkIHBvcHVsYXRpb24tbGV2ZWwgcmVzcG9uc2UuIiwKICAgICJ5ZWFyIiA6IDIwMjEsCiAgICAibW9udGgiIDogIjgiLAogICAgInZvbHVtZSIgOiAiMzYiLAogICAgImlzc3VlIiA6ICI2IiwKICAgICJwYWdlcyIgOiAiMTA5NTA3IiwKICAgICJsaW5rIiA6ICJodHRwOi8vaWRlbnRpZmllcnMub3JnL3B1Ym1lZC8zNDM4MDA0MCIsCiAgICAiYXV0aG9ycyIgOiBbIHsKICAgICAgIm5hbWUiIDogIkFkbHVuZyBMIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU3RhcG9yIFAiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJUw7Zuc2luZyBDIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU2NobWllc3RlciBMIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU2Nod2Fyem3DvGxsZXIgTEUiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJQb3N0YXdhIEwiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJXYW5nIEQiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJUaW1tZXIgSiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIktsaW5nbcO8bGxlciBVIgogICAgfSwgewogICAgICAibmFtZSIgOiAiSGFzZW5hdWVyIEoiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJTY2hpbGxpbmcgTSIKICAgIH0gXQogIH0sCiAgImZpbGVzIiA6IHsKICAgICJtYWluIiA6IFsgewogICAgICAibmFtZSIgOiAiQWRsdW5nMjAyMSBfbW9kZWxfamFrc3RhdF9wYS54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjEwMTEzOSIKICAgIH0gXSwKICAgICJhZGRpdGlvbmFsIiA6IFsgewogICAgICAibmFtZSIgOiAibW9kZWxfamFrc3RhdF9wYS54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjMzMjA2IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJQb3B1bGF0aW9uLWF2ZXJhZ2UgbWF0aGVtYXRpY2FsIG1vZGVsIG9mIEVwby1pbmR1Y2VkIEpBSzIvU1RBVDUgc2lnbmFsIHRyYW5zZHVjdGlvbiBpbiBDRlUtRSBjZWxscyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFkbHVuZzIwMjEgX21vZGVsX2pha3N0YXRfcGEuc2VkbWwiLAogICAgICAiZmlsZVNpemUiIDogIjY0NTMiLAogICAgICAiZGVzY3JpcHRpb24iIDogIlNFRE1MIGZpbGUgZm9yIHRoZSBjdXJhdGVkIG1vZGVsIgogICAgfSwgewogICAgICAibmFtZSIgOiAiQWRsdW5nMjAyMSBfbW9kZWxfamFrc3RhdF9wYS5jcHMiLAogICAgICAiZmlsZVNpemUiIDogIjE2ODQ4NCIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiQ09QQVNJIGZpbGUgZm9yIHRoZSBjdXJhdGVkIG1vZGVsIgogICAgfSBdCiAgfSwKICAiaGlzdG9yeSIgOiB7CiAgICAicmV2aXNpb25zIiA6IFsgewogICAgICAidmVyc2lvbiIgOiAyLAogICAgICAic3VibWl0dGVkIiA6IDE2Mjg4NjE2MjEwMDAsCiAgICAgICJzdWJtaXR0ZXIiIDogIktyaXNobmEgS3VtYXIgVGl3YXJpIiwKICAgICAgImNvbW1lbnQiIDogIlVwZGF0ZWQgbW9kZWwgZGVzY3JpcHRpb24gYW5kIHB1YmxpY2F0aW9uIGlkLiIKICAgIH0sIHsKICAgICAgInZlcnNpb24iIDogNCwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA2MjkyODc2MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJLcmlzaG5hIEt1bWFyIFRpd2FyaSIsCiAgICAgICJjb21tZW50IiA6ICJBdXRvbWF0aWNhbGx5IGFkZGVkIG1vZGVsIGlkZW50aWZpZXIgQklPTUQwMDAwMDAxMDc3IgogICAgfSBdCiAgfSwKICAiZmlyc3RQdWJsaXNoZWQiIDogMTcwNjI5Mjg4MTAwMCwKICAic3VibWlzc2lvbklkIiA6ICJNT0RFTDIxMDMwODAwMDEiLAogICJwdWJsaWNhdGlvbklkIiA6ICJCSU9NRDAwMDAwMDEwNzciCn0= + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2307180001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=7839a8b8-e512-4bfa-944c-758568ad9573; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.694.16427.851355; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJaZXJyb3VrMjAyMyAtIExhcmdlIHNjYWxlIGNvbXB1dGF0aW9uYWwgbW9kZWxpbmcgb2YgdGhlIE0xIHN5bm92aWFsIG1hY3JvcGhhZ2UgaW4gcmhldW1hdG9pZCBhcnRocml0aXMiLAogICJmb3JtYXQiIDogewogICAgIm5hbWUiIDogIlNCTUwiLAogICAgInZlcnNpb24iIDogIkwzVjEiCiAgfSwKICAicHVibGljYXRpb24iIDogewogICAgImpvdXJuYWwiIDogIk5QSiBTeXN0IEJpb2wgQXBwbCIsCiAgICAidGl0bGUiIDogIkxhcmdlLXNjYWxlIGNvbXB1dGF0aW9uYWwgbW9kZWxsaW5nIG9mIHRoZSBNMSBhbmQgTTIgc3lub3ZpYWwgbWFjcm9waGFnZXMgaW4gcmhldW1hdG9pZCBhcnRocml0aXMiLAogICAgImFmZmlsaWF0aW9uIiA6ICJHZW5Ib3RlbCwgTGFib3JhdG9pcmUgRXVyb3DDqWVuIGRlIFJlY2hlcmNoZSBQb3VyIExhIFBvbHlhcnRocml0ZSBSaHVtYXRvw69kZSwgVW5pdmVyc2l0eSBQYXJpcy1TYWNsYXksIFVuaXZlcnNpdHkgRXZyeSwgRXZyeSwgRnJhbmNlLiBcblNhbm9maSBSJkQgRGF0YSBhbmQgRGF0YSBTY2llbmNlLFxuQXJ0aWZpY2lhbCBJbnRlbGxpZ2VuY2UgJiBEZWVwIEFuYWx5dGljcywgT21pY3MgRGF0YSBTY2llbmNlLCAxLCBBdiBQaWVycmUgQnJvc3NvbGV0dGUsIDkxMzg1IENoaWxseS1NYXphcmluLCBGcmFuY2UuIFxuQWR2YW5jZWQgUmVzZWFyY2ggQ29tcHV0aW5nIENlbnRyZSwgVW5pdmVyc2l0eSBDb2xsZWdlXG5Mb25kb24sIExvbmRvbiwgVUsuIFxuRGVwYXJ0bWVudCBvZiBNZWRpY2FsIFBoeXNpY3MgYW5kIEJpb21lZGljYWwgRW5naW5lZXJpbmcsIFVuaXZlcnNpdHkgQ29sbGVnZSBMb25kb24sIExvbmRvbiwgVUsuIFxuTGlmZXdhcmUgR3JvdXAsIElucmlhIFNhY2xheSwgUGFsYWlzZWF1LCBGcmFuY2UuIiwKICAgICJzeW5vcHNpcyIgOiAiTWFjcm9waGFnZXMgcGxheSBhbiBlc3NlbnRpYWwgcm9sZSBpbiByaGV1bWF0b2lkIGFydGhyaXRpcy4gRGVwZW5kaW5nIG9uIHRoZWlyIHBoZW5vdHlwZSAoTTEgb3IgTTIpLCB0aGV5IGNhbiBwbGF5IGEgcm9sZSBpbiB0aGVcbmluaXRpYXRpb24gb3IgcmVzb2x1dGlvbiBvZiBpbmZsYW1tYXRpb24uIFRoZSBNMS9NMiByYXRpbyBpbiByaGV1bWF0b2lkIGFydGhyaXRpcyBpcyBoaWdoZXIgdGhhbiBpbiBoZWFsdGh5IGNvbnRyb2xzLiBEZXNwaXRlIHRoaXMsIG5vXG50cmVhdG1lbnQgdGFyZ2V0aW5nIHNwZWNpZmljYWxseSBtYWNyb3BoYWdlcyBpcyBjdXJyZW50bHkgdXNlZCBpbiBjbGluaWNzLiBUaHVzLCBkZXZpc2luZyBzdHJhdGVnaWVzIHRvIHNlbGVjdGl2ZWx5IGRlcGxldGVcbnByb2luZmxhbW1hdG9yeSBtYWNyb3BoYWdlcyBhbmQgcHJvbW90ZSBhbnRpLWluZmxhbW1hdG9yeSBtYWNyb3BoYWdlcyBjb3VsZCBiZSBhIHByb21pc2luZyB0aGVyYXBldXRpYyBhcHByb2FjaC4gU3RhdGUtb2Z0aGUtYXJ0IG1vbGVjdWxhciBpbnRlcmFjdGlvbiBtYXBzIG9mIE0xIGFuZCBNMiBtYWNyb3BoYWdlcyBpbiByaGV1bWF0b2lkIGFydGhyaXRpcyBhcmUgYXZhaWxhYmxlIGFuZCByZXByZXNlbnQgYSBkZW5zZSBzb3VyY2Vcbm9mIGtub3dsZWRnZTsgaG93ZXZlciwgdGhlc2UgbWFwcyByZW1haW4gbGltaXRlZCBieSB0aGVpciBzdGF0aWMgbmF0dXJlLiBEaXNjcmV0ZSBkeW5hbWljIG1vZGVsbGluZyBjYW4gYmUgZW1wbG95ZWQgdG8gc3R1ZHlcbnRoZSBlbWVyZ2VudCBiZWhhdmlvdXJzIG9mIHRoZXNlIHN5c3RlbXMuIE5ldmVydGhlbGVzcywgaGFuZGxpbmcgc3VjaCBsYXJnZS1zY2FsZSBtb2RlbHMgaXMgY2hhbGxlbmdpbmcuIER1ZSB0byB0aGVpciBtYXNzaXZlIHNpemUsXG5pdCBpcyBjb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nIHRvIGlkZW50aWZ5IGJpb2xvZ2ljYWxseSByZWxldmFudCBzdGF0ZXMgaW4gYSBjZWxsLSBhbmQgZGlzZWFzZS1zcGVjaWZpYyBjb250ZXh0LiBJbiB0aGlzIHdvcmssIHdlXG5kZXZlbG9wZWQgYW4gZWZmaWNpZW50IGNvbXB1dGF0aW9uYWwgZnJhbWV3b3JrIHRoYXQgY29udmVydHMgbW9sZWN1bGFyIGludGVyYWN0aW9uIG1hcHMgaW50byBCb29sZWFuIG1vZGVscyB1c2luZyB0aGUgQ2FTUVxudG9vbC4gTmV4dCwgd2UgdXNlZCBhIG5ld2x5IGRldmVsb3BlZCB2ZXJzaW9uIG9mIHRoZSBCTUEgdG9vbCBkZXBsb3llZCB0byBhIGhpZ2gtcGVyZm9ybWFuY2UgY29tcHV0aW5nIGNsdXN0ZXIgdG8gaWRlbnRpZnkgdGhlXG5tb2RlbHPigJkgc3RlYWR5IHN0YXRlcy4gVGhlIGlkZW50aWZpZWQgYXR0cmFjdG9ycyBhcmUgdGhlbiB2YWxpZGF0ZWQgdXNpbmcgZ2VuZSBleHByZXNzaW9uIGRhdGEgc2V0cyBhbmQgcHJpb3Iga25vd2xlZGdlLiBXZVxuc3VjY2Vzc2Z1bGx5IGFwcGxpZWQgb3VyIGZyYW1ld29yayB0byBnZW5lcmF0ZSBhbmQgY2FsaWJyYXRlIHRoZSBNMSBhbmQgTTIgbWFjcm9waGFnZSBCb29sZWFuIG1vZGVscyBmb3IgcmhldW1hdG9pZCBhcnRocml0aXMuXG5Vc2luZyBLTyBzaW11bGF0aW9ucywgd2UgaWRlbnRpZmllZCBORmtCLCBKQUsxL0pBSzIsIGFuZCBFUksxL05vdGNoMSBhcyBwb3RlbnRpYWwgdGFyZ2V0cyB0aGF0IGNvdWxkIHNlbGVjdGl2ZWx5IHN1cHByZXNzXG5wcm9pbmZsYW1tYXRvcnkgbWFjcm9waGFnZXMgYW5kIEdTSzNCIGFzIGEgcHJvbWlzaW5nIHRhcmdldCB0aGF0IGNvdWxkIHByb21vdGUgYW50aS1pbmZsYW1tYXRvcnkgbWFjcm9waGFnZXMgaW5cbnJoZXVtYXRvaWQgYXJ0aHJpdGlzLiIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9wdWJtZWQvMzgyNzI5MTkiLAogICAgImF1dGhvcnMiIDogWyB7CiAgICAgICJuYW1lIiA6ICJOYW91ZWwgWmVycm91ayIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlJhY2hlbCBBbGNyYWZ0IgogICAgfSwgewogICAgICAibmFtZSIgOiAiQmVuamFtaW4gQS4gSGFsbCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkZyYW5jayBBdWfDqSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFubmEgTmlhcmFraXMiCiAgICB9IF0KICB9LAogICJmaWxlcyIgOiB7CiAgICAibWFpbiIgOiBbIHsKICAgICAgIm5hbWUiIDogIlJBX00xX21hY3JvcGhhZ2UueG1sIiwKICAgICAgImZpbGVTaXplIiA6ICI4NjYzOTciCiAgICB9IF0KICB9LAogICJoaXN0b3J5IiA6IHsKICAgICJyZXZpc2lvbnMiIDogWyB7CiAgICAgICJ2ZXJzaW9uIiA6IDMsCiAgICAgICJzdWJtaXR0ZWQiIDogMTcwNjI2OTYwMzAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAibmFvdWVsIHplcnJvdWsiLAogICAgICAiY29tbWVudCIgOiAiSW1wb3J0IG9mIFplcnJvdWsyMDIzIC0gTGFyZ2Ugc2NhbGUgY29tcHV0YXRpb25hbCBtb2RlbGluZyBvZiB0aGUgTTEgc3lub3ZpYWwgbWFjcm9waGFnZSBpbiByaGV1bWF0b2lkIGFydGhyaXRpcyIKICAgIH0gXQogIH0sCiAgImZpcnN0UHVibGlzaGVkIiA6IDE3MDYyNzA4NzUwMDAsCiAgInN1Ym1pc3Npb25JZCIgOiAiTU9ERUwyMzA3MTgwMDAxIgp9 + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/BIOMD0000001078 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:20 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=c2171afe-29be-44c0-a7f7-2af9704166ca; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.844.16423.462209; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJIYW1tYXJlbi1HZWlzc2VuMjAyMl9QUFRvUF9Nb2RlbDEyIiwKICAiZGVzY3JpcHRpb24iIDogIlRoZSBtb2RlbCBlbmNvZGVzIHRoZSBnZW5lcmFsIGJpb2xvZ2ljYWwgcHJvY2VzcyBvZiBwcm90ZWluIHN5bnRoZXNpcyBhbmQgcG9zdC10cmFuc2xhdGlvbmFsIG1vZGlmaWNhdGlvbiAoUFRNLCBzdWNoIGFzIHByb3RlaW4gcGhvc3Bob3J5bGF0aW9uKSwgdmlld2VkIHRocm91Z2ggdGhlIGV5ZXMgb2YgYSBzcGVjaWZpYywgd2lkZWx5LXVzZWQgZXhwZXJpbWVudGFsIG1ldGhvZC4gU3BlY2lmaWNhbGx5LCB3ZSBtb2RlbCBtZWFzdXJlbWVudHMgb2YgcHVsc2VkIHN0YWJsZSBpc290b3BlIGxhYmVsbGluZyBvZiBhbWlubyBhY2lkcyBpbiBjZWxsIGN1bHR1cmUgKHBTSUxBQyksIHdoaWNoIGlzIGEgbWV0aG9kIG9mdGVuIHVzZWQgdG8gcXVhbnRpZnkgcHJvdGVpbiB0dXJub3ZlciAoaS5lLiB0aGUgZGVncmFkYXRpb24gb2Ygb2xkIGFuZCByZXBsYWNlbWVudCB3aXRoIG5ldykgb2YgcHJvdGVpbnMgaW4gYSBjZWxsLiIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiU0JNTCIsCiAgICAidmVyc2lvbiIgOiAiTDJWNCIKICB9LAogICJwdWJsaWNhdGlvbiIgOiB7CiAgICAiam91cm5hbCIgOiAiTmF0dXJlIGNvbW11bmljYXRpb25zIiwKICAgICJ0aXRsZSIgOiAiUHJvdGVpbi1QZXB0aWRlIFR1cm5vdmVyIFByb2ZpbGluZyByZXZlYWxzIHRoZSBvcmRlciBvZiBQVE0gYWRkaXRpb24gYW5kIHJlbW92YWwgZHVyaW5nIHByb3RlaW4gbWF0dXJhdGlvbi4iLAogICAgImFmZmlsaWF0aW9uIiA6ICJFdXJvcGVhbiBNb2xlY3VsYXIgQmlvbG9neSBMYWJvcmF0b3J5LCBHZW5vbWUgQmlvbG9neSBVbml0LCA2OTExNywgSGVpZGVsYmVyZywgR2VybWFueS4gaGVucmlrLmhhbW1hcmVuQGVtYmwuZGUuIiwKICAgICJzeW5vcHNpcyIgOiAiUG9zdC10cmFuc2xhdGlvbmFsIG1vZGlmaWNhdGlvbnMgKFBUTXMpIHJlZ3VsYXRlIHZhcmlvdXMgYXNwZWN0cyBvZiBwcm90ZWluIGZ1bmN0aW9uLCBpbmNsdWRpbmcgZGVncmFkYXRpb24uIE1hc3Mgc3BlY3Ryb21ldHJpYyBtZXRob2RzIHJlbHlpbmcgb24gcHVsc2VkIG1ldGFib2xpYyBsYWJlbGluZyBhcmUgcG9wdWxhciB0byBxdWFudGlmeSB0dXJub3ZlciByYXRlcyBvbiBhIHByb3Rlb21lLXdpZGUgc2NhbGUuIFN1Y2ggZGF0YSBoYXZlIHRyYWRpdGlvbmFsbHkgYmVlbiBpbnRlcnByZXRlZCBpbiB0aGUgY29udGV4dCBvZiBwcm90ZWluIHByb3Rlb2x5dGljIHN0YWJpbGl0eS4gSGVyZSwgd2UgY29tYmluZSB0aGVvcmV0aWNhbCBraW5ldGljIG1vZGVsaW5nIHdpdGggZXhwZXJpbWVudGFsIHB1bHNlZCBzdGFibGUgaXNvdG9wZSBsYWJlbGluZyBvZiBhbWlubyBhY2lkcyBpbiBjZWxsIGN1bHR1cmUgKHBTSUxBQykgZm9yIHRoZSBzdHVkeSBvZiBwcm90ZWluIHBob3NwaG9yeWxhdGlvbi4gV2UgZGVtb25zdHJhdGUgdGhhdCBtZXRhYm9saWMgbGFiZWxpbmcgY29tYmluZWQgd2l0aCBQVE0tc3BlY2lmaWMgZW5yaWNobWVudCBkb2VzIG5vdCBtZWFzdXJlIGVmZmVjdHMgb2YgUFRNcyBvbiBwcm90ZWluIHN0YWJpbGl0eS4gUmF0aGVyLCBpdCByZXZlYWxzIHRoZSByZWxhdGl2ZSBvcmRlciBvZiBQVE0gYWRkaXRpb24gYW5kIHJlbW92YWwgYWxvbmcgYSBwcm90ZWluJ3MgbGlmZXRpbWUtYSBmdW5kYW1lbnRhbGx5IGRpZmZlcmVudCBtZXRyaWMuIFRoaXMgaXMgZHVlIHRvIGludGVyY29udmVyc2lvbiBvZiB0aGUgbWVhc3VyZWQgcHJvdGVvZm9ybSBzcGVjaWVzLiBVc2luZyB0aGlzIGZyYW1ld29yaywgd2UgaWRlbnRpZnkgdGVtcG9yYWwgcGhvc3Bob3J5bGF0aW9uIHNpdGVzIG9uIGNlbGwgY3ljbGUtc3BlY2lmaWMgZmFjdG9ycyBhbmQgcHJvdGVpbiBjb21wbGV4IGFzc2VtYmx5IGludGVybWVkaWF0ZXMuIE91ciByZXN1bHRzIHRodXMgYWxsb3cgdHlpbmcgUFRNcyB0byB0aGUgYWdlIG9mIHRoZSBtb2RpZmllZCBwcm90ZWlucy4iLAogICAgInllYXIiIDogMjAyMiwKICAgICJtb250aCIgOiAiMTIiLAogICAgInZvbHVtZSIgOiAiMTMiLAogICAgImlzc3VlIiA6ICIxIiwKICAgICJwYWdlcyIgOiAiNzQzMSIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9wdWJtZWQvMzY0NjA2MzciLAogICAgImF1dGhvcnMiIDogWyB7CiAgICAgICJuYW1lIiA6ICJIYW1tYXLDqW4gSE0iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi04NTM0LTI1MzAiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJFdmEgR2Vpc3NlbiIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRU1CTCBIZWlkZWxiZXJnIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDItMDQyMy03MDE5IgogICAgfSwgewogICAgICAibmFtZSIgOiAiUG90ZWwgQ00iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJCZWNrIE0iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi03Mzk3LTEzMjEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJTYXZpdHNraSBNTSIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAzLTIwMTEtOTI0NyIKICAgIH0gXQogIH0sCiAgImZpbGVzIiA6IHsKICAgICJtYWluIiA6IFsgewogICAgICAibmFtZSIgOiAiSGFtbWFyZW4tR2Vpc3NlbjIwMjJfUFBUb1BfTW9kZWwxMl93aXRoUGFyYW1ldGVyU2V0cy54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjI2OTkzIgogICAgfSBdLAogICAgImFkZGl0aW9uYWwiIDogWyB7CiAgICAgICJuYW1lIiA6ICJQUFRvUF9Nb2RlbDEyLmNwcyIsCiAgICAgICJmaWxlU2l6ZSIgOiAiNTkwODEiLAogICAgICAiZGVzY3JpcHRpb24iIDogIk1vZGVsIGluIENvcGFzaSBmb3JtYXQgaW5jbHVkaW5nIHBsb3RzIGZvciBzcGVjaWVzIHRpbWUgY291cnNlLCBvYnNlcnZhYmxlIHRpbWUgY291cnNlIGFuZCBjbGVhcmFuY2UgcHJvZmlsZXMiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJIYW1tYXJlbi1HZWlzc2VuMjAyMl9QUFRvUF9Nb2RlbDEyX3dpdGhQYXJhbWV0ZXJTZXRzLnNlZG1sIiwKICAgICAgImZpbGVTaXplIiA6ICI2NjQ4IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJTRURNTCBmaWxlIG9mIHRoZSBjdXJhdGVkIG1vZGVsIgogICAgfSwgewogICAgICAibmFtZSIgOiAiUFBUb1BfTW9kZWwxMi54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjIzMDE0IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJTQk1MIHZlcnNpb24gb2YgTW9kZWwgMToyLCByZWR1Y2VkIHRvIDIgT0RFcyBhbmQgMiBhbGdlYnJhaWMgZXF1YXRpb25zIGFzIGRlc2NyaWJlZCBpbiB0aGUgcGFwZXIgYW5kIHRoZSBtb2RlbCByZWxhdGVkIHBhcnQgaW4gU3VwcGxlbWVudGFyeSBOb3RlIDEsIFNlY3Rpb24gNC4iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJIYW1tYXJlbi1HZWlzc2VuMjAyMl9QUFRvUF9Nb2RlbDEyX3dpdGhQYXJhbWV0ZXJTZXRzLmNwcyIsCiAgICAgICJmaWxlU2l6ZSIgOiAiOTYwNTMiLAogICAgICAiZGVzY3JpcHRpb24iIDogIkNPQVBTSSBmaWxlIG9mIHRoZSBjdXJhdGVkIG1vZGVsIgogICAgfSwgewogICAgICAibmFtZSIgOiAiVGhlb3J5X1N1cHBsZW1lbnRfUFBUb1AucGRmIiwKICAgICAgImZpbGVTaXplIiA6ICIxNTY0MjEwIiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJQYXJ0IG9mIFN1cHBsZW1lbnRhcnkgTm90ZSAxIGZyb20gSGFtbWFyw6luIGFuZCBHZWlzc2VuIGV0IGFsIDIwMjIsIHRoYXQgZGVzY3JpYmVzIHRoZSBtb2RlbHMiCiAgICB9IF0KICB9LAogICJoaXN0b3J5IiA6IHsKICAgICJyZXZpc2lvbnMiIDogWyB7CiAgICAgICJ2ZXJzaW9uIiA6IDEsCiAgICAgICJzdWJtaXR0ZWQiIDogMTY3MTIxMDI0MzAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAiRXZhIEdlaXNzZW4iLAogICAgICAiY29tbWVudCIgOiAiSW1wb3J0IG9mIEhhbW1hcmVuX0dlaXNzZW5fMjAyMl9Nb2RlbDEyIgogICAgfSwgewogICAgICAidmVyc2lvbiIgOiAzLAogICAgICAic3VibWl0dGVkIiA6IDE3MDY1MjI2MzIwMDAsCiAgICAgICJzdWJtaXR0ZXIiIDogIktyaXNobmEgS3VtYXIgVGl3YXJpIiwKICAgICAgImNvbW1lbnQiIDogIkF1dG9tYXRpY2FsbHkgYWRkZWQgbW9kZWwgaWRlbnRpZmllciBCSU9NRDAwMDAwMDEwNzgiCiAgICB9IF0KICB9LAogICJmaXJzdFB1Ymxpc2hlZCIgOiAxNzA2NTIyNjM2MDAwLAogICJzdWJtaXNzaW9uSWQiIDogIk1PREVMMjIxMjE2MDAwMiIsCiAgInB1YmxpY2F0aW9uSWQiIDogIkJJT01EMDAwMDAwMTA3OCIKfQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:21 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/BIOMD0000001080 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=f4c18b0e-5168-4174-9fb4-5bab98dc93d2; Path=/biomodels/; HttpOnly + - biomodels-session=1706699241.979.16427.368106; Expires=Wed, 31-Jan-24 12:07:20 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: |- + { + "name" : "DeBoeck2021 - Modular approach to modeling the cell cycle, 5 ODE model with 3 bistable switches", + "description" : "\n \n

    Model of the mammalian cell cycle as a chain of bistable switches. \nThere are three bistable responses: response of E2F to Cyclin D, of Cdk1 to Cyclin B and of APC/C to Cdk1 activity. \nThe model for the given parameters admits a complex limit cycle characterized by transitions through the bistable switches. \nThe bistable responses are modeled directly using a functional motif, not through biochemical interactions. This modular approach allows to easily modify the properties of the bistable response curves. This version of the model correspond to Fig. 7 in the publication. \nWe illustrated how, using this model, the system can be coupled to the circadian clock, by periodically modifying thresholds of one of the switches. We also illustrated how to implement the restriction point checkpoint using this model (those applications are not coded in the associated sbml file and can be seen in Fig. 8 of the publication). \n\nA related, simpler model that illustrates the bistable motif is MODEL2212060001

    \n \n
    ", + "format" : { + "name" : "SBML", + "version" : "L2V4" + }, + "publication" : { + "journal" : "PLoS computational biology", + "title" : "A modular approach for modeling the cell cycle based on functional response curves.", + "affiliation" : "Laboratory of Dynamics in Biological Systems, Department of Cellular and Molecular Medicine, University of Leuven, Leuven, Belgium.", + "synopsis" : "Modeling biochemical reactions by means of differential equations often results in systems with a large number of variables and parameters. As this might complicate the interpretation and generalization of the obtained results, it is often desirable to reduce the complexity of the model. One way to accomplish this is by replacing the detailed reaction mechanisms of certain modules in the model by a mathematical expression that qualitatively describes the dynamical behavior of these modules. Such an approach has been widely adopted for ultrasensitive responses, for which underlying reaction mechanisms are often replaced by a single Hill function. Also time delays are usually accounted for by using an explicit delay in delay differential equations. In contrast, however, S-shaped response curves, which by definition have multiple output values for certain input values and are often encountered in bistable systems, are not easily modeled in such an explicit way. Here, we extend the classical Hill function into a mathematical expression that can be used to describe both ultrasensitive and S-shaped responses. We show how three ubiquitous modules (ultrasensitive responses, S-shaped responses and time delays) can be combined in different configurations and explore the dynamics of these systems. As an example, we apply our strategy to set up a model of the cell cycle consisting of multiple bistable switches, which can incorporate events such as DNA damage and coupling to the circadian clock in a phenomenological way.", + "year" : 2021, + "month" : "8", + "volume" : "17", + "issue" : "8", + "pages" : "e1009008", + "link" : "http://identifiers.org/doi/10.1371/journal.pcbi.1009008", + "authors" : [ { + "name" : "De Boeck J", + "orcid" : "0000-0001-6180-3323" + }, { + "name" : "Jan Rombouts", + "institution" : "EMBL Heidelberg", + "orcid" : "0000-0003-4135-0771" + }, { + "name" : "Gelens L", + "orcid" : "0000-0001-7290-9561" + } ] + }, + "files" : { + "main" : [ { + "name" : "DeBoeck2021_cellcycle_threeswitches.xml", + "fileSize" : "32302" + } ], + "additional" : [ { + "name" : "cellcycle_threeswitches.xml", + "fileSize" : "11473", + "description" : "5 ODE model of cell cycle built on bistable modules" + }, { + "name" : "DeBoeck2021_cellcycle_threeswitches.cps", + "fileSize" : "91123", + "description" : "COPASI file for the curated model" + }, { + "name" : "DeBoeck2021_cellcycle_threeswitches.sedml", + "fileSize" : "9720", + "description" : "SEDMl file for the curated model" + } ] + }, + "history" : { + "revisions" : [ { + "version" : 2, + "submitted" : 1670416891000, + "submitter" : "Jan Rombouts", + "comment" : "Fixed typo in name + extended description a bit" + }, { + "version" : 3, + "submitted" : 1705967007000, + "submitter" : "Tung Nguyen", + "comment" : "Improved the model description. Also, triggered the automation of adding publication and model identifier annotation." + }, { + "version" : 5, + "submitted" : 1706523787000, + "submitter" : "Krishna Kumar Tiwari", + "comment" : "Automatically added model identifier BIOMD0000001080" + } ] + }, + "firstPublished" : 1706523792000, + "submissionId" : "MODEL2212060002", + "publicationId" : "BIOMD0000001080" + } + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2211290002 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=e56791b6-03c1-40d2-b543-d0370ad68947; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.108.16430.825963; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJDYXNpbmkyMDIyIC0gTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgWi0yNDUiLAogICJkZXNjcmlwdGlvbiIgOiAiKGlNVEQyMklDKSBcblxuVGhpcyBtb2RlbCBpcyBkZXNjcmliZWQgaW4gdGhlc2UgYXJ0aWNsZXM6XG5cbkNhc2luaSwgSS4sIE1jQ3ViYmluLCBULiwgRXNxdWl2ZWwtRWxpem9uZG8sIFMuLCBMdXF1ZSwgRy5HLiwgRXZzZWV2YSwgRC4sIEZpbmssIEMuLCBCZWJsYXd5LCBTLiwgWW91bmdibHV0LCBOLkQuLCBBcmlzdGlsZGUsIEwuLCBIdXNvbiwgRC4sIERyYWdlciwgQS4sIExleSwgUi4sIE1hcmNlbGxpbiwgRS4sIEFuZ2VuZW50LCBMLlQuLCBNb2xpdG9yLCBCLiAyMDIzLiBBbiBpbnRlZ3JhdGVkIHN5c3RlbXMgYmlvbG9neSBhcHByb2FjaCByZXZlYWxzIGRpZmZlcmVuY2VzIGluIGZvcm1hdGUgbWV0YWJvbGlzbSBpbiB0aGUgZ2VudXMgTWV0aGFub3RoZXJtb2JhY3Rlci4gSXNjaWVuY2UsIDI2KDEwKS5cblxuQ2FzaW5pLCBJLiwgTWNDdWJiaW4sIFQuLCBFc3F1aXZlbC1FbGl6b25kbywgUy4sIEx1cXVlLCBHLkcuLCBFdnNlZXZhLCBELiwgRmluaywgQy4sIEJlYmxhd3ksIFMuLCBZb3VuZ2JsdXQsIE4uRC4sIEFyaXN0aWxkZSwgTC4sIEh1c29uLCBELiwgRHJhZ2VyLCBBLiwgTGV5LCBSLiwgTWFyY2VsbGluLCBFLiwgQW5nZW5lbnQsIEwuVC4sIE1vbGl0b3IsIEIuIDIwMjIuIEFuIGludGVncmF0ZWQgc3lzdGVtcy1iaW9sb2d5IHBsYXRmb3JtIGZvciBwb3dlci10by1nYXMgdGVjaG5vbG9neS4gYmlvUnhpdiwgMjAyMi4xMi4zMC41MjIyMzYuIFxuXG5NZXRoYW5vZ2VuZXNpcyBhbGxvd3MgbWV0aGFub2dlbmljIGFyY2hhZWEgKG1ldGhhbm9nZW5zKSB0byBnZW5lcmF0ZSBjZWxsdWxhciBlbmVyZ3kgZm9yIHRoZWlyIGdyb3d0aCB3aGlsZSBwcm9kdWNpbmcgbWV0aGFuZS4gSHlkcm9nZW5vdHJvcGhpYyBtZXRoYW5vZ2VucyB0aHJpdmUgb24gY2FyYm9uIGRpb3hpZGUgYW5kIG1vbGVjdWxhciBoeWRyb2dlbiBhcyBzb2xlIGNhcmJvbiBhbmQgZW5lcmd5IHNvdXJjZXMuIFRoZXJtb3BoaWxpYyBhbmQgaHlkcm9nZW5vdHJvcGhpYyBNZXRoYW5vdGhlcm1vYmFjdGVyIHNwcC4gaGF2ZSBiZWVuIHJlY29nbml6ZWQgYXMgcm9idXN0IGJpb2NhdGFseXN0cyBmb3IgYSBjaXJjdWxhciBjYXJib24gZWNvbm9teSBhbmQgYXJlIG5vdyBhcHBsaWVkIGluIHBvd2VyLXRvLWdhcyB0ZWNobm9sb2d5LiBIZXJlLCB3ZSBnZW5lcmF0ZWQgdGhlIGZpcnN0IG1hbnVhbGx5IGN1cmF0ZWQgZ2Vub21lLXNjYWxlIG1ldGFib2xpYyByZWNvbnN0cnVjdGlvbiBmb3IgdGhyZWUgTWV0aGFub3RoZXJtb2JhY3RlciBzcHAuLiBXZSBpbnZlc3RpZ2F0ZWQgZGlmZmVyZW5jZXMgaW4gZ3Jvd3RoIHBlcmZvcm1hbmNlIG9mIHRocmVlIHdpbGQtdHlwZSBzdHJhaW5zIGFuZCBvbmUgZ2VuZXRpY2FsbHkgZW5naW5lZXJlZCBzdHJhaW4gaW4gdHdvIGluZGVwZW5kZW50IGNoZW1vc3RhdCBiaW9yZWFjdG9yIGV4cGVyaW1lbnRzLCBmaXJzdCwgd2l0aCBtb2xlY3VsYXIgaHlkcm9nZW4gYW5kIGNhcmJvbiBkaW94aWRlLCBhbmQgc2Vjb25kLCB3aXRoIHNvZGl1bSBmb3JtYXRlLiBJbiB0aGUgZmlyc3QgZXhwZXJpbWVudCwgd2UgZm91bmQgdGhlIGhpZ2hlc3QgbWV0aGFuZSBwcm9kdWN0aW9uIHJhdGUgZm9yIE1ldGhhbm90aGVybW9iYWN0ZXIgdGhlcm1hdXRvdHJvcGhpY3VzIM6USCwgd2hpbGUgTWV0aGFub3RoZXJtb2JhY3RlciBtYXJidXJnZW5zaXMgTWFyYnVyZyByZWFjaGVkIHRoZSBoaWdoZXN0IGJpb21hc3MgZ3Jvd3RoIHJhdGUuIFRyYW5zY3JpcHRvbWljcyBhbmQgcHJvdGVvbWljcyBkYXRhIHNldHMgZnJvbSB0aGVzZSBzdGVhZHktc3RhdGUgYmlvcmVhY3RvcnMsIGluIGNvbWJpbmF0aW9uIHdpdGggdGhlIGltcGxlbWVudGF0aW9uIG9mIGEgcGFuLW1vZGVsIHRoYXQgY29udGFpbnMgY29tYmluZWQgcmVhY3Rpb25zIGZyb20gYWxsIHRocmVlIG1pY3JvYmVzLCBhbGxvd2VkIHVzIHRvIHBlcmZvcm0gYW4gaW50ZXJzcGVjaWVzIGNvbXBhcmlzb24uIFdoaWxlIHRoZSBvYnNlcnZlZCBkaWZmZXJlbmNlcyBpbiB0aGUgZ3Jvd3RoIGJlaGF2aW9yIGNhbm5vdCBiZSBmdWxseSBleHBsYWluZWQsIHRoZSBjb21wYXJpc29uIGVuYWJsZWQgdXMgdG8gaWRlbnRpZnkgY3J1Y2lhbCBkaWZmZXJlbmNlcyBpbiBncm93dGgtcmVsYXRlZCBwYXRod2F5cyBzdWNoIGFzIGZvcm1hdGUgYW5hYm9saXNtLiBJbiB0aGUgc2Vjb25kIGV4cGVyaW1lbnQsIHdlIGZvdW5kIHN0YWJsZSBncm93dGggd2l0aCBhIE0uIHRoZXJtYXV0b3Ryb3BoaWN1cyDOlEggcGxhc21pZC1jYXJyeWluZyBzdHJhaW4gb24gZm9ybWF0ZSB3aXRoIHNpbWlsYXIgcGVyZm9ybWFuY2UgcGFyYW1ldGVycyBjb21wYXJlZCB0byB3aWxkLXR5cGUgTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgWi0yNDUuIFRoZSByZXN1bHRzIG9mIHRoZSB0d28gc3R1ZGllcyBkZW1vbnN0cmF0ZSB0aGUgYWR2YW50YWdlcyBvZiBhbiBpbnRlZ3JhdGl2ZSBhcHByb2FjaCBjb21iaW5pbmcgZmVybWVudGF0aW9uIGFuZCBvbWljcyBkYXRhIHdpdGggZ2Vub21lLXNjYWxlIG1vZGVsaW5nIHRvIHJldmVhbCBrbm93bGVkZ2UgZ2FwcyBvZiBhcmNoYWVhbCBtZXRhYm9saXNtcyBhbmQgdGhlIGJpb3RlY2hub2xvZ2ljYWwgcG90ZW50aWFsIG9mIE1ldGhhbm90aGVybW9iYWN0ZXIgc3BwLiBhcyBwcm9kdWN0aW9uIHBsYXRmb3JtIGhvc3RzLiIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiU0JNTCIsCiAgICAidmVyc2lvbiIgOiAiTDNWMSIKICB9LAogICJwdWJsaWNhdGlvbiIgOiB7CiAgICAiam91cm5hbCIgOiAiaVNjaWVuY2UiLAogICAgInRpdGxlIiA6ICJBbiBpbnRlZ3JhdGVkIHN5c3RlbXMgYmlvbG9neSBhcHByb2FjaCByZXZlYWxzIGRpZmZlcmVuY2VzIGluIGZvcm1hdGUgbWV0YWJvbGlzbSBpbiB0aGUgZ2VudXMgPGk+TWV0aGFub3RoZXJtb2JhY3RlcjwvaT4uIiwKICAgICJhZmZpbGlhdGlvbiIgOiAiRW52aXJvbm1lbnRhbCBCaW90ZWNobm9sb2d5IEdyb3VwLCBEZXBhcnRtZW50IG9mIEdlb3NjaWVuY2VzLCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgU2NobmFycmVuYmVyZ3N0cmHDn2UgOTQtOTYsIDcyMDc2IFTDvGJpbmdlbiwgR2VybWFueS4iLAogICAgInN5bm9wc2lzIiA6ICJNZXRoYW5vZ2VuZXNpcyBhbGxvd3MgbWV0aGFub2dlbmljIGFyY2hhZWEgdG8gZ2VuZXJhdGUgY2VsbHVsYXIgZW5lcmd5IGZvciB0aGVpciBncm93dGggd2hpbGUgcHJvZHVjaW5nIG1ldGhhbmUuIFRoZXJtb3BoaWxpYyBoeWRyb2dlbm90cm9waGljIHNwZWNpZXMgb2YgdGhlIGdlbnVzIDxpPk1ldGhhbm90aGVybW9iYWN0ZXI8L2k+IGhhdmUgYmVlbiByZWNvZ25pemVkIGFzIHJvYnVzdCBiaW9jYXRhbHlzdHMgZm9yIGEgY2lyY3VsYXIgY2FyYm9uIGVjb25vbXkgYW5kIGFyZSBhbHJlYWR5IGFwcGxpZWQgaW4gcG93ZXItdG8tZ2FzIHRlY2hub2xvZ3kgd2l0aCBiaW9tZXRoYW5hdGlvbiwgd2hpY2ggaXMgYSBwbGF0Zm9ybSB0byBzdG9yZSByZW5ld2FibGUgZW5lcmd5IGFuZCB1dGlsaXplIGNhcHR1cmVkIGNhcmJvbiBkaW94aWRlLiBIZXJlLCB3ZSBnZW5lcmF0ZWQgY3VyYXRlZCBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIHJlY29uc3RydWN0aW9ucyBmb3IgdGhyZWUgPGk+TWV0aGFub3RoZXJtb2JhY3RlcjwvaT4gc3RyYWlucyBhbmQgaW52ZXN0aWdhdGVkIGRpZmZlcmVuY2VzIGluIHRoZSBncm93dGggcGVyZm9ybWFuY2Ugb2YgdGhlc2Ugc2FtZSBzdHJhaW5zIGluIGNoZW1vc3RhdCBiaW9yZWFjdG9yIGV4cGVyaW1lbnRzIHdpdGggaHlkcm9nZW4gYW5kIGNhcmJvbiBkaW94aWRlIG9yIGZvcm1hdGUgYXMgc3Vic3RyYXRlcy4gVXNpbmcgYW4gaW50ZWdyYXRlZCBzeXN0ZW1zIGJpb2xvZ3kgYXBwcm9hY2gsIHdlIGlkZW50aWZpZWQgZGlmZmVyZW5jZXMgaW4gZm9ybWF0ZSBhbmFib2xpc20gYmV0d2VlbiB0aGUgc3RyYWlucyBhbmQgcmV2ZWFsZWQgdGhhdCBmb3JtYXRlIGFuYWJvbGlzbSBpbmZsdWVuY2VzIHRoZSBkaXZlcnNpb24gb2YgY2FyYm9uIGJldHdlZW4gYmlvbWFzcyBhbmQgbWV0aGFuZS4gVGhpcyBmaW5kaW5nLCB0b2dldGhlciB3aXRoIHRoZSBvbWljcyBkYXRhc2V0cyBhbmQgdGhlIG1ldGFib2xpYyBtb2RlbHMgd2UgZ2VuZXJhdGVkLCBjYW4gYmUgaW1wbGVtZW50ZWQgZm9yIGJpb3RlY2hub2xvZ2ljYWwgYXBwbGljYXRpb25zIG9mIDxpPk1ldGhhbm90aGVybW9iYWN0ZXI8L2k+IGluIHBvd2VyLXRvLWdhcyB0ZWNobm9sb2d5LCBhbmQgYXMgYSBwZXJzcGVjdGl2ZSwgZm9yIHZhbHVlLWFkZGVkIGNoZW1pY2FsIHByb2R1Y3Rpb24uIiwKICAgICJ5ZWFyIiA6IDIwMjMsCiAgICAibW9udGgiIDogIjEwIiwKICAgICJ2b2x1bWUiIDogIjI2IiwKICAgICJpc3N1ZSIgOiAiMTAiLAogICAgInBhZ2VzIiA6ICIxMDgwMTYiLAogICAgImxpbmsiIDogImh0dHA6Ly9pZGVudGlmaWVycy5vcmcvZG9pLzEwLjEwMTYvai5pc2NpLjIwMjMuMTA4MDE2IiwKICAgICJhdXRob3JzIiA6IFsgewogICAgICAibmFtZSIgOiAiSXNhYmVsbGEgQ2FzaW5pIgogICAgfSwgewogICAgICAibmFtZSIgOiAiVGltIE1jQ3ViYmluIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU29maWEgRXNxdWl2ZWwtRWxpem9uZG8iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJHdWlsbGVybW8gRyBMdXF1ZSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkRhcmlhIEV2c2VldmEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJDaHJpc3RpYW4gRmluayIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlNlYmFzdGlhbiBCZWJsYXd5IgogICAgfSwgewogICAgICAibmFtZSIgOiAiTmljaG9sYXMgRCBZb3VuZ2JsdXQiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJMdWRtaWxsYSBBcmlzdGlsZGUiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJEYW5pZWwgSCBIdXNvbiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFuZHJlYXMgRHLDpGdlciIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlJ1dGggRSBMZXkiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJFc3RlYmFuIE1hcmNlbGxpbiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkxhcmd1cyBUIEFuZ2VuZW50IgogICAgfSwgewogICAgICAibmFtZSIgOiAiQmFzdGlhbiBNb2xpdG9yIgogICAgfSBdCiAgfSwKICAiZmlsZXMiIDogewogICAgIm1haW4iIDogWyB7CiAgICAgICJuYW1lIiA6ICJpTVRaMjJJQ191bmJvdW5kLnhtbCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMTc4OTM1MCIKICAgIH0gXSwKICAgICJhZGRpdGlvbmFsIiA6IFsgewogICAgICAibmFtZSIgOiAiWlpfdW5ib3VuZF9GUk9HLm9tZXgiLAogICAgICAiZmlsZVNpemUiIDogIjIwMDI5NiIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiRlJPRyBhbmFseXNpcyBvZiB0aGUgdW5ib3VuZCBpTVRaMjJJQyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlpaX2JvdW5kX0ZST0cub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMjAwNTI3IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJGUk9HIGFuYWx5c2lzIG9mIHRoZSBib3VuZCBpTVRaMjJJQyAodXNpbmcgY29uZGl0aW9ucyBkZXNjcmliZWQgaW4gQ2FzaW5pIGV0IGFsLiAyMDJYKSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkZST0dfdW5ib3VuZF9jdXJhdGVkLm9tZXgiLAogICAgICAiZmlsZVNpemUiIDogIjE0Mzc0NiIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiRlJPRyBSZXBvcnQgLSB1bmJvdW5kIHZlcnNpb24iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJNRU1PVEUtUmVwb3J0LXVuYm91bmQuaHRtbCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMzc4NzM3NCIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiTUVNT1RFIFJlcG9ydCAtIHVuYm91bmQgdmVyc2lvbiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIm9tZXhfWloub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMzEyNDUxMCIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiQ2FzaW5pIGV0IGFsLiAyMDIyLCBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIChpTVRaMjJJQykgb2YgTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgWi0yNDUsIGluY2x1ZGluZyBwYW4tbWV0aGFub3RoZXJtb2JhY3RlciBzcHAuIHJlY29uc3RydWN0aW9uICgueGxzeCkiCiAgICB9IF0KICB9LAogICJoaXN0b3J5IiA6IHsKICAgICJyZXZpc2lvbnMiIDogWyB7CiAgICAgICJ2ZXJzaW9uIiA6IDUsCiAgICAgICJzdWJtaXR0ZWQiIDogMTY3MjQ2OTQ2ODAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAiSXNhYmVsbGEgQ2FzaW5pIiwKICAgICAgImNvbW1lbnQiIDogIlVwZGF0ZWQgdGhlIHB1YmxpY2F0aW9uLiIKICAgIH0sIHsKICAgICAgInZlcnNpb24iIDogNiwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzAyNTQ2Nzc5MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJUdW5nIE5ndXllbiIsCiAgICAgICJjb21tZW50IiA6ICJVcGRhdGVkIHRoZSBtb2RlbCB3aXRoIHRoZSBjdXJhdGVkIEZST0cgYW5kIE1FTU9URSByZXBvcnQuIgogICAgfSwgewogICAgICAidmVyc2lvbiIgOiA3LAogICAgICAic3VibWl0dGVkIiA6IDE3MDM5ODE0ODUwMDAsCiAgICAgICJzdWJtaXR0ZXIiIDogIklzYWJlbGxhIENhc2luaSIsCiAgICAgICJjb21tZW50IiA6ICJJIHVwZGF0ZWQgdGhlIHB1YmxpY2F0aW9ucyB0byBpbmNsdWRlIHRoZSBmaW5hbCBwdWJsaWNhdGlvbiAobm90IGp1c3QgdGhlIHByZXByaW50KS4iCiAgICB9LCB7CiAgICAgICJ2ZXJzaW9uIiA6IDgsCiAgICAgICJzdWJtaXR0ZWQiIDogMTcwNDk3MTU4MjAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAiVHVuZyBOZ3V5ZW4iLAogICAgICAiY29tbWVudCIgOiAiTWFkZSB0aGUgbW9kZWwgdGl0bGUgYmV0dGVyIGFuZCBjb21wbGllZCB3aXRoIHRoZSBzdWJtaXNzaW9uIGd1aWRlbGluZXMiCiAgICB9IF0KICB9LAogICJmaXJzdFB1Ymxpc2hlZCIgOiAxNzA0OTcxNTk1MDAwLAogICJzdWJtaXNzaW9uSWQiIDogIk1PREVMMjIxMTI5MDAwMiIKfQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL1812210002 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=e71f3503-5d73-4a7a-8288-f4507bd24ac3; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.226.16430.839679; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: |- + { + "name" : "Henze2017 - A Dynamical Model for Activating and Silencing the Mitotic Checkpoint", + "description" : "\n \n

    The spindle assembly checkpoint (SAC) is an evolutionarily conserved mechanism, exclusively sensitive to the states of kinetochores attached to microtubules. During metaphase, the anaphase-promoting complex/cyclosome (APC/C) is inhibited by the SAC but it rapidly switches to its active form following proper attachment of the final spindle. It had been thought that APC/C activity is an all-or-nothing response, but recent findings have demonstrated that it switches steadily. In this study, we develop a detailed mathematical model that considers all 92 human kinetochores and all major proteins involved in SAC activation and silencing. We perform deterministic and spatially-stochastic simulations and find that certain spatial properties do not play significant roles. Furthermore, we show that our model is consistent with in-vitro mutation experiments of crucial proteins as well as the recently-suggested rheostat switch behavior, measured by Securin or CyclinB concentration. Considering an autocatalytic feedback loop leads to an all-or-nothing toggle switch in the underlying core components, while the output signal of the SAC still behaves like a rheostat switch. The results of this study support the hypothesis that the SAC signal varies with increasing number of attached kinetochores, even though it might still contain toggle switches in some of its components.

    \n \n
    ", + "format" : { + "name" : "SBML", + "version" : "L2V4" + }, + "publication" : { + "journal" : "Scientific reports", + "title" : "A Dynamical Model for Activating and Silencing the Mitotic Checkpoint.", + "affiliation" : "Department of Mathematics and Computer Science, Friedrich Schiller University Jena, 07743, Jena, Germany.", + "synopsis" : "The spindle assembly checkpoint (SAC) is an evolutionarily conserved mechanism, exclusively sensitive to the states of kinetochores attached to microtubules. During metaphase, the anaphase-promoting complex/cyclosome (APC/C) is inhibited by the SAC but it rapidly switches to its active form following proper attachment of the final spindle. It had been thought that APC/C activity is an all-or-nothing response, but recent findings have demonstrated that it switches steadily. In this study, we develop a detailed mathematical model that considers all 92 human kinetochores and all major proteins involved in SAC activation and silencing. We perform deterministic and spatially-stochastic simulations and find that certain spatial properties do not play significant roles. Furthermore, we show that our model is consistent with in-vitro mutation experiments of crucial proteins as well as the recently-suggested rheostat switch behavior, measured by Securin or CyclinB concentration. Considering an autocatalytic feedback loop leads to an all-or-nothing toggle switch in the underlying core components, while the output signal of the SAC still behaves like a rheostat switch. The results of this study support the hypothesis that the SAC signal varies with increasing number of attached kinetochores, even though it might still contain toggle switches in some of its components.", + "year" : 2017, + "month" : "6", + "volume" : "7", + "issue" : "1", + "pages" : "3865", + "link" : "http://identifiers.org/pubmed/28634351", + "authors" : [ { + "name" : "Henze R" + }, { + "name" : "Dittrich P" + }, { + "name" : "Ibrahim B", + "orcid" : "0000-0001-7773-0122" + } ] + }, + "files" : { + "main" : [ { + "name" : "Henze2017.xml", + "fileSize" : "148399" + } ] + }, + "history" : { + "revisions" : [ { + "version" : 2, + "submitted" : 1545400756000, + "submitter" : "Ashley Xavier", + "comment" : "Edited model metadata online." + }, { + "version" : 3, + "submitted" : 1706079641000, + "submitter" : "Tung Nguyen", + "comment" : "Updated the main file description and model approach. Also, the model is annotated with the publication and model submission identifier annotation." + } ] + }, + "firstPublished" : 1706079784000, + "submissionId" : "MODEL1812210002" + } + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2304270003 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=cced2d3b-8a50-41a0-841c-31509cee54e0; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.345.16430.266166; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJpQ3N0cjExOTdGQjIzOiBHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bSBzdHJhaW4gRkRBQVJHT1NfMTE5NyIsCiAgImRlc2NyaXB0aW9uIiA6ICJHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bSBzdHJhaW4gRkRBQVJHT1NfMTE5NyIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiQ09NQklORSBhcmNoaXZlIiwKICAgICJ2ZXJzaW9uIiA6ICIwLjEiCiAgfSwKICAicHVibGljYXRpb24iIDogewogICAgImpvdXJuYWwiIDogIkZyb250aWVycyBpbiBCaW9pbmZvcm1hdGljcyIsCiAgICAidGl0bGUiIDogIkdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWxzIGNvbnNpc3RlbnRseSBwcmVkaWN0IGluIHZpdHJvIGNoYXJhY3RlcmlzdGljcyBvZiBDb3J5bmViYWN0ZXJpdW0gc3RyaWF0dW0iLAogICAgImFmZmlsaWF0aW9uIiA6ICIxKSBDb21wdXRhdGlvbmFsIFN5c3RlbXMgQmlvbG9neSBvZiBJbmZlY3Rpb25zIGFuZCBBbnRpbWljcm9iaWFsLVJlc2lzdGFudCBQYXRob2dlbnMsIEluc3RpdHV0ZSBmb3IgQmlvaW5mb3JtYXRpY3MgYW5kIE1lZGljYWwgSW5mb3JtYXRpY3MgKElCTUkpLCBFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCBUw7xiaW5nZW4sIEdlcm1hbnlcbjIpIEludGVyZmFjdWx0eSBJbnN0aXR1dGUgb2YgTWljcm9iaW9sb2d5IGFuZCBJbmZlY3Rpb24gTWVkaWNpbmUgVMO8YmluZ2VuIChJTUlUKSwgRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgVMO8YmluZ2VuLCBHZXJtYW55XG4zKSBEZXBhcnRtZW50IG9mIENvbXB1dGVyIFNjaWVuY2UsIEViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4sIFTDvGJpbmdlbiwgR2VybWFueVxuNCkgR2VybWFuIENlbnRlciBmb3IgSW5mZWN0aW9uIFJlc2VhcmNoIChEWklGKSwgUGFydG5lciBTaXRlIFTDvGJpbmdlbiwgVMO8YmluZ2VuLCBHZXJtYW55XG41KSBDbHVzdGVyIG9mIEV4Y2VsbGVuY2Ug4oCcQ29udHJvbGxpbmcgTWljcm9iZXMgdG8gRmlnaHQgSW5mZWN0aW9ucyAoQ01GSSnigJ0sIEViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4sIFTDvGJpbmdlbiwgR2VybWFueVxuNikgRmFjdWx0eSBvZiBCaW9sb2d5LCBNaWNyb2Jpb2xvZ3ksIEx1ZHdpZyBNYXhpbWlsaWFuIFVuaXZlcnNpdHkgb2YgTXVuaWNoLCBNdW5pY2gsIEdlcm1hbnkiLAogICAgInN5bm9wc2lzIiA6ICJJbnRyb2R1Y3Rpb246IEdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWxzIChHRU1zKSBhcmUgb3JnYW5pc20tc3BlY2lmaWMga25vd2xlZGdlIGJhc2VzIHdoaWNoIGNhbiBiZSB1c2VkIHRvIHVucmF2ZWwgcGF0aG9nZW5pY2l0eSBvciBpbXByb3ZlIHByb2R1Y3Rpb24gb2Ygc3BlY2lmaWMgbWV0YWJvbGl0ZXMgaW4gYmlvdGVjaG5vbG9neSBhcHBsaWNhdGlvbnMuIEhvd2V2ZXIsIHRoZSB2YWxpZGl0eSBvZiBwcmVkaWN0aW9ucyBmb3IgYmFjdGVyaWFsIHByb2xpZmVyYXRpb24gaW4gaW4gdml0cm8gc2V0dGluZ3MgaXMgaGFyZGx5IGludmVzdGlnYXRlZC5cbk1ldGhvZHM6IFRoZSBwcmVzZW50IHdvcmsgY29tYmluZXMgaW4gc2lsaWNvIGFuZCBpbiB2aXRybyBhcHByb2FjaGVzIHRvIGNyZWF0ZSBhbmQgY3VyYXRlIHN0cmFpbi1zcGVjaWZpYyBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVscyBvZiBDb3J5bmViYWN0ZXJpdW0gc3RyaWF0dW0uXG5SZXN1bHRzOiBXZSBpbnRyb2R1Y2UgZml2ZSBuZXdseSBjcmVhdGVkIHN0cmFpbi1zcGVjaWZpYyBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVscyAoR0VNcykgb2YgaGlnaCBxdWFsaXR5LCBzYXRpc2Z5aW5nIGFsbCBjb250ZW1wb3Jhcnkgc3RhbmRhcmRzIGFuZCByZXF1aXJlbWVudHMuIEFsbCB0aGVzZSBtb2RlbHMgaGF2ZSBiZWVuIGJlbmNobWFya2VkIHVzaW5nIHRoZSBjb21tdW5pdHkgc3RhbmRhcmQgdGVzdCBzdWl0ZSBNZXRhYm9saWMgTW9kZWwgVGVzdGluZyAoTUVNT1RFKSBhbmQgd2VyZSB2YWxpZGF0ZWQgYnkgbGFib3JhdG9yeSBleHBlcmltZW50cy4gRm9yIHRoZSBjdXJhdGlvbiBvZiB0aG9zZSBtb2RlbHMsIHRoZSBzb2Z0d2FyZSBpbmZyYXN0cnVjdHVyZSByZWZpbmVHRU1zIHdhcyBkZXZlbG9wZWQgdG8gd29yayBvbiB0aGVzZSBtb2RlbHMgaW4gcGFyYWxsZWwgYW5kIHRvIGNvbXBseSB3aXRoIHRoZSBxdWFsaXR5IHN0YW5kYXJkcyBmb3IgR0VNcy4gVGhlIG1vZGVsIHByZWRpY3Rpb25zIHdlcmUgY29uZmlybWVkIGJ5IGV4cGVyaW1lbnRhbCBkYXRhIGFuZCBhIG5ldyBjb21wYXJpc29uIG1ldHJpYyBiYXNlZCBvbiB0aGUgZG91YmxpbmcgdGltZSB3YXMgZGV2ZWxvcGVkIHRvIHF1YW50aWZ5IGJhY3RlcmlhbCBncm93dGguXG5EaXNjdXNzaW9uOiBGdXR1cmUgbW9kZWxpbmcgcHJvamVjdHMgY2FuIHJlbHkgb24gdGhlIHByb3Bvc2VkIHNvZnR3YXJlLCB3aGljaCBpcyBpbmRlcGVuZGVudCBvZiBzcGVjaWZpYyBlbnZpcm9ubWVudGFsIGNvbmRpdGlvbnMuIFRoZSB2YWxpZGF0aW9uIGFwcHJvYWNoIGJhc2VkIG9uIHRoZSBncm93dGggcmF0ZSBjYWxjdWxhdGlvbiBpcyBub3cgYWNjZXNzaWJsZSBhbmQgY2xvc2VseSBhbGlnbmVkIHdpdGggYmlvbG9naWNhbCBxdWVzdGlvbnMuIFRoZSBjdXJhdGVkIG1vZGVscyBhcmUgZnJlZWx5IGF2YWlsYWJsZSB2aWEgQmlvTW9kZWxzIGFuZCBhIEdpdEh1YiByZXBvc2l0b3J5IGFuZCBjYW4gYmUgdXNlZC4gVGhlIG9wZW4tc291cmNlIHNvZnR3YXJlIHJlZmluZUdFTXMgaXMgYXZhaWxhYmxlIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL2RyYWVnZXItbGFiL3JlZmluZWdlbXMuIiwKICAgICJ5ZWFyIiA6IDIwMjMsCiAgICAibW9udGgiIDogIjEwIiwKICAgICJ2b2x1bWUiIDogIjMiLAogICAgImxpbmsiIDogImh0dHA6Ly9pZGVudGlmaWVycy5vcmcvZG9pLzEwLjMzODkvZmJpbmYuMjAyMy4xMjE0MDc0IiwKICAgICJhdXRob3JzIiA6IFsgewogICAgICAibmFtZSIgOiAiRmFta2UgQsOkdWVybGUiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMy0xMzg3LTAyNTEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJHd2VuZG9seW4gTy4gRMO2YmVsIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDItODIwNi0yNTc2IgogICAgfSwgewogICAgICAibmFtZSIgOiAiTGF1cmEgQ2FtdXMiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMy0xMzM1LTg5MDEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJTaW1vbiBIZWlsYnJvbm5lciIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiTHVkd2lnIE1heGltaWxpYW4gVW5pdmVyc2l0eSBvZiBNdW5pY2giLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi02Nzc0LTIzMTEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJBbmRyZWFzIERyw6RnZXIiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi0xMjQwLTU1NTMiCiAgICB9IF0KICB9LAogICJmaWxlcyIgOiB7IH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogMiwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA0ODM5OTcxMDAwLAogICAgICAic3VibWl0dGVyIiA6ICJBbmRyZWFzIERyw6RnZXIiLAogICAgICAiY29tbWVudCIgOiAiSW5jbHVkZWQgRlJPRyBhbmFseXNpcy4iCiAgICB9IF0KICB9LAogICJmaXJzdFB1Ymxpc2hlZCIgOiAxNjk4NDAyNjc3MDAwLAogICJzdWJtaXNzaW9uSWQiIDogIk1PREVMMjMwNDI3MDAwMyIKfQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2012220003 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=062af8bb-aa7f-4c8b-b5ad-c979c1fd36f9; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.458.16423.33464; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJSZW56MjAyMSAtIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWwgKGlEUE0yMVJXKSBvZiBEb2xvc2lncmFudWx1bSBwaWdydW0gODNWUHMgS0I1IiwKICAiZGVzY3JpcHRpb24iIDogIkRvbG9zaWdyYW51bHVtIHBpZ3J1bSBpcyBhIHF1aXRlIHJlY2VudGx5IGRpc2NvdmVyZWQgR3JhbS1wb3NpdGl2ZSBjb2NjdXMuIEl0IGhhcyBnYWluZWQgaW5jcmVhc2luZyBhdHRlbnRpb24gZHVlIHRvIGl0cyBuZWdhdGl2ZSBjb3JyZWxhdGlvbiB3aXRoIFN0YXBoeWxvY29jY3VzIGF1cmV1cywgd2hpY2ggaXMgb25lIG9mIHRoZSBtb3N0IHN1Y2Nlc3NmdWwgbW9kZXJuIHBhdGhvZ2VucyBjYXVzaW5nIHNldmVyZSBpbmZlY3Rpb25zIHdpdGggdHJlbWVuZG91cyBtb3JiaWRpdHkgYW5kIG1vcnRhbGl0eSBkdWUgdG8gaXRzIG11bHRpcGxlIHJlc2lzdGFuY2VzLiBBcyB0aGUgcG9zc2libGUgbWVjaGFuaXNtcyBiZWhpbmQgaXRzIGluaGliaXRpb24gb2YgUy4gYXVyZXVzIHJlbWFpbiB1bmNsZWFyLCBhIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWwgKEdFTSkgaXMgb2YgZW5vcm1vdXMgaW50ZXJlc3QgYW5kIGhpZ2ggaW1wb3J0YW5jZSB0byBiZXR0ZXIgc3R1ZHkgaXRzIHJvbGUgaW4gdGhpcyBmaWdodC4gVGhpcyBhcnRpY2xlIHByZXNlbnRzIHRoZSBmaXJzdCBHRU0gb2YgRC4gcGlncnVtLCB3aGljaCB3YXMgY3VyYXRlZCB1c2luZyBhdXRvbWF0ZWQgcmVjb25zdHJ1Y3Rpb24gdG9vbHMgYW5kIGV4dGVuc2l2ZSBtYW51YWwgY3VyYXRpb24gc3RlcHMgdG8geWllbGQgYSBoaWdoLXF1YWxpdHkgR0VNLiBJdCB3YXMgZXZhbHVhdGVkIGFuZCB2YWxpZGF0ZWQgdXNpbmcgYWxsIGN1cnJlbnRseSBhdmFpbGFibGUgZXhwZXJpbWVudGFsIGRhdGEgb2YgRC4gcGlncnVtLiBXaXRoIHRoaXMgbW9kZWwsIGFscmVhZHkgcHJlZGljdGVkIGF1eG90cm9waGllcyBhbmQgYmlvc3ludGhldGljIHBhdGh3YXlzIGNvdWxkIGJlIHZlcmlmaWVkLiBUaGUgbW9kZWwgd2FzIHVzZWQgdG8gZGVmaW5lIGEgbWluaW1hbCBtZWRpdW0gZm9yIGZ1cnRoZXIgbGFib3JhdG9yeSBleHBlcmltZW50cyBhbmQgdG8gcHJlZGljdCB2YXJpb3VzIGNhcmJvbiBzb3VyY2Vz4oCZIGdyb3d0aCBjYXBhY2l0aWVzLiBUaGlzIG1vZGVsIHdpbGwgcGF2ZSB0aGUgd2F5IHRvIGJldHRlciB1bmRlcnN0YW5kIEQuIHBpZ3J1beKAmXMgcm9sZSBpbiB0aGUgZmlnaHQgYWdhaW5zdCBTLiBhdXJldXMuIiwKICAiZm9ybWF0IiA6IHsKICAgICJuYW1lIiA6ICJDT01CSU5FIGFyY2hpdmUiLAogICAgInZlcnNpb24iIDogIjAuMSIKICB9LAogICJwdWJsaWNhdGlvbiIgOiB7CiAgICAiam91cm5hbCIgOiAiTWV0YWJvbGl0ZXMiLAogICAgInRpdGxlIiA6ICJGaXJzdCBHZW5vbWUtU2NhbGUgTWV0YWJvbGljIE1vZGVsIG9mIERvbG9zaWdyYW51bHVtIHBpZ3J1bSBDb25maXJtcyBNdWx0aXBsZSBBdXhvdHJvcGhpZXMiLAogICAgImFmZmlsaWF0aW9uIiA6ICIxIENvbXB1dGF0aW9uYWwgU3lzdGVtcyBCaW9sb2d5IG9mIEluZmVjdGlvbnMgYW5kIEFudGltaWNyb2JpYWwtUmVzaXN0YW50IFBhdGhvZ2VucywgSW5zdGl0dXRlIGZvciBCaW9pbmZvcm1hdGljcyBhbmQgTWVkaWNhbCBJbmZvcm1hdGljcyAoSUJNSSksIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCA3MjA3NiBUw7xiaW5nZW4sIEdlcm1hbnlcbjIgRGVwYXJ0bWVudCBvZiBDb21wdXRlciBTY2llbmNlLCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgNzIwNzYgVMO8YmluZ2VuLCBHZXJtYW55XG4zIENsdXN0ZXIgb2YgRXhjZWxsZW5jZSDigJhDb250cm9sbGluZyBNaWNyb2JlcyB0byBGaWdodCBJbmZlY3Rpb25zLOKAmSBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgNzIwNzYgVMO8YmluZ2VuLCBHZXJtYW55XG40IEdlcm1hbiBDZW50ZXIgZm9yIEluZmVjdGlvbiBSZXNlYXJjaCAoRFpJRiksIFBhcnRuZXIgc2l0ZSBUw7xiaW5nZW4sIDcyMDc2IFTDvGJpbmdlbiwgR2VybWFueSIsCiAgICAic3lub3BzaXMiIDogIkRvbG9zaWdyYW51bHVtIHBpZ3J1bSBpcyBhIHF1aXRlIHJlY2VudGx5IGRpc2NvdmVyZWQgR3JhbS1wb3NpdGl2ZSBjb2NjdXMuIEl0IGhhcyBnYWluZWQgaW5jcmVhc2luZyBhdHRlbnRpb24gZHVlIHRvIGl0cyBuZWdhdGl2ZSBjb3JyZWxhdGlvbiB3aXRoIFN0YXBoeWxvY29jY3VzIGF1cmV1cywgd2hpY2ggaXMgb25lIG9mIHRoZSBtb3N0IHN1Y2Nlc3NmdWwgbW9kZXJuIHBhdGhvZ2VucyBjYXVzaW5nIHNldmVyZSBpbmZlY3Rpb25zIHdpdGggdHJlbWVuZG91cyBtb3JiaWRpdHkgYW5kIG1vcnRhbGl0eSBkdWUgdG8gaXRzIG11bHRpcGxlIHJlc2lzdGFuY2VzLiBBcyB0aGUgcG9zc2libGUgbWVjaGFuaXNtcyBiZWhpbmQgaXRzIGluaGliaXRpb24gb2YgUy4gYXVyZXVzIHJlbWFpbiB1bmNsZWFyLCBhIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWwgKEdFTSkgaXMgb2YgZW5vcm1vdXMgaW50ZXJlc3QgYW5kIGhpZ2ggaW1wb3J0YW5jZSB0byBiZXR0ZXIgc3R1ZHkgaXRzIHJvbGUgaW4gdGhpcyBmaWdodC4gVGhpcyBhcnRpY2xlIHByZXNlbnRzIHRoZSBmaXJzdCBHRU0gb2YgRC4gcGlncnVtLCB3aGljaCB3YXMgY3VyYXRlZCB1c2luZyBhdXRvbWF0ZWQgcmVjb25zdHJ1Y3Rpb24gdG9vbHMgYW5kIGV4dGVuc2l2ZSBtYW51YWwgY3VyYXRpb24gc3RlcHMgdG8geWllbGQgYSBoaWdoLXF1YWxpdHkgR0VNLiBJdCB3YXMgZXZhbHVhdGVkIGFuZCB2YWxpZGF0ZWQgdXNpbmcgYWxsIGN1cnJlbnRseSBhdmFpbGFibGUgZXhwZXJpbWVudGFsIGRhdGEgb2YgRC4gcGlncnVtLiBXaXRoIHRoaXMgbW9kZWwsIGFscmVhZHkgcHJlZGljdGVkIGF1eG90cm9waGllcyBhbmQgYmlvc3ludGhldGljIHBhdGh3YXlzIGNvdWxkIGJlIHZlcmlmaWVkLiBUaGUgbW9kZWwgd2FzIHVzZWQgdG8gZGVmaW5lIGEgbWluaW1hbCBtZWRpdW0gZm9yIGZ1cnRoZXIgbGFib3JhdG9yeSBleHBlcmltZW50cyBhbmQgdG8gcHJlZGljdCB2YXJpb3VzIGNhcmJvbiBzb3VyY2Vz4oCZIGdyb3d0aCBjYXBhY2l0aWVzLiBUaGlzIG1vZGVsIHdpbGwgcGF2ZSB0aGUgd2F5IHRvIGJldHRlciB1bmRlcnN0YW5kIEQuIHBpZ3J1beKAmXMgcm9sZSBpbiB0aGUgZmlnaHQgYWdhaW5zdCBTLiBhdXJldXMuIiwKICAgICJ5ZWFyIiA6IDIwMjEsCiAgICAibW9udGgiIDogIjQiLAogICAgInZvbHVtZSIgOiAiMTEiLAogICAgImlzc3VlIiA6ICI0IiwKICAgICJwYWdlcyIgOiAiMjMyIiwKICAgICJsaW5rIiA6ICJodHRwOi8vaWRlbnRpZmllcnMub3JnL2RvaS8xMC4zMzkwL21ldGFibzExMDQwMjMyIiwKICAgICJhdXRob3JzIiA6IFsgewogICAgICAibmFtZSIgOiAiQWxpbmEgUmVueiIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAzLTM4NTEtOTk3OCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkxpbmEgV2lkZXJzcGljayIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiQmVybmhhcmQtTm9jaHQtSW5zdGl0dXRlIGZvciBUcm9waWNhbCBNZWRpY2luZSIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAxLTY2NjYtNzc3MCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFuZHJlYXMgRHLDpGdlciIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAyLTEyNDAtNTU1MyIKICAgIH0gXQogIH0sCiAgImZpbGVzIiA6IHsKICAgICJtYWluIiA6IFsgewogICAgICAibmFtZSIgOiAiaURQTTIxUlcub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMTI5NTgyNiIKICAgIH0gXQogIH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogNSwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA0NzUzNzQ5MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJBbmRyZWFzIERyw6RnZXIiLAogICAgICAiY29tbWVudCIgOiAiQWRkZWQgRlJPRyByZXBvcnQuIgogICAgfSBdCiAgfSwKICAiZmlyc3RQdWJsaXNoZWQiIDogMTYxODI5OTM4MDAwMCwKICAic3VibWlzc2lvbklkIiA6ICJNT0RFTDIwMTIyMjAwMDMiCn0= + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2212060002 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=a333feec-5ae2-4f1d-9114-110399e60d4b; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.595.16427.918320; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: |- + { + "name" : "DeBoeck2021 - Modular approach to modeling the cell cycle, 5 ODE model with 3 bistable switches", + "description" : "\n \n

    Model of the mammalian cell cycle as a chain of bistable switches. \nThere are three bistable responses: response of E2F to Cyclin D, of Cdk1 to Cyclin B and of APC/C to Cdk1 activity. \nThe model for the given parameters admits a complex limit cycle characterized by transitions through the bistable switches. \nThe bistable responses are modeled directly using a functional motif, not through biochemical interactions. This modular approach allows to easily modify the properties of the bistable response curves. This version of the model correspond to Fig. 7 in the publication. \nWe illustrated how, using this model, the system can be coupled to the circadian clock, by periodically modifying thresholds of one of the switches. We also illustrated how to implement the restriction point checkpoint using this model (those applications are not coded in the associated sbml file and can be seen in Fig. 8 of the publication). \n\nA related, simpler model that illustrates the bistable motif is MODEL2212060001

    \n \n
    ", + "format" : { + "name" : "SBML", + "version" : "L2V4" + }, + "publication" : { + "journal" : "PLoS computational biology", + "title" : "A modular approach for modeling the cell cycle based on functional response curves.", + "affiliation" : "Laboratory of Dynamics in Biological Systems, Department of Cellular and Molecular Medicine, University of Leuven, Leuven, Belgium.", + "synopsis" : "Modeling biochemical reactions by means of differential equations often results in systems with a large number of variables and parameters. As this might complicate the interpretation and generalization of the obtained results, it is often desirable to reduce the complexity of the model. One way to accomplish this is by replacing the detailed reaction mechanisms of certain modules in the model by a mathematical expression that qualitatively describes the dynamical behavior of these modules. Such an approach has been widely adopted for ultrasensitive responses, for which underlying reaction mechanisms are often replaced by a single Hill function. Also time delays are usually accounted for by using an explicit delay in delay differential equations. In contrast, however, S-shaped response curves, which by definition have multiple output values for certain input values and are often encountered in bistable systems, are not easily modeled in such an explicit way. Here, we extend the classical Hill function into a mathematical expression that can be used to describe both ultrasensitive and S-shaped responses. We show how three ubiquitous modules (ultrasensitive responses, S-shaped responses and time delays) can be combined in different configurations and explore the dynamics of these systems. As an example, we apply our strategy to set up a model of the cell cycle consisting of multiple bistable switches, which can incorporate events such as DNA damage and coupling to the circadian clock in a phenomenological way.", + "year" : 2021, + "month" : "8", + "volume" : "17", + "issue" : "8", + "pages" : "e1009008", + "link" : "http://identifiers.org/doi/10.1371/journal.pcbi.1009008", + "authors" : [ { + "name" : "De Boeck J", + "orcid" : "0000-0001-6180-3323" + }, { + "name" : "Jan Rombouts", + "institution" : "EMBL Heidelberg", + "orcid" : "0000-0003-4135-0771" + }, { + "name" : "Gelens L", + "orcid" : "0000-0001-7290-9561" + } ] + }, + "files" : { + "main" : [ { + "name" : "DeBoeck2021_cellcycle_threeswitches.xml", + "fileSize" : "32302" + } ], + "additional" : [ { + "name" : "DeBoeck2021_cellcycle_threeswitches.sedml", + "fileSize" : "9720", + "description" : "SEDMl file for the curated model" + }, { + "name" : "cellcycle_threeswitches.xml", + "fileSize" : "11473", + "description" : "5 ODE model of cell cycle built on bistable modules" + }, { + "name" : "DeBoeck2021_cellcycle_threeswitches.cps", + "fileSize" : "91123", + "description" : "COPASI file for the curated model" + } ] + }, + "history" : { + "revisions" : [ { + "version" : 2, + "submitted" : 1670416891000, + "submitter" : "Jan Rombouts", + "comment" : "Fixed typo in name + extended description a bit" + }, { + "version" : 3, + "submitted" : 1705967007000, + "submitter" : "Tung Nguyen", + "comment" : "Improved the model description. Also, triggered the automation of adding publication and model identifier annotation." + }, { + "version" : 5, + "submitted" : 1706523787000, + "submitter" : "Krishna Kumar Tiwari", + "comment" : "Automatically added model identifier BIOMD0000001080" + } ] + }, + "firstPublished" : 1706523792000, + "submissionId" : "MODEL2212060002", + "publicationId" : "BIOMD0000001080" + } + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2211290003 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=2966a65d-d968-4470-8604-4e81129aa485; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.689.16427.671411; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJDYXNpbmkyMDIyIC0gTWV0aGFub3RoZXJtb2JhY3RlciBtYXJidXJnZW5zaXMgTWFyYnVyZyIsCiAgImRlc2NyaXB0aW9uIiA6ICJUaGlzIG1vZGVsIGlzIGRlc2NyaWJlZCBpbiB0aGVzZSBhcnRpY2xlczpcblxuQ2FzaW5pLCBJLiwgTWNDdWJiaW4sIFQuLCBFc3F1aXZlbC1FbGl6b25kbywgUy4sIEx1cXVlLCBHLkcuLCBFdnNlZXZhLCBELiwgRmluaywgQy4sIEJlYmxhd3ksIFMuLCBZb3VuZ2JsdXQsIE4uRC4sIEFyaXN0aWxkZSwgTC4sIEh1c29uLCBELiwgRHJhZ2VyLCBBLiwgTGV5LCBSLiwgTWFyY2VsbGluLCBFLiwgQW5nZW5lbnQsIEwuVC4sIE1vbGl0b3IsIEIuIDIwMjMuIEFuIGludGVncmF0ZWQgc3lzdGVtcyBiaW9sb2d5IGFwcHJvYWNoIHJldmVhbHMgZGlmZmVyZW5jZXMgaW4gZm9ybWF0ZSBtZXRhYm9saXNtIGluIHRoZSBnZW51cyBNZXRoYW5vdGhlcm1vYmFjdGVyLiBJc2NpZW5jZSwgMjYoMTApLlxuXG5DYXNpbmksIEkuLCBNY0N1YmJpbiwgVC4sIEVzcXVpdmVsLUVsaXpvbmRvLCBTLiwgTHVxdWUsIEcuRy4sIEV2c2VldmEsIEQuLCBGaW5rLCBDLiwgQmVibGF3eSwgUy4sIFlvdW5nYmx1dCwgTi5ELiwgQXJpc3RpbGRlLCBMLiwgSHVzb24sIEQuLCBEcmFnZXIsIEEuLCBMZXksIFIuLCBNYXJjZWxsaW4sIEUuLCBBbmdlbmVudCwgTC5ULiwgTW9saXRvciwgQi4gMjAyMi4gQW4gaW50ZWdyYXRlZCBzeXN0ZW1zLWJpb2xvZ3kgcGxhdGZvcm0gZm9yIHBvd2VyLXRvLWdhcyB0ZWNobm9sb2d5LiBiaW9SeGl2LCAyMDIyLjEyLjMwLjUyMjIzNi5cblxuTWV0aGFub2dlbmVzaXMgYWxsb3dzIG1ldGhhbm9nZW5pYyBhcmNoYWVhIChtZXRoYW5vZ2VucykgdG8gZ2VuZXJhdGUgY2VsbHVsYXIgZW5lcmd5IGZvciB0aGVpciBncm93dGggd2hpbGUgcHJvZHVjaW5nIG1ldGhhbmUuIEh5ZHJvZ2Vub3Ryb3BoaWMgbWV0aGFub2dlbnMgdGhyaXZlIG9uIGNhcmJvbiBkaW94aWRlIGFuZCBtb2xlY3VsYXIgaHlkcm9nZW4gYXMgc29sZSBjYXJib24gYW5kIGVuZXJneSBzb3VyY2VzLiBUaGVybW9waGlsaWMgYW5kIGh5ZHJvZ2Vub3Ryb3BoaWMgTWV0aGFub3RoZXJtb2JhY3RlciBzcHAuIGhhdmUgYmVlbiByZWNvZ25pemVkIGFzIHJvYnVzdCBiaW9jYXRhbHlzdHMgZm9yIGEgY2lyY3VsYXIgY2FyYm9uIGVjb25vbXkgYW5kIGFyZSBub3cgYXBwbGllZCBpbiBwb3dlci10by1nYXMgdGVjaG5vbG9neS4gSGVyZSwgd2UgZ2VuZXJhdGVkIHRoZSBmaXJzdCBtYW51YWxseSBjdXJhdGVkIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgcmVjb25zdHJ1Y3Rpb24gZm9yIHRocmVlIE1ldGhhbm90aGVybW9iYWN0ZXIgc3BwLi4gV2UgaW52ZXN0aWdhdGVkIGRpZmZlcmVuY2VzIGluIGdyb3d0aCBwZXJmb3JtYW5jZSBvZiB0aHJlZSB3aWxkLXR5cGUgc3RyYWlucyBhbmQgb25lIGdlbmV0aWNhbGx5IGVuZ2luZWVyZWQgc3RyYWluIGluIHR3byBpbmRlcGVuZGVudCBjaGVtb3N0YXQgYmlvcmVhY3RvciBleHBlcmltZW50cywgZmlyc3QsIHdpdGggbW9sZWN1bGFyIGh5ZHJvZ2VuIGFuZCBjYXJib24gZGlveGlkZSwgYW5kIHNlY29uZCwgd2l0aCBzb2RpdW0gZm9ybWF0ZS4gSW4gdGhlIGZpcnN0IGV4cGVyaW1lbnQsIHdlIGZvdW5kIHRoZSBoaWdoZXN0IG1ldGhhbmUgcHJvZHVjdGlvbiByYXRlIGZvciBNZXRoYW5vdGhlcm1vYmFjdGVyIHRoZXJtYXV0b3Ryb3BoaWN1cyDOlEgsIHdoaWxlIE1ldGhhbm90aGVybW9iYWN0ZXIgbWFyYnVyZ2Vuc2lzIE1hcmJ1cmcgcmVhY2hlZCB0aGUgaGlnaGVzdCBiaW9tYXNzIGdyb3d0aCByYXRlLiBUcmFuc2NyaXB0b21pY3MgYW5kIHByb3Rlb21pY3MgZGF0YSBzZXRzIGZyb20gdGhlc2Ugc3RlYWR5LXN0YXRlIGJpb3JlYWN0b3JzLCBpbiBjb21iaW5hdGlvbiB3aXRoIHRoZSBpbXBsZW1lbnRhdGlvbiBvZiBhIHBhbi1tb2RlbCB0aGF0IGNvbnRhaW5zIGNvbWJpbmVkIHJlYWN0aW9ucyBmcm9tIGFsbCB0aHJlZSBtaWNyb2JlcywgYWxsb3dlZCB1cyB0byBwZXJmb3JtIGFuIGludGVyc3BlY2llcyBjb21wYXJpc29uLiBXaGlsZSB0aGUgb2JzZXJ2ZWQgZGlmZmVyZW5jZXMgaW4gdGhlIGdyb3d0aCBiZWhhdmlvciBjYW5ub3QgYmUgZnVsbHkgZXhwbGFpbmVkLCB0aGUgY29tcGFyaXNvbiBlbmFibGVkIHVzIHRvIGlkZW50aWZ5IGNydWNpYWwgZGlmZmVyZW5jZXMgaW4gZ3Jvd3RoLXJlbGF0ZWQgcGF0aHdheXMgc3VjaCBhcyBmb3JtYXRlIGFuYWJvbGlzbS4gSW4gdGhlIHNlY29uZCBleHBlcmltZW50LCB3ZSBmb3VuZCBzdGFibGUgZ3Jvd3RoIHdpdGggYSBNLiB0aGVybWF1dG90cm9waGljdXMgzpRIIHBsYXNtaWQtY2Fycnlpbmcgc3RyYWluIG9uIGZvcm1hdGUgd2l0aCBzaW1pbGFyIHBlcmZvcm1hbmNlIHBhcmFtZXRlcnMgY29tcGFyZWQgdG8gd2lsZC10eXBlIE1ldGhhbm90aGVybW9iYWN0ZXIgdGhlcm1hdXRvdHJvcGhpY3VzIFotMjQ1LiBUaGUgcmVzdWx0cyBvZiB0aGUgdHdvIHN0dWRpZXMgZGVtb25zdHJhdGUgdGhlIGFkdmFudGFnZXMgb2YgYW4gaW50ZWdyYXRpdmUgYXBwcm9hY2ggY29tYmluaW5nIGZlcm1lbnRhdGlvbiBhbmQgb21pY3MgZGF0YSB3aXRoIGdlbm9tZS1zY2FsZSBtb2RlbGluZyB0byByZXZlYWwga25vd2xlZGdlIGdhcHMgb2YgYXJjaGFlYWwgbWV0YWJvbGlzbXMgYW5kIHRoZSBiaW90ZWNobm9sb2dpY2FsIHBvdGVudGlhbCBvZiBNZXRoYW5vdGhlcm1vYmFjdGVyIHNwcC4gYXMgcHJvZHVjdGlvbiBwbGF0Zm9ybSBob3N0cy4iLAogICJmb3JtYXQiIDogewogICAgIm5hbWUiIDogIlNCTUwiLAogICAgInZlcnNpb24iIDogIkwzVjEiCiAgfSwKICAicHVibGljYXRpb24iIDogewogICAgImpvdXJuYWwiIDogImlTY2llbmNlIiwKICAgICJ0aXRsZSIgOiAiQW4gaW50ZWdyYXRlZCBzeXN0ZW1zIGJpb2xvZ3kgYXBwcm9hY2ggcmV2ZWFscyBkaWZmZXJlbmNlcyBpbiBmb3JtYXRlIG1ldGFib2xpc20gaW4gdGhlIGdlbnVzIDxpPk1ldGhhbm90aGVybW9iYWN0ZXI8L2k+LiIsCiAgICAiYWZmaWxpYXRpb24iIDogIkVudmlyb25tZW50YWwgQmlvdGVjaG5vbG9neSBHcm91cCwgRGVwYXJ0bWVudCBvZiBHZW9zY2llbmNlcywgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4sIFNjaG5hcnJlbmJlcmdzdHJhw59lIDk0LTk2LCA3MjA3NiBUw7xiaW5nZW4sIEdlcm1hbnkuIiwKICAgICJzeW5vcHNpcyIgOiAiTWV0aGFub2dlbmVzaXMgYWxsb3dzIG1ldGhhbm9nZW5pYyBhcmNoYWVhIHRvIGdlbmVyYXRlIGNlbGx1bGFyIGVuZXJneSBmb3IgdGhlaXIgZ3Jvd3RoIHdoaWxlIHByb2R1Y2luZyBtZXRoYW5lLiBUaGVybW9waGlsaWMgaHlkcm9nZW5vdHJvcGhpYyBzcGVjaWVzIG9mIHRoZSBnZW51cyA8aT5NZXRoYW5vdGhlcm1vYmFjdGVyPC9pPiBoYXZlIGJlZW4gcmVjb2duaXplZCBhcyByb2J1c3QgYmlvY2F0YWx5c3RzIGZvciBhIGNpcmN1bGFyIGNhcmJvbiBlY29ub215IGFuZCBhcmUgYWxyZWFkeSBhcHBsaWVkIGluIHBvd2VyLXRvLWdhcyB0ZWNobm9sb2d5IHdpdGggYmlvbWV0aGFuYXRpb24sIHdoaWNoIGlzIGEgcGxhdGZvcm0gdG8gc3RvcmUgcmVuZXdhYmxlIGVuZXJneSBhbmQgdXRpbGl6ZSBjYXB0dXJlZCBjYXJib24gZGlveGlkZS4gSGVyZSwgd2UgZ2VuZXJhdGVkIGN1cmF0ZWQgZ2Vub21lLXNjYWxlIG1ldGFib2xpYyByZWNvbnN0cnVjdGlvbnMgZm9yIHRocmVlIDxpPk1ldGhhbm90aGVybW9iYWN0ZXI8L2k+IHN0cmFpbnMgYW5kIGludmVzdGlnYXRlZCBkaWZmZXJlbmNlcyBpbiB0aGUgZ3Jvd3RoIHBlcmZvcm1hbmNlIG9mIHRoZXNlIHNhbWUgc3RyYWlucyBpbiBjaGVtb3N0YXQgYmlvcmVhY3RvciBleHBlcmltZW50cyB3aXRoIGh5ZHJvZ2VuIGFuZCBjYXJib24gZGlveGlkZSBvciBmb3JtYXRlIGFzIHN1YnN0cmF0ZXMuIFVzaW5nIGFuIGludGVncmF0ZWQgc3lzdGVtcyBiaW9sb2d5IGFwcHJvYWNoLCB3ZSBpZGVudGlmaWVkIGRpZmZlcmVuY2VzIGluIGZvcm1hdGUgYW5hYm9saXNtIGJldHdlZW4gdGhlIHN0cmFpbnMgYW5kIHJldmVhbGVkIHRoYXQgZm9ybWF0ZSBhbmFib2xpc20gaW5mbHVlbmNlcyB0aGUgZGl2ZXJzaW9uIG9mIGNhcmJvbiBiZXR3ZWVuIGJpb21hc3MgYW5kIG1ldGhhbmUuIFRoaXMgZmluZGluZywgdG9nZXRoZXIgd2l0aCB0aGUgb21pY3MgZGF0YXNldHMgYW5kIHRoZSBtZXRhYm9saWMgbW9kZWxzIHdlIGdlbmVyYXRlZCwgY2FuIGJlIGltcGxlbWVudGVkIGZvciBiaW90ZWNobm9sb2dpY2FsIGFwcGxpY2F0aW9ucyBvZiA8aT5NZXRoYW5vdGhlcm1vYmFjdGVyPC9pPiBpbiBwb3dlci10by1nYXMgdGVjaG5vbG9neSwgYW5kIGFzIGEgcGVyc3BlY3RpdmUsIGZvciB2YWx1ZS1hZGRlZCBjaGVtaWNhbCBwcm9kdWN0aW9uLiIsCiAgICAieWVhciIgOiAyMDIzLAogICAgIm1vbnRoIiA6ICIxMCIsCiAgICAidm9sdW1lIiA6ICIyNiIsCiAgICAiaXNzdWUiIDogIjEwIiwKICAgICJwYWdlcyIgOiAiMTA4MDE2IiwKICAgICJsaW5rIiA6ICJodHRwOi8vaWRlbnRpZmllcnMub3JnL2RvaS8xMC4xMDE2L2ouaXNjaS4yMDIzLjEwODAxNiIsCiAgICAiYXV0aG9ycyIgOiBbIHsKICAgICAgIm5hbWUiIDogIklzYWJlbGxhIENhc2luaSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlRpbSBNY0N1YmJpbiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlNvZmlhIEVzcXVpdmVsLUVsaXpvbmRvIgogICAgfSwgewogICAgICAibmFtZSIgOiAiR3VpbGxlcm1vIEcgTHVxdWUiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJEYXJpYSBFdnNlZXZhIgogICAgfSwgewogICAgICAibmFtZSIgOiAiQ2hyaXN0aWFuIEZpbmsiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJTZWJhc3RpYW4gQmVibGF3eSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIk5pY2hvbGFzIEQgWW91bmdibHV0IgogICAgfSwgewogICAgICAibmFtZSIgOiAiTHVkbWlsbGEgQXJpc3RpbGRlIgogICAgfSwgewogICAgICAibmFtZSIgOiAiRGFuaWVsIEggSHVzb24iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJBbmRyZWFzIERyw6RnZXIiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJSdXRoIEUgTGV5IgogICAgfSwgewogICAgICAibmFtZSIgOiAiRXN0ZWJhbiBNYXJjZWxsaW4iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJMYXJndXMgVCBBbmdlbmVudCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkJhc3RpYW4gTW9saXRvciIKICAgIH0gXQogIH0sCiAgImZpbGVzIiA6IHsKICAgICJtYWluIiA6IFsgewogICAgICAibmFtZSIgOiAiQ2FzaW5pMjAyMl9pTU1NMjJJQ191bmJvdW5kLnhtbCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMTc4ODEwMiIKICAgIH0gXSwKICAgICJhZGRpdGlvbmFsIiA6IFsgewogICAgICAibmFtZSIgOiAib21leF9NTS5vbWV4IiwKICAgICAgImZpbGVTaXplIiA6ICIzMTI0NDQ5IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJDYXNpbmkgZXQgYWwuIDIwMjIsIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWwgKGlNTU0yMklDKSBvZiBNZXRoYW5vdGhlcm1vYmFjdGVyIG1hcmJ1cmdlbnNpcyBNYXJidXJnLCBpbmNsdWRpbmcgcGFuLW1ldGhhbm90aGVybW9iYWN0ZXIgc3BwLiByZWNvbnN0cnVjdGlvbiAoLnhsc3gpIgogICAgfSwgewogICAgICAibmFtZSIgOiAiRlJPR191bmJvdW5kX2N1cmF0ZWQub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMTQzODA3IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJGUk9HIFJlcG9ydCAtIHVuYm91bmQgdmVyc2lvbiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIk1FTU9URS1SZXBvcnQtdW5ib3VuZC5odG1sIiwKICAgICAgImZpbGVTaXplIiA6ICIzNzg2MDAxIiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJNRU1PVEUgUmVwb3J0IC0gdW5ib3VuZCB2ZXJzaW9uIgogICAgfSwgewogICAgICAibmFtZSIgOiAiTU1fdW5ib3VuZF9GUk9HLm9tZXgiLAogICAgICAiZmlsZVNpemUiIDogIjE5OTk0MyIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiRlJPRyBhbmFseXNpcyBvZiB0aGUgdW5ib3VuZCBpTU1NMjJJQyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIk1NX2JvdW5kX0ZST0cub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMTk5MzM2IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJGUk9HIGFuYWx5c2lzIG9mIHRoZSB1bmJvdW5kIGlNTU0yMklDIgogICAgfSBdCiAgfSwKICAiaGlzdG9yeSIgOiB7CiAgICAicmV2aXNpb25zIiA6IFsgewogICAgICAidmVyc2lvbiIgOiAzLAogICAgICAic3VibWl0dGVkIiA6IDE2NzI0Njg5MzkwMDAsCiAgICAgICJzdWJtaXR0ZXIiIDogIklzYWJlbGxhIENhc2luaSIsCiAgICAgICJjb21tZW50IiA6ICJVcGRhdGVkIHRoZSBwdWJsaWNhdGlvbi4iCiAgICB9LCB7CiAgICAgICJ2ZXJzaW9uIiA6IDQsCiAgICAgICJzdWJtaXR0ZWQiIDogMTcwMjU0OTA2MDAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAiVHVuZyBOZ3V5ZW4iLAogICAgICAiY29tbWVudCIgOiAiVXBkYXRlZCB0aGUgbW9kZWwgd2l0aCB0aGUgY3VyYXRlZCBGUk9HIGFuZCBNRU1PVEUgcmVwb3J0LiIKICAgIH0sIHsKICAgICAgInZlcnNpb24iIDogNiwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA0OTcxNDAxMDAwLAogICAgICAic3VibWl0dGVyIiA6ICJUdW5nIE5ndXllbiIsCiAgICAgICJjb21tZW50IiA6ICJNYWRlIHRoZSBtb2RlbCB0aXRsZSBiZXR0ZXIgYW5kIGNvbXBsaWVkIHdpdGggdGhlIHN1Ym1pc3Npb24gZ3VpZGVsaW5lcy4iCiAgICB9IF0KICB9LAogICJmaXJzdFB1Ymxpc2hlZCIgOiAxNzA0OTcxNDI2MDAwLAogICJzdWJtaXNzaW9uSWQiIDogIk1PREVMMjIxMTI5MDAwMyIKfQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2212060001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=fb8b0b7b-73fd-43d4-a010-5e7845120fb4; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.813.16427.161005; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: |- + { + "name" : "DeBoeck2021 - Modular approach to modeling the cell cycle, simple cell cycle model", + "description" : "\n \n

    Models the production and degradation of cyclin B that drives the early embryonic cell cycle.\nCyclin B is degraded by APC/C. The activity of APC/C is modeled not through biochemical interactions, but through a 'functional response curve'. This can be ultrasensitive (with the parameter alpha=0). in this case the system does not oscillate. Importantly the response can be made bistable and the form of the bistability can be easily prescribed. With a bistable response, the system oscillates.\n\nThe uploaded file corresponds to the model used for Figs.3H, I in the publication.

    \n \n
    ", + "format" : { + "name" : "SBML", + "version" : "L2V4" + }, + "publication" : { + "journal" : "PLoS computational biology", + "title" : "A modular approach for modeling the cell cycle based on functional response curves.", + "affiliation" : "Laboratory of Dynamics in Biological Systems, Department of Cellular and Molecular Medicine, University of Leuven, Leuven, Belgium.", + "synopsis" : "Modeling biochemical reactions by means of differential equations often results in systems with a large number of variables and parameters. As this might complicate the interpretation and generalization of the obtained results, it is often desirable to reduce the complexity of the model. One way to accomplish this is by replacing the detailed reaction mechanisms of certain modules in the model by a mathematical expression that qualitatively describes the dynamical behavior of these modules. Such an approach has been widely adopted for ultrasensitive responses, for which underlying reaction mechanisms are often replaced by a single Hill function. Also time delays are usually accounted for by using an explicit delay in delay differential equations. In contrast, however, S-shaped response curves, which by definition have multiple output values for certain input values and are often encountered in bistable systems, are not easily modeled in such an explicit way. Here, we extend the classical Hill function into a mathematical expression that can be used to describe both ultrasensitive and S-shaped responses. We show how three ubiquitous modules (ultrasensitive responses, S-shaped responses and time delays) can be combined in different configurations and explore the dynamics of these systems. As an example, we apply our strategy to set up a model of the cell cycle consisting of multiple bistable switches, which can incorporate events such as DNA damage and coupling to the circadian clock in a phenomenological way.", + "year" : 2021, + "month" : "8", + "volume" : "17", + "issue" : "8", + "pages" : "e1009008", + "link" : "http://identifiers.org/doi/10.1371/journal.pcbi.1009008", + "authors" : [ { + "name" : "De Boeck J", + "orcid" : "0000-0001-6180-3323" + }, { + "name" : "Jan Rombouts", + "institution" : "EMBL Heidelberg", + "orcid" : "0000-0003-4135-0771" + }, { + "name" : "Gelens L", + "orcid" : "0000-0001-7290-9561" + } ] + }, + "files" : { + "main" : [ { + "name" : "DeBoeck2021_cellcycle_bistableapc.xml", + "fileSize" : "17441" + } ], + "additional" : [ { + "name" : "cellcycle_bistableapc.xml", + "fileSize" : "5769", + "description" : "2 ODE Cell cycle model with Cdk1-Cyclin B and APC/C, APC/C implemented as bistable module" + }, { + "name" : "DeBoeck2021_cellcycle_bistableapc.cps", + "fileSize" : "52751", + "description" : "COPASI file for the curated model" + }, { + "name" : "DeBoeck2021_cellcycle_bistableapc.sedml", + "fileSize" : "4466", + "description" : "SEDML file for the curated model" + } ] + }, + "history" : { + "revisions" : [ { + "version" : 2, + "submitted" : 1670321462000, + "submitter" : "Jan Rombouts", + "comment" : "Parameter change + indicated the corresponding figure in the publication." + }, { + "version" : 3, + "submitted" : 1705966766000, + "submitter" : "Tung Nguyen", + "comment" : "Made a minor update on the model description to trigger the automation of adding publication and model identifier annotation." + }, { + "version" : 5, + "submitted" : 1706523391000, + "submitter" : "Krishna Kumar Tiwari", + "comment" : "Automatically added model identifier BIOMD0000001079" + } ] + }, + "firstPublished" : 1706523397000, + "submissionId" : "MODEL2212060001", + "publicationId" : "BIOMD0000001079" + } + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2006080001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:21 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=43553eb3-bdd2-4e95-84ad-4e5bbc2bd074; Path=/biomodels/; HttpOnly + - biomodels-session=1706699242.928.16423.372688; Expires=Wed, 31-Jan-24 12:07:21 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJOb3ZhazIwMDQgLSBBIE1vZGVsIGZvciBSZXN0cmljdGlvbiBQb2ludCBDb250cm9sIG9mIHRoZSBNYW1tYWxpYW4gQ2VsbCBDeWNsZSIsCiAgImRlc2NyaXB0aW9uIiA6ICI8bm90ZXMgeG1sbnM9XCJodHRwOi8vd3d3LnNibWwub3JnL3NibWwvbGV2ZWwyL3ZlcnNpb240XCI+XG4gICAgICA8Ym9keSB4bWxucz1cImh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWxcIj5cbiAgICAgPHA+Jmx0O25vdGVzIHhtbG5zPVwiaHR0cDovL3d3dy5zYm1sLm9yZy9zYm1sL2xldmVsMi92ZXJzaW9uNFwiJmd0O1xuICAgICAgJmx0O2JvZHkgeG1sbnM9XCJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sXCImZ3Q7XG4gICAgICAgICZsdDtwcmUmZ3Q7SW5oaWJpdGlvbiBvZiBwcm90ZWluIHN5bnRoZXNpcyBieSBjeWNsb2hleGltaWRlIGJsb2NrcyBzdWJzZXF1ZW50IGRpdmlzaW9uIG9mIGEgbWFtbWFsaWFuIGNlbGwsIGJ1dCBvbmx5IGlmIHRoZSBjZWxsIGlzIGV4cG9zZWQgdG8gdGhlIGRydWcgYmVmb3JlIHRoZSBcInJlc3RyaWN0aW9uIHBvaW50XCIgKGkuZS4gd2l0aGluIHRoZSBmaXJzdCBzZXZlcmFsIGhvdXJzIGFmdGVyIGJpcnRoKS4gSWYgZXhwb3NlZCB0byBjeWNsb2hleGltaWRlIGFmdGVyIHRoZSByZXN0cmljdGlvbiBwb2ludCwgYSBjZWxsIHByb2NlZWRzIHdpdGggRE5BIHN5bnRoZXNpcywgbWl0b3NpcyBhbmQgY2VsbCBkaXZpc2lvbiBhbmQgaGFsdHMgaW4gdGhlIG5leHQgY2VsbCBjeWNsZS4gSWYgY3ljbG9oZXhpbWlkZSBpcyBsYXRlciByZW1vdmVkIGZyb20gdGhlIGN1bHR1cmUgbWVkaXVtLCB0cmVhdGVkIGNlbGxzIHdpbGwgcmV0dXJuIHRvIHRoZSBkaXZpc2lvbiBjeWNsZSwgc2hvd2luZyBhIGNvbXBsZXggcGF0dGVybiBvZiBkaXZpc2lvbiB0aW1lcyBwb3N0LXRyZWF0bWVudCwgYXMgZmlyc3QgbWVhc3VyZWQgYnkgWmV0dGVyYmVyZyBhbmQgY29sbGVhZ3Vlcy4gV2Ugc2ltdWxhdGUgdGhlc2UgcGh5c2lvbG9naWNhbCByZXNwb25zZXMgb2YgbWFtbWFsaWFuIGNlbGxzIHRvIHRyYW5zaWVudCBpbmhpYml0aW9uIG9mIGdyb3d0aCwgdXNpbmcgYSBzZXQgb2Ygbm9ubGluZWFyIGRpZmZlcmVudGlhbCBlcXVhdGlvbnMgYmFzZWQgb24gYSByZWFsaXN0aWMgbW9kZWwgb2YgdGhlIG1vbGVjdWxhciBldmVudHMgdW5kZXJseWluZyBwcm9ncmVzc2lvbiB0aHJvdWdoIHRoZSBjZWxsIGN5Y2xlLiBUaGUgbW9kZWwgcmVsaWVzIG9uIG91ciBlYXJsaWVyIHdvcmsgb24gdGhlIHJlZ3VsYXRpb24gb2YgY3ljbGluLWRlcGVuZGVudCBwcm90ZWluIGtpbmFzZXMgZHVyaW5nIHRoZSBjZWxsIGRpdmlzaW9uIGN5Y2xlIG9mIHllYXN0LiBUaGUgeWVhc3QgbW9kZWwgaXMgc3VwcGxlbWVudGVkIHdpdGggZXF1YXRpb25zIGRlc2NyaWJpbmcgdGhlIGVmZmVjdHMgb2YgcmV0aW5vYmxhc3RvbWEgcHJvdGVpbiBvbiBjZWxsIGdyb3d0aCBhbmQgdGhlIHN5bnRoZXNpcyBvZiBjeWNsaW5zIEEgYW5kIEUsIGFuZCB3aXRoIGEgcHJpbWl0aXZlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBzaWduYWxpbmcgcGF0aHdheSB0aGF0IGNvbnRyb2xzIHN5bnRoZXNpcyBvZiBjeWNsaW4gRC4mbHQ7L3ByZSZndDtcbiAgICAgICZsdDsvYm9keSZndDtcbiAgICAmbHQ7L25vdGVzJmd0OzwvcD5cbiAgPC9ib2R5PlxuICAgIDwvbm90ZXM+IiwKICAiZm9ybWF0IiA6IHsKICAgICJuYW1lIiA6ICJTQk1MIiwKICAgICJ2ZXJzaW9uIiA6ICJMMlY0IgogIH0sCiAgInB1YmxpY2F0aW9uIiA6IHsKICAgICJqb3VybmFsIiA6ICJKb3VybmFsIG9mIHRoZW9yZXRpY2FsIGJpb2xvZ3kiLAogICAgInRpdGxlIiA6ICJBIG1vZGVsIGZvciByZXN0cmljdGlvbiBwb2ludCBjb250cm9sIG9mIHRoZSBtYW1tYWxpYW4gY2VsbCBjeWNsZS4iLAogICAgImFmZmlsaWF0aW9uIiA6ICJNb2xlY3VsYXIgTmV0d29yayBEeW5hbWljcyBSZXNlYXJjaCBHcm91cCBvZiBIdW5nYXJpYW4gQWNhZGVteSBvZiBTY2llbmNlcyBhbmQgQnVkYXBlc3QgVW5pdmVyc2l0eSBvZiBUZWNobm9sb2d5IGFuZCBFY29ub21pY3MsIEdlbGxlcnQgdGVyIDQsIDE1MjEgQnVkYXBlc3QsIEh1bmdhcnkuIiwKICAgICJzeW5vcHNpcyIgOiAiSW5oaWJpdGlvbiBvZiBwcm90ZWluIHN5bnRoZXNpcyBieSBjeWNsb2hleGltaWRlIGJsb2NrcyBzdWJzZXF1ZW50IGRpdmlzaW9uIG9mIGEgbWFtbWFsaWFuIGNlbGwsIGJ1dCBvbmx5IGlmIHRoZSBjZWxsIGlzIGV4cG9zZWQgdG8gdGhlIGRydWcgYmVmb3JlIHRoZSBcInJlc3RyaWN0aW9uIHBvaW50XCIgKGkuZS4gd2l0aGluIHRoZSBmaXJzdCBzZXZlcmFsIGhvdXJzIGFmdGVyIGJpcnRoKS4gSWYgZXhwb3NlZCB0byBjeWNsb2hleGltaWRlIGFmdGVyIHRoZSByZXN0cmljdGlvbiBwb2ludCwgYSBjZWxsIHByb2NlZWRzIHdpdGggRE5BIHN5bnRoZXNpcywgbWl0b3NpcyBhbmQgY2VsbCBkaXZpc2lvbiBhbmQgaGFsdHMgaW4gdGhlIG5leHQgY2VsbCBjeWNsZS4gSWYgY3ljbG9oZXhpbWlkZSBpcyBsYXRlciByZW1vdmVkIGZyb20gdGhlIGN1bHR1cmUgbWVkaXVtLCB0cmVhdGVkIGNlbGxzIHdpbGwgcmV0dXJuIHRvIHRoZSBkaXZpc2lvbiBjeWNsZSwgc2hvd2luZyBhIGNvbXBsZXggcGF0dGVybiBvZiBkaXZpc2lvbiB0aW1lcyBwb3N0LXRyZWF0bWVudCwgYXMgZmlyc3QgbWVhc3VyZWQgYnkgWmV0dGVyYmVyZyBhbmQgY29sbGVhZ3Vlcy4gV2Ugc2ltdWxhdGUgdGhlc2UgcGh5c2lvbG9naWNhbCByZXNwb25zZXMgb2YgbWFtbWFsaWFuIGNlbGxzIHRvIHRyYW5zaWVudCBpbmhpYml0aW9uIG9mIGdyb3d0aCwgdXNpbmcgYSBzZXQgb2Ygbm9ubGluZWFyIGRpZmZlcmVudGlhbCBlcXVhdGlvbnMgYmFzZWQgb24gYSByZWFsaXN0aWMgbW9kZWwgb2YgdGhlIG1vbGVjdWxhciBldmVudHMgdW5kZXJseWluZyBwcm9ncmVzc2lvbiB0aHJvdWdoIHRoZSBjZWxsIGN5Y2xlLiBUaGUgbW9kZWwgcmVsaWVzIG9uIG91ciBlYXJsaWVyIHdvcmsgb24gdGhlIHJlZ3VsYXRpb24gb2YgY3ljbGluLWRlcGVuZGVudCBwcm90ZWluIGtpbmFzZXMgZHVyaW5nIHRoZSBjZWxsIGRpdmlzaW9uIGN5Y2xlIG9mIHllYXN0LiBUaGUgeWVhc3QgbW9kZWwgaXMgc3VwcGxlbWVudGVkIHdpdGggZXF1YXRpb25zIGRlc2NyaWJpbmcgdGhlIGVmZmVjdHMgb2YgcmV0aW5vYmxhc3RvbWEgcHJvdGVpbiBvbiBjZWxsIGdyb3d0aCBhbmQgdGhlIHN5bnRoZXNpcyBvZiBjeWNsaW5zIEEgYW5kIEUsIGFuZCB3aXRoIGEgcHJpbWl0aXZlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBzaWduYWxpbmcgcGF0aHdheSB0aGF0IGNvbnRyb2xzIHN5bnRoZXNpcyBvZiBjeWNsaW4gRC4iLAogICAgInllYXIiIDogMjAwNCwKICAgICJtb250aCIgOiAiMTAiLAogICAgInZvbHVtZSIgOiAiMjMwIiwKICAgICJpc3N1ZSIgOiAiNCIsCiAgICAicGFnZXMiIDogIjU2My01NzkiLAogICAgImxpbmsiIDogImh0dHA6Ly9pZGVudGlmaWVycy5vcmcvcHVibWVkLzE1MzYzNjc2IiwKICAgICJhdXRob3JzIiA6IFsgewogICAgICAibmFtZSIgOiAiQmVsYSBOb3ZhayIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiVW5pdmVyc2l0eSBvZiBPeGZvcmQiLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi02OTYxLTEzNjYiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJUeXNvbiBKSiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAxLTc1NjAtNjAxMyIKICAgIH0gXQogIH0sCiAgImZpbGVzIiA6IHsKICAgICJtYWluIiA6IFsgewogICAgICAibmFtZSIgOiAiTm92YWsyMDA0LnhtbCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMTk3MjQ0IgogICAgfSBdLAogICAgImFkZGl0aW9uYWwiIDogWyB7CiAgICAgICJuYW1lIiA6ICJOb3ZhazIwMDQuY3BzIiwKICAgICAgImZpbGVTaXplIiA6ICIzNDI0MjgiLAogICAgICAiZGVzY3JpcHRpb24iIDogIkNPUEFTSSB2ZXJzaW9uIDQuMjcgKEJ1aWxkIDIxNykgTm92w6FrMjAwNC1BIE1vZGVsIGZvciBSZXN0cmljdGlvbiBQb2ludCBDb250cm9sIG9mIHRoZSBNYW1tYWxpYW4gQ2VsbCBDeWNsZSIKICAgIH0gXQogIH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogMiwKICAgICAgInN1Ym1pdHRlZCIgOiAxNTkxNjI1NjU5MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJBaG1hZCBaeW91ZCIsCiAgICAgICJjb21tZW50IiA6ICJFZGl0ZWQgbW9kZWwgbWV0YWRhdGEgb25saW5lLiIKICAgIH0sIHsKICAgICAgInZlcnNpb24iIDogMywKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA2MTk0OTQ4MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJUdW5nIE5ndXllbiIsCiAgICAgICJjb21tZW50IiA6ICJSZW1vdmVkIHRoZSBhY3V0ZSBhY2NlbnRzIGluIHRoZSBmaWxlIG5hbWVzLiBJdCBkb2Vzbid0IHN1cHBvcnQgaW4gQmlvTW9kZWxzLiBUaGVyZSBpcyBhIGJ1ZyB3aGVuIGRvd25sb2FkaW5nIGZpbGVzLiIKICAgIH0sIHsKICAgICAgInZlcnNpb24iIDogNCwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA2MTk1ODE2MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJUdW5nIE5ndXllbiIsCiAgICAgICJjb21tZW50IiA6ICJSZW1vdmVkIGJxYmlvbDppc0Rlc2NyaWJlZEJ5IGZyb20gdGhlIFB1Yk1lZCBhbm5vdGF0aW9uLiBUaGUgcHVibGljYXRpb24gYW5ub3RhdGlvbnMgYXJlIHJlY29tbWVuZGVkIGdvaW5nIHdpdGggdGhlIGJxbW9kZWw6aXNEZXNjcmliZWRCeSB2b2NhYnVsYXJ5LiIKICAgIH0gXQogIH0sCiAgImZpcnN0UHVibGlzaGVkIiA6IDE3MDYxOTU3OTQwMDAsCiAgInN1Ym1pc3Npb25JZCIgOiAiTU9ERUwyMDA2MDgwMDAxIgp9 + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:22 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/BIOMD0000001079 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=60ae6261-530f-4967-b448-adf282ea72b6; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.054.16427.278419; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: |- + { + "name" : "DeBoeck2021 - Modular approach to modeling the cell cycle, simple cell cycle model", + "description" : "\n \n

    Models the production and degradation of cyclin B that drives the early embryonic cell cycle.\nCyclin B is degraded by APC/C. The activity of APC/C is modeled not through biochemical interactions, but through a 'functional response curve'. This can be ultrasensitive (with the parameter alpha=0). in this case the system does not oscillate. Importantly the response can be made bistable and the form of the bistability can be easily prescribed. With a bistable response, the system oscillates.\n\nThe uploaded file corresponds to the model used for Figs.3H, I in the publication.

    \n \n
    ", + "format" : { + "name" : "SBML", + "version" : "L2V4" + }, + "publication" : { + "journal" : "PLoS computational biology", + "title" : "A modular approach for modeling the cell cycle based on functional response curves.", + "affiliation" : "Laboratory of Dynamics in Biological Systems, Department of Cellular and Molecular Medicine, University of Leuven, Leuven, Belgium.", + "synopsis" : "Modeling biochemical reactions by means of differential equations often results in systems with a large number of variables and parameters. As this might complicate the interpretation and generalization of the obtained results, it is often desirable to reduce the complexity of the model. One way to accomplish this is by replacing the detailed reaction mechanisms of certain modules in the model by a mathematical expression that qualitatively describes the dynamical behavior of these modules. Such an approach has been widely adopted for ultrasensitive responses, for which underlying reaction mechanisms are often replaced by a single Hill function. Also time delays are usually accounted for by using an explicit delay in delay differential equations. In contrast, however, S-shaped response curves, which by definition have multiple output values for certain input values and are often encountered in bistable systems, are not easily modeled in such an explicit way. Here, we extend the classical Hill function into a mathematical expression that can be used to describe both ultrasensitive and S-shaped responses. We show how three ubiquitous modules (ultrasensitive responses, S-shaped responses and time delays) can be combined in different configurations and explore the dynamics of these systems. As an example, we apply our strategy to set up a model of the cell cycle consisting of multiple bistable switches, which can incorporate events such as DNA damage and coupling to the circadian clock in a phenomenological way.", + "year" : 2021, + "month" : "8", + "volume" : "17", + "issue" : "8", + "pages" : "e1009008", + "link" : "http://identifiers.org/doi/10.1371/journal.pcbi.1009008", + "authors" : [ { + "name" : "De Boeck J", + "orcid" : "0000-0001-6180-3323" + }, { + "name" : "Jan Rombouts", + "institution" : "EMBL Heidelberg", + "orcid" : "0000-0003-4135-0771" + }, { + "name" : "Gelens L", + "orcid" : "0000-0001-7290-9561" + } ] + }, + "files" : { + "main" : [ { + "name" : "DeBoeck2021_cellcycle_bistableapc.xml", + "fileSize" : "17441" + } ], + "additional" : [ { + "name" : "DeBoeck2021_cellcycle_bistableapc.sedml", + "fileSize" : "4466", + "description" : "SEDML file for the curated model" + }, { + "name" : "DeBoeck2021_cellcycle_bistableapc.cps", + "fileSize" : "52751", + "description" : "COPASI file for the curated model" + }, { + "name" : "cellcycle_bistableapc.xml", + "fileSize" : "5769", + "description" : "2 ODE Cell cycle model with Cdk1-Cyclin B and APC/C, APC/C implemented as bistable module" + } ] + }, + "history" : { + "revisions" : [ { + "version" : 2, + "submitted" : 1670321462000, + "submitter" : "Jan Rombouts", + "comment" : "Parameter change + indicated the corresponding figure in the publication." + }, { + "version" : 3, + "submitted" : 1705966766000, + "submitter" : "Tung Nguyen", + "comment" : "Made a minor update on the model description to trigger the automation of adding publication and model identifier annotation." + }, { + "version" : 5, + "submitted" : 1706523391000, + "submitter" : "Krishna Kumar Tiwari", + "comment" : "Automatically added model identifier BIOMD0000001079" + } ] + }, + "firstPublished" : 1706523397000, + "submissionId" : "MODEL2212060001", + "publicationId" : "BIOMD0000001079" + } + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2203250001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=c3d0af84-c923-4b74-bf2a-b23127124eeb; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.157.16423.959933; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJVc3RpbGFnbyBtYXlkaXMgTWV0YWJvbGljIENoYXJhY3Rlcml6YXRpb24gYW5kIEdyb3d0aCBRdWFudGlmaWNhdGlvbiB3aXRoIGEgR2Vub21lLVNjYWxlIE1ldGFib2xpYyBNb2RlbCIsCiAgImRlc2NyaXB0aW9uIiA6ICJVc3RpbGFnbyBtYXlkaXMgaXMgYW4gaW1wb3J0YW50IHBsYW50IHBhdGhvZ2VuIGNhdXNpbmcgY29ybi1zbXV0IGRpc2Vhc2UgYW5kIGFuIGVmZmVjdGl2ZSBiaW90ZWNobm9sb2dpY2FsIHByb2R1Y3Rpb24gaG9zdC4gVGhlIGxhY2sgb2YgYSBjb21wcmVoZW5zaXZlIG1ldGFib2xpYyBvdmVydmlldyBoaW5kZXJzIGEgZnVsbCB1bmRlcnN0YW5kaW5nIG9mIHRoZSBvcmdhbmlzbeKAmXMgZW52aXJvbm1lbnRhbCBhZGFwdGF0aW9uIGFuZCBhIGZ1bGwgdXNlIG9mIGl0cyBtZXRhYm9saWMgcG90ZW50aWFsLiBIZXJlLCB3ZSByZXBvcnQgdGhlIGZpcnN0IGdlbm9tZSBzY2FsZSBtZXRhYm9saWMgbW9kZWwgKEdTTU0pIG9mIFVzdGlsYWdvIG1heWRpcyAoaVVtYTIyKSBmb3IgdGhlIHNpbXVsYXRpb24gb2YgbWV0YWJvbGljIGFjdGl2aXRpZXMuIGlVbWEyMiB3YXMgcmVjb25zdHJ1Y3RlZCBmcm9tIHNlcXVlbmNpbmcgYW5kIGFubm90YXRpb24gdXNpbmcgUGF0aHdheVRvb2xzLCB0aGUgYmlvbWFzcyBlcXVhdGlvbiB3YXMgZGVyaXZlZCBmcm9tIGxpdGVyYXR1cmUgdmFsdWVzIGFuZCBmcm9tIHRoZSBjb2RvbiBjb21wb3NpdGlvbi4gVGhlIGZpbmFsIG1vZGVsIGNvbnRhaW5zIG92ZXIgMjUlIG9mIGFubm90YXRlZCBnZW5lcyAoNiw5MDkpIGluIHRoZSBzZXF1ZW5jZWQgZ2Vub21lLiBTdWJzdHJhdGUgdXRpbGl6YXRpb24gd2FzIGNvcnJlY3RlZCBieSBCaW9sb2ctUGhlbm90eXBlIGFycmF5cyBhbmQgZXhwb25lbnRpYWwgYmF0Y2ggY3VsdGl2YXRpb25zIHdlcmUgdXNlZCB0byB0ZXN0IGdyb3d0aCBwcmVkaWN0aW9ucy4gVGhlIGdyb3d0aCBkYXRhIHJldmVhbGVkIGEgbWV0YWJvbGljIHBoZW5vdHlwZSBzaGlmdCBhdCBoaWdoIGdsdWNvc2UgdXB0YWtlIHJhdGVzIGFuZCB0aGUgbW9kZWwgYWxsb3dlZCBpdHMgcXVhbnRpZmljYXRpb24uIEEgcGFuLWdlbm9tZSBvZiBmb3VyIGRpZmZlcmVudCBVLiBtYXlkaXMgc3RyYWlucyByZXZlYWxlZCBtaXNzaW5nIG1ldGFib2xpYyBwYXRod2F5cyBpbiBpVW1hMjIuIFRoZSBuZXcgbW9kZWwgYWxsb3dzIHN0dWRpZXMgb2YgbWV0YWJvbGljIGFkYXB0YXRpb25zIHRvIGRpZmZlcmVudCBlbnZpcm9ubWVudGFsIG5pY2hlcyBhcyB3ZWxsIGFzIGZvciBiaW90ZWNobm9sb2dpY2FsIGFwcGxpY2F0aW9ucy4iLAogICJmb3JtYXQiIDogewogICAgIm5hbWUiIDogIlNCTUwiLAogICAgInZlcnNpb24iIDogIkwzVjEiCiAgfSwKICAicHVibGljYXRpb24iIDogewogICAgImpvdXJuYWwiIDogImJpb1J4aXYiLAogICAgInRpdGxlIiA6ICJBIEdlbm9tZS1TY2FsZSBNZXRhYm9saWMgTW9kZWwgZm9yIHRoZSBTbXV0LUZ1bmd1cyBVc3RpbGFnbyBtYXlkaXMiLAogICAgImFmZmlsaWF0aW9uIiA6ICJpQU1CLCBSV1RIIEFhY2hlbiIsCiAgICAic3lub3BzaXMiIDogIlVzdGlsYWdvIG1heWRpcyBpcyBhbiBpbXBvcnRhbnQgcGxhbnQgcGF0aG9nZW4gY2F1c2luZyBjb3JuLXNtdXQgZGlzZWFzZSBhbmQgYW4gZWZmZWN0aXZlIGJpb3RlY2hub2xvZ2ljYWwgcHJvZHVjdGlvbiBob3N0LiBUaGUgbGFjayBvZiBhIGNvbXByZWhlbnNpdmUgbWV0YWJvbGljIG92ZXJ2aWV3IGhpbmRlcnMgYSBmdWxsIHVuZGVyc3RhbmRpbmcgb2YgZW52aXJvbm1lbnRhbCBhZGFwdGF0aW9uIGFuZCBhIGZ1bGwgdXNlIG9mIHRoZSBvcmdhbmlzbeKAmXMgbWV0YWJvbGljIHBvdGVudGlhbC4gSGVyZSwgd2UgcmVwb3J0IHRoZSBmaXJzdCBnZW5vbWUgc2NhbGUgbWV0YWJvbGljIG1vZGVsIChHU01NKSBvZiBVc3RpbGFnbyBtYXlkaXMgKGlVbWEyMikgZm9yIHRoZSBzaW11bGF0aW9uIG9mIG1ldGFib2xpYyBhY3Rpdml0aWVzLiBpVW1hMjIgd2FzIHJlY29uc3RydWN0ZWQgZnJvbSBzZXF1ZW5jaW5nIGFuZCBhbm5vdGF0aW9uIHVzaW5nIFBhdGh3YXlUb29scywgdGhlIGJpb21hc3MgZXF1YXRpb24gd2FzIGRlcml2ZWQgZnJvbSBsaXRlcmF0dXJlIHZhbHVlcyBhbmQgZnJvbSB0aGUgY29kb24gY29tcG9zaXRpb24uIFRoZSBmaW5hbCBtb2RlbCBjb250YWlucyBvdmVyIDI1JSBvZiBhbm5vdGF0ZWQgZ2VuZXMgaW4gdGhlIHNlcXVlbmNlZCBnZW5vbWUuIFN1YnN0cmF0ZSB1dGlsaXphdGlvbiB3YXMgY29ycmVjdGVkIGJ5IEJpb2xvZy1QaGVub3R5cGUgYXJyYXlzIGFuZCBleHBvbmVudGlhbCBiYXRjaCBjdWx0aXZhdGlvbnMgd2VyZSB1c2VkIHRvIHRlc3QgZ3Jvd3RoIHByZWRpY3Rpb25zLiBBIHBhbi1nZW5vbWUgb2YgZm91ciBkaWZmZXJlbnQgVS4gbWF5ZGlzIHN0cmFpbnMgcmV2ZWFsZWQgbWlzc2luZyBtZXRhYm9saWMgcGF0aHdheXMgaW4gaVVtYTIyLiBUaGUgbWFqb3JpdHkgb2YgbWV0YWJvbGljIGRpZmZlcmVuY2VzIGJldHdlZW4gaVVtYTIyIGFuZCB0aGUgcGFuZ2Vub21lIG9jY3VycyBpbiB0aGUgaW5vc2l0b2wsIHB1cmluZSBhbmQgc3RhcmNoIG1ldGFib2xpYyBwYXRod2F5cy4gVGhlIG5ldyBtb2RlbCBhbGxvd3Mgc3R1ZGllcyBvZiBtZXRhYm9saWMgYWRhcHRhdGlvbnMgdG8gZGlmZmVyZW50IGVudmlyb25tZW50YWwgbmljaGVzIGFzIHdlbGwgYXMgZm9yIGJpb3RlY2hub2xvZ2ljYWwgYXBwbGljYXRpb25zLiIsCiAgICAieWVhciIgOiAyMDIyLAogICAgIm1vbnRoIiA6ICIzIiwKICAgICJsaW5rIiA6ICJodHRwOi8vaWRlbnRpZmllcnMub3JnL2RvaS8xMC4xMTAxLzIwMjIuMDMuMDMuNDgyNzgwIiwKICAgICJhdXRob3JzIiA6IFsgewogICAgICAibmFtZSIgOiAiVWxmIFcuIExpZWJhbCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkxlbmEgVWxsbWFubiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkNocmlzdGlhbiBMaWV2ZW4iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJQaGlsaXBwIEtvaGwiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJEYW5pZWwgV2liYmVyZyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlRoaWVtbyBaYW1iYW5pbmkiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJMYXJzIE0uIEJsYW5rIgogICAgfSBdCiAgfSwKICAiZmlsZXMiIDogewogICAgIm1haW4iIDogWyB7CiAgICAgICJuYW1lIiA6ICJpVW1hMjIueG1sIiwKICAgICAgImZpbGVTaXplIiA6ICI1MTIwOTI3IgogICAgfSBdLAogICAgImFkZGl0aW9uYWwiIDogWyB7CiAgICAgICJuYW1lIiA6ICJpVW1hMjJfRlJPRy56aXAiLAogICAgICAiZmlsZVNpemUiIDogIjE2MCIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiRlJPRy1GaWxlcyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogImlVbWEyMi5qc29uIiwKICAgICAgImZpbGVTaXplIiA6ICIxMTkxMDI5IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJpVW1hMjIgSlNPTiBtb2RlbCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogImlVbWEyMi5tYXQiLAogICAgICAiZmlsZVNpemUiIDogIjE5MzE2MDE2IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJpVW1hMjIgbWF0LUZpbGUiCiAgICB9IF0KICB9LAogICJoaXN0b3J5IiA6IHsKICAgICJyZXZpc2lvbnMiIDogWyB7CiAgICAgICJ2ZXJzaW9uIiA6IDIsCiAgICAgICJzdWJtaXR0ZWQiIDogMTY0OTA3NjI5OTAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAiVWxmIExpZWJhbCIsCiAgICAgICJjb21tZW50IiA6ICJNb2RlbCByZXZpc2VkIHdpdGhvdXQgY29tbWl0IG1lc3NhZ2UiCiAgICB9IF0KICB9LAogICJmaXJzdFB1Ymxpc2hlZCIgOiAxNjUwOTE4NDA4MDAwLAogICJzdWJtaXNzaW9uSWQiIDogIk1PREVMMjIwMzI1MDAwMSIKfQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2211290001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=312fe359-da42-4999-940d-8ce42128808b; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.279.16429.539356; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJDYXNpbmkyMDIyIC0gZ2Vub21lIHNjYWxlIG1ldGFib2xpYyBtb2RlbCAoaU1URDIySUMpIG9mIE1ldGhhbm90aGVybW9iYWN0ZXIgdGhlcm1hdXRvdHJvcGhpY3VzIERlbHRhIEgiLAogICJkZXNjcmlwdGlvbiIgOiAiKGlNVEQyMklDKVxuXG5UaGlzIG1vZGVsIGlzIGRlc2NyaWJlZCBpbiB0aGVzZSBhcnRpY2xlczpcblxuQ2FzaW5pLCBJLiwgTWNDdWJiaW4sIFQuLCBFc3F1aXZlbC1FbGl6b25kbywgUy4sIEx1cXVlLCBHLkcuLCBFdnNlZXZhLCBELiwgRmluaywgQy4sIEJlYmxhd3ksIFMuLCBZb3VuZ2JsdXQsIE4uRC4sIEFyaXN0aWxkZSwgTC4sIEh1c29uLCBELiwgRHJhZ2VyLCBBLiwgTGV5LCBSLiwgTWFyY2VsbGluLCBFLiwgQW5nZW5lbnQsIEwuVC4sIE1vbGl0b3IsIEIuIDIwMjMuIEFuIGludGVncmF0ZWQgc3lzdGVtcyBiaW9sb2d5IGFwcHJvYWNoIHJldmVhbHMgZGlmZmVyZW5jZXMgaW4gZm9ybWF0ZSBtZXRhYm9saXNtIGluIHRoZSBnZW51cyBNZXRoYW5vdGhlcm1vYmFjdGVyLiBJc2NpZW5jZSwgMjYoMTApLlxuXG5DYXNpbmksIEkuLCBNY0N1YmJpbiwgVC4sIEVzcXVpdmVsLUVsaXpvbmRvLCBTLiwgTHVxdWUsIEcuRy4sIEV2c2VldmEsIEQuLCBGaW5rLCBDLiwgQmVibGF3eSwgUy4sIFlvdW5nYmx1dCwgTi5ELiwgQXJpc3RpbGRlLCBMLiwgSHVzb24sIEQuLCBEcmFnZXIsIEEuLCBMZXksIFIuLCBNYXJjZWxsaW4sIEUuLCBBbmdlbmVudCwgTC5ULiwgTW9saXRvciwgQi4gMjAyMi4gQW4gaW50ZWdyYXRlZCBzeXN0ZW1zLWJpb2xvZ3kgcGxhdGZvcm0gZm9yIHBvd2VyLXRvLWdhcyB0ZWNobm9sb2d5LiBiaW9SeGl2LCAyMDIyLjEyLjMwLjUyMjIzNi4gXG5cbk1ldGhhbm9nZW5lc2lzIGFsbG93cyBtZXRoYW5vZ2VuaWMgYXJjaGFlYSAobWV0aGFub2dlbnMpIHRvIGdlbmVyYXRlIGNlbGx1bGFyIGVuZXJneSBmb3IgdGhlaXIgZ3Jvd3RoIHdoaWxlIHByb2R1Y2luZyBtZXRoYW5lLiBIeWRyb2dlbm90cm9waGljIG1ldGhhbm9nZW5zIHRocml2ZSBvbiBjYXJib24gZGlveGlkZSBhbmQgbW9sZWN1bGFyIGh5ZHJvZ2VuIGFzIHNvbGUgY2FyYm9uIGFuZCBlbmVyZ3kgc291cmNlcy4gVGhlcm1vcGhpbGljIGFuZCBoeWRyb2dlbm90cm9waGljIE1ldGhhbm90aGVybW9iYWN0ZXIgc3BwLiBoYXZlIGJlZW4gcmVjb2duaXplZCBhcyByb2J1c3QgYmlvY2F0YWx5c3RzIGZvciBhIGNpcmN1bGFyIGNhcmJvbiBlY29ub215IGFuZCBhcmUgbm93IGFwcGxpZWQgaW4gcG93ZXItdG8tZ2FzIHRlY2hub2xvZ3kuIEhlcmUsIHdlIGdlbmVyYXRlZCB0aGUgZmlyc3QgbWFudWFsbHkgY3VyYXRlZCBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIHJlY29uc3RydWN0aW9uIGZvciB0aHJlZSBNZXRoYW5vdGhlcm1vYmFjdGVyIHNwcC4uIFdlIGludmVzdGlnYXRlZCBkaWZmZXJlbmNlcyBpbiBncm93dGggcGVyZm9ybWFuY2Ugb2YgdGhyZWUgd2lsZC10eXBlIHN0cmFpbnMgYW5kIG9uZSBnZW5ldGljYWxseSBlbmdpbmVlcmVkIHN0cmFpbiBpbiB0d28gaW5kZXBlbmRlbnQgY2hlbW9zdGF0IGJpb3JlYWN0b3IgZXhwZXJpbWVudHMsIGZpcnN0LCB3aXRoIG1vbGVjdWxhciBoeWRyb2dlbiBhbmQgY2FyYm9uIGRpb3hpZGUsIGFuZCBzZWNvbmQsIHdpdGggc29kaXVtIGZvcm1hdGUuIEluIHRoZSBmaXJzdCBleHBlcmltZW50LCB3ZSBmb3VuZCB0aGUgaGlnaGVzdCBtZXRoYW5lIHByb2R1Y3Rpb24gcmF0ZSBmb3IgTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgzpRILCB3aGlsZSBNZXRoYW5vdGhlcm1vYmFjdGVyIG1hcmJ1cmdlbnNpcyBNYXJidXJnIHJlYWNoZWQgdGhlIGhpZ2hlc3QgYmlvbWFzcyBncm93dGggcmF0ZS4gVHJhbnNjcmlwdG9taWNzIGFuZCBwcm90ZW9taWNzIGRhdGEgc2V0cyBmcm9tIHRoZXNlIHN0ZWFkeS1zdGF0ZSBiaW9yZWFjdG9ycywgaW4gY29tYmluYXRpb24gd2l0aCB0aGUgaW1wbGVtZW50YXRpb24gb2YgYSBwYW4tbW9kZWwgdGhhdCBjb250YWlucyBjb21iaW5lZCByZWFjdGlvbnMgZnJvbSBhbGwgdGhyZWUgbWljcm9iZXMsIGFsbG93ZWQgdXMgdG8gcGVyZm9ybSBhbiBpbnRlcnNwZWNpZXMgY29tcGFyaXNvbi4gV2hpbGUgdGhlIG9ic2VydmVkIGRpZmZlcmVuY2VzIGluIHRoZSBncm93dGggYmVoYXZpb3IgY2Fubm90IGJlIGZ1bGx5IGV4cGxhaW5lZCwgdGhlIGNvbXBhcmlzb24gZW5hYmxlZCB1cyB0byBpZGVudGlmeSBjcnVjaWFsIGRpZmZlcmVuY2VzIGluIGdyb3d0aC1yZWxhdGVkIHBhdGh3YXlzIHN1Y2ggYXMgZm9ybWF0ZSBhbmFib2xpc20uIEluIHRoZSBzZWNvbmQgZXhwZXJpbWVudCwgd2UgZm91bmQgc3RhYmxlIGdyb3d0aCB3aXRoIGEgTS4gdGhlcm1hdXRvdHJvcGhpY3VzIM6USCBwbGFzbWlkLWNhcnJ5aW5nIHN0cmFpbiBvbiBmb3JtYXRlIHdpdGggc2ltaWxhciBwZXJmb3JtYW5jZSBwYXJhbWV0ZXJzIGNvbXBhcmVkIHRvIHdpbGQtdHlwZSBNZXRoYW5vdGhlcm1vYmFjdGVyIHRoZXJtYXV0b3Ryb3BoaWN1cyBaLTI0NS4gVGhlIHJlc3VsdHMgb2YgdGhlIHR3byBzdHVkaWVzIGRlbW9uc3RyYXRlIHRoZSBhZHZhbnRhZ2VzIG9mIGFuIGludGVncmF0aXZlIGFwcHJvYWNoIGNvbWJpbmluZyBmZXJtZW50YXRpb24gYW5kIG9taWNzIGRhdGEgd2l0aCBnZW5vbWUtc2NhbGUgbW9kZWxpbmcgdG8gcmV2ZWFsIGtub3dsZWRnZSBnYXBzIG9mIGFyY2hhZWFsIG1ldGFib2xpc21zIGFuZCB0aGUgYmlvdGVjaG5vbG9naWNhbCBwb3RlbnRpYWwgb2YgTWV0aGFub3RoZXJtb2JhY3RlciBzcHAuIGFzIHByb2R1Y3Rpb24gcGxhdGZvcm0gaG9zdHMuIiwKICAiZm9ybWF0IiA6IHsKICAgICJuYW1lIiA6ICJTQk1MIiwKICAgICJ2ZXJzaW9uIiA6ICJMM1YxIgogIH0sCiAgInB1YmxpY2F0aW9uIiA6IHsKICAgICJqb3VybmFsIiA6ICJpU2NpZW5jZSIsCiAgICAidGl0bGUiIDogIkFuIGludGVncmF0ZWQgc3lzdGVtcyBiaW9sb2d5IGFwcHJvYWNoIHJldmVhbHMgZGlmZmVyZW5jZXMgaW4gZm9ybWF0ZSBtZXRhYm9saXNtIGluIHRoZSBnZW51cyA8aT5NZXRoYW5vdGhlcm1vYmFjdGVyPC9pPi4iLAogICAgImFmZmlsaWF0aW9uIiA6ICJFbnZpcm9ubWVudGFsIEJpb3RlY2hub2xvZ3kgR3JvdXAsIERlcGFydG1lbnQgb2YgR2Vvc2NpZW5jZXMsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCBTY2huYXJyZW5iZXJnc3RyYcOfZSA5NC05NiwgNzIwNzYgVMO8YmluZ2VuLCBHZXJtYW55LiIsCiAgICAic3lub3BzaXMiIDogIk1ldGhhbm9nZW5lc2lzIGFsbG93cyBtZXRoYW5vZ2VuaWMgYXJjaGFlYSB0byBnZW5lcmF0ZSBjZWxsdWxhciBlbmVyZ3kgZm9yIHRoZWlyIGdyb3d0aCB3aGlsZSBwcm9kdWNpbmcgbWV0aGFuZS4gVGhlcm1vcGhpbGljIGh5ZHJvZ2Vub3Ryb3BoaWMgc3BlY2llcyBvZiB0aGUgZ2VudXMgPGk+TWV0aGFub3RoZXJtb2JhY3RlcjwvaT4gaGF2ZSBiZWVuIHJlY29nbml6ZWQgYXMgcm9idXN0IGJpb2NhdGFseXN0cyBmb3IgYSBjaXJjdWxhciBjYXJib24gZWNvbm9teSBhbmQgYXJlIGFscmVhZHkgYXBwbGllZCBpbiBwb3dlci10by1nYXMgdGVjaG5vbG9neSB3aXRoIGJpb21ldGhhbmF0aW9uLCB3aGljaCBpcyBhIHBsYXRmb3JtIHRvIHN0b3JlIHJlbmV3YWJsZSBlbmVyZ3kgYW5kIHV0aWxpemUgY2FwdHVyZWQgY2FyYm9uIGRpb3hpZGUuIEhlcmUsIHdlIGdlbmVyYXRlZCBjdXJhdGVkIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgcmVjb25zdHJ1Y3Rpb25zIGZvciB0aHJlZSA8aT5NZXRoYW5vdGhlcm1vYmFjdGVyPC9pPiBzdHJhaW5zIGFuZCBpbnZlc3RpZ2F0ZWQgZGlmZmVyZW5jZXMgaW4gdGhlIGdyb3d0aCBwZXJmb3JtYW5jZSBvZiB0aGVzZSBzYW1lIHN0cmFpbnMgaW4gY2hlbW9zdGF0IGJpb3JlYWN0b3IgZXhwZXJpbWVudHMgd2l0aCBoeWRyb2dlbiBhbmQgY2FyYm9uIGRpb3hpZGUgb3IgZm9ybWF0ZSBhcyBzdWJzdHJhdGVzLiBVc2luZyBhbiBpbnRlZ3JhdGVkIHN5c3RlbXMgYmlvbG9neSBhcHByb2FjaCwgd2UgaWRlbnRpZmllZCBkaWZmZXJlbmNlcyBpbiBmb3JtYXRlIGFuYWJvbGlzbSBiZXR3ZWVuIHRoZSBzdHJhaW5zIGFuZCByZXZlYWxlZCB0aGF0IGZvcm1hdGUgYW5hYm9saXNtIGluZmx1ZW5jZXMgdGhlIGRpdmVyc2lvbiBvZiBjYXJib24gYmV0d2VlbiBiaW9tYXNzIGFuZCBtZXRoYW5lLiBUaGlzIGZpbmRpbmcsIHRvZ2V0aGVyIHdpdGggdGhlIG9taWNzIGRhdGFzZXRzIGFuZCB0aGUgbWV0YWJvbGljIG1vZGVscyB3ZSBnZW5lcmF0ZWQsIGNhbiBiZSBpbXBsZW1lbnRlZCBmb3IgYmlvdGVjaG5vbG9naWNhbCBhcHBsaWNhdGlvbnMgb2YgPGk+TWV0aGFub3RoZXJtb2JhY3RlcjwvaT4gaW4gcG93ZXItdG8tZ2FzIHRlY2hub2xvZ3ksIGFuZCBhcyBhIHBlcnNwZWN0aXZlLCBmb3IgdmFsdWUtYWRkZWQgY2hlbWljYWwgcHJvZHVjdGlvbi4iLAogICAgInllYXIiIDogMjAyMywKICAgICJtb250aCIgOiAiMTAiLAogICAgInZvbHVtZSIgOiAiMjYiLAogICAgImlzc3VlIiA6ICIxMCIsCiAgICAicGFnZXMiIDogIjEwODAxNiIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9kb2kvMTAuMTAxNi9qLmlzY2kuMjAyMy4xMDgwMTYiLAogICAgImF1dGhvcnMiIDogWyB7CiAgICAgICJuYW1lIiA6ICJJc2FiZWxsYSBDYXNpbmkiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJUaW0gTWNDdWJiaW4iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJTb2ZpYSBFc3F1aXZlbC1FbGl6b25kbyIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkd1aWxsZXJtbyBHIEx1cXVlIgogICAgfSwgewogICAgICAibmFtZSIgOiAiRGFyaWEgRXZzZWV2YSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkNocmlzdGlhbiBGaW5rIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU2ViYXN0aWFuIEJlYmxhd3kiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJOaWNob2xhcyBEIFlvdW5nYmx1dCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkx1ZG1pbGxhIEFyaXN0aWxkZSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkRhbmllbCBIIEh1c29uIgogICAgfSwgewogICAgICAibmFtZSIgOiAiQW5kcmVhcyBEcsOkZ2VyIgogICAgfSwgewogICAgICAibmFtZSIgOiAiUnV0aCBFIExleSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkVzdGViYW4gTWFyY2VsbGluIgogICAgfSwgewogICAgICAibmFtZSIgOiAiTGFyZ3VzIFQgQW5nZW5lbnQiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJCYXN0aWFuIE1vbGl0b3IiCiAgICB9IF0KICB9LAogICJmaWxlcyIgOiB7CiAgICAibWFpbiIgOiBbIHsKICAgICAgIm5hbWUiIDogIkNhc2luaTIwMjJfaU1URDIySUNfdW5ib3VuZC54bWwiLAogICAgICAiZmlsZVNpemUiIDogIjE3OTAwNzMiCiAgICB9IF0sCiAgICAiYWRkaXRpb25hbCIgOiBbIHsKICAgICAgIm5hbWUiIDogIkRIX2JvdW5kX0ZST0cub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMjAxMjY4IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJGUk9HIGFuYWx5c2lzIG9mIHRoZSBib3VuZCBpTVREMjJJQyAodXNpbmcgY29uZGl0aW9ucyBkZXNjcmliZWQgaW4gQ2FzaW5pIGV0IGFsLiAyMDJYKSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkZST0dfdW5ib3VuZF9jdXJhdGVkLm9tZXgiLAogICAgICAiZmlsZVNpemUiIDogIjE0NDA1OCIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiRlJPRyBSZXBvcnQgZ2VuZXJhdGVkIGJ5IEJpb01vZGVscyBjdXJhdG9ycyAtIFVuYm91bmQgdmVyc2lvbiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIm9tZXhfREgub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMzEyNjE3MCIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiQ2FzaW5pIGV0IGFsLiAyMDJYLCBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIChpTVREMjJJQykgb2YgTWV0aGFub3RoZXJtb2JhY3RlciB0aGVybWF1dG90cm9waGljdXMgRGVsdGEgSCwgaW5jbHVkaW5nIHBhbi1tZXRoYW5vdGhlcm1vYmFjdGVyIHNwcC4gcmVjb25zdHJ1Y3Rpb24gKC54bHN4KSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIk1PREVMMjIxMTI5MDAwMV91bmJvdW5kLU1lbW90ZVJlcG9ydC5odG1sIiwKICAgICAgImZpbGVTaXplIiA6ICIzNzg4NTU5IiwKICAgICAgImRlc2NyaXB0aW9uIiA6ICJNRU1PVEUgUmVwb3J0IgogICAgfSwgewogICAgICAibmFtZSIgOiAiREhfdW5ib3VuZF9GUk9HLm9tZXgiLAogICAgICAiZmlsZVNpemUiIDogIjIwMDU1NyIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiRlJPRyBhbmFseXNpcyBvZiB0aGUgdW5ib3VuZCBpTVREMjJJQyIKICAgIH0gXQogIH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogNCwKICAgICAgInN1Ym1pdHRlZCIgOiAxNjcyNDY5NTM4MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJJc2FiZWxsYSBDYXNpbmkiLAogICAgICAiY29tbWVudCIgOiAiVXBkYXRlZCB0aGUgcHVibGljYXRpb24uIgogICAgfSwgewogICAgICAidmVyc2lvbiIgOiA2LAogICAgICAic3VibWl0dGVkIiA6IDE3MDI0ODM2MjcwMDAsCiAgICAgICJzdWJtaXR0ZXIiIDogIlR1bmcgTmd1eWVuIiwKICAgICAgImNvbW1lbnQiIDogIlVwZGF0ZWQgdGhlIG1vZGVsIHdpdGggdGhlIGN1cmF0ZWQgRlJPRyBhbmQgTUVNT1RFIHJlcG9ydC4iCiAgICB9LCB7CiAgICAgICJ2ZXJzaW9uIiA6IDcsCiAgICAgICJzdWJtaXR0ZWQiIDogMTcwMzk4MTU4MTAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAiSXNhYmVsbGEgQ2FzaW5pIiwKICAgICAgImNvbW1lbnQiIDogIkkgdXBkYXRlZCB0aGUgcHVibGljYXRpb24gdG8gdGhlIGZpbmFsIHB1YmxpY2F0aW9uIChub3QganVzdCB0aGUgcHJlcHJpbnQpLiIKICAgIH0gXQogIH0sCiAgImZpcnN0UHVibGlzaGVkIiA6IDE3MDQ5NzE2NDIwMDAsCiAgInN1Ym1pc3Npb25JZCIgOiAiTU9ERUwyMjExMjkwMDAxIgp9 + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2312220001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=97ceb8da-e850-4ed3-97af-6b4aa874e7c3; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.409.16423.546645; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJOaXRpbjIwMjMtIEtpbmV0aWMgbW9kZWwgb2YgY2VsbHVsYXIgbWV0YWJvbGlzbSIsCiAgImRlc2NyaXB0aW9uIiA6ICI8bm90ZXMgeG1sbnM9XCJodHRwOi8vd3d3LnNibWwub3JnL3NibWwvbGV2ZWwyL3ZlcnNpb240XCI+XG4gICAgICA8Ym9keSB4bWxucz1cImh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWxcIj5cbiAgICAgPHA+QSBzaW1wbGlzdGljIG51bWVyaWNhbCBtb2RlbCBkZXNpZ25lZCB0byBzaW11bGF0ZSBhbmQgYXVnbWVudCB0aGUgY29tbWVyY2lhbGx5IGF2YWlsYWJsZSByZWFsLXRpbWUsIGluLXZpdHJvLCBlbmQtcG9pbnQga2luZXRpYyBnbHljb2x5c2lzIGFzc2F5IHdpdGggYW5kIHdpdGhvdXQgcGF0aHdheS1tb2R1bGF0aW5nIGRydWdzIHRvIGV4dGVuZCB0aGUgaW5zaWdodCBpbnRvIHRoZSBjZWxsdWxhciBtZXRhYm9saXNtIGJ5IGVsdWNpZGF0aW5nIHRoZSB1bmRlcmx5aW5nIG1lY2hhbmlzbXMgbGVhZGluZyB0byB0aGUgcGF0aHdheSBlbmQtcHJvZHVjdC48L3A+XG4gIDwvYm9keT5cbiAgICA8L25vdGVzPiIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiU0JNTCIsCiAgICAidmVyc2lvbiIgOiAiTDJWNCIKICB9LAogICJwdWJsaWNhdGlvbiIgOiB7CiAgICAiam91cm5hbCIgOiAiRkVCUyBvcGVuIGJpbyIsCiAgICAidGl0bGUiIDogIktpbmV0aWMgbW9kZWxsaW5nIG9mIHRoZSBjZWxsdWxhciBtZXRhYm9saWMgcmVzcG9uc2VzIHVuZGVycGlubmluZyBpbiB2aXRybyBnbHljb2x5c2lzIGFzc2F5cy4iLAogICAgImFmZmlsaWF0aW9uIiA6ICJGT0NBUyBSZXNlYXJjaCBJbnN0aXR1dGUsIFRVIER1YmxpbiwgSXJlbGFuZC4iLAogICAgInN5bm9wc2lzIiA6ICJUaGlzIHN0dWR5IGFpbXMgdG8gZGVtb25zdHJhdGUgdGhlIGJlbmVmaXRzIG9mIGF1Z21lbnRpbmcgY29tbWVyY2lhbGx5IGF2YWlsYWJsZSwgcmVhbC10aW1lLCBpbuKAiXZpdHJvIGdseWNvbHlzaXMgYXNzYXlzIHdpdGggcGhlbm9tZW5vbG9naWNhbCByYXRlIGVxdWF0aW9uLWJhc2VkIGtpbmV0aWMgbW9kZWxzLCBkZXNjcmliaW5nIHRoZSBjb250cmlidXRpb25zIG9mIHRoZSB1bmRlcnBpbm5pbmcgbWV0YWJvbGljIHBhdGh3YXlzLiBUbyB0aGlzIGVuZCwgYSBjb21tZXJjaWFsbHkgYXZhaWxhYmxlIGdseWNvbHlzaXMgYXNzYXksIHNlbnNpdGl2ZSB0byBjaGFuZ2VzIGluIGV4dHJhY2VsbHVsYXIgYWNpZGlmaWNhdGlvbiAoZXh0cmFjZWxsdWxhciBwSCksIHdhcyB1c2VkIHRvIGRlcml2ZSB0aGUgZ2x5Y29seXNpcyBwYXRod2F5IGtpbmV0aWNzLiBUaGUgcGF0aHdheSB3YXMgbnVtZXJpY2FsbHkgbW9kZWxsZWQgdXNpbmcgYSBzZXJpZXMgb2Ygb3JkaW5hcnkgZGlmZmVyZW50aWFsIHJhdGUgZXF1YXRpb25zLCB0byBzaW11bGF0ZSB0aGUgb2J0YWluZWQgZXhwZXJpbWVudGFsIHJlc3VsdHMuIFRoZSBzZW5zaXRpdml0eSBvZiB0aGUgbW9kZWwgdG8gdGhlIGtleSBlcXVhdGlvbiBwYXJhbWV0ZXJzIHdhcyBhbHNvIGV4cGxvcmVkLiBUaGUgY2VsbHVsYXIgZ2x5Y29seXNpcyBwYXRod2F5IGtpbmV0aWNzIHdlcmUgZGV0ZXJtaW5lZCBmb3IgdGhyZWUgZGlmZmVyZW50IGNlbGwtbGluZXMsIHVuZGVyIG5vbm1vZHVsYXRlZCBhbmQgbW9kdWxhdGVkIGNvbmRpdGlvbnMuIE92ZXIgdGhlIHRpbWVzY2FsZSBzdHVkaWVkLCB0aGUgYXNzYXkgZGVtb25zdHJhdGVkIGEgdHdvLXBoYXNlIG1ldGFib2xpYyByZXNwb25zZSwgcmVwcmVzZW50aW5nIHRoZSBkaWZmZXJlbnRpYWwga2luZXRpY3Mgb2YgZ2x5Y29seXNpcyBwYXRod2F5IHJhdGUgYXMgYSBmdW5jdGlvbiBvZiB0aW1lLCBhbmQgdGhpcyBiZWhhdmlvdXIgd2FzIGZhaXRoZnVsbHkgcmVwcm9kdWNlZCBieSB0aGUgbW9kZWwgc2ltdWxhdGlvbnMuIFRoZSBtb2RlbCBlbmFibGVkIHF1YW50aXRhdGl2ZSBjb21wYXJpc29uIG9mIHRoZSBwYXRod2F5IGtpbmV0aWNzIG9mIHRocmVlIGNlbGwgbGluZXMsIGFuZCBhbHNvIHRoZSBtb2R1bGF0aW5nIGVmZmVjdCBvZiB0d28ga25vd24gZHJ1Z3MuIE1vcmVvdmVyLCB0aGUgbW9kZWxsaW5nIHRvb2wgYWxsb3dzIHRoZSBzdWJ0bGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBkaWZmZXJlbnQgY2VsbCBsaW5lcyB0byBiZSBiZXR0ZXIgZWx1Y2lkYXRlZCBhbmQgYWxzbyBhbGxvd3MgYXVnbWVudGF0aW9uIG9mIHRoZSBhc3NheSBzZW5zaXRpdml0eS4gQSBzaW1wbGlzdGljIG51bWVyaWNhbCBtb2RlbCBjYW4gZmFpdGhmdWxseSByZXByb2R1Y2UgdGhlIGRpZmZlcmVudGlhbCBwYXRod2F5IGtpbmV0aWNzIGZvciB0aHJlZSBkaWZmZXJlbnQgY2VsbCBsaW5lcywgd2l0aCBhbmQgd2l0aG91dCBwYXRod2F5LW1vZHVsYXRpbmcgZHJ1Z3MsIGFuZCBmdXJ0aGVybW9yZSBwcm92aWRlcyBpbnNpZ2h0cyBpbnRvIHRoZSBjZWxsdWxhciBtZXRhYm9saXNtIGJ5IGVsdWNpZGF0aW5nIHRoZSB1bmRlcmx5aW5nIG1lY2hhbmlzbXMgbGVhZGluZyB0byB0aGUgcGF0aHdheSBlbmQtcHJvZHVjdC4gVGhpcyBzdHVkeSBkZW1vbnN0cmF0ZXMgdGhhdCBhdWdtZW50aW5nIGEgcmVsYXRpdmVseSBzaW1wbGUsIHJlYWwtdGltZSwgaW7igIl2aXRybyBhc3NheSB3aXRoIGEgbW9kZWwgb2YgdGhlIHVuZGVycGlubmluZyBtZXRhYm9saWMgcGF0aHdheSBwcm92aWRlcyBjb25zaWRlcmFibGUgaW5zaWdodHMgaW50byB0aGUgb2JzZXJ2ZWQgZGlmZmVyZW5jZXMgaW4gY2VsbHVsYXIgc3lzdGVtcy4iLAogICAgInllYXIiIDogMjAyNCwKICAgICJtb250aCIgOiAiMSIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9kb2kvMTAuMTAwMi8yMjExLTU0NjMuMTM3NjUiLAogICAgImF1dGhvcnMiIDogWyB7CiAgICAgICJuYW1lIiA6ICJOaXRpbiBQYXRpbCIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAxLTcxMTMtNTExMSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlpvaHJlaCBNaXJ2ZWlzIgogICAgfSwgewogICAgICAibmFtZSIgOiAiSHVnaCBKIEJ5cm5lIgogICAgfSBdCiAgfSwKICAiZmlsZXMiIDogewogICAgIm1haW4iIDogWyB7CiAgICAgICJuYW1lIiA6ICJLaW5ldGljIGdseWNvbHlzaXMgYXNzYXkgbW9kZWwuc2JtbCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiMTM0NjEiCiAgICB9IF0sCiAgICAiYWRkaXRpb25hbCIgOiBbIHsKICAgICAgIm5hbWUiIDogIk1vZGVsIHJlcG9ydC5odG1sIiwKICAgICAgImZpbGVTaXplIiA6ICI4NzE3NSIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiTW9kZWwgcmVwb3J0IGdlbmVyYXRlZCBpbiBTaW1CaW9sb2d5IHdpdGggcGFyYW1ldGVyIHZhbHVlcyBvcHRpbWlzZWQgZm9yIHNpbXVsYXRpbmcgZXh0cmFjZWxsdWxhciBhY2lkaWZpY2F0aW9uIHJlc3BvbnNlcyBvZiBBNTQ5IGNlbGwgbGluZS4iCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJNb2RlbCB2YXJpYWJsZXMueGxzeCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiOTQ0MSIsCiAgICAgICJkZXNjcmlwdGlvbiIgOiAiTW9kZWwgdmFyaWFibGVzIGZvciBzaW11bGF0aW5nIHRoZSBleHBlcmltZW50YWwgZXh0cmFjZWxsdWxhciBhY2lkaWZpY2F0aW9uIGZvciBlYWNoIGNlbGwgbGluZSBkZXNjcmliZWQgaW4gdGhlICdFeHBlcmltZW50YWwgRUNBUiBkYXRhJyBmaWxlLiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkV4cGVyaW1lbnRhbCBFQ0FSIGRhdGEueGxzeCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiNDE3OTQiLAogICAgICAiZGVzY3JpcHRpb24iIDogIkV4cGVyaW1lbnRhbCBleHRyYWNlbGx1bGFyIGFjaWRpZmljYXRpb24gKExhY19leCBpbiBtb2RlbCkgZGF0YSBmb3IgQTU0OSwgTExDTUsyIGFuZCBIZXBHMiBjZWxsIGxpbmVzLiIKICAgIH0gXQogIH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogNCwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzAzMjY5MTI5MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJOaXRpbiBQYXRpbCIsCiAgICAgICJjb21tZW50IiA6ICJJbXBvcnQgb2YgTml0aW4yMDIzLSBLaW5ldGljcyBvZiBjZWxsdWxhciBtZXRhYm9saWMgcGF0aHdheXMiCiAgICB9IF0KICB9LAogICJmaXJzdFB1Ymxpc2hlZCIgOiAxNzAzMzc0NzQyMDAwLAogICJzdWJtaXNzaW9uSWQiIDogIk1PREVMMjMxMjIyMDAwMSIKfQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2304270004 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=124333d1-1309-4481-be7d-c451a46b90b3; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.556.16427.564518; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJpQ3N0cjExMTZGQjIzOiBHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bSBzdHJhaW4gRkRBQVJHT1NfMTExNiIsCiAgImRlc2NyaXB0aW9uIiA6ICJHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bSBzdHJhaW4gRkRBQVJHT1NfMTExNiIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiQ09NQklORSBhcmNoaXZlIiwKICAgICJ2ZXJzaW9uIiA6ICIwLjEiCiAgfSwKICAicHVibGljYXRpb24iIDogewogICAgImpvdXJuYWwiIDogIkZyb250aWVycyBpbiBCaW9pbmZvcm1hdGljcyIsCiAgICAidGl0bGUiIDogIkdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWxzIGNvbnNpc3RlbnRseSBwcmVkaWN0IGluIHZpdHJvIGNoYXJhY3RlcmlzdGljcyBvZiBDb3J5bmViYWN0ZXJpdW0gc3RyaWF0dW0iLAogICAgImFmZmlsaWF0aW9uIiA6ICIxKSBDb21wdXRhdGlvbmFsIFN5c3RlbXMgQmlvbG9neSBvZiBJbmZlY3Rpb25zIGFuZCBBbnRpbWljcm9iaWFsLVJlc2lzdGFudCBQYXRob2dlbnMsIEluc3RpdHV0ZSBmb3IgQmlvaW5mb3JtYXRpY3MgYW5kIE1lZGljYWwgSW5mb3JtYXRpY3MgKElCTUkpLCBFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCBUw7xiaW5nZW4sIEdlcm1hbnlcbjIpIEludGVyZmFjdWx0eSBJbnN0aXR1dGUgb2YgTWljcm9iaW9sb2d5IGFuZCBJbmZlY3Rpb24gTWVkaWNpbmUgVMO8YmluZ2VuIChJTUlUKSwgRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgVMO8YmluZ2VuLCBHZXJtYW55XG4zKSBEZXBhcnRtZW50IG9mIENvbXB1dGVyIFNjaWVuY2UsIEViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4sIFTDvGJpbmdlbiwgR2VybWFueVxuNCkgR2VybWFuIENlbnRlciBmb3IgSW5mZWN0aW9uIFJlc2VhcmNoIChEWklGKSwgUGFydG5lciBTaXRlIFTDvGJpbmdlbiwgVMO8YmluZ2VuLCBHZXJtYW55XG41KSBDbHVzdGVyIG9mIEV4Y2VsbGVuY2Ug4oCcQ29udHJvbGxpbmcgTWljcm9iZXMgdG8gRmlnaHQgSW5mZWN0aW9ucyAoQ01GSSnigJ0sIEViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4sIFTDvGJpbmdlbiwgR2VybWFueVxuNikgRmFjdWx0eSBvZiBCaW9sb2d5LCBNaWNyb2Jpb2xvZ3ksIEx1ZHdpZyBNYXhpbWlsaWFuIFVuaXZlcnNpdHkgb2YgTXVuaWNoLCBNdW5pY2gsIEdlcm1hbnkiLAogICAgInN5bm9wc2lzIiA6ICJJbnRyb2R1Y3Rpb246IEdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWxzIChHRU1zKSBhcmUgb3JnYW5pc20tc3BlY2lmaWMga25vd2xlZGdlIGJhc2VzIHdoaWNoIGNhbiBiZSB1c2VkIHRvIHVucmF2ZWwgcGF0aG9nZW5pY2l0eSBvciBpbXByb3ZlIHByb2R1Y3Rpb24gb2Ygc3BlY2lmaWMgbWV0YWJvbGl0ZXMgaW4gYmlvdGVjaG5vbG9neSBhcHBsaWNhdGlvbnMuIEhvd2V2ZXIsIHRoZSB2YWxpZGl0eSBvZiBwcmVkaWN0aW9ucyBmb3IgYmFjdGVyaWFsIHByb2xpZmVyYXRpb24gaW4gaW4gdml0cm8gc2V0dGluZ3MgaXMgaGFyZGx5IGludmVzdGlnYXRlZC5cbk1ldGhvZHM6IFRoZSBwcmVzZW50IHdvcmsgY29tYmluZXMgaW4gc2lsaWNvIGFuZCBpbiB2aXRybyBhcHByb2FjaGVzIHRvIGNyZWF0ZSBhbmQgY3VyYXRlIHN0cmFpbi1zcGVjaWZpYyBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVscyBvZiBDb3J5bmViYWN0ZXJpdW0gc3RyaWF0dW0uXG5SZXN1bHRzOiBXZSBpbnRyb2R1Y2UgZml2ZSBuZXdseSBjcmVhdGVkIHN0cmFpbi1zcGVjaWZpYyBnZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVscyAoR0VNcykgb2YgaGlnaCBxdWFsaXR5LCBzYXRpc2Z5aW5nIGFsbCBjb250ZW1wb3Jhcnkgc3RhbmRhcmRzIGFuZCByZXF1aXJlbWVudHMuIEFsbCB0aGVzZSBtb2RlbHMgaGF2ZSBiZWVuIGJlbmNobWFya2VkIHVzaW5nIHRoZSBjb21tdW5pdHkgc3RhbmRhcmQgdGVzdCBzdWl0ZSBNZXRhYm9saWMgTW9kZWwgVGVzdGluZyAoTUVNT1RFKSBhbmQgd2VyZSB2YWxpZGF0ZWQgYnkgbGFib3JhdG9yeSBleHBlcmltZW50cy4gRm9yIHRoZSBjdXJhdGlvbiBvZiB0aG9zZSBtb2RlbHMsIHRoZSBzb2Z0d2FyZSBpbmZyYXN0cnVjdHVyZSByZWZpbmVHRU1zIHdhcyBkZXZlbG9wZWQgdG8gd29yayBvbiB0aGVzZSBtb2RlbHMgaW4gcGFyYWxsZWwgYW5kIHRvIGNvbXBseSB3aXRoIHRoZSBxdWFsaXR5IHN0YW5kYXJkcyBmb3IgR0VNcy4gVGhlIG1vZGVsIHByZWRpY3Rpb25zIHdlcmUgY29uZmlybWVkIGJ5IGV4cGVyaW1lbnRhbCBkYXRhIGFuZCBhIG5ldyBjb21wYXJpc29uIG1ldHJpYyBiYXNlZCBvbiB0aGUgZG91YmxpbmcgdGltZSB3YXMgZGV2ZWxvcGVkIHRvIHF1YW50aWZ5IGJhY3RlcmlhbCBncm93dGguXG5EaXNjdXNzaW9uOiBGdXR1cmUgbW9kZWxpbmcgcHJvamVjdHMgY2FuIHJlbHkgb24gdGhlIHByb3Bvc2VkIHNvZnR3YXJlLCB3aGljaCBpcyBpbmRlcGVuZGVudCBvZiBzcGVjaWZpYyBlbnZpcm9ubWVudGFsIGNvbmRpdGlvbnMuIFRoZSB2YWxpZGF0aW9uIGFwcHJvYWNoIGJhc2VkIG9uIHRoZSBncm93dGggcmF0ZSBjYWxjdWxhdGlvbiBpcyBub3cgYWNjZXNzaWJsZSBhbmQgY2xvc2VseSBhbGlnbmVkIHdpdGggYmlvbG9naWNhbCBxdWVzdGlvbnMuIFRoZSBjdXJhdGVkIG1vZGVscyBhcmUgZnJlZWx5IGF2YWlsYWJsZSB2aWEgQmlvTW9kZWxzIGFuZCBhIEdpdEh1YiByZXBvc2l0b3J5IGFuZCBjYW4gYmUgdXNlZC4gVGhlIG9wZW4tc291cmNlIHNvZnR3YXJlIHJlZmluZUdFTXMgaXMgYXZhaWxhYmxlIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL2RyYWVnZXItbGFiL3JlZmluZWdlbXMuIiwKICAgICJ5ZWFyIiA6IDIwMjMsCiAgICAibW9udGgiIDogIjEwIiwKICAgICJ2b2x1bWUiIDogIjMiLAogICAgImxpbmsiIDogImh0dHA6Ly9pZGVudGlmaWVycy5vcmcvZG9pLzEwLjMzODkvZmJpbmYuMjAyMy4xMjE0MDc0IiwKICAgICJhdXRob3JzIiA6IFsgewogICAgICAibmFtZSIgOiAiRmFta2UgQsOkdWVybGUiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMy0xMzg3LTAyNTEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJHd2VuZG9seW4gTy4gRMO2YmVsIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDItODIwNi0yNTc2IgogICAgfSwgewogICAgICAibmFtZSIgOiAiTGF1cmEgQ2FtdXMiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMy0xMzM1LTg5MDEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJTaW1vbiBIZWlsYnJvbm5lciIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiTHVkd2lnIE1heGltaWxpYW4gVW5pdmVyc2l0eSBvZiBNdW5pY2giLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi02Nzc0LTIzMTEiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJBbmRyZWFzIERyw6RnZXIiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi0xMjQwLTU1NTMiCiAgICB9IF0KICB9LAogICJmaWxlcyIgOiB7IH0sCiAgImhpc3RvcnkiIDogewogICAgInJldmlzaW9ucyIgOiBbIHsKICAgICAgInZlcnNpb24iIDogMiwKICAgICAgInN1Ym1pdHRlZCIgOiAxNzA0ODQwODI4MDAwLAogICAgICAic3VibWl0dGVyIiA6ICJBbmRyZWFzIERyw6RnZXIiLAogICAgICAiY29tbWVudCIgOiAiQWRkZWQgRlJPRyByZXBvcnQuIgogICAgfSBdCiAgfSwKICAiZmlyc3RQdWJsaXNoZWQiIDogMTY5ODQwMjcyMzAwMCwKICAic3VibWlzc2lvbklkIiA6ICJNT0RFTDIzMDQyNzAwMDQiCn0= + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2304270005 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=7ebe5a7b-e7d4-4405-a434-a6d7a12567d6; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.667.16423.248865; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJpQ3N0cktDTmEwMUZCMjM6IEdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWwgb2YgQ29yeW5lYmFjdGVyaXVtIHN0cmlhdHVtIHN0cmFpbiBLQy1OYS0wMSIsCiAgImRlc2NyaXB0aW9uIiA6ICJHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bSBzdHJhaW4gS0MtTmEtMDEiLAogICJmb3JtYXQiIDogewogICAgIm5hbWUiIDogIkNPTUJJTkUgYXJjaGl2ZSIsCiAgICAidmVyc2lvbiIgOiAiMC4xIgogIH0sCiAgInB1YmxpY2F0aW9uIiA6IHsKICAgICJqb3VybmFsIiA6ICJGcm9udGllcnMgaW4gQmlvaW5mb3JtYXRpY3MiLAogICAgInRpdGxlIiA6ICJHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVscyBjb25zaXN0ZW50bHkgcHJlZGljdCBpbiB2aXRybyBjaGFyYWN0ZXJpc3RpY3Mgb2YgQ29yeW5lYmFjdGVyaXVtIHN0cmlhdHVtIiwKICAgICJhZmZpbGlhdGlvbiIgOiAiMSkgQ29tcHV0YXRpb25hbCBTeXN0ZW1zIEJpb2xvZ3kgb2YgSW5mZWN0aW9ucyBhbmQgQW50aW1pY3JvYmlhbC1SZXNpc3RhbnQgUGF0aG9nZW5zLCBJbnN0aXR1dGUgZm9yIEJpb2luZm9ybWF0aWNzIGFuZCBNZWRpY2FsIEluZm9ybWF0aWNzIChJQk1JKSwgRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgVMO8YmluZ2VuLCBHZXJtYW55XG4yKSBJbnRlcmZhY3VsdHkgSW5zdGl0dXRlIG9mIE1pY3JvYmlvbG9neSBhbmQgSW5mZWN0aW9uIE1lZGljaW5lIFTDvGJpbmdlbiAoSU1JVCksIEViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4sIFTDvGJpbmdlbiwgR2VybWFueVxuMykgRGVwYXJ0bWVudCBvZiBDb21wdXRlciBTY2llbmNlLCBFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCBUw7xiaW5nZW4sIEdlcm1hbnlcbjQpIEdlcm1hbiBDZW50ZXIgZm9yIEluZmVjdGlvbiBSZXNlYXJjaCAoRFpJRiksIFBhcnRuZXIgU2l0ZSBUw7xiaW5nZW4sIFTDvGJpbmdlbiwgR2VybWFueVxuNSkgQ2x1c3RlciBvZiBFeGNlbGxlbmNlIOKAnENvbnRyb2xsaW5nIE1pY3JvYmVzIHRvIEZpZ2h0IEluZmVjdGlvbnMgKENNRkkp4oCdLCBFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCBUw7xiaW5nZW4sIEdlcm1hbnlcbjYpIEZhY3VsdHkgb2YgQmlvbG9neSwgTWljcm9iaW9sb2d5LCBMdWR3aWcgTWF4aW1pbGlhbiBVbml2ZXJzaXR5IG9mIE11bmljaCwgTXVuaWNoLCBHZXJtYW55IiwKICAgICJzeW5vcHNpcyIgOiAiSW50cm9kdWN0aW9uOiBHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVscyAoR0VNcykgYXJlIG9yZ2FuaXNtLXNwZWNpZmljIGtub3dsZWRnZSBiYXNlcyB3aGljaCBjYW4gYmUgdXNlZCB0byB1bnJhdmVsIHBhdGhvZ2VuaWNpdHkgb3IgaW1wcm92ZSBwcm9kdWN0aW9uIG9mIHNwZWNpZmljIG1ldGFib2xpdGVzIGluIGJpb3RlY2hub2xvZ3kgYXBwbGljYXRpb25zLiBIb3dldmVyLCB0aGUgdmFsaWRpdHkgb2YgcHJlZGljdGlvbnMgZm9yIGJhY3RlcmlhbCBwcm9saWZlcmF0aW9uIGluIGluIHZpdHJvIHNldHRpbmdzIGlzIGhhcmRseSBpbnZlc3RpZ2F0ZWQuXG5NZXRob2RzOiBUaGUgcHJlc2VudCB3b3JrIGNvbWJpbmVzIGluIHNpbGljbyBhbmQgaW4gdml0cm8gYXBwcm9hY2hlcyB0byBjcmVhdGUgYW5kIGN1cmF0ZSBzdHJhaW4tc3BlY2lmaWMgZ2Vub21lLXNjYWxlIG1ldGFib2xpYyBtb2RlbHMgb2YgQ29yeW5lYmFjdGVyaXVtIHN0cmlhdHVtLlxuUmVzdWx0czogV2UgaW50cm9kdWNlIGZpdmUgbmV3bHkgY3JlYXRlZCBzdHJhaW4tc3BlY2lmaWMgZ2Vub21lLXNjYWxlIG1ldGFib2xpYyBtb2RlbHMgKEdFTXMpIG9mIGhpZ2ggcXVhbGl0eSwgc2F0aXNmeWluZyBhbGwgY29udGVtcG9yYXJ5IHN0YW5kYXJkcyBhbmQgcmVxdWlyZW1lbnRzLiBBbGwgdGhlc2UgbW9kZWxzIGhhdmUgYmVlbiBiZW5jaG1hcmtlZCB1c2luZyB0aGUgY29tbXVuaXR5IHN0YW5kYXJkIHRlc3Qgc3VpdGUgTWV0YWJvbGljIE1vZGVsIFRlc3RpbmcgKE1FTU9URSkgYW5kIHdlcmUgdmFsaWRhdGVkIGJ5IGxhYm9yYXRvcnkgZXhwZXJpbWVudHMuIEZvciB0aGUgY3VyYXRpb24gb2YgdGhvc2UgbW9kZWxzLCB0aGUgc29mdHdhcmUgaW5mcmFzdHJ1Y3R1cmUgcmVmaW5lR0VNcyB3YXMgZGV2ZWxvcGVkIHRvIHdvcmsgb24gdGhlc2UgbW9kZWxzIGluIHBhcmFsbGVsIGFuZCB0byBjb21wbHkgd2l0aCB0aGUgcXVhbGl0eSBzdGFuZGFyZHMgZm9yIEdFTXMuIFRoZSBtb2RlbCBwcmVkaWN0aW9ucyB3ZXJlIGNvbmZpcm1lZCBieSBleHBlcmltZW50YWwgZGF0YSBhbmQgYSBuZXcgY29tcGFyaXNvbiBtZXRyaWMgYmFzZWQgb24gdGhlIGRvdWJsaW5nIHRpbWUgd2FzIGRldmVsb3BlZCB0byBxdWFudGlmeSBiYWN0ZXJpYWwgZ3Jvd3RoLlxuRGlzY3Vzc2lvbjogRnV0dXJlIG1vZGVsaW5nIHByb2plY3RzIGNhbiByZWx5IG9uIHRoZSBwcm9wb3NlZCBzb2Z0d2FyZSwgd2hpY2ggaXMgaW5kZXBlbmRlbnQgb2Ygc3BlY2lmaWMgZW52aXJvbm1lbnRhbCBjb25kaXRpb25zLiBUaGUgdmFsaWRhdGlvbiBhcHByb2FjaCBiYXNlZCBvbiB0aGUgZ3Jvd3RoIHJhdGUgY2FsY3VsYXRpb24gaXMgbm93IGFjY2Vzc2libGUgYW5kIGNsb3NlbHkgYWxpZ25lZCB3aXRoIGJpb2xvZ2ljYWwgcXVlc3Rpb25zLiBUaGUgY3VyYXRlZCBtb2RlbHMgYXJlIGZyZWVseSBhdmFpbGFibGUgdmlhIEJpb01vZGVscyBhbmQgYSBHaXRIdWIgcmVwb3NpdG9yeSBhbmQgY2FuIGJlIHVzZWQuIFRoZSBvcGVuLXNvdXJjZSBzb2Z0d2FyZSByZWZpbmVHRU1zIGlzIGF2YWlsYWJsZSBmcm9tIGh0dHBzOi8vZ2l0aHViLmNvbS9kcmFlZ2VyLWxhYi9yZWZpbmVnZW1zLiIsCiAgICAieWVhciIgOiAyMDIzLAogICAgIm1vbnRoIiA6ICIxMCIsCiAgICAidm9sdW1lIiA6ICIzIiwKICAgICJsaW5rIiA6ICJodHRwOi8vaWRlbnRpZmllcnMub3JnL2RvaS8xMC4zMzg5L2ZiaW5mLjIwMjMuMTIxNDA3NCIsCiAgICAiYXV0aG9ycyIgOiBbIHsKICAgICAgIm5hbWUiIDogIkZhbWtlIELDpHVlcmxlIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDMtMTM4Ny0wMjUxIgogICAgfSwgewogICAgICAibmFtZSIgOiAiR3dlbmRvbHluIE8uIETDtmJlbCIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAyLTgyMDYtMjU3NiIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkxhdXJhIENhbXVzIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDMtMTMzNS04OTAxIgogICAgfSwgewogICAgICAibmFtZSIgOiAiU2ltb24gSGVpbGJyb25uZXIiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkx1ZHdpZyBNYXhpbWlsaWFuIFVuaXZlcnNpdHkgb2YgTXVuaWNoIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDItNjc3NC0yMzExIgogICAgfSwgewogICAgICAibmFtZSIgOiAiQW5kcmVhcyBEcsOkZ2VyIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDItMTI0MC01NTUzIgogICAgfSBdCiAgfSwKICAiZmlsZXMiIDogeyB9LAogICJoaXN0b3J5IiA6IHsKICAgICJyZXZpc2lvbnMiIDogWyB7CiAgICAgICJ2ZXJzaW9uIiA6IDIsCiAgICAgICJzdWJtaXR0ZWQiIDogMTcwNDg0MTM1NzAwMCwKICAgICAgInN1Ym1pdHRlciIgOiAiQW5kcmVhcyBEcsOkZ2VyIiwKICAgICAgImNvbW1lbnQiIDogIkFkZGVkIEZST0cgcmVwb3J0LiIKICAgIH0gXQogIH0sCiAgImZpcnN0UHVibGlzaGVkIiA6IDE2OTg0MDI3MzYwMDAsCiAgInN1Ym1pc3Npb25JZCIgOiAiTU9ERUwyMzA0MjcwMDA1Igp9 + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2304270002 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=f422551f-b25e-4627-8eb9-66e7fb096841; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.776.16423.792970; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJpQ3N0cjExMTVGQjIzOiBHZW5vbWUtc2NhbGUgbWV0YWJvbGljIG1vZGVsIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bSBzdHJhaW4gRkRBLUFSR09TXzExMTUiLAogICJkZXNjcmlwdGlvbiIgOiAiR2Vub21lLXNjYWxlIG1ldGFib2xpYyBtb2RlbCBvZiBDb3J5bmViYWN0ZXJpdW0gc3RyaWF0dW0gc3RyYWluIEZEQS1BUkdPU18xMTE1IiwKICAiZm9ybWF0IiA6IHsKICAgICJuYW1lIiA6ICJDT01CSU5FIGFyY2hpdmUiLAogICAgInZlcnNpb24iIDogIjAuMSIKICB9LAogICJwdWJsaWNhdGlvbiIgOiB7CiAgICAiam91cm5hbCIgOiAiRnJvbnRpZXJzIGluIEJpb2luZm9ybWF0aWNzIiwKICAgICJ0aXRsZSIgOiAiR2Vub21lLXNjYWxlIG1ldGFib2xpYyBtb2RlbHMgY29uc2lzdGVudGx5IHByZWRpY3QgaW4gdml0cm8gY2hhcmFjdGVyaXN0aWNzIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bSIsCiAgICAiYWZmaWxpYXRpb24iIDogIjEpIENvbXB1dGF0aW9uYWwgU3lzdGVtcyBCaW9sb2d5IG9mIEluZmVjdGlvbnMgYW5kIEFudGltaWNyb2JpYWwtUmVzaXN0YW50IFBhdGhvZ2VucywgSW5zdGl0dXRlIGZvciBCaW9pbmZvcm1hdGljcyBhbmQgTWVkaWNhbCBJbmZvcm1hdGljcyAoSUJNSSksIEViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4sIFTDvGJpbmdlbiwgR2VybWFueVxuMikgSW50ZXJmYWN1bHR5IEluc3RpdHV0ZSBvZiBNaWNyb2Jpb2xvZ3kgYW5kIEluZmVjdGlvbiBNZWRpY2luZSBUw7xiaW5nZW4gKElNSVQpLCBFYmVyaGFyZCBLYXJsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCBUw7xiaW5nZW4sIEdlcm1hbnlcbjMpIERlcGFydG1lbnQgb2YgQ29tcHV0ZXIgU2NpZW5jZSwgRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgVMO8YmluZ2VuLCBHZXJtYW55XG40KSBHZXJtYW4gQ2VudGVyIGZvciBJbmZlY3Rpb24gUmVzZWFyY2ggKERaSUYpLCBQYXJ0bmVyIFNpdGUgVMO8YmluZ2VuLCBUw7xiaW5nZW4sIEdlcm1hbnlcbjUpIENsdXN0ZXIgb2YgRXhjZWxsZW5jZSDigJxDb250cm9sbGluZyBNaWNyb2JlcyB0byBGaWdodCBJbmZlY3Rpb25zIChDTUZJKeKAnSwgRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgVMO8YmluZ2VuLCBHZXJtYW55XG42KSBGYWN1bHR5IG9mIEJpb2xvZ3ksIE1pY3JvYmlvbG9neSwgTHVkd2lnIE1heGltaWxpYW4gVW5pdmVyc2l0eSBvZiBNdW5pY2gsIE11bmljaCwgR2VybWFueSIsCiAgICAic3lub3BzaXMiIDogIkludHJvZHVjdGlvbjogR2Vub21lLXNjYWxlIG1ldGFib2xpYyBtb2RlbHMgKEdFTXMpIGFyZSBvcmdhbmlzbS1zcGVjaWZpYyBrbm93bGVkZ2UgYmFzZXMgd2hpY2ggY2FuIGJlIHVzZWQgdG8gdW5yYXZlbCBwYXRob2dlbmljaXR5IG9yIGltcHJvdmUgcHJvZHVjdGlvbiBvZiBzcGVjaWZpYyBtZXRhYm9saXRlcyBpbiBiaW90ZWNobm9sb2d5IGFwcGxpY2F0aW9ucy4gSG93ZXZlciwgdGhlIHZhbGlkaXR5IG9mIHByZWRpY3Rpb25zIGZvciBiYWN0ZXJpYWwgcHJvbGlmZXJhdGlvbiBpbiBpbiB2aXRybyBzZXR0aW5ncyBpcyBoYXJkbHkgaW52ZXN0aWdhdGVkLlxuTWV0aG9kczogVGhlIHByZXNlbnQgd29yayBjb21iaW5lcyBpbiBzaWxpY28gYW5kIGluIHZpdHJvIGFwcHJvYWNoZXMgdG8gY3JlYXRlIGFuZCBjdXJhdGUgc3RyYWluLXNwZWNpZmljIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWxzIG9mIENvcnluZWJhY3Rlcml1bSBzdHJpYXR1bS5cblJlc3VsdHM6IFdlIGludHJvZHVjZSBmaXZlIG5ld2x5IGNyZWF0ZWQgc3RyYWluLXNwZWNpZmljIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWxzIChHRU1zKSBvZiBoaWdoIHF1YWxpdHksIHNhdGlzZnlpbmcgYWxsIGNvbnRlbXBvcmFyeSBzdGFuZGFyZHMgYW5kIHJlcXVpcmVtZW50cy4gQWxsIHRoZXNlIG1vZGVscyBoYXZlIGJlZW4gYmVuY2htYXJrZWQgdXNpbmcgdGhlIGNvbW11bml0eSBzdGFuZGFyZCB0ZXN0IHN1aXRlIE1ldGFib2xpYyBNb2RlbCBUZXN0aW5nIChNRU1PVEUpIGFuZCB3ZXJlIHZhbGlkYXRlZCBieSBsYWJvcmF0b3J5IGV4cGVyaW1lbnRzLiBGb3IgdGhlIGN1cmF0aW9uIG9mIHRob3NlIG1vZGVscywgdGhlIHNvZnR3YXJlIGluZnJhc3RydWN0dXJlIHJlZmluZUdFTXMgd2FzIGRldmVsb3BlZCB0byB3b3JrIG9uIHRoZXNlIG1vZGVscyBpbiBwYXJhbGxlbCBhbmQgdG8gY29tcGx5IHdpdGggdGhlIHF1YWxpdHkgc3RhbmRhcmRzIGZvciBHRU1zLiBUaGUgbW9kZWwgcHJlZGljdGlvbnMgd2VyZSBjb25maXJtZWQgYnkgZXhwZXJpbWVudGFsIGRhdGEgYW5kIGEgbmV3IGNvbXBhcmlzb24gbWV0cmljIGJhc2VkIG9uIHRoZSBkb3VibGluZyB0aW1lIHdhcyBkZXZlbG9wZWQgdG8gcXVhbnRpZnkgYmFjdGVyaWFsIGdyb3d0aC5cbkRpc2N1c3Npb246IEZ1dHVyZSBtb2RlbGluZyBwcm9qZWN0cyBjYW4gcmVseSBvbiB0aGUgcHJvcG9zZWQgc29mdHdhcmUsIHdoaWNoIGlzIGluZGVwZW5kZW50IG9mIHNwZWNpZmljIGVudmlyb25tZW50YWwgY29uZGl0aW9ucy4gVGhlIHZhbGlkYXRpb24gYXBwcm9hY2ggYmFzZWQgb24gdGhlIGdyb3d0aCByYXRlIGNhbGN1bGF0aW9uIGlzIG5vdyBhY2Nlc3NpYmxlIGFuZCBjbG9zZWx5IGFsaWduZWQgd2l0aCBiaW9sb2dpY2FsIHF1ZXN0aW9ucy4gVGhlIGN1cmF0ZWQgbW9kZWxzIGFyZSBmcmVlbHkgYXZhaWxhYmxlIHZpYSBCaW9Nb2RlbHMgYW5kIGEgR2l0SHViIHJlcG9zaXRvcnkgYW5kIGNhbiBiZSB1c2VkLiBUaGUgb3Blbi1zb3VyY2Ugc29mdHdhcmUgcmVmaW5lR0VNcyBpcyBhdmFpbGFibGUgZnJvbSBodHRwczovL2dpdGh1Yi5jb20vZHJhZWdlci1sYWIvcmVmaW5lZ2Vtcy4iLAogICAgInllYXIiIDogMjAyMywKICAgICJtb250aCIgOiAiMTAiLAogICAgInZvbHVtZSIgOiAiMyIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9kb2kvMTAuMzM4OS9mYmluZi4yMDIzLjEyMTQwNzQiLAogICAgImF1dGhvcnMiIDogWyB7CiAgICAgICJuYW1lIiA6ICJGYW1rZSBCw6R1ZXJsZSIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAzLTEzODctMDI1MSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkd3ZW5kb2x5biBPLiBEw7ZiZWwiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkViZXJoYXJkIEthcmwgVW5pdmVyc2l0eSBvZiBUw7xiaW5nZW4iLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMi04MjA2LTI1NzYiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJMYXVyYSBDYW11cyIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAzLTEzMzUtODkwMSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIlNpbW9uIEhlaWxicm9ubmVyIiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJMdWR3aWcgTWF4aW1pbGlhbiBVbml2ZXJzaXR5IG9mIE11bmljaCIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAyLTY3NzQtMjMxMSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFuZHJlYXMgRHLDpGdlciIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRWJlcmhhcmQgS2FybCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAyLTEyNDAtNTU1MyIKICAgIH0gXQogIH0sCiAgImZpbGVzIiA6IHsgfSwKICAiaGlzdG9yeSIgOiB7CiAgICAicmV2aXNpb25zIiA6IFsgewogICAgICAidmVyc2lvbiIgOiAyLAogICAgICAic3VibWl0dGVkIiA6IDE3MDQ3OTI4NzUwMDAsCiAgICAgICJzdWJtaXR0ZXIiIDogIkFuZHJlYXMgRHLDpGdlciIsCiAgICAgICJjb21tZW50IiA6ICJDb3JyZWN0ZWQgdGhlIEZST0cgYW5hbHlzaXMgZW1iZWRkaW5nIGluIHRoZSBhcmNoaXZlLiIKICAgIH0gXQogIH0sCiAgImZpcnN0UHVibGlzaGVkIiA6IDE2OTg0MDI2NTQwMDAsCiAgInN1Ym1pc3Npb25JZCIgOiAiTU9ERUwyMzA0MjcwMDAyIgp9 + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2110010001 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:22 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=66df48ed-cd39-4c7b-b3e2-4af776fa30d4; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.877.16427.707893; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJuYW1lIiA6ICJaaGFuZzIwMTcgLSBNb2RlbCBpQ1c3NzMgb2YgQ29yeW5lYmFjdGVyaXVtIGdsdXRhbWljdW0gQVRDQyAxMzAzMiAoQmllbGVmZWxkKSIsCiAgImRlc2NyaXB0aW9uIiA6ICJUaGlzIG1vZGVsIG9mIENvcnluZWJhY3Rlcml1bSBnbHV0YW1pY3VtIHdhcyBpbml0aWFsbHkgcHVibGlzaGVkIGJ5IFkuIFpoYW5nIGV0IGFsLiAoRE9JOiAxMC4xMTg2L3MxMzA2OC0wMTctMDg1Ni0zLiBQTUlEOiAyODY4MDQ3ODsgUE1DSUQ6IFBNQzU0OTM4ODApIGFuZCBwdWJsaXNoZWQgaW4gTWljcm9zb2Z0IEV4Y2VsIFNwcmVhZHNoZWV0IGZvcm1hdC4gTS4gRmVpZXJhYmVuZCBhbmQgQS4gUmVueiBldCBhbC4gY29udmVydGVkIHRoaXMgbW9kZWwgaW50byBTQk1MIExldmVsIDMgVmVyc2lvbiAxIHdpdGggdGhlIFNCTUwgZXh0ZW5zaW9uIHBhY2thZ2VzIGZvciBncm91cHMgYW5kIGZsdXgtYmFsYW5jZSBjb25zdHJhaW50cyB1c2luZyBhbGwgaW5mb3JtYXRpb24gZ2l2ZW4gaW4gdGhlIG9yaWdpbmFsIHB1YmxpY2F0aW9uIGRyaXZlbiBieSB0aGUgYWltIHRvIG1ha2UgdGhpcyBtb2RlbCBhY2Nlc3NpYmxlIHRvIHRoZSByZXNlYXJjaCBjb21tdW5pdHkuIFN1YnNlcXVlbnRseSwgTS4gRmVpZXJhYmVuZCBhbmQgQS4gUmVueiBldCBhbC4gY2FyZWZ1bGx5IGN1cmF0ZWQgYW5kIGFubm90YXRlZCB0aGUgbW9kZWwgZm9sbG93aW5nIHRoZSBNSVJJQU0gZ3VpZGVsaW5lcyBhbmQgYWRkZWQgc3BlY2lmaWMgU0JPIHRlcm1zIHRvIGl0LiIsCiAgImZvcm1hdCIgOiB7CiAgICAibmFtZSIgOiAiQ09NQklORSBhcmNoaXZlIiwKICAgICJ2ZXJzaW9uIiA6ICIwLjEiCiAgfSwKICAicHVibGljYXRpb24iIDogewogICAgImpvdXJuYWwiIDogIkZyb250aWVycyBpbiBNaWNyb2Jpb2xvZ3kiLAogICAgInRpdGxlIiA6ICJIaWdoLXF1YWxpdHkgZ2Vub21lLXNjYWxlIHJlY29uc3RydWN0aW9uIG9mIENvcnluZWJhY3Rlcml1bSBnbHV0YW1pY3VtIEFUQ0MgMTMwMzIiLAogICAgImFmZmlsaWF0aW9uIiA6ICIxLiBEZXBhcnRtZW50IG9mIENvbXB1dGVyIFNjaWVuY2UsIEZhY3VsdHkgb2YgU2NpZW5jZXMsIFVuaXZlcnNpdHkgb2YgVMO8YmluZ2VuLCBHZXJtYW55XG4yLiBJbnRlcmZhY3VsdHkgSW5zdGl0dXRlIGZvciBCaW9tZWRpY2FsIEluZm9ybWF0aWNzLCBVbml2ZXJzaXR5IEhvc3BpdGFsIGFuZCBGYWN1bHR5IG9mIE1lZGljaW5lLCBVbml2ZXJzaXR5IG9mIFTDvGJpbmdlbiwgR2VybWFueVxuMy4gSW5zdGl0dXQgZsO8ciBCaW8tIHVuZCBHZW93aXNzZW5zY2hhZnRlbiAoSUJHKSwgR2VybWFueVxuNC4gQ2VudGVyIGZvciBDb21wdXRhdGlvbmFsIEJpb21lZGljaW5lLCBSV1RIIEFhY2hlbiBVbml2ZXJzaXR5LCBHZXJtYW55IiwKICAgICJzeW5vcHNpcyIgOiAiQ29yeW5lYmFjdGVyaXVtIGdsdXRhbWljdW0gYmVsb25ncyB0byB0aGUgbWljcm9iZXMgb2YgZW5vcm1vdXMgYmlvdGVjaG5vbG9naWNhbCByZWxldmFuY2UuIEluIHBhcnRpY3VsYXIsIGl0cyBzdHJhaW4gQVRDQyAxMzAzMiBpcyBhIHdpZGVseSB1c2VkIHByb2R1Y2VyIG9mIEwtYW1pbm8gYWNpZHMgYXQgYW4gaW5kdXN0cmlhbCBzY2FsZS4gSXRzIGFwcGFyZW50IHJvYnVzdG5lc3MgYWxzbyB0dXJucyBpdCBpbnRvIGEgZmF2b3JhYmxlIHBsYXRmb3JtIGhvc3QgZm9yIGEgd2lkZSByYW5nZSBvZiBmdXJ0aGVyIGNvbXBvdW5kcywgbWFpbmx5IGJlY2F1c2Ugb2YgZW1lcmdpbmcgYmlvLWJhc2VkIGVjb25vbWllcy4gQSBkZWVwIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGJpb2NoZW1pY2FsIHByb2Nlc3NlcyBpbiBDLiBnbHV0YW1pY3VtIGlzIGVzc2VudGlhbCBmb3IgYSBzdXN0YWluYWJsZSBlbmhhbmNlbWVudCBvZiB0aGUgbWljcm9iZeKAmXMgcHJvZHVjdGl2aXR5LiBDb21wdXRhdGlvbmFsIHN5c3RlbXMgYmlvbG9neSBoYXMgdGhlIHBvdGVudGlhbCB0byBwcm92aWRlIGEgdmFsdWFibGUgYmFzaXMgZm9yIGRyaXZpbmcgbWV0YWJvbGljIGVuZ2luZWVyaW5nIGFuZCBiaW90ZWNobm9sb2dpY2FsIGFkdmFuY2VzLCBzdWNoIGFzIGluY3JlYXNlZCB5aWVsZHMgb2YgaGVhbHRoeSBwcm9kdWNlciBzdHJhaW5zIGJhc2VkIG9uIGdlbm9tZS1zY2FsZSBtZXRhYm9saWMgbW9kZWxzIChHRU1zKS4gQWR2YW5jZWQgcmVjb25zdHJ1Y3Rpb24gcGlwZWxpbmVzIGFyZSBub3cgYXZhaWxhYmxlIHRoYXQgZmFjaWxpdGF0ZSB0aGUgcmVjb25zdHJ1Y3Rpb24gb2YgR0VNcyBhbmQgc3VwcG9ydCB0aGVpciBtYW51YWwgY3VyYXRpb24uIFRoaXMgYXJ0aWNsZSBwcmVzZW50cyBpQ0dCMjFGUiwgYW4gdXBkYXRlZCBhbmQgdW5pZmllZCBHRU0gb2YgQy4gZ2x1dGFtaWN1bSBBVENDIDEzMDMyIHdpdGggaGlnaCBxdWFsaXR5IHJlZ2FyZGluZyBjb21wcmVoZW5zaXZlbmVzcyBhbmQgZGF0YSBzdGFuZGFyZHMsIGJ1aWx0IHdpdGggdGhlIGxhdGVzdCBtb2RlbGluZyB0ZWNobmlxdWVzIGFuZCBhZHZhbmNlZCByZWNvbnN0cnVjdGlvbiBwaXBlbGluZXMuIEl0IGNvbXByaXNlcyAxLDA0MiBtZXRhYm9saXRlcywgMSw1MzkgcmVhY3Rpb25zLCBhbmQgODA1IGdlbmVzIHdpdGggZGV0YWlsZWQgYW5ub3RhdGlvbnMgYW5kIGRhdGFiYXNlIGNyb3NzLXJlZmVyZW5jZXMuIFRoZSBtb2RlbCB2YWxpZGF0aW9uIHRvb2sgcGxhY2UgdXNpbmcgZGlmZmVyZW50IG1lZGlhIGFuZCByZXN1bHRlZCBpbiByZWFsaXN0aWMgZ3Jvd3RoIHJhdGUgcHJlZGljdGlvbnMgdW5kZXIgYWVyb2JpYyBhbmQgYW5hZXJvYmljIGNvbmRpdGlvbnMuIFRoZSBuZXcgR0VNIHByb2R1Y2VzIGFsbCBjYW5vbmljYWwgYW1pbm8gYWNpZHMsIGFuZCBpdHMgcGhlbm90eXBpYyBwcmVkaWN0aW9ucyBhcmUgY29uc2lzdGVudCB3aXRoIGxhYm9yYXRvcnkgZGF0YS4gVGhlIGluIHNpbGljbyBtb2RlbCBwcm92ZWQgZnJ1aXRmdWwgaW4gYWRkaW5nIGtub3dsZWRnZSB0byB0aGUgbWV0YWJvbGlzbSBvZiBDLiBnbHV0YW1pY3VtOiBpQ0dCMjFGUiBzdGlsbCBwcm9kdWNlcyBMLWdsdXRhbWF0ZSB3aXRoIHRoZSBrbm9jay1vdXQgb2YgdGhlIGVuenltZSBweXJ1dmF0ZSBjYXJib3h5bGFzZSwgZGVzcGl0ZSB0aGUgY29tbW9uIGJlbGllZiB0byBiZSByZWxldmFudCBmb3IgdGhlIGFtaW5vIGFjaWTigJlzIHByb2R1Y3Rpb24uIFdlIGNvbmNsdWRlIHRoYXQgaW50ZWdyYXRpbmcgaGlnaCBzdGFuZGFyZHMgaW50byB0aGUgcmVjb25zdHJ1Y3Rpb24gb2YgR0VNcyBmYWNpbGl0YXRlcyByZXBsaWNhdGluZyB2YWxpZGF0ZWQga25vd2xlZGdlLCBjbG9zaW5nIGtub3dsZWRnZSBnYXBzLCBhbmQgbWFraW5nIGl0IGEgdXNlZnVsIGJhc2lzIGZvciBtZXRhYm9saWMgZW5naW5lZXJpbmcuIFRoZSBtb2RlbCBpcyBmcmVlbHkgYXZhaWxhYmxlIGZyb20gQmlvTW9kZWxzIERhdGFiYXNlIHVuZGVyIGlkZW50aWZpZXIgTU9ERUwyMTAyMDUwMDAxLiIsCiAgICAieWVhciIgOiAyMDIxLAogICAgIm1vbnRoIiA6ICIxMSIsCiAgICAibGluayIgOiAiaHR0cDovL2lkZW50aWZpZXJzLm9yZy9kb2kvMTAuMzM4OS9mbWljYi4yMDIxLjc1MDIwNiIsCiAgICAiYXV0aG9ycyIgOiBbIHsKICAgICAgIm5hbWUiIDogIk1hcnRpbmEgRmVpZXJhYmVuZCIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIkFsaW5hIFJlbnoiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJFbGlzYWJldGggWmVsbGUiLAogICAgICAiaW5zdGl0dXRpb24iIDogIkZvcnNjaHVuZ3N6ZW50cnVtIErDvGxpY2giLAogICAgICAib3JjaWQiIDogIjAwMDAtMDAwMy00OTgzLTMwMjIiCiAgICB9LCB7CiAgICAgICJuYW1lIiA6ICJLYXRoYXJpbmEgTsO2aCIsCiAgICAgICJpbnN0aXR1dGlvbiIgOiAiRm9yc2NodW5nc3plbnRydW0gSsO8bGljaCIsCiAgICAgICJvcmNpZCIgOiAiMDAwMC0wMDAyLTU0MDctMjI3NSIKICAgIH0sIHsKICAgICAgIm5hbWUiIDogIldvbGZnYW5nIFdpZWNoZXJ0IiwKICAgICAgImluc3RpdHV0aW9uIiA6ICJGb3JzY2h1bmdzemVudHJ1bSBKw7xsaWNoIiwKICAgICAgIm9yY2lkIiA6ICIwMDAwLTAwMDEtODUwMS0wNjk0IgogICAgfSwgewogICAgICAibmFtZSIgOiAiQW5kcmVhcyBEcsOkZ2VyIgogICAgfSBdCiAgfSwKICAiZmlsZXMiIDogewogICAgIm1haW4iIDogWyB7CiAgICAgICJuYW1lIiA6ICJpQ1c3NzMub21leCIsCiAgICAgICJmaWxlU2l6ZSIgOiAiNDAzNzIwIgogICAgfSBdCiAgfSwKICAiaGlzdG9yeSIgOiB7CiAgICAicmV2aXNpb25zIiA6IFsgewogICAgICAidmVyc2lvbiIgOiAyLAogICAgICAic3VibWl0dGVkIiA6IDE3MDQ3ODg3NjMwMDAsCiAgICAgICJzdWJtaXR0ZXIiIDogIkFuZHJlYXMgRHLDpGdlciIsCiAgICAgICJjb21tZW50IiA6ICJBZGRlZCBGUk9HIGFuYWx5c2lzIHRvIHRoZSBtb2RlbC4iCiAgICB9IF0KICB9LAogICJmaXJzdFB1Ymxpc2hlZCIgOiAxNjM2NzQzOTUxMDAwLAogICJzdWJtaXNzaW9uSWQiIDogIk1PREVMMjExMDAxMDAwMSIKfQ== + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:23 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/biomodels/MODEL2204200003 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx/1.19.1 + Vary: + - Accept-Encoding + Content-Type: + - application/json;charset=utf-8 + Strict-Transport-Security: + - max-age=0 + Date: + - Wed, 31 Jan 2024 11:07:23 GMT + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Set-Cookie: + - SESSION=f7de3e4e-3460-43b2-aa22-96fe974d24a3; Path=/biomodels/; HttpOnly + - biomodels-session=1706699243.981.16430.383059; Expires=Wed, 31-Jan-24 12:07:22 + GMT; Max-Age=3600; Path=/biomodels; HttpOnly + body: + encoding: ASCII-8BIT + string: |- + { + "name" : "Liu2019 - Clostridium ljungdahlii metabolism model", + "description" : "The unique capability of acetogens to ferment a broad range of substrates renders them ideal candidates for the biotechnological production of commodity chemicals. In particular the ability to grow with H2:CO2 or syngas (a mixture of H2/CO/CO2) makes these microorganisms ideal chassis for sustainable bioproduction. However, advanced design strategies for acetogens are currently hampered by incomplete knowledge about their physiology and our inability to accurately predict phenotypes. Here we describe the reconstruction of a novel genome-scale model of metabolism and macromolecular synthesis (ME-model) to gain new insights into the biology of the model acetogen Clostridium ljungdahlii. The model represents the first ME-model of a Gram-positive bacterium and captures all major central metabolic, amino acid, nucleotide, lipid, major cofactors, and vitamin synthesis pathways as well as pathways to synthesis RNA and protein molecules necessary to catalyze these reactions, thus significantly broadens the scope and predictability. Use of the model revealed how protein allocation and media composition influence metabolic pathways and energy conservation in acetogens and accurately predicted secretion of multiple fermentation products. Predicting overflow metabolism is of particular interest since it enables new design strategies, e.g. the formation of glycerol, a novel product for C. ljungdahlii, thus broadening the metabolic capability for this model microbe. Furthermore, prediction and experimental validation of changing secretion rates based on different metal availability opens the window into fermentation optimization and provides new knowledge about the proteome utilization and carbon flux in acetogens.", + "format" : { + "name" : "SBML", + "version" : "L3V1" + }, + "publication" : { + "journal" : "PLoS computational biology", + "title" : "Predicting proteome allocation, overflow metabolism, and metal requirements in a model acetogen.", + "affiliation" : "Bioinformatics and Systems Biology, University of California, San Diego, La Jolla, California, United States of America.", + "synopsis" : "The unique capability of acetogens to ferment a broad range of substrates renders them ideal candidates for the biotechnological production of commodity chemicals. In particular the ability to grow with H2:CO2 or syngas (a mixture of H2/CO/CO2) makes these microorganisms ideal chassis for sustainable bioproduction. However, advanced design strategies for acetogens are currently hampered by incomplete knowledge about their physiology and our inability to accurately predict phenotypes. Here we describe the reconstruction of a novel genome-scale model of metabolism and macromolecular synthesis (ME-model) to gain new insights into the biology of the model acetogen Clostridium ljungdahlii. The model represents the first ME-model of a Gram-positive bacterium and captures all major central metabolic, amino acid, nucleotide, lipid, major cofactors, and vitamin synthesis pathways as well as pathways to synthesis RNA and protein molecules necessary to catalyze these reactions, thus significantly broadens the scope and predictability. Use of the model revealed how protein allocation and media composition influence metabolic pathways and energy conservation in acetogens and accurately predicted secretion of multiple fermentation products. Predicting overflow metabolism is of particular interest since it enables new design strategies, e.g. the formation of glycerol, a novel product for C. ljungdahlii, thus broadening the metabolic capability for this model microbe. Furthermore, prediction and experimental validation of changing secretion rates based on different metal availability opens the window into fermentation optimization and provides new knowledge about the proteome utilization and carbon flux in acetogens.", + "year" : 2019, + "month" : "3", + "volume" : "15", + "issue" : "3", + "pages" : "e1006848", + "link" : "http://identifiers.org/doi/10.1371/journal.pcbi.1006848", + "authors" : [ { + "name" : "Liu JK", + "orcid" : "0000-0001-9973-524X" + }, { + "name" : "Lloyd C" + }, { + "name" : "Al-Bassam MM" + }, { + "name" : "Ebrahim A" + }, { + "name" : "Kim JN" + }, { + "name" : "Olson C", + "orcid" : "0000-0003-3681-0446" + }, { + "name" : "Aksenov A" + }, { + "name" : "Dorrestein P" + }, { + "name" : "Zengler K", + "orcid" : "0000-0002-8062-3296" + } ] + }, + "files" : { + "main" : [ { + "name" : "Clostridium ljungdahlii Liu Updated.xml", + "fileSize" : "900840" + } ], + "additional" : [ { + "name" : "03_gene_deletion.tsv", + "fileSize" : "41878", + "description" : "FROG report" + }, { + "name" : "01_objective.tsv", + "fileSize" : "82", + "description" : "FROG report" + }, { + "name" : "miniFROG_Clostridium.xlsx", + "fileSize" : "16317", + "description" : "miniFROG report" + }, { + "name" : "04_reaction_deletion.tsv", + "fileSize" : "49062", + "description" : "FROG report" + }, { + "name" : "metadata.json", + "fileSize" : "351", + "description" : "FROG report" + }, { + "name" : "02_fva.tsv", + "fileSize" : "59189", + "description" : "FROG report" + } ] + }, + "history" : { + "revisions" : [ { + "version" : 1, + "submitted" : 1650489853000, + "submitter" : "Rodrigo Santibanez", + "comment" : "Import of Levering2016 - Phaeodactylum tricornutum metabolic model" + }, { + "version" : 2, + "submitted" : 1660305342000, + "submitter" : "Mikal Daou", + "comment" : "Correction of model title." + } ] + }, + "firstPublished" : 1704970398000, + "submissionId" : "MODEL2204200003" + } + http_version: + recorded_at: Wed, 31 Jan 2024 11:07:24 GMT +recorded_with: VCR 2.9.3 From 2a462b48deeadcc78a8f464d8dcda0f50eb04af2 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 31 Jan 2024 11:51:01 +0000 Subject: [PATCH 092/344] fix the exception handling #1729 --- lib/seek/biomodels_search/search_biomodels_adaptor.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/seek/biomodels_search/search_biomodels_adaptor.rb b/lib/seek/biomodels_search/search_biomodels_adaptor.rb index e3ebe002a9..3b5f6bc886 100644 --- a/lib/seek/biomodels_search/search_biomodels_adaptor.rb +++ b/lib/seek/biomodels_search/search_biomodels_adaptor.rb @@ -14,8 +14,9 @@ def perform_search(query) json['models'].collect do |result| begin BiomodelsSearchResult.new result['id'] - rescue NoMethodError=>e + rescue NoMethodError=>exception Seek::Errors::ExceptionForwarder.send_notification(exception, data: { error: 'error reading response from BioModels', item_id: result['id'], query: query }) + nil end end.compact.reject do |biomodels_result| biomodels_result.title.blank? From 3b45e1fbf9c9550b24c7a9030e0d1a5b0ee5c966 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 1 Feb 2024 16:15:40 +0000 Subject: [PATCH 093/344] update simple-spreadsheet-extractor gem to fix #1728 includes updated javacode, and error handling in the gem, to ensure any errors are logged to stderr rather than stdout, and therefore prevent uncritical errors appearing the successful output --- Gemfile | 2 +- Gemfile.lock | 16 ++++++++-------- test/factories/content_blobs.rb | 6 ++++++ test/factories/data_files.rb | 4 ++++ .../files/spreadsheet-with-poi-error-logs.xlsx | Bin 0 -> 98076 bytes test/functional/data_files_controller_test.rb | 7 +++++++ 6 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/files/spreadsheet-with-poi-error-logs.xlsx diff --git a/Gemfile b/Gemfile index 53db06845e..dd46d46965 100644 --- a/Gemfile +++ b/Gemfile @@ -15,7 +15,7 @@ gem 'hpricot', '~>0.8.2' gem 'libxml-ruby', '~>2.9.0', require: 'libxml' gem 'uuid', '~>2.3' gem 'RedCloth', '>=4.3.0' -gem 'simple-spreadsheet-extractor', '~> 0.18.0' +gem 'simple-spreadsheet-extractor', '0.18.1' gem 'open4' gem 'sample-template-generator', '~>0.7' gem 'rmagick', '4.2.5' diff --git a/Gemfile.lock b/Gemfile.lock index 8f9cfa2612..7410cf9f69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -186,7 +186,7 @@ GEM citeproc-ruby (2.0.0) citeproc (~> 1.0, >= 1.0.9) csl (~> 2.0) - climate_control (0.2.0) + climate_control (1.2.0) code_analyzer (0.5.5) sexp_processor coderay (1.1.3) @@ -774,9 +774,9 @@ GEM rubyntlm (0.6.3) rubyzip (2.0.0) rugged (1.1.0) - sample-template-generator (0.7.0) + sample-template-generator (0.7.1) rdoc (~> 6.0) - terrapin (~> 0.6) + terrapin (~> 1.0) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -821,9 +821,9 @@ GEM rdf-xsd (~> 3.2) sparql (~> 3.2) sxp (~> 1.2) - simple-spreadsheet-extractor (0.18.0) + simple-spreadsheet-extractor (0.18.1) libxml-ruby (~> 2.9) - terrapin (~> 0.6) + terrapin (~> 1.0) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -875,8 +875,8 @@ GEM teaspoon-mocha (2.3.3) teaspoon (>= 1.0.0) temple (0.8.2) - terrapin (0.6.0) - climate_control (>= 0.0.3, < 1.0) + terrapin (1.0.1) + climate_control terser (1.1.8) execjs (>= 0.3.0, < 3) test-prof (1.0.7) @@ -1073,7 +1073,7 @@ DEPENDENCIES sass-rails (>= 6) savon (= 1.1.0) seedbank - simple-spreadsheet-extractor (~> 0.18.0) + simple-spreadsheet-extractor (= 0.18.1) simplecov sprockets-rails sqlite3 (~> 1.4) diff --git a/test/factories/content_blobs.rb b/test/factories/content_blobs.rb index b968107ef8..392295c3fe 100644 --- a/test/factories/content_blobs.rb +++ b/test/factories/content_blobs.rb @@ -313,6 +313,12 @@ content_type { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } data { File.new("#{Rails.root}/test/fixtures/files/blank-master-template.xlsx", 'rb').read } end + + factory(:spreadsheet_with_error_logs_content_blob, parent: :content_blob) do + original_filename { 'spreadsheet-with-poi-error-logs.xlsx' } + content_type { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } + data { File.new("#{Rails.root}/test/fixtures/files/spreadsheet-with-poi-error-logs.xlsx", 'rb').read } + end factory(:blank_content_blob, class: ContentBlob) do url { nil } diff --git a/test/factories/data_files.rb b/test/factories/data_files.rb index 630776077d..6b414f23df 100644 --- a/test/factories/data_files.rb +++ b/test/factories/data_files.rb @@ -121,6 +121,10 @@ factory(:small_test_spreadsheet_datafile, parent: :data_file) do association :content_blob, factory: :small_test_spreadsheet_content_blob end + + factory(:spreadsheet_with_error_logs_datafile, parent: :data_file) do + association :content_blob, factory: :spreadsheet_with_error_logs_content_blob + end factory(:strain_sample_data_file, parent: :data_file) do association :content_blob, factory: :strain_sample_data_content_blob diff --git a/test/fixtures/files/spreadsheet-with-poi-error-logs.xlsx b/test/fixtures/files/spreadsheet-with-poi-error-logs.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..54c90e9bf162ce8c258d9f82387ae072df07f23d GIT binary patch literal 98076 zcmeFXbzfZHlP%nMa6)j0gy8P(k^sToU4pwe4#6e3yEU%C9fG?%!QHjt>3n~4=gxg* z=DvZs{l@`5Reg5twX0UGItK+A7+5R-Jm4Jw03Zj*7qG7_LID6Or~tqRz&mJdF*{pl zQ(I?!6%Tt;CtXH&8*8#WSZL}T05rt^|9$=kBQTmQXSc$P5^@f6M~b2ws_|vPMfZJi zsEdFt+&omP70w6bDO_<%xmU0SB~3_=3l6uiOO1hy=Z4fL!@lr(qqa2H3XHmX@Mp$5ch;C0{Q_?*sLmm+UWL+~T>r)xMujv>2VZe}4DVG< ziuIiH2@9Q~@4RP{=TY*S3O5Oup`8S#AU1Rt`61{Ry9f}^A7M|Dr1p9zn=C|pNzNVf zvlvkPfe3TzjKl*o00W!9IBy}&k^f<6QY{YFqs*#HsCsn6V&hBC|-|pb*q9#6!#Xi z7>RhED~9goIlp~dz4!x`eM#6@A>*8mlM}$373o$BY!NKn%z*n`u?+kpVY1BfL*=l#Du&?(wIv zE;}y2Ws9QS&0V||3lwcNr_-POLshhf9d z6vi+lR9fKdgY)lGN=lNH!D%p=dygmFaP@5k5>6k0tErOS`^;^u=*1egOuzH)S!x7~ z$JHmeFvr(xpCBmw=aQ-7XWDRuq(!B=e^Q877xR!tiL$x@74^NET__A zrj=TS`4k7y@(wFK{IY=Z$h`uso4LV5>1)+sWR=~L9PG35s+4kl_|7-i_S_Rw$-@X} zDB5-PWKoc)?*0;Ha+v-UcBO&oH_acgC`+RIbRml4$#@a9lMKD~x0rEX*CgI8QL-F( ze`C#<1Ti{Lx{Ba2#sVI^zo@-nRE#nrs#Dx4Rnqb+jdzL&k`Cv9_gxj*{8BFDMZ$l?+!aEO;nj>0`|xm}>yQwM zX1)T@?_I#O`|t}Ge0oX-&5kj^*&ssDV5(tJ_8m8A`ouKsR^mTozfiR{k#54v z)0Wh|f8gmUDxEeD8sav;y7N8a0O#Jicm^b*6TO+pbPn^k9`wG%3J((xNgt^S4MiL? zMCUt+fDw}NWo>JHL3a18tuYFpk~RN!J-~%;nYZTlpHG=9}P&sa+R?RPP3;0=QoK7lz^ zv93MwJUAmc$EMkS_t#e4oouq-Q+2~bod=z2ejcCXTTiOrI@3zSsuhhs&M>`o zMtW^eBe+>Bux}{Ovd@O8Zr9YMokTpdv#}z#l*H+#Wq!kqsLmx}tA@hHI_EM!-6sh) zu^hUot{y}v(S(fe7`+^ukyNvSDiAdEDPMJ=R5#>a{@Vvu9So!0+y2A3h0t%y5Qey;*Bo{rt@iLQL%1;6rJF{YuHuVsdKa}rIXn{z)9 z{#WeNr-Zd;25HzpBmjT_@D2*XKL6_3|B-|KSO12B2ziL_|Ljj&lHB)RW)$J`Kyc{h z^LD`ov78rvpk5Q2E60ztpwF(=vo_#+HewldYAv*ZWIr2GbMqP_%PT}Xa93Z^nSvNzh)6I2n4?~y$SKNCL}keeeie%deO7gEOQDUW{%Gg)@K zA8U{2P6Ahg5SLj(cT|7-LDw)}cdfCSBXc0hCPZrxRXK*-phkSqLOVCxt5XqMy-??O z-m&-DQ62L`7Bo)hMy&{8m}O@k`{}Zi2v294>rQzr3Dx#ORN1IW!1Wat>{N^x%7!S* zwq=myZpzm%001zu?g5X%k>0PyjD_eduTQ&VTB ze@obZ{^0!EGXF`}wuJv6GuFl$eaWWRNM8I%+N8lyVyvx%X|kf2rqAb5aQ$B;?rM{T zhuS9inN#OHoz>cuWe1#KBFf-JKy93>To)}qEzNLothJKIsRWD40WMy?jh^HYzk;R> zL@|SVO8v?A?Ws(aV0dH`(ff7_ucr(d7ZykStU?8To9b>1JtRP#zE`$(hH=Pe60yF| zb$-o0Z5^lfyf_+rzyE>ZkAn0R!8$heK-Lx0iOAGJJZEO^gX-;x%X@bsHVH}OtNKGn zS7o^`5+4KyndMwqT50q1Eu$B%xu?SzHOhXvIi_vSp#&UyrK9O98y7uG@6G~Shx6yG zg&-*XXRVxyv49}*{L}vpM;6w92XZvN`|H2-+BwGF`dDK?82gG0w&w-KEtDv0 zI%@i8(5J++mGlUsT(Ybi?$ObEAWGd@rZL8ON`$#m^_eGzbVvnN@#g76n~lHH z2U?BlF5Bkzr4FOX28P7n#S{d=;Q^)dH`JyqA0%~oze!iHIYzfMvhtvQ7XAL6-bj z?i=t`18#%bA!`9ny(lE3!#^Lru1dcM!MLds`T`cQVZ?IRhs|*G2NJtmu4T?Z>a@m~ z!0R%qQxu$@Gew04ro_jhUm#2QltnMRBKktV;P-r}4d>)ISobNN>F`>DF!?9pZ(t0*jKKn_JW*DjCv zBY3#D(=K2)!)@}q>f}?Xj{NwELabt3wJS72Z%odXuI1C(giLzJCoV#XC3k*bq`i?x z3it5P5i%L|FR>;Os!w>7ij5g>`E{1$mq9CKVZp4$F_(Wlq?fFhZzq@#5$+9TmtA!T z>ujj>m}P@t5s<`J@PhJ}dWn7xAJPs)zs|5YgbBNn`x0dzmRI_BYAXr=XG%Vn@pBQGw_;j35n)C-+q+f zj(n_MG#%e#SbPG%{Xxz}iOPlw4cZ^8O3Dn0NO?4)^6>tAg_CF~S^z$4k zKp3M>U*Zg^mhF@@SwZWG08=EJR_W+=rToHmFQ?rZO zLD;smZ{bm1xwpS|0$xAIy97a{+YP>G6FQ|<{4wGJw$P7Sq5Wh zBz=urh@OiXyANjU7@eMy;tSh;!sQ~$f1$yn3O7fkp~U_{Yn(Q@had&?KS7f|_d~6~ zD^wLNu*i{4dY@Pb7uSHLsfiUof7>bS2MRH0CHPFavEYyv-%>(Afu$^5h!v#T|8wy= z?>(1+^BZs>>&EF=-YjD>Cx(3RQVgDOv{e7eV9jQj#j}Qdv5E4w<+MKggF8_Ls*E4o z=xQJuZm16Of?!JdcumicXa3+Myzw9r-*KRwTvtix<6KX#0w0WcC9x~hh=Wg_yv{h0K4ge5+|KAeGKW!)@LC$XbFM-gV0;|2{i4dS+-7IV)X!QK4H!8+EOalE$-XPcElw=OPE#no@K(<5|N!S+` z?LBRTyb}q@I`%J;qwnS>*$j`)3>V9kFgrAVaZLuX_4(sKUEeoYfrHKd2ylN~wcU`M zPQ!UmSg7iLn)?%v$7h{${zue6i@Z1WCmtSeC2xde=JvPG@ru)$cI6l!Q!|}97=+Au zK3simsd!^VA!S8Cm?PfgqXp}Hc3a#`sGh=}w@FSvQ`Ma8lz%kI_!Qy5uJK9p zXJjaU+Qk;_*W{Q$)2p8hNlV+LnG;`wsa91bh*aQw{>*U5Q>RZn!9Nvl9tbTK91lh{ zwAyat?|cfo$PsbP6oExU&Tc;ndL!+yZg!lcS=-y;67R7zLa|x11aFF~B@2ye#P5Q^`v%whL z8$8d5gDDfK;??K<{FLH@4(uAi8{j`Y@h`GSGPH$|MR1iUxA1!$Si(=1ipk&cjJK;A z-y2tLXmXsiHR(P`dB-?(EG^+LUpfkIxDKw=e6QN*{OyvyXUY0X)G6uVX*d1HF(LG* zqH?-YfCt=*-)I^4IWO|+xLvzpsr`H1%*f@Vv-Qu9Qc|S6NX{psp(2BV&OZ;h`H4MB zF-s?ndda>!qvYmi5v9&r(`KlcblA6;peD+Ghl{(@8ZBMq!zdm1dMC46!)VB}nkY#1 zbPNUkunpC$360x3ONUa~5MZ_Oodtjp*G&!*s+K~~PG$~FF!uupS^|;>(u5q=JRm+s zV)tSZP~2fCl@em@M!G$)dJ2Bzn1HTwN_!S@d7bYe2!S{-_89m?J=5%vg`l}sk1)D6 zumSo^{t@Izl^kM$B@0FXG>SkmjTVu4%F#`TooxE(7PA>IZ&QnH^+*e)OT@%J`9n?O z>}BZhg!ZHa@Hs;V%#)qWhRh^VE#W>>Cte=*l-BPZy$BJBO}Vs#=e?Tf>zyS4TEr8jE(aYYUB<~-hL7nm?{6Zkz>^lpew9~AUpU@uhtcbvc5Z?#G>eU%Jl z(0Z>I!V-pgz2WyC4%s?>#cD+uBSgRd!1qXiRFo)X zZ3(sgK&)9qFqWOQR#fZjMti>BT=0>tFQQ<_(j)0aXC?T-)5GhRfB@sQ*-1?6nWkI% z<(A{3%g#dJ38&Ct=sgd3KmplsIH$b9uU{if!d|wY@&jri-mCMGCLZy1vYXe^RCHd%}i3JiiE3`b~nMd>(|TS*W> z3;fWvkk}PLA$m?MtO6@Eh7&BI%rp(sybg!AEvkpDnwsc)xcT{ocg=Y(JF3vx;p#a$ zn479{#R0F#YqQZ#DCBb2_3ZiZkm;Y|`~N?<-*|8knSe}JyNLfS7qk6Ux*0RtcInJm zfX(|a&*(`T-K+7Oiqdm6d3QExt{$$V+xQjy&}Z6qlxxG>Wwr6mcvgP>+%@`=qz z#3AO{E$U>o#F8a=X7%h&mn`xuc2SxVeC(%`*MDWYLNLOGbS>DAkNd~u^6SO7LDP`S z^u&6sd2Z!r3{%(jYnvzJMgtw4!j%^)7KZ_PB9k|mjgmjc+TrHvbRNCa=tkFc^FYSd z)e+iaVE#7Em;J+Jvf*$F>2Q?qC$B7swY6b8+ojYynolv6>WO_qtL75)vphbL$rJdd z$8!ooou7!-S_KbB9E?XRv)#K*DX@sBF?Nd-`Fdz@ zFJtjYRvqdc%tuWi@4!&F+>uQ;CvW&)HvG}!J^di_U`30~1dAV1$eqP|#km4mI|=L= zinUPtGD`?b6nEo<0#~?yhFEw zS8{Q~*DgEh#O>J|c}j4BmAd&)p1FRQg?=PlO$|EyGa_?^aWmLjMCWlv-i`hzJR z+G7tKF-=;hpn55^=E0^eldnLqJ@~ zFB+5fk&An~bc@KiYY<}!Ys1h*x&4FX>*LIc$;rB^s5@-XvT5^GM=IvFJ0-hQjiop$ zU13d{$3p|(HUXW+dvUEe_AepgY??dBhE}A8DYZX!Kqx{MiqFV>Nd5c|Zi&`9QZp~_ zGt69p78JJ5Fkuxc)1C0tl4jmZ)s z_Mfk)OhoZIrcg& z^?Ea(w9uYC>UHS3rmTKxL``#?qT@W0@@HdXerv8k(o{5a7b%{Ak=?7C6cQ@n{^e#o z+qg5I;?sHoZHY++k*IcrFf8w+;*bkj+X9AHtGa^70lyO;d{?Kqa{W6sRKntke?sOt z8f5)$)7<9t7prd*qGz<|b%E}f-j4ZxQ2GU?UJW_{$a4Vk?V`%O=7qvfQ zX%%8wvI>5ZJu76T#kcDm~UNE0i znBz-~DH6p$e4zVM&l2|qGTO-wNRClX_kUKy^pVA4%Pp<@#clP+&7rN9LuG&iS$9^4 zUBUIE_`xsiMg@a?grZ$WO#%26J-i@aiHo_;P1w<{#c%a8u# z&8RXy`W{RuJ(CpN#PjD-*CV}~I(38(g3WkvxD@dFMo>-D5I1)u_;%)ZdGE5`5Z66( zR8prm^n3iSh0f8M2NnGqYwvGLNf??z~WGXw8x0QI#gQLQs%Igt2O7{ z>6$mp^_Y+oQK#84)68jGMznUO`wH$sYHlBCQKj%};YaGno6vhcAE$6cvot6!72(8d zf}a#T%#(!P zQe0xc(~-M%)d_4?G=aq_`EHCLjEz`dZlIpz_ufO?|B5Gg%x1A+gCI|S9AW;uCqEqj z@Wjuh=w)WCe|UmoBf$pK9>b8mD`#3wb?(s7h6sUvo4a9*wc_baV4PD$8Y@bi`|=97 zFQmu%OXx=r4|Cx!dS$F;K}U@*rUN5#;T=A@Xv~iFi#+-j71j)_2xN3w>I@{Nv=&c?&Iw8BTrqkg`}q*hVH|M4BAptTOHcq1&vk^ z>t#If2BPsC#~ysdJ1_5iF){5zOcA zE>g)UfDmJH;1mp+dEMX?eb_#5(mCFN+pCdQs)kFi#9>OFW96I_qGM0fY5uU^y9d|AB^-}!D+5{QD9kBKup(rY6M(>|;}zZnsd6^` z6<0R%#aiiwYs5Nb*J;dtk6R*kR76;~v2XG2@aY{C*Wg1|B`V%SD3^IzkNUuYbZ9fZQ}| zv^JZ!f=or;Y%4EISh*M*V&T2_ukW8&39PhUc~G+Q1`u0m_9%B`_hP& zvg@vcHePxy)BF<4*(qkt7B#&0Uha7QW9N$i?xC_Ijtl|=nU3!e+8bKU^K1_?3q#$b za^FT>yrAhzwKD*^XE*fZ_8g;CUL23rM(EqF@wvA(x6tdOqb9J;X5-BnH?atdZv6-U z%*xyre9qi%Xr(7cYeL;_HYfgljVs;q4%4?*CTtOnWXcTYWfXryoZN_BS# z`DMd4M(9vzeKpc0_3g`?4SqH~v9Q0rCf1(ViIZVt8~s%gaPp3}b-FmLbMS|qO$e<_ zs4AHbmRqOT$u}sMMI>1Ey5aif5r{GuJvB1dWGmnyO+IH28Y$yL?%QK6ykm)GvT@S% z)zrmtaJh;VnOOEGd6%qO%-DB!WxaHV&r*?3Z{&m@Ysp~0u9eYPKgi(6k-ptF^QQ`b zn^AcwB-IF9=67mJ*FiUr5|NI`2?JGUkQlBo(S|CP=%y=?gD zUGmr-qe3ik=9ahVjA?SCo35*b4t?u)c$xhjPcnZES3b0ienGz6lW$XvnfHo1euW%? zh1U+JSQsOf28b5K(1kc{(00$S0v$0dYWk1 zd9Y5;8Q$(Of_Ln_`U~w+!p+>G^_XFJn@Qif}v|l@L4Pk7sFGAVe{~VrvMkl>e9~3Q+%$WPy~12JznwK5cZ{d|yh_Hu|1PqBd@%F4&RrKPLS#iZJ8{h= zt?~DXg7uvH8kH|FbK3>5>J?(NzcYTT*=7a5!&kp{ixV#5HfOQnQ?D+mE59JvFL@dK z%bXf=$~xKo$7T8{ldcQqrlc-u^Gu^t>&YLoIKy?H<7<{4n;nR^=R}<`(vN@AvVWLq zv5dJ3KPF&$E{LjFtY^Xp{cE=^hefE6oNOF}+`ukoO3zm3p}_^IhQ*TeW1URQD48Bd z-yK>FAJR(5XkX*_xiVPS=5Clw zIX`U-KUCL~2>#M4S0HRnF(WrMAE4SgW+p_b%~5!-oTe^3DFbh|zoT#5cES%4;;1qi3PY2A%pFEq|;~g<;LJMYa3a!2Zx{jvWxBfN1&%K6!;UdM$6$zh2Gl zoh|+ScIlq&fTnsLF&rV*_tg_(BRA*9imB)rvHP||&u2^jyP2}5FtcyDJbK?o{En7k zXv0tEsr$~1y{0LPu;nw2T(E311%(eRB;^-K1Qe}tCCOc-v@bpt-S!?}9f!t+yh2h5 zDytHoKQpEk7sn_Hr9*5*7ok=W(5*l3z70t?Jq`9EBo;k3B?x&%i&~RQTD0b?r~$gO z=P$N=*fsbIyOD%VN}Nw~G5c{8gBAsXPq|<)uvREe6uxLF)Aux?B#sY`rHHm^TtQk5 zjZOK3nDsFd2f-){cGR}TRZ{NsKeY|!_xZbVQOegVdDP74JIE|8`<}Pmw8UBPj3{_f zv>0+K4$`KYZ7-I{nbGhO#ny6AiMIH(5LD&K8%HW8UFD$?SCyCN7o7jbBzc==268^G z!R$-d^$cDVu$d_6tCt-8R%2b29%3rgFUgU~7SwW!fwV36u@=(mk8SpnQs%||l&vej zQ}``XM;F-Qt3VKW6o}x8TK)@(od~uwS9`N3>8Yu-;Y`>Km5qwJqTOGozieTateAUR z*_4CSlCG!P4$1xWu>qACzQ8hV^fIz_oH3q|MdC~wJAw@`#!7gGe2q6nm?TF!TS?1p z4$}5mrM~D%*44`wk-1Xu7S_N_xxkM5eSNvZFl!ZT1mBUxBk$Ew!!WK07%^)!F{{E) zef1R4Q+nfuQ`CDG+98m(G(M%-c329A@Ps1m2*nH3uAE)&s#IXVG66lr<8R{{pV4ev z65**|Q#POj&j+4yt`!hTdHl^!tf*9q4*EawxpUg+6=ds0_Ln;b?~UB*1%feWINiWl zU)jrkrN_-_$AHMzPb5G0ol#T#;LNLYu0u{Ow$$`3WP#K09pQ$AD7U8BW_j>H49V~4 zJQR8{j~TkG`zeFeR>)B$ZLnHH@+S+L(~|oU;$exLp3{1YlqBVan65U`f=SrV@~9mm z2Z_^#3>%fX^Dp2#?vrcm^gg7gs(mHNO^~)`cAexu``GM6tRbDR4=Ga!;UVjaxvg+` z^DWa=90^lOZqTb=d`?5ydn-w6AI-cniSADi)A z!kQumg2^}+$Zs8W0SP!WM5nuXpQC7s?k6H^AoQFs%z-l#)X29?qA%jsdT>K>iY>L2 zvds-P57B_cshE=%F|d<+8ZD~1lS`_wPG7`BK%X^jz~RO=@U&!1ABKCoOKfzHg*URU zT~k4JjD>uM5e01m9wmc_geaQ6q&ak(`+9+nZ6Np<+kRK!LyH=&Ks>)VX4wOM!& z7IaxO?R+SRI`Z2$AJF}K3Sp<72!Fed+hy{t#)aVWv*SRWNB_bfMbT@G&!Srw*B(1B zlg~+B{!$0&&t;WVp0o@7r&HFp^JU8Pb|W#7T?5zi#=`8aP2%89hlrj(3)s=?q!ntR zOL@>)^g^tdgcI7K4o0TxgQcWIMJ;=}ias?fJ27VEgY}xk;!}sFAB9B&x zd_NIlRuY&RD&Jp{ClhPCgr_`QeT1pId|AM->oznKI*cMdLI~BiFY2spnkA`7av6EdL;u?s|U#xp6aw`FRy;#f-FH^2G#1AHR zNz`xa0`MkXih`M&>pauOUp(4b}f5SJJky#U2`?JRX^@>Cd|0ioU-*Fn)g> z(k@wNsuuArxP3VOoLdQrM2uv(W5SWtI4O8RdQ9=uGs{q|*L+CdtUE06kX|%=9oGV- zVX;?D?w4r~J=1WX@`9guaqWRRl^QSPQ3&Tj7`}5(BZ(xce*H6@rfra@?M=Y^x+P6ly*hlE1!M-&4 zI>o2mt8B;5prS=R>zc}`dqZ88R6QYXYTwP?35r@boiRpuaDN;=?V#Q3Eu6;zbjku9 z+#q}Ls@nCGHL>%N3gMk5iQqRUKgKs45zWUjqJvTwrOOyiFb^?3Qo`9^erkDPh<=VG5sZ?Lx-zCbK8Bt}ImSOrC8M!Lp z%9IQHW&~3VJA!mUwOZVq6U`G6G5+HdRmGMHCyE5=NxvvJYCJi?&;BD)oQ=^4W>CoaHAO z>0ZNxx;VLW{*uR~3{tNZHlT*fmc+yO!X!iVF|xYAN4{>lEruENou=J5sJ9eP_Dt2M zy|6@2D644%=OR9FG2a(&OLMLCv!btW_PeT%E~yg3&SRcAs+DT_dt#GOy+3AHW4kYw8zKO7 z$Ub^<2#TP+Lq6)F3iXdZv4X!1?DZMO^YvMXz=dS3R0{7*6q-ZxJEgn_FGsw^k+M;S zNpv*jwQ;zdj+Mel!*%|mF(hwnxMSQ|Z+Q~74;$lm&uO>pov;KJX$!KixppMV#k`kv zN3%>NINAJ?<0;wTo5CYYL2lXOyVMtYH6bZB`oe6X+E;3XcJ5bE)rVO)ww$Z_*TO18 z`Aef8VA}gcdLm%Wfv?B3u1c-G0*atVWCn}Od9km1jrlF}LA`Bp8?Y=(V5K_Ae>pD? z9FTWWij3i2&kj5aWz1>${G`0b1`gF3_Ys)W2dVsel~G*wvZwsSXPP3+T^)SeIjiZzW3E5KCOfoZ+Gk=7|REh6qZ9w#KsSQ$e0@?o)!V&uxB|JwkiRM7#U<<-(W7OL-n*mHA7KA)k0v{mg%c z$=k6+=k#R2S6)5Sm(Y^(XU+9u%(UMz&o1HY5^(3pIbV3bqJcGjoz` z@EoMVuv1^j5aW2BJFlxI0D5E>mY72l&L?dbpqS-0n_OS~ICmYbA-{267gT#^Rrf?d z=(0VNv!g%0Nm5wP;=DgZLvh>B>w+BQrC}ub2IwpIEx5hN zqcz1XEaI6yw?hE zqVyRKZa-m=ZCq0zzwGZB8S-B5N0oaV9RI-l{zu+9g>^sz4WD*`D#qL%>#XiZ^*lx~ zeSq6T_WDkMh4yx2VASYQIVs6q$AIZ)l|0Eu#L@G*o85EV@Y9c&+hgZ-{3Dwamw4qz z!=y{Y&-D4T-Wm;xxq>cGN|#_k8GT}4?qaLokJq~|xAd_wjFU%|FL6(TBh;eVEt_!s zV!ED8PAxYs*EFu~lE=Q;yPCYf*MWE(RjEOV(ib@F9wWr^)tP~#*3g#Nk$c?OiaWz%WOU*+T)SG(OyH)EeLVx zoNz2#IqSz!RUFF;dJU+(={6i{ucx>rj;uLq-+(;J?VP|wI=rT8`u*9hD(~lOK=|En zW0N`U9}-6hF>#J&GF-kskYy$Up1?jhS~TZ%SQ2-*KU?H=yFi}E&$ljjmg_?7x$UJd zg0uSOuMvq>EKFtROw>7$o-3tAf^dR-5#hnGcAM+?yoko}pj!MiNu+w(>l-BsS*B=D z8%;?+44CWje3G~5)Ro=vun*R9F8kP{I3mas`H}9eR?_1+mT@I{L&&OSQ?FrdId65w zG@J5aizSV}Kg`6EZhBOqK@gQCq2=eO>tXp>#9}h1R?~+%1$QBn<*88CnM&&R&Kzjp zBWPG!mq*`XUyZfWGaun!rtCpTFE76f+j`l*E*D!yuU0XSIhf$6)e2r_t3It6YTb*x z4f7wf5lDYhFk_FTxGzQ4%gD{e^c}J|IKC#`I(q!D%0?h=o4uLK*|`i7^^|aV{m3b} z(R?Xl{^&blae#a?({{dTw8l!1#qs2Y@llPoL~r&~UqL{q?J$;iT6QqnHyO*}rO13&BAv3JbYFjSupd<}=-QDxByFAKx9tsCCL7kr|4p z4msef-u7OFPCu0*r?{96RZ>CY2|d$+uviI(L~~RG?;&~zR>^sIw@TVjO!4*4Wn{h@ zWGDDgIALJ6Olw%ZF=l({Nt4Veg=%Obula~ul5W{5v&9cElaN94mzrN0BlM9 z@+6BjTFc>tuqDNv-?0v2KGxWuy{Gi{DdwlQ0a(52U32<%E|{0Qe5;e#%@BOA=o#U^ z6VMw=SbQ>F2T;gZH%e<_+ok#d2ub0c(`~Ag2HxORGq^`i79%`7Z@(e;Z7~$NKp1R} zw{NyIz*wUFEgy|{4a~>z*s?Xr)Un?~q<8J&535+?nc)9Y(7s^8Jk?g); zP(dRuh{*8~gq2y!35b5TQ1=9U7$Sg-eNY1+1IoXm!;@f+3;9AQtwL<{Gj{8O*5p5H zNXyp&gdj6kN#~Xr$}!F+uwF|Zc6aQ-gs_pe3%ClVhtFJkddlUw={OhG#EQI{gsbyd zX`gK&q2$*ZV_XE)XKT^mUK1hf%D?)#;t1Osvize9^!Adnn<(tWZ0&e$aI$p=m*iLu z_<>g<@x*@akLO+dr^jG9ExZ&YOl9$Y}&?bUT+hAvsiaqVqEgu_W zjzQ!rza8pMV7VrE1Gh1Y-*2zoz<-#dP3X^)noL5O3y2I|L$PB+k$V?GBbJ$JSMWf< z!%u5LY?^!lBO9Aufo?YkHwASLtc(kAUP4@cZHfjV)^qGi&WstH zAyTE^&h~ADWps6Z@O|mujt-U^tg4dy!VT(-Jg!AG?GAAHxpi+pz^o7k~wkWCT~GA*^NH4=kj$1 zl8Ok`^~45Ap)c_!oOa26%JSy^yq=oymc3X{|wk4HD8`;+?G5^ zV>1u1Ah-Dh9VYeyz2q;vDGl354(~jZ7Q15(L=6qSSFL2pld6Mjg#h_Q7XyU|xtqT) z&EX<+xbVyQ&u5I@Hgp!?+Sxh!aZ900giPqQ>LfPY>-{D(bbeB$BzTi~yGYGd9Js!| z-Nlr{JEyI4feb1c%jO?Y%=j!#Dz@6onU;MwAGdK&E>sow2ZA(YPRCYbI)GK1y_P3| zY19;jx$VW<0gc^#e|b9EAM}^=>ugrTMxGClGvI%Nw=`7xcs)vsqaeh+O4<-t-i1j3 zc|@_cgh{X%67TOstojJ^&D8XD60XZ4X_EPb69Bg-kn4Xm-t}nFNWW%O_e8`eb5T4N zUicU|2#|Z_nNL6@+8}fq21)qQ+=s)7*`9MKnjAX#cWIzi7Q-_?aA%&*cHje5)VaYE=AzauO})bHIN znX{U{<2iQF=lVnHT!-Y{4F2Qd8b|a|UHRBR5QJ6XP z!jxa=zs9XBmEa_hNcy>ICz>VZ{^h=K)!$+qgEpfQu&LEh5`S15zh~V zziZ?oFPB5luG8mkO<5r#9wwg@Yn0+~F9cW_@Y}Tl9He9{a(oX0NV4J%{skOvCNDP3 zK5zJ4Xn#iVb8o#AXqpz1dZ?V_3H~hhN^AJsq+@C^O9ixT`u3boNK%tzxg&$

    $zj2{M<G`|iC$-FQ??WK#_lYCtU3cwITR=bd3p}N2N!sN4i4MzlpOj+C}qtb#I9A|_{G1jey z1#)wXNi}F1o3ubHO?zZ=?x~YxYGwJs0Wk(}*}_NAeKbCX$B*wU1V6D0gPZSjYoe*h zyEFPJcz1c#U^x^=Ae{=Pt~#3haTM}QlIlXF*oE|_Zmu@nLlnlhz{EfUSuo(TO?S~A zc8|(plkFWc;T%IA!fov2#ivhQ5RSTq(wjsF<&Z)UAC; zttcc}|0~#2=mA!S+}!<-IUb#dAV=h2cv0xN(xlFSSOYj?;gtJWuXgC8P?2*x;xHE( zi+A3zFa5RuA5U)`7G?824)3xoOD{+x9a54a9ZLx)E!{03B}&(_bVwrzNQ-nxOM^5> z2m;cLOLxQXem>v#djH)$*O`+ur|uckPQycDK>I{%Wy2)YK2jLz!1@}}obDWDI3bu0 zHDaIGv2k=iT?8g&-^y)B6%+3W>O)SV`!3*DR4jiR)-q>W+=Vcl)ZEe9Rm5da_vl-C zpqL|{{k{dHjR9sPHc@WSow)r65O zH{_;2?`owl=ZamOiru#H;QMjSAJPQ;&M-_M3GEqG@a+vsiGiHs$g~A7A9~MxBdk%F z@_GL9f-PJ#T}WhD@L4nDHwj@GQ8;m-ySRYQBf{Z6Rz8ERDY8=xu76New@A-%%a1Hm zI&dp zF3aw#ASOL$&QJzOG)3u4(e)py%1I^6NR)v&YYpO%a?4PcJ>t4^%|NoH0TOpY|6hZ(dTt^zusn44`V?isr0w$Wga`VRh+$-k5fap1jr0LD3z|M7Yq%6i)iD%f$Kv%tS!J z*fPKz%U{t+&_xAtd}7MJ4M-zjsM^5aj=T)q*XQ<7_f>+)VSN^kKlI2pl`sb`P+d=zIgi7pgLE1b?Pdu-Qd`{%>US>8k3{6w}MXM zkq434+`pz+rW^uXf&722U`;hvfW8?^m5b0|kI)@#`GL}3F2Y}Qaeeyt;IXkfp!&9% zNcUGoFMnc0Z%mE9{H&)(gjdEwe(0H<@*r7R!AAaHVj}YQL{>)KH0WM6UPyN<4*I*} zWns`5^cf$Xfvx0k(8vuNmp6WkqZ$Ks&VbmEBk4Z8l>Rda)m_xEf~aSs)@LM)3}L$P zrf15{dtH73UFvwJa=)t3=MDx0dxd2uwLX;~-f@D)SVI1N%H|GU*EoDQmizQkT6eIO zoD>JTt1mn06iw0uZt4)uQ7u6lcM7`_k*Zi_N`%d;Q{-0C9((d1GCIk0Vx znR(bB^Y{7>QUwN_Dfhq_br7o$EBffr!qV~^

    bPV|V1 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 Date: Fri, 2 Feb 2024 10:53:11 +0100 Subject: [PATCH 094/344] Fix / simplify unit tests --- test/factories/samples.rb | 78 +++++++++++++++++-- .../unit/helpers/dynamic_table_helper_test.rb | 76 ++++++++---------- 2 files changed, 107 insertions(+), 47 deletions(-) diff --git a/test/factories/samples.rb b/test/factories/samples.rb index 3f69eafbe0..56eed1d565 100644 --- a/test/factories/samples.rb +++ b/test/factories/samples.rb @@ -9,7 +9,7 @@ sample.set_attribute_value(:the_title, sample.title) if sample.data.key?(:the_title) end end - + factory(:patient_sample, parent: :sample) do association :sample_type, factory: :patient_sample_type, strategy: :create after(:build) do |sample| @@ -18,21 +18,21 @@ sample.set_attribute_value(:weight, 88.7) end end - + factory(:sample_from_file, parent: :sample) do sequence(:title) { |n| "Sample #{n}" } association :sample_type, factory: :strain_sample_type, strategy: :create - + after(:build) do |sample| sample.set_attribute_value(:name, sample.title) if sample.data.key?(:name) sample.set_attribute_value(:seekstrain, '1234') end - + after(:build) do |sample| sample.originating_data_file = FactoryBot.create(:strain_sample_data_file, projects:sample.projects, contributor:sample.contributor) if sample.originating_data_file.nil? end end - + factory(:min_sample, parent: :sample) do association :sample_type, factory: :min_sample_type, strategy: :create association :contributor, factory: :person, strategy: :create @@ -55,4 +55,72 @@ sample.set_attribute_value(:patients, [FactoryBot.create(:patient_sample).id.to_s, FactoryBot.create(:patient_sample).id.to_s]) end end + + factory(:isa_source, parent: :sample) do + sequence(:title) { |n| "Source sample #{n}" } + association :sample_type, factory: :isa_source_sample_type, strategy: :create + after(:build) do |sample| + sample.set_attribute_value('Source Name', sample.title) + sample.set_attribute_value('Source Characteristic 1', 'Source Characteristic 1') + sample.set_attribute_value('Source Characteristic 2', sample.sample_type.sample_attributes.find_by_title('Source Characteristic 2').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + sample.set_attribute_value('Source Characteristic 3', sample.sample_type.sample_attributes.find_by_title('Source Characteristic 3').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + end + end + + factory(:isa_sample, parent: :sample) do + transient do + linked_samples { nil } + end + sequence(:title) { |n| "Source #{n}" } + association :sample_type, factory: :isa_sample_collection_sample_type, strategy: :create + after(:build) do |sample, eval| + sample.data = { + Input: eval.linked_samples.map(&:id), + } + sample.set_attribute_value('Sample Name', sample.title) + sample.set_attribute_value('sample collection', 'sample collection') + sample.set_attribute_value('sample collection parameter value 1', 'sample collection parameter value 1') + sample.set_attribute_value('sample collection parameter value 2', sample.sample_type.sample_attributes.find_by_title('sample collection parameter value 2').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + sample.set_attribute_value('sample characteristic 1', 'sample characteristic 1') + sample.set_attribute_value('sample characteristic 2', sample.sample_type.sample_attributes.find_by_title('sample characteristic 2').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + end + end + + factory(:isa_material_assay_sample, parent: :sample) do + transient do + linked_samples { nil } + end + sequence(:title) { |n| "Material output #{n}" } + association :sample_type, factory: :isa_assay_material_sample_type, strategy: :create + after(:build) do |sample, eval| + sample.data = { + Input: eval.linked_samples.map(&:id), + } + sample.set_attribute_value('Extract Name', sample.title) + sample.set_attribute_value('Protocol Assay 1', 'Protocol Assay 1') + sample.set_attribute_value('Assay 1 parameter value 1', 'Assay 1 parameter value 1') + sample.set_attribute_value('Assay 1 parameter value 2', sample.sample_type.sample_attributes.find_by_title('Assay 1 parameter value 2').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + sample.set_attribute_value('other material characteristic 1', 'other material characteristic 1') + sample.set_attribute_value('other material characteristic 2', sample.sample_type.sample_attributes.find_by_title('other material characteristic 2').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + end + end + + factory(:isa_datafile_assay_sample, parent: :sample) do + transient do + linked_samples { nil } + end + sequence(:title) { |n| "Data file #{n}" } + association :sample_type, factory: :isa_assay_data_file_sample_type, strategy: :create + after(:build) do |sample, eval| + sample.data = { + Input: eval.linked_samples.map(&:id), + } + sample.set_attribute_value('File Name', sample.title) + sample.set_attribute_value('Protocol Assay 2', 'Protocol Assay 2') + sample.set_attribute_value('Assay 2 parameter value 1', 'Assay 2 parameter value 1') + sample.set_attribute_value('Assay 2 parameter value 2', sample.sample_type.sample_attributes.find_by_title('Assay 2 parameter value 2').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + sample.set_attribute_value('sample characteristic 1', 'sample characteristic 1') + sample.set_attribute_value('sample characteristic 2', sample.sample_type.sample_attributes.find_by_title('sample characteristic 2').sample_controlled_vocab.sample_controlled_vocab_terms.first.label) + end + end end diff --git a/test/unit/helpers/dynamic_table_helper_test.rb b/test/unit/helpers/dynamic_table_helper_test.rb index d1828e1966..af03f2b7a8 100644 --- a/test/unit/helpers/dynamic_table_helper_test.rb +++ b/test/unit/helpers/dynamic_table_helper_test.rb @@ -7,48 +7,44 @@ class DynamicTableHelperTest < ActionView::TestCase project = person.projects.first User.with_current_user(person.user) do - inv = FactoryBot.create(:investigation, projects: [project], contributor: person) + inv = FactoryBot.create(:investigation, projects: [project], contributor: person, is_isa_json_compliant: true) - sample_a1 = FactoryBot.create(:patient_sample, contributor: person) - type_a = sample_a1.sample_type - sample_a2 = FactoryBot.create(:patient_sample, sample_type: type_a, contributor: person) - sample_a3 = FactoryBot.create(:patient_sample, sample_type: type_a, contributor: person) + # Sample types + source_sample_type = FactoryBot.create(:isa_source_sample_type, projects: [project], contributor: person) + sample_collection_sample_type = FactoryBot.create(:isa_sample_collection_sample_type, projects: [project], contributor: person, linked_sample_type: source_sample_type) + material_assay_sample_type = FactoryBot.create(:isa_assay_material_sample_type, projects: [project], contributor: person, linked_sample_type: sample_collection_sample_type) - type_b = FactoryBot.create(:multi_linked_sample_type, project_ids: [project.id]) - type_b.sample_attributes.last.linked_sample_type = type_a - type_b.save! + # Samples + source1 = FactoryBot.create(:isa_source, sample_type: source_sample_type, contributor: person) + source2 = FactoryBot.create(:isa_source, sample_type: source_sample_type, contributor: person) + source3 = FactoryBot.create(:isa_source, sample_type: source_sample_type, contributor: person) - sample_b1 = type_b.samples.create!(data: { title: 'sample_b1', patient: [sample_a1.id] }, - sample_type: type_b, project_ids: [project.id]) + sample1 = FactoryBot.create(:isa_sample, sample_type: sample_collection_sample_type, contributor: person, linked_samples: [ source1 ]) + sample2 = FactoryBot.create(:isa_sample, sample_type: sample_collection_sample_type, contributor: person, linked_samples: [ source2 ]) - sample_b2 = type_b.samples.create!(data: { title: 'sample_b2', patient: [sample_a2.id] }, - sample_type: type_b, project_ids: [project.id]) + intermediate_material1 = FactoryBot.create(:isa_material_assay_sample, sample_type: material_assay_sample_type, contributor: person, linked_samples: [ sample1 ]) - type_c = FactoryBot.create(:multi_linked_sample_type, project_ids: [project.id]) - type_c.sample_attributes.last.linked_sample_type = type_b - type_c.save! - - sample_c1 = type_c.samples.create!(data: { title: 'sample_c1', patient: [sample_b1.id] }, - sample_type: type_c, project_ids: [project.id]) - - study = FactoryBot.create(:study, investigation: inv, contributor: person, sample_types: [type_a, type_b]) - assay = FactoryBot.create(:assay, study: study, contributor: person, sample_type: type_c, position: 1) + # ISA + study = FactoryBot.create(:study, investigation: inv, contributor: person, sample_types: [source_sample_type, sample_collection_sample_type]) + assay_stream = FactoryBot.create(:assay_stream, contributor: person, study: ) + assay = FactoryBot.create(:assay, study: , contributor: person, sample_type: material_assay_sample_type, position: 0) # Query with the Study: - # |-------------------------------------------------| - # | type_a | type_b | - # |------------------------|------------------------| - # | (status)(id)sample_a1 | (status)(id)sample_b1 | - # | (status)(id)sample_a2 | (status)(id)sample_b2 | - # | (status)(id)sample_a3 | x | - # |-------------------------------------------------| + # |---------------------------------------------------------| + # | source_sample_type | sample_collection_sample_type | + # |------------------------|------------------------ | + # | (status)(id)source1 | (status)(id)sample1 | + # | (status)(id)source2 | (status)(id)sample2 | + # | (status)(id)source3 | x | + # |---------------------------------------------------------| dt = dt_aggregated(study) + # Each sample types' attributes count + the sample.id columns_count = study.sample_types[0].sample_attributes.length + 2 columns_count += study.sample_types[1].sample_attributes.length + 2 - assert_equal type_a.samples.length, dt[:rows].length + assert_equal source_sample_type.samples.length, dt[:rows].length assert_equal columns_count, dt[:columns].length dt[:rows].each { |r| assert_equal columns_count, r.length } @@ -57,17 +53,17 @@ class DynamicTableHelperTest < ActionView::TestCase assert_equal true, (dt[:rows][2].any? { |x| x == '' }) # Query with the Assay: - # |-----------------------| - # | type_c | - # |-----------------------| - # | (status)(id)sample_c1 | - # |-----------------------| + # |------------------------------------| + # | material_assay_sample_type | + # |------------------------------------| + # | (status)(id)intermediate_material1 | + # |------------------------------------| dt = dt_aggregated(study, assay) # Each sample types' attributes count + the sample.id columns_count = assay.sample_type.sample_attributes.length + 2 - assert_equal type_c.samples.length, dt[:rows].length + assert_equal material_assay_sample_type.samples.length, dt[:rows].length assert_equal columns_count, dt[:columns].length dt[:rows].each { |r| assert_equal columns_count, r.length } @@ -76,13 +72,9 @@ class DynamicTableHelperTest < ActionView::TestCase end test 'Should return the sequence of sample_type links' do - type1 = FactoryBot.create(:simple_sample_type) - type2 = FactoryBot.create(:multi_linked_sample_type) - type3 = FactoryBot.create(:multi_linked_sample_type) - type2.sample_attributes.detect(&:seek_sample_multi?).linked_sample_type = type1 - type3.sample_attributes.detect(&:seek_sample_multi?).linked_sample_type = type2 - type2.save! - type3.save! + type1 = FactoryBot.create(:isa_source_sample_type) + type2 = FactoryBot.create(:isa_sample_collection_sample_type, linked_sample_type: type1) + type3 = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: type2) sequence = link_sequence(type3) assert_equal sequence, [type3, type2, type1] From 8a6c20ccff7925b095f1ce64cb7e53115442c06d Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 2 Feb 2024 11:02:04 +0100 Subject: [PATCH 095/344] Fix the existing functional tests --- app/views/isa_assays/_form.html.erb | 2 +- test/functional/isa_assays_controller_test.rb | 7 +++++-- test/functional/studies_controller_test.rb | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index a6e55c458d..dc314c94d0 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -6,7 +6,7 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] - assay_position = study.assay_streams.map(&:position).max + 1 + assay_position = study.assay_streams.any? ? study.assay_streams.map(&:position).max + 1 : 0 assay_class_id = AssayClass.assay_stream.id is_assay_stream = true else diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index bc7edf4d79..a2632c6881 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -228,8 +228,11 @@ def setup test 'hide sops, publications, documents, and discussion channels if assay stream' do person = FactoryBot.create(:person) - study = FactoryBot.create(:isa_json_compliant_study, contributor: person) - assay_stream = FactoryBot.create(:assay_stream, study: , contributor: person) + investigation = FactoryBot.create(:investigation, contributor: person, is_isa_json_compliant: true) + study = FactoryBot.create(:isa_json_compliant_study, contributor: person, investigation: ) + assay_stream = FactoryBot.create(:assay_stream, study: , contributor: person, position: 0) + + login_as(person) get :new, params: {study_id: study.id, is_assay_stream: true} assert_response :success diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb index c022e8aa04..6ea7c70d67 100644 --- a/test/functional/studies_controller_test.rb +++ b/test/functional/studies_controller_test.rb @@ -2019,9 +2019,10 @@ def test_should_show_investigation_tab 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) + person = FactoryBot.create(:person) + login_as(person) + investigation = FactoryBot.create(:investigation, contributor: person, is_isa_json_compliant: true) + study = FactoryBot.create(:isa_json_compliant_study, contributor: person, investigation: ) get :show, params: { id: study } assert_response :success From cd7198d02a18fabf8e4beb818bfaff76184b404a Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 2 Feb 2024 11:03:16 +0000 Subject: [PATCH 096/344] version 1.14.2 --- config/version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/version.yml b/config/version.yml index 2c565b3488..33d2c6764a 100644 --- a/config/version.yml +++ b/config/version.yml @@ -9,4 +9,4 @@ major: 1 minor: 14 -patch: 1 +patch: 2 From 33a4e0c92c8346d5951a4e5557363f87eca2bd86 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 2 Feb 2024 13:22:37 +0100 Subject: [PATCH 097/344] Test isa asssay positions --- app/views/isa_assays/_form.html.erb | 8 +++-- test/functional/isa_assays_controller_test.rb | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index dc314c94d0..20fe51faaf 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -1,5 +1,9 @@ <% assay = params[:isa_assay][:assay] if params.dig(:isa_assay, :assay) %> <% study = Study.find(params[:study_id] || assay[:study_id]) %> +<% def first_assay_in_stream? + params[:source_assay_id].nil? || (params[:source_assay_id] == params[:assay_stream_id]) + end +%> <% source_assay = Assay.find(params[:source_assay_id]) if params[:source_assay_id] assay_stream_id = params[:assay_stream_id] if params[:assay_stream_id] @@ -10,7 +14,7 @@ assay_class_id = AssayClass.assay_stream.id is_assay_stream = true else - assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 + assay_position = first_assay_in_stream? ? 0 : source_assay.position + 1 assay_class_id = AssayClass.experimental.id is_assay_stream = false end @@ -68,7 +72,7 @@

    <% else %> <% end %> diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index a2632c6881..ba746b6d70 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -600,4 +600,34 @@ def setup end assert_response :not_found end + + test 'position when creating assays' do + person = FactoryBot.create(:person) + investigation = FactoryBot.create(:investigation, contributor: person, is_isa_json_compliant: true) + study = FactoryBot.create(:isa_json_compliant_study, contributor: person, investigation: ) + + login_as(person) + + get :new, params: { study_id: study.id, is_assay_stream: true } + assert_response :success + # New assay stream should have position 0 and is of type 'number' + assert_select 'input[type=number][value=0]#isa_assay_assay_position', count: 1 + + assay_stream1 = FactoryBot.create(:assay_stream, study: , contributor: person, position: 0) + get :new, params: { study_id: study.id, is_assay_stream: true } + assert_response :success + # New assay stream should have position 1 and is of type 'number' + assert_select 'input[type=number][value=1]#isa_assay_assay_position', count: 1 + + assay_stream2 = FactoryBot.create(:assay_stream, study: , contributor: person, position: 5) + get :new, params: { study_id: study.id, is_assay_stream: true } + assert_response :success + # New assay stream should have position 6 and is of type 'number' + assert_select 'input[type=number][value=6]#isa_assay_assay_position', count: 1 + + get :new, params: {study_id: study.id, assay_stream_id: assay_stream1.id, source_assay_id: assay_stream1.id} + # New assay should have position 0 and is of type 'hidden' + assert_select 'input[type=hidden][value=0]#isa_assay_assay_position', count: 1 + + end end From f7154c1d24c68fc9e9c123c155c742c29bcf5c1b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 2 Feb 2024 16:36:48 +0100 Subject: [PATCH 098/344] update class parameter passed to `new_assay_path` --- app/views/assays/new.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/assays/new.html.erb b/app/views/assays/new.html.erb index 3f6bb904af..76941614d6 100644 --- a/app/views/assays/new.html.erb +++ b/app/views/assays/new.html.erb @@ -21,13 +21,13 @@

    Please select the class of <%= t('assays.assay') %> you wish to create

    - <%= link_to new_assay_path(:class=>:experimental, :assay=>@permitted_params), + <%= link_to new_assay_path(:class=>'EXP', :assay=>@permitted_params), :class => 'select_assay_class' do %> <%= image("assay_experimental_avatar") %> An <%= t('assays.experimental_assay') -%> <% end %> - <%= link_to new_assay_path(:class=>:modelling, :assay=>@permitted_params), + <%= link_to new_assay_path(:class=>'MODEL', :assay=>@permitted_params), :class => 'select_assay_class' do %> <%= image("assay_modelling_avatar") %> A <%= t('assays.modelling_analysis') -%> From a7491642ec2f29ddb54fce5d8efc324c77ad8dca Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 2 Feb 2024 17:04:05 +0100 Subject: [PATCH 099/344] Fix assays controller tests --- test/functional/assays_controller_test.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 1996d36918..145cab20f3 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -579,9 +579,9 @@ def test_title login_as(:model_owner) get :new assert_response :success - assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 1 + assert_select 'a[href=?]', new_assay_path(class: 'EXP'), count: 1 assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 1 - assert_select 'a[href=?]', new_assay_path(class: :modelling), count: 1 + assert_select 'a[href=?]', new_assay_path(class: 'MODEL'), count: 1 assert_select 'a', text: /A #{I18n.t('assays.modelling_analysis')}/i, count: 1 end @@ -589,16 +589,16 @@ def test_title login_as(:model_owner) get :new, params: { class: 'EXP' } assert_response :success - assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0 + assert_select 'a[href=?]', new_assay_path(class: 'EXP'), count: 0 assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0 - assert_select 'a[href=?]', new_assay_path(class: :modelling), count: 0 + assert_select 'a[href=?]', new_assay_path(class: 'MODEL'), count: 0 assert_select 'a', text: /A #{I18n.t('assays.modelling_analysis')}/i, count: 0 get :new, params: { class: 'MODEL' } assert_response :success - assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0 + assert_select 'a[href=?]', new_assay_path(class: 'EXP'), count: 0 assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0 - assert_select 'a[href=?]', new_assay_path(class: :modelling), count: 0 + assert_select 'a[href=?]', new_assay_path(class: 'MODEL'), count: 0 assert_select 'a', text: /A #{I18n.t('assays.modelling_analysis')}/i, count: 0 end @@ -1084,7 +1084,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links test 'new should not include tags element when tags disabled' do with_config_value :tagging_enabled, false do - get :new, params: { class: :experimental } + get :new, params: { class: 'EXP' } assert_response :success assert_select 'div.panel-heading', text: /Tags/, count: 0 assert_select 'select#tag_list', count: 0 From 433a80c22809fba5f73c8566e8b3c0b572dada2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 03:36:01 +0000 Subject: [PATCH 100/344] Bump nokogiri from 1.14.5 to 1.16.2 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.14.5 to 1.16.2. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.14.5...v1.16.2) --- updated-dependencies: - dependency-name: nokogiri dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 1803823005..b6c2f20f43 100644 --- a/Gemfile +++ b/Gemfile @@ -52,7 +52,7 @@ gem 'will_paginate', '~> 3.1' gem 'yaml_db' gem 'rails_autolink' gem 'rfc-822' -gem 'nokogiri', '~> 1.14.3' +gem 'nokogiri', '~> 1.16.2' #necessary for newer hashie dependency, original api_smith is no longer active gem 'api_smith', git: 'https://github.com/youroute/api_smith.git', ref: '1fb428cebc17b9afab25ac9f809bde87b0ec315b' gem 'rdf-virtuoso', '>= 0.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 1a2831e0db..71ab432279 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -460,7 +460,7 @@ GEM nokogiri (~> 1) rake mini_mime (1.1.5) - mini_portile2 (2.8.4) + mini_portile2 (2.8.5) minitest (5.20.0) minitest-reporters (1.5.0) ansi @@ -492,8 +492,8 @@ GEM net-protocol netrc (0.11.0) nio4r (2.7.0) - nokogiri (1.14.5) - mini_portile2 (~> 2.8.0) + nokogiri (1.16.2) + mini_portile2 (~> 2.8.2) racc (~> 1.4) nori (1.1.5) oauth2 (2.0.9) @@ -566,7 +566,7 @@ GEM puma (5.6.8) nio4r (~> 2.0) pyu-ruby-sasl (0.0.3.3) - racc (1.7.1) + racc (1.7.3) rack (2.2.8) rack-attack (6.6.0) rack (>= 1.0, < 3) @@ -1029,7 +1029,7 @@ DEPENDENCIES my_responds_to_parent! mysql2 net-ftp - nokogiri (~> 1.14.3) + nokogiri (~> 1.16.2) omniauth (~> 2.1.0) omniauth-github omniauth-rails_csrf_protection From 50e216c458acc9b7b00d470d3aad45603de7ad50 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 6 Feb 2024 10:42:58 +0000 Subject: [PATCH 101/344] update controller to take and handle a comma separated list of root uris #1690 --- .../sample_controlled_vocabs_controller.rb | 9 +- ...ample_controlled_vocabs_controller_test.rb | 65 +++++ .../ols/fetch_obo_haustorium.yml | 224 ++++++++++++++++++ 3 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 test/vcr_cassettes/ols/fetch_obo_haustorium.yml diff --git a/app/controllers/sample_controlled_vocabs_controller.rb b/app/controllers/sample_controlled_vocabs_controller.rb index 05b65f3d77..c4549e7009 100644 --- a/app/controllers/sample_controlled_vocabs_controller.rb +++ b/app/controllers/sample_controlled_vocabs_controller.rb @@ -92,10 +92,13 @@ def fetch_ols_terms_html root_uri = params[:root_uri] raise 'No root URI provided' if root_uri.blank? - + @terms = [] client = Ebi::OlsClient.new - @terms = client.all_descendants(source_ontology, root_uri) - @terms.reject! { |t| t[:iri] == root_uri } unless params[:include_root_term] == '1' + root_uri.split(',').collect(&:strip).each do |uri| + terms = client.all_descendants(source_ontology, uri) + terms.reject! { |t| t[:iri] == uri } unless params[:include_root_term] == '1' + @terms = @terms | terms + end error_msg = "There are no descendant terms to populate the list." unless @terms.present? rescue StandardError => e error_msg = e.message diff --git a/test/functional/sample_controlled_vocabs_controller_test.rb b/test/functional/sample_controlled_vocabs_controller_test.rb index c8659b5320..4b16e523f8 100644 --- a/test/functional/sample_controlled_vocabs_controller_test.rb +++ b/test/functional/sample_controlled_vocabs_controller_test.rb @@ -306,6 +306,71 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_label[value=?]','trichome papilla' end + test 'fetch ols terms as HTML with multiple root uris and root term included' do + person = FactoryBot.create(:person) + login_as(person) + VCR.use_cassette('ols/fetch_obo_plant_cell_papilla') do + VCR.use_cassette('ols/fetch_obo_haustorium') do + get :fetch_ols_terms_html, params: { source_ontology_id: 'go', + root_uri: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', + include_root_term: '1' } + end + end + + assert_response :success + assert_select 'tr.sample-cv-term', count: 6 + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_0_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090395' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_0_parent_iri:not([value])' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_0_label[value=?]','plant cell papilla' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_1_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090397' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_1_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090395' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_1_label[value=?]','stigma papilla' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_2_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090396' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_2_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090395' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_2_label[value=?]','leaf papilla' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090705' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090395' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_label[value=?]','trichome papilla' + + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_4_iri[value=?]','http://purl.obolibrary.org/obo/GO_0085035' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_4_parent_iri:not([value])' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_4_label[value=?]','haustorium' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_5_iri[value=?]','http://purl.obolibrary.org/obo/GO_0085041' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_5_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0085035' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_5_label[value=?]','arbuscule' + + end + + test 'fetch ols terms as HTML with multiple root uris and no root term included' do + person = FactoryBot.create(:person) + login_as(person) + VCR.use_cassette('ols/fetch_obo_plant_cell_papilla') do + VCR.use_cassette('ols/fetch_obo_haustorium') do + get :fetch_ols_terms_html, params: { source_ontology_id: 'go', + root_uri: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', + include_root_term: '0' } + end + end + + assert_response :success + assert_select 'tr.sample-cv-term', count: 4 + + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_0_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090397' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_0_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090395' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_0_label[value=?]','stigma papilla' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_1_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090396' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_1_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090395' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_1_label[value=?]','leaf papilla' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_2_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090705' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_2_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0090395' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_2_label[value=?]','trichome papilla' + + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_iri[value=?]','http://purl.obolibrary.org/obo/GO_0085041' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_parent_iri[value=?]','http://purl.obolibrary.org/obo/GO_0085035' + assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_label[value=?]','arbuscule' + + end + test 'fetch ols terms as HTML without root term included' do person = FactoryBot.create(:person) login_as(person) diff --git a/test/vcr_cassettes/ols/fetch_obo_haustorium.yml b/test/vcr_cassettes/ols/fetch_obo_haustorium.yml new file mode 100644 index 0000000000..4d3b0063d4 --- /dev/null +++ b/test/vcr_cassettes/ols/fetch_obo_haustorium.yml @@ -0,0 +1,224 @@ +--- +http_interactions: +- request: + method: get + uri: https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: '' + headers: + Vary: + - Access-Control-Request-Headers + - Access-Control-Request-Method + - Origin + Content-Type: + - application/json + Strict-Transport-Security: + - max-age=0 + Date: + - Tue, 06 Feb 2024 10:29:41 GMT + Transfer-Encoding: + - chunked + Content-Disposition: + - inline;filename=f.txt + body: + encoding: UTF-8 + string: |- + { + "iri" : "http://purl.obolibrary.org/obo/GO_0085035", + "lang" : "en", + "description" : [ "A projection from a cell or tissue that penetrates the host's cell wall and invaginates the host cell membrane.", "See also: extrahaustorial matrix ; GO:0085036 and extrahaustorial membrane ; GO:0085037." ], + "synonyms" : [ ], + "annotation" : { + "created_by" : [ "jl" ], + "creation_date" : [ "2010-07-27T03:42:24Z" ], + "has_obo_namespace" : [ "cellular_component" ], + "id" : [ "GO:0085035" ] + }, + "label" : "haustorium", + "ontology_name" : "go", + "ontology_prefix" : "GO", + "ontology_iri" : "http://purl.obolibrary.org/obo/go/extensions/go-plus.owl", + "is_obsolete" : false, + "term_replaced_by" : null, + "is_defining_ontology" : true, + "has_children" : true, + "is_root" : false, + "short_form" : "GO_0085035", + "obo_id" : "GO:0085035", + "in_subset" : null, + "obo_definition_citation" : [ { + "definition" : "A projection from a cell or tissue that penetrates the host's cell wall and invaginates the host cell membrane.", + "oboXrefs" : [ { + "database" : "GOC", + "id" : "pamgo_curators", + "description" : null, + "url" : null + } ] + } ], + "obo_xref" : null, + "obo_synonym" : null, + "is_preferred_root" : false, + "_links" : { + "self" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035?lang=en" + }, + "parents" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/parents" + }, + "ancestors" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/ancestors" + }, + "hierarchicalParents" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/hierarchicalParents" + }, + "hierarchicalAncestors" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/hierarchicalAncestors" + }, + "jstree" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/jstree" + }, + "children" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/children" + }, + "descendants" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/descendants" + }, + "hierarchicalChildren" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/hierarchicalChildren" + }, + "hierarchicalDescendants" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/hierarchicalDescendants" + }, + "graph" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/graph" + } + } + } + http_version: + recorded_at: Tue, 06 Feb 2024 10:29:41 GMT +- request: + method: get + uri: https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/children + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - www.ebi.ac.uk + response: + status: + code: 200 + message: '' + headers: + Vary: + - Access-Control-Request-Headers + - Access-Control-Request-Method + - Origin + Content-Type: + - application/json + Strict-Transport-Security: + - max-age=0 + Date: + - Tue, 06 Feb 2024 10:29:41 GMT + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: |- + { + "_embedded" : { + "terms" : [ { + "iri" : "http://purl.obolibrary.org/obo/GO_0085041", + "lang" : "en", + "description" : [ "Highly branched symbiont haustoria within host root cortex cells, responsible for nutrient exchange.", "See also: periarbuscular membrane ; GO:0085042." ], + "synonyms" : [ ], + "annotation" : { + "created_by" : [ "jl" ], + "creation_date" : [ "2010-07-27T04:29:03Z" ], + "has_obo_namespace" : [ "cellular_component" ], + "id" : [ "GO:0085041" ] + }, + "label" : "arbuscule", + "ontology_name" : "go", + "ontology_prefix" : "GO", + "ontology_iri" : "http://purl.obolibrary.org/obo/go/extensions/go-plus.owl", + "is_obsolete" : false, + "term_replaced_by" : null, + "is_defining_ontology" : true, + "has_children" : false, + "is_root" : false, + "short_form" : "GO_0085041", + "obo_id" : "GO:0085041", + "in_subset" : null, + "obo_definition_citation" : [ { + "definition" : "Highly branched symbiont haustoria within host root cortex cells, responsible for nutrient exchange.", + "oboXrefs" : [ { + "database" : "GOC", + "id" : "pamgo_curators", + "description" : null, + "url" : null + } ] + } ], + "obo_xref" : null, + "obo_synonym" : null, + "is_preferred_root" : false, + "_links" : { + "self" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085041?lang=en" + }, + "parents" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085041/parents" + }, + "ancestors" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085041/ancestors" + }, + "hierarchicalParents" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085041/hierarchicalParents" + }, + "hierarchicalAncestors" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085041/hierarchicalAncestors" + }, + "jstree" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085041/jstree" + }, + "graph" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085041/graph" + } + } + } ] + }, + "_links" : { + "self" : { + "href" : "https://www.ebi.ac.uk/ols4/api/ontologies/go/terms/http%253A%252F%252Fpurl.obolibrary.org%252Fobo%252FGO_0085035/children?page=0&size=20" + } + }, + "page" : { + "size" : 20, + "totalElements" : 1, + "totalPages" : 1, + "number" : 0 + } + } + http_version: + recorded_at: Tue, 06 Feb 2024 10:29:41 GMT +recorded_with: VCR 2.9.3 From 9323541f8fcde2ad518384f1a8c2859c6e0a4eb5 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 6 Feb 2024 11:45:00 +0000 Subject: [PATCH 102/344] update the ols root term validation to support comma separated #1690 --- app/models/sample_controlled_vocab.rb | 18 +++++++++++++-- ...ample_controlled_vocabs_controller_test.rb | 23 +++++++++++++++++++ test/unit/sample_controlled_vocab_test.rb | 22 ++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/app/models/sample_controlled_vocab.rb b/app/models/sample_controlled_vocab.rb index 41f954a002..0e060bf86a 100644 --- a/app/models/sample_controlled_vocab.rb +++ b/app/models/sample_controlled_vocab.rb @@ -1,5 +1,5 @@ class SampleControlledVocab < ApplicationRecord - # attr_accessible :title, :description, :sample_controlled_vocab_terms_attributes + include Seek::UrlValidation has_many :sample_controlled_vocab_terms, inverse_of: :sample_controlled_vocab, after_add: :update_sample_type_templates, @@ -16,8 +16,8 @@ class SampleControlledVocab < ApplicationRecord auto_strip_attributes :ols_root_term_uri validates :title, presence: true, uniqueness: true - validates :ols_root_term_uri, url: { allow_blank: true } validates :key, uniqueness: { allow_blank: true } + validate :validate_ols_root_term_uris accepts_nested_attributes_for :sample_controlled_vocab_terms, allow_destroy: true accepts_nested_attributes_for :repository_standard, reject_if: :check_repository_standard @@ -60,12 +60,26 @@ def ontology_based? source_ontology.present? && ols_root_term_uri.present? end + def validate_ols_root_term_uris + return if self.ols_root_term_uri.blank? + uris = self.ols_root_term_uri.split(',').collect(&:strip) + uris.each do |uri| + unless valid_url?(uri) + errors.add(:ols_root_term_uri, "invalid URI - #{uri}") + return false + end + end + self.ols_root_term_uri = uris.join(', ') + end + private def update_sample_type_templates(_term) sample_types.each(&:queue_template_generation) unless new_record? end + + class SystemVocabs # property -> database key MAPPING = { diff --git a/test/functional/sample_controlled_vocabs_controller_test.rb b/test/functional/sample_controlled_vocabs_controller_test.rb index 4b16e523f8..3fbefc8842 100644 --- a/test/functional/sample_controlled_vocabs_controller_test.rb +++ b/test/functional/sample_controlled_vocabs_controller_test.rb @@ -306,6 +306,29 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase assert_select 'input[type=hidden]#sample_controlled_vocab_sample_controlled_vocab_terms_attributes_3_label[value=?]','trichome papilla' end + test 'create with root uris' do + login_as(FactoryBot.create(:project_administrator)) + assert_difference('SampleControlledVocab.count') do + assert_difference('SampleControlledVocabTerm.count', 2) do + post :create, params: { sample_controlled_vocab: { title: 'plant_cell_papilla and haustorium', description: 'multiple root uris', + ols_root_term_uri: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', + sample_controlled_vocab_terms_attributes: { + '0' => { label: 'plant cell papilla', iri:'http://purl.obolibrary.org/obo/GO_0090395', parent_iri:'', _destroy: '0' }, + '1' => { label: 'haustorium', iri:'http://purl.obolibrary.org/obo/GO_0085035', parent_iri:'', _destroy: '0' } + } + } } + end + end + assert cv = assigns(:sample_controlled_vocab) + assert_redirected_to sample_controlled_vocab_path(cv) + assert_equal 'plant_cell_papilla and haustorium', cv.title + assert_equal 'multiple root uris', cv.description + assert_equal 2, cv.sample_controlled_vocab_terms.count + assert_equal ['plant cell papilla','haustorium'], cv.labels + assert_equal ['http://purl.obolibrary.org/obo/GO_0090395','http://purl.obolibrary.org/obo/GO_0085035'], cv.sample_controlled_vocab_terms.collect(&:iri) + assert_equal 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', cv.ols_root_term_uri + end + test 'fetch ols terms as HTML with multiple root uris and root term included' do person = FactoryBot.create(:person) login_as(person) diff --git a/test/unit/sample_controlled_vocab_test.rb b/test/unit/sample_controlled_vocab_test.rb index a2b07721ba..d5f6798b90 100644 --- a/test/unit/sample_controlled_vocab_test.rb +++ b/test/unit/sample_controlled_vocab_test.rb @@ -35,6 +35,28 @@ class SampleControlledVocabTest < ActiveSupport::TestCase end end + test 'validate ols ols_root_term_uris' do + vocab = SampleControlledVocab.new(title: 'multiple uris') + assert vocab.valid? + + vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395' + assert vocab.valid? + vocab.ols_root_term_uri = 'wibble' + refute vocab.valid? + + vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035' + assert vocab.valid? + vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, http://purl.obolibrary.org/obo/GO_0090396' + assert vocab.valid? + assert_equal 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, http://purl.obolibrary.org/obo/GO_0090396', vocab.ols_root_term_uri + vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, wibble' + refute vocab.valid? + + vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, ' + assert vocab.valid? + assert_equal 'http://purl.obolibrary.org/obo/GO_0090395', vocab.ols_root_term_uri + end + test 'validate unique key' do User.with_current_user(FactoryBot.create(:project_administrator).user) do SampleControlledVocab.create(title: 'no key') From 07ab74b5d05ece2da6264fa6ad51cc867c8ea72e Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 6 Feb 2024 13:34:56 +0000 Subject: [PATCH 103/344] pluralized ols_root_to_uri database field on sample controlled vocabs #1690 --- app/assets/javascripts/controlled_vocabs.js.erb | 4 ++-- .../sample_controlled_vocabs_controller.rb | 2 +- app/models/sample_controlled_vocab.rb | 12 ++++++------ .../sample_controlled_vocab_serializer.rb | 2 +- .../sample_controlled_vocabs/_form.html.erb | 2 +- app/views/sample_controlled_vocabs/show.html.erb | 2 +- .../data-annotations-controlled-vocab.json | 2 +- .../format-annotations-controlled-vocab.json | 2 +- .../operation-annotations-controlled-vocab.json | 2 +- .../topics-annotations-controlled-vocab.json | 2 +- ...2054_change_controlled_vocab_root_term_uri.rb | 5 +++++ db/schema.rb | 4 ++-- db/seeds/011_topics_controlled_vocab.seeds.rb | 2 +- .../012_operations_controlled_vocab.seeds.rb | 2 +- db/seeds/013_formats_controlled_vocab.seeds.rb | 2 +- db/seeds/014_data_controlled_vocab.seeds.rb | 2 +- lib/isa_exporter.rb | 2 +- lib/seek/isa_templates/template_extractor.rb | 2 +- lib/tasks/seek_dev.rake | 2 +- .../api/examples/sampleControlledVocabPatch.json | 2 +- .../sampleControlledVocabPatchResponse.json | 2 +- .../api/examples/sampleControlledVocabPost.json | 2 +- .../sampleControlledVocabPostResponse.json | 2 +- test/factories/sample_attribute_types.rb | 14 +++++++------- .../patch_max_sample_controlled_vocab.json.erb | 2 +- .../post_max_sample_controlled_vocab.json.erb | 2 +- .../get_max_sample_controlled_vocab.json.erb | 2 +- .../get_min_sample_controlled_vocab.json.erb | 2 +- .../sample_controlled_vocabs_controller_test.rb | 4 ++-- .../api/sample_controlled_vocab_api_test.rb | 2 +- test/unit/sample_controlled_vocab_test.rb | 16 ++++++++-------- 31 files changed, 56 insertions(+), 51 deletions(-) create mode 100644 db/migrate/20240206132054_change_controlled_vocab_root_term_uri.rb diff --git a/app/assets/javascripts/controlled_vocabs.js.erb b/app/assets/javascripts/controlled_vocabs.js.erb index db2a303dd3..214351975f 100644 --- a/app/assets/javascripts/controlled_vocabs.js.erb +++ b/app/assets/javascripts/controlled_vocabs.js.erb @@ -137,7 +137,7 @@ CVTerms = { dataType: "html", data: { source_ontology_id: $j('select#sample_controlled_vocab_source_ontology').val(), - root_uri: $j('#sample_controlled_vocab_ols_root_term_uri').val(), + root_uri: $j('#sample_controlled_vocab_ols_root_term_uris').val(), include_root_term: $j('#include_root_term:checked').val() }, success: function (resp) { @@ -179,7 +179,7 @@ CVTerms = { $j('select#sample_controlled_vocab_source_ontology').on('change', function () { var selected = this.selectedOptions[0]; if (selected.value == "") { - $j('#sample_controlled_vocab_ols_root_term_uri').val(''); + $j('#sample_controlled_vocab_ols_root_term_uris').val(''); $j('#ontology-root-uri').hide(); } else { $j('#ontology-root-uri').show(); diff --git a/app/controllers/sample_controlled_vocabs_controller.rb b/app/controllers/sample_controlled_vocabs_controller.rb index c4549e7009..d8842cca3e 100644 --- a/app/controllers/sample_controlled_vocabs_controller.rb +++ b/app/controllers/sample_controlled_vocabs_controller.rb @@ -132,7 +132,7 @@ def typeahead private def cv_params - params.require(:sample_controlled_vocab).permit(:title, :description, :group, :source_ontology, :ols_root_term_uri, + params.require(:sample_controlled_vocab).permit(:title, :description, :group, :source_ontology, :ols_root_term_uris, :required, :short_name, { sample_controlled_vocab_terms_attributes: %i[id _destroy label iri parent_iri] }) diff --git a/app/models/sample_controlled_vocab.rb b/app/models/sample_controlled_vocab.rb index 0e060bf86a..73bd6b82a4 100644 --- a/app/models/sample_controlled_vocab.rb +++ b/app/models/sample_controlled_vocab.rb @@ -13,7 +13,7 @@ class SampleControlledVocab < ApplicationRecord has_many :samples, through: :sample_types belongs_to :repository_standard, inverse_of: :sample_controlled_vocabs - auto_strip_attributes :ols_root_term_uri + auto_strip_attributes :ols_root_term_uris validates :title, presence: true, uniqueness: true validates :key, uniqueness: { allow_blank: true } @@ -57,19 +57,19 @@ def self.can_create? # whether the controlled vocab is linked to an ontology def ontology_based? - source_ontology.present? && ols_root_term_uri.present? + source_ontology.present? && ols_root_term_uris.present? end def validate_ols_root_term_uris - return if self.ols_root_term_uri.blank? - uris = self.ols_root_term_uri.split(',').collect(&:strip) + return if self.ols_root_term_uris.blank? + uris = self.ols_root_term_uris.split(',').collect(&:strip) uris.each do |uri| unless valid_url?(uri) - errors.add(:ols_root_term_uri, "invalid URI - #{uri}") + errors.add(:ols_root_term_uris, "invalid URI - #{uri}") return false end end - self.ols_root_term_uri = uris.join(', ') + self.ols_root_term_uris = uris.join(', ') end private diff --git a/app/serializers/sample_controlled_vocab_serializer.rb b/app/serializers/sample_controlled_vocab_serializer.rb index 7700a2a34f..dcccc2c08f 100644 --- a/app/serializers/sample_controlled_vocab_serializer.rb +++ b/app/serializers/sample_controlled_vocab_serializer.rb @@ -1,5 +1,5 @@ class SampleControlledVocabSerializer < BaseSerializer - attributes :title, :description, :source_ontology, :ols_root_term_uri, :short_name + attributes :title, :description, :source_ontology, :ols_root_term_uris, :short_name attributes :sample_controlled_vocab_terms_attributes has_many :sample_controlled_vocab_terms diff --git a/app/views/sample_controlled_vocabs/_form.html.erb b/app/views/sample_controlled_vocabs/_form.html.erb index a79d14aed1..54ad43a833 100644 --- a/app/views/sample_controlled_vocabs/_form.html.erb +++ b/app/views/sample_controlled_vocabs/_form.html.erb @@ -42,7 +42,7 @@
    - <%= f.text_field :ols_root_term_uri, :class => 'form-control', :placeholder => 'e.g. http://www.ebi.ac.uk/efo/EFO_0000635' %> + <%= f.text_field :ols_root_term_uris, :class => 'form-control', :placeholder => 'e.g. http://www.ebi.ac.uk/efo/EFO_0000635' %>
    diff --git a/app/views/sample_controlled_vocabs/show.html.erb b/app/views/sample_controlled_vocabs/show.html.erb index bbffcffc69..9b53dea7d9 100644 --- a/app/views/sample_controlled_vocabs/show.html.erb +++ b/app/views/sample_controlled_vocabs/show.html.erb @@ -11,7 +11,7 @@

    Root URI: - <%= ols_root_term_link(@sample_controlled_vocab.source_ontology,@sample_controlled_vocab.ols_root_term_uri) %> + <%= ols_root_term_link(@sample_controlled_vocab.source_ontology,@sample_controlled_vocab.ols_root_term_uris) %>

    <% end %> diff --git a/config/default_data/data-annotations-controlled-vocab.json b/config/default_data/data-annotations-controlled-vocab.json index 826415f073..f93a6c4896 100644 --- a/config/default_data/data-annotations-controlled-vocab.json +++ b/config/default_data/data-annotations-controlled-vocab.json @@ -1,7 +1,7 @@ { "title": "Data types", "description": "Data types, used for annotating. Describes information that is that can be processed by dedicated computational tools that can use the data as input or produce it as output. Initially seeded from the EDAM ontology", - "ols_root_term_uri": "http://edamontology.org/data_0006", + "ols_root_term_uris": "http://edamontology.org/data_0006", "source_ontology": "edam", "terms": [ { diff --git a/config/default_data/format-annotations-controlled-vocab.json b/config/default_data/format-annotations-controlled-vocab.json index 88fa862632..cffad4787d 100644 --- a/config/default_data/format-annotations-controlled-vocab.json +++ b/config/default_data/format-annotations-controlled-vocab.json @@ -1,7 +1,7 @@ { "title": "Data Formats", "description": "Data formats, used for annotating. Describes a defined way or layout of representing and structuring data in a computer file, blob, string, message, or elsewhere. Initially seeded from the EDAM ontology.", - "ols_root_term_uri": "http://edamontology.org/format_1915", + "ols_root_term_uris": "http://edamontology.org/format_1915", "source_ontology": "edam", "terms": [ { diff --git a/config/default_data/operation-annotations-controlled-vocab.json b/config/default_data/operation-annotations-controlled-vocab.json index fadf4e2bee..748695f726 100644 --- a/config/default_data/operation-annotations-controlled-vocab.json +++ b/config/default_data/operation-annotations-controlled-vocab.json @@ -1,7 +1,7 @@ { "title": "Operations", "description": "Operations, used for annotating. Describes a function that can take place over some inputs to produce an output. Initially seeded from the EDAM ontology", - "ols_root_term_uri": "http://edamontology.org/operation_0004", + "ols_root_term_uris": "http://edamontology.org/operation_0004", "source_ontology": "edam", "terms": [ { diff --git a/config/default_data/topics-annotations-controlled-vocab.json b/config/default_data/topics-annotations-controlled-vocab.json index b7dcdc0e1f..d09e156870 100644 --- a/config/default_data/topics-annotations-controlled-vocab.json +++ b/config/default_data/topics-annotations-controlled-vocab.json @@ -1,7 +1,7 @@ { "title": "Topics", "description": "Topics, used for annotating. Describes the domain, field of interest, of study, application, work, data, or technology. Initially seeded from the EDAM ontology.", - "ols_root_term_uri": "http://edamontology.org/topic_0003", + "ols_root_term_uris": "http://edamontology.org/topic_0003", "source_ontology": "edam", "terms": [ { diff --git a/db/migrate/20240206132054_change_controlled_vocab_root_term_uri.rb b/db/migrate/20240206132054_change_controlled_vocab_root_term_uri.rb new file mode 100644 index 0000000000..5bc256b6dd --- /dev/null +++ b/db/migrate/20240206132054_change_controlled_vocab_root_term_uri.rb @@ -0,0 +1,5 @@ +class ChangeControlledVocabRootTermUri < ActiveRecord::Migration[6.1] + def change + rename_column :sample_controlled_vocabs, :ols_root_term_uri, :ols_root_term_uris + end +end diff --git a/db/schema.rb b/db/schema.rb index 2f51e08778..c9d24b5489 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: 2024_01_12_141513) do +ActiveRecord::Schema.define(version: 2024_02_06_132054) do create_table "activity_logs", id: :integer, force: :cascade do |t| t.string "action" @@ -1747,7 +1747,7 @@ t.datetime "updated_at", null: false t.string "first_letter", limit: 1 t.string "source_ontology" - t.string "ols_root_term_uri" + t.string "ols_root_term_uris" t.string "short_name" t.string "key" t.integer "template_id" diff --git a/db/seeds/011_topics_controlled_vocab.seeds.rb b/db/seeds/011_topics_controlled_vocab.seeds.rb index e1c63a3293..aaa81baa0c 100644 --- a/db/seeds/011_topics_controlled_vocab.seeds.rb +++ b/db/seeds/011_topics_controlled_vocab.seeds.rb @@ -8,7 +8,7 @@ key: SampleControlledVocab::SystemVocabs.database_key_for_property(:topics), description: data[:description], source_ontology: data[:source_ontology], - ols_root_term_uri: data[:ols_root_term_uri]) + ols_root_term_uris: data[:ols_root_term_uris]) data[:terms].each do |term| vocab.sample_controlled_vocab_terms << SampleControlledVocabTerm.new(label: term[:label], iri: term[:iri], parent_iri: term[:parent_iri]) end diff --git a/db/seeds/012_operations_controlled_vocab.seeds.rb b/db/seeds/012_operations_controlled_vocab.seeds.rb index f16809ba2b..0ed2dbd13c 100644 --- a/db/seeds/012_operations_controlled_vocab.seeds.rb +++ b/db/seeds/012_operations_controlled_vocab.seeds.rb @@ -7,7 +7,7 @@ key: SampleControlledVocab::SystemVocabs.database_key_for_property(:operations), description: data[:description], source_ontology: data[:source_ontology], - ols_root_term_uri: data[:ols_root_term_uri]) + ols_root_term_uris: data[:ols_root_term_uris]) data[:terms].each do |term| vocab.sample_controlled_vocab_terms << SampleControlledVocabTerm.new(label: term[:label], iri: term[:iri], parent_iri: term[:parent_iri]) end diff --git a/db/seeds/013_formats_controlled_vocab.seeds.rb b/db/seeds/013_formats_controlled_vocab.seeds.rb index 2e65bd28ce..23f994e473 100644 --- a/db/seeds/013_formats_controlled_vocab.seeds.rb +++ b/db/seeds/013_formats_controlled_vocab.seeds.rb @@ -7,7 +7,7 @@ key: SampleControlledVocab::SystemVocabs.database_key_for_property(:data_formats), description: data[:description], source_ontology: data[:source_ontology], - ols_root_term_uri: data[:ols_root_term_uri]) + ols_root_term_uris: data[:ols_root_term_uris]) data[:terms].each do |term| vocab.sample_controlled_vocab_terms << SampleControlledVocabTerm.new(label: term[:label], iri: term[:iri], parent_iri: term[:parent_iri]) end diff --git a/db/seeds/014_data_controlled_vocab.seeds.rb b/db/seeds/014_data_controlled_vocab.seeds.rb index e01f199a70..3140159095 100644 --- a/db/seeds/014_data_controlled_vocab.seeds.rb +++ b/db/seeds/014_data_controlled_vocab.seeds.rb @@ -7,7 +7,7 @@ key: SampleControlledVocab::SystemVocabs.database_key_for_property(:data_types), description: data[:description], source_ontology: data[:source_ontology], - ols_root_term_uri: data[:ols_root_term_uri]) + ols_root_term_uris: data[:ols_root_term_uris]) data[:terms].each do |term| vocab.sample_controlled_vocab_terms << SampleControlledVocabTerm.new(label: term[:label], iri: term[:iri], parent_iri: term[:parent_iri]) end diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 5d5a3ff288..853cb4f489 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -725,7 +725,7 @@ def get_ontology_details(sample_attribute, label, vocab_term) if vocab_term sample_attribute.sample_controlled_vocab.sample_controlled_vocab_terms.find_by_label(label)&.iri else - sample_attribute.sample_controlled_vocab.ols_root_term_uri + sample_attribute.sample_controlled_vocab.ols_root_term_uris end end term_accession = iri || '' diff --git a/lib/seek/isa_templates/template_extractor.rb b/lib/seek/isa_templates/template_extractor.rb index b048f8a56c..d0eb8d6f2c 100644 --- a/lib/seek/isa_templates/template_extractor.rb +++ b/lib/seek/isa_templates/template_extractor.rb @@ -46,7 +46,7 @@ def self.extract_templates { title: attribute['name'], source_ontology: is_ontology ? attribute['ontology']['name'] : nil, - ols_root_term_uri: is_ontology ? attribute['ontology']['rootTermURI'] : nil + ols_root_term_uris: is_ontology ? attribute['ontology']['rootTermURI'] : nil } ) end diff --git a/lib/tasks/seek_dev.rake b/lib/tasks/seek_dev.rake index d54973eb6c..f0405daca1 100644 --- a/lib/tasks/seek_dev.rake +++ b/lib/tasks/seek_dev.rake @@ -53,7 +53,7 @@ namespace :seek_dev do 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, + json = { title: vocab.title, description: vocab.description, ols_root_term_uris: vocab.ols_root_term_uris, source_ontology: vocab.source_ontology, terms: [] } vocab.sample_controlled_vocab_terms.each do |term| json[:terms] << { label: term.label, iri: term.iri, parent_iri: term.parent_iri } diff --git a/public/api/examples/sampleControlledVocabPatch.json b/public/api/examples/sampleControlledVocabPatch.json index 70834236d3..da157689e0 100644 --- a/public/api/examples/sampleControlledVocabPatch.json +++ b/public/api/examples/sampleControlledVocabPatch.json @@ -6,7 +6,7 @@ "title": "new title", "description": "new description", "source_ontology": "new source ontology", - "ols_root_term_uri": "http://new-uri.org", + "ols_root_term_uris": "http://new-uri.org", "short_name": "new short name", "sample_controlled_vocab_terms_attributes": [ { diff --git a/public/api/examples/sampleControlledVocabPatchResponse.json b/public/api/examples/sampleControlledVocabPatchResponse.json index eb9543717b..b02456a5a8 100644 --- a/public/api/examples/sampleControlledVocabPatchResponse.json +++ b/public/api/examples/sampleControlledVocabPatchResponse.json @@ -6,7 +6,7 @@ "title": "new title", "description": "new description", "source_ontology": "new source ontology", - "ols_root_term_uri": "http://new-uri.org", + "ols_root_term_uris": "http://new-uri.org", "short_name": "new short name", "sample_controlled_vocab_terms_attributes": [ { diff --git a/public/api/examples/sampleControlledVocabPost.json b/public/api/examples/sampleControlledVocabPost.json index bb72ca6d9c..0a8ea33735 100644 --- a/public/api/examples/sampleControlledVocabPost.json +++ b/public/api/examples/sampleControlledVocabPost.json @@ -5,7 +5,7 @@ "title": "New Vocab Max", "description": "some description", "source_ontology": "EFO", - "ols_root_term_uri": null, + "ols_root_term_uris": null, "short_name": "short_name", "sample_controlled_vocab_terms_attributes": [ { diff --git a/public/api/examples/sampleControlledVocabPostResponse.json b/public/api/examples/sampleControlledVocabPostResponse.json index 947b4be3b0..e7fccd07f4 100644 --- a/public/api/examples/sampleControlledVocabPostResponse.json +++ b/public/api/examples/sampleControlledVocabPostResponse.json @@ -6,7 +6,7 @@ "title": "New Vocab Max", "description": "some description", "source_ontology": "EFO", - "ols_root_term_uri": null, + "ols_root_term_uris": null, "short_name": "short_name", "sample_controlled_vocab_terms_attributes": [ { diff --git a/test/factories/sample_attribute_types.rb b/test/factories/sample_attribute_types.rb index 5f4ce68c24..93706d1c13 100644 --- a/test/factories/sample_attribute_types.rb +++ b/test/factories/sample_attribute_types.rb @@ -129,7 +129,7 @@ factory(:ontology_sample_controlled_vocab, parent: :sample_controlled_vocab) do source_ontology { 'http://ontology.org' } - ols_root_term_uri { 'http://ontology.org/#parent' } + ols_root_term_uris { 'http://ontology.org/#parent' } after(:build) do |vocab| vocab.sample_controlled_vocab_terms << FactoryBot.build(:sample_controlled_vocab_term, label: 'Parent',iri:'http://ontology.org/#parent',parent_iri:'') vocab.sample_controlled_vocab_terms << FactoryBot.build(:sample_controlled_vocab_term, label: 'Mother',iri:'http://ontology.org/#mother',parent_iri:'http://ontology.org/#parent') @@ -139,7 +139,7 @@ factory(:topics_controlled_vocab, parent: :sample_controlled_vocab) do title { 'Topics' } - ols_root_term_uri { 'http://edamontology.org/topic_0003' } + ols_root_term_uris { 'http://edamontology.org/topic_0003' } key { SampleControlledVocab::SystemVocabs.database_key_for_property(:topics) } source_ontology { 'edam' } after(:build) do |vocab| @@ -152,7 +152,7 @@ factory(:operations_controlled_vocab, parent: :sample_controlled_vocab) do title { 'Operations' } - ols_root_term_uri { 'http://edamontology.org/operation_0004' } + ols_root_term_uris { 'http://edamontology.org/operation_0004' } key { SampleControlledVocab::SystemVocabs.database_key_for_property(:operations) } source_ontology { 'edam' } after(:build) do |vocab| @@ -165,7 +165,7 @@ factory(:data_types_controlled_vocab, parent: :sample_controlled_vocab) do title { 'Data' } - ols_root_term_uri { 'http://edamontology.org/data_0006' } + ols_root_term_uris { 'http://edamontology.org/data_0006' } key { SampleControlledVocab::SystemVocabs.database_key_for_property(:data_types) } source_ontology { 'edam' } after(:build) do |vocab| @@ -176,7 +176,7 @@ factory(:data_formats_controlled_vocab, parent: :sample_controlled_vocab) do title { 'Formats' } - ols_root_term_uri { 'http://edamontology.org/format_1915' } + ols_root_term_uris { 'http://edamontology.org/format_1915' } key { SampleControlledVocab::SystemVocabs.database_key_for_property(:data_formats) } source_ontology { 'edam' } after(:build) do |vocab| @@ -188,7 +188,7 @@ factory(:efo_ontology, class: SampleControlledVocab) do sequence(:title) { |n| "EFO ontology #{n}" } source_ontology { 'EFO' } - ols_root_term_uri { 'http://www.ebi.ac.uk/efo/EFO_0000635' } + ols_root_term_uris { 'http://www.ebi.ac.uk/efo/EFO_0000635' } after(:build) do |vocab| vocab.sample_controlled_vocab_terms << FactoryBot.build(:sample_controlled_vocab_term, label: 'anatomical entity') vocab.sample_controlled_vocab_terms << FactoryBot.build(:sample_controlled_vocab_term, label: 'retroperitoneal space') @@ -199,7 +199,7 @@ factory(:obi_ontology, class: SampleControlledVocab) do sequence(:title) { |n| "OBI ontology #{n}" } source_ontology { 'OBI' } - ols_root_term_uri { 'http://purl.obolibrary.org/obo/OBI_0000094' } + ols_root_term_uris { 'http://purl.obolibrary.org/obo/OBI_0000094' } after(:build) do |vocab| vocab.sample_controlled_vocab_terms << FactoryBot.build(:sample_controlled_vocab_term, label: 'dissection') vocab.sample_controlled_vocab_terms << FactoryBot.build(:sample_controlled_vocab_term, label: 'enzymatic cleavage') diff --git a/test/fixtures/json/requests/patch_max_sample_controlled_vocab.json.erb b/test/fixtures/json/requests/patch_max_sample_controlled_vocab.json.erb index 5e84d07778..af1715d021 100644 --- a/test/fixtures/json/requests/patch_max_sample_controlled_vocab.json.erb +++ b/test/fixtures/json/requests/patch_max_sample_controlled_vocab.json.erb @@ -6,7 +6,7 @@ "title": "new title", "description": "new description", "source_ontology": "new source ontology", - "ols_root_term_uri": "http://new-uri.org", + "ols_root_term_uris": "http://new-uri.org", "short_name": "new short name", "sample_controlled_vocab_terms_attributes": [ { diff --git a/test/fixtures/json/requests/post_max_sample_controlled_vocab.json.erb b/test/fixtures/json/requests/post_max_sample_controlled_vocab.json.erb index 97167af91b..9afd4e918c 100644 --- a/test/fixtures/json/requests/post_max_sample_controlled_vocab.json.erb +++ b/test/fixtures/json/requests/post_max_sample_controlled_vocab.json.erb @@ -5,7 +5,7 @@ "title": "New Vocab Max", "description": "some description", "source_ontology": "EFO", - "ols_root_term_uri": null, + "ols_root_term_uris": null, "short_name": "short_name", "sample_controlled_vocab_terms_attributes": [ { diff --git a/test/fixtures/json/responses/get_max_sample_controlled_vocab.json.erb b/test/fixtures/json/responses/get_max_sample_controlled_vocab.json.erb index 66f1f2414c..536f12227c 100644 --- a/test/fixtures/json/responses/get_max_sample_controlled_vocab.json.erb +++ b/test/fixtures/json/responses/get_max_sample_controlled_vocab.json.erb @@ -6,7 +6,7 @@ "title": "Organism", "description": "Description for organism", "source_ontology": "EFO", - "ols_root_term_uri": "", + "ols_root_term_uris": "", "short_name": "organism", "sample_controlled_vocab_terms_attributes": [ { diff --git a/test/fixtures/json/responses/get_min_sample_controlled_vocab.json.erb b/test/fixtures/json/responses/get_min_sample_controlled_vocab.json.erb index f824be5a53..8286fb87ab 100644 --- a/test/fixtures/json/responses/get_min_sample_controlled_vocab.json.erb +++ b/test/fixtures/json/responses/get_min_sample_controlled_vocab.json.erb @@ -6,7 +6,7 @@ "title": "Organism", "description": null, "source_ontology": null, - "ols_root_term_uri": null, + "ols_root_term_uris": null, "short_name": null, "sample_controlled_vocab_terms_attributes": null, "repository_standard": null diff --git a/test/functional/sample_controlled_vocabs_controller_test.rb b/test/functional/sample_controlled_vocabs_controller_test.rb index 3fbefc8842..7edebe38f5 100644 --- a/test/functional/sample_controlled_vocabs_controller_test.rb +++ b/test/functional/sample_controlled_vocabs_controller_test.rb @@ -311,7 +311,7 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase assert_difference('SampleControlledVocab.count') do assert_difference('SampleControlledVocabTerm.count', 2) do post :create, params: { sample_controlled_vocab: { title: 'plant_cell_papilla and haustorium', description: 'multiple root uris', - ols_root_term_uri: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', + ols_root_term_uris: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', sample_controlled_vocab_terms_attributes: { '0' => { label: 'plant cell papilla', iri:'http://purl.obolibrary.org/obo/GO_0090395', parent_iri:'', _destroy: '0' }, '1' => { label: 'haustorium', iri:'http://purl.obolibrary.org/obo/GO_0085035', parent_iri:'', _destroy: '0' } @@ -326,7 +326,7 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase assert_equal 2, cv.sample_controlled_vocab_terms.count assert_equal ['plant cell papilla','haustorium'], cv.labels assert_equal ['http://purl.obolibrary.org/obo/GO_0090395','http://purl.obolibrary.org/obo/GO_0085035'], cv.sample_controlled_vocab_terms.collect(&:iri) - assert_equal 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', cv.ols_root_term_uri + assert_equal 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', cv.ols_root_term_uris end test 'fetch ols terms as HTML with multiple root uris and root term included' do diff --git a/test/integration/api/sample_controlled_vocab_api_test.rb b/test/integration/api/sample_controlled_vocab_api_test.rb index 809f9439ae..e8def6b2d2 100644 --- a/test/integration/api/sample_controlled_vocab_api_test.rb +++ b/test/integration/api/sample_controlled_vocab_api_test.rb @@ -9,7 +9,7 @@ def setup login_as(FactoryBot.create(:project_administrator)) @sample_controlled_vocab = SampleControlledVocab.new({ title:"a title", description:"some description", - source_ontology: "EFO", ols_root_term_uri: "http://a_uri", + source_ontology: "EFO", ols_root_term_uris: "http://a_uri", short_name: "short_name" }) @sample_controlled_vocab_term = SampleControlledVocabTerm.new({ label: "organism", iri: "http://some_iri", diff --git a/test/unit/sample_controlled_vocab_test.rb b/test/unit/sample_controlled_vocab_test.rb index d5f6798b90..16ab2d1f62 100644 --- a/test/unit/sample_controlled_vocab_test.rb +++ b/test/unit/sample_controlled_vocab_test.rb @@ -39,22 +39,22 @@ class SampleControlledVocabTest < ActiveSupport::TestCase vocab = SampleControlledVocab.new(title: 'multiple uris') assert vocab.valid? - vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395' + vocab.ols_root_term_uris = 'http://purl.obolibrary.org/obo/GO_0090395' assert vocab.valid? - vocab.ols_root_term_uri = 'wibble' + vocab.ols_root_term_uris = 'wibble' refute vocab.valid? - vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035' + vocab.ols_root_term_uris = 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035' assert vocab.valid? - vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, http://purl.obolibrary.org/obo/GO_0090396' + vocab.ols_root_term_uris = 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, http://purl.obolibrary.org/obo/GO_0090396' assert vocab.valid? - assert_equal 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, http://purl.obolibrary.org/obo/GO_0090396', vocab.ols_root_term_uri - vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, wibble' + assert_equal 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, http://purl.obolibrary.org/obo/GO_0090396', vocab.ols_root_term_uris + vocab.ols_root_term_uris = 'http://purl.obolibrary.org/obo/GO_0090395, wibble' refute vocab.valid? - vocab.ols_root_term_uri = 'http://purl.obolibrary.org/obo/GO_0090395, ' + vocab.ols_root_term_uris = 'http://purl.obolibrary.org/obo/GO_0090395, ' assert vocab.valid? - assert_equal 'http://purl.obolibrary.org/obo/GO_0090395', vocab.ols_root_term_uri + assert_equal 'http://purl.obolibrary.org/obo/GO_0090395', vocab.ols_root_term_uris end test 'validate unique key' do From 186cbdece8053d5577ce71a801b4362b4ce8ed79 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 6 Feb 2024 14:16:00 +0000 Subject: [PATCH 104/344] pluralized root_uris parameter used to fetch terms #1690 --- .../javascripts/controlled_vocabs.js.erb | 2 +- .../sample_controlled_vocabs_controller.rb | 6 ++--- app/models/sample_controlled_vocab.rb | 2 +- .../sample_controlled_vocabs/_form.html.erb | 8 +++--- ...ample_controlled_vocabs_controller_test.rb | 26 +++++++++++++++---- test/unit/sample_controlled_vocab_test.rb | 4 +++ 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/controlled_vocabs.js.erb b/app/assets/javascripts/controlled_vocabs.js.erb index 214351975f..c2fbd1af68 100644 --- a/app/assets/javascripts/controlled_vocabs.js.erb +++ b/app/assets/javascripts/controlled_vocabs.js.erb @@ -137,7 +137,7 @@ CVTerms = { dataType: "html", data: { source_ontology_id: $j('select#sample_controlled_vocab_source_ontology').val(), - root_uri: $j('#sample_controlled_vocab_ols_root_term_uris').val(), + root_uris: $j('#sample_controlled_vocab_ols_root_term_uris').val(), include_root_term: $j('#include_root_term:checked').val() }, success: function (resp) { diff --git a/app/controllers/sample_controlled_vocabs_controller.rb b/app/controllers/sample_controlled_vocabs_controller.rb index d8842cca3e..a06b2afce9 100644 --- a/app/controllers/sample_controlled_vocabs_controller.rb +++ b/app/controllers/sample_controlled_vocabs_controller.rb @@ -89,12 +89,12 @@ def fetch_ols_terms_html error_msg = nil begin source_ontology = params[:source_ontology_id] - root_uri = params[:root_uri] + root_uris = params[:root_uris] - raise 'No root URI provided' if root_uri.blank? + raise 'No root URI provided' if root_uris.blank? @terms = [] client = Ebi::OlsClient.new - root_uri.split(',').collect(&:strip).each do |uri| + root_uris.split(',').collect(&:strip).reject(&:blank?).each do |uri| terms = client.all_descendants(source_ontology, uri) terms.reject! { |t| t[:iri] == uri } unless params[:include_root_term] == '1' @terms = @terms | terms diff --git a/app/models/sample_controlled_vocab.rb b/app/models/sample_controlled_vocab.rb index 73bd6b82a4..cb7c8dc7ce 100644 --- a/app/models/sample_controlled_vocab.rb +++ b/app/models/sample_controlled_vocab.rb @@ -62,7 +62,7 @@ def ontology_based? def validate_ols_root_term_uris return if self.ols_root_term_uris.blank? - uris = self.ols_root_term_uris.split(',').collect(&:strip) + uris = self.ols_root_term_uris.split(',').collect(&:strip).reject(&:blank?) uris.each do |uri| unless valid_url?(uri) errors.add(:ols_root_term_uris, "invalid URI - #{uri}") diff --git a/app/views/sample_controlled_vocabs/_form.html.erb b/app/views/sample_controlled_vocabs/_form.html.erb index 54ad43a833..74556c98ef 100644 --- a/app/views/sample_controlled_vocabs/_form.html.erb +++ b/app/views/sample_controlled_vocabs/_form.html.erb @@ -39,17 +39,19 @@ You have selected the ontology, click the link to browse on the Ontology Lookup Service in another tab and find the suitable root term URI. You should then copy that URI into the field below. + If you wish to include terms from more than one root, then add the URI's separated by a comma (,).
    +
    - + <%= f.text_field :ols_root_term_uris, :class => 'form-control', :placeholder => 'e.g. http://www.ebi.ac.uk/efo/EFO_0000635' %>
    diff --git a/test/functional/sample_controlled_vocabs_controller_test.rb b/test/functional/sample_controlled_vocabs_controller_test.rb index 7edebe38f5..85a9f08650 100644 --- a/test/functional/sample_controlled_vocabs_controller_test.rb +++ b/test/functional/sample_controlled_vocabs_controller_test.rb @@ -273,7 +273,7 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase login_as(person) VCR.use_cassette('ols/fetch_obo_bad_term') do get :fetch_ols_terms_html, params: { source_ontology_id: 'go', - root_uri: 'http://purl.obolibrary.org/obo/banana', + root_uris: 'http://purl.obolibrary.org/obo/banana', include_root_term: '1' } assert_response :unprocessable_entity @@ -286,7 +286,7 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase login_as(person) VCR.use_cassette('ols/fetch_obo_plant_cell_papilla') do get :fetch_ols_terms_html, params: { source_ontology_id: 'go', - root_uri: 'http://purl.obolibrary.org/obo/GO_0090395', + root_uris: 'http://purl.obolibrary.org/obo/GO_0090395', include_root_term: '1' } end @@ -335,7 +335,7 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase VCR.use_cassette('ols/fetch_obo_plant_cell_papilla') do VCR.use_cassette('ols/fetch_obo_haustorium') do get :fetch_ols_terms_html, params: { source_ontology_id: 'go', - root_uri: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', + root_uris: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', include_root_term: '1' } end end @@ -370,7 +370,7 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase VCR.use_cassette('ols/fetch_obo_plant_cell_papilla') do VCR.use_cassette('ols/fetch_obo_haustorium') do get :fetch_ols_terms_html, params: { source_ontology_id: 'go', - root_uri: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', + root_uris: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', include_root_term: '0' } end end @@ -394,12 +394,28 @@ class SampleControlledVocabsControllerTest < ActionController::TestCase end + test 'fetch ols terms as HTML with multiple root uris forgiving of trailing comma' do + person = FactoryBot.create(:person) + login_as(person) + VCR.use_cassette('ols/fetch_obo_plant_cell_papilla') do + VCR.use_cassette('ols/fetch_obo_haustorium') do + get :fetch_ols_terms_html, params: { source_ontology_id: 'go', + root_uris: 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, ', + include_root_term: '0' } + end + end + + assert_response :success + assert_select 'tr.sample-cv-term', count: 4 + end + + test 'fetch ols terms as HTML without root term included' do person = FactoryBot.create(:person) login_as(person) VCR.use_cassette('ols/fetch_obo_plant_cell_papilla') do get :fetch_ols_terms_html, params: { source_ontology_id: 'go', - root_uri: 'http://purl.obolibrary.org/obo/GO_0090395' } + root_uris: 'http://purl.obolibrary.org/obo/GO_0090395' } end assert_response :success assert_select 'tr.sample-cv-term', count: 3 diff --git a/test/unit/sample_controlled_vocab_test.rb b/test/unit/sample_controlled_vocab_test.rb index 16ab2d1f62..6918c77450 100644 --- a/test/unit/sample_controlled_vocab_test.rb +++ b/test/unit/sample_controlled_vocab_test.rb @@ -55,6 +55,10 @@ class SampleControlledVocabTest < ActiveSupport::TestCase vocab.ols_root_term_uris = 'http://purl.obolibrary.org/obo/GO_0090395, ' assert vocab.valid? assert_equal 'http://purl.obolibrary.org/obo/GO_0090395', vocab.ols_root_term_uris + + vocab.ols_root_term_uris = 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035, ' + assert vocab.valid? + assert_equal 'http://purl.obolibrary.org/obo/GO_0090395, http://purl.obolibrary.org/obo/GO_0085035', vocab.ols_root_term_uris end test 'validate unique key' do From 65c6e47de996568e2215d954066adcffdeac69e3 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 6 Feb 2024 14:19:53 +0000 Subject: [PATCH 105/344] handle multiple root uris when showing the CV #1690 --- app/helpers/samples_helper.rb | 8 +++++--- app/views/sample_controlled_vocabs/show.html.erb | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index a0c7b0bd50..bd4bfd36dd 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -283,9 +283,11 @@ def ols_ontology_link(ols_id) link_to(link,link,target: :_blank) end - def ols_root_term_link(ols_id, term_uri) - ols_link = "#{Ebi::OlsClient::ROOT_URL}/ontologies/#{ols_id}/terms?iri=#{term_uri}" - link_to(term_uri, ols_link, target: :_blank) + def ols_root_term_link(ols_id, term_uris) + term_uris.split(',').collect(&:strip).collect do |uri| + ols_link = "#{Ebi::OlsClient::ROOT_URL}/ontologies/#{ols_id}/terms?iri=#{uri}" + link_to(uri, ols_link, target: :_blank) + end.join(', ').html_safe end def get_extra_info(sample) diff --git a/app/views/sample_controlled_vocabs/show.html.erb b/app/views/sample_controlled_vocabs/show.html.erb index 9b53dea7d9..9f8cca04ca 100644 --- a/app/views/sample_controlled_vocabs/show.html.erb +++ b/app/views/sample_controlled_vocabs/show.html.erb @@ -10,7 +10,7 @@ <%= ols_ontology_link(@sample_controlled_vocab.source_ontology) %>

    - Root URI: + Root URIs: <%= ols_root_term_link(@sample_controlled_vocab.source_ontology,@sample_controlled_vocab.ols_root_term_uris) %>

    <% end %> From fab6eb2dca4441e7b723c74ba75578dc65ed606d Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 6 Feb 2024 14:45:13 +0000 Subject: [PATCH 106/344] fix javascript for deleting new terms #1690 relied on a class being set of .success, which if set allowed it to be immediately removed rather than being marked for removal. Fixes a bug introduced with #1691 --- .../_term_form_row_disabled.html.erb | 10 +++++++++- .../fetch_ols_terms_html.html.erb | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/sample_controlled_vocabs/_term_form_row_disabled.html.erb b/app/views/sample_controlled_vocabs/_term_form_row_disabled.html.erb index ef7bf9fd7d..2be8514245 100644 --- a/app/views/sample_controlled_vocabs/_term_form_row_disabled.html.erb +++ b/app/views/sample_controlled_vocabs/_term_form_row_disabled.html.erb @@ -1,4 +1,12 @@ - +<% + new_term ||= false + row_class = 'sample-cv-term' + # will be marked in green, and also the javascript will immediately remove rather than mark for removal + if new_term + row_class+=' success' + end +%> + <%= hidden_field_tag "sample_controlled_vocab[sample_controlled_vocab_terms_attributes][#{index}][label]", term.label %>
    <%= term.label %>
    diff --git a/app/views/sample_controlled_vocabs/fetch_ols_terms_html.html.erb b/app/views/sample_controlled_vocabs/fetch_ols_terms_html.html.erb index d070cfc3db..ab892a4fa3 100644 --- a/app/views/sample_controlled_vocabs/fetch_ols_terms_html.html.erb +++ b/app/views/sample_controlled_vocabs/fetch_ols_terms_html.html.erb @@ -1,5 +1,5 @@ <% @terms.each_with_index do |term, index| %> - <%= render partial: 'sample_controlled_vocabs/term_form_row_disabled', locals: { index: index, term: OpenStruct.new(term)} %> + <%= render partial: 'sample_controlled_vocabs/term_form_row_disabled', locals: { index: index, term: OpenStruct.new(term), new_term: true} %> <% end %> \ No newline at end of file From 5a3c22af861eff76ae1af6048d0c861d884bcd91 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 7 Feb 2024 13:55:35 +0000 Subject: [PATCH 107/344] Use instance name in large file warning (for git file). Fixes #1733 --- app/views/git/_add_file_form.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/git/_add_file_form.html.erb b/app/views/git/_add_file_form.html.erb index e56ae42f1b..cc689a942c 100644 --- a/app/views/git/_add_file_form.html.erb +++ b/app/views/git/_add_file_form.html.erb @@ -39,7 +39,7 @@ From 2d64a261509202bf45b453c41613a3bf0dc42935 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 7 Feb 2024 17:09:39 +0000 Subject: [PATCH 108/344] Fix managing git files with `.json` extension responding with JSON in browser. Fixes #1732 --- app/controllers/git_controller.rb | 14 ++++++- test/integration/git_format_test.rb | 57 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 test/integration/git_format_test.rb diff --git a/app/controllers/git_controller.rb b/app/controllers/git_controller.rb index 9a283e78af..05e86cca36 100644 --- a/app/controllers/git_controller.rb +++ b/app/controllers/git_controller.rb @@ -233,8 +233,18 @@ def coerce_format # However this results in an UnknownFormat error when trying to load the HTML view, as Rails still seems to be # looking for an e.g. application/yaml view. # You can fix this by adding { defaults: { format: :html } }, but then it is not possible to request JSON, - # even with an explicit `Accept: application/json` header! -Finn - request.format = :html unless json_api_request? + # even with an explicit `Accept: application/json` header! + # + # GitLab uses a monkeypatch to avoid this: + # https://gitlab.com/gitlab-org/gitlab/-/blob/7a0c278e/config/initializers/action_dispatch_http_mime_negotiation.rb + # but not sure what the wider consequences of that are. + # -Finn + accept_format = request.accepts.first + if accept_format.nil? || accept_format.ref == '*/*' + request.format = :html + else + request.format = accept_format.symbol + end end def file_content diff --git a/test/integration/git_format_test.rb b/test/integration/git_format_test.rb new file mode 100644 index 0000000000..60cdadb15a --- /dev/null +++ b/test/integration/git_format_test.rb @@ -0,0 +1,57 @@ +require 'test_helper' + +class GitFormatTest < ActionDispatch::IntegrationTest + test 'gets appropriate response format from git controller' do + workflow = FactoryBot.create(:local_git_workflow, policy: FactoryBot.create(:public_policy)) + git_version = workflow.git_version + disable_authorization_checks do + git_version.add_file('dmp.json', open_fixture_file('dmp.json')) + git_version.add_file('file', open_fixture_file('file')) + git_version.add_file('html_file.html', open_fixture_file('html_file.html')) + git_version.save! + end + + ['dmp.json', 'file', 'html_file.html', 'diagram.png'].each do |path| + # Default to HTML response + get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => '*/*' } + assert_response :success + assert_equal 'text/html; charset=utf-8', response.headers['Content-Type'] + assert response.body.start_with?('') + + # Even without any Accept + get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => '' } + assert_response :success + assert_equal 'text/html; charset=utf-8', response.headers['Content-Type'] + assert response.body.start_with?('') + + # Default headers + get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5'} + assert_response :success + assert_equal 'text/html; charset=utf-8', response.headers['Content-Type'] + assert response.body.start_with?('') + + # Explicit HTML + get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => 'text/html' } + assert_response :success + assert_equal 'text/html; charset=utf-8', response.headers['Content-Type'] + assert response.body.start_with?('') + + # Permit JSON response + get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => 'application/json' } + assert_response :success + assert_equal 'application/vnd.api+json; charset=utf-8', response.headers['Content-Type'] + assert_equal path, JSON.parse(response.body)['path'] + + # Provide JSON response if Accept prefers + get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => 'application/json;q=0.9,text/html;q=0.8,*/*;q=0.5' } + assert_response :success + assert_equal 'application/vnd.api+json; charset=utf-8', response.headers['Content-Type'] + assert_equal path, JSON.parse(response.body)['path'] + + # Otherwise unrecognized format + assert_raises(ActionController::UnknownFormat) do + get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => 'application/zip' } + end + end + end +end \ No newline at end of file From 85c26d7ec87faeca0963b89d82001e93535422b6 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 8 Feb 2024 09:59:56 +0000 Subject: [PATCH 109/344] Monkeypatch MimeNegotiation to ignore format from extension --- app/controllers/application_controller.rb | 5 +++ app/controllers/git_controller.rb | 33 +++++-------------- .../action_dispatch_http_mime_negotiation.rb | 23 +++++++++++++ test/integration/git_format_test.rb | 6 ---- 4 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 config/initializers/action_dispatch_http_mime_negotiation.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b9c458fc5c..ee63880624 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -677,4 +677,9 @@ def safe_class_lookup(class_name, raise: true) end helper_method :safe_class_lookup + + # See: config/initializers/action_dispatch_http_mime_negotiation.rb + def self.ignore_format_from_extension + false + end end diff --git a/app/controllers/git_controller.rb b/app/controllers/git_controller.rb index 05e86cca36..bbcce46bbd 100644 --- a/app/controllers/git_controller.rb +++ b/app/controllers/git_controller.rb @@ -8,7 +8,6 @@ class GitController < ApplicationController before_action :fetch_git_version before_action :get_tree, only: [:tree] before_action :get_blob, only: [:blob, :download, :raw] - before_action :coerce_format user_content_actions :raw @@ -17,6 +16,11 @@ class GitController < ApplicationController rescue_from Git::InvalidPathException, with: :render_invalid_path_error rescue_from URI::InvalidURIError, with: :render_invalid_url_error + # See: config/initializers/action_dispatch_http_mime_negotiation.rb + def self.ignore_format_from_extension + true + end + def browse respond_to do |format| format.html @@ -25,12 +29,12 @@ def browse def tree respond_to do |format| - format.json { render json: @tree, adapter: :attributes, root: '' } if request.xhr? format.html { render partial: 'tree' } else format.html end + format.json { render json: @tree, adapter: :attributes, root: '' } end end @@ -40,12 +44,12 @@ def download def blob respond_to do |format| - format.json { render json: @blob, adapter: :attributes, root: '' } if request.xhr? format.html { render partial: 'blob' } else format.html end + format.json { render json: @blob, adapter: :attributes, root: '' } end end @@ -118,7 +122,6 @@ def update def operation_response(notice = nil, status: 200) respond_to do |format| - format.json { render json: { }, status: status, adapter: :attributes, root: '' } format.html do if request.xhr? render partial: 'files', locals: { resource: @parent_resource, git_version: @git_version }, status: status @@ -127,6 +130,7 @@ def operation_response(notice = nil, status: 200) redirect_to polymorphic_path(@parent_resource, tab: 'files') end end + format.json { render json: { }, status: status, adapter: :attributes, root: '' } end end @@ -226,27 +230,6 @@ def add_remote_file @git_version.save! end - def coerce_format - # I have to do this because Rails doesn't seem to be behaving as expected. - # In routes.rb, the git routes are scoped with "format: false", so Rails should disregard the extension - # (e.g. /git/1/blob/my_file.yml) when determining the response format. - # However this results in an UnknownFormat error when trying to load the HTML view, as Rails still seems to be - # looking for an e.g. application/yaml view. - # You can fix this by adding { defaults: { format: :html } }, but then it is not possible to request JSON, - # even with an explicit `Accept: application/json` header! - # - # GitLab uses a monkeypatch to avoid this: - # https://gitlab.com/gitlab-org/gitlab/-/blob/7a0c278e/config/initializers/action_dispatch_http_mime_negotiation.rb - # but not sure what the wider consequences of that are. - # -Finn - accept_format = request.accepts.first - if accept_format.nil? || accept_format.ref == '*/*' - request.format = :html - else - request.format = accept_format.symbol - end - end - def file_content file_params.key?(:content) ? StringIO.new(Base64.decode64(file_params[:content])) : file_params[:data] end diff --git a/config/initializers/action_dispatch_http_mime_negotiation.rb b/config/initializers/action_dispatch_http_mime_negotiation.rb new file mode 100644 index 0000000000..30d12c9252 --- /dev/null +++ b/config/initializers/action_dispatch_http_mime_negotiation.rb @@ -0,0 +1,23 @@ +# Monkeypatch because Rails doesn't seem to be behaving as expected. +# In routes.rb, the git routes are scoped with "format: false", so Rails should disregard the extension +# (e.g. /git/1/blob/my_file.yml) when determining the response format. +# However this results in an UnknownFormat error when trying to load the HTML view, as Rails still seems to be +# looking for an e.g. application/yaml view. +# You can fix this by adding { defaults: { format: :html } }, but then it is not possible to request JSON, +# even with an explicit `Accept: application/json` header! +# +# Inspired by GitLab's change: +# https://gitlab.com/gitlab-org/gitlab/-/blob/7a0c278e/config/initializers/action_dispatch_http_mime_negotiation.rb +# -Finn + +module ActionDispatch + module Http + module MimeNegotiation + alias original_format_from_path_extension format_from_path_extension + + def format_from_path_extension + controller_class&.ignore_format_from_extension ? nil : original_format_from_path_extension + end + end + end +end diff --git a/test/integration/git_format_test.rb b/test/integration/git_format_test.rb index 60cdadb15a..9d892f8061 100644 --- a/test/integration/git_format_test.rb +++ b/test/integration/git_format_test.rb @@ -42,12 +42,6 @@ class GitFormatTest < ActionDispatch::IntegrationTest assert_equal 'application/vnd.api+json; charset=utf-8', response.headers['Content-Type'] assert_equal path, JSON.parse(response.body)['path'] - # Provide JSON response if Accept prefers - get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => 'application/json;q=0.9,text/html;q=0.8,*/*;q=0.5' } - assert_response :success - assert_equal 'application/vnd.api+json; charset=utf-8', response.headers['Content-Type'] - assert_equal path, JSON.parse(response.body)['path'] - # Otherwise unrecognized format assert_raises(ActionController::UnknownFormat) do get "/workflows/#{workflow.id}/git/1/blob/#{path}", headers: { 'Accept' => 'application/zip' } From 46bdb1653f64aaca05ab65dbf45643f3c8307114 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 8 Feb 2024 10:05:49 +0000 Subject: [PATCH 110/344] Patch `ignore_format_from_extension` into `ActionController::Base` In case we ever use any controllers from engines etc. that don't inherit from `ApplicationController` --- app/controllers/application_controller.rb | 5 ----- .../initializers/action_dispatch_http_mime_negotiation.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ee63880624..b9c458fc5c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -677,9 +677,4 @@ def safe_class_lookup(class_name, raise: true) end helper_method :safe_class_lookup - - # See: config/initializers/action_dispatch_http_mime_negotiation.rb - def self.ignore_format_from_extension - false - end end diff --git a/config/initializers/action_dispatch_http_mime_negotiation.rb b/config/initializers/action_dispatch_http_mime_negotiation.rb index 30d12c9252..d37c457204 100644 --- a/config/initializers/action_dispatch_http_mime_negotiation.rb +++ b/config/initializers/action_dispatch_http_mime_negotiation.rb @@ -21,3 +21,11 @@ def format_from_path_extension end end end + +module ActionController + class Base + def self.ignore_format_from_extension + false + end + end +end From ad1d8f8dffb5ec2d0629faae095111114b01a214 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 7 Feb 2024 11:16:41 +0000 Subject: [PATCH 111/344] update docker compose to use mysql 8.0 #1743 tested updating an existing database, and also starting with a fresh install --- docker-compose-relative-root.yml | 4 ++-- docker-compose-virtuoso.yml | 4 ++-- docker-compose-with-email.yml | 4 ++-- docker-compose.yml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docker-compose-relative-root.yml b/docker-compose-relative-root.yml index 79ecc5d6fb..c9f214cbbf 100644 --- a/docker-compose-relative-root.yml +++ b/docker-compose-relative-root.yml @@ -1,9 +1,9 @@ version: '3' services: db: # Database implementation, in this case MySQL - image: mysql:5.7 + image: mysql:8.0 container_name: seek-mysql - command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --log-error-verbosity=1 restart: always stop_grace_period: 1m30s env_file: diff --git a/docker-compose-virtuoso.yml b/docker-compose-virtuoso.yml index 339571384f..626485b5a5 100644 --- a/docker-compose-virtuoso.yml +++ b/docker-compose-virtuoso.yml @@ -1,9 +1,9 @@ version: '3' services: db: # Database implementation, in this case MySQL - image: mysql:5.7 + image: mysql:8.0 container_name: seek-mysql - command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --log-error-verbosity=1 restart: always env_file: - docker/db.env diff --git a/docker-compose-with-email.yml b/docker-compose-with-email.yml index 2da2c1831f..19beb35095 100644 --- a/docker-compose-with-email.yml +++ b/docker-compose-with-email.yml @@ -1,9 +1,9 @@ version: '3' services: db: # Database implementation, in this case MySQL - image: mysql:5.7 + image: mysql:8.0 container_name: seek-mysql - command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --log-error-verbosity=1 restart: always stop_grace_period: 1m30s env_file: diff --git a/docker-compose.yml b/docker-compose.yml index caee6980d5..e784ad4286 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ version: '3' services: db: # Database implementation, in this case MySQL - image: mysql:5.7 + image: mysql:8.0 container_name: seek-mysql - command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --log-error-verbosity=1 restart: always stop_grace_period: 1m30s env_file: From a21c3df626f34c24a6cb6ba8b4ef991cd293d91b Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 5 Feb 2024 15:29:37 +0000 Subject: [PATCH 112/344] updates to the file view icon (magnifier) to also link to explore for excel, csv and tsv files #1711 --- app/views/assets/_view_content.html.erb | 10 +++++++++- test/functional/data_files_controller_test.rb | 19 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/views/assets/_view_content.html.erb b/app/views/assets/_view_content.html.erb index ca4b1574e7..a6c807019e 100644 --- a/app/views/assets/_view_content.html.erb +++ b/app/views/assets/_view_content.html.erb @@ -2,9 +2,10 @@ <% options = {} %> <% url = nil %> <% asset = content_blob.asset %> +<% asset_version = content_blob.asset_version %> <% if Seek::Util.inline_viewable_content_types.include?(asset.class) %> - <% if content_blob.is_content_viewable? %> + <% if content_blob.is_content_viewable? && !content_blob.is_supported_spreadsheet_format? %> <%# Use fancy lightbox thing for displaying images in page %> <% if content_blob.is_image? %> <% zoom_width = content_blob.width.to_i.clamp(Seek::ActsAsFleximageExtension::STANDARD_SIZE, @@ -17,6 +18,13 @@ <% url = polymorphic_path([asset, content_blob], action: 'view_content', code: params[:code]) %> <% options = { title: 'View contents of this file' } %> <% end %> + <% elsif !asset.is_a?(Model) && content_blob.is_supported_spreadsheet_format? %> + <% if content_blob.is_extractable_spreadsheet? %> + <% url = polymorphic_path([asset], action: 'explore', code: params[:code], version: asset_version) %> + <% options = { title: 'Explore the contents of this file' } %> + <% else %> + <% options = { disabled_reason: "This spreadsheet is too big to be explored (larger than #{Seek::Config.max_extractable_spreadsheet_size} MB), or the file does not exist." } %> + <% end %> <% elsif !asset.is_a?(Model) && asset.is_downloadable_asset? %> <% if content_blob.file_exists? %> <% options[:disabled_reason] = "This #{text_for_resource(asset).downcase} is unable to be viewed in-browser. " + diff --git a/test/functional/data_files_controller_test.rb b/test/functional/data_files_controller_test.rb index ceec265aec..14d319c299 100644 --- a/test/functional/data_files_controller_test.rb +++ b/test/functional/data_files_controller_test.rb @@ -608,7 +608,7 @@ def test_title end end - test 'show explore button' do + test 'show explore button and magnify icon' do df = FactoryBot.create(:small_test_spreadsheet_datafile) login_as(df.contributor.user) get :show, params: { id: df } @@ -617,9 +617,12 @@ def test_title assert_select 'a[href=?]', explore_data_file_path(df, version: df.version), count: 1 assert_select 'a.disabled', text: 'Explore', count: 0 end + assert_select 'div.fileinfo span.filename' do + assert_select 'a[href=?][title=?]', explore_data_file_path(df, version: df.version), 'Explore the contents of this file', count: 1 + end end - test 'show explore button for csv file' do + test 'show explore button and magnify icon for csv file' do df = FactoryBot.create(:csv_spreadsheet_datafile) login_as(df.contributor.user) get :show, params: { id: df } @@ -628,6 +631,9 @@ def test_title assert_select 'a[href=?]', explore_data_file_path(df, version: df.version), count: 1 assert_select 'a.disabled', text: 'Explore', count: 0 end + assert_select 'div.fileinfo span.filename' do + assert_select 'a[href=?][title=?]', explore_data_file_path(df, version: df.version), 'Explore the contents of this file', count: 1 + end end @@ -642,9 +648,12 @@ def test_title assert_select 'a[href=?]', explore_data_file_path(df, version: df.version), count: 0 assert_select 'a', text: 'Explore', count: 0 end + assert_select 'div.fileinfo span.filename' do + assert_select 'a[href=?][title=?]', explore_data_file_path(df, version: df.version), 'Explore the contents of this file', count: 0 + end end - test 'show disabled explore button if spreadsheet too big' do + test 'show disabled explore button and magnify icon if spreadsheet too big' do df = FactoryBot.create(:small_test_spreadsheet_datafile) login_as(df.contributor.user) with_config_value(:max_extractable_spreadsheet_size, 0) do @@ -655,6 +664,10 @@ def test_title assert_select 'a[href=?]', explore_data_file_path(df, version: df.version), count: 0 assert_select 'a.disabled', text: 'Explore', count: 1 end + assert_select 'div.fileinfo span.filename' do + assert_select 'a[href=?][title=?]', explore_data_file_path(df, version: df.version), 'Explore the contents of this file', count: 0 + assert_select 'span > a.disabled > img', count: 1 + end end test 'should download datafile from standard route' do From 99c8e3f121a23b8879b0d0cca008e33f4d91c051 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 2 Feb 2024 15:10:55 +0100 Subject: [PATCH 113/344] Update syntax --- .../isa_studies/_sample_types_form.html.erb | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/views/isa_studies/_sample_types_form.html.erb b/app/views/isa_studies/_sample_types_form.html.erb index b2c7605af6..3eff0b17f1 100644 --- a/app/views/isa_studies/_sample_types_form.html.erb +++ b/app/views/isa_studies/_sample_types_form.html.erb @@ -8,19 +8,19 @@ <%= f.fields_for main_field_name, sample_type do |field| %> <% unless action == :edit %> - <%= folding_panel("Choose a #{t(:template)} to create a #{t(:sample_type)}", false, {help_text: "Create #{t(:template)} based on existing #{t(:template).pluralize()}", :body_options => {:id => 'add_custom_attribute_panel'}}) do %> + <%= folding_panel("Choose a #{t(:template)} to create a #{t(:sample_type)}", false, {help_text: "Create #{t(:template)} based on existing #{t(:template).pluralize()}", body_options: {id: 'add_custom_attribute_panel'}}) do %> <% end %> <% end %>
    <%= required_span %> - <%= field.text_field :title, :class => 'form-control', :placeholder => 'Sample type name' %> + <%= field.text_field :title, class: 'form-control', placeholder: 'Sample type name' %>
    - <%= field.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%> + <%= field.text_area :description, class: "form-control rich-text-edit", rows: 5 -%>
    <%= field.hidden_field :template_id, { id: "isa_study#{id_suffix}template_parent_id" } %> @@ -45,17 +45,17 @@ <% sample_type.sample_attributes.each_with_index do |sample_attribute, index| %> - <%= render :partial => 'sample_types/sample_attribute_form', :locals => { :index => index, - :sample_attribute => sample_attribute, - :sample_type=>sample_type, - :prefix => "isa_#{isa_element}[#{main_field_name}]", - :display_isa_tag => true } %> + <%= render partial: 'sample_types/sample_attribute_form', locals: { index: index, + sample_attribute: sample_attribute, + sample_type: sample_type, + prefix: "isa_#{isa_element}[#{main_field_name}]", + display_isa_tag: true } %> <% end %> <% unless sample_type.uploaded_template? || !sample_type.editing_constraints.allow_new_attribute? %> > - <%= button_link_to('Add new attribute', 'add', '#', :id => add_attribute_id ) %> + <%= button_link_to('Add new attribute', 'add', '#', id: add_attribute_id ) %> <% end %> @@ -68,9 +68,9 @@ style="display:none"> - <%= render :partial => 'sample_types/sample_attribute_form', :locals=> { sample_type:sample_type, - prefix: "isa_#{isa_element}[#{main_field_name}]", - display_isa_tag: true } %> + <%= render partial: 'sample_types/sample_attribute_form', locals: { sample_type: sample_type, + prefix: "isa_#{isa_element}[#{main_field_name}]", + display_isa_tag: true } %>
    From c238b881ef0e7a004a15441bfe56c7097557c103 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 2 Feb 2024 15:35:08 +0100 Subject: [PATCH 114/344] Add link between template attribute and sample attribute by adding a foreign key `template_attribute_id` --- app/models/sample_attribute.rb | 1 + app/models/template_attribute.rb | 1 + ...142619_add_template_attribute_id_to_sample_attributes.rb | 6 ++++++ db/schema.rb | 4 +++- 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240202142619_add_template_attribute_id_to_sample_attributes.rb diff --git a/app/models/sample_attribute.rb b/app/models/sample_attribute.rb index 97707ea259..4811502452 100644 --- a/app/models/sample_attribute.rb +++ b/app/models/sample_attribute.rb @@ -7,6 +7,7 @@ class SampleAttribute < ApplicationRecord belongs_to :linked_sample_type, class_name: 'SampleType' belongs_to :isa_tag + belongs_to :template_attribute, class_name: 'TemplateAttribute', foreign_key: 'template_id' auto_strip_attributes :pid validates :sample_type, presence: true diff --git a/app/models/template_attribute.rb b/app/models/template_attribute.rb index 65574d7855..d74d090832 100644 --- a/app/models/template_attribute.rb +++ b/app/models/template_attribute.rb @@ -5,6 +5,7 @@ class TemplateAttribute < ApplicationRecord belongs_to :unit belongs_to :isa_tag belongs_to :linked_sample_type, class_name: 'SampleType' + has_many :sample_attributes validates :title, presence: true diff --git a/db/migrate/20240202142619_add_template_attribute_id_to_sample_attributes.rb b/db/migrate/20240202142619_add_template_attribute_id_to_sample_attributes.rb new file mode 100644 index 0000000000..423fc2de58 --- /dev/null +++ b/db/migrate/20240202142619_add_template_attribute_id_to_sample_attributes.rb @@ -0,0 +1,6 @@ +class AddTemplateAttributeIdToSampleAttributes < ActiveRecord::Migration[6.1] + def change + add_column :sample_attributes, :template_attribute_id, :integer + add_index :sample_attributes, :template_attribute_id + end +end diff --git a/db/schema.rb b/db/schema.rb index c9d24b5489..8d3c26b86b 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: 2024_02_06_132054) do +ActiveRecord::Schema.define(version: 2024_02_02_142619) do create_table "activity_logs", id: :integer, force: :cascade do |t| t.string "action" @@ -1715,7 +1715,9 @@ t.text "description" t.integer "isa_tag_id" t.boolean "allow_cv_free_text", default: false + t.integer "template_attribute_id" t.index ["sample_type_id"], name: "index_sample_attributes_on_sample_type_id" + t.index ["template_attribute_id"], name: "index_sample_attributes_on_template_attribute_id" t.index ["unit_id"], name: "index_sample_attributes_on_unit_id" end From 48c99d7fd1a0e76a1baba105e7ec7c8a81ea92e1 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 5 Feb 2024 14:34:07 +0100 Subject: [PATCH 115/344] Add sample type level function --- 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 36648afa04..d64709da02 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 level + isa_template&.level + end + def previous_linked_sample_type sample_attributes.detect(&:input_attribute?)&.linked_sample_type end From 3dcd518da7022bdefd36e9ea63274e3b2938b056 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 5 Feb 2024 14:35:18 +0100 Subject: [PATCH 116/344] Select ISA tags based on the level --- app/models/isa_tag.rb | 17 +++++++++++++++++ lib/seek/isa/tag_type.rb | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/app/models/isa_tag.rb b/app/models/isa_tag.rb index 4494bf5afc..367a309a68 100644 --- a/app/models/isa_tag.rb +++ b/app/models/isa_tag.rb @@ -43,4 +43,21 @@ def isa_data_file_comment? def isa_parameter_value? title == Seek::ISA::TagType::PARAMETER_VALUE end + + def self.allowed_isa_tags_for_level(level) + tags = case level + when 'study source' + Seek::ISA::TagType::SOURCE_TAGS + when 'study sample' + Seek::ISA::TagType::SAMPLE_TAGS + when 'assay - material' + Seek::ISA::TagType::OTHER_MATERIAL_TAGS + when 'assay - data file' + Seek::ISA::TagType::DATA_FILE_TAGS + else + Seek::ISA::TagType::ALL_TYPES + end + + tags.map { |tag| IsaTag.find_by(title: tag) } + end end diff --git a/lib/seek/isa/tag_type.rb b/lib/seek/isa/tag_type.rb index 81dc79b65a..70f6001e10 100644 --- a/lib/seek/isa/tag_type.rb +++ b/lib/seek/isa/tag_type.rb @@ -3,6 +3,11 @@ module ISA module TagType ALL_TYPES = %w(source source_characteristic sample sample_characteristic protocol other_material other_material_characteristic data_file data_file_comment parameter_value) + SOURCE_TAGS = %w(source source_characteristic) + SAMPLE_TAGS = %w(sample sample_characteristic protocol parameter_value) + OTHER_MATERIAL_TAGS = %w(other_material other_material_characteristic protocol parameter_value) + DATA_FILE_TAGS = %w(data_file data_file_comment protocol parameter_value) + ALL_TYPES.each do |type| TagType.const_set(type.underscore.upcase, type) end From 8e46c0c8ab780e2ffd8ed75d8cf4558ed3ed059a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 5 Feb 2024 14:37:33 +0100 Subject: [PATCH 117/344] Add template attribute id to the template creation modal --- app/assets/javascripts/templates.js | 5 +++-- app/helpers/templates_helper.rb | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/templates.js b/app/assets/javascripts/templates.js index 2584f23ed6..8a8050999f 100644 --- a/app/assets/javascripts/templates.js +++ b/app/assets/javascripts/templates.js @@ -58,7 +58,6 @@ Templates.init = function (elem) { : 'Remove'; } }, - { title: "linked sample type id", width: "10%" } ]; Templates.table = elem.DataTable({ @@ -125,7 +124,8 @@ Templates.mapData = (data) => item.pos, item.isa_tag_id, item.isa_tag_title, - item.linked_sample_type_id + item.linked_sample_type_id, + item.template_attribute_id ]); function loadFilterSelectors(data) { @@ -249,6 +249,7 @@ const applyTemplate = () => { $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); + $j(newRow).find('[data-attr="template_attribute_id"]').val(row[14]); // Show the CV block if cv_id is not empty if (row[4]) $j(newRow).find(".controlled-vocab-block").show(); diff --git a/app/helpers/templates_helper.rb b/app/helpers/templates_helper.rb index 80f97a8734..5d8b7eaa90 100644 --- a/app/helpers/templates_helper.rb +++ b/app/helpers/templates_helper.rb @@ -90,7 +90,8 @@ def map_template_attributes(attribute) pos: attribute.pos, isa_tag_id: attribute.isa_tag_id, isa_tag_title: attribute.isa_tag&.title, - linked_sample_type_id: attribute.linked_sample_type_id + linked_sample_type_id: attribute.linked_sample_type_id, + template_attribute_id: attribute.id } end end From 58ad6b8e4f7c94807a3e209759569ac49c40a5e0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 5 Feb 2024 14:39:50 +0100 Subject: [PATCH 118/344] Display ISA Tag options depending on the level --- app/views/sample_types/_sample_attribute_form.html.erb | 3 ++- 1 file 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 69004977cb..80adecdd4d 100644 --- a/app/views/sample_types/_sample_attribute_form.html.erb +++ b/app/views/sample_types/_sample_attribute_form.html.erb @@ -23,7 +23,8 @@ <% 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? && sample_attribute&.isa_tag.blank? && sample_attribute&.title.downcase.include?('input') %> -<% isa_tags_options = IsaTag.all.map { |it| [it.title, it.id] } %> +<% level = sample_type.level %> +<% isa_tags_options = IsaTag.allowed_isa_tags_for_level(level).map { |it| [it.title, it.id] } %> <% display_isa_tag ||= false %> <% isa_tag_id = sample_attribute&.isa_tag_id %> <% isa_tag_title = sample_attribute&.isa_tag&.title %> From f36e5f2836e5f8f8a228415610c964a51cb75fe3 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 5 Feb 2024 14:40:23 +0100 Subject: [PATCH 119/344] Add template attribute id to the sample attribute forms --- app/views/sample_types/_sample_attribute_form.html.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/sample_types/_sample_attribute_form.html.erb b/app/views/sample_types/_sample_attribute_form.html.erb index 80adecdd4d..7442f5a26e 100644 --- a/app/views/sample_types/_sample_attribute_form.html.erb +++ b/app/views/sample_types/_sample_attribute_form.html.erb @@ -28,6 +28,8 @@ <% display_isa_tag ||= false %> <% isa_tag_id = sample_attribute&.isa_tag_id %> <% isa_tag_title = sample_attribute&.isa_tag&.title %> +<% template_attribute_id = sample_attribute&.template_attribute_id %> +<% allow_isa_tag_change = constraints.allow_isa_tag_change?(sample_attribute) %> @@ -134,6 +136,7 @@ <% if allow_attribute_removal %> <%= hidden_field_tag "#{field_name_prefix}[isa_tag_id]", (display_isa_tag ? isa_tag_id : nil), data: { attr: "isa_tag_id" } %> + <%= hidden_field_tag "#{field_name_prefix}[template_attribute_id]", template_attribute_id, data: { attr: "template_attribute_id" } %> <%= hidden_field_tag "#{field_name_prefix}[_destroy]", '0', autocomplete: 'off' %>
    -<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:f, resource:@assay} %> +<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:f, resource:@assay} %> -<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:f,resource:@assay} %> +<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:f,resource:@assay} %>
    diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 9f85fc1b39..dcdfb72798 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -109,7 +109,7 @@
    <% end %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @assay } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @assay } %> <% if ((@assay.is_modelling?) && !@assay.models.empty? && !@assay.data_files.empty?) %><%#MODELLING ASSAY %>
    diff --git a/app/views/collections/edit.html.erb b/app/views/collections/edit.html.erb index baa4e84c63..e7ba5695fa 100644 --- a/app/views/collections/edit.html.erb +++ b/app/views/collections/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: @collection } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: {f: f, resource: @collection } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: {f: f, resource: @collection } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: {f: f, resource: @collection } %> <%= panel('Items') do %>

    Items can be re-arranged by clicking and dragging the button on the left-hand side of each row.

    diff --git a/app/views/collections/new.html.erb b/app/views/collections/new.html.erb index 9b519b39d3..fa98eaf160 100644 --- a/app/views/collections/new.html.erb +++ b/app/views/collections/new.html.erb @@ -19,8 +19,8 @@ <%= fields_for(@collection) do |collection_fields| %> - <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: collection_fields, resource: @collection } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: collection_fields, resource: @collection } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: collection_fields, resource: @collection } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: collection_fields, resource: @collection } %> <% end %> <%= render :partial => "projects/project_selector", :locals => { :resource => @collection } %> diff --git a/app/views/collections/show.html.erb b/app/views/collections/show.html.erb index faaf8c262f..64fd08936b 100644 --- a/app/views/collections/show.html.erb +++ b/app/views/collections/show.html.erb @@ -19,7 +19,7 @@ - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @collection } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @collection } %>
    diff --git a/app/views/data_files/edit.html.erb b/app/views/data_files/edit.html.erb index 9d147a8bea..44cb455de9 100644 --- a/app/views/data_files/edit.html.erb +++ b/app/views/data_files/edit.html.erb @@ -15,8 +15,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: @data_file } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %>
    Check this box if this data is the result of a model simulation
    diff --git a/app/views/data_files/multi-steps/_basic_metadata.html.erb b/app/views/data_files/multi-steps/_basic_metadata.html.erb index c021e4f32f..081a2e1a99 100644 --- a/app/views/data_files/multi-steps/_basic_metadata.html.erb +++ b/app/views/data_files/multi-steps/_basic_metadata.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: @data_file } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %>
    Check this box if this data is the result of a model simulation
    diff --git a/app/views/data_files/show.html.erb b/app/views/data_files/show.html.erb index 2b2585b194..1c1cfb5abf 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_types/extended_metadata_attribute_values', locals: { resource: @data_file } %> <%= render :partial => 'nels/data_sheet', locals: { displayed_resource: @display_data_file } if @data_file.nels? %>
    diff --git a/app/views/documents/edit.html.erb b/app/views/documents/edit.html.erb index 9c72f276d6..0f3280bad2 100644 --- a/app/views/documents/edit.html.erb +++ b/app/views/documents/edit.html.erb @@ -15,8 +15,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: @document } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @document } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @document } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @document } %> <%= render :partial => 'assets/license_selector', :locals => { :resource => @document } %> diff --git a/app/views/documents/new.html.erb b/app/views/documents/new.html.erb index 3ff17da491..8750c1550e 100644 --- a/app/views/documents/new.html.erb +++ b/app/views/documents/new.html.erb @@ -21,8 +21,8 @@ <%= fields_for(@document) do |document_fields| %> - <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: document_fields, resource: @document } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: document_fields, resource: @document } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: document_fields, resource: @document } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: document_fields, resource: @document } %> <% end %> diff --git a/app/views/documents/show.html.erb b/app/views/documents/show.html.erb index a428dc2cfb..1fcb7f1663 100644 --- a/app/views/documents/show.html.erb +++ b/app/views/documents/show.html.erb @@ -23,7 +23,7 @@ - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @document } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @document } %> <%= render :partial=>"general/isa_graph", :locals => {:root_item => @document, :deep => true, :include_parents => true} %> diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index a386b4271f..6f51797926 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -62,8 +62,8 @@
    - <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @event } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @event } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @event } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @event } %>
    <% if show_form_manage_specific_attributes? && @event.can_manage? %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index fcc858fc82..000cfef4e6 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -58,7 +58,7 @@ - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @event } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @event } %> <%= render :partial=>"general/isa_graph", :locals => {:root_item => @event, :deep => true, :include_parents => true} %> diff --git a/app/views/extended_metadata/_extended_metadata_attribute_input.html.erb b/app/views/extended_metadata_types/_extended_metadata_attribute_input.html.erb similarity index 91% rename from app/views/extended_metadata/_extended_metadata_attribute_input.html.erb rename to app/views/extended_metadata_types/_extended_metadata_attribute_input.html.erb index 32ee78fee6..d7cf168d3b 100644 --- a/app/views/extended_metadata/_extended_metadata_attribute_input.html.erb +++ b/app/views/extended_metadata_types/_extended_metadata_attribute_input.html.erb @@ -7,7 +7,7 @@ <%= folding_panel(panel_label, false, style:panel_style,id:'extended_metadata_attribute_panel') do %>
    <% if resource.extended_metadata.try(:extended_metadata_type) %> - <%= render partial: 'extended_metadata/extended_metadata_fields', locals: { extended_metadata_type: resource.extended_metadata.extended_metadata_type, resource:, parent_resource: } %> + <%= render partial: 'extended_metadata_types/extended_metadata_fields', locals: { extended_metadata_type: resource.extended_metadata.extended_metadata_type, resource:, parent_resource: } %> <% end %>
    <% end %> diff --git a/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb b/app/views/extended_metadata_types/_extended_metadata_attribute_values.html.erb similarity index 100% rename from app/views/extended_metadata/_extended_metadata_attribute_values.html.erb rename to app/views/extended_metadata_types/_extended_metadata_attribute_values.html.erb diff --git a/app/views/extended_metadata/_extended_metadata_fields.html.erb b/app/views/extended_metadata_types/_extended_metadata_fields.html.erb similarity index 100% rename from app/views/extended_metadata/_extended_metadata_fields.html.erb rename to app/views/extended_metadata_types/_extended_metadata_fields.html.erb diff --git a/app/views/extended_metadata/_extended_metadata_type_selection.html.erb b/app/views/extended_metadata_types/_extended_metadata_type_selection.html.erb similarity index 100% rename from app/views/extended_metadata/_extended_metadata_type_selection.html.erb rename to app/views/extended_metadata_types/_extended_metadata_type_selection.html.erb diff --git a/app/views/extended_metadata/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb b/app/views/extended_metadata_types/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb similarity index 79% rename from app/views/extended_metadata/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb rename to app/views/extended_metadata_types/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb index 06e156178a..9b681ad78a 100644 --- a/app/views/extended_metadata/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb +++ b/app/views/extended_metadata_types/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb @@ -10,11 +10,11 @@
    <% if value.blank? %> - <%= render partial: 'extended_metadata/single_row', locals: { index: index, value: nil, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> + <%= render partial: 'extended_metadata_types/single_row', locals: { index: index, value: nil, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> <% else %> <% value.each_with_index do |v, idx| %> <% index = idx %> - <%= render partial: 'extended_metadata/single_row', locals: { index: index, value: v, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> + <%= render partial: 'extended_metadata_types/single_row', locals: { index: index, value: v, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> <% end %> <% end %>
    @@ -25,7 +25,7 @@
    diff --git a/app/views/extended_metadata/_single_row.html.erb b/app/views/extended_metadata_types/_single_row.html.erb similarity index 100% rename from app/views/extended_metadata/_single_row.html.erb rename to app/views/extended_metadata_types/_single_row.html.erb diff --git a/app/views/investigations/_form.html.erb b/app/views/investigations/_form.html.erb index 6fa4080225..e6a3960e40 100644 --- a/app/views/investigations/_form.html.erb +++ b/app/views/investigations/_form.html.erb @@ -12,7 +12,7 @@ <%= f.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%>
    -<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:f, resource:@investigation} %> +<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:f, resource:@investigation} %>
    "> <%#= panel("Compliance with ISA-JSON schemas") do %> @@ -42,7 +42,7 @@ <%= f.number_field :position, :rows => 5, :class=>"form-control" -%>
    -<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:f,resource:@investigation} %> +<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:f,resource:@investigation} %> <% if displaying_single_page?%> /> diff --git a/app/views/investigations/show.html.erb b/app/views/investigations/show.html.erb index 2a20f868f8..d1836d9c94 100644 --- a/app/views/investigations/show.html.erb +++ b/app/views/investigations/show.html.erb @@ -24,7 +24,7 @@ <%= @investigation.is_isa_json_compliant? %>

    <% end %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: {resource: @investigation} %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: {resource: @investigation} %> <%= render partial: 'general/isa_graph', locals: { root_item: @investigation, options: { depth: 4 } } -%>
    diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 20fe51faaf..a2a7cbb151 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -56,8 +56,8 @@
    <% if show_extended_metadata %> - <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:assay_fields, resource:@isa_assay.assay} %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:assay_fields,resource:@isa_assay.assay, parent_resource: "isa_assay"} %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:assay_fields, resource:@isa_assay.assay} %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:assay_fields,resource:@isa_assay.assay, parent_resource: "isa_assay"} %> <% end %> - <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:study_fields, resource:@isa_study.study} %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:study_fields,resource:@isa_study.study, parent_resource: "isa_study"} %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:study_fields, resource:@isa_study.study} %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:study_fields,resource:@isa_study.study, parent_resource: "isa_study"} %> <% unless @isa_study.study.experimentalists.blank? %>
    diff --git a/app/views/models/edit.html.erb b/app/views/models/edit.html.erb index ed6c65e7a7..2a3c78ced3 100644 --- a/app/views/models/edit.html.erb +++ b/app/views/models/edit.html.erb @@ -16,8 +16,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: @model } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @model } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @model } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @model } %> <%= render :partial => 'assets/license_selector', :locals => { :resource => @model } %> diff --git a/app/views/models/new.html.erb b/app/views/models/new.html.erb index cb00d0080c..65ef2e59a8 100644 --- a/app/views/models/new.html.erb +++ b/app/views/models/new.html.erb @@ -33,8 +33,8 @@ <%= fields_for(@model) do |model_fields| %> - <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: model_fields, resource: @model } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: model_fields, resource: @model } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: model_fields, resource: @model } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: model_fields, resource: @model } %> <% end %> <%= render partial: "projects/project_selector", locals: { resource: @model } -%> diff --git a/app/views/models/show.html.erb b/app/views/models/show.html.erb index ee7a52a6bf..1668835233 100644 --- a/app/views/models/show.html.erb +++ b/app/views/models/show.html.erb @@ -80,7 +80,7 @@ <%= render :partial => "model_visualisation" -%> <%= render :partial => "import_details", :object => @display_model %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @model } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @model } %> <%= render :partial => "general/isa_graph", :locals => { :root_item => @model, :deep => true, :include_parents => true } %> diff --git a/app/views/presentations/edit.html.erb b/app/views/presentations/edit.html.erb index 4f02f995fa..3479ccd6b0 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 } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @presentation } %> + <%= render partial: 'extended_metadata_types/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 6dd32274fb..77bbeac75b 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_types/extended_metadata_type_selection', locals: { f: presentation_fields, resource: @presentation } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: presentation_fields, resource: @presentation } %> <% end %> <%= render :partial => "projects/project_selector", :locals => { :resource => @presentation } -%> diff --git a/app/views/presentations/show.html.erb b/app/views/presentations/show.html.erb index 3766710e66..f23577ce3d 100644 --- a/app/views/presentations/show.html.erb +++ b/app/views/presentations/show.html.erb @@ -22,7 +22,7 @@ <%= rendered_asset_view(@display_presentation) %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @presentation } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @presentation } %> <%= render :partial => "general/isa_graph", :locals => { :root_item => @presentation, :deep => true, :include_parents => true } %> diff --git a/app/views/projects/_form.html.erb b/app/views/projects/_form.html.erb index 0ac80b0376..2ac4615be7 100644 --- a/app/views/projects/_form.html.erb +++ b/app/views/projects/_form.html.erb @@ -16,8 +16,8 @@ <%= f.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%> -<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: {f: f, resource: @project } %> -<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: {f: f, resource: @project } %> +<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: {f: f, resource: @project } %> +<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: {f: f, resource: @project } %> <%= render partial: 'assets/controlled_vocab_annotations_form_properties', locals: { resource: @project } %> diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index 53591297cd..e09ae87270 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_types/extended_metadata_attribute_values', locals: { resource: @project } %>
    diff --git a/app/views/sops/edit.html.erb b/app/views/sops/edit.html.erb index b15e10bee0..58827940e7 100644 --- a/app/views/sops/edit.html.erb +++ b/app/views/sops/edit.html.erb @@ -15,8 +15,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: @sop } %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @sop } %> + <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @sop } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @sop } %> <%= render :partial => 'assets/license_selector', :locals => { :resource => @sop } %> diff --git a/app/views/sops/new.html.erb b/app/views/sops/new.html.erb index d8480b0bf3..181b526ad9 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_types/extended_metadata_type_selection', locals: { f: sop_fields, resource: @sop } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: sop_fields, resource: @sop } %> <% end %> <%= render :partial => "projects/project_selector", :locals => { :resource => @sop } %> diff --git a/app/views/sops/show.html.erb b/app/views/sops/show.html.erb index d9ccf7e55a..354c53138e 100644 --- a/app/views/sops/show.html.erb +++ b/app/views/sops/show.html.erb @@ -23,7 +23,7 @@ - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @sop } %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @sop } %> <%= render :partial=>"general/isa_graph", :locals => {:root_item => @sop, :deep => true, :include_parents => true} %> diff --git a/app/views/studies/_form.html.erb b/app/views/studies/_form.html.erb index d47ae809b9..cebdd071c4 100644 --- a/app/views/studies/_form.html.erb +++ b/app/views/studies/_form.html.erb @@ -10,7 +10,7 @@ <%= f.text_area :description, :rows=>5, :class=>"form-control rich-text-edit" %> -<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:f, resource:@study} %> +<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:f, resource:@study} %> <% unless @study.experimentalists.blank? %>
    @@ -31,7 +31,7 @@ <%= f.number_field :position, :rows => 5, :class=>"form-control" -%>
    -<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:f,resource:@study} %> +<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:f,resource:@study} %> <%= render partial: 'assets/manage_specific_attributes', locals:{f:f} if show_form_manage_specific_attributes? %> @@ -43,7 +43,7 @@ <%= render :partial=> "assets/discussion_links_form", :locals=>{:resource => @study} -%> -<%#= render partial: 'extended_metadata/extended_metadata_fields_form_input', locals:{f:f, resource:@study} %> +<%#= render partial: 'extended_metadata_types/extended_metadata_fields_form_input', locals:{f:f, resource:@study} %> <%= form_submit_buttons(@study) %> diff --git a/app/views/studies/show.html.erb b/app/views/studies/show.html.erb index e4d19b66c2..2ed82dfb9e 100644 --- a/app/views/studies/show.html.erb +++ b/app/views/studies/show.html.erb @@ -51,7 +51,7 @@ <% end %> - <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: {resource: @study} %> + <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: {resource: @study} %> <%= render partial: 'general/isa_graph', locals: { root_item: @study, options: { depth: 4 } } -%> From 7d7611b7c639a98bc6451df646ca742fdccb4966 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 12 Feb 2024 14:04:57 +0000 Subject: [PATCH 148/344] Revert "fix the extended metadata types view folder name #1649" This reverts commit 7f42a60187dc509371c17752b0afd0c7132a4da1. actually made more sense where they were, as they relate to the metadata rather than the type --- app/controllers/extended_metadata_types_controller.rb | 2 +- app/helpers/samples_helper.rb | 2 +- app/views/assays/_form.html.erb | 4 ++-- app/views/assays/show.html.erb | 2 +- app/views/collections/edit.html.erb | 4 ++-- app/views/collections/new.html.erb | 4 ++-- app/views/collections/show.html.erb | 2 +- app/views/data_files/edit.html.erb | 4 ++-- app/views/data_files/multi-steps/_basic_metadata.html.erb | 4 ++-- app/views/data_files/show.html.erb | 2 +- app/views/documents/edit.html.erb | 4 ++-- app/views/documents/new.html.erb | 4 ++-- app/views/documents/show.html.erb | 2 +- app/views/events/_form.html.erb | 4 ++-- app/views/events/show.html.erb | 2 +- .../_extended_metadata_attribute_input.html.erb | 2 +- .../_extended_metadata_attribute_values.html.erb | 0 .../_extended_metadata_fields.html.erb | 0 .../_extended_metadata_type_selection.html.erb | 0 ...linked_extended_metadata_multi_attribute_fields.html.erb | 6 +++--- .../_single_row.html.erb | 0 app/views/investigations/_form.html.erb | 4 ++-- app/views/investigations/show.html.erb | 2 +- app/views/isa_assays/_form.html.erb | 4 ++-- app/views/isa_studies/_form.html.erb | 4 ++-- app/views/models/edit.html.erb | 4 ++-- app/views/models/new.html.erb | 4 ++-- app/views/models/show.html.erb | 2 +- app/views/presentations/edit.html.erb | 4 ++-- app/views/presentations/new.html.erb | 4 ++-- app/views/presentations/show.html.erb | 2 +- app/views/projects/_form.html.erb | 4 ++-- app/views/projects/show.html.erb | 2 +- app/views/sops/edit.html.erb | 4 ++-- app/views/sops/new.html.erb | 4 ++-- app/views/sops/show.html.erb | 2 +- app/views/studies/_form.html.erb | 6 +++--- app/views/studies/show.html.erb | 2 +- 38 files changed, 56 insertions(+), 56 deletions(-) rename app/views/{extended_metadata_types => extended_metadata}/_extended_metadata_attribute_input.html.erb (91%) rename app/views/{extended_metadata_types => extended_metadata}/_extended_metadata_attribute_values.html.erb (100%) rename app/views/{extended_metadata_types => extended_metadata}/_extended_metadata_fields.html.erb (100%) rename app/views/{extended_metadata_types => extended_metadata}/_extended_metadata_type_selection.html.erb (100%) rename app/views/{extended_metadata_types => extended_metadata}/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb (79%) rename app/views/{extended_metadata_types => extended_metadata}/_single_row.html.erb (100%) diff --git a/app/controllers/extended_metadata_types_controller.rb b/app/controllers/extended_metadata_types_controller.rb index 4ae3b2098a..47b129942f 100644 --- a/app/controllers/extended_metadata_types_controller.rb +++ b/app/controllers/extended_metadata_types_controller.rb @@ -16,7 +16,7 @@ def form_fields resource = safe_class_lookup(cm.supported_type).new resource.extended_metadata = ExtendedMetadata.new(extended_metadata_type: cm) format.html do - render partial: 'extended_metadata_types/extended_metadata_fields', + render partial: 'extended_metadata/extended_metadata_fields', locals: { extended_metadata_type: cm, resource: resource, parent_resource: parent_resource} end end diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index 403e262134..bd4bfd36dd 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -42,7 +42,7 @@ def controlled_vocab_list_form_field(sample_controlled_vocab, element_name, valu end def linked_extended_metadata_multi_form_field(attribute, value, element_name, element_class) - render partial: 'extended_metadata_types/fancy_linked_extended_metadata_multi_attribute_fields', + render partial: 'extended_metadata/fancy_linked_extended_metadata_multi_attribute_fields', locals: { value: value, attribute: attribute, element_name: element_name, element_class: element_class, collapsed: false } end diff --git a/app/views/assays/_form.html.erb b/app/views/assays/_form.html.erb index 8038a52964..827f93a834 100644 --- a/app/views/assays/_form.html.erb +++ b/app/views/assays/_form.html.erb @@ -15,9 +15,9 @@ <%= f.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%> -<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:f, resource:@assay} %> +<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:f, resource:@assay} %> -<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:f,resource:@assay} %> +<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:f,resource:@assay} %>
    diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index dcdfb72798..9f85fc1b39 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -109,7 +109,7 @@
    <% end %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @assay } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @assay } %> <% if ((@assay.is_modelling?) && !@assay.models.empty? && !@assay.data_files.empty?) %><%#MODELLING ASSAY %>
    diff --git a/app/views/collections/edit.html.erb b/app/views/collections/edit.html.erb index e7ba5695fa..baa4e84c63 100644 --- a/app/views/collections/edit.html.erb +++ b/app/views/collections/edit.html.erb @@ -14,8 +14,8 @@ <%= f.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%>
    - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: {f: f, resource: @collection } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: {f: f, resource: @collection } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: {f: f, resource: @collection } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: {f: f, resource: @collection } %> <%= panel('Items') do %>

    Items can be re-arranged by clicking and dragging the button on the left-hand side of each row.

    diff --git a/app/views/collections/new.html.erb b/app/views/collections/new.html.erb index fa98eaf160..9b519b39d3 100644 --- a/app/views/collections/new.html.erb +++ b/app/views/collections/new.html.erb @@ -19,8 +19,8 @@ <%= fields_for(@collection) do |collection_fields| %> - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: collection_fields, resource: @collection } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: collection_fields, resource: @collection } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: collection_fields, resource: @collection } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: collection_fields, resource: @collection } %> <% end %> <%= render :partial => "projects/project_selector", :locals => { :resource => @collection } %> diff --git a/app/views/collections/show.html.erb b/app/views/collections/show.html.erb index 64fd08936b..faaf8c262f 100644 --- a/app/views/collections/show.html.erb +++ b/app/views/collections/show.html.erb @@ -19,7 +19,7 @@ - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @collection } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @collection } %>
    diff --git a/app/views/data_files/edit.html.erb b/app/views/data_files/edit.html.erb index 44cb455de9..9d147a8bea 100644 --- a/app/views/data_files/edit.html.erb +++ b/app/views/data_files/edit.html.erb @@ -15,8 +15,8 @@ <%= f.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%>
    - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @data_file } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %>
    Check this box if this data is the result of a model simulation
    diff --git a/app/views/data_files/multi-steps/_basic_metadata.html.erb b/app/views/data_files/multi-steps/_basic_metadata.html.erb index 081a2e1a99..c021e4f32f 100644 --- a/app/views/data_files/multi-steps/_basic_metadata.html.erb +++ b/app/views/data_files/multi-steps/_basic_metadata.html.erb @@ -14,8 +14,8 @@ <%= f.text_area :description, :class => 'form-control rich-text-edit', :rows => 5 -%>
    - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @data_file } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @data_file } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @data_file } %>
    Check this box if this data is the result of a model simulation
    diff --git a/app/views/data_files/show.html.erb b/app/views/data_files/show.html.erb index 1c1cfb5abf..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_types/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? %>
    diff --git a/app/views/documents/edit.html.erb b/app/views/documents/edit.html.erb index 0f3280bad2..9c72f276d6 100644 --- a/app/views/documents/edit.html.erb +++ b/app/views/documents/edit.html.erb @@ -15,8 +15,8 @@ <%= f.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%>
    - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @document } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @document } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @document } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @document } %> <%= render :partial => 'assets/license_selector', :locals => { :resource => @document } %> diff --git a/app/views/documents/new.html.erb b/app/views/documents/new.html.erb index 8750c1550e..3ff17da491 100644 --- a/app/views/documents/new.html.erb +++ b/app/views/documents/new.html.erb @@ -21,8 +21,8 @@ <%= fields_for(@document) do |document_fields| %> - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: document_fields, resource: @document } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: document_fields, resource: @document } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: document_fields, resource: @document } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: document_fields, resource: @document } %> <% end %> diff --git a/app/views/documents/show.html.erb b/app/views/documents/show.html.erb index 1fcb7f1663..a428dc2cfb 100644 --- a/app/views/documents/show.html.erb +++ b/app/views/documents/show.html.erb @@ -23,7 +23,7 @@ - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @document } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @document } %> <%= render :partial=>"general/isa_graph", :locals => {:root_item => @document, :deep => true, :include_parents => true} %> diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index 6f51797926..a386b4271f 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -62,8 +62,8 @@
    - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @event } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @event } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @event } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @event } %>
    <% if show_form_manage_specific_attributes? && @event.can_manage? %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 000cfef4e6..fcc858fc82 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -58,7 +58,7 @@ - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @event } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @event } %> <%= render :partial=>"general/isa_graph", :locals => {:root_item => @event, :deep => true, :include_parents => true} %> diff --git a/app/views/extended_metadata_types/_extended_metadata_attribute_input.html.erb b/app/views/extended_metadata/_extended_metadata_attribute_input.html.erb similarity index 91% rename from app/views/extended_metadata_types/_extended_metadata_attribute_input.html.erb rename to app/views/extended_metadata/_extended_metadata_attribute_input.html.erb index d7cf168d3b..32ee78fee6 100644 --- a/app/views/extended_metadata_types/_extended_metadata_attribute_input.html.erb +++ b/app/views/extended_metadata/_extended_metadata_attribute_input.html.erb @@ -7,7 +7,7 @@ <%= folding_panel(panel_label, false, style:panel_style,id:'extended_metadata_attribute_panel') do %>
    <% if resource.extended_metadata.try(:extended_metadata_type) %> - <%= render partial: 'extended_metadata_types/extended_metadata_fields', locals: { extended_metadata_type: resource.extended_metadata.extended_metadata_type, resource:, parent_resource: } %> + <%= render partial: 'extended_metadata/extended_metadata_fields', locals: { extended_metadata_type: resource.extended_metadata.extended_metadata_type, resource:, parent_resource: } %> <% end %>
    <% end %> diff --git a/app/views/extended_metadata_types/_extended_metadata_attribute_values.html.erb b/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb similarity index 100% rename from app/views/extended_metadata_types/_extended_metadata_attribute_values.html.erb rename to app/views/extended_metadata/_extended_metadata_attribute_values.html.erb diff --git a/app/views/extended_metadata_types/_extended_metadata_fields.html.erb b/app/views/extended_metadata/_extended_metadata_fields.html.erb similarity index 100% rename from app/views/extended_metadata_types/_extended_metadata_fields.html.erb rename to app/views/extended_metadata/_extended_metadata_fields.html.erb diff --git a/app/views/extended_metadata_types/_extended_metadata_type_selection.html.erb b/app/views/extended_metadata/_extended_metadata_type_selection.html.erb similarity index 100% rename from app/views/extended_metadata_types/_extended_metadata_type_selection.html.erb rename to app/views/extended_metadata/_extended_metadata_type_selection.html.erb diff --git a/app/views/extended_metadata_types/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb b/app/views/extended_metadata/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb similarity index 79% rename from app/views/extended_metadata_types/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb rename to app/views/extended_metadata/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb index 9b681ad78a..06e156178a 100644 --- a/app/views/extended_metadata_types/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb +++ b/app/views/extended_metadata/_fancy_linked_extended_metadata_multi_attribute_fields.html.erb @@ -10,11 +10,11 @@
    <% if value.blank? %> - <%= render partial: 'extended_metadata_types/single_row', locals: { index: index, value: nil, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> + <%= render partial: 'extended_metadata/single_row', locals: { index: index, value: nil, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> <% else %> <% value.each_with_index do |v, idx| %> <% index = idx %> - <%= render partial: 'extended_metadata_types/single_row', locals: { index: index, value: v, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> + <%= render partial: 'extended_metadata/single_row', locals: { index: index, value: v, attribute: attribute, element_name: element_name, element_class: element_class, prefix: prefix } %> <% end %> <% end %>
    @@ -25,7 +25,7 @@
    diff --git a/app/views/extended_metadata_types/_single_row.html.erb b/app/views/extended_metadata/_single_row.html.erb similarity index 100% rename from app/views/extended_metadata_types/_single_row.html.erb rename to app/views/extended_metadata/_single_row.html.erb diff --git a/app/views/investigations/_form.html.erb b/app/views/investigations/_form.html.erb index e6a3960e40..6fa4080225 100644 --- a/app/views/investigations/_form.html.erb +++ b/app/views/investigations/_form.html.erb @@ -12,7 +12,7 @@ <%= f.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%>
    -<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:f, resource:@investigation} %> +<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:f, resource:@investigation} %>
    "> <%#= panel("Compliance with ISA-JSON schemas") do %> @@ -42,7 +42,7 @@ <%= f.number_field :position, :rows => 5, :class=>"form-control" -%>
    -<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:f,resource:@investigation} %> +<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:f,resource:@investigation} %> <% if displaying_single_page?%> /> diff --git a/app/views/investigations/show.html.erb b/app/views/investigations/show.html.erb index d1836d9c94..2a20f868f8 100644 --- a/app/views/investigations/show.html.erb +++ b/app/views/investigations/show.html.erb @@ -24,7 +24,7 @@ <%= @investigation.is_isa_json_compliant? %>

    <% end %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: {resource: @investigation} %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: {resource: @investigation} %> <%= render partial: 'general/isa_graph', locals: { root_item: @investigation, options: { depth: 4 } } -%>
    diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index a2a7cbb151..20fe51faaf 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -56,8 +56,8 @@
    <% if show_extended_metadata %> - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:assay_fields, resource:@isa_assay.assay} %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:assay_fields,resource:@isa_assay.assay, parent_resource: "isa_assay"} %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:assay_fields, resource:@isa_assay.assay} %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:assay_fields,resource:@isa_assay.assay, parent_resource: "isa_assay"} %> <% end %> - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:study_fields, resource:@isa_study.study} %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:study_fields,resource:@isa_study.study, parent_resource: "isa_study"} %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:study_fields, resource:@isa_study.study} %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:study_fields,resource:@isa_study.study, parent_resource: "isa_study"} %> <% unless @isa_study.study.experimentalists.blank? %>
    diff --git a/app/views/models/edit.html.erb b/app/views/models/edit.html.erb index 2a3c78ced3..ed6c65e7a7 100644 --- a/app/views/models/edit.html.erb +++ b/app/views/models/edit.html.erb @@ -16,8 +16,8 @@ <%= f.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%>
    - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @model } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @model } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: f, resource: @model } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: f, resource: @model } %> <%= render :partial => 'assets/license_selector', :locals => { :resource => @model } %> diff --git a/app/views/models/new.html.erb b/app/views/models/new.html.erb index 65ef2e59a8..cb00d0080c 100644 --- a/app/views/models/new.html.erb +++ b/app/views/models/new.html.erb @@ -33,8 +33,8 @@ <%= fields_for(@model) do |model_fields| %> - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: model_fields, resource: @model } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: model_fields, resource: @model } %> + <%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: { f: model_fields, resource: @model } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: { f: model_fields, resource: @model } %> <% end %> <%= render partial: "projects/project_selector", locals: { resource: @model } -%> diff --git a/app/views/models/show.html.erb b/app/views/models/show.html.erb index 1668835233..ee7a52a6bf 100644 --- a/app/views/models/show.html.erb +++ b/app/views/models/show.html.erb @@ -80,7 +80,7 @@ <%= render :partial => "model_visualisation" -%> <%= render :partial => "import_details", :object => @display_model %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @model } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @model } %> <%= render :partial => "general/isa_graph", :locals => { :root_item => @model, :deep => true, :include_parents => true } %> diff --git a/app/views/presentations/edit.html.erb b/app/views/presentations/edit.html.erb index 3479ccd6b0..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_types/extended_metadata_type_selection', locals: { f: f, resource: @presentation } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @presentation } %> + <%= 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 77bbeac75b..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_types/extended_metadata_type_selection', locals: { f: presentation_fields, resource: @presentation } %> - <%= render partial: 'extended_metadata_types/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/presentations/show.html.erb b/app/views/presentations/show.html.erb index f23577ce3d..3766710e66 100644 --- a/app/views/presentations/show.html.erb +++ b/app/views/presentations/show.html.erb @@ -22,7 +22,7 @@ <%= rendered_asset_view(@display_presentation) %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @presentation } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @presentation } %> <%= render :partial => "general/isa_graph", :locals => { :root_item => @presentation, :deep => true, :include_parents => true } %> diff --git a/app/views/projects/_form.html.erb b/app/views/projects/_form.html.erb index 2ac4615be7..0ac80b0376 100644 --- a/app/views/projects/_form.html.erb +++ b/app/views/projects/_form.html.erb @@ -16,8 +16,8 @@ <%= f.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%> -<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: {f: f, resource: @project } %> -<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: {f: f, resource: @project } %> +<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals: {f: f, resource: @project } %> +<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals: {f: f, resource: @project } %> <%= render partial: 'assets/controlled_vocab_annotations_form_properties', locals: { resource: @project } %> diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index e09ae87270..53591297cd 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -99,7 +99,7 @@
    - <%= render partial: 'extended_metadata_types/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 58827940e7..b15e10bee0 100644 --- a/app/views/sops/edit.html.erb +++ b/app/views/sops/edit.html.erb @@ -15,8 +15,8 @@ <%= f.text_area :description, :class=>"form-control rich-text-edit", :rows => 5 -%> - <%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals: { f: f, resource: @sop } %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals: { f: f, resource: @sop } %> + <%= 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 } %> diff --git a/app/views/sops/new.html.erb b/app/views/sops/new.html.erb index 181b526ad9..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_types/extended_metadata_type_selection', locals: { f: sop_fields, resource: @sop } %> - <%= render partial: 'extended_metadata_types/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 } %> diff --git a/app/views/sops/show.html.erb b/app/views/sops/show.html.erb index 354c53138e..d9ccf7e55a 100644 --- a/app/views/sops/show.html.erb +++ b/app/views/sops/show.html.erb @@ -23,7 +23,7 @@ - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: { resource: @sop } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @sop } %> <%= render :partial=>"general/isa_graph", :locals => {:root_item => @sop, :deep => true, :include_parents => true} %> diff --git a/app/views/studies/_form.html.erb b/app/views/studies/_form.html.erb index cebdd071c4..d47ae809b9 100644 --- a/app/views/studies/_form.html.erb +++ b/app/views/studies/_form.html.erb @@ -10,7 +10,7 @@ <%= f.text_area :description, :rows=>5, :class=>"form-control rich-text-edit" %> -<%= render partial: 'extended_metadata_types/extended_metadata_type_selection', locals:{f:f, resource:@study} %> +<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:f, resource:@study} %> <% unless @study.experimentalists.blank? %>
    @@ -31,7 +31,7 @@ <%= f.number_field :position, :rows => 5, :class=>"form-control" -%>
    -<%= render partial: 'extended_metadata_types/extended_metadata_attribute_input', locals:{f:f,resource:@study} %> +<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:f,resource:@study} %> <%= render partial: 'assets/manage_specific_attributes', locals:{f:f} if show_form_manage_specific_attributes? %> @@ -43,7 +43,7 @@ <%= render :partial=> "assets/discussion_links_form", :locals=>{:resource => @study} -%> -<%#= render partial: 'extended_metadata_types/extended_metadata_fields_form_input', locals:{f:f, resource:@study} %> +<%#= render partial: 'extended_metadata/extended_metadata_fields_form_input', locals:{f:f, resource:@study} %> <%= form_submit_buttons(@study) %> diff --git a/app/views/studies/show.html.erb b/app/views/studies/show.html.erb index 2ed82dfb9e..e4d19b66c2 100644 --- a/app/views/studies/show.html.erb +++ b/app/views/studies/show.html.erb @@ -51,7 +51,7 @@ <% end %> - <%= render partial: 'extended_metadata_types/extended_metadata_attribute_values', locals: {resource: @study} %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: {resource: @study} %> <%= render partial: 'general/isa_graph', locals: { root_item: @study, options: { depth: 4 } } -%> From cfeaff77088f106b795aadb90dc1393d7b7a186f Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 12 Feb 2024 14:32:00 +0000 Subject: [PATCH 149/344] start basic table #1649 --- .../extended_metadata_types_controller.rb | 1 + app/models/extended_metadata_type.rb | 2 +- .../_extended_metadata_type_table_row.html.erb | 5 +++++ .../extended_metadata_types/administer.html.erb | 14 ++++++++++++++ .../extended_metadata_types_controller_test.rb | 14 ++++++++++++-- 5 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb diff --git a/app/controllers/extended_metadata_types_controller.rb b/app/controllers/extended_metadata_types_controller.rb index 47b129942f..5c544a5843 100644 --- a/app/controllers/extended_metadata_types_controller.rb +++ b/app/controllers/extended_metadata_types_controller.rb @@ -30,6 +30,7 @@ def show end def administer + @extended_metadata_types = ExtendedMetadataType.all respond_to do |format| format.html end diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb index 5b17a9abe4..c3f361a03d 100644 --- a/app/models/extended_metadata_type.rb +++ b/app/models/extended_metadata_type.rb @@ -1,6 +1,6 @@ class ExtendedMetadataType < ApplicationRecord has_many :extended_metadata_attributes, inverse_of: :extended_metadata_type, dependent: :destroy - + has_many :extended_metadatas, inverse_of: :extended_metadata_type validates :title, presence: true validates :extended_metadata_attributes, presence: true validates :supported_type, presence: true diff --git a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb new file mode 100644 index 0000000000..eaa37f4092 --- /dev/null +++ b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb @@ -0,0 +1,5 @@ + + <%= extended_metadata_type.title %> + <%= extended_metadata_type.supported_type %> + <%= extended_metadata_type.extended_metadatas.count %> + \ No newline at end of file diff --git a/app/views/extended_metadata_types/administer.html.erb b/app/views/extended_metadata_types/administer.html.erb index e69de29bb2..f59c747dac 100644 --- a/app/views/extended_metadata_types/administer.html.erb +++ b/app/views/extended_metadata_types/administer.html.erb @@ -0,0 +1,14 @@ + + + + + + + + <% @extended_metadata_types.each do |extended_metadata_type| %> + + <%= render partial: 'extended_metadata_type_table_row', locals: { extended_metadata_type: extended_metadata_type } %> + + <% end %> + +
    TitleSupported TypeUsage
    \ No newline at end of file diff --git a/test/functional/extended_metadata_types_controller_test.rb b/test/functional/extended_metadata_types_controller_test.rb index 20afb1dc1d..7ad91c096b 100644 --- a/test/functional/extended_metadata_types_controller_test.rb +++ b/test/functional/extended_metadata_types_controller_test.rb @@ -29,15 +29,25 @@ class ExtendedMetadataTypesControllerTest < ActionController::TestCase assert_select 'label', text:'Date', count:1 end - test 'can access administer as admin' do + test 'administer' do + emt = FactoryBot.create(:simple_investigation_extended_metadata_type) person = FactoryBot.create(:admin) login_as(person) get :administer assert_response :success refute flash[:error] + assert_select 'table tbody tr', count: 1 + assert_select 'table tbody tr td', text: emt.title + end + + test 'can access administer as admin' do + person = FactoryBot.create(:admin) + login_as(person) + get :administer + assert_response :success end - test 'can access administer as project admin' do + test 'cannot access administer as project admin' do person = FactoryBot.create(:project_administrator) login_as(person) get :administer From 08f358d1b7fcd5331022057019a94808184b41ba Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 12 Feb 2024 16:33:10 +0000 Subject: [PATCH 150/344] started adding ability to enable / disable EMT's #1649 --- .../extended_metadata_types_controller.rb | 17 ++++++++++++--- app/models/extended_metadata_type.rb | 4 ++++ ..._extended_metadata_type_table_row.html.erb | 13 ++++++++++++ .../administer.html.erb | 2 ++ config/routes.rb | 3 +++ ..._add_enabled_to_extended_metadata_types.rb | 5 +++++ db/schema.rb | 3 ++- ...extended_metadata_types_controller_test.rb | 16 ++++++++++++++ test/unit/extended_metadata_type_test.rb | 21 +++++++++++++++++++ 9 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20240212145449_add_enabled_to_extended_metadata_types.rb diff --git a/app/controllers/extended_metadata_types_controller.rb b/app/controllers/extended_metadata_types_controller.rb index 5c544a5843..76f44be7d9 100644 --- a/app/controllers/extended_metadata_types_controller.rb +++ b/app/controllers/extended_metadata_types_controller.rb @@ -1,7 +1,8 @@ class ExtendedMetadataTypesController < ApplicationController + before_action :is_user_admin_auth, except: [:form_fields, :show] - before_action :find_extended_metadata_type, only: [:show] + before_action :find_requested_item, only: [:administer_update, :show] # generated for form, to display fields for selected metadata type @@ -29,6 +30,16 @@ def show end end + def administer_update + @extended_metadata_type.update(extended_metadata_type_params) + unless @extended_metadata_type.save + flash[:error] = "Unable to save" + end + respond_to do |format| + format.html { redirect_to administer_extended_metadata_types_path } + end + end + def administer @extended_metadata_types = ExtendedMetadataType.all respond_to do |format| @@ -38,8 +49,8 @@ def administer private - def find_extended_metadata_type - @extended_metadata_type = ExtendedMetadataType.find(params[:id]) + def extended_metadata_type_params + params.require(:extended_metadata_type).permit(:title, :enabled) end end diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb index c3f361a03d..824fb3c82c 100644 --- a/app/models/extended_metadata_type.rb +++ b/app/models/extended_metadata_type.rb @@ -34,4 +34,8 @@ def unique_titles_for_extended_metadata_attributes errors.add(:extended_metadata_attributes, 'must have unique titles') end end + + def extended_type? + supported_type == 'ExtendedMetadata' + end end diff --git a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb index eaa37f4092..ac1fdb98fb 100644 --- a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb +++ b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb @@ -1,5 +1,18 @@ + <%= extended_metadata_type.id %> <%= extended_metadata_type.title %> <%= extended_metadata_type.supported_type %> <%= extended_metadata_type.extended_metadatas.count %> + + <% unless extended_metadata_type.extended_type? %> + <%= form_for(extended_metadata_type, url: { action: 'administer_update' }, method: :put) do |f| %> + <%= f.hidden_field :enabled, value: !extended_metadata_type.enabled? %> + <% if extended_metadata_type.enabled? %> + <%= f.submit 'Disable' %> + <% else %> + <%= f.submit 'Enable' %> + <% end %> + <% end %> + <% end %> + \ No newline at end of file diff --git a/app/views/extended_metadata_types/administer.html.erb b/app/views/extended_metadata_types/administer.html.erb index f59c747dac..9761933949 100644 --- a/app/views/extended_metadata_types/administer.html.erb +++ b/app/views/extended_metadata_types/administer.html.erb @@ -1,8 +1,10 @@ + + <% @extended_metadata_types.each do |extended_metadata_type| %> diff --git a/config/routes.rb b/config/routes.rb index f52d5f157b..d0a3054d4c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -208,6 +208,9 @@ get :form_fields get :administer end + member do + put :administer_update + end end resource :favourites do diff --git a/db/migrate/20240212145449_add_enabled_to_extended_metadata_types.rb b/db/migrate/20240212145449_add_enabled_to_extended_metadata_types.rb new file mode 100644 index 0000000000..a17d0ff1aa --- /dev/null +++ b/db/migrate/20240212145449_add_enabled_to_extended_metadata_types.rb @@ -0,0 +1,5 @@ +class AddEnabledToExtendedMetadataTypes < ActiveRecord::Migration[6.1] + def change + add_column :extended_metadata_types, :enabled, :boolean, default: true + end +end diff --git a/db/schema.rb b/db/schema.rb index c9d24b5489..cd975fa81a 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: 2024_02_06_132054) do +ActiveRecord::Schema.define(version: 2024_02_12_145449) do create_table "activity_logs", id: :integer, force: :cascade do |t| t.string "action" @@ -646,6 +646,7 @@ t.string "title" t.integer "contributor_id" t.text "supported_type" + t.boolean "enabled", default: true end create_table "external_assets", id: :integer, force: :cascade do |t| diff --git a/test/functional/extended_metadata_types_controller_test.rb b/test/functional/extended_metadata_types_controller_test.rb index 7ad91c096b..b15f616cab 100644 --- a/test/functional/extended_metadata_types_controller_test.rb +++ b/test/functional/extended_metadata_types_controller_test.rb @@ -18,6 +18,22 @@ class ExtendedMetadataTypesControllerTest < ActionController::TestCase end + test 'administer update enabled' do + emt = FactoryBot.create(:simple_investigation_extended_metadata_type) + person = FactoryBot.create(:admin) + login_as(person) + + assert emt.enabled? + + put :administer_update, params:{id: emt.id, extended_metadata_type: {enabled: false}} + assert_redirected_to administer_extended_metadata_types_path + refute emt.reload.enabled? + + put :administer_update, params:{id: emt.id, extended_metadata_type: {enabled: true}} + assert_redirected_to administer_extended_metadata_types_path + assert emt.reload.enabled? + end + test 'show help text' do cmt = FactoryBot.create(:simple_investigation_extended_metadata_type_with_description_and_label) login_as(FactoryBot.create(:person)) diff --git a/test/unit/extended_metadata_type_test.rb b/test/unit/extended_metadata_type_test.rb index 318eca180b..fd37846df1 100644 --- a/test/unit/extended_metadata_type_test.rb +++ b/test/unit/extended_metadata_type_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class ExtendedMetadataTypeTest < ActiveSupport::TestCase + test 'validation' do cmt = ExtendedMetadataType.new(title: 'test metadata', supported_type: 'Investigation') refute cmt.valid? @@ -24,6 +25,26 @@ class ExtendedMetadataTypeTest < ActiveSupport::TestCase cmt.supported_type = 'Study' assert cmt.valid? + + cmt.supported_type = 'ExtendedMetadata' + assert cmt.valid? + + cmt.supported_type = 'Study' + cmt.enabled = false + assert cmt.valid? + + # extended metadata, to be used as nested, cannot be disabled + cmt.supported_type = 'ExtendedMetadata' + refute cmt.valid? + end + + test 'extended type?' do + emt = FactoryBot.create(:simple_investigation_extended_metadata_type) + refute emt.extended_type? + emt.supported_type = 'Study' + refute emt.extended_type? + emt.supported_type = 'ExtendedMetadata' + assert emt.extended_type? end test 'validates attribute titles are unique' do From 4fcc5e326d8cea28ca016890479668e5caf0a2fd Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 13 Feb 2024 10:57:49 +0000 Subject: [PATCH 151/344] fixed tests #1649 --- app/models/extended_metadata_type.rb | 15 +++++++++++++-- .../_extended_metadata_type_table_row.html.erb | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb index 824fb3c82c..11e9951263 100644 --- a/app/models/extended_metadata_type.rb +++ b/app/models/extended_metadata_type.rb @@ -6,6 +6,7 @@ class ExtendedMetadataType < ApplicationRecord validates :supported_type, presence: true validate :supported_type_must_be_valid_type validate :unique_titles_for_extended_metadata_attributes + validate :cannot_disable_nested_extended_metadata alias_method :metadata_attributes, :extended_metadata_attributes @@ -21,6 +22,10 @@ def attributes_with_linked_extended_metadata_type extended_metadata_attributes.reject {|attr| attr.linked_extended_metadata_type.nil?} end + def extended_type? + supported_type == 'ExtendedMetadata' + end + def supported_type_must_be_valid_type return if supported_type.blank? # already convered by presence validation unless Seek::Util.lookup_class(supported_type, raise: false) @@ -35,7 +40,13 @@ def unique_titles_for_extended_metadata_attributes end end - def extended_type? - supported_type == 'ExtendedMetadata' + def cannot_disable_nested_extended_metadata + if !enabled && extended_type? + errors.add(:enabled, 'cannot be set to false if it is an extended_type used for nested types') + end end + + + + end diff --git a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb index ac1fdb98fb..b7c34a6180 100644 --- a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb +++ b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb @@ -5,7 +5,7 @@ +
    Internal Id Title Supported Type Usage
    <%= extended_metadata_type.extended_metadatas.count %> <% unless extended_metadata_type.extended_type? %> - <%= form_for(extended_metadata_type, url: { action: 'administer_update' }, method: :put) do |f| %> + <%= form_for(extended_metadata_type, url: administer_update_extended_metadata_type_path(extended_metadata_type), method: :put) do |f| %> <%= f.hidden_field :enabled, value: !extended_metadata_type.enabled? %> <% if extended_metadata_type.enabled? %> <%= f.submit 'Disable' %> From 44580dbb1b8d01daf9bdda6749a36ee7759c1e24 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 13 Feb 2024 13:39:07 +0000 Subject: [PATCH 152/344] split table by top level and nested emt #1649 --- .../extended_metadata_types_controller.rb | 2 +- .../_extended_metadata_type_table_row.html.erb | 5 +++-- .../extended_metadata_types/administer.html.erb | 17 +++++++++++++++-- .../extended_metadata_types_controller_test.rb | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/controllers/extended_metadata_types_controller.rb b/app/controllers/extended_metadata_types_controller.rb index 76f44be7d9..e775bd424c 100644 --- a/app/controllers/extended_metadata_types_controller.rb +++ b/app/controllers/extended_metadata_types_controller.rb @@ -41,7 +41,7 @@ def administer_update end def administer - @extended_metadata_types = ExtendedMetadataType.all + @extended_metadata_types = ExtendedMetadataType.order(:supported_type) respond_to do |format| format.html end diff --git a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb index b7c34a6180..a211b29978 100644 --- a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb +++ b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb @@ -3,14 +3,15 @@ <%= extended_metadata_type.title %> <%= extended_metadata_type.supported_type %> <%= extended_metadata_type.extended_metadatas.count %><%= extended_metadata_type.enabled? ? 'Enabled' : 'Disabled' %> <% unless extended_metadata_type.extended_type? %> <%= form_for(extended_metadata_type, url: administer_update_extended_metadata_type_path(extended_metadata_type), method: :put) do |f| %> <%= f.hidden_field :enabled, value: !extended_metadata_type.enabled? %> <% if extended_metadata_type.enabled? %> - <%= f.submit 'Disable' %> + <%= f.submit 'Disable', class:'btn' %> <% else %> - <%= f.submit 'Enable' %> + <%= f.submit 'Enable', class:'btn' %> <% end %> <% end %> <% end %> diff --git a/app/views/extended_metadata_types/administer.html.erb b/app/views/extended_metadata_types/administer.html.erb index 9761933949..6ecbfe9d57 100644 --- a/app/views/extended_metadata_types/administer.html.erb +++ b/app/views/extended_metadata_types/administer.html.erb @@ -1,16 +1,29 @@ +<% nested, top_level = @extended_metadata_types.partition(&:extended_type?) %> + + + - <% @extended_metadata_types.each do |extended_metadata_type| %> + + <% top_level.each do |extended_metadata_type| %> <%= render partial: 'extended_metadata_type_table_row', locals: { extended_metadata_type: extended_metadata_type } %> + <% end %> + + <% nested.each do |extended_metadata_type| %> + <%= render partial: 'extended_metadata_type_table_row', locals: { extended_metadata_type: extended_metadata_type } %> <% end %> + -
    Internal Id Title Supported Type UsageStatus

    Top Level

    Nested

    \ No newline at end of file + +
    + + diff --git a/test/functional/extended_metadata_types_controller_test.rb b/test/functional/extended_metadata_types_controller_test.rb index b15f616cab..5c51c2a213 100644 --- a/test/functional/extended_metadata_types_controller_test.rb +++ b/test/functional/extended_metadata_types_controller_test.rb @@ -52,8 +52,8 @@ class ExtendedMetadataTypesControllerTest < ActionController::TestCase get :administer assert_response :success refute flash[:error] - assert_select 'table tbody tr', count: 1 - assert_select 'table tbody tr td', text: emt.title + assert_select 'table tbody tr:not(.emt-partition-title)', count: 1 + assert_select 'table tbody tr:not(.emt-partition-title) td', text: emt.title end test 'can access administer as admin' do From 8d3546464a12792e48928cfd2c738e107fa2aa53 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 13 Feb 2024 14:20:55 +0000 Subject: [PATCH 153/344] dont show disabled emt's as an option to use #1649 --- app/helpers/extended_metadata_helper.rb | 24 +++++++++---------- app/models/extended_metadata_type.rb | 2 ++ ..._extended_metadata_type_selection.html.erb | 4 ++-- .../investigations_controller_test.rb | 17 +++++++++++++ test/unit/extended_metadata_type_test.rb | 8 +++++++ 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/app/helpers/extended_metadata_helper.rb b/app/helpers/extended_metadata_helper.rb index 46738abb76..987687040c 100644 --- a/app/helpers/extended_metadata_helper.rb +++ b/app/helpers/extended_metadata_helper.rb @@ -29,22 +29,22 @@ def extended_metadata_attribute_description(description) html.html_safe end - def render_extended_metadata_value(attribute, resource) + def render_extended_metadata_value(attribute, resource) - if resource.extended_metadata.data[attribute.title].blank? - return '' # Return an empty string if the extended metadata is blank. - end + if resource.extended_metadata.data[attribute.title].blank? + return '' # Return an empty string if the extended metadata is blank. + end - 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, false, id: attribute.title) do - display_attribute(resource.extended_metadata, attribute, link: true) - end + 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, false, id: attribute.title) do + display_attribute(resource.extended_metadata, attribute, link: true) end - else - label_tag("#{attribute.label} : ") + " " + display_attribute(resource.extended_metadata, attribute, link: true) end + else + label_tag("#{attribute.label} : ") + " " + display_attribute(resource.extended_metadata, attribute, link: true) end end + end end diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb index 11e9951263..2413ee8dad 100644 --- a/app/models/extended_metadata_type.rb +++ b/app/models/extended_metadata_type.rb @@ -10,6 +10,8 @@ class ExtendedMetadataType < ApplicationRecord alias_method :metadata_attributes, :extended_metadata_attributes + scope :enabled, ->{ where(enabled: true) } + def attribute_by_title(title) extended_metadata_attributes.where(title: title).first end diff --git a/app/views/extended_metadata/_extended_metadata_type_selection.html.erb b/app/views/extended_metadata/_extended_metadata_type_selection.html.erb index 2b97ea9c44..732acebde4 100644 --- a/app/views/extended_metadata/_extended_metadata_type_selection.html.erb +++ b/app/views/extended_metadata/_extended_metadata_type_selection.html.erb @@ -1,4 +1,4 @@ -<% return if ExtendedMetadataType.where(supported_type:resource.class.name).empty? %> +<% return if ExtendedMetadataType.enabled.where(supported_type:resource.class.name).empty? %> <% label ||= 'Extended Metadata' @@ -7,7 +7,7 @@
    <%= f.fields_for(:extended_metadata,resource.extended_metadata || ExtendedMetadata.new) do |ff| %> <%= f.label label %> - <%= ff.collection_select(:extended_metadata_type_id,ExtendedMetadataType.where(supported_type:resource.class.name), + <%= ff.collection_select(:extended_metadata_type_id,ExtendedMetadataType.enabled.where(supported_type:resource.class.name), :id, :title, {prompt:'Select'}, {class:"form-control", id:'extended_metadata_attributes_extended_metadata_type_id'}) %> diff --git a/test/functional/investigations_controller_test.rb b/test/functional/investigations_controller_test.rb index b38571a183..ad9e560365 100644 --- a/test/functional/investigations_controller_test.rb +++ b/test/functional/investigations_controller_test.rb @@ -657,6 +657,23 @@ def test_title end + test 'show enabled extended metadata types only' do + emt = FactoryBot.create(:simple_investigation_extended_metadata_type) + emt2 = FactoryBot.create(:simple_investigation_extended_metadata_type, enabled: false) + login_as(FactoryBot.create(:person)) + get :new + assert_response :success + assert_select 'select#extended_metadata_attributes_extended_metadata_type_id' do + assert_select 'option[value=?]',emt.id, text:emt.title, count: 1 + assert_select 'option[value=?]',emt2.id, text:emt2.title, count: 0 + end + emt.update_column(:enabled, false) + get :new + assert_response :success + assert_select 'select#extended_metadata_attributes_extended_metadata_type_id', count: 0 + + end + test 'should create with discussion link' do person = FactoryBot.create(:person) login_as(person) diff --git a/test/unit/extended_metadata_type_test.rb b/test/unit/extended_metadata_type_test.rb index fd37846df1..4409bcbeca 100644 --- a/test/unit/extended_metadata_type_test.rb +++ b/test/unit/extended_metadata_type_test.rb @@ -90,4 +90,12 @@ class ExtendedMetadataTypeTest < ActiveSupport::TestCase assert cmt.destroyed? assert_equal attributes, attributes.select(&:destroyed?) end + + test 'enabled' do + cmt = FactoryBot.create(:simple_investigation_extended_metadata_type) + cmt2 = FactoryBot.create(:simple_investigation_extended_metadata_type, enabled: false) + + assert_equal [cmt,cmt2], ExtendedMetadataType.all.order(:id) + assert_equal [cmt], ExtendedMetadataType.enabled.order(:id) + end end From f2296db6d11df0d09be118537934306a50be1f59 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 13 Feb 2024 14:47:09 +0000 Subject: [PATCH 154/344] dont show extended metadata if it's type is disabled #1649 --- app/models/extended_metadata.rb | 2 +- ...xtended_metadata_attribute_values.html.erb | 2 +- test/functional/studies_controller_test.rb | 23 +++++++++++++++++-- test/unit/extended_metadata_test.rb | 6 +++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/models/extended_metadata.rb b/app/models/extended_metadata.rb index 873b2a3a2e..05ffa2c5e5 100644 --- a/app/models/extended_metadata.rb +++ b/app/models/extended_metadata.rb @@ -7,7 +7,7 @@ class ExtendedMetadata < ApplicationRecord validates_with ExtendedMetadataValidator - delegate :extended_metadata_attributes, to: :extended_metadata_type + delegate :extended_metadata_attributes, :enabled?, to: :extended_metadata_type # for polymorphic behaviour with sample alias_method :metadata_type, :extended_metadata_type 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 1cf70e55eb..060d8e6172 100644 --- a/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb +++ b/app/views/extended_metadata/_extended_metadata_attribute_values.html.erb @@ -1,7 +1,7 @@ <% label = 'Extended Metadata' %> -<% if resource.respond_to?(:extended_metadata) && resource.extended_metadata %> +<% if resource.respond_to?(:extended_metadata) && resource.extended_metadata&.enabled? %> <% metadata_type = resource.extended_metadata.extended_metadata_type %>
    diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb index 6ea7c70d67..3464ea0b54 100644 --- a/test/functional/studies_controller_test.rb +++ b/test/functional/studies_controller_test.rb @@ -763,6 +763,25 @@ def test_should_show_investigation_tab refute study.valid? end + test 'should not show extended metadata if disabled' do + person = FactoryBot.create(:person) + login_as(person) + emt = FactoryBot.create(:simple_study_extended_metadata_type) + study = FactoryBot.create(:study, extended_metadata: ExtendedMetadata.new(extended_metadata_type: emt, data: { name: 'Fred', age: 25 }), contributor: person) + + #first check it's shown + get :show, params: {id: study} + assert_response :success + assert_select 'div#extended-metadata.panel', count: 1 + + emt.update_column(:enabled, false) + get :show, params: {id: study} + assert_response :success + assert_select 'div#extended-metadata.panel', count: 0 + + + end + test 'create a study with extended metadata with spaces in attribute names' do cmt = FactoryBot.create(:study_extended_metadata_type_with_spaces) @@ -1980,7 +1999,7 @@ def test_should_show_investigation_tab study_source_sample_type = FactoryBot.create :isa_source_sample_type, contributor: person study_sample_sample_type = FactoryBot.create :isa_sample_collection_sample_type, linked_sample_type: study_source_sample_type, contributor: person study = FactoryBot.create(:study, - investigation: , + investigation: investigation , policy:FactoryBot.create(:private_policy, permissions:[FactoryBot.create(:permission,contributor: person, access_type:Policy::EDITING)]), sample_types: [study_source_sample_type, study_sample_sample_type], contributor: person) @@ -2022,7 +2041,7 @@ def test_should_show_investigation_tab person = FactoryBot.create(:person) login_as(person) investigation = FactoryBot.create(:investigation, contributor: person, is_isa_json_compliant: true) - study = FactoryBot.create(:isa_json_compliant_study, contributor: person, investigation: ) + study = FactoryBot.create(:isa_json_compliant_study, contributor: person, investigation: investigation) get :show, params: { id: study } assert_response :success diff --git a/test/unit/extended_metadata_test.rb b/test/unit/extended_metadata_test.rb index 7ced1d0f71..58acff2fe4 100644 --- a/test/unit/extended_metadata_test.rb +++ b/test/unit/extended_metadata_test.rb @@ -275,6 +275,12 @@ class ExtendedMetadataTest < ActiveSupport::TestCase end + test 'enabled' do + em = simple_test_object + assert em.enabled? + em.extended_metadata_type.enabled = false + refute em.enabled? + end private From 14afac16d7bfb9c78a3a74611aa9b755e2561b40 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 13 Feb 2024 15:53:21 +0000 Subject: [PATCH 155/344] add Manage extended metadata types to admin page #1649 --- app/views/admin/_general.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/admin/_general.html.erb b/app/views/admin/_general.html.erb index 41572bbeb7..ebf6c379ea 100644 --- a/app/views/admin/_general.html.erb +++ b/app/views/admin/_general.html.erb @@ -33,6 +33,7 @@
  • <%= link_to 'Manage assay types', suggested_assay_types_path %>
  • <%= link_to 'Manage technology types', suggested_technology_types_path %>
  • <%= link_to 'Manage tags', tags_admin_path %>
  • +
  • <%= link_to 'Manage extended metadata types', administer_extended_metadata_types_path %>
  • <%= link_to "Manage #{WorkflowClass.model_name.human.pluralize}", workflow_classes_path %>
    • From 6609ac4b3ccfbec9c74dda32ae1301da55b601c0 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 13 Feb 2024 16:44:22 +0000 Subject: [PATCH 156/344] cannot create new with disabled extended metadata types, but can update existing ones #1649 if a metadata type is disabled after the fact, then it should be possible to update items that use them, but creating new items is not allowed --- app/validators/extended_metadata_validator.rb | 4 ++ test/functional/studies_controller_test.rb | 51 ++++++++++++++++++- test/unit/extended_metadata_test.rb | 31 +++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/app/validators/extended_metadata_validator.rb b/app/validators/extended_metadata_validator.rb index ba110e9d61..3df78be1b8 100644 --- a/app/validators/extended_metadata_validator.rb +++ b/app/validators/extended_metadata_validator.rb @@ -4,6 +4,10 @@ def validate(record) val = record.get_attribute_value(attribute) validate_attribute(record, attribute, val) end + + if record.new_record? && !record.enabled? + record.errors.add(:extended_metadata_type, "is not enabled, which is invalid for a new record") + end end private diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb index 3464ea0b54..f8d9b32127 100644 --- a/test/functional/studies_controller_test.rb +++ b/test/functional/studies_controller_test.rb @@ -733,6 +733,55 @@ def test_should_show_investigation_tab assert_equal old_id, new_study.extended_metadata.id end + test 'create a study with disabled extended metadata should fail' do + cmt = FactoryBot.create(:simple_study_extended_metadata_type, enabled: false) + + login_as(FactoryBot.create(:person)) + + assert_no_difference('Study.count') do + assert_no_difference('ExtendedMetadata.count') do + investigation = FactoryBot.create(:investigation,projects:User.current_user.person.projects,contributor:User.current_user.person) + study_attributes = { title: 'test', investigation_id: investigation.id } + cm_attributes = {extended_metadata_attributes:{ extended_metadata_type_id: cmt.id, + data:{ + "name":'fred', + "age":22}}} + + post :create, params: { study: study_attributes.merge(cm_attributes), sharing: valid_sharing } + end + end + assert study=assigns(:study) + refute study.valid? + end + + test 'update a study with disabled extended metadata should succeed' do + person = FactoryBot.create(:person) + login_as(person) + emt = FactoryBot.create(:simple_study_extended_metadata_type) + study = FactoryBot.create(:study, extended_metadata: ExtendedMetadata.new(extended_metadata_type: emt, data: { name: 'John', age: 12 }), contributor: person) + study.save! + study.extended_metadata.extended_metadata_type.update_column(:enabled, false) + refute study.extended_metadata.enabled? + + assert_no_difference('Study.count') do + assert_no_difference('ExtendedMetadata.count') do + put :update, params: { id: study.id, study: { title: "new title", + extended_metadata_attributes: { extended_metadata_type_id: emt.id, + id: study.extended_metadata.id, + data: { + "name": 'max', + "age": 20 } } + } + } + end + end + assert study = assigns(:study) + assert study.valid? + assert_equal 'new title', study.title + assert_equal 'max', study.extended_metadata.get_attribute_value('name') + assert_equal 20, study.extended_metadata.get_attribute_value('age') + end + test 'create a study with extended metadata validated' do cmt = FactoryBot.create(:simple_study_extended_metadata_type) @@ -744,7 +793,7 @@ def test_should_show_investigation_tab study_attributes = { title: 'test', investigation_id: investigation.id } cm_attributes = {extended_metadata_attributes:{extended_metadata_type_id: cmt.id, data:{'name':'fred','age':'not a number'}}} - put :create, params: { study: study_attributes.merge(cm_attributes), sharing: valid_sharing } + post :create, params: { study: study_attributes.merge(cm_attributes), sharing: valid_sharing } end assert study=assigns(:study) diff --git a/test/unit/extended_metadata_test.rb b/test/unit/extended_metadata_test.rb index 58acff2fe4..51b5c49432 100644 --- a/test/unit/extended_metadata_test.rb +++ b/test/unit/extended_metadata_test.rb @@ -282,6 +282,37 @@ class ExtendedMetadataTest < ActiveSupport::TestCase refute em.enabled? end + test 'validate that type is enabled for new records only' do + em = simple_test_object + em.set_attribute_value('name', 'bob') + assert em.new_record? + assert em.valid? + em.extended_metadata_type.enabled = false + refute em.valid? + + em.extended_metadata_type.enabled = true + em.save! + em.extended_metadata_type.enabled = false + assert em.valid? + end + + test 'validate item - not valid if em disabled if new record but ok if existing' do + emt = FactoryBot.create(:simple_study_extended_metadata_type) + study = FactoryBot.build(:study, extended_metadata: ExtendedMetadata.new(extended_metadata_type: emt, data: { name: 'Fred', age: 25 }), contributor: FactoryBot.create(:person)) + assert study.new_record? + assert study.extended_metadata.new_record? + assert study.valid? + study.extended_metadata.extended_metadata_type.enabled = false + refute study.valid? + + study.extended_metadata.extended_metadata_type.enabled = true + disable_authorization_checks do + study.save! + end + study.extended_metadata.extended_metadata_type.enabled = false + assert study.valid? + end + private def simple_test_object From 7f2a370ed2fe7be011ab0eea99bb317e1c51bc89 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 14 Feb 2024 10:25:14 +0000 Subject: [PATCH 157/344] handle editing an item with disabled extended metadata #1649 if disabled, you still have the ability to edit, and it appears in the list but marked as DISABLED --- .../_extended_metadata_type_selection.html.erb | 16 +++++++++++++--- .../functional/investigations_controller_test.rb | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/views/extended_metadata/_extended_metadata_type_selection.html.erb b/app/views/extended_metadata/_extended_metadata_type_selection.html.erb index 732acebde4..4f184f30e3 100644 --- a/app/views/extended_metadata/_extended_metadata_type_selection.html.erb +++ b/app/views/extended_metadata/_extended_metadata_type_selection.html.erb @@ -1,4 +1,15 @@ -<% return if ExtendedMetadataType.enabled.where(supported_type:resource.class.name).empty? %> +<% + options = ExtendedMetadataType.enabled.where(supported_type:resource.class.name).to_a + if resource.extended_metadata && !resource.extended_metadata.enabled? + options << resource.extended_metadata.extended_metadata_type + end + + return if options.empty? + options = options.collect do |extended_metadata_type| + title = extended_metadata_type.enabled? ? extended_metadata_type.title : "#{extended_metadata_type.title} (DISABLED)" + [title, extended_metadata_type.id] + end +%> <% label ||= 'Extended Metadata' @@ -7,8 +18,7 @@
      <%= f.fields_for(:extended_metadata,resource.extended_metadata || ExtendedMetadata.new) do |ff| %> <%= f.label label %> - <%= ff.collection_select(:extended_metadata_type_id,ExtendedMetadataType.enabled.where(supported_type:resource.class.name), - :id, :title, + <%= ff.select(:extended_metadata_type_id, options, {prompt:'Select'}, {class:"form-control", id:'extended_metadata_attributes_extended_metadata_type_id'}) %> <% end %> diff --git a/test/functional/investigations_controller_test.rb b/test/functional/investigations_controller_test.rb index ad9e560365..d0a7ecdee0 100644 --- a/test/functional/investigations_controller_test.rb +++ b/test/functional/investigations_controller_test.rb @@ -674,6 +674,22 @@ def test_title end + test 'editing an investigation with disabled extended metadata should show the option' do + person = FactoryBot.create(:person) + login_as(person) + emt = FactoryBot.create(:simple_study_extended_metadata_type) + investigation = FactoryBot.create(:investigation, extended_metadata: ExtendedMetadata.new(extended_metadata_type: emt, data: { name: 'John', age: 12 }), contributor: person) + investigation.save! + investigation.extended_metadata.extended_metadata_type.update_column(:enabled, false) + refute investigation.extended_metadata.enabled? + + get :edit, params: { id: investigation } + assert_response :success + assert_select 'select#extended_metadata_attributes_extended_metadata_type_id' do + assert_select 'option[value=?]',emt.id, text: "#{emt.title} (DISABLED)", count: 1 + end + end + test 'should create with discussion link' do person = FactoryBot.create(:person) login_as(person) From 5316c5409eda030d5d9d00ad3b801fba0044c552 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 14 Feb 2024 14:28:07 +0000 Subject: [PATCH 158/344] some text, and a warning when disabling a used type is disabled #1649 --- app/models/extended_metadata_type.rb | 13 +++++++++++ ..._extended_metadata_type_table_row.html.erb | 2 +- .../administer.html.erb | 23 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb index 2413ee8dad..8ea439518e 100644 --- a/app/models/extended_metadata_type.rb +++ b/app/models/extended_metadata_type.rb @@ -11,6 +11,7 @@ class ExtendedMetadataType < ApplicationRecord alias_method :metadata_attributes, :extended_metadata_attributes scope :enabled, ->{ where(enabled: true) } + scope :disabled, ->{ where(enabled: false) } def attribute_by_title(title) extended_metadata_attributes.where(title: title).first @@ -48,6 +49,18 @@ def cannot_disable_nested_extended_metadata end end + def usage + extended_metadatas.count + end + + def disabled_but_used? + !enabled && usage > 0 + end + + def self.disabled_but_in_use + disabled.select{|emt| emt.disabled_but_used?} + end + diff --git a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb index a211b29978..1509f32764 100644 --- a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb +++ b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb @@ -1,4 +1,4 @@ - + <%= extended_metadata_type.id %> <%= extended_metadata_type.title %> <%= extended_metadata_type.supported_type %> diff --git a/app/views/extended_metadata_types/administer.html.erb b/app/views/extended_metadata_types/administer.html.erb index 6ecbfe9d57..00f282bd8a 100644 --- a/app/views/extended_metadata_types/administer.html.erb +++ b/app/views/extended_metadata_types/administer.html.erb @@ -1,5 +1,28 @@ <% nested, top_level = @extended_metadata_types.partition(&:extended_type?) %> +
      +

      + You can disable Extended metadata types to prevent them from appearing as an option in the UI. + Disabling them won't delete them, or where they've been used, and they can be re-enabled again here. +

      + +

      + If a type is disabled but has been used, for those cases it will no longer be shown when viewing those items. + However, it will still be possible to edit the metadata for existing items and the metadata won't be deleted. +

      +
      + +<% if ExtendedMetadataType.disabled_but_in_use.any? %> +
      +

      + Extended metadata types that are in use have been disabled, and are high lighted below. +

      +

      + The existing metadata won't be lost but will be hidden and the metadata type unavailable for new items. +

      +
      +<% end %> + From c94f2802affd1e56040dcff339a7ed4a6a085bd2 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 14 Feb 2024 14:29:42 +0000 Subject: [PATCH 159/344] update Usage column title to be clearer #1649 --- app/views/extended_metadata_types/administer.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/extended_metadata_types/administer.html.erb b/app/views/extended_metadata_types/administer.html.erb index 00f282bd8a..3a9f0a6674 100644 --- a/app/views/extended_metadata_types/administer.html.erb +++ b/app/views/extended_metadata_types/administer.html.erb @@ -28,7 +28,7 @@ - + From 395287293364d543d2c824643204fc9ccdc460be Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 14 Feb 2024 14:31:39 +0000 Subject: [PATCH 160/344] do not display the extended types for the current version #1649 it will be necessary to display them though in a later version that supports editing --- app/views/extended_metadata_types/administer.html.erb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/views/extended_metadata_types/administer.html.erb b/app/views/extended_metadata_types/administer.html.erb index 3a9f0a6674..8ed9bf5c72 100644 --- a/app/views/extended_metadata_types/administer.html.erb +++ b/app/views/extended_metadata_types/administer.html.erb @@ -34,17 +34,9 @@ - - <% top_level.each do |extended_metadata_type| %> <%= render partial: 'extended_metadata_type_table_row', locals: { extended_metadata_type: extended_metadata_type } %> <% end %> - - - <% nested.each do |extended_metadata_type| %> - <%= render partial: 'extended_metadata_type_table_row', locals: { extended_metadata_type: extended_metadata_type } %> - <% end %> -
      Internal IdInternal Id Title Supported TypeUsageNumber of
      times used
      Status

      Top Level

      Nested

      From 5e259fbddd0022cede9fb6e9a351c2b075341236 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 14 Feb 2024 15:00:55 +0000 Subject: [PATCH 161/344] hide from api if disabled #1649 --- app/serializers/base_serializer.rb | 2 +- .../integration/api/investigation_api_test.rb | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb index cf38c19f80..d77b6f05ec 100644 --- a/app/serializers/base_serializer.rb +++ b/app/serializers/base_serializer.rb @@ -121,7 +121,7 @@ def BaseSerializer.permits policy end end - attribute :extended_attributes, if: -> { object.respond_to?(:extended_metadata) && !object.extended_metadata.blank? } do + attribute :extended_attributes, if: -> { object.respond_to?(:extended_metadata) && object.extended_metadata&.enabled? } do { extended_metadata_type_id: object.extended_metadata.extended_metadata_type_id.to_s, attribute_map: object.extended_metadata.data.to_hash } end diff --git a/test/integration/api/investigation_api_test.rb b/test/integration/api/investigation_api_test.rb index 0b60834023..f71a37cd88 100644 --- a/test/integration/api/investigation_api_test.rb +++ b/test/integration/api/investigation_api_test.rb @@ -39,4 +39,26 @@ def setup end end end + + test 'extended metadata not shown if disabled' do + person = FactoryBot.create(:person) + + emt = FactoryBot.create(:simple_study_extended_metadata_type) + investigation = FactoryBot.create(:investigation, extended_metadata: ExtendedMetadata.new(extended_metadata_type: emt, data: { name: 'John', age: 12 }), contributor: person) + user_login(person) + + # first check it's present + get "/#{plural_name}/#{investigation.id}.json" + assert_response :success + json = JSON.parse(response.body) + assert_equal emt.id.to_s, json.dig('data','attributes','extended_attributes','extended_metadata_type_id') + + # now without + emt.update_column(:enabled, false) + get "/#{plural_name}/#{investigation.id}.json" + assert_response :success + json = JSON.parse(response.body) + refute_nil attributes = json.dig('data','attributes') + refute attributes.key?('extended_attributes') + end end From 0292a88e8889e31cdd7ec93349f5079db836ff6f Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 16 Feb 2024 15:54:16 +0000 Subject: [PATCH 162/344] Capitalize the Manage* links at the top right of the admin page #1649 --- app/views/admin/_general.html.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/admin/_general.html.erb b/app/views/admin/_general.html.erb index ebf6c379ea..d262ce40c9 100644 --- a/app/views/admin/_general.html.erb +++ b/app/views/admin/_general.html.erb @@ -30,10 +30,10 @@
      • <%= link_to 'Dashboard', dashboard_admin_stats_path -%>
      • <%= link_to 'Announcements', site_announcements_path -%>
      • -
      • <%= link_to 'Manage assay types', suggested_assay_types_path %>
      • -
      • <%= link_to 'Manage technology types', suggested_technology_types_path %>
      • -
      • <%= link_to 'Manage tags', tags_admin_path %>
      • -
      • <%= link_to 'Manage extended metadata types', administer_extended_metadata_types_path %>
      • +
      • <%= link_to 'Manage Assay Types', suggested_assay_types_path %>
      • +
      • <%= link_to 'Manage Technology Types', suggested_technology_types_path %>
      • +
      • <%= link_to 'Manage Tags', tags_admin_path %>
      • +
      • <%= link_to "Manage Extended Metadata Types", administer_extended_metadata_types_path %>
      • <%= link_to "Manage #{WorkflowClass.model_name.human.pluralize}", workflow_classes_path %>
        From 697fd0118e8d5ed1bf3bb9f046ec3d412c09c9ad Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 16 Feb 2024 16:40:29 +0000 Subject: [PATCH 163/344] a little styling of table based on PR requests #1649 colour buttons, warn for disable. Align table text with buttons. Faded text if disabled (by introducing .text-secondary from later bootstrap versions). --- app/assets/stylesheets/_variables.scss | 2 ++ app/assets/stylesheets/bootstrap_include.scss | 5 +++++ .../_extended_metadata_type_table_row.html.erb | 12 +++++++++--- .../extended_metadata_types/administer.html.erb | 6 ++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/_variables.scss b/app/assets/stylesheets/_variables.scss index ecae6d8bfc..8f998bd6e3 100644 --- a/app/assets/stylesheets/_variables.scss +++ b/app/assets/stylesheets/_variables.scss @@ -27,3 +27,5 @@ $isa-bg-Collection: #ABEACB; $isa-bg-HiddenItem: #D3D3D3; $hover-highlight: #E3F8FF; + +$text-secondary: #6c757d; diff --git a/app/assets/stylesheets/bootstrap_include.scss b/app/assets/stylesheets/bootstrap_include.scss index 630f65fb66..88cdff6a59 100644 --- a/app/assets/stylesheets/bootstrap_include.scss +++ b/app/assets/stylesheets/bootstrap_include.scss @@ -124,3 +124,8 @@ textarea.form-control { .nav-tabs a[data-target] { cursor: pointer; } + +/* is missing from the documented options - https://getbootstrap.com/docs/4.0/utilities/colors/ - so added to match */ +.text-secondary { + color: $text-secondary !important +} \ No newline at end of file diff --git a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb index 1509f32764..af75e520ff 100644 --- a/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb +++ b/app/views/extended_metadata_types/_extended_metadata_type_table_row.html.erb @@ -1,4 +1,10 @@ - +<% + row_cls = "" + row_cls << ' warning' if extended_metadata_type.disabled_but_used? + row_cls << ' text-secondary' unless extended_metadata_type.enabled? + +%> + <%= extended_metadata_type.id %> <%= extended_metadata_type.title %> <%= extended_metadata_type.supported_type %> @@ -9,9 +15,9 @@ <%= form_for(extended_metadata_type, url: administer_update_extended_metadata_type_path(extended_metadata_type), method: :put) do |f| %> <%= f.hidden_field :enabled, value: !extended_metadata_type.enabled? %> <% if extended_metadata_type.enabled? %> - <%= f.submit 'Disable', class:'btn' %> + <%= f.submit 'Disable', class:'btn btn-warning' %> <% else %> - <%= f.submit 'Enable', class:'btn' %> + <%= f.submit 'Enable', class:'btn btn-primary' %> <% end %> <% end %> <% end %> diff --git a/app/views/extended_metadata_types/administer.html.erb b/app/views/extended_metadata_types/administer.html.erb index 8ed9bf5c72..da61dcb9d9 100644 --- a/app/views/extended_metadata_types/administer.html.erb +++ b/app/views/extended_metadata_types/administer.html.erb @@ -1,3 +1,9 @@ + + <% nested, top_level = @extended_metadata_types.partition(&:extended_type?) %>
        From e8138ba42834ace1b3353c4e307e46fa85520fa9 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 16 Feb 2024 16:52:07 +0000 Subject: [PATCH 164/344] a test for the fainter text for disabled #1649 --- .../extended_metadata_types_controller_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/functional/extended_metadata_types_controller_test.rb b/test/functional/extended_metadata_types_controller_test.rb index 5c51c2a213..9a58878c55 100644 --- a/test/functional/extended_metadata_types_controller_test.rb +++ b/test/functional/extended_metadata_types_controller_test.rb @@ -56,6 +56,18 @@ class ExtendedMetadataTypesControllerTest < ActionController::TestCase assert_select 'table tbody tr:not(.emt-partition-title) td', text: emt.title end + test 'administer table - disabled as text-secondary' do + emt = FactoryBot.create(:simple_investigation_extended_metadata_type) + person = FactoryBot.create(:admin) + login_as(person) + get :administer + assert_select 'table tr.text-secondary', count: 0 + + emt.update_column(:enabled, false) + get :administer + assert_select 'table tr.text-secondary', count: 1 + end + test 'can access administer as admin' do person = FactoryBot.create(:admin) login_as(person) From 02f5ec3252b64d3b1eaebe442f6334a08a1cf03a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 20 Feb 2024 13:46:48 +0100 Subject: [PATCH 165/344] Remove unused variable --- app/views/sample_types/_sample_attribute_form.html.erb | 1 - 1 file changed, 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 ede51bc499..acb98013d0 100644 --- a/app/views/sample_types/_sample_attribute_form.html.erb +++ b/app/views/sample_types/_sample_attribute_form.html.erb @@ -27,7 +27,6 @@ <% level = sample_type.level if display_isa_tag %> <% isa_tags_options = IsaTag.allowed_isa_tags_for_level(level).map { |it| [it.title, it.id] } if display_isa_tag %> <% isa_tag_id = sample_attribute&.isa_tag_id %> -<% isa_tag_title = sample_attribute&.isa_tag&.title %> <% template_attribute_id = sample_attribute&.template_attribute_id if display_isa_tag %> <% allow_isa_tag_change = constraints.allow_isa_tag_change?(sample_attribute) if display_isa_tag %> From 657f1a637daa2c9a07d38aad7275b3cacfd38765 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 21 Feb 2024 09:30:14 +0100 Subject: [PATCH 166/344] Lock inherited sample attributes --- app/assets/javascripts/templates.js | 21 +++++++++++++++++-- .../sample_type_editing_constraints.rb | 12 +++++++++-- .../sample_type_editing_constraints_test.rb | 3 ++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/templates.js b/app/assets/javascripts/templates.js index 46d2c67d37..4b0c733526 100644 --- a/app/assets/javascripts/templates.js +++ b/app/assets/javascripts/templates.js @@ -235,22 +235,39 @@ const applyTemplate = () => { }); index++; - const isInputRow = row[7] === 'Registered Sample List' && row[1].includes('Input') && row[11] === null + const isInputRow = + row[7] === "Registered Sample List" && + row[1].includes("Input") && + row[11] === null; + const isInherited = row[14] !== "undefined" || row[14] !== null; + const isRequired = row[0] ? "checked" : ""; 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="title"]').addClass("disabled"); $j(newRow).find('[data-attr="description"]').val(row[2]); $j(newRow).find('[data-attr="type"]').val(row[3]); + $j(newRow).find('[data-attr="type"]').addClass("disabled"); $j(newRow).find('[data-attr="cv_id"]').val(row[4]); + $j(newRow).find('[data-attr="cv_id"]').parent().addClass("disabled"); $j(newRow).find('[data-attr="allow_cv_free_text"]').prop("checked", row[5]); + $j(newRow) + .find('[data-attr="allow_cv_free_text"]') + .addClass("disabled"); $j(newRow).find('[data-attr="unit"]').val(row[6]); + $j(newRow).find('[data-attr="unit"]').addClass("disabled"); $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); + $j(newRow) + .find('[data-attr="isa_tag_title"]') + .addClass("disabled"); $j(newRow).find('[data-attr="template_attribute_id"]').val(row[14]); // In case of a sample type $j(newRow).find('[data-attr="parent_attribute_id"]').val(row[14]); // In case of a template + if (isRequired) { + $j(newRow).find('label.btn.btn-danger').addClass("hidden"); + } // Show the CV block if cv_id is not empty if (row[4]) $j(newRow).find(".controlled-vocab-block").show(); diff --git a/lib/seek/samples/sample_type_editing_constraints.rb b/lib/seek/samples/sample_type_editing_constraints.rb index 1c02e9dcee..39f5a64cd7 100644 --- a/lib/seek/samples/sample_type_editing_constraints.rb +++ b/lib/seek/samples/sample_type_editing_constraints.rb @@ -5,6 +5,7 @@ module Samples class SampleTypeEditingConstraints attr_reader :sample_type delegate :samples, to: :sample_type + delegate :is_isa_json_compliant?, to: :sample_type def initialize(sample_type) fail Exception.new('Sample type cannot be nil') unless sample_type @@ -21,6 +22,7 @@ def samples? def allow_required?(attr) if attr.is_a?(SampleAttribute) return true if attr.new_record? + attr = attr.accessor_name end if attr @@ -34,7 +36,9 @@ def allow_required?(attr) # attr can be the attribute accessor name, or the attribute itself def allow_attribute_removal?(attr) if attr.is_a?(SampleAttribute) - return true if attr.new_record? + return true if attr.new_record? && !inherited?(attr) + return false if inherited?(attr) && attr.required? + attr = attr.accessor_name end if attr @@ -53,6 +57,8 @@ def allow_new_attribute? def allow_name_change?(attr) if attr.is_a?(SampleAttribute) return true if attr.new_record? + return false if inherited?(attr) + attr = attr.accessor_name end if attr @@ -66,6 +72,8 @@ def allow_name_change?(attr) def allow_type_change?(attr) if attr.is_a?(SampleAttribute) return true if attr.new_record? + return false if inherited?(attr) + attr = attr.accessor_name end if attr @@ -96,7 +104,7 @@ def refresh_cache private def inherited?(attr) - attr&.inherited_from_template_attribute? + attr&.inherited_from_template_attribute? && is_isa_json_compliant? end def blanks?(attr) diff --git a/test/unit/samples/sample_type_editing_constraints_test.rb b/test/unit/samples/sample_type_editing_constraints_test.rb index e96168528d..ace957c974 100644 --- a/test/unit/samples/sample_type_editing_constraints_test.rb +++ b/test/unit/samples/sample_type_editing_constraints_test.rb @@ -102,7 +102,7 @@ class SampleTypeEditingConstraintsTest < ActiveSupport::TestCase assert c.allow_attribute_removal?(:postcode) refute c.allow_attribute_removal?(:age) refute c.allow_attribute_removal?('full name') - # accespts attribute + # accepts attribute attr = c.sample_type.sample_attributes.detect { |t| t.accessor_name == 'address' } refute_nil attr refute c.allow_attribute_removal?(attr) @@ -264,6 +264,7 @@ def create_sample_type_from_template(template, project, person) projects:[project], contributor: person, template_id: template.id, + assays: [FactoryBot.build(:assay, contributor: person)], sample_attributes: ) end end From 46d19953aa7fc18121867dfaa2f67b7fe506a5bc Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 21 Feb 2024 16:22:39 +0100 Subject: [PATCH 167/344] Making it possible to reorder Sample Attributes in a ISA JSON compliant assay / study --- app/assets/javascripts/sample_types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/sample_types.js b/app/assets/javascripts/sample_types.js index 1d933fd260..298f65396d 100644 --- a/app/assets/javascripts/sample_types.js +++ b/app/assets/javascripts/sample_types.js @@ -12,7 +12,7 @@ var SampleTypes = { helper: SampleTypes.fixHelper, handle: '.attribute-handle' }).on('sortupdate', function() { - SampleTypes.recalculatePositions(); + SampleTypes.recalculatePositions(selector); }); }, From 2e88af7078c668ba52ed9b4f6099489d6f342666 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 21 Feb 2024 16:28:18 +0100 Subject: [PATCH 168/344] Add column reordering to the DataTable.js --- app/assets/javascripts/single_page/dynamic_table.js.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index e3032c5cfe..209f99087f 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -105,6 +105,7 @@ const handleSelect = (e) => { ]; const editor = this.editor; this.table = this.table.DataTable({ + colReorder: true, columnDefs, columns, scrollX: "100%", From 64dacd523d95803e89a2ebaf9c4fc0a1fcf8adb9 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 22 Feb 2024 15:05:36 +0100 Subject: [PATCH 169/344] Display the sample metadata in the same order as the sample attributes in the sample type --- .../single_page/dynamic_table.js.erb | 1 - app/helpers/dynamic_table_helper.rb | 27 ++++++++++++------- app/views/isa_assays/_assay_samples.html.erb | 2 +- .../unit/helpers/dynamic_table_helper_test.rb | 24 +++++++++++++++++ 4 files changed, 43 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 209f99087f..3f4df0cbda 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -94,7 +94,6 @@ const handleSelect = (e) => { columns.unshift(...defaultCols); const columnDefs = [{ - orderable: false, targets: options.readonly ? [0] : [0, 1] }, { diff --git a/app/helpers/dynamic_table_helper.rb b/app/helpers/dynamic_table_helper.rb index 5a547108bd..973c88ef65 100644 --- a/app/helpers/dynamic_table_helper.rb +++ b/app/helpers/dynamic_table_helper.rb @@ -1,8 +1,19 @@ module DynamicTableHelper def dt_data(sample_type) - rows = dt_rows(sample_type) columns = dt_cols(sample_type) - { columns: columns, rows: rows } + rows = dt_rows(sample_type) + row_values = get_rows_for_columns(rows, columns) + { columns:, rows: row_values } + end + + # Gets the row values from the JSON metadata in the order that the columns are. + # Makes switching attribute positions possible without scrambling the JSON metadata + def get_rows_for_columns(rows, columns) + rows.map do |row| + columns.map do |col| + row[col[:title]] + end + end end def dt_aggregated(study, assay = nil) @@ -14,7 +25,7 @@ def dt_aggregated(study, assay = nil) end columns = dt_cumulative_cols(sample_types) rows = dt_cumulative_rows(sample_types, columns.length) - { columns: columns, rows: rows, sample_types: sample_types.map { |s| { title: s.title, id: s.id } } } + { columns:, rows:, sample_types: sample_types.map { |s| { title: s.title, id: s.id } } } end private @@ -35,12 +46,10 @@ def dt_rows(sample_type) sample_type.samples.map do |s| if s.can_view? - sanitized_json_metadata = hide_unauthorized_inputs(JSON(s.json_metadata), registered_sample_attributes, registered_sample_multi_attributes) - ['', s.id, s.uuid] + - sanitized_json_metadata.values + sanitized_json_metadata = hide_unauthorized_inputs(JSON.parse(s.json_metadata), registered_sample_attributes, registered_sample_multi_attributes) + { 'selected' => '', 'id' => s.id, 'uuid' => s.uuid }.merge!(sanitized_json_metadata) else - ['', '#HIDDEN', '#HIDDEN'] + - Array.new(JSON(s.json_metadata).length, '#HIDDEN') + { 'selected' => '', 'id' => '#HIDDEN', 'uuid' => '#HIDDEN' }.merge!(sanitized_json_metadata&.transform_values { '#HIDDEN' }) end end end @@ -105,7 +114,7 @@ def dt_cols(sample_type) end def dt_default_cols(name) - [{ title: 'status', name: name, status: true }, { title: 'id', name: name }, { title: 'uuid', name: name }] + [{ title: 'status', name:, status: true }, { title: 'id', name: }, { title: 'uuid', name: }] end def dt_cumulative_rows(sample_types, col_count) diff --git a/app/views/isa_assays/_assay_samples.html.erb b/app/views/isa_assays/_assay_samples.html.erb index 192536ec25..731515536d 100644 --- a/app/views/isa_assays/_assay_samples.html.erb +++ b/app/views/isa_assays/_assay_samples.html.erb @@ -24,7 +24,7 @@ $j('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { $j.fn.dataTable.tables( {visible: true, api: true} ).columns.adjust(); }); - const dt = <%= sanitize(dt_data(sample_type).to_json) %> + const dt = <%= sanitize(dt_data(sample_type).to_json) %>; window.sampleDynamicTable = new $j.dynamicTable('#assay-samples-table'); const elem = $j("#btn_save_assay_sample") const options = { diff --git a/test/unit/helpers/dynamic_table_helper_test.rb b/test/unit/helpers/dynamic_table_helper_test.rb index af03f2b7a8..137124a5a7 100644 --- a/test/unit/helpers/dynamic_table_helper_test.rb +++ b/test/unit/helpers/dynamic_table_helper_test.rb @@ -79,4 +79,28 @@ class DynamicTableHelperTest < ActionView::TestCase sequence = link_sequence(type3) assert_equal sequence, [type3, type2, type1] end + + test 'should display the data correctly independent of the order in the json metadata' do + person = FactoryBot.create(:person) + sample_type = FactoryBot.create(:isa_source_sample_type, contributor: person) + sample1 = FactoryBot.create(:isa_source, sample_type:, contributor: person) + sample_type.reload + rows_case1 = User.with_current_user(person.user) do + dt_data(sample_type)[:rows] + end + refute_nil rows_case1 + sample1_metadata = [[nil, sample1.id, sample1.uuid].push(*JSON.parse(sample1.json_metadata).values)] + assert_equal sample1_metadata, rows_case1 + + sample_type.sample_attributes.first.update(pos: 2) + sample_type.sample_attributes.second.update(pos: 1) + sample_type.reload + + rows_case2 = User.with_current_user(person.user) do + dt_data(sample_type)[:rows] + end + refute_equal rows_case2, sample1_metadata + assert_equal sample1_metadata[0][3], rows_case2[0][4] + assert_equal sample1_metadata[0][4], rows_case2[0][3] + end end From 0af587d4904883234651450761f4edcfdee1619c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 23 Feb 2024 12:01:06 +0100 Subject: [PATCH 170/344] Add alert in case of error --- app/assets/javascripts/single_page/index.js.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/single_page/index.js.erb b/app/assets/javascripts/single_page/index.js.erb index 8b93dd1657..0225c1e949 100644 --- a/app/assets/javascripts/single_page/index.js.erb +++ b/app/assets/javascripts/single_page/index.js.erb @@ -316,6 +316,7 @@ async function handleUploadSubmit(formData){ $j('#upload-excel').html(response); }, error: function(err){ + alert(`Failed to upload file!\nStatus: ${err.status}\nError: ${err.statusText}`); location.reload(); // Page needs reloading for the notice message to appear } }); From 11aa640c9477446ee33ac4219d9bfd824e19330f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 23 Feb 2024 12:02:45 +0100 Subject: [PATCH 171/344] Add error handling --- app/views/single_pages/sample_upload_content.html.erb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/views/single_pages/sample_upload_content.html.erb b/app/views/single_pages/sample_upload_content.html.erb index 9102c2d905..7ecee0c311 100644 --- a/app/views/single_pages/sample_upload_content.html.erb +++ b/app/views/single_pages/sample_upload_content.html.erb @@ -83,10 +83,15 @@ try{ const postCall = await uploadAjaxCall("<%= batch_create_samples_path %>", "POST", { data: JSON.stringify(newSamples) }); const putCall = await uploadAjaxCall("<%= batch_update_samples_path %>", "PUT", { data: JSON.stringify(updatedSamples) }); + let errors = {newSamples: postCall.errors, updatedSamples: putCall.errors}; + if (errors.newSamples.length > 0 || errors.updatedSamples.length > 0) { + throw new Error(JSON.stringify(errors)); + } $j('#sample-upload-spinner').hide(); closeModalForm(); location.reload(); } catch (error){ + alert(`Error: ${error}`); $j('#sample-upload-spinner').hide(); } } @@ -132,7 +137,7 @@ const inputIds = multiInputfields.map(is => is.title.split(" ").pop()).join(','); samplesObj[key] = inputIds; } else if (cvListFields.length > 0){ - cvTerms = cvListFields.map(cvt => cvt.title) + let cvTerms = cvListFields.map(cvt => cvt.title) samplesObj[key] = cvTerms; } else if (seekSample.length > 0) { samplesObj[key] = seekSample[0].title From 7a63fa8b7f81c76f2cc86ec05eecfda8942a0ffb Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 23 Feb 2024 12:03:48 +0100 Subject: [PATCH 172/344] Change status key to sampleUploadAction --- app/views/single_pages/sample_upload_content.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/single_pages/sample_upload_content.html.erb b/app/views/single_pages/sample_upload_content.html.erb index 7ecee0c311..a2a939ec81 100644 --- a/app/views/single_pages/sample_upload_content.html.erb +++ b/app/views/single_pages/sample_upload_content.html.erb @@ -148,7 +148,7 @@ const {id: objId, ...attrMap} = samplesObj; - attrMap["status"] = action; + attrMap["sampleUploadAction"] = action; if (action === "update"){ return { From b1bb8d06e1e123bf93379e44f4b960465f30660c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 26 Feb 2024 13:25:02 +0100 Subject: [PATCH 173/344] Add definition for Single Page + help text. --- config/locales/en.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 8880ff79e8..46eb98d2ae 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -9,6 +9,7 @@ en: ldap: 'LDAP' github: 'GitHub' + single_page: "Single Page" investigation: &investigation "Investigation" study: &study "Study" assay: &assay "Assay" @@ -292,6 +293,7 @@ you can choose to have a %{project} linked to a site managed %{programme} instea

        ' info_text: + single_page: "A view specifically designed to visualize and interact with ISA JSON compliant items." institution: "An Institution in SEEK is where someone is employed or works or a person's affiliation." #person: "A Person (People) in SEEK is a registered user or a Profile of a person that has not registered with SEEK. A Person is someone who participates directly or indirectly in the scientific research described within SEEK." programme: "A Programme is an umbrella to group one or more Projects." From fd2dc339d4951b4645709b73c818493eac895916 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 26 Feb 2024 13:43:05 +0100 Subject: [PATCH 174/344] Add definition for Default View + help text. --- config/locales/en.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 46eb98d2ae..555a9ffe55 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -10,6 +10,7 @@ en: github: 'GitHub' single_page: "Single Page" + default_view: "Default View" investigation: &investigation "Investigation" study: &study "Study" assay: &assay "Assay" @@ -293,6 +294,7 @@ you can choose to have a %{project} linked to a site managed %{programme} instea

        ' info_text: + default_view: "The default view is the view that is shown when you first visit a page. You can change the default view by clicking on the 'Default View' button." single_page: "A view specifically designed to visualize and interact with ISA JSON compliant items." institution: "An Institution in SEEK is where someone is employed or works or a person's affiliation." #person: "A Person (People) in SEEK is a registered user or a Profile of a person that has not registered with SEEK. A Person is someone who participates directly or indirectly in the scientific research described within SEEK." From d58722e91bb4c021a5e01c36e287411009a91b59 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 26 Feb 2024 14:14:27 +0100 Subject: [PATCH 175/344] Change buttons --- app/views/assays/_buttons.html.erb | 8 ++++---- app/views/investigations/_buttons.html.erb | 8 ++++---- app/views/projects/_buttons.html.erb | 8 ++++---- app/views/studies/_buttons.html.erb | 8 ++++---- config/locales/en.yml | 6 +++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 747e72addc..82ce9dad0d 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -28,12 +28,12 @@ <% if Seek::Config.project_single_page_enabled %> <% if !displaying_single_page? %> - - <%= button_link_to("Single Page", 'sop', single_page_path(id: item.projects.first.id, item_type: 'assay', item_id: item.id)) -%> + "> + <%= button_link_to(t('single_page'), 'sop', single_page_path(id: item.projects.first.id, item_type: 'assay', item_id: item.id)) -%> <% else %> - - <%= button_link_to("Default View", 'sop', assay_path(item.id)) -%> + + <%= button_link_to(t('default_view'), 'sop', assay_path(item.id)) -%> <% end %> <% end -%> diff --git a/app/views/investigations/_buttons.html.erb b/app/views/investigations/_buttons.html.erb index 2b260a9965..26ccd792f2 100644 --- a/app/views/investigations/_buttons.html.erb +++ b/app/views/investigations/_buttons.html.erb @@ -2,12 +2,12 @@ <% if Seek::Config.project_single_page_enabled %> <% if !displaying_single_page? %> - - <%= button_link_to("Single Page", 'sop', single_page_path(id: item.projects.first.id, item_type: 'investigation', item_id: item.id)) -%> + "> + <%= button_link_to(t('single_page'), 'sop', single_page_path(id: item.projects.first.id, item_type: 'investigation', item_id: item.id)) -%> <% else %> - - <%= button_link_to("Default View", 'sop', investigation_path(item.id)) -%> + + <%= button_link_to(t('default_view'), 'sop', investigation_path(item.id)) -%> <% end %> <% end -%> diff --git a/app/views/projects/_buttons.html.erb b/app/views/projects/_buttons.html.erb index 15a4981c5f..d71dad5b2d 100644 --- a/app/views/projects/_buttons.html.erb +++ b/app/views/projects/_buttons.html.erb @@ -1,11 +1,11 @@ <% if Seek::Config.project_single_page_enabled %> <% if !displaying_single_page? %> - - <%= button_link_to("Single Page", 'sop', single_page_path(item.id)) -%> + "> + <%= button_link_to(t("single_page"), 'sop', single_page_path(item.id)) -%> <% else %> - - <%= button_link_to("Default View", 'sop', project_path(item.id)) -%> + + <%= button_link_to(t('default_view'), 'sop', project_path(item.id)) -%> <% end %> <% end -%> diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index a308561224..8af4b1e38a 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -2,12 +2,12 @@ <% if Seek::Config.project_single_page_enabled %> <% if !displaying_single_page? %> - - <%= button_link_to("Single Page", 'sop', single_page_path(id: item.projects.first.id, item_type: 'study', item_id: item.id)) -%> + "> + <%= button_link_to(t("single_page"), 'sop', single_page_path(id: item.projects.first.id, item_type: 'study', item_id: item.id)) -%> <% else %> - - <%= button_link_to("Default View", 'sop', study_path(item.id)) -%> + + <%= button_link_to(t('default_view'), 'sop', study_path(item.id)) -%> <% end %> <% end -%> diff --git a/config/locales/en.yml b/config/locales/en.yml index 555a9ffe55..664f5a32c7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -9,7 +9,7 @@ en: ldap: 'LDAP' github: 'GitHub' - single_page: "Single Page" + single_page: "Experiment view" default_view: "Default View" investigation: &investigation "Investigation" study: &study "Study" @@ -294,8 +294,8 @@ you can choose to have a %{project} linked to a site managed %{programme} instea

        ' info_text: - default_view: "The default view is the view that is shown when you first visit a page. You can change the default view by clicking on the 'Default View' button." - single_page: "A view specifically designed to visualize and interact with ISA JSON compliant items." + default_view: "The default view used to visualize your experiments." + single_page: "A view specifically designed to visualize and interact with your experiments." institution: "An Institution in SEEK is where someone is employed or works or a person's affiliation." #person: "A Person (People) in SEEK is a registered user or a Profile of a person that has not registered with SEEK. A Person is someone who participates directly or indirectly in the scientific research described within SEEK." programme: "A Programme is an umbrella to group one or more Projects." From 76082619c3b0d40914afa1b708b12f19ce122fb0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 26 Feb 2024 14:22:50 +0100 Subject: [PATCH 176/344] Abstract modals to partial html files --- app/views/assays/show.html.erb | 2 ++ .../_change_batch_persmission_modal.html.erb | 8 ++++++++ .../single_pages/_upload_excel_modal.html.erb | 6 ++++++ app/views/single_pages/show.html.erb | 18 ++---------------- app/views/studies/show.html.erb | 4 +++- 5 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 app/views/single_pages/_change_batch_persmission_modal.html.erb create mode 100644 app/views/single_pages/_upload_excel_modal.html.erb diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 9f85fc1b39..658ff21331 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -165,6 +165,8 @@ <% 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} -%> + <%= render partial: 'single_pages/change_batch_persmission_modal' %> + <%= render partial: 'single_pages/upload_excel_modal' %> <% end %> <% end %>

        diff --git a/app/views/single_pages/_change_batch_persmission_modal.html.erb b/app/views/single_pages/_change_batch_persmission_modal.html.erb new file mode 100644 index 0000000000..d4b665d795 --- /dev/null +++ b/app/views/single_pages/_change_batch_persmission_modal.html.erb @@ -0,0 +1,8 @@ +<%= modal(id: 'change-batch-permission-modal', size: 'xl') do %> + <%= modal_header("Batch permission changes") %> + <%= modal_body do %> +
        + hello +
        + <% end %> +<% end %> diff --git a/app/views/single_pages/_upload_excel_modal.html.erb b/app/views/single_pages/_upload_excel_modal.html.erb new file mode 100644 index 0000000000..ceff7f6155 --- /dev/null +++ b/app/views/single_pages/_upload_excel_modal.html.erb @@ -0,0 +1,6 @@ +<%= modal(id: 'upload-excel-modal', size: 'xl') do %> + <%= modal_header("Upload from spreadsheet") %> + <%= modal_body do %> +
        ...
        + <% end %> +<% end %> diff --git a/app/views/single_pages/show.html.erb b/app/views/single_pages/show.html.erb index 681d587e91..b8fb676c8c 100644 --- a/app/views/single_pages/show.html.erb +++ b/app/views/single_pages/show.html.erb @@ -47,22 +47,8 @@
        - <%= modal(id: 'change-batch-permission-modal', size: 'xl') do %> - <%= modal_header("Batch permission changes") %> - <%= modal_body do %> -
        - hello -
        - <% end %> - <% end %> - - <%= modal(id: 'upload-excel-modal', size: 'xl') do %> - <%= modal_header("Upload from spreadsheet") %> - <%= modal_body do %> -
        ...
        - <% end %> - <% end %> - + <%= render partial: 'change_batch_persmission_modal' %> + <%= render partial: 'upload_excel_modal' %>
      diff --git a/app/views/studies/show.html.erb b/app/views/studies/show.html.erb index fe1af1d537..6c32da6c69 100644 --- a/app/views/studies/show.html.erb +++ b/app/views/studies/show.html.erb @@ -78,10 +78,12 @@
    From 5c1326a9b0b9c937f9ff5c3bb19ad5dfe3ff111e Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 26 Feb 2024 14:45:16 +0100 Subject: [PATCH 178/344] Revert to default --- 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 664f5a32c7..d04a19a1bc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -9,7 +9,7 @@ en: ldap: 'LDAP' github: 'GitHub' - single_page: "Experiment view" + single_page: "Single Page" default_view: "Default View" investigation: &investigation "Investigation" study: &study "Study" From 291030019930a120e0404ceeacd6b521f02a9b3f Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 26 Oct 2023 13:11:52 +0100 Subject: [PATCH 179/344] Delete auth lookup records asynchronously. Fixes #1628 --- app/jobs/auth_lookup_delete_job.rb | 15 ++++ app/models/user.rb | 8 +- .../permissions/policy_based_authorization.rb | 7 +- test/functional/data_files_controller_test.rb | 28 +++++- test/unit/jobs/auth_lookup_delete_job_test.rb | 90 +++++++++++++++++++ .../permissions/auth_lookup_table_test.rb | 33 ++++--- 6 files changed, 157 insertions(+), 24 deletions(-) create mode 100644 app/jobs/auth_lookup_delete_job.rb create mode 100644 test/unit/jobs/auth_lookup_delete_job_test.rb diff --git a/app/jobs/auth_lookup_delete_job.rb b/app/jobs/auth_lookup_delete_job.rb new file mode 100644 index 0000000000..9eae301569 --- /dev/null +++ b/app/jobs/auth_lookup_delete_job.rb @@ -0,0 +1,15 @@ +class AuthLookupDeleteJob < ApplicationJob + BATCH_SIZE = 1000 + queue_as QueueNames::AUTH_LOOKUP + queue_with_priority 2 + + def perform(item_class, item_id) + if item_class == 'User' + Seek::Util.authorized_types.each do |type| + type.lookup_class.where(user: item_id).in_batches(of: BATCH_SIZE).delete_all + end + else + item_class.constantize.lookup_class.where(asset_id: item_id).in_batches(of: BATCH_SIZE).delete_all + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index bc96c22eb3..ed0e487e03 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -69,7 +69,7 @@ class User < ApplicationRecord after_commit :queue_update_auth_table, on: :create - after_destroy :remove_from_auth_tables + after_destroy :queue_auth_lookup_delete_job # related_#{type} are resources that user created RELATED_RESOURCE_TYPES = %i[data_files models sops events presentations publications].freeze @@ -339,10 +339,8 @@ def queue_update_auth_table AuthLookupUpdateQueue.enqueue(self) end - def remove_from_auth_tables - Seek::Util.authorized_types.each do |type| - type.lookup_class.where(user: id).in_batches(of:1000).delete_all - end + def queue_auth_lookup_delete_job + AuthLookupDeleteJob.perform_later(self.class.name, id) end def self.unique_login(original_login) diff --git a/lib/seek/permissions/policy_based_authorization.rb b/lib/seek/permissions/policy_based_authorization.rb index a340c169fb..ecd8e2cea4 100644 --- a/lib/seek/permissions/policy_based_authorization.rb +++ b/lib/seek/permissions/policy_based_authorization.rb @@ -22,6 +22,7 @@ def self.included(klass) after_commit :immediate_auth_update, on: [:create, :update] after_commit :check_to_queue_update_auth_table, on: [:create, :update] after_destroy { |record| record.policy.try(:destroy_if_redundant) } + after_destroy :queue_auth_lookup_delete_job const_set('AuthLookup', Class.new(::AuthLookup)).class_eval do |c| c.table_name = klass.lookup_table_name @@ -29,7 +30,6 @@ def self.included(klass) end has_many :auth_lookup, foreign_key: :asset_id, inverse_of: :asset - before_destroy :delete_auth_lookup_in_batches end end # the can_#{action}? methods are split into 2 parts, to differentiate between pure authorization and additional permissions based upon the state of the object or other objects it depends upon) @@ -349,10 +349,9 @@ def lookup_for(action, user_id) auth_lookup.where(user_id: user_id).limit(1).pluck("can_#{action}").first end - def delete_auth_lookup_in_batches - auth_lookup.in_batches(of: 1000).delete_all + def queue_auth_lookup_delete_job + AuthLookupDeleteJob.perform_later(self.class.name, id) end - end end end diff --git a/test/functional/data_files_controller_test.rb b/test/functional/data_files_controller_test.rb index 79faebcfb1..bdc34c30cb 100644 --- a/test/functional/data_files_controller_test.rb +++ b/test/functional/data_files_controller_test.rb @@ -2828,11 +2828,11 @@ def test_show_item_attributed_to_jerm_file assert_equal visible, assigns(:visible_count) end - test 'delete with data file with extracted samples' do + test 'delete data file and extracted samples' do login_as(FactoryBot.create(:person)) - df = nil df = data_file_with_extracted_samples + sample = df.extracted_samples.first assert_no_difference('DataFile.count') do delete :destroy, params: { id: df.id } @@ -2841,17 +2841,37 @@ def test_show_item_attributed_to_jerm_file assert_difference('DataFile.count', -1) do assert_difference('Sample.count', -4) do - delete :destroy, params: { id: df.id, destroy_extracted_samples: '1' } + assert_enqueued_jobs(5, only: AuthLookupDeleteJob) do + assert_enqueued_with(job: AuthLookupDeleteJob, args: ['DataFile', df.id]) do + assert_enqueued_with(job: AuthLookupDeleteJob, args: ['Sample', sample.id]) do + delete :destroy, params: { id: df.id, destroy_extracted_samples: '1' } + end + end + end end end assert_redirected_to data_files_path + end + + test 'delete data file but not extracted samples' do + login_as(FactoryBot.create(:person)) df = data_file_with_extracted_samples + sample = df.extracted_samples.first + + assert_no_difference('DataFile.count') do + delete :destroy, params: { id: df.id } + end + assert_redirected_to destroy_samples_confirm_data_file_path(df) assert_difference('DataFile.count', -1) do assert_no_difference('Sample.count') do - delete :destroy, params: { id: df.id, destroy_extracted_samples: '0' } + assert_enqueued_jobs(1, only: AuthLookupDeleteJob) do + assert_enqueued_with(job: AuthLookupDeleteJob, args: ['DataFile', df.id]) do + delete :destroy, params: { id: df.id, destroy_extracted_samples: '0' } + end + end end end diff --git a/test/unit/jobs/auth_lookup_delete_job_test.rb b/test/unit/jobs/auth_lookup_delete_job_test.rb new file mode 100644 index 0000000000..58b1bf8c17 --- /dev/null +++ b/test/unit/jobs/auth_lookup_delete_job_test.rb @@ -0,0 +1,90 @@ +require 'test_helper' +require 'minitest/mock' + +class AuthLookupDeleteJobTest < ActiveSupport::TestCase + test 'perform for asset' do + disable_authorization_checks do + Person.destroy_all + User.destroy_all + Assay.destroy_all + DataFile.destroy_all + end + + u = FactoryBot.create(:user) + u2 = FactoryBot.create(:user) + a = FactoryBot.create(:assay, contributor: u2.person) + d = FactoryBot.create(:data_file, contributor: u2.person) + a.update_lookup_table_for_all_users + d.update_lookup_table_for_all_users + + assert_equal (User.count + 1), a.auth_lookup.count + assert_equal (User.count + 1), d.auth_lookup.count + + assert Assay::AuthLookup.where(user_id: u.id).exists? + assert DataFile::AuthLookup.where(user_id: u.id).exists? + assert DataFile::AuthLookup.where(user_id: u2.id).exists? + assert DataFile::AuthLookup.where(user_id: 0).exists? + + assert_difference('DataFile::AuthLookup.count', -3) do # 2 users + anonymous + assert_no_difference('Assay::AuthLookup.count') do + AuthLookupDeleteJob.perform_now('DataFile', d.id) + end + end + + assert Assay::AuthLookup.where(user_id: u.id).exists? + refute DataFile::AuthLookup.where(user_id: u.id).exists? + refute DataFile::AuthLookup.where(user_id: u2.id).exists? + refute DataFile::AuthLookup.where(user_id: 0).exists? + end + + test 'perform when asset record no longer exists' do + assert_nothing_raised do + assert_no_difference('DataFile::AuthLookup.count') do + AuthLookupDeleteJob.perform_now('DataFile', DataFile.maximum(:id) + 10) + end + end + end + + test 'perform when user record no longer exists' do + assert_nothing_raised do + assert_no_difference('DataFile::AuthLookup.count') do + AuthLookupDeleteJob.perform_now('User', User.maximum(:id) + 10) + end + end + end + + test 'perform for user' do + disable_authorization_checks do + Person.destroy_all + User.destroy_all + Assay.destroy_all + DataFile.destroy_all + end + + u = FactoryBot.create(:user) + u2 = FactoryBot.create(:user) + a = FactoryBot.create(:assay, contributor: u2.person) + d = FactoryBot.create(:data_file, contributor: u2.person) + a.update_lookup_table_for_all_users + d.update_lookup_table_for_all_users + + assert_equal (User.count + 1), a.auth_lookup.count + assert_equal (User.count + 1), d.auth_lookup.count + + assert Assay::AuthLookup.where(user_id: u.id).exists? + assert DataFile::AuthLookup.where(user_id: u.id).exists? + assert DataFile::AuthLookup.where(user_id: u2.id).exists? + assert DataFile::AuthLookup.where(user_id: 0).exists? + + assert_difference('DataFile::AuthLookup.count', -1) do + assert_difference('Assay::AuthLookup.count', -1) do + AuthLookupDeleteJob.perform_now('User', u.id) + end + end + + refute Assay::AuthLookup.where(user_id: u.id).exists? + refute DataFile::AuthLookup.where(user_id: u.id).exists? + assert DataFile::AuthLookup.where(user_id: u2.id).exists? + assert DataFile::AuthLookup.where(user_id: 0).exists? + end +end diff --git a/test/unit/permissions/auth_lookup_table_test.rb b/test/unit/permissions/auth_lookup_table_test.rb index 08ba02251a..1a25895324 100644 --- a/test/unit/permissions/auth_lookup_table_test.rb +++ b/test/unit/permissions/auth_lookup_table_test.rb @@ -13,7 +13,7 @@ def teardown Seek::Config.auth_lookup_enabled = @val end - test 'Removes a user from the lookup tables when they are destroyed' do # + test 'queues delete job when user is destroyed' do disable_authorization_checks do Person.destroy_all User.destroy_all @@ -31,9 +31,17 @@ def teardown assert_equal (User.count + 1), a.auth_lookup.count assert_equal (User.count + 1), d.auth_lookup.count + assert_no_difference('a.auth_lookup.count') do + assert_no_difference('d.auth_lookup.count') do + assert_enqueued_with(job: AuthLookupDeleteJob, args: ['User', u.id]) do + disable_authorization_checks { u.destroy } + end + end + end + assert_difference('a.auth_lookup.count', -1) do assert_difference('d.auth_lookup.count', -1) do - disable_authorization_checks { u.destroy } + AuthLookupDeleteJob.perform_now('User', u.id) end end @@ -185,7 +193,7 @@ def teardown assert_equal [true, false, false, false, false], sop.auth_lookup.where(user: anon).first.as_array end - test 'deletes records when assets removed' do + test 'queues delete job when assets removed' do Person.destroy_all User.destroy_all Sop.destroy_all @@ -193,22 +201,25 @@ def teardown person = FactoryBot.create(:person) FactoryBot.create(:person) - sop=FactoryBot.create(:sop, contributor:person) - sop2=FactoryBot.create(:sop, contributor:person) + sop = FactoryBot.create(:sop, contributor: person) + sop2 = FactoryBot.create(:sop, contributor: person) sop.update_lookup_table_for_all_users sop2.update_lookup_table_for_all_users - assert_equal 3,sop.auth_lookup.count - assert_equal 3,sop2.auth_lookup.count + assert_equal 3, sop.auth_lookup.count + assert_equal 3, sop2.auth_lookup.count - disable_authorization_checks do - assert_difference('Sop::AuthLookup.count',-3) do - sop.destroy + assert_no_difference('Sop::AuthLookup.count') do + assert_enqueued_with(job: AuthLookupDeleteJob, args: ['Sop', sop.id]) do + disable_authorization_checks { sop.destroy } end end - assert_equal 3,sop2.auth_lookup.count + assert_difference('Sop::AuthLookup.count',-3) do + AuthLookupDeleteJob.perform_now('Sop', sop.id) + end + assert_equal 3, sop2.auth_lookup.count end end From 34463d5d6c13b6e724e92f9c14a2ece444192846 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 26 Oct 2023 15:22:12 +0100 Subject: [PATCH 180/344] Test fix --- test/unit/permissions/policy_based_auth_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/permissions/policy_based_auth_test.rb b/test/unit/permissions/policy_based_auth_test.rb index 1386438101..7ceb23ef2e 100644 --- a/test/unit/permissions/policy_based_auth_test.rb +++ b/test/unit/permissions/policy_based_auth_test.rb @@ -204,6 +204,8 @@ class PolicyBasedAuthTest < ActiveSupport::TestCase sop.update_lookup_table(user) assert_equal 1, Sop.lookup_count_for_user(user.id) assert sop.destroy + assert_equal 1, Sop.lookup_count_for_user(user.id) + AuthLookupDeleteJob.perform_now('Sop', sop.id) assert_equal 0, Sop.lookup_count_for_user(user.id) end end From 7888d4333420b506664b2d2fd9898251ee3a1b27 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 16 Feb 2024 15:48:13 +0000 Subject: [PATCH 181/344] optimise the query to delete by asset_id, but leave by user_id as it is #1628 whilst profiling, but asset_id gave a significant improvement (9s to 0.1s), but by user_id was actually slower due to the indexing --- app/jobs/auth_lookup_delete_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/auth_lookup_delete_job.rb b/app/jobs/auth_lookup_delete_job.rb index 9eae301569..d8cc3d6010 100644 --- a/app/jobs/auth_lookup_delete_job.rb +++ b/app/jobs/auth_lookup_delete_job.rb @@ -9,7 +9,7 @@ def perform(item_class, item_id) type.lookup_class.where(user: item_id).in_batches(of: BATCH_SIZE).delete_all end else - item_class.constantize.lookup_class.where(asset_id: item_id).in_batches(of: BATCH_SIZE).delete_all + item_class.constantize.lookup_class.where(asset_id: item_id).in_batches(of: 100000, order: :desc) { |r| r.delete_all } end end end From c5ff4ff5b6027e938cac8122a576cbd7a53fa9b3 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 20 Feb 2024 10:40:14 +0000 Subject: [PATCH 182/344] adjusted the batch sizes #1628 adjusted the separate batch sizes according to if by asset or user, chosen through testing and profiling --- app/jobs/auth_lookup_delete_job.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/jobs/auth_lookup_delete_job.rb b/app/jobs/auth_lookup_delete_job.rb index d8cc3d6010..45e4b08aa3 100644 --- a/app/jobs/auth_lookup_delete_job.rb +++ b/app/jobs/auth_lookup_delete_job.rb @@ -1,15 +1,18 @@ class AuthLookupDeleteJob < ApplicationJob - BATCH_SIZE = 1000 + # optimum batch size is different according the queries due to different indexes and ordering + USER_BATCH_SIZE=500 + ASSET_BATCH_SIZE=10000 + queue_as QueueNames::AUTH_LOOKUP queue_with_priority 2 def perform(item_class, item_id) if item_class == 'User' Seek::Util.authorized_types.each do |type| - type.lookup_class.where(user: item_id).in_batches(of: BATCH_SIZE).delete_all + type.lookup_class.where(user: item_id).in_batches(of: USER_BATCH_SIZE).delete_all end else - item_class.constantize.lookup_class.where(asset_id: item_id).in_batches(of: 100000, order: :desc) { |r| r.delete_all } + item_class.constantize.lookup_class.where(asset_id: item_id).in_batches(of: ASSET_BATCH_SIZE, order: :desc) { |r| r.delete_all } end end end From 51b3223086c2a9726012aa1a4946fb63468cc20d Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 20 Feb 2024 16:00:23 +0000 Subject: [PATCH 183/344] a job to delete a batch of samples #1628 triggered for the extracted sample linked to a data file when requested to be deleted. Also ensures only the required SampleTypeUpdateJob's are created --- app/controllers/data_files_controller.rb | 4 +- app/jobs/samples_batch_delete_job.rb | 33 +++++++++++ .../jobs/samples_batch_delete_job_test.rb | 56 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 app/jobs/samples_batch_delete_job.rb create mode 100644 test/unit/jobs/samples_batch_delete_job_test.rb diff --git a/app/controllers/data_files_controller.rb b/app/controllers/data_files_controller.rb index a835d35df6..a420f1f745 100644 --- a/app/controllers/data_files_controller.rb +++ b/app/controllers/data_files_controller.rb @@ -38,7 +38,9 @@ def destroy redirect_to destroy_samples_confirm_data_file_path(@data_file) else if params[:destroy_extracted_samples] == '1' - @data_file.extracted_samples.destroy_all + @data_file.extracted_samples_ids.each_slice(500) do |ids| + SamplesBatchDeleteJob.perform_later(ids) + end end super end diff --git a/app/jobs/samples_batch_delete_job.rb b/app/jobs/samples_batch_delete_job.rb new file mode 100644 index 0000000000..b0a44639c6 --- /dev/null +++ b/app/jobs/samples_batch_delete_job.rb @@ -0,0 +1,33 @@ +class SamplesBatchDeleteJob < ApplicationJob + queue_as QueueNames::SAMPLES + queue_with_priority 2 + + def perform(sample_ids) + Sample.skip_callback :destroy, :after, :queue_sample_type_update_job + @sample_types = [] + sample_ids.each_slice(500) do |ids| + samples = Sample.where(id: ids).includes(:sample_type) + collect_sample_types(samples) + disable_authorization_checks do + Sample.where(id: ids).destroy_all + end + end + ensure + Sample.set_callback :destroy, :after, :queue_sample_type_update_job + create_sample_type_update_jobs + end + + private + + def collect_sample_types(samples) + @sample_types |= samples.collect(&:sample_type).uniq + end + + def create_sample_type_update_jobs + return unless @sample_types.present? + + @sample_types.each do |type| + SampleTypeUpdateJob.perform_later(type, false) + end + end +end diff --git a/test/unit/jobs/samples_batch_delete_job_test.rb b/test/unit/jobs/samples_batch_delete_job_test.rb new file mode 100644 index 0000000000..179c50de42 --- /dev/null +++ b/test/unit/jobs/samples_batch_delete_job_test.rb @@ -0,0 +1,56 @@ +require 'test_helper' + +class SamplesBatchDeleteJobTest < ActiveSupport::TestCase + + test 'perform' do + person = FactoryBot.create(:person) + sample_type = FactoryBot.create(:simple_sample_type) + samples = FactoryBot.create_list(:sample, 3, contributor: person, sample_type: sample_type) + + # to check it handles no permission + refute samples.first.can_delete? + + assert_difference('Sample.count', -3 ) do + assert_enqueued_with(job: SampleTypeUpdateJob, args: [sample_type, false]) do + assert_enqueued_jobs(1, only: SampleTypeUpdateJob) do + assert_enqueued_with(job: AuthLookupDeleteJob, args: ['Sample', samples.first.id]) do + assert_enqueued_jobs(3, only: AuthLookupDeleteJob) do + SamplesBatchDeleteJob.perform_now(samples.collect(&:id)) + end + end + end + end + end + + end + + test 'perform handles non existent ids' do + id = (Sample.maximum(:id) || 0) + 1 + assert_empty Sample.where(id: id) + + assert_nothing_raised do + assert_no_difference('Sample.count' ) do + SamplesBatchDeleteJob.perform_now([id]) + end + end + + end + + test 'mixture of sample types' do + person = FactoryBot.create(:person) + sample_type = FactoryBot.create(:simple_sample_type) + sample_type2 = FactoryBot.create(:simple_sample_type) + samples = [FactoryBot.create(:sample, contributor: person, sample_type: sample_type), FactoryBot.create(:sample, contributor: person, sample_type: sample_type2)] + + assert_difference('Sample.count', -2 ) do + assert_enqueued_with(job: SampleTypeUpdateJob, args: [sample_type, false]) do + assert_enqueued_with(job: SampleTypeUpdateJob, args: [sample_type2, false]) do + assert_enqueued_jobs(2, only: SampleTypeUpdateJob) do + SamplesBatchDeleteJob.perform_now(samples.collect(&:id)) + end + end + end + end + end + +end \ No newline at end of file From 7f2bc8f9b01f77aa2248bde1828a85a19dec08a2 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 20 Feb 2024 17:03:10 +0000 Subject: [PATCH 184/344] test updates #1628 --- app/controllers/data_files_controller.rb | 2 +- test/functional/data_files_controller_test.rb | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/controllers/data_files_controller.rb b/app/controllers/data_files_controller.rb index a420f1f745..d42906f834 100644 --- a/app/controllers/data_files_controller.rb +++ b/app/controllers/data_files_controller.rb @@ -38,7 +38,7 @@ def destroy redirect_to destroy_samples_confirm_data_file_path(@data_file) else if params[:destroy_extracted_samples] == '1' - @data_file.extracted_samples_ids.each_slice(500) do |ids| + @data_file.extracted_sample_ids.each_slice(500) do |ids| SamplesBatchDeleteJob.perform_later(ids) end end diff --git a/test/functional/data_files_controller_test.rb b/test/functional/data_files_controller_test.rb index bdc34c30cb..5d5a242b0b 100644 --- a/test/functional/data_files_controller_test.rb +++ b/test/functional/data_files_controller_test.rb @@ -2833,6 +2833,7 @@ def test_show_item_attributed_to_jerm_file df = data_file_with_extracted_samples sample = df.extracted_samples.first + sample_ids = df.extracted_sample_ids assert_no_difference('DataFile.count') do delete :destroy, params: { id: df.id } @@ -2840,11 +2841,13 @@ def test_show_item_attributed_to_jerm_file assert_redirected_to destroy_samples_confirm_data_file_path(df) assert_difference('DataFile.count', -1) do - assert_difference('Sample.count', -4) do - assert_enqueued_jobs(5, only: AuthLookupDeleteJob) do + assert_no_difference('Sample.count') do + assert_enqueued_jobs(1, only: AuthLookupDeleteJob) do assert_enqueued_with(job: AuthLookupDeleteJob, args: ['DataFile', df.id]) do - assert_enqueued_with(job: AuthLookupDeleteJob, args: ['Sample', sample.id]) do - delete :destroy, params: { id: df.id, destroy_extracted_samples: '1' } + assert_enqueued_jobs(1, only: SamplesBatchDeleteJob) do + assert_enqueued_with(job: SamplesBatchDeleteJob, args: [sample_ids]) do + delete :destroy, params: { id: df.id, destroy_extracted_samples: '1' } + end end end end @@ -2869,7 +2872,9 @@ def test_show_item_attributed_to_jerm_file assert_no_difference('Sample.count') do assert_enqueued_jobs(1, only: AuthLookupDeleteJob) do assert_enqueued_with(job: AuthLookupDeleteJob, args: ['DataFile', df.id]) do - delete :destroy, params: { id: df.id, destroy_extracted_samples: '0' } + assert_no_enqueued_jobs(only: SamplesBatchDeleteJob) do + delete :destroy, params: { id: df.id, destroy_extracted_samples: '0' } + end end end end From c4c00d0ec085cafd26890871225afaf19b33c83f Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 28 Feb 2024 09:55:27 +0000 Subject: [PATCH 185/344] fix test susceptible to random failures --- test/unit/jobs/auth_lookup_delete_job_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/jobs/auth_lookup_delete_job_test.rb b/test/unit/jobs/auth_lookup_delete_job_test.rb index 58b1bf8c17..afb19473e7 100644 --- a/test/unit/jobs/auth_lookup_delete_job_test.rb +++ b/test/unit/jobs/auth_lookup_delete_job_test.rb @@ -40,7 +40,7 @@ class AuthLookupDeleteJobTest < ActiveSupport::TestCase test 'perform when asset record no longer exists' do assert_nothing_raised do assert_no_difference('DataFile::AuthLookup.count') do - AuthLookupDeleteJob.perform_now('DataFile', DataFile.maximum(:id) + 10) + AuthLookupDeleteJob.perform_now('DataFile', (DataFile.maximum(:id) || 0) + 10) end end end @@ -48,7 +48,7 @@ class AuthLookupDeleteJobTest < ActiveSupport::TestCase test 'perform when user record no longer exists' do assert_nothing_raised do assert_no_difference('DataFile::AuthLookup.count') do - AuthLookupDeleteJob.perform_now('User', User.maximum(:id) + 10) + AuthLookupDeleteJob.perform_now('User', (User.maximum(:id) || 0) + 10) end end end From 9c84d2761b6278cbf2a5a7dcc7c601e50191fe5c Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 4 Jan 2024 17:01:12 +0000 Subject: [PATCH 186/344] Add nf-core scraper --- app/models/git_workflow_wizard.rb | 10 +- lib/scrapers/github_scraper.rb | 41 +- lib/scrapers/nfcore_scraper.rb | 38 + lib/tasks/scrapers.rake | 10 + test/factories/git.rb | 7 + .../files/mocking/nfcore_pipelines_trunc.json | 1658 +++++++++++++++++ test/integration/nfcore_scraper_test.rb | 118 ++ 7 files changed, 1864 insertions(+), 18 deletions(-) create mode 100644 lib/scrapers/nfcore_scraper.rb create mode 100644 test/fixtures/files/mocking/nfcore_pipelines_trunc.json create mode 100644 test/integration/nfcore_scraper_test.rb diff --git a/app/models/git_workflow_wizard.rb b/app/models/git_workflow_wizard.rb index 4ae82bdbec..7ab39547fa 100644 --- a/app/models/git_workflow_wizard.rb +++ b/app/models/git_workflow_wizard.rb @@ -8,14 +8,14 @@ class GitWorkflowWizard include ActiveModel::Model - attr_reader :next_step, :workflow_class, :git_repository + attr_reader :next_step, :git_repository - attr_accessor :params, :workflow + attr_accessor :params, :workflow, :workflow_class def run if new_version? @next_step = :new_git_version - workflow_class = workflow.workflow_class + self.workflow_class = workflow.workflow_class current_version = workflow.git_version git_version = workflow.git_versions.build(params.delete(:git_version_attributes)) unless git_version.remote? # It's a new local version, so just use next_version and finish the wizard @@ -27,7 +27,7 @@ def run end else @next_step = :new - self.workflow = Workflow.new + self.workflow = Workflow.new(workflow_class: self.workflow_class) current_version = nil end @@ -61,7 +61,7 @@ def run git_version.abstract_cwl_path ||= crate.main_workflow&.cwl_description&.id if crate.main_workflow&.cwl_description&.id git_version.diagram_path ||= crate.main_workflow&.diagram&.id if crate.main_workflow&.diagram&.id - workflow_class ||= WorkflowClass.match_from_metadata(crate&.main_workflow&.programming_language&.properties || {}) + self.workflow_class ||= WorkflowClass.match_from_metadata(crate&.main_workflow&.programming_language&.properties || {}) end end diff --git a/lib/scrapers/github_scraper.rb b/lib/scrapers/github_scraper.rb index 4c25901986..d95c1c82fc 100644 --- a/lib/scrapers/github_scraper.rb +++ b/lib/scrapers/github_scraper.rb @@ -68,29 +68,22 @@ def existing_resource(repo) def create_resources(repositories) repositories.map do |repo| output.puts " Considering #{repo.remote.chomp('.git')}..." - latest_tag = `cd #{repo.git_base.path}/.. && git describe --tags --abbrev=0 remotes/origin/#{@main_branch}`.chomp - unless $?.success? + tag = latest_tag(repo) + if tag.nil? output.puts " Error while getting latest tag - wrong branch name?" next end - wiz = GitWorkflowWizard.new(params: { - git_version_attributes: { - git_repository_id: repo.id, - ref: "refs/tags/#{latest_tag}", - name: latest_tag, - comment: "Updated to #{latest_tag}" - } - }) + wiz = workflow_wizard(repo, tag) workflow = existing_resource(repo) new_version = false if workflow new_version = true wiz.workflow = workflow - unless workflow.git_versions.none? { |gv| gv.name == latest_tag } - output.puts " Version #{latest_tag} already registered, doing nothing" + unless workflow.git_versions.none? { |gv| gv.name == tag } + output.puts " Version #{tag} already registered, doing nothing" next end - output.puts " New version detected! (#{latest_tag}), creating new version" + output.puts " New version detected! (#{tag}), creating new version" else output.puts " Creating new workflow" end @@ -117,6 +110,21 @@ def create_resources(repositories) end.compact end + def main_branch(repo) + @main_branch + end + + def workflow_wizard(repo, tag) + GitWorkflowWizard.new(params: { + git_version_attributes: { + git_repository_id: repo.id, + ref: "refs/tags/#{tag}", + name: tag, + comment: "Updated to #{tag}" + } + }) + end + # Get all the repositories in the given org from the GitHub API. def list_repositories JSON.parse(github["users/#{@organization}/repos?sort=updated&direction=desc"].get.body) @@ -148,5 +156,12 @@ def cached(name) File.read(path) end end + + def latest_tag(repo) + tag = `cd #{repo.git_base.path}/.. && git describe --tags --abbrev=0 remotes/origin/#{main_branch(repo)}`.chomp + return nil unless $?.success? + + tag + end end end diff --git a/lib/scrapers/nfcore_scraper.rb b/lib/scrapers/nfcore_scraper.rb new file mode 100644 index 0000000000..1e9869f660 --- /dev/null +++ b/lib/scrapers/nfcore_scraper.rb @@ -0,0 +1,38 @@ +module Scrapers + class NfcoreScraper < GithubScraper + + def initialize(organization, project, contributor, main_branch: 'master', debug: false, output: STDOUT) + super + end + + private + + def list_repositories + repos = JSON.parse(RestClient.get('https://nf-co.re/pipelines.json'))['remote_workflows'] + @nfcore_pipelines = {} # Store repo metadata from pipelines.json to fetch main branch name later + repos.each do |r| + r['clone_url'] = "https://github.com/#{r['full_name']}.git" + @nfcore_pipelines[r['clone_url']] = r + end + + repos + end + + def main_branch(repo) + @nfcore_pipelines.dig(repo.remote, 'default_branch') || super + end + + def workflow_wizard(repo, tag) + GitWorkflowWizard.new(workflow_class: WorkflowClass.find_by_key('nextflow'), + params: { + git_version_attributes: { + main_workflow_path: 'nextflow.config', + git_repository_id: repo.id, + ref: "refs/tags/#{tag}", + name: tag, + comment: "Updated to #{tag}" + } + }) + end + end +end \ No newline at end of file diff --git a/lib/tasks/scrapers.rake b/lib/tasks/scrapers.rake index fc90ea897b..0cbb8e7b42 100644 --- a/lib/tasks/scrapers.rake +++ b/lib/tasks/scrapers.rake @@ -1,6 +1,7 @@ require 'rubygems' require 'rails/all' require 'rake' +require 'json' namespace :seek do namespace :scrape do @@ -12,5 +13,14 @@ namespace :seek do scraper.scrape end + + desc 'Scrape nf-core workflows' + task nfcore: :environment do + project = Scrapers::Util.bot_project(title: 'nf-core') + person = Scrapers::Util.bot_account + scraper = Scrapers::NfcoreScraper.new('nf-core', project, person) + + scraper.scrape + end end end diff --git a/test/factories/git.rb b/test/factories/git.rb index e482e8af34..82547a55ad 100644 --- a/test/factories/git.rb +++ b/test/factories/git.rb @@ -43,6 +43,13 @@ FileUtils.cp_r(File.join(Rails.root, 'test', 'fixtures', 'git', 'nf-core-rnaseq', '_git', '.'), File.join(r.local_path, '.git')) end end + + factory(:nfcore_remote_repository, class: Git::Repository) do + after(:create) do |r| + FileUtils.cp_r(File.join(Rails.root, 'test', 'fixtures', 'git', 'nf-core-rnaseq', '_git', '.'), File.join(r.local_path, '.git')) + end + remote { "https://github.com/nf-core/rnaseq.git" } + end # GitVersions factory(:git_version, class: Git::Version) do diff --git a/test/fixtures/files/mocking/nfcore_pipelines_trunc.json b/test/fixtures/files/mocking/nfcore_pipelines_trunc.json new file mode 100644 index 0000000000..f8866a417b --- /dev/null +++ b/test/fixtures/files/mocking/nfcore_pipelines_trunc.json @@ -0,0 +1,1658 @@ +{ + "remote_workflows": [ + { + "id": 127293091, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjcyOTMwOTE=", + "name": "rnaseq", + "full_name": "nf-core/rnaseq", + "private": false, + "description": "RNA sequencing analysis pipeline using STAR, RSEM, HISAT2 or Salmon with gene/isoform counts and extensive quality control.", + "fork": false, + "url": "https://api.github.com/repos/nf-core/rnaseq", + "created_at": "2018-03-29T13:10:58Z", + "updated_at": "2024-01-04T13:18:56Z", + "pushed_at": "2024-01-04T12:54:23Z", + "homepage": "https://nf-co.re/rnaseq", + "size": 66190, + "stargazers_count": 713, + "watchers_count": 713, + "language": "Nextflow", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 623, + "archived": false, + "disabled": false, + "open_issues_count": 56, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "rna", + "rna-seq" + ], + "visibility": "public", + "forks": 623, + "open_issues": 56, + "watchers": 713, + "default_branch": "master", + "network_count": 623, + "subscribers_count": 135, + "open_pr_count": 8, + "contributors": [ + { + "name": "drpatelh", + "count": 2041, + "avatar_url": "https://avatars.githubusercontent.com/u/23529759?v=4" + }, + { + "name": "ewels", + "count": 633, + "avatar_url": "https://avatars.githubusercontent.com/u/465550?v=4" + }, + { + "name": "apeltzer", + "count": 301, + "avatar_url": "https://avatars.githubusercontent.com/u/2359510?v=4" + }, + { + "name": "olgabot", + "count": 174, + "avatar_url": "https://avatars.githubusercontent.com/u/806256?v=4" + }, + { + "name": "pinin4fjords", + "count": 165, + "avatar_url": "https://avatars.githubusercontent.com/u/5775915?v=4" + }, + { + "name": "grst", + "count": 71, + "avatar_url": "https://avatars.githubusercontent.com/u/7051479?v=4" + }, + { + "name": "maxulysse", + "count": 61, + "avatar_url": "https://avatars.githubusercontent.com/u/1019628?v=4" + }, + { + "name": "Galithil", + "count": 54, + "avatar_url": "https://avatars.githubusercontent.com/u/7523280?v=4" + }, + { + "name": "pranathivemuri", + "count": 51, + "avatar_url": "https://avatars.githubusercontent.com/u/10284094?v=4" + }, + { + "name": "silviamorins", + "count": 36, + "avatar_url": "https://avatars.githubusercontent.com/u/35500664?v=4" + }, + { + "name": "lpantano", + "count": 33, + "avatar_url": "https://avatars.githubusercontent.com/u/1621788?v=4" + }, + { + "name": "mahesh-panchal", + "count": 32, + "avatar_url": "https://avatars.githubusercontent.com/u/15652539?v=4" + }, + { + "name": "MatthiasZepper", + "count": 30, + "avatar_url": "https://avatars.githubusercontent.com/u/6963520?v=4" + }, + { + "name": "robsyme", + "count": 25, + "avatar_url": "https://avatars.githubusercontent.com/u/31576?v=4" + }, + { + "name": "macroscian", + "count": 23, + "avatar_url": "https://avatars.githubusercontent.com/u/1587213?v=4" + }, + { + "name": "FriederikeHanssen", + "count": 18, + "avatar_url": "https://avatars.githubusercontent.com/u/12273093?v=4" + }, + { + "name": "adamrtalbot", + "count": 16, + "avatar_url": "https://avatars.githubusercontent.com/u/12817534?v=4" + }, + { + "name": "jfy133", + "count": 15, + "avatar_url": "https://avatars.githubusercontent.com/u/17950287?v=4" + }, + { + "name": "JoseEspinosa", + "count": 12, + "avatar_url": "https://avatars.githubusercontent.com/u/6224346?v=4" + }, + { + "name": "rfenouil", + "count": 12, + "avatar_url": "https://avatars.githubusercontent.com/u/35697321?v=4" + }, + { + "name": "chris-cheshire", + "count": 12, + "avatar_url": "https://avatars.githubusercontent.com/u/2357039?v=4" + }, + { + "name": "marchoeppner", + "count": 10, + "avatar_url": "https://avatars.githubusercontent.com/u/22975?v=4" + }, + { + "name": "edmundmiller", + "count": 9, + "avatar_url": "https://avatars.githubusercontent.com/u/20095261?v=4" + }, + { + "name": "orionzhou", + "count": 9, + "avatar_url": "https://avatars.githubusercontent.com/u/332126?v=4" + }, + { + "name": "G-Sarah", + "count": 9, + "avatar_url": "https://avatars.githubusercontent.com/u/99276764?v=4" + }, + { + "name": "ggabernet", + "count": 8, + "avatar_url": "https://avatars.githubusercontent.com/u/3973503?v=4" + }, + { + "name": "c-mertes", + "count": 6, + "avatar_url": "https://avatars.githubusercontent.com/u/22544249?v=4" + }, + { + "name": "d4straub", + "count": 6, + "avatar_url": "https://avatars.githubusercontent.com/u/42973691?v=4" + }, + { + "name": "pditommaso", + "count": 6, + "avatar_url": "https://avatars.githubusercontent.com/u/816968?v=4" + }, + { + "name": "senthil10", + "count": 5, + "avatar_url": "https://avatars.githubusercontent.com/u/5311007?v=4" + }, + { + "name": "george-hall-ucl", + "count": 5, + "avatar_url": "https://avatars.githubusercontent.com/u/77325372?v=4" + }, + { + "name": "sven1103", + "count": 5, + "avatar_url": "https://avatars.githubusercontent.com/u/9976560?v=4" + }, + { + "name": "drejom", + "count": 4, + "avatar_url": "https://avatars.githubusercontent.com/u/91294?v=4" + }, + { + "name": "jun-wan", + "count": 4, + "avatar_url": "https://avatars.githubusercontent.com/u/5436208?v=4" + }, + { + "name": "mashehu", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/6169021?v=4" + }, + { + "name": "alneberg", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/1250075?v=4" + }, + { + "name": "aanil", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/11080857?v=4" + }, + { + "name": "SpikyClip", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/74215773?v=4" + }, + { + "name": "sofiahag", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/37932118?v=4" + }, + { + "name": "RaqManzano", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/36073691?v=4" + }, + { + "name": "kviljoen", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/9252016?v=4" + }, + { + "name": "amayer21", + "count": 3, + "avatar_url": "https://avatars.githubusercontent.com/u/31933289?v=4" + }, + { + "name": "ryanyord", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/85899130?v=4" + }, + { + "name": "jemten", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/3168925?v=4" + }, + { + "name": "arontommi", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/15101751?v=4" + }, + { + "name": "zxl124", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/37191025?v=4" + }, + { + "name": "suhrig", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/10560654?v=4" + }, + { + "name": "skrakau", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/1982769?v=4" + }, + { + "name": "RHReynolds", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/31013971?v=4" + }, + { + "name": "ppericard", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/7090732?v=4" + }, + { + "name": "paulklemm", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/2169489?v=4" + }, + { + "name": "pcantalupo", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/310962?v=4" + }, + { + "name": "veeravalli", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/9376769?v=4" + }, + { + "name": "jordwil", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/3279797?v=4" + }, + { + "name": "jburos", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/923453?v=4" + }, + { + "name": "colindaven", + "count": 2, + "avatar_url": "https://avatars.githubusercontent.com/u/6094884?v=4" + }, + { + "name": "na399", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/13469652?v=4" + }, + { + "name": "mvanins", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/8451093?v=4" + }, + { + "name": "rsuchecki", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/5534231?v=4" + }, + { + "name": "rhr-cosyne", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/116022680?v=4" + }, + { + "name": "smoe", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/207407?v=4" + }, + { + "name": "tomsing1", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/10490984?v=4" + }, + { + "name": "adf-ncgr", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/2009605?v=4" + }, + { + "name": "ctuni", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/49684557?v=4" + }, + { + "name": "hmehlan", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/43400079?v=4" + }, + { + "name": "KevinMenden", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/15126788?v=4" + }, + { + "name": "matrulda", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/1584541?v=4" + }, + { + "name": "lbeltrame", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/25192?v=4" + }, + { + "name": "jlorent", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/5382593?v=4" + }, + { + "name": "jenmuell", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/43847598?v=4" + }, + { + "name": "Ghepardo", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/71766441?v=4" + }, + { + "name": "vezzi", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/1209695?v=4" + }, + { + "name": "drpowell", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/123456?v=4" + }, + { + "name": "chuan-wang", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/18303540?v=4" + }, + { + "name": "adomingues", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/2607577?v=4" + }, + { + "name": "abhi18av", + "count": 1, + "avatar_url": "https://avatars.githubusercontent.com/u/12799326?v=4" + } + ], + "releases": [ + { + "tag_name": "3.13.2", + "published_at": "2023-11-21T11:36:49Z", + "tag_sha": "a10f41afa204538d5dcc89a5910c299d68f94f41", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "kallisto_index", + "kallisto_quant", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.13.1", + "published_at": "2023-11-17T18:04:37Z", + "tag_sha": "b59e87a54eae60e02f9ae12e3b9c9c59959328d7", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "kallisto_index", + "kallisto_quant", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.13.0", + "published_at": "2023-11-17T15:29:38Z", + "tag_sha": "14f9d26444e08da7b51ddcb1b8c4e0703edde375", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "kallisto_index", + "kallisto_quant", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.12.0", + "published_at": "2023-06-02T15:39:54Z", + "tag_sha": "3bec2331cac2b5ff88a1dc71a21fab6529b57a0f", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.11.2", + "published_at": "2023-04-25T10:56:51Z", + "tag_sha": "5671b65af97fe78a2f9b4d05d850304918b1b86e", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.11.1", + "published_at": "2023-03-31T16:06:30Z", + "tag_sha": "287afcfe30a93de77e9b7cf70a1085f58c9525d8", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.11.0", + "published_at": "2023-03-30T13:52:38Z", + "tag_sha": "48fb9b4ea640f029f48c79283217d0f20661d38e", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.10.1", + "published_at": "2023-01-05T12:14:44Z", + "tag_sha": "6e1e448f535ccf34d11cc691bb241cfd6e60a647", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.10", + "published_at": "2022-12-21T13:49:31Z", + "tag_sha": "adce7ce9abc8f6b338b4f53d0d988ff9a0fd52ff", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + }, + { + "tag_name": "3.9", + "published_at": "2022-09-30T20:24:37Z", + "tag_sha": "e049f51f0214b2aef7624b9dd496a404a7c34d14", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.8.1", + "published_at": "2022-05-27T16:34:40Z", + "tag_sha": "89bf536ce4faa98b4d50a8ec0a0343780bc62e0a", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.8", + "published_at": "2022-05-25T09:45:00Z", + "tag_sha": "6995330476244a6bffe55ddcbe50b8ed5cf6c2e2", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.7", + "published_at": "2022-05-03T11:13:51Z", + "tag_sha": "e0dfce9af5c2299bcc2b8a74b6559ce055965455", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.6", + "published_at": "2022-03-04T13:46:29Z", + "tag_sha": "7106bd792b3fb04f9f09b4e737165fa4e736ea81", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.5", + "published_at": "2021-12-17T16:08:03Z", + "tag_sha": "646723c70f04ee6d66391758b02822d4f0fe2966", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.4", + "published_at": "2021-10-05T13:46:22Z", + "tag_sha": "964425e3fd8bfc3dc7bce43279a98d17a874d3f7", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.3", + "published_at": "2021-07-29T13:24:52Z", + "tag_sha": "8094c42add6dcdf69ce54dfdec957789c37ae903", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "cat_fastq", + "fastqc", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ] + } + }, + { + "tag_name": "3.2", + "published_at": "2021-06-18T13:59:37Z", + "tag_sha": "b3ff92bc54363faf17d820689a8e9074ffd99045", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "3.1", + "published_at": "2021-05-13T13:02:56Z", + "tag_sha": "0fcbb0ac491ecb8a80ef879c4f3dad5f869021f9", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "3.0", + "published_at": "2020-12-15T16:22:27Z", + "tag_sha": "3643a94411b65f42bce5357c5015603099556ad9", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "2.0", + "published_at": "2020-11-12T20:01:34Z", + "tag_sha": "bc5fc76f40b2da6082a854927184c9d6e5060393", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "1.4.2", + "published_at": "2019-10-18T10:51:00Z", + "tag_sha": "3b6df9bd104927298fcdf69e97cca7ff1f80527c", + "has_schema": false, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "1.4.1", + "published_at": "2019-10-17T17:40:46Z", + "tag_sha": "f213557a0851d3f897efb1faeb3e54e8eee1424c", + "has_schema": false, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "1.4", + "published_at": "2019-10-15T13:11:57Z", + "tag_sha": "55eee0c01c36db013d1dcbe6a4e65b407c358f0f", + "has_schema": false, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "1.3", + "published_at": "2019-03-26T20:04:29Z", + "tag_sha": "37f260d360e59df7166cfd60e2b3c9a3999adf75", + "has_schema": false, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "1.2", + "published_at": "2018-12-12T16:44:28Z", + "tag_sha": "e8376373da8c1dfb0cae5372947cf5b241056076", + "has_schema": false, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "1.1", + "published_at": "2018-10-05T22:20:27Z", + "tag_sha": "1cd5ab733a35727b01370de2ae8462d65f57fbb9", + "has_schema": false, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "1.0", + "published_at": "2018-08-20T16:39:10Z", + "tag_sha": "44f1525d7adfa3a6afcafdcf7730eca78a1a2511", + "has_schema": false, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ] + }, + { + "tag_name": "dev", + "published_at": "2024-01-04T11:18:39Z", + "tag_sha": "63df1d7fd7f25bc9d65400ed7fabe3d6b734e690", + "has_schema": true, + "doc_files": [ + "docs/output.md", + "docs/usage.md" + ], + "components": { + "modules": [ + "bbmap_bbsplit", + "cat_fastq", + "custom_dumpsoftwareversions", + "custom_getchromsizes", + "fastp", + "fastqc", + "fq_subsample", + "gffread", + "gunzip", + "hisat2_align", + "hisat2_build", + "hisat2_extractsplicesites", + "kallisto_index", + "kallisto_quant", + "picard_markduplicates", + "preseq_lcextrap", + "qualimap_rnaseq", + "rsem_calculateexpression", + "rsem_preparereference", + "rseqc_bamstat", + "rseqc_inferexperiment", + "rseqc_innerdistance", + "rseqc_junctionannotation", + "rseqc_junctionsaturation", + "rseqc_readdistribution", + "rseqc_readduplication", + "rseqc_tin", + "salmon_index", + "salmon_quant", + "samtools_flagstat", + "samtools_idxstats", + "samtools_index", + "samtools_sort", + "samtools_stats", + "sortmerna", + "star_align", + "star_genomegenerate", + "stringtie_stringtie", + "subread_featurecounts", + "trimgalore", + "ucsc_bedclip", + "ucsc_bedgraphtobigwig", + "umitools_dedup", + "umitools_extract", + "untar" + ], + "subworkflows": [ + "bam_dedup_stats_samtools_umitools", + "bam_markduplicates_picard", + "bam_rseqc", + "bam_sort_stats_samtools", + "bam_stats_samtools", + "bedgraph_bedclip_bedgraphtobigwig", + "fastq_align_hisat2", + "fastq_fastqc_umitools_fastp", + "fastq_fastqc_umitools_trimgalore", + "fastq_subsample_fq_salmon" + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/integration/nfcore_scraper_test.rb b/test/integration/nfcore_scraper_test.rb new file mode 100644 index 0000000000..357a321ac8 --- /dev/null +++ b/test/integration/nfcore_scraper_test.rb @@ -0,0 +1,118 @@ +require 'test_helper' +require 'minitest/mock' + +class NfcoreScraperTest < ActionDispatch::IntegrationTest + setup do + stub_request(:get, 'https://nf-co.re/pipelines.json') + .to_return(body: File.new("#{Rails.root}/test/fixtures/files/mocking/nfcore_pipelines_trunc.json"), status: 200) + FactoryBot.create(:nextflow_workflow_class) + end + + test 'can scrape a new workflow' do + project = Scrapers::Util.bot_project(title: 'test') + bot = Scrapers::Util.bot_account + scraper = Scrapers::NfcoreScraper.new('test-123', project, bot, output: StringIO.new) + + repos = [FactoryBot.create(:nfcore_remote_repository)] + + scraper.stub(:clone_repositories, -> (_) { repos }) do + assert_difference('Workflow.count', 1) do + assert_difference('Workflow::Git::Version.count', 1) do + assert_difference('Git::Annotation.count', 1) do + assert_no_difference('Git::Repository.count') do + scraped = scraper.scrape + wf = scraped.first + assert_equal bot, wf.contributor + assert_equal [project], wf.projects + assert_equal 'nf-core/rnaseq', wf.title + assert_equal 'Nextflow RNA-Seq analysis pipeline, part of the nf-core community.', wf.description + assert_equal 'MIT', wf.license + assert_equal 'nextflow.config', wf.main_workflow_path + assert_equal '3.0', wf.git_version.name + end + end + end + end + end + end + + test 'can scrape a new version of a workflow' do + project = Scrapers::Util.bot_project(title: 'test') + bot = Scrapers::Util.bot_account + scraper = Scrapers::NfcoreScraper.new('test-123', project, bot, output: StringIO.new) + + repos = [FactoryBot.create(:nfcore_remote_repository)] + + existing = FactoryBot.create(:nfcore_git_workflow, + contributor: bot, + projects: [project], + source_link_url: 'https://github.com/nf-core/rnaseq', + git_version_attributes: { name: '2.0', + git_repository_id: repos.first.id, + ref: 'refs/tags/2.0', + commit: 'bc5fc76', + main_workflow_path: 'nextflow.config', + mutable: false }) + + scraper.stub(:clone_repositories, -> (_) { repos }) do + assert_no_difference('Workflow.count') do + assert_difference('Workflow::Git::Version.count', 1) do + assert_difference('Git::Annotation.count', 1) do + assert_no_difference('Git::Repository.count') do + scraped = scraper.scrape + wf = scraped.first + assert_equal 2, existing.reload.git_versions.count + assert_equal existing, wf + assert_equal bot, wf.contributor + assert_equal [project], wf.projects + assert_equal 'nf-core/rnaseq', wf.title + assert_equal 'Nextflow RNA-Seq analysis pipeline, part of the nf-core community.', wf.description + assert_equal 'MIT', wf.license + assert_equal 'nextflow.config', wf.main_workflow_path + assert_equal '3.0', wf.git_version.name + end + end + end + end + end + end + + test 'does not register duplicates' do + project = Scrapers::Util.bot_project(title: 'test') + bot = Scrapers::Util.bot_account + scraper = Scrapers::NfcoreScraper.new('test-123', project, bot, output: StringIO.new) + + repos = [FactoryBot.create(:nfcore_remote_repository)] + + existing = FactoryBot.create(:nfcore_git_workflow, + contributor: bot, + projects: [project], + source_link_url: 'https://github.com/nf-core/rnaseq', + git_version_attributes: { name: '3.0', + git_repository_id: repos.first.id, + ref: 'refs/tags/3.0', + commit: '3643a94', + main_workflow_path: 'nextflow.config', + mutable: false }) + + scraper.stub(:clone_repositories, -> (_) { repos }) do + assert_no_difference('Workflow.count') do + assert_no_difference('Workflow::Git::Version.count') do + assert_no_difference('Git::Annotation.count') do + assert_no_difference('Git::Repository.count') do + scraped = scraper.scrape + assert scraped.empty? + end + end + end + end + end + end + + private + + def login_as(user) + User.current_user = user + post '/session', params: { login: user.login, password: generate_user_password } + end +end \ No newline at end of file From 812607fea0a252ba3f475f7afb93cd923a4e2ba5 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 15 Jan 2024 18:59:00 +0000 Subject: [PATCH 187/344] Add option to scrape all tags, not just latest --- lib/scrapers/github_scraper.rb | 80 +++++++++++++++---------- lib/scrapers/nfcore_scraper.rb | 4 -- test/integration/nfcore_scraper_test.rb | 38 ++++++++++++ 3 files changed, 85 insertions(+), 37 deletions(-) diff --git a/lib/scrapers/github_scraper.rb b/lib/scrapers/github_scraper.rb index d95c1c82fc..9ebf76b18a 100644 --- a/lib/scrapers/github_scraper.rb +++ b/lib/scrapers/github_scraper.rb @@ -11,7 +11,7 @@ class GithubScraper CRATE_DESTINATION = Rails.root.join('tmp', 'scrapers', 'crates') CACHE_DESTINATION = Rails.root.join('tmp', 'scrapers', 'cache') - def initialize(organization, project, contributor, main_branch: 'master', debug: false, output: STDOUT) + def initialize(organization, project, contributor, main_branch: 'master', debug: false, output: STDOUT, only_latest: true) @organization = organization # The GitHub organization to scrape raise "Missing GitHub organization" unless @organization @project = project # The SEEK project who will own the resources @@ -26,6 +26,7 @@ def initialize(organization, project, contributor, main_branch: 'master', debug: output.puts "Contributor: #{@contributor.title} (ID: #{@contributor.id}, user ID: #{@contributor.user.id})" end @output = output + @only_latest = only_latest end def scrape @@ -68,46 +69,59 @@ def existing_resource(repo) def create_resources(repositories) repositories.map do |repo| output.puts " Considering #{repo.remote.chomp('.git')}..." - tag = latest_tag(repo) - if tag.nil? - output.puts " Error while getting latest tag - wrong branch name?" - next - end - wiz = workflow_wizard(repo, tag) - workflow = existing_resource(repo) - new_version = false - if workflow - new_version = true - wiz.workflow = workflow - unless workflow.git_versions.none? { |gv| gv.name == tag } - output.puts " Version #{tag} already registered, doing nothing" + if @only_latest + tag = latest_tag(repo) + if tag.nil? + output.puts " Error while getting latest tag - wrong branch name?" next end - output.puts " New version detected! (#{tag}), creating new version" + tags = [tag] else - output.puts " Creating new workflow" + tags = repo.remote_refs[:tags]&.map { |t| t[:name] } || [] + if tags.empty? + output.puts " No tags found to register" + next + end end + tags.map { |tag| create_resource(repo, tag) } + end.flatten.compact + end - workflow = wiz.run - workflow.contributor = @contributor - workflow.projects = Array(@project) - workflow.policy = Policy.projects_policy(workflow.projects) - workflow.policy.access_type = Policy::ACCESSIBLE - workflow.source_link_url = repo.remote.chomp('.git') - if wiz.next_step == :provide_metadata - unless @debug - if new_version - workflow.git_version.resource_attributes = workflow.attributes - workflow.git_version.save - end - workflow.save + def create_resource(repo, tag) + wiz = workflow_wizard(repo, tag) + workflow = existing_resource(repo) + new_version = false + if workflow + new_version = true + wiz.workflow = workflow + unless workflow.git_versions.none? { |gv| gv.name == tag } + output.puts " Version #{tag} already registered, doing nothing" + return nil + end + output.puts " New version detected! (#{tag}), creating new version" + else + output.puts " Creating new workflow" + end + + workflow = wiz.run + workflow.contributor = @contributor + workflow.projects = Array(@project) + workflow.policy = Policy.projects_policy(workflow.projects) + workflow.policy.access_type = Policy::ACCESSIBLE + workflow.source_link_url = repo.remote.chomp('.git') + if wiz.next_step == :provide_metadata + unless @debug + if new_version + workflow.git_version.resource_attributes = workflow.attributes + workflow.git_version.save end - else - workflow.valid? + workflow.save end + else + workflow.valid? + end - workflow - end.compact + workflow end def main_branch(repo) diff --git a/lib/scrapers/nfcore_scraper.rb b/lib/scrapers/nfcore_scraper.rb index 1e9869f660..abadedf0cb 100644 --- a/lib/scrapers/nfcore_scraper.rb +++ b/lib/scrapers/nfcore_scraper.rb @@ -1,10 +1,6 @@ module Scrapers class NfcoreScraper < GithubScraper - def initialize(organization, project, contributor, main_branch: 'master', debug: false, output: STDOUT) - super - end - private def list_repositories diff --git a/test/integration/nfcore_scraper_test.rb b/test/integration/nfcore_scraper_test.rb index 357a321ac8..1be7345e8c 100644 --- a/test/integration/nfcore_scraper_test.rb +++ b/test/integration/nfcore_scraper_test.rb @@ -109,6 +109,44 @@ class NfcoreScraperTest < ActionDispatch::IntegrationTest end end + test 'can scrape all tags (and skips duplicates)' do + project = Scrapers::Util.bot_project(title: 'test') + bot = Scrapers::Util.bot_account + scraper = Scrapers::NfcoreScraper.new('test-123', project, bot, output: StringIO.new, only_latest: false) + + repos = [FactoryBot.create(:nfcore_remote_repository)] + # These are the available remote Git tags in the above repo: + tags = ['1.0', '1.1', '1.2', '1.3', '1.4', '1.4.1', '1.4.2', '2.0', '3.0'] + + existing = FactoryBot.create(:nfcore_git_workflow, + contributor: bot, + projects: [project], + source_link_url: 'https://github.com/nf-core/rnaseq', + git_version_attributes: { name: '3.0', + git_repository_id: repos.first.id, + ref: 'refs/tags/3.0', + commit: '3643a94', + main_workflow_path: 'nextflow.config', + mutable: false }) + + assert_equal 1, existing.versions.count + scraper.stub(:clone_repositories, -> (_) { repos }) do + assert_no_difference('Workflow.count') do + assert_difference('Workflow::Git::Version.count', tags.count - 1) do + assert_difference('Git::Annotation.count', tags.count - 1) do + assert_no_difference('Git::Repository.count') do + scraped = scraper.scrape + refute scraped.empty? + + assert_equal tags.count, existing.reload.versions.count + assert_equal tags.sort, existing.versions.map(&:name).sort + end + end + end + end + end + end + private def login_as(user) From 30fc252dd6eab59ebdbfaab6ece1609c474a0d33 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 16 Jan 2024 09:38:09 +0000 Subject: [PATCH 188/344] Update nextflow logo --- .../workflow_types/avatar-nextflow.png | Bin 10838 -> 5970 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/app/assets/images/avatars/workflow_types/avatar-nextflow.png b/app/assets/images/avatars/workflow_types/avatar-nextflow.png index c170f328ff6d4da7bee6160c2ec4775faf0db58e..aba825a681bced717cf4e3b0f8913967cf4baeae 100644 GIT binary patch literal 5970 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 BId=d6 From 968ef4f1cab9fdc1c0837efdfed1be725da95c1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 01:40:34 +0000 Subject: [PATCH 189/344] Bump rails from 6.1.7.6 to 6.1.7.7 Bumps [rails](https://github.com/rails/rails) from 6.1.7.6 to 6.1.7.7. - [Release notes](https://github.com/rails/rails/releases) - [Commits](https://github.com/rails/rails/compare/v6.1.7.6...v6.1.7.7) --- updated-dependencies: - dependency-name: rails dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 126 +++++++++++++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 71ab432279..07c9a2d37e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,40 +51,40 @@ GEM remote: https://rubygems.org/ specs: RedCloth (4.3.3) - actioncable (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + actioncable (6.1.7.7) + actionpack (= 6.1.7.7) + activesupport (= 6.1.7.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionmailbox (6.1.7.7) + actionpack (= 6.1.7.7) + activejob (= 6.1.7.7) + activerecord (= 6.1.7.7) + activestorage (= 6.1.7.7) + activesupport (= 6.1.7.7) mail (>= 2.7.1) - actionmailer (6.1.7.6) - actionpack (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionmailer (6.1.7.7) + actionpack (= 6.1.7.7) + actionview (= 6.1.7.7) + activejob (= 6.1.7.7) + activesupport (= 6.1.7.7) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.6) - actionview (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionpack (6.1.7.7) + actionview (= 6.1.7.7) + activesupport (= 6.1.7.7) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.6) - actionpack (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actiontext (6.1.7.7) + actionpack (= 6.1.7.7) + activerecord (= 6.1.7.7) + activestorage (= 6.1.7.7) + activesupport (= 6.1.7.7) nokogiri (>= 1.8.5) - actionview (6.1.7.6) - activesupport (= 6.1.7.6) + actionview (6.1.7.7) + activesupport (= 6.1.7.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -94,14 +94,14 @@ GEM activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.7.6) - activesupport (= 6.1.7.6) + activejob (6.1.7.7) + activesupport (= 6.1.7.7) globalid (>= 0.3.6) - activemodel (6.1.7.6) - activesupport (= 6.1.7.6) - activerecord (6.1.7.6) - activemodel (= 6.1.7.6) - activesupport (= 6.1.7.6) + activemodel (6.1.7.7) + activesupport (= 6.1.7.7) + activerecord (6.1.7.7) + activemodel (= 6.1.7.7) + activesupport (= 6.1.7.7) activerecord-import (1.3.0) activerecord (>= 4.2) activerecord-session_store (2.0.0) @@ -110,14 +110,14 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 3) railties (>= 5.2.4.1) - activestorage (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activesupport (= 6.1.7.6) + activestorage (6.1.7.7) + actionpack (= 6.1.7.7) + activejob (= 6.1.7.7) + activerecord (= 6.1.7.7) + activesupport (= 6.1.7.7) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.6) + activesupport (6.1.7.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -198,7 +198,7 @@ GEM execjs coffee-script-source (1.12.2) commonmarker (0.23.10) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.3.0) countries (5.2.0) unaccent (~> 0.3) @@ -214,7 +214,7 @@ GEM csl (~> 2.0) daemons (1.1.9) database_cleaner (1.7.0) - date (3.3.3) + date (3.3.4) debug_inspector (1.1.0) delayed_job (4.1.11) activesupport (>= 3.0, < 8.0) @@ -480,15 +480,15 @@ GEM net-http-digest_auth (1.4.1) net-http-persistent (4.0.1) connection_pool (~> 2.2) - net-imap (0.3.7) + net-imap (0.4.10) date net-protocol net-ldap (0.17.1) net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.3.3) + net-smtp (0.4.0.1) net-protocol netrc (0.11.0) nio4r (2.7.0) @@ -567,7 +567,7 @@ GEM nio4r (~> 2.0) pyu-ruby-sasl (0.0.3.3) racc (1.7.3) - rack (2.2.8) + rack (2.2.8.1) rack-attack (6.6.0) rack (>= 1.0, < 3) rack-cors (1.1.1) @@ -584,20 +584,20 @@ GEM rack rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.6) - actioncable (= 6.1.7.6) - actionmailbox (= 6.1.7.6) - actionmailer (= 6.1.7.6) - actionpack (= 6.1.7.6) - actiontext (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activemodel (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + rails (6.1.7.7) + actioncable (= 6.1.7.7) + actionmailbox (= 6.1.7.7) + actionmailer (= 6.1.7.7) + actionpack (= 6.1.7.7) + actiontext (= 6.1.7.7) + actionview (= 6.1.7.7) + activejob (= 6.1.7.7) + activemodel (= 6.1.7.7) + activerecord (= 6.1.7.7) + activestorage (= 6.1.7.7) + activesupport (= 6.1.7.7) bundler (>= 1.15.0) - railties (= 6.1.7.6) + railties (= 6.1.7.7) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -625,15 +625,15 @@ GEM json require_all (~> 3.0) ruby-progressbar - railties (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + railties (6.1.7.7) + actionpack (= 6.1.7.7) + activesupport (= 6.1.7.7) method_source rake (>= 12.2) thor (~> 1.0) rainbow (3.1.1) raindrops (0.20.0) - rake (13.0.6) + rake (13.1.0) ransack (2.5.0) activerecord (>= 5.2.4) activesupport (>= 5.2.4) @@ -886,11 +886,11 @@ GEM terser (1.1.8) execjs (>= 0.3.0, < 3) test-prof (1.0.7) - thor (1.2.2) + thor (1.3.1) tilt (2.0.10) time (0.2.2) date - timeout (0.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) ucf (2.0.2) @@ -951,7 +951,7 @@ GEM rake (>= 0.8.7) yard (0.9.27) webrick (~> 1.7.0) - zeitwerk (2.6.11) + zeitwerk (2.6.13) zip-container (4.0.2) rubyzip (~> 2.0.0) From 000df831e7b25de900c4eb7cd867facf214a338f Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 28 Feb 2024 12:29:59 +0000 Subject: [PATCH 190/344] Remove redundant code --- lib/scrapers/github_scraper.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/scrapers/github_scraper.rb b/lib/scrapers/github_scraper.rb index 9ebf76b18a..7ed04da4c5 100644 --- a/lib/scrapers/github_scraper.rb +++ b/lib/scrapers/github_scraper.rb @@ -7,10 +7,6 @@ module Scrapers class GithubScraper attr_reader :output - GIT_DESTINATION = Rails.root.join('tmp', 'scrapers', 'git') - CRATE_DESTINATION = Rails.root.join('tmp', 'scrapers', 'crates') - CACHE_DESTINATION = Rails.root.join('tmp', 'scrapers', 'cache') - def initialize(organization, project, contributor, main_branch: 'master', debug: false, output: STDOUT, only_latest: true) @organization = organization # The GitHub organization to scrape raise "Missing GitHub organization" unless @organization @@ -157,20 +153,6 @@ def github RestClient::Resource.new('https://api.github.com', {}) end - def cache_path - ::File.join(CACHE_DESTINATION, @organization).tap { |x| FileUtils.mkdir_p(x) } - end - - def cached(name) - path = File.expand_path(File.join(cache_path, name.gsub('/', '-'))) - if File.exist?(path) - File.read(path) - else - File.write(path, yield) - File.read(path) - end - end - def latest_tag(repo) tag = `cd #{repo.git_base.path}/.. && git describe --tags --abbrev=0 remotes/origin/#{main_branch(repo)}`.chomp return nil unless $?.success? From cb3c9ceec7b2407e7db53cce1f297d76d3bee005 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 17 Aug 2022 18:00:35 +0100 Subject: [PATCH 191/344] Allow Galaxy instance to be specified per workflow --- app/controllers/workflows_controller.rb | 2 +- app/helpers/workflows_helper.rb | 2 +- app/models/asset_link.rb | 1 + app/models/concerns/workflow_extraction.rb | 21 ++++++++++++++++++- app/models/workflow.rb | 19 +++++++++++++++++ app/models/workflow_class.rb | 4 ++++ app/views/assets/_asset_buttons.html.erb | 2 +- app/views/workflows/edit.html.erb | 8 +++++++ app/views/workflows/provide_metadata.html.erb | 8 +++++++ config/initializers/seek_testing.rb | 2 ++ lib/seek/config_setting_attributes.yml | 4 ++-- test/functional/workflows_controller_test.rb | 19 +++++++++++++++++ 12 files changed, 86 insertions(+), 6 deletions(-) diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 245e227144..e7b877ce00 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -355,7 +355,7 @@ def workflow_params { creator_ids: [] }, { assay_assets_attributes: [:assay_id] }, { publication_ids: [] }, { presentation_ids: [] }, { document_ids: [] }, { data_file_ids: [] }, { sop_ids: [] }, { workflow_data_files_attributes:[:id, :data_file_id, :workflow_data_file_relationship_id, :_destroy] }, - :internals, :maturity_level, :source_link_url, + :internals, :maturity_level, :source_link_url, :execution_instance, { topic_annotations: [] }, { operation_annotations: [] }, { discussion_links_attributes: [:id, :url, :label, :_destroy] }, { git_version_attributes: [:name, :comment, :ref, :commit, :root_path, diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb index a447ff61e1..bcd354603d 100644 --- a/app/helpers/workflows_helper.rb +++ b/app/helpers/workflows_helper.rb @@ -78,7 +78,7 @@ def test_status_badge(resource) end def run_workflow_url(workflow_version) - if workflow_version.workflow_class_title == 'Galaxy' + if workflow_version.workflow_class&.key == 'galaxy' "#{Seek::Config.galaxy_instance_trs_import_url}&trs_id=#{workflow_version.parent.id}&trs_version=#{workflow_version.version}" end end diff --git a/app/models/asset_link.rb b/app/models/asset_link.rb index 3970f4866c..82b1e37b1b 100644 --- a/app/models/asset_link.rb +++ b/app/models/asset_link.rb @@ -1,6 +1,7 @@ class AssetLink < ApplicationRecord DISCUSSION = 'discussion'.freeze SOURCE = 'source'.freeze + EXECUTION_INSTANCE = 'execution_instance'.freeze MISC_LINKS = 'misc'.freeze scope :discussion, -> { where(link_type: AssetLink::DISCUSSION) } diff --git a/app/models/concerns/workflow_extraction.rb b/app/models/concerns/workflow_extraction.rb index f4c612b952..dd3b38b0cc 100644 --- a/app/models/concerns/workflow_extraction.rb +++ b/app/models/concerns/workflow_extraction.rb @@ -58,7 +58,26 @@ def structure delegate :inputs, :outputs, :steps, to: :structure def can_run? - can_download?(nil) && workflow_class_title == 'Galaxy' && Seek::Config.galaxy_instance_trs_import_url.present? + can_download?(nil) && workflow_class&.key == 'galaxy' && run_url.present? + end + + def run_url + if workflow_class&.key == 'galaxy' + base = execution_instance_url || Seek::Config.galaxy_instance_default + return if base.nil? + + parent_id = is_a_version? ? parent.id : id + url = URI(base) + url.path = '/trs_import' + params = { + trs_server: Seek::Config.galaxy_instance_trs_server, + run_form: true, + trs_id: parent_id, + trs_version: version + } + url.query = URI.encode_www_form(params) + url.to_s + end end def diagram_exists? diff --git a/app/models/workflow.rb b/app/models/workflow.rb index 18bd0ff54b..eede3c2f6a 100644 --- a/app/models/workflow.rb +++ b/app/models/workflow.rb @@ -30,6 +30,9 @@ class Workflow < ApplicationRecord accepts_nested_attributes_for :workflow_data_files + has_one :execution_instance, -> { where(link_type: AssetLink::EXECUTION_INSTANCE) }, + class_name: 'AssetLink', as: :asset, dependent: :destroy, inverse_of: :asset, autosave: true + def initialize(*args) @extraction_errors = [] @extraction_warnings = [] @@ -131,6 +134,10 @@ def source_link_url parent&.source_link&.url end + def execution_instance_url + parent&.execution_instance&.url + end + def submit_to_life_monitor LifeMonitorSubmissionJob.perform_later(self) end @@ -237,6 +244,18 @@ def update_test_status(status, ver = version) v.save! end + def execution_instance_url= url + (execution_instance || build_execution_instance).assign_attributes(url: url) + + execution_instance.mark_for_destruction if url.blank? + + url + end + + def execution_instance_url + execution_instance&.url + end + has_filter maturity: Seek::Filtering::Filter.new( value_field: 'maturity_level', label_mapping: ->(values) { diff --git a/app/models/workflow_class.rb b/app/models/workflow_class.rb index 915b88c320..21507f2575 100644 --- a/app/models/workflow_class.rb +++ b/app/models/workflow_class.rb @@ -20,6 +20,10 @@ def extractable? extractor.present? end + def executable? + key == 'galaxy' + end + def self.extractable where.not(extractor: nil) end diff --git a/app/views/assets/_asset_buttons.html.erb b/app/views/assets/_asset_buttons.html.erb index fead830f7c..9d26ddbc7f 100644 --- a/app/views/assets/_asset_buttons.html.erb +++ b/app/views/assets/_asset_buttons.html.erb @@ -36,7 +36,7 @@ <%= button_link_to('Download RO Crate', 'ro_crate_file', ro_crate_workflow_path(asset, version: version, code: params[:code]), 'data-tooltip' => tooltip("The Workflow RO-Crate is a package containing the workflow definition, its metadata and supporting resources like test data")) %> <% if asset.can_run? %> - <%= button_link_to("Run on #{Seek::Config.galaxy_instance_name || 'Galaxy'}", 'run_galaxy', run_workflow_url(display_asset)) %> + <%= button_link_to("Run on Galaxy", 'run_galaxy', display_asset.run_url) %> <% end %> <% else %> <%= button_link_to('Download RO-Crate', 'ro_crate_file', nil, diff --git a/app/views/workflows/edit.html.erb b/app/views/workflows/edit.html.erb index 060296b5e2..01d7022e83 100644 --- a/app/views/workflows/edit.html.erb +++ b/app/views/workflows/edit.html.erb @@ -18,6 +18,14 @@ <%= f.text_area :description, rows: 5, class: 'form-control rich-text-edit' -%>
    + <% if @workflow.workflow_class&.executable? %> +
    + + <%= f.text_field :execution_instance, placeholder: 'https://usegalaxy.eu', class: 'form-control' -%> +

    The URL to the Galaxy instance where this workflow originated from.

    +
    + <% end %> +
    <%= f.text_field :source_link_url, placeholder: 'https://...', class: 'form-control' -%> diff --git a/app/views/workflows/provide_metadata.html.erb b/app/views/workflows/provide_metadata.html.erb index efa5bf2053..a01b1ad819 100644 --- a/app/views/workflows/provide_metadata.html.erb +++ b/app/views/workflows/provide_metadata.html.erb @@ -45,6 +45,14 @@ <%= text_area_tag 'workflow[description]', @workflow.description, class: "form-control rich-text-edit" -%>
    + <% if @workflow.workflow_class&.executable? %> +
    + + <%= text_field_tag 'workflow[execution_instance]', @workflow.execution_instance, placeholder: 'https://usegalaxy.eu', class: 'form-control' -%> +

    The URL to the Galaxy instance where this workflow originated from.

    +
    + <% end %> +
    <%= text_field_tag 'workflow[source_link_url]', @workflow.source_link_url, placeholder: 'https://...', class: "form-control" -%> diff --git a/config/initializers/seek_testing.rb b/config/initializers/seek_testing.rb index 0d41997c13..4135897e67 100644 --- a/config/initializers/seek_testing.rb +++ b/config/initializers/seek_testing.rb @@ -139,5 +139,7 @@ def load_seek_testing_defaults! Settings.defaults[:git_support_enabled] = true Settings.defaults[:fair_signposting_enabled] = true Settings.defaults[:bio_tools_enabled] = true + Settings.defaults[:galaxy_instance_default] = 'https://usegalaxy.eu' + Settings.defaults[:galaxy_instance_trs_server] = 'testseek.test' end end diff --git a/lib/seek/config_setting_attributes.yml b/lib/seek/config_setting_attributes.yml index 6cff135b3f..a026837c8b 100644 --- a/lib/seek/config_setting_attributes.yml +++ b/lib/seek/config_setting_attributes.yml @@ -240,8 +240,8 @@ life_monitor_url: life_monitor_client_id: life_monitor_client_secret: life_monitor_ui_url: -galaxy_instance_name: -galaxy_instance_trs_import_url: +galaxy_instance_default: +galaxy_instance_trs_server: # Controlled vocabs cv_dropdown_limit: convert: :to_i diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index ce332e0750..0c2fe9e64d 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -1775,4 +1775,23 @@ def bad_generator.write_graph(struct) assert flash[:error].include?('disabled') end end + + test 'shows run button for galaxy workflows using default galaxy endpoint' do + workflow = Factory(:existing_galaxy_ro_crate_workflow, policy: Factory(:public_policy)) + + get :show, params: { id: workflow.id } + + assert workflow.can_run? + assert_equal 'https://usegalaxy.eu', Seek::Config.galaxy_instance_default + assert_select 'a.btn[href=?]', "https://usegalaxy.eu/trs_import?trs_server=testseek.test&run_form=true&trs_id=#{workflow.id}&trs_version=1", { text: 'Run on Galaxy' } + end + + test 'shows run button for galaxy workflows using specified galaxy endpoint' do + workflow = Factory(:existing_galaxy_ro_crate_workflow, policy: Factory(:public_policy), execution_instance_url: 'https://galaxygalaxy.org/') + + get :show, params: { id: workflow.id } + + assert workflow.can_run? + assert_select 'a.btn[href=?]', "https://galaxygalaxy.org/trs_import?trs_server=testseek.test&run_form=true&trs_id=#{workflow.id}&trs_version=1", { text: 'Run on Galaxy' } + end end From c12e9faf33006846127724861b7495f05b1a5136 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 16 May 2023 10:26:08 +0100 Subject: [PATCH 192/344] Update to use direct TRS URL import method in Galaxy --- app/helpers/workflows_helper.rb | 6 ------ app/models/concerns/workflow_extraction.rb | 8 +++----- config/initializers/seek_testing.rb | 1 - config/routes.rb | 2 +- test/functional/workflows_controller_test.rb | 11 ++++++++--- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb index bcd354603d..7136b79d5b 100644 --- a/app/helpers/workflows_helper.rb +++ b/app/helpers/workflows_helper.rb @@ -77,12 +77,6 @@ def test_status_badge(resource) end end - def run_workflow_url(workflow_version) - if workflow_version.workflow_class&.key == 'galaxy' - "#{Seek::Config.galaxy_instance_trs_import_url}&trs_id=#{workflow_version.parent.id}&trs_version=#{workflow_version.version}" - end - end - def workflow_class_options_for_select(selected = nil) opts = WorkflowClass.order(:title).map do |c| extra = {} diff --git a/app/models/concerns/workflow_extraction.rb b/app/models/concerns/workflow_extraction.rb index dd3b38b0cc..f4c37915b3 100644 --- a/app/models/concerns/workflow_extraction.rb +++ b/app/models/concerns/workflow_extraction.rb @@ -58,7 +58,7 @@ def structure delegate :inputs, :outputs, :steps, to: :structure def can_run? - can_download?(nil) && workflow_class&.key == 'galaxy' && run_url.present? + can_download?(nil) && workflow_class&.executable? && run_url.present? end def run_url @@ -70,10 +70,8 @@ def run_url url = URI(base) url.path = '/trs_import' params = { - trs_server: Seek::Config.galaxy_instance_trs_server, - run_form: true, - trs_id: parent_id, - trs_version: version + trs_url: Seek::Util.routes.ga4gh_trs_v2_tool_version_url(parent_id, version_id: version), + run_form: true } url.query = URI.encode_www_form(params) url.to_s diff --git a/config/initializers/seek_testing.rb b/config/initializers/seek_testing.rb index 4135897e67..c2abfb51bb 100644 --- a/config/initializers/seek_testing.rb +++ b/config/initializers/seek_testing.rb @@ -140,6 +140,5 @@ def load_seek_testing_defaults! Settings.defaults[:fair_signposting_enabled] = true Settings.defaults[:bio_tools_enabled] = true Settings.defaults[:galaxy_instance_default] = 'https://usegalaxy.eu' - Settings.defaults[:galaxy_instance_trs_server] = 'testseek.test' end end diff --git a/config/routes.rb b/config/routes.rb index d0a3054d4c..67aa9cd4e9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,7 +16,7 @@ get 'tools' => 'tools#index' get 'tools/:id' => 'tools#show' get 'tools/:id/versions' => 'tool_versions#index' - get 'tools/:id/versions/:version_id' => 'tool_versions#show' + get 'tools/:id/versions/:version_id' => 'tool_versions#show', as: :tool_version get 'tools/:id/versions/:version_id/containerfile' => 'tool_versions#containerfile' get 'tools/:id/versions/:version_id/:type/descriptor(/*relative_path)' => 'tool_versions#descriptor', constraints: { relative_path: /.+/ }, format: false, as: :tool_versions_descriptor get 'tools/:id/versions/:version_id/:type/files' => 'tool_versions#files', format: false diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index 0c2fe9e64d..ef4f5a6cc5 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -1783,15 +1783,20 @@ def bad_generator.write_graph(struct) assert workflow.can_run? assert_equal 'https://usegalaxy.eu', Seek::Config.galaxy_instance_default - assert_select 'a.btn[href=?]', "https://usegalaxy.eu/trs_import?trs_server=testseek.test&run_form=true&trs_id=#{workflow.id}&trs_version=1", { text: 'Run on Galaxy' } + trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") + assert_select 'a.btn[href=?]', "https://usegalaxy.eu/trs_import?trs_url=#{trs_url}&run_form=true", + { text: 'Run on Galaxy' } end test 'shows run button for galaxy workflows using specified galaxy endpoint' do - workflow = Factory(:existing_galaxy_ro_crate_workflow, policy: Factory(:public_policy), execution_instance_url: 'https://galaxygalaxy.org/') + workflow = Factory(:existing_galaxy_ro_crate_workflow, policy: Factory(:public_policy), + execution_instance_url: 'https://galaxygalaxy.org/mygalaxy/') get :show, params: { id: workflow.id } assert workflow.can_run? - assert_select 'a.btn[href=?]', "https://galaxygalaxy.org/trs_import?trs_server=testseek.test&run_form=true&trs_id=#{workflow.id}&trs_version=1", { text: 'Run on Galaxy' } + trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") + assert_select 'a.btn[href=?]', "https://galaxygalaxy.org/mygalaxy/trs_import?trs_url=#{trs_url}&run_form=true", + { text: 'Run on Galaxy' } end end From 12e2985ecb3a3197bbf5221a192e1d0387ecc0ce Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 16 May 2023 11:11:44 +0100 Subject: [PATCH 193/344] Fix Galaxy TRS import URL --- app/models/concerns/workflow_extraction.rb | 3 +-- test/functional/workflows_controller_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/workflow_extraction.rb b/app/models/concerns/workflow_extraction.rb index f4c37915b3..a9cf9ed027 100644 --- a/app/models/concerns/workflow_extraction.rb +++ b/app/models/concerns/workflow_extraction.rb @@ -67,8 +67,7 @@ def run_url return if base.nil? parent_id = is_a_version? ? parent.id : id - url = URI(base) - url.path = '/trs_import' + url = URI(base) + 'workflows/trs_import' params = { trs_url: Seek::Util.routes.ga4gh_trs_v2_tool_version_url(parent_id, version_id: version), run_form: true diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index ef4f5a6cc5..70daed1a4f 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -1784,7 +1784,7 @@ def bad_generator.write_graph(struct) assert workflow.can_run? assert_equal 'https://usegalaxy.eu', Seek::Config.galaxy_instance_default trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") - assert_select 'a.btn[href=?]', "https://usegalaxy.eu/trs_import?trs_url=#{trs_url}&run_form=true", + assert_select 'a.btn[href=?]', "https://usegalaxy.eu/workflows/trs_import?trs_url=#{trs_url}&run_form=true", { text: 'Run on Galaxy' } end @@ -1796,7 +1796,7 @@ def bad_generator.write_graph(struct) assert workflow.can_run? trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") - assert_select 'a.btn[href=?]', "https://galaxygalaxy.org/mygalaxy/trs_import?trs_url=#{trs_url}&run_form=true", + assert_select 'a.btn[href=?]', "https://galaxygalaxy.org/mygalaxy/workflows/trs_import?trs_url=#{trs_url}&run_form=true", { text: 'Run on Galaxy' } end end From 6a8bc3d98b6aed921a1bd6136eec006737cac824 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 16 May 2023 14:09:12 +0100 Subject: [PATCH 194/344] Fix `superclass mismatch for class ...` when running all tests e.g. `bundle exec rails test` --- ...tionalization_test.rb => internationalization_error_test.rb} | 2 +- .../{isa_exporter_test.rb => isa_exporter_compliance_test.rb} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename test/functional/{internationalization_test.rb => internationalization_error_test.rb} (98%) rename test/integration/{isa_exporter_test.rb => isa_exporter_compliance_test.rb} (99%) diff --git a/test/functional/internationalization_test.rb b/test/functional/internationalization_error_test.rb similarity index 98% rename from test/functional/internationalization_test.rb rename to test/functional/internationalization_error_test.rb index 3979c810f7..27a4d093cd 100644 --- a/test/functional/internationalization_test.rb +++ b/test/functional/internationalization_error_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class InternationalizationTest < ActionController::TestCase +class InternationalizationErrorTest < ActionController::TestCase include AuthenticatedTestHelper diff --git a/test/integration/isa_exporter_test.rb b/test/integration/isa_exporter_compliance_test.rb similarity index 99% rename from test/integration/isa_exporter_test.rb rename to test/integration/isa_exporter_compliance_test.rb index 84c5a6662f..9981c94b22 100644 --- a/test/integration/isa_exporter_test.rb +++ b/test/integration/isa_exporter_compliance_test.rb @@ -2,7 +2,7 @@ require 'json' require 'json-schema' -class IsaExporterTest < ActionDispatch::IntegrationTest +class IsaExporterComplianceTest < ActionDispatch::IntegrationTest fixtures :all include SharingFormTestHelper From c839a4f5792b34ea0572d72fffc3863a5e371474 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 16 May 2023 16:19:55 +0100 Subject: [PATCH 195/344] Remove spurious params --- test/integration/fair_signposting_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/fair_signposting_test.rb b/test/integration/fair_signposting_test.rb index 8f85c788c6..af8ba74f17 100644 --- a/test/integration/fair_signposting_test.rb +++ b/test/integration/fair_signposting_test.rb @@ -106,7 +106,7 @@ class FairSignpostingTest < ActionDispatch::IntegrationTest end test 'fair signposting for home' do - get root_path(p) + get root_path assert_response :success links = parse_link_header @@ -133,7 +133,7 @@ class FairSignpostingTest < ActionDispatch::IntegrationTest end test 'fair signposting for privacy page' do - get privacy_home_path(p) + get privacy_home_path assert_response :success assert_nil response.headers['Link'], 'Should not have any signposting links' From 3171d3d41cf6e092c5f6dd905ad1d0a47c48066a Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 16 May 2023 16:21:08 +0100 Subject: [PATCH 196/344] Extract workflow execution instance URL Just Galaxy for now --- app/controllers/workflows_controller.rb | 2 +- app/models/git/blob.rb | 6 +- app/views/workflows/edit.html.erb | 4 +- app/views/workflows/provide_metadata.html.erb | 4 +- .../download_handling/galaxy_http_handler.rb | 4 + lib/seek/workflow_extractors/base.rb | 26 +++++- lib/seek/workflow_extractors/galaxy.rb | 5 +- lib/seek/workflow_extractors/knime.rb | 2 +- lib/seek/workflow_extractors/ro_crate.rb | 13 +-- .../integration/git_workflow_creation_test.rb | 81 +++++++++++++++++++ .../galaxy_extraction_test.rb | 16 ++++ test/unit/workflow_test.rb | 70 +++++++++++++++- 12 files changed, 213 insertions(+), 20 deletions(-) diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index e7b877ce00..1704c8ba86 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -355,7 +355,7 @@ def workflow_params { creator_ids: [] }, { assay_assets_attributes: [:assay_id] }, { publication_ids: [] }, { presentation_ids: [] }, { document_ids: [] }, { data_file_ids: [] }, { sop_ids: [] }, { workflow_data_files_attributes:[:id, :data_file_id, :workflow_data_file_relationship_id, :_destroy] }, - :internals, :maturity_level, :source_link_url, :execution_instance, + :internals, :maturity_level, :source_link_url, :execution_instance_url, { topic_annotations: [] }, { operation_annotations: [] }, { discussion_links_attributes: [:id, :url, :label, :_destroy] }, { git_version_attributes: [:name, :comment, :ref, :commit, :root_path, diff --git a/app/models/git/blob.rb b/app/models/git/blob.rb index b8dcd7cbc9..8423b3536c 100644 --- a/app/models/git/blob.rb +++ b/app/models/git/blob.rb @@ -92,13 +92,17 @@ def text_contents_for_search def remote_content return unless remote? - handler = ContentBlob.remote_content_handler_for(url) + handler = remote_content_handler return unless handler io = handler.fetch io.rewind io end + def remote_content_handler + ContentBlob.remote_content_handler_for(url) + end + def cache_key "#{git_repository.cache_key}/blobs/#{oid}" end diff --git a/app/views/workflows/edit.html.erb b/app/views/workflows/edit.html.erb index 01d7022e83..ef6e2a52ae 100644 --- a/app/views/workflows/edit.html.erb +++ b/app/views/workflows/edit.html.erb @@ -21,8 +21,8 @@ <% if @workflow.workflow_class&.executable? %>
    - <%= f.text_field :execution_instance, placeholder: 'https://usegalaxy.eu', class: 'form-control' -%> -

    The URL to the Galaxy instance where this workflow originated from.

    + <%= f.text_field :execution_instance_url, placeholder: 'https://usegalaxy.eu/', class: 'form-control' -%> +

    The root URL of the Galaxy instance where this workflow originated from.

    <% end %> diff --git a/app/views/workflows/provide_metadata.html.erb b/app/views/workflows/provide_metadata.html.erb index a01b1ad819..840f8371ee 100644 --- a/app/views/workflows/provide_metadata.html.erb +++ b/app/views/workflows/provide_metadata.html.erb @@ -48,8 +48,8 @@ <% if @workflow.workflow_class&.executable? %>
    - <%= text_field_tag 'workflow[execution_instance]', @workflow.execution_instance, placeholder: 'https://usegalaxy.eu', class: 'form-control' -%> -

    The URL to the Galaxy instance where this workflow originated from.

    + <%= text_field_tag 'workflow[execution_instance_url]', @workflow.execution_instance_url, placeholder: 'https://usegalaxy.eu/', class: 'form-control' -%> +

    The root URL of the Galaxy instance where this workflow originated from.

    <% end %> diff --git a/lib/seek/download_handling/galaxy_http_handler.rb b/lib/seek/download_handling/galaxy_http_handler.rb index 52cd276e72..0cb7c8681b 100644 --- a/lib/seek/download_handling/galaxy_http_handler.rb +++ b/lib/seek/download_handling/galaxy_http_handler.rb @@ -38,6 +38,10 @@ def run_url URI.join(galaxy_host, "workflows/run?id=#{workflow_id}").to_s end + def execution_instance_url + galaxy_host.to_s + end + def self.is_galaxy_workflow_url?(uri) uri.hostname.include?('galaxy') && (uri.path.include?('/workflow/') || uri.path.include?('/workflows/')) && uri.query.present? && CGI.parse(uri.query)&.key?('id') diff --git a/lib/seek/workflow_extractors/base.rb b/lib/seek/workflow_extractors/base.rb index f4adf4177e..7d0b14fc91 100644 --- a/lib/seek/workflow_extractors/base.rb +++ b/lib/seek/workflow_extractors/base.rb @@ -12,7 +12,13 @@ def initialize(io) end def metadata - { } + m = {} + + if @io.respond_to?(:remote_content_handler) + m.merge!(extract_source_metadata(@io.remote_content_handler)) + end + + m end def has_tests? @@ -84,6 +90,24 @@ def extract_author(obj) author end + + def extract_source_metadata(handler) + m = {} + return m unless handler + source_url = nil + if handler.respond_to?(:repository_url) + source_url = handler.repository_url + elsif handler.respond_to?(:display_url) + source_url = handler.display_url + end + m[:source_link_url] = source_url + + if handler.respond_to?(:execution_instance_url) + m[:execution_instance_url] = handler.execution_instance_url + end + + m + end end end end diff --git a/lib/seek/workflow_extractors/galaxy.rb b/lib/seek/workflow_extractors/galaxy.rb index aef35386c3..af0ccd3680 100644 --- a/lib/seek/workflow_extractors/galaxy.rb +++ b/lib/seek/workflow_extractors/galaxy.rb @@ -8,6 +8,7 @@ def self.file_extensions end def metadata + metadata = super galaxy_string = @io.read f = Tempfile.new('ga') f.binmode @@ -21,13 +22,13 @@ def metadata end cf.rewind if status.success? - metadata = Seek::WorkflowExtractors::CWL.new(cf).metadata + metadata.merge!(Seek::WorkflowExtractors::CWL.new(cf).metadata) else - metadata = super metadata[:warnings] ||= [] metadata[:warnings] << 'Unable to convert workflow to CWL, some metadata may be missing.' Rails.logger.error("Galaxy -> CWL conversion failed. Error was: #{err}") end + galaxy = JSON.parse(galaxy_string) if galaxy.has_key?('name') diff --git a/lib/seek/workflow_extractors/knime.rb b/lib/seek/workflow_extractors/knime.rb index dfddac054b..6a15e00100 100644 --- a/lib/seek/workflow_extractors/knime.rb +++ b/lib/seek/workflow_extractors/knime.rb @@ -8,7 +8,7 @@ def self.file_extensions def metadata metadata = super - metadata.merge(parse_internal_workflow(extract_workflow)) + metadata.merge!(parse_internal_workflow(extract_workflow)) metadata end diff --git a/lib/seek/workflow_extractors/ro_crate.rb b/lib/seek/workflow_extractors/ro_crate.rb index 5f2567dfbc..33fbde69d4 100644 --- a/lib/seek/workflow_extractors/ro_crate.rb +++ b/lib/seek/workflow_extractors/ro_crate.rb @@ -105,16 +105,11 @@ def metadata_from_crate(crate, m) end end - source_url = crate['isBasedOn'] || crate['url'] || crate.main_workflow['url'] - if source_url - handler = ContentBlob.remote_content_handler_for(source_url) - if handler.respond_to?(:repository_url) - source_url = handler.repository_url - elsif handler.respond_to?(:display_url) - source_url = handler.display_url + source_url = crate['isBasedOn'] || crate['url'] || crate.main_workflow['url'] + if source_url + m.merge!(extract_source_metadata(ContentBlob.remote_content_handler_for(source_url))) + m[:source_link_url] ||= source_url # Use plain source URL if handler doesn't have something more appropriate end - m[:source_link_url] = source_url - end m end diff --git a/test/integration/git_workflow_creation_test.rb b/test/integration/git_workflow_creation_test.rb index 4c56ec0d72..646e805a0c 100644 --- a/test/integration/git_workflow_creation_test.rb +++ b/test/integration/git_workflow_creation_test.rb @@ -313,6 +313,87 @@ class GitWorkflowCreationTest < ActionDispatch::IntegrationTest assert_select '#extraction-errors ul li', text: /Couldn't parse main workflow/ end + test 'can extract source metadata using original URL for galaxy workflow' do + mock_remote_file "#{Rails.root}/test/fixtures/files/workflows/1-PreProcessing.ga", + 'http://galaxy.instance/workflow/export_to_file?id=abcdxyz', + { 'content-disposition' => 'attachment; filename="1-PreProcessing.ga"'} + + repo_count = Git::Repository.count + workflow_count = Workflow.count + version_count = Git::Version.count + annotation_count = Git::Annotation.count + + person = Factory(:person) + galaxy = WorkflowClass.find_by_key('galaxy') || Factory(:galaxy_workflow_class) + login_as(person.user) + + get new_workflow_path + + assert_enqueued_jobs(0) do + assert_difference('Git::Repository.count', 1) do + assert_no_difference('Task.count') do + post create_from_files_workflows_path, params: { + ro_crate: { + main_workflow: { data_url: 'http://galaxy.instance/workflows/?id=abcdxyz' }, + }, + workflow_class_id: galaxy.id + } # Should go to metadata page... + end + end + end + + repo = assigns(:workflow).git_version.git_repository + assert_select 'input[name="workflow[title]"]', count: 1 + a = assigns(:workflow).git_version.remote_source_annotations + assert_equal 1, a.length + assert_equal 'http://galaxy.instance/workflows/?id=abcdxyz', a.first.value + assert_equal '1-PreProcessing.ga', a.first.path + assert_equal 'http://galaxy.instance/', assigns(:workflow).execution_instance_url + assert_select '#workflow_execution_instance_url[value=?]', 'http://galaxy.instance/' + + assert_difference('Workflow.count', 1) do + assert_difference('Git::Version.count', 1) do + # 2 annotations = Main WF path, 1x remote source + assert_difference('Git::Annotation.count', 2) do + post create_metadata_workflows_path, params: { + workflow: { + workflow_class_id: galaxy.id, + title: 'blabla', + execution_instance_url: 'http://galaxy.instance/', + project_ids: [person.projects.first.id], + git_version_attributes: { + root_path: '/', + git_repository_id: repo.id, + ref: 'refs/heads/master', + main_workflow_path: '1-PreProcessing.ga', + remote_sources: { + '1-PreProcessing.ga' => 'http://galaxy.instance/workflows/?id=abcdxyz' + } + } + } + } # Should go to workflow page... + end + end + end + + assert_redirected_to workflow_path(assigns(:workflow)) + + assert assigns(:workflow).latest_git_version.commit.present? + assert_equal 'refs/heads/master', assigns(:workflow).latest_git_version.ref + refute assigns(:workflow).latest_git_version.git_repository.remote? + assert_equal assigns(:workflow), assigns(:workflow).latest_git_version.git_repository.resource + assert assigns(:workflow).latest_git_version.get_blob('1-PreProcessing.ga').remote? + assert_equal({ '1-PreProcessing.ga' => 'http://galaxy.instance/workflows/?id=abcdxyz' }, + assigns(:workflow).latest_git_version.remote_sources) + assert_equal 'http://galaxy.instance/', assigns(:workflow).latest_git_version.execution_instance_url + + # Check there wasn't anything extra created in the whole flow... + assert_equal repo_count + 1, Git::Repository.count + assert_equal workflow_count + 1, Workflow.count + assert_equal version_count + 1, Git::Version.count + assert_equal annotation_count + 2, Git::Annotation.count + end + private def login_as(user) diff --git a/test/unit/workflow_extraction/galaxy_extraction_test.rb b/test/unit/workflow_extraction/galaxy_extraction_test.rb index 42ad004569..ed606dee81 100644 --- a/test/unit/workflow_extraction/galaxy_extraction_test.rb +++ b/test/unit/workflow_extraction/galaxy_extraction_test.rb @@ -74,4 +74,20 @@ class GalaxyExtractionTest < ActiveSupport::TestCase assert_equal [{ bio_tools_id: 'multiqc', name: 'MultiQC' }], metadata[:tools_attributes] end + + test 'extracts execution instance URL' do + mock_remote_file "#{Rails.root}/test/fixtures/files/workflows/1-PreProcessing.ga", 'http://galaxy.instance/workflow/export_to_file?id=abcdxyz' + git_version = Factory(:git_version) + disable_authorization_checks do + git_version.add_remote_file('1-PreProcessing.ga', 'http://galaxy.instance/workflows/?id=abcdxyz') + git_version.fetch_remote_file('1-PreProcessing.ga') + git_version.save! + end + remote_blob = git_version.get_blob('1-PreProcessing.ga') + + extractor = Seek::WorkflowExtractors::Galaxy.new(remote_blob) + metadata = extractor.metadata + + assert_equal 'http://galaxy.instance/', metadata[:execution_instance_url] + end end diff --git a/test/unit/workflow_test.rb b/test/unit/workflow_test.rb index 94480af0f6..c5ed7e842e 100644 --- a/test/unit/workflow_test.rb +++ b/test/unit/workflow_test.rb @@ -116,7 +116,6 @@ class WorkflowTest < ActiveSupport::TestCase end assert_equal 'https://github.com/seek4science/cool-workflow', workflow.source_link_url - assert_equal 'https://github.com/seek4science/cool-workflow', workflow.source_link.url end test 'can clear source URL' do @@ -133,6 +132,7 @@ class WorkflowTest < ActiveSupport::TestCase end assert_nil workflow.reload.source_link + assert_nil workflow.reload.source_link_url end test 'generates RO-Crate and diagram for workflow/abstract workflow' do @@ -817,4 +817,72 @@ def bad_generator.write_graph(struct) assert_nil workflow.maturity_level end end + + test 'can get and set execution instance URL' do + workflow = Factory(:workflow) + + assert_no_difference('AssetLink.count') do + workflow.execution_instance_url = 'https://mygalaxy.instance/' + end + + assert_difference('AssetLink.count', 1) do + disable_authorization_checks { workflow.save! } + end + + assert_equal 'https://mygalaxy.instance/', workflow.execution_instance_url + end + + test 'can clear execution instance URL' do + workflow = Factory(:workflow, execution_instance_url: 'https://mygalaxy.instance/') + assert workflow.execution_instance + assert workflow.execution_instance_url + + assert_no_difference('AssetLink.count') do + workflow.execution_instance_url = nil + end + + assert_difference('AssetLink.count', -1) do + disable_authorization_checks { workflow.save! } + end + + assert_nil workflow.reload.execution_instance + assert_nil workflow.reload.execution_instance_url + end + + test 'can_run?' do + assert Factory(:generated_galaxy_ro_crate_workflow, policy: Factory(:public_policy)).can_run? + assert Factory(:generated_galaxy_ro_crate_workflow, policy: Factory(:public_policy), execution_instance_url: 'https://mygalaxy.instance/').can_run? + refute Factory(:generated_galaxy_ro_crate_workflow, policy: Factory(:private_policy)).can_run? + refute Factory(:cwl_workflow, policy: Factory(:public_policy)).can_run? + end + + test 'run_url' do + # Using default + with_config_value(:galaxy_instance_default, 'http://default-galaxy-instance.com') do + default = Factory(:generated_galaxy_ro_crate_workflow) + trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{default.id}/versions/1") + assert_equal "http://default-galaxy-instance.com/workflows/trs_import?trs_url=#{trs_url}&run_form=true", default.run_url + end + + # With explicit execution instance + workflow = Factory(:generated_galaxy_ro_crate_workflow, execution_instance_url: 'https://mygalaxy.instance/') + trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") + assert_equal "https://mygalaxy.instance/workflows/trs_import?trs_url=#{trs_url}&run_form=true", workflow.run_url + + # Versions + disable_authorization_checks do + workflow.save_as_new_version('new version') + end + v2_trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/2") + assert_equal "https://mygalaxy.instance/workflows/trs_import?trs_url=#{v2_trs_url}&run_form=true", workflow.run_url + assert_equal "https://mygalaxy.instance/workflows/trs_import?trs_url=#{trs_url}&run_form=true", workflow.find_version(1).run_url + + # Galaxy instance with sub-URI + workflow = Factory(:generated_galaxy_ro_crate_workflow, execution_instance_url: 'https://mygalaxy.instance/galaxy/') + trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") + assert_equal "https://mygalaxy.instance/galaxy/workflows/trs_import?trs_url=#{trs_url}&run_form=true", workflow.run_url + + # Not supported for non-galaxy currently + assert_nil Factory(:cwl_workflow, policy: Factory(:public_policy)).run_url + end end From 7415fa6df736de9ca29d79ad18b1fa892f55777a Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 28 Feb 2024 16:27:59 +0000 Subject: [PATCH 197/344] Update factory calls --- test/functional/workflows_controller_test.rb | 4 ++-- .../integration/git_workflow_creation_test.rb | 4 ++-- .../galaxy_extraction_test.rb | 2 +- test/unit/workflow_test.rb | 20 +++++++++---------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index 70daed1a4f..845f75d45e 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -1777,7 +1777,7 @@ def bad_generator.write_graph(struct) end test 'shows run button for galaxy workflows using default galaxy endpoint' do - workflow = Factory(:existing_galaxy_ro_crate_workflow, policy: Factory(:public_policy)) + workflow = FactoryBot.create(:existing_galaxy_ro_crate_workflow, policy: FactoryBot.create(:public_policy)) get :show, params: { id: workflow.id } @@ -1789,7 +1789,7 @@ def bad_generator.write_graph(struct) end test 'shows run button for galaxy workflows using specified galaxy endpoint' do - workflow = Factory(:existing_galaxy_ro_crate_workflow, policy: Factory(:public_policy), + workflow = FactoryBot.create(:existing_galaxy_ro_crate_workflow, policy: FactoryBot.create(:public_policy), execution_instance_url: 'https://galaxygalaxy.org/mygalaxy/') get :show, params: { id: workflow.id } diff --git a/test/integration/git_workflow_creation_test.rb b/test/integration/git_workflow_creation_test.rb index 646e805a0c..21d90401e9 100644 --- a/test/integration/git_workflow_creation_test.rb +++ b/test/integration/git_workflow_creation_test.rb @@ -323,8 +323,8 @@ class GitWorkflowCreationTest < ActionDispatch::IntegrationTest version_count = Git::Version.count annotation_count = Git::Annotation.count - person = Factory(:person) - galaxy = WorkflowClass.find_by_key('galaxy') || Factory(:galaxy_workflow_class) + person = FactoryBot.create(:person) + galaxy = WorkflowClass.find_by_key('galaxy') || FactoryBot.create(:galaxy_workflow_class) login_as(person.user) get new_workflow_path diff --git a/test/unit/workflow_extraction/galaxy_extraction_test.rb b/test/unit/workflow_extraction/galaxy_extraction_test.rb index ed606dee81..93b18bd5ac 100644 --- a/test/unit/workflow_extraction/galaxy_extraction_test.rb +++ b/test/unit/workflow_extraction/galaxy_extraction_test.rb @@ -77,7 +77,7 @@ class GalaxyExtractionTest < ActiveSupport::TestCase test 'extracts execution instance URL' do mock_remote_file "#{Rails.root}/test/fixtures/files/workflows/1-PreProcessing.ga", 'http://galaxy.instance/workflow/export_to_file?id=abcdxyz' - git_version = Factory(:git_version) + git_version = FactoryBot.create(:git_version) disable_authorization_checks do git_version.add_remote_file('1-PreProcessing.ga', 'http://galaxy.instance/workflows/?id=abcdxyz') git_version.fetch_remote_file('1-PreProcessing.ga') diff --git a/test/unit/workflow_test.rb b/test/unit/workflow_test.rb index c5ed7e842e..3bc0455299 100644 --- a/test/unit/workflow_test.rb +++ b/test/unit/workflow_test.rb @@ -819,7 +819,7 @@ def bad_generator.write_graph(struct) end test 'can get and set execution instance URL' do - workflow = Factory(:workflow) + workflow = FactoryBot.create(:workflow) assert_no_difference('AssetLink.count') do workflow.execution_instance_url = 'https://mygalaxy.instance/' @@ -833,7 +833,7 @@ def bad_generator.write_graph(struct) end test 'can clear execution instance URL' do - workflow = Factory(:workflow, execution_instance_url: 'https://mygalaxy.instance/') + workflow = FactoryBot.create(:workflow, execution_instance_url: 'https://mygalaxy.instance/') assert workflow.execution_instance assert workflow.execution_instance_url @@ -850,22 +850,22 @@ def bad_generator.write_graph(struct) end test 'can_run?' do - assert Factory(:generated_galaxy_ro_crate_workflow, policy: Factory(:public_policy)).can_run? - assert Factory(:generated_galaxy_ro_crate_workflow, policy: Factory(:public_policy), execution_instance_url: 'https://mygalaxy.instance/').can_run? - refute Factory(:generated_galaxy_ro_crate_workflow, policy: Factory(:private_policy)).can_run? - refute Factory(:cwl_workflow, policy: Factory(:public_policy)).can_run? + assert FactoryBot.create(:generated_galaxy_ro_crate_workflow, policy: FactoryBot.create(:public_policy)).can_run? + assert FactoryBot.create(:generated_galaxy_ro_crate_workflow, policy: FactoryBot.create(:public_policy), execution_instance_url: 'https://mygalaxy.instance/').can_run? + refute FactoryBot.create(:generated_galaxy_ro_crate_workflow, policy: FactoryBot.create(:private_policy)).can_run? + refute FactoryBot.create(:cwl_workflow, policy: FactoryBot.create(:public_policy)).can_run? end test 'run_url' do # Using default with_config_value(:galaxy_instance_default, 'http://default-galaxy-instance.com') do - default = Factory(:generated_galaxy_ro_crate_workflow) + default = FactoryBot.create(:generated_galaxy_ro_crate_workflow) trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{default.id}/versions/1") assert_equal "http://default-galaxy-instance.com/workflows/trs_import?trs_url=#{trs_url}&run_form=true", default.run_url end # With explicit execution instance - workflow = Factory(:generated_galaxy_ro_crate_workflow, execution_instance_url: 'https://mygalaxy.instance/') + workflow = FactoryBot.create(:generated_galaxy_ro_crate_workflow, execution_instance_url: 'https://mygalaxy.instance/') trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") assert_equal "https://mygalaxy.instance/workflows/trs_import?trs_url=#{trs_url}&run_form=true", workflow.run_url @@ -878,11 +878,11 @@ def bad_generator.write_graph(struct) assert_equal "https://mygalaxy.instance/workflows/trs_import?trs_url=#{trs_url}&run_form=true", workflow.find_version(1).run_url # Galaxy instance with sub-URI - workflow = Factory(:generated_galaxy_ro_crate_workflow, execution_instance_url: 'https://mygalaxy.instance/galaxy/') + workflow = FactoryBot.create(:generated_galaxy_ro_crate_workflow, execution_instance_url: 'https://mygalaxy.instance/galaxy/') trs_url = URI.encode_www_form_component("http://localhost:3000/ga4gh/trs/v2/tools/#{workflow.id}/versions/1") assert_equal "https://mygalaxy.instance/galaxy/workflows/trs_import?trs_url=#{trs_url}&run_form=true", workflow.run_url # Not supported for non-galaxy currently - assert_nil Factory(:cwl_workflow, policy: Factory(:public_policy)).run_url + assert_nil FactoryBot.create(:cwl_workflow, policy: FactoryBot.create(:public_policy)).run_url end end From fe3c23708d988aff225e5b5fc00f40a041354862 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 28 Feb 2024 19:04:19 +0000 Subject: [PATCH 198/344] Fix metadata not being pulled from base extractor in ROLike --- lib/seek/workflow_extractors/ro_like.rb | 37 +++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/seek/workflow_extractors/ro_like.rb b/lib/seek/workflow_extractors/ro_like.rb index 04bba108cd..65eefe744e 100644 --- a/lib/seek/workflow_extractors/ro_like.rb +++ b/lib/seek/workflow_extractors/ro_like.rb @@ -36,24 +36,27 @@ def generate_diagram end def metadata + m = super # Use CWL description - m = if abstract_cwl_extractor - begin - abstract_cwl_extractor.metadata - rescue StandardError => e - Rails.logger.error('Error extracting abstract CWL:') - Rails.logger.error(e) - { errors: ["Couldn't parse abstract CWL"] } - end - else - begin - main_workflow_extractor.metadata - rescue StandardError => e - Rails.logger.error('Error extracting workflow:') - Rails.logger.error(e) - { errors: ["Couldn't parse main workflow"] } - end - end + if abstract_cwl_extractor + begin + m.merge!(abstract_cwl_extractor.metadata) + rescue StandardError => e + Rails.logger.error('Error extracting abstract CWL:') + Rails.logger.error(e) + m[:errors] ||= [] + m[:errors] << "Couldn't parse abstract CWL" + end + else + begin + m.merge!(main_workflow_extractor.metadata) + rescue StandardError => e + Rails.logger.error('Error extracting workflow:') + Rails.logger.error(e) + m[:errors] ||= [] + m[:errors] << "Couldn't parse main workflow" + end + end if file_exists?('README.md') m[:description] ||= file('README.md').read.force_encoding('utf-8').gsub(/^(---\s*\n.*?\n?)^(---\s*$\n?)/m,'') # Remove "Front matter" From 69c56b7a1fe5dc11dea911c69a2adbb4056c48d1 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 28 Feb 2024 19:06:29 +0000 Subject: [PATCH 199/344] Use `Git::Blob`s instead of `File`s in extractors called from `GitRepo` extractor Or we lose the ability to call `remote_content_handler`, get `Git::Attributes` etc. --- app/models/git/blob.rb | 10 +++++++--- lib/seek/workflow_extractors/git_repo.rb | 2 +- test/unit/git/git_blob_test.rb | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/models/git/blob.rb b/app/models/git/blob.rb index 8423b3536c..5f91335ce3 100644 --- a/app/models/git/blob.rb +++ b/app/models/git/blob.rb @@ -10,12 +10,16 @@ class Blob delegate :read, :rewind, to: :file attr_reader :git_version, :path + # Flag to decide if `read`ing this blob should eagerly fetch any remote content pointed to by this blob's `url`. + attr_accessor :fetch_remote + alias_method :original_filename, :path def initialize(git_version, blob, path) @git_version = git_version @blob = blob @path = path + @fetch_remote = false end def annotations @@ -26,7 +30,7 @@ def url git_version.remote_sources[path] end - def file(fetch_remote: false) + def file(fetch_remote: @fetch_remote) @file ||= to_tempfile(fetch_remote: fetch_remote) end @@ -34,7 +38,7 @@ def binread file_contents end - def file_contents(as_text: false, fetch_remote: false, &block) + def file_contents(as_text: false, fetch_remote: @fetch_remote, &block) if fetch_remote && remote? && !fetched? if block_given? block.call(remote_content) @@ -138,7 +142,7 @@ def is_text? private - def to_tempfile(fetch_remote: false) + def to_tempfile(fetch_remote: @fetch_remote) f = Tempfile.new(path) f.binmode if binary? f << file_contents(as_text: !binary?, fetch_remote: fetch_remote) diff --git a/lib/seek/workflow_extractors/git_repo.rb b/lib/seek/workflow_extractors/git_repo.rb index 41370140d8..cb53e8dab5 100644 --- a/lib/seek/workflow_extractors/git_repo.rb +++ b/lib/seek/workflow_extractors/git_repo.rb @@ -32,7 +32,7 @@ def file_exists?(path) end def file(path) - @obj.get_blob(path)&.file(fetch_remote: true) + @obj.get_blob(path).tap { |blob| blob.fetch_remote = true } end def licensee_project diff --git a/test/unit/git/git_blob_test.rb b/test/unit/git/git_blob_test.rb index f906466fc5..b6d767c765 100644 --- a/test/unit/git/git_blob_test.rb +++ b/test/unit/git/git_blob_test.rb @@ -59,6 +59,21 @@ class GitBlobTest < ActiveSupport::TestCase remote_blob.file_contents(fetch_remote: true) do |c| assert_equal 'lit', c.read(3) end + + # fetch_remote + refute remote_blob.fetch_remote + assert_equal 0, remote_blob.size + assert_equal 0, remote_blob.file_contents.size + remote_blob.file_contents do |c| + assert_nil c.read(1) + end + + remote_blob.fetch_remote = true + assert remote_blob.fetch_remote + assert_equal 11, remote_blob.file_contents.size + remote_blob.file_contents do |c| + assert_equal 'lit', c.read(3) + end end test 'fetched remote blob' do From 9af40ab1523f97e3bbfcd40a0c768160bf220e93 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 29 Feb 2024 10:06:29 +0000 Subject: [PATCH 200/344] Tolerate blob not being found --- lib/seek/workflow_extractors/git_repo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/seek/workflow_extractors/git_repo.rb b/lib/seek/workflow_extractors/git_repo.rb index cb53e8dab5..b5f0d1d472 100644 --- a/lib/seek/workflow_extractors/git_repo.rb +++ b/lib/seek/workflow_extractors/git_repo.rb @@ -32,7 +32,7 @@ def file_exists?(path) end def file(path) - @obj.get_blob(path).tap { |blob| blob.fetch_remote = true } + @obj.get_blob(path)&.tap { |blob| blob.fetch_remote = true } end def licensee_project From eb2fa35dd8ebc9d0ab51945721ae1fff7a6dcaa1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:06:32 +0000 Subject: [PATCH 201/344] Bump yard from 0.9.27 to 0.9.35 Bumps [yard](https://github.com/lsegal/yard) from 0.9.27 to 0.9.35. - [Release notes](https://github.com/lsegal/yard/releases) - [Changelog](https://github.com/lsegal/yard/blob/main/CHANGELOG.md) - [Commits](https://github.com/lsegal/yard/compare/v0.9.27...v0.9.35) --- updated-dependencies: - dependency-name: yard dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 07c9a2d37e..ece4c2291c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -936,7 +936,7 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.7.0) + webrick (1.8.1) webrobots (0.1.2) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -949,8 +949,7 @@ GEM yaml_db (0.7.0) rails (>= 3.0) rake (>= 0.8.7) - yard (0.9.27) - webrick (~> 1.7.0) + yard (0.9.35) zeitwerk (2.6.13) zip-container (4.0.2) rubyzip (~> 2.0.0) From 07dcbb8cb5ea47af87cadcb1cab6549e69373c3b Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 29 Feb 2024 11:14:02 +0000 Subject: [PATCH 202/344] `Git::Blob#path` is not the local file path --- lib/seek/workflow_extractors/cff.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/seek/workflow_extractors/cff.rb b/lib/seek/workflow_extractors/cff.rb index 1312b2911b..271efdf2a8 100644 --- a/lib/seek/workflow_extractors/cff.rb +++ b/lib/seek/workflow_extractors/cff.rb @@ -6,7 +6,7 @@ class CFF FILENAME = 'CITATION.cff' def initialize(io) - if io.respond_to?(:path) + if io.respond_to?(:path) && !io.is_a?(Git::Blob) @path = io.path else f = Tempfile.new('cff') From 2e4b96c2cb9479d071d3366c914b66cd1d03871c Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 29 Feb 2024 16:59:52 +0000 Subject: [PATCH 203/344] Update Galaxy workflow endpoints --- .../download_handling/galaxy_http_handler.rb | 26 ++++++++++++------- .../content_blobs_controller_test.rb | 8 +++--- .../integration/git_workflow_creation_test.rb | 10 +++---- .../galaxy_extraction_test.rb | 5 ++-- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/seek/download_handling/galaxy_http_handler.rb b/lib/seek/download_handling/galaxy_http_handler.rb index 0cb7c8681b..9f755ec508 100644 --- a/lib/seek/download_handling/galaxy_http_handler.rb +++ b/lib/seek/download_handling/galaxy_http_handler.rb @@ -10,11 +10,20 @@ module DownloadHandling class GalaxyHTTPHandler < Seek::DownloadHandling::HTTPHandler attr_reader :galaxy_host, :workflow_id - def initialize(url, fallback_to_get: true) - uri = URI(url) + URL_PATTERNS = [ + /(.+)\/api\/workflows\/([^\/]+)\/download?format=json-download/, # Download + /(.+)\/workflows\/run\?id=([^&]+)/, # Run + /(.+)\/published\/workflow\?id=([^&]+)/, # View + ].freeze - @galaxy_host = URI(url.split(/\/workflows?\//).first + '/') - @workflow_id = CGI.parse(uri.query)['id'].first + def initialize(url, fallback_to_get: true) + URL_PATTERNS.each do |pattern| + matches = url.match(pattern) + if matches + @galaxy_host = matches[1].chomp('/') + '/' + @workflow_id = matches[2] + end + end super(download_url, fallback_to_get: fallback_to_get) end @@ -26,14 +35,13 @@ def info end def display_url - URI.join(galaxy_host, "workflow/display_by_id?id=#{workflow_id}").to_s + URI.join(galaxy_host, "published/workflow?id=#{workflow_id}").to_s end def download_url - URI.join(galaxy_host, "workflow/export_to_file?id=#{workflow_id}").to_s + URI.join(galaxy_host, "api/workflows/#{workflow_id}/download?format=json-download").to_s end - # Note that the path is `/workflows/` (plural) here for some reason. def run_url URI.join(galaxy_host, "workflows/run?id=#{workflow_id}").to_s end @@ -43,8 +51,8 @@ def execution_instance_url end def self.is_galaxy_workflow_url?(uri) - uri.hostname.include?('galaxy') && (uri.path.include?('/workflow/') || uri.path.include?('/workflows/')) && - uri.query.present? && CGI.parse(uri.query)&.key?('id') + string_uri = uri.to_s + uri.hostname.include?('galaxy') && URL_PATTERNS.any? { |pattern| string_uri.match?(pattern) } end end end diff --git a/test/functional/content_blobs_controller_test.rb b/test/functional/content_blobs_controller_test.rb index 5e9b8e32c1..da4b367bd6 100644 --- a/test/functional/content_blobs_controller_test.rb +++ b/test/functional/content_blobs_controller_test.rb @@ -94,7 +94,7 @@ def setup test 'examine url to galaxy instance works with the various workflow endpoints' do stub_request(:any, 'https://galaxy-instance.biz/banana/workflows/run?id=123').to_return(status: 200) - stub_request(:any, 'https://galaxy-instance.biz/banana/workflow/export_to_file?id=123').to_return( + stub_request(:any, 'https://galaxy-instance.biz/banana/api/workflows/123/download?format=json-download').to_return( body: File.new("#{Rails.root}/test/fixtures/files/workflows/1-PreProcessing.ga"), status: 200, headers: { 'Content-Length' => 40296, @@ -109,14 +109,14 @@ def setup assert_equal 'galaxy', assigns(:type) assert_equal '123', assigns(:info)[:workflow_id] assert_equal 'https://galaxy-instance.biz/banana/', assigns(:info)[:galaxy_host].to_s - assert_equal 'https://galaxy-instance.biz/banana/workflow/display_by_id?id=123', assigns(:info)[:display_url] + assert_equal 'https://galaxy-instance.biz/banana/published/workflow?id=123', assigns(:info)[:display_url] assert_equal 40296, assigns(:info)[:file_size] assert_equal '1-PreProcessing.ga', assigns(:info)[:file_name] } suite.call('https://galaxy-instance.biz/banana/workflows/run?id=123') - suite.call('https://galaxy-instance.biz/banana/workflow/export_to_file?id=123') - suite.call('https://galaxy-instance.biz/banana/workflow/display_by_id?id=123') + suite.call('https://galaxy-instance.biz/banana/api/workflows/123/download?format=json-download') + suite.call('https://galaxy-instance.biz/banana/published/workflow?id=123') end test 'examine url does not crash when examining galaxy-like URL' do diff --git a/test/integration/git_workflow_creation_test.rb b/test/integration/git_workflow_creation_test.rb index 21d90401e9..4464baa93d 100644 --- a/test/integration/git_workflow_creation_test.rb +++ b/test/integration/git_workflow_creation_test.rb @@ -315,7 +315,7 @@ class GitWorkflowCreationTest < ActionDispatch::IntegrationTest test 'can extract source metadata using original URL for galaxy workflow' do mock_remote_file "#{Rails.root}/test/fixtures/files/workflows/1-PreProcessing.ga", - 'http://galaxy.instance/workflow/export_to_file?id=abcdxyz', + 'http://galaxy.instance/api/workflows/abcdxyz/download?format=json-download', { 'content-disposition' => 'attachment; filename="1-PreProcessing.ga"'} repo_count = Git::Repository.count @@ -334,7 +334,7 @@ class GitWorkflowCreationTest < ActionDispatch::IntegrationTest assert_no_difference('Task.count') do post create_from_files_workflows_path, params: { ro_crate: { - main_workflow: { data_url: 'http://galaxy.instance/workflows/?id=abcdxyz' }, + main_workflow: { data_url: 'http://galaxy.instance/workflows/run?id=abcdxyz' }, }, workflow_class_id: galaxy.id } # Should go to metadata page... @@ -346,7 +346,7 @@ class GitWorkflowCreationTest < ActionDispatch::IntegrationTest assert_select 'input[name="workflow[title]"]', count: 1 a = assigns(:workflow).git_version.remote_source_annotations assert_equal 1, a.length - assert_equal 'http://galaxy.instance/workflows/?id=abcdxyz', a.first.value + assert_equal 'http://galaxy.instance/workflows/run?id=abcdxyz', a.first.value assert_equal '1-PreProcessing.ga', a.first.path assert_equal 'http://galaxy.instance/', assigns(:workflow).execution_instance_url assert_select '#workflow_execution_instance_url[value=?]', 'http://galaxy.instance/' @@ -367,7 +367,7 @@ class GitWorkflowCreationTest < ActionDispatch::IntegrationTest ref: 'refs/heads/master', main_workflow_path: '1-PreProcessing.ga', remote_sources: { - '1-PreProcessing.ga' => 'http://galaxy.instance/workflows/?id=abcdxyz' + '1-PreProcessing.ga' => 'http://galaxy.instance/workflows/run?id=abcdxyz' } } } @@ -383,7 +383,7 @@ class GitWorkflowCreationTest < ActionDispatch::IntegrationTest refute assigns(:workflow).latest_git_version.git_repository.remote? assert_equal assigns(:workflow), assigns(:workflow).latest_git_version.git_repository.resource assert assigns(:workflow).latest_git_version.get_blob('1-PreProcessing.ga').remote? - assert_equal({ '1-PreProcessing.ga' => 'http://galaxy.instance/workflows/?id=abcdxyz' }, + assert_equal({ '1-PreProcessing.ga' => 'http://galaxy.instance/workflows/run?id=abcdxyz' }, assigns(:workflow).latest_git_version.remote_sources) assert_equal 'http://galaxy.instance/', assigns(:workflow).latest_git_version.execution_instance_url diff --git a/test/unit/workflow_extraction/galaxy_extraction_test.rb b/test/unit/workflow_extraction/galaxy_extraction_test.rb index 93b18bd5ac..0851b06431 100644 --- a/test/unit/workflow_extraction/galaxy_extraction_test.rb +++ b/test/unit/workflow_extraction/galaxy_extraction_test.rb @@ -76,10 +76,11 @@ class GalaxyExtractionTest < ActiveSupport::TestCase end test 'extracts execution instance URL' do - mock_remote_file "#{Rails.root}/test/fixtures/files/workflows/1-PreProcessing.ga", 'http://galaxy.instance/workflow/export_to_file?id=abcdxyz' + mock_remote_file "#{Rails.root}/test/fixtures/files/workflows/1-PreProcessing.ga", + 'http://galaxy.instance/api/workflows/abcdxyz/download?format=json-download' git_version = FactoryBot.create(:git_version) disable_authorization_checks do - git_version.add_remote_file('1-PreProcessing.ga', 'http://galaxy.instance/workflows/?id=abcdxyz') + git_version.add_remote_file('1-PreProcessing.ga', 'http://galaxy.instance/published/workflow?id=abcdxyz') git_version.fetch_remote_file('1-PreProcessing.ga') git_version.save! end From c7d822bb6bb5c95575260962459738f3c6299301 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Fri, 1 Mar 2024 13:06:07 +0000 Subject: [PATCH 204/344] Escape `?` in regex --- lib/seek/download_handling/galaxy_http_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/seek/download_handling/galaxy_http_handler.rb b/lib/seek/download_handling/galaxy_http_handler.rb index 9f755ec508..d8ecfbc612 100644 --- a/lib/seek/download_handling/galaxy_http_handler.rb +++ b/lib/seek/download_handling/galaxy_http_handler.rb @@ -11,7 +11,7 @@ class GalaxyHTTPHandler < Seek::DownloadHandling::HTTPHandler attr_reader :galaxy_host, :workflow_id URL_PATTERNS = [ - /(.+)\/api\/workflows\/([^\/]+)\/download?format=json-download/, # Download + /(.+)\/api\/workflows\/([^\/]+)\/download\?format=json-download/, # Download /(.+)\/workflows\/run\?id=([^&]+)/, # Run /(.+)\/published\/workflow\?id=([^&]+)/, # View ].freeze From d18adb0b3139050afd0839b13326b693a4acf269 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:20:24 +0000 Subject: [PATCH 205/344] Bump yard from 0.9.35 to 0.9.36 Bumps [yard](https://github.com/lsegal/yard) from 0.9.35 to 0.9.36. - [Release notes](https://github.com/lsegal/yard/releases) - [Changelog](https://github.com/lsegal/yard/blob/main/CHANGELOG.md) - [Commits](https://github.com/lsegal/yard/compare/v0.9.35...v0.9.36) --- updated-dependencies: - dependency-name: yard dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ece4c2291c..f2a665db54 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -949,7 +949,7 @@ GEM yaml_db (0.7.0) rails (>= 3.0) rake (>= 0.8.7) - yard (0.9.35) + yard (0.9.36) zeitwerk (2.6.13) zip-container (4.0.2) rubyzip (~> 2.0.0) From c2e115bbba3d566d30fb266aabf901a6fdbbc355 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 29 Feb 2024 17:19:09 +0000 Subject: [PATCH 206/344] Fix blank project ID breaking projects selector --- app/views/projects/_project_selector.html.erb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/app/views/projects/_project_selector.html.erb b/app/views/projects/_project_selector.html.erb index 52fd423377..038e764433 100644 --- a/app/views/projects/_project_selector.html.erb +++ b/app/views/projects/_project_selector.html.erb @@ -119,16 +119,10 @@ $j(document).ready(function () { // Auto select projects from params[:project_ids] or params[resource][:project_ids] if this is a new object - <% if resource && resource.new_record? %> - <% if params[:project_ids] %> - <% params[:project_ids].each do |project_id| %> - Sharing.projectsSelector.add(<%= project_id %>, true); - <% end %> - <% end %> - <% if params[controller_name.singularize] && params[controller_name.singularize][:project_ids] %> - <% params[controller_name.singularize][:project_ids].each do |project_id| %> - Sharing.projectsSelector.add(<%= project_id %>, true); - <% end %> + <% if resource &.new_record? %> + <% project_ids = (params[:project_ids] || params.dig(controller_name.singularize, :project_ids) || []).compact_blank %> + <% project_ids.each do |project_id| %> + Sharing.projectsSelector.add(<%= project_id %>, true); <% end %> <% end %> // Auto select project if there is only one From 322076f43c4da2222ec9834ae0941843ef31afd2 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Fri, 1 Mar 2024 13:01:15 +0000 Subject: [PATCH 207/344] Formatting --- app/views/projects/_project_selector.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/_project_selector.html.erb b/app/views/projects/_project_selector.html.erb index 038e764433..5d12d5efd5 100644 --- a/app/views/projects/_project_selector.html.erb +++ b/app/views/projects/_project_selector.html.erb @@ -119,7 +119,7 @@ $j(document).ready(function () { // Auto select projects from params[:project_ids] or params[resource][:project_ids] if this is a new object - <% if resource &.new_record? %> + <% if resource&.new_record? %> <% project_ids = (params[:project_ids] || params.dig(controller_name.singularize, :project_ids) || []).compact_blank %> <% project_ids.each do |project_id| %> Sharing.projectsSelector.add(<%= project_id %>, true); From 350d19650ad3050858aa1be84588e7d8e5393e7d Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 5 Mar 2024 15:28:56 +0100 Subject: [PATCH 208/344] Seed Minimal starter templates as upgrade task --- lib/tasks/seek_upgrades.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index e9b1ae3c15..0d284c5116 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -16,6 +16,7 @@ namespace :seek do remove_ontology_attribute_type db:seed:007_sample_attribute_types db:seed:001_create_controlled_vocabs + db:seed:017_minimal_starter_isa_templates recognise_isa_json_compliant_items implement_assay_streams_for_isa_assays ] From a75ee56c734f48f3811b24ac87a8aeead3215e15 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 5 Mar 2024 15:29:35 +0100 Subject: [PATCH 209/344] Also include investigations where is_isa_json_compliant is nil --- lib/tasks/seek_upgrades.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 0d284c5116..47b4ab267c 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -165,7 +165,7 @@ namespace :seek do investigations_updated = 0 disable_authorization_checks do investigations_to_update = Study.joins(:investigation) - .where('investigations.is_isa_json_compliant = ?', false) + .where('investigations.is_isa_json_compliant IS NULL OR investigations.is_isa_json_compliant = ?', false) .select { |study| study.sample_types.any? } .map(&:investigation) .compact From b48860bec1957a525c7239f79c8038d8ec6d196c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 5 Mar 2024 15:52:21 +0100 Subject: [PATCH 210/344] Fix assay streams upgrade task --- lib/tasks/seek_upgrades.rake | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 47b4ab267c..38cb4b50bd 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -189,7 +189,7 @@ namespace :seek do # Previous ST should be second ST of study first_assays_in_stream = Assay.joins(:sample_type, study: :investigation) .where(assay_stream_id: nil, investigation: { is_isa_json_compliant: true }) - .select { |a| a.previous_linked_sample_type == a.study.sample_types.second } + .select { |a| a.sample_type.previous_linked_sample_type == a.study.sample_types.second } first_assays_in_stream.map do |fas| stream_name = "Assay Stream - #{UUID.generate}" @@ -212,7 +212,11 @@ namespace :seek do current_assay.update_column(:assay_stream_id, assay_stream.id) assay_position += 1 - current_assay = current_assay.next_linked_child_assay + current_assay = if current_assay.sample_type.nil? + nil + else + current_assay.sample_type.next_linked_sample_types.first&.assays&.first + end end assay_streams_created += 1 end From a3e3e971f28ce3946cd30156e3fdda1cddbd871d Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 5 Mar 2024 17:57:35 +0000 Subject: [PATCH 211/344] Update Workflow RO-Crate generation to match 1.0 spec --- lib/ro_crate/workflow_crate.rb | 14 ++++---------- test/integration/workflow_ro_crate_test.rb | 8 ++++++++ test/unit/workflow_repository_builder_test.rb | 3 +-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/ro_crate/workflow_crate.rb b/lib/ro_crate/workflow_crate.rb index 9b423e96ad..f222760f23 100644 --- a/lib/ro_crate/workflow_crate.rb +++ b/lib/ro_crate/workflow_crate.rb @@ -2,12 +2,7 @@ module ROCrate class WorkflowCrate < ::ROCrate::Crate - PROFILE = { - '@id' => 'https://about.workflowhub.eu/Workflow-RO-Crate/', - '@type' => 'CreativeWork', - 'name' => 'Workflow RO-Crate Profile', - 'version' => '0.2.0' - }.freeze + PROFILE_REF = { '@id' => 'https://w3id.org/workflowhub/workflow-ro-crate/1.0' }.freeze include ActiveModel::Model @@ -17,12 +12,11 @@ class WorkflowCrate < ::ROCrate::Crate def initialize(*args) super.tap do - prof = add_contextual_entity(ROCrate::ContextualEntity.new(self, nil, PROFILE)) - conforms = metadata['conformsTo'] + conforms = self['conformsTo'] if conforms.is_a?(Array) - metadata['conformsTo'] << prof.reference + self['conformsTo'] << PROFILE_REF else - metadata['conformsTo'] = [{ '@id' => ::ROCrate::Metadata::SPEC }, prof.reference] + self['conformsTo'] = [{ '@id' => ::ROCrate::Metadata::SPEC }, PROFILE_REF] end end end diff --git a/test/integration/workflow_ro_crate_test.rb b/test/integration/workflow_ro_crate_test.rb index 514e3e8e3a..3e660423e6 100644 --- a/test/integration/workflow_ro_crate_test.rb +++ b/test/integration/workflow_ro_crate_test.rb @@ -123,4 +123,12 @@ class WorkflowRoCrateTest < ActionDispatch::IntegrationTest File.delete(git_version.send(:ro_crate_path)) end end + + test 'conformsTo' do + crate = ROCrate::WorkflowCrate.new + + ids = crate['conformsTo'].map { |x| x['@id'] } + assert_includes ids, 'https://w3id.org/ro/crate/1.1' + assert_includes ids, 'https://w3id.org/workflowhub/workflow-ro-crate/1.0' + end end diff --git a/test/unit/workflow_repository_builder_test.rb b/test/unit/workflow_repository_builder_test.rb index b4694cb4b0..85e7ed14b4 100644 --- a/test/unit/workflow_repository_builder_test.rb +++ b/test/unit/workflow_repository_builder_test.rb @@ -91,7 +91,7 @@ class WorkflowRepositoryBuilderTest < ActiveSupport::TestCase disable_authorization_checks { workflow.save } crate = workflow.ro_crate - assert_equal 19, crate.entities.count + assert_equal 18, crate.entities.count assert crate.get("ro-crate-metadata.json").is_a?(ROCrate::Metadata) assert crate.get("ro-crate-preview.html").is_a?(ROCrate::Preview) assert crate.get("./").is_a?(ROCrate::WorkflowCrate) @@ -101,6 +101,5 @@ class WorkflowRepositoryBuilderTest < ActiveSupport::TestCase assert crate.get("#galaxy").is_a?(ROCrate::ContextualEntity) assert crate.get("#cwl").is_a?(ROCrate::ContextualEntity) assert 9, crate.entities.select { |e| e.type == 'FormalParameter' }.count - assert crate.get(ROCrate::WorkflowCrate::PROFILE['@id']).is_a?(ROCrate::ContextualEntity) end end From eed66e7cb8707a27f71a55e514ea948fcdc4d058 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 5 Mar 2024 18:34:04 +0000 Subject: [PATCH 212/344] Update blob size because of RO-Crate metadata changes --- test/integration/workflow_versioning_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/workflow_versioning_test.rb b/test/integration/workflow_versioning_test.rb index 2233c635ab..4f870a2fa5 100644 --- a/test/integration/workflow_versioning_test.rb +++ b/test/integration/workflow_versioning_test.rb @@ -141,7 +141,7 @@ class WorkflowVersioningTest < ActionDispatch::IntegrationTest assert_equal 2, workflow.reload.versions.count assert_equal 732, workflow.find_version(1).content_blob.file_size - assert_equal 12349, workflow.find_version(2).content_blob.file_size + assert_equal 12287, workflow.find_version(2).content_blob.file_size end test 'new workflow version upload copes with workflow class change' do From 97168a7ea049ebc39c0967199870346b902dcfcc Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 6 Mar 2024 10:50:03 +0100 Subject: [PATCH 213/344] Display template_attribute even if Sample Controlled vocab is `nil`. --- app/helpers/templates_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/templates_helper.rb b/app/helpers/templates_helper.rb index 5d8b7eaa90..ff3a032de4 100644 --- a/app/helpers/templates_helper.rb +++ b/app/helpers/templates_helper.rb @@ -67,7 +67,7 @@ def template_attribute_type_link(template_attribute) type += ' - ' + link_to(template_attribute.linked_sample_type&.title, template_attribute.linked_sample_type) end - if template_attribute.sample_attribute_type.controlled_vocab? + if template_attribute.sample_attribute_type.controlled_vocab? && template_attribute.sample_controlled_vocab type += ' - ' + link_to(template_attribute.sample_controlled_vocab.title, template_attribute.sample_controlled_vocab) end From e04dcf57df6e71b69073685766395548061210a2 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 6 Mar 2024 11:05:28 +0100 Subject: [PATCH 214/344] Add generic attribute validation to template_attribute model --- app/models/template_attribute.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/template_attribute.rb b/app/models/template_attribute.rb index 377422eb45..3e12de3e88 100644 --- a/app/models/template_attribute.rb +++ b/app/models/template_attribute.rb @@ -1,4 +1,5 @@ class TemplateAttribute < ApplicationRecord + include Seek::JSONMetadata::Attribute belongs_to :sample_controlled_vocab belongs_to :sample_attribute_type belongs_to :template, inverse_of: :template_attributes From ec2d7e7e8fa2b0881b0e1af8ed273d04cc3b6777 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 6 Mar 2024 10:39:42 +0000 Subject: [PATCH 215/344] Improve how remote git blobs are displayed. #1776 --- app/views/git/_blob.html.erb | 13 ++++++++++--- lib/seek/renderers/text_renderer.rb | 8 +++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/views/git/_blob.html.erb b/app/views/git/_blob.html.erb index d89bb8f8c0..8bbf2d40dd 100644 --- a/app/views/git/_blob.html.erb +++ b/app/views/git/_blob.html.erb @@ -8,8 +8,13 @@
    - <%= button_link_to('Download', 'download', polymorphic_path([@parent_resource, :git_download], version: @git_version.version, path: @blob.path)) %> - <%= button_link_to('Raw', 'markup', polymorphic_path([@parent_resource, :git_raw], version: @git_version.version, path: @blob.path)) %> + <% if @blob.fetched? %> + <%= button_link_to('Download', 'download', polymorphic_path([@parent_resource, :git_download], version: @git_version.version, path: @blob.path)) %> + <%= button_link_to('Raw', 'markup', polymorphic_path([@parent_resource, :git_raw], version: @git_version.version, path: @blob.path)) %> + <% end %> + <% if @blob.remote? %> + <%= button_link_to('External Link', 'external_link', @blob.url, target: :_blank) %> + <% end %> <% if @git_version.can_edit? %> <% if @git_version.mutable? %> <%= button_link_to('Move/rename', 'move', '#', { 'data-toggle' => 'modal', 'data-target' => '#git-move-modal' }) %> @@ -25,7 +30,9 @@
    Path: <%= @blob.path -%>
    - Size: <%= number_to_human_size @blob.size -%>
    + <% if @blob.fetched? %> + Size: <%= number_to_human_size @blob.size -%>
    + <% end %> diff --git a/lib/seek/renderers/text_renderer.rb b/lib/seek/renderers/text_renderer.rb index 62d013ee61..b6ca7797c2 100644 --- a/lib/seek/renderers/text_renderer.rb +++ b/lib/seek/renderers/text_renderer.rb @@ -6,7 +6,13 @@ def can_render? end def render_content - "
    #{h(blob.read)}
    " + content = blob.read + if content.empty? + 'No content to display' + else + "
    #{h(content)}
    " + end + end def render_standalone From 146c2574da189fc862a46f4757431b49de590302 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 6 Mar 2024 11:39:57 +0100 Subject: [PATCH 216/344] Make an exception Isa Json compliant sample attributes and template attributes --- lib/seek/json_metadata/attribute.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index d6954c231e..f3ef709dbe 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -78,9 +78,9 @@ def linked_sample_type_and_attribute_type_consistency if sample_attribute_type && linked_sample_type && !seek_sample? && !seek_sample_multi? errors.add(:sample_attribute_type, 'Attribute type must be SeekSample if linked sample type set') end - if seek_sample? && linked_sample_type.nil? + if seek_sample? && linked_sample_type.nil? && is_isa_compliant_input? errors.add(:seek_sample, 'Linked Sample Type must be set if attribute type is Registered Sample') - elsif seek_sample_multi? && linked_sample_type.nil? + elsif seek_sample_multi? && linked_sample_type.nil? && is_isa_compliant_input? errors.add(:seek_sample_multi, 'Linked Sample Type must be set if attribute type is Registered Sample List') end end @@ -94,6 +94,13 @@ def check_value_against_base_type(value) base_type_handler.validate_value?(value) end + def is_isa_compliant_input? + if is_a?(SampleAttribute) + !(input_attribute? && sample_type.is_isa_json_compliant?) + else + !input_attribute? + end + end end end end From 94874f5fd8499a8d3d8faaaf3ecf9d0e85158c65 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 6 Mar 2024 16:24:22 +0000 Subject: [PATCH 217/344] Restrict `download` and `raw` for unfetched remote blobs. Tests --- app/controllers/git_controller.rb | 7 +++++++ test/functional/git_controller_test.rb | 29 ++++++++++++++++++++++++++ test/unit/renderers_test.rb | 6 ++++++ 3 files changed, 42 insertions(+) diff --git a/app/controllers/git_controller.rb b/app/controllers/git_controller.rb index bbcce46bbd..d3f39f5963 100644 --- a/app/controllers/git_controller.rb +++ b/app/controllers/git_controller.rb @@ -8,6 +8,7 @@ class GitController < ApplicationController before_action :fetch_git_version before_action :get_tree, only: [:tree] before_action :get_blob, only: [:blob, :download, :raw] + before_action :check_fetched, only: [:download, :raw] user_content_actions :raw @@ -257,6 +258,12 @@ def log_event end end + def check_fetched + if @blob.remote? && !@blob.fetched? + render_git_error('This file is held externally.', status: 404) + end + end + # # Rugged does not allow streaming blobs # def stream_blob(blob, filename) # response.headers['Content-Disposition'] = "attachment; filename=#{filename}" diff --git a/test/functional/git_controller_test.rb b/test/functional/git_controller_test.rb index 5a43c947ee..5223dcb68f 100644 --- a/test/functional/git_controller_test.rb +++ b/test/functional/git_controller_test.rb @@ -210,6 +210,17 @@ def setup assert_select 'img.git-image-preview[src=?]', workflow_git_raw_path(@workflow, version: @git_version.version, path: 'test.svg') end + test 'get remote blob' do + @git_version.add_remote_file('something', 'https://example.com/something', fetch: false) + @git_version.save! + get :blob, params: { workflow_id: @workflow.id, version: @git_version.version, path: 'something' } + + assert_response :success + assert_select 'a.btn[href=?]', workflow_git_remove_file_path(@workflow, version: @git_version.version, path: 'something') + assert_select 'a.btn[href=?]', 'https://example.com/something' + assert_select 'img.git-image-preview[src=?]', workflow_git_raw_path(@workflow, version: @git_version.version, path: 'something'), count: 0 + end + test 'get raw binary file' do get :raw, params: { workflow_id: @workflow.id, version: @git_version.version, path: 'diagram.png' } @@ -224,6 +235,24 @@ def setup assert @response.header['Content-Disposition'].include?('attachment') end + test 'attempting to download remote blob throws error' do + @git_version.add_remote_file('something', 'https://example.com/something', fetch: false) + @git_version.save! + get :download, params: { workflow_id: @workflow.id, version: @git_version.version, path: 'something' } + + assert_redirected_to workflow_path(@workflow, tab: 'files') + assert flash[:error].include?('held externally') + end + + test 'attempting to get raw remote blob throws error' do + @git_version.add_remote_file('something', 'https://example.com/something', fetch: false) + @git_version.save! + get :raw, params: { workflow_id: @workflow.id, version: @git_version.version, path: 'something' } + + assert_redirected_to workflow_path(@workflow, tab: 'files') + assert flash[:error].include?('held externally') + end + test 'getting non-existent blob throws error' do get :blob, params: { workflow_id: @workflow.id, version: @git_version.version, path: 'doesnotexist' } diff --git a/test/unit/renderers_test.rb b/test/unit/renderers_test.rb index 0f80fc7f4d..32c42d06f2 100644 --- a/test/unit/renderers_test.rb +++ b/test/unit/renderers_test.rb @@ -363,6 +363,12 @@ class RenderersTest < ActiveSupport::TestCase blob = FactoryBot.create(:image_content_blob, asset: @asset) renderer = Seek::Renderers::TextRenderer.new(blob) refute renderer.can_render? + + @git.add_remote_file('empty', 'https://example.com/file', fetch: false) + git_blob = @git.get_blob('empty') + renderer = Seek::Renderers::TextRenderer.new(git_blob) + assert renderer.can_render? + assert_equal 'No content to display', renderer.render end test 'image renderer' do From 03bd53709145548b2f9daa68bb1088c8fbe7eaa6 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 28 Feb 2024 15:55:26 +0000 Subject: [PATCH 218/344] update github actions for cache, checkout, and setup-java - due to deprecation warnings --- .github/workflows/ansible-install.yml | 2 +- .github/workflows/docker-image.yml | 9 +++------ .github/workflows/tests.yml | 6 +++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ansible-install.yml b/.github/workflows/ansible-install.yml index b0b6cc9611..290cefc69e 100644 --- a/.github/workflows/ansible-install.yml +++ b/.github/workflows/ansible-install.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure ansible for local install working-directory: /home/runner/work/seek/seek/script/ansible/ diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index e4c22fe333..229d099530 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -6,11 +6,8 @@ on: - main - workflow - workflowhub - - seek-1.11 - - seek-1.12 - - seek-1.13 - - master-ibisba-demonstrator - - ruby-3 + - full-test-suite + pull_request: jobs: @@ -20,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Build the Docker image run: docker build . --file Dockerfile --tag test-image:$(date +%s) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ca563f20ac..f9ef27ba75 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,9 +66,9 @@ jobs: sudo apt install -y graphicsmagick graphviz libcurl4-gnutls-dev libreoffice poppler-utils build-essential \ git imagemagick libgmp-dev python3.9-dev python3.9-distutils python3-pip - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' # The JDK version to make available on the path. @@ -77,7 +77,7 @@ jobs: with: bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Cache pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: # This path is specific to Ubuntu path: ~/.cache/pip From e587a7b5d50e557102cc6334efc3edced00480f4 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 7 Mar 2024 19:19:59 +0000 Subject: [PATCH 219/344] Initial attempt at custom OIDC provider --- app/controllers/sessions_controller.rb | 2 ++ app/helpers/sessions_helper.rb | 4 ++++ app/views/gadgets/_sign_in.html.erb | 12 ++++++++++++ app/views/identities/index.html.erb | 3 +++ app/views/users/new.html.erb | 3 +++ config/initializers/seek_configuration.rb | 2 ++ lib/seek/config.rb | 16 ++++++++++++++++ lib/seek/config_setting_attributes.yml | 4 ++++ 8 files changed, 46 insertions(+) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index b532c1a637..4d1db3f1ad 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -30,6 +30,8 @@ def create Seek::Config.omniauth_ldap_enabled when 'elixir_aai' Seek::Config.omniauth_elixir_aai_enabled + when 'oidc' + Seek::Config.omniauth_oidc_enabled else true end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 07becce270..7e3ca96996 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -47,4 +47,8 @@ def show_ldap_login? def show_github_login? Seek::Config.omniauth_github_enabled end + + def show_oidc_login? + Seek::Config.omniauth_oidc_enabled + end end diff --git a/app/views/gadgets/_sign_in.html.erb b/app/views/gadgets/_sign_in.html.erb index dea3d33bf3..ae8d75dba2 100644 --- a/app/views/gadgets/_sign_in.html.erb +++ b/app/views/gadgets/_sign_in.html.erb @@ -31,6 +31,11 @@ <%= t('login.github') %> <% end %> <% end %> + <% if show_oidc_login? %> + <%= content_tag(:li, role: 'presentation', class: strategy == 'oidc' ? 'active' : nil) do %> + <%= Seek::Config.omniauth_oidc_name %> + <% end %> + <% end %> <% end #omniauth enabled and providers available %> @@ -83,6 +88,13 @@
    <% end %> <% end %> + <% if show_oidc_login? %> + <%= content_tag(:div, id: 'oidc_login', role: 'tabpanel', class: strategy == 'oidc' ? 'tab-pane active' : 'tab-pane') do %> +
    + <%= button_link_to Seek::Config.omniauth_oidc_name, 'lock', omniauth_authorize_path(:oidc, state: "return_to:#{original_path}"), method: :post %> +
    + <% end %> + <% end %> <% end #omniauth enabled and providers available %>
    diff --git a/app/views/identities/index.html.erb b/app/views/identities/index.html.erb index 321dc5d11d..72fbb297c2 100644 --- a/app/views/identities/index.html.erb +++ b/app/views/identities/index.html.erb @@ -10,6 +10,9 @@ <% if Seek::Config.omniauth_github_enabled %>
  • <%= link_to(t("login.github"), omniauth_authorize_path(:github), method: :post) -%>
  • <% end %> + <% if Seek::Config.omniauth_oidc_enabled %> +
  • <%= link_to(Seek::Config.omniauth_oidc_name, omniauth_authorize_path(:oidc), method: :post) -%>
  • + <% end %> <% end %> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 4d36e5dce7..aca0d167ba 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -90,6 +90,9 @@ <% end %> <% end %> + <% if show_oidc_login? %> +
  • <%= link_to "Log in using #{Seek::Config.omniauth_oidc_name}", omniauth_authorize_path(:oidc, state: "return_to:/") %>
  • + <% end %> <% end %> diff --git a/config/initializers/seek_configuration.rb b/config/initializers/seek_configuration.rb index a3078a1309..371d8f8a4d 100644 --- a/config/initializers/seek_configuration.rb +++ b/config/initializers/seek_configuration.rb @@ -237,6 +237,8 @@ def load_seek_config_defaults! password: '', bind_dn: '' } + Seek::Config.default :omniauth_oidc_enabled, false + Seek::Config.default :omniauth_oidc_name, 'OpenID Connect Provider' Seek::Config.default :openbis_enabled,false Seek::Config.default :openbis_download_limit, 2.gigabytes diff --git a/lib/seek/config.rb b/lib/seek/config.rb index ffcc4484ca..d233149a7e 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -375,11 +375,27 @@ def omniauth_github_config [omniauth_github_client_id, omniauth_github_secret, { scope: 'user:email' }] end + def omniauth_oidc_config + # Cannot use url helpers here because routes are not loaded at this point :( -Finn + callback_path = 'identities/auth/oidc/callback' + + omniauth_oidc_settings.merge( + { + callback_path: "#{Rails.application.config.relative_url_root}/#{callback_path}", + name: :oidc, + response_type: 'code', + discovery: true, + client_options: omniauth_oidc_settings.merge( + redirect_uri: site_base_url.join(callback_path).to_s) + }) + end + def omniauth_providers providers = {} providers[:ldap] = omniauth_ldap_config.merge(name: :ldap, form: SessionsController.action(:new)) if omniauth_ldap_enabled providers[:openid_connect] = omniauth_elixir_aai_config if omniauth_elixir_aai_enabled providers[:github] = omniauth_github_config if omniauth_github_enabled + providers[:oidc] = omniauth_oidc_config if omniauth_oidc_enabled providers end end diff --git a/lib/seek/config_setting_attributes.yml b/lib/seek/config_setting_attributes.yml index a026837c8b..4c97c2adf9 100644 --- a/lib/seek/config_setting_attributes.yml +++ b/lib/seek/config_setting_attributes.yml @@ -232,6 +232,10 @@ omniauth_github_enabled: omniauth_github_client_id: omniauth_github_secret: encrypt: true +omniauth_oidc_enabled: +omniauth_oidc_name: +omniauth_oidc_settings: + encrypt: true ga4gh_trs_api_enabled: search_results_limit: convert: :to_i From f94e51a4a4db61213d9a37a990e9a82eea75c9f5 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 8 Mar 2024 08:36:49 +0100 Subject: [PATCH 220/344] Add sample_attribute_types to tests --- test/unit/template_attribute_test.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/unit/template_attribute_test.rb b/test/unit/template_attribute_test.rb index dc6e092cd0..1f4c4fa2c2 100644 --- a/test/unit/template_attribute_test.rb +++ b/test/unit/template_attribute_test.rb @@ -1,12 +1,18 @@ require 'test_helper' + class TemplateAttributeTest < ActiveSupport::TestCase test 'allow isa tag change' do # When template doesn't have child templates => editable # When template has child templates => disabled - parent_attribute = FactoryBot.create(:template_attribute) - child_attribute = FactoryBot.create(:template_attribute, parent_attribute: parent_attribute) - parentless_attribute = FactoryBot.create(:template_attribute) + string_type = FactoryBot.create(:string_sample_attribute_type) + + parent_attribute = FactoryBot.create(:template_attribute, + sample_attribute_type: string_type) + child_attribute = FactoryBot.create(:template_attribute, + parent_attribute: parent_attribute, + sample_attribute_type: string_type) + parentless_attribute = FactoryBot.create(:template_attribute, sample_attribute_type: string_type) assert parentless_attribute.allow_isa_tag_change? refute child_attribute.allow_isa_tag_change? end From 109209fbd2df19a84c8a210934b598172b694e11 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 8 Mar 2024 10:32:35 +0100 Subject: [PATCH 221/344] Replace isa_tag by isa_tag_id --- app/models/template_attribute.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/template_attribute.rb b/app/models/template_attribute.rb index 3e12de3e88..96032e1c29 100644 --- a/app/models/template_attribute.rb +++ b/app/models/template_attribute.rb @@ -27,7 +27,7 @@ def inherited? end def input_attribute? - isa_tag.nil? && title&.downcase&.include?('input') && sample_attribute_type.seek_sample_multi? + isa_tag_id && title&.downcase&.include?('input') && sample_attribute_type.seek_sample_multi? end private From 4d1f2d8095fc3dc94cc2d4f8957dee83170bd56a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 8 Mar 2024 11:18:40 +0100 Subject: [PATCH 222/344] Add test for defining input template attributes --- test/factories/template_attributes.rb | 2 +- test/unit/template_attribute_test.rb | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/test/factories/template_attributes.rb b/test/factories/template_attributes.rb index 1ddeb12b9a..8547fdf25d 100644 --- a/test/factories/template_attributes.rb +++ b/test/factories/template_attributes.rb @@ -3,7 +3,7 @@ factory(:template_attribute) do sequence(:title) { |n| "Template attribute #{n}" } association :template, factory: :template - isa_tag_id { FactoryBot.create(:default_isa_tag).id } + isa_tag_id { nil } end factory(:apples_controlled_vocab_template_attribute, parent: :template_attribute) do diff --git a/test/unit/template_attribute_test.rb b/test/unit/template_attribute_test.rb index 1f4c4fa2c2..614b534c52 100644 --- a/test/unit/template_attribute_test.rb +++ b/test/unit/template_attribute_test.rb @@ -2,18 +2,35 @@ class TemplateAttributeTest < ActiveSupport::TestCase + def setup + @string_type = FactoryBot.create(:string_sample_attribute_type) + @registered_sample_multi_attribute_type = FactoryBot.create(:sample_multi_sample_attribute_type) + @registered_sample_attribute_type = FactoryBot.create(:sample_sample_attribute_type) + end + test 'allow isa tag change' do # When template doesn't have child templates => editable # When template has child templates => disabled - string_type = FactoryBot.create(:string_sample_attribute_type) parent_attribute = FactoryBot.create(:template_attribute, - sample_attribute_type: string_type) + sample_attribute_type: @string_type) child_attribute = FactoryBot.create(:template_attribute, parent_attribute: parent_attribute, - sample_attribute_type: string_type) - parentless_attribute = FactoryBot.create(:template_attribute, sample_attribute_type: string_type) + sample_attribute_type: @string_type) + parentless_attribute = FactoryBot.create(:template_attribute, sample_attribute_type: @string_type) assert parentless_attribute.allow_isa_tag_change? refute child_attribute.allow_isa_tag_change? end + + test 'is input attribute?' do + # When isa tag is nil, title includes 'input' and sample attribute type is seek sample multi => true + # Otherwise => false + + attribute = FactoryBot.create(:template_attribute, + sample_attribute_type: @registered_sample_multi_attribute_type, + title: 'Input attribute') + assert attribute.input_attribute? + attribute.isa_tag = FactoryBot.create(:source_characteristic_isa_tag) + refute attribute.input_attribute? + end end From 55322ec143d82304cdb7ee474b7fba72d0441df7 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 8 Mar 2024 11:28:52 +0100 Subject: [PATCH 223/344] Revert isa_tag_id to isa_tag --- app/models/template_attribute.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/template_attribute.rb b/app/models/template_attribute.rb index 96032e1c29..3e12de3e88 100644 --- a/app/models/template_attribute.rb +++ b/app/models/template_attribute.rb @@ -27,7 +27,7 @@ def inherited? end def input_attribute? - isa_tag_id && title&.downcase&.include?('input') && sample_attribute_type.seek_sample_multi? + isa_tag.nil? && title&.downcase&.include?('input') && sample_attribute_type.seek_sample_multi? end private From 45c6cf5c398b5a0b21832e6ebe53a58faf5e524c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 8 Mar 2024 11:32:01 +0100 Subject: [PATCH 224/344] Add test for registered samples attributes --- test/functional/templates_controller_test.rb | 145 +++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/test/functional/templates_controller_test.rb b/test/functional/templates_controller_test.rb index 83e1d6622e..d4ed993acf 100644 --- a/test/functional/templates_controller_test.rb +++ b/test/functional/templates_controller_test.rb @@ -16,6 +16,8 @@ class TemplatesControllerTest < ActionController::TestCase @template = FactoryBot.create(:min_template, project_ids: @project_ids, contributor: @person) @string_type = FactoryBot.create(:string_sample_attribute_type) @int_type = FactoryBot.create(:integer_sample_attribute_type) + @registered_sample_attribute_type = FactoryBot.create(:sample_sample_attribute_type) + @registered_sample_multi_attribute_type = FactoryBot.create(:sample_multi_sample_attribute_type) @controlled_vocab_type = FactoryBot.create(:controlled_vocab_attribute_type) @default_isa_tag = FactoryBot.create(:default_isa_tag) end @@ -307,6 +309,149 @@ class TemplatesControllerTest < ActionController::TestCase end + test 'should not allow to create template with registered sample template attributes with no linked sample type' do + source_isa_tag = FactoryBot.create(:source_isa_tag) + source_characteristic_isa_tag = FactoryBot.create(:source_characteristic_isa_tag) + source_attribute = { + pos: '1', + title: 'Source Name', + required: '1', + short_name: 'attribute1 short name', + ontology_version: '0.1.1', + description: 'attribute1 description', + isa_tag_id: source_isa_tag.id, + sample_attribute_type_id: @string_type.id, + is_title: '1', + _destroy: '0' + } + + bad_registered_sample_attribute = { + pos: '2', + title: 'Correct registered sample attribute', + required: '1', + short_name: 'attribute2 short name', + ontology_version: '0.1.2', + description: 'attribute2 description', + isa_tag_id: source_characteristic_isa_tag, + sample_attribute_type_id: @registered_sample_attribute_type.id, + linked_sample_type_id: nil, + _destroy: '0' + } + + bad_template_params = { title: 'Bad template', + project_ids: @project_ids, + level: 'study source', + organism: 'any', + version: '1.0.0', + parent_id: nil, + description: 'Template containing registered samples attributes with no linked sample type', + template_attributes_attributes: { + '0' => source_attribute, + '1' => bad_registered_sample_attribute + } } + + assert_no_difference('Template.count') do + post :create, params: { template: bad_template_params } + assert_response :unprocessable_entity + assert_template :new + assert_select 'div#error_explanation' + end + + sample_type = FactoryBot.create(:simple_sample_type, project_ids: @project_ids, contributor: @person) + + correct_registered_sample_attribute = { + pos: '2', + title: 'Correct registered sample attribute', + required: '1', + short_name: 'attribute2 short name', + ontology_version: '0.1.2', + description: 'attribute2 description', + isa_tag_id: source_characteristic_isa_tag, + sample_attribute_type_id: @registered_sample_attribute_type.id, + linked_sample_type_id: sample_type.id, + _destroy: '0' + } + + correct_source_template_params = { title: 'Correct source template', + project_ids: @project_ids, + level: 'study source', + organism: 'any', + version: '1.0.0', + parent_id: nil, + description: 'Template containing attributes with no isa tags', + template_attributes_attributes: { + '0' => source_attribute, + '1' => correct_registered_sample_attribute + } } + + assert_difference('Template.count', 1) do + post :create, params: { template: correct_source_template_params } + assert_response :redirect + assert_redirected_to template_path(assigns(:template)) + end + + sample_isa_tag = FactoryBot.create(:sample_isa_tag) + sample_characteristic_isa_tag = FactoryBot.create(:sample_characteristic_isa_tag) + + input_sample_attribute = { + pos: '1', + title: 'Input (Source sample)', + required: '1', + short_name: 'attribute1 short name', + ontology_version: '0.1.2', + description: 'attribute1 description', + sample_attribute_type_id: @registered_sample_multi_attribute_type.id, + linked_sample_type_id: nil, + _destroy: '0' + } + + collected_sample_attribute = { + pos: '2', + title: 'Sample Name', + required: '1', + short_name: 'attribute2 short name', + ontology_version: '0.1.1', + description: 'attribute2 description', + isa_tag_id: sample_isa_tag.id, + sample_attribute_type_id: @string_type.id, + is_title: '1', + _destroy: '0' + } + + sample_characteristic_attribute = { + pos: '3', + title: 'Correct registered sample attribute', + required: '1', + short_name: 'attribute3 short name', + ontology_version: '0.1.2', + description: 'attribute3 description', + isa_tag_id: sample_characteristic_isa_tag.id, + sample_attribute_type_id: @registered_sample_attribute_type.id, + linked_sample_type_id: sample_type.id, + _destroy: '0' + } + + correct_sample_collection_template_params = { title: 'Correct Sample collection template', + project_ids: @project_ids, + level: 'study sample', + organism: 'any', + version: '1.0.0', + parent_id: nil, + description: 'Sample collection template made correctly', + template_attributes_attributes: { + '0' => input_sample_attribute, + '1' => collected_sample_attribute, + '2' => sample_characteristic_attribute + } } + + assert_difference('Template.count', 1) do + post :create, params: { template: correct_sample_collection_template_params } + assert_response :redirect + assert_redirected_to template_path(assigns(:template)) + end + + end + def create_template_from_parent_template(parent_template, person= @person, linked_sample_type= nil) child_template_attributes = parent_template.template_attributes.map do |ta| FactoryBot.create(:template_attribute, parent_attribute_id: ta.id, title: ta.title, isa_tag_id: ta.isa_tag_id, sample_attribute_type: ta.sample_attribute_type, is_title: ta.is_title, required: ta.required, sample_controlled_vocab: ta.sample_controlled_vocab, pos: ta.pos) From 180ce85b9f4ad80693323cbe09946c92aea04505 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 8 Mar 2024 12:09:17 +0100 Subject: [PATCH 225/344] Add test for preventing the user to make controlled vocabulary template attributes with no sample controlled vocabulary --- test/functional/templates_controller_test.rb | 82 +++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/test/functional/templates_controller_test.rb b/test/functional/templates_controller_test.rb index d4ed993acf..2f0b779391 100644 --- a/test/functional/templates_controller_test.rb +++ b/test/functional/templates_controller_test.rb @@ -354,7 +354,7 @@ class TemplatesControllerTest < ActionController::TestCase post :create, params: { template: bad_template_params } assert_response :unprocessable_entity assert_template :new - assert_select 'div#error_explanation' + assert_select 'div#error_explanation', text: /Linked Sample Type must be set if attribute type is Registered Sample/ end sample_type = FactoryBot.create(:simple_sample_type, project_ids: @project_ids, contributor: @person) @@ -452,6 +452,86 @@ class TemplatesControllerTest < ActionController::TestCase end + test 'should not allow to create template with controlled vocabulary template attributes with no sample controlled vocabulary' do + source_isa_tag = FactoryBot.create(:source_isa_tag) + source_characteristic_isa_tag = FactoryBot.create(:source_characteristic_isa_tag) + source_attribute = { + pos: '1', + title: 'Source Name', + required: '1', + short_name: 'attribute1 short name', + ontology_version: '0.1.1', + description: 'attribute1 description', + isa_tag_id: source_isa_tag.id, + sample_attribute_type_id: @string_type.id, + is_title: '1', + _destroy: '0' + } + + bad_controlled_vocab_attribute = { + pos: '2', + title: 'Correct registered sample attribute', + required: '1', + short_name: 'attribute2 short name', + ontology_version: '0.1.2', + description: 'attribute2 description', + isa_tag_id: source_characteristic_isa_tag, + sample_attribute_type_id: @controlled_vocab_type.id, + _destroy: '0' + } + + bad_template_params = { title: 'Bad template', + project_ids: @project_ids, + level: 'study source', + organism: 'any', + version: '1.0.0', + parent_id: nil, + description: 'Template containing controlled vocabulary attributes with no sample controlled vocabulary', + template_attributes_attributes: { + '0' => source_attribute, + '1' => bad_controlled_vocab_attribute + } } + + assert_no_difference('Template.count') do + post :create, params: { template: bad_template_params } + assert_response :unprocessable_entity + assert_template :new + assert_select 'div#error_explanation', text: /Controlled vocabulary must be set if attribute type is CV/ + end + + controlled_vocab = FactoryBot.create(:apples_sample_controlled_vocab) + correct_controlled_vocab_attribute = { + pos: '2', + title: 'Correct registered sample attribute', + required: '1', + short_name: 'attribute2 short name', + ontology_version: '0.1.2', + description: 'attribute2 description', + isa_tag_id: source_characteristic_isa_tag, + sample_attribute_type_id: @controlled_vocab_type.id, + sample_controlled_vocab_id: controlled_vocab.id, + _destroy: '0' + } + + correct_source_template_params = { title: 'Correct source template', + project_ids: @project_ids, + level: 'study source', + organism: 'any', + version: '1.0.0', + parent_id: nil, + description: 'Template containing controlled vocabulary attributes with no linked sample type', + template_attributes_attributes: { + '0' => source_attribute, + '1' => correct_controlled_vocab_attribute + } } + + assert_difference('Template.count', 1) do + post :create, params: { template: correct_source_template_params } + assert_response :redirect + assert_redirected_to template_path(assigns(:template)) + end + end + def create_template_from_parent_template(parent_template, person= @person, linked_sample_type= nil) child_template_attributes = parent_template.template_attributes.map do |ta| FactoryBot.create(:template_attribute, parent_attribute_id: ta.id, title: ta.title, isa_tag_id: ta.isa_tag_id, sample_attribute_type: ta.sample_attribute_type, is_title: ta.is_title, required: ta.required, sample_controlled_vocab: ta.sample_controlled_vocab, pos: ta.pos) From 4f0c5399c4b94c7d9d22ca2e24769e2d1ffdbb23 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Fri, 8 Mar 2024 12:13:31 +0100 Subject: [PATCH 226/344] 1. Allowing users with edit rights to delete their uploaded fulltext for publications 2. Incorrect response message following deletion of publication fulltext --- app/controllers/publications_controller.rb | 7 ++++--- app/models/publication.rb | 6 ------ test/functional/publications_controller_test.rb | 9 ++++++--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/controllers/publications_controller.rb b/app/controllers/publications_controller.rb index 4b3ae2a496..de5560f6fb 100644 --- a/app/controllers/publications_controller.rb +++ b/app/controllers/publications_controller.rb @@ -141,18 +141,19 @@ def upload_pdf def create_new_version comments if @publication.save_as_new_version(comments) - flash[:notice]="New full text uploaded #{@publication.version}" + flash[:notice]="The new full text has been successfully uploaded." else - flash[:error]="Unable to save new fulltext" + flash[:error]="The full text can not be saved." end end def soft_delete_fulltext # replace this version as a new empty version - if @publication.can_soft_delete_full_text? + if @publication.can_delete? # create an empty version respond_to do |format| create_new_version 'Soft delete' + flash[:notice] = 'The attached full text for the publication was successfully deleted.' format.html { redirect_to @publication } end else diff --git a/app/models/publication.rb b/app/models/publication.rb index 9e378ff882..81c20429b4 100755 --- a/app/models/publication.rb +++ b/app/models/publication.rb @@ -556,12 +556,6 @@ def latest_citable_resource self end - def can_soft_delete_full_text?(user = User.current_user) - return false if user.nil? || user.person.nil? || !Seek::Config.allow_publications_fulltext - return true if user.is_admin? - contributor == can_edit(user) || projects.detect { |project| project.can_manage?(user) }.present? - end - private def populate_policy_from_authors(pol) diff --git a/test/functional/publications_controller_test.rb b/test/functional/publications_controller_test.rb index 13fb186e03..896184e6cd 100644 --- a/test/functional/publications_controller_test.rb +++ b/test/functional/publications_controller_test.rb @@ -1447,10 +1447,13 @@ def test_title end end - test 'can soft-delete content_blob' do - publication = FactoryBot.create :max_publication, contributor: User.current_user.person + test 'can soft-delete content_blob if the user is submitter' do + person = FactoryBot.create(:person) + publication = FactoryBot.create :max_publication, contributor: person + + refute person.user.is_admin? - login_as(User.current_user.person) + login_as(person) with_config_value(:allow_publications_fulltext, true) do assert_difference('Publication::Version.count', 1) do From d26e9b54764cc1cfc519745d801cd700162132d8 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 8 Mar 2024 12:18:08 +0100 Subject: [PATCH 227/344] Add test for preventing the user to make controlled vocabulary list template attributes with no sample controlled vocabulary --- test/functional/templates_controller_test.rb | 83 +++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/test/functional/templates_controller_test.rb b/test/functional/templates_controller_test.rb index 2f0b779391..d9a47f6295 100644 --- a/test/functional/templates_controller_test.rb +++ b/test/functional/templates_controller_test.rb @@ -19,6 +19,7 @@ class TemplatesControllerTest < ActionController::TestCase @registered_sample_attribute_type = FactoryBot.create(:sample_sample_attribute_type) @registered_sample_multi_attribute_type = FactoryBot.create(:sample_multi_sample_attribute_type) @controlled_vocab_type = FactoryBot.create(:controlled_vocab_attribute_type) + @controlled_vocab_list_type = FactoryBot.create(:cv_list_attribute_type) @default_isa_tag = FactoryBot.create(:default_isa_tag) end @@ -519,7 +520,7 @@ class TemplatesControllerTest < ActionController::TestCase organism: 'any', version: '1.0.0', parent_id: nil, - description: 'Template containing controlled vocabulary attributes with no linked sample type', + description: 'Template containing controlled vocabulary attributes with sample controlled vocabulary', template_attributes_attributes: { '0' => source_attribute, '1' => correct_controlled_vocab_attribute @@ -532,6 +533,86 @@ class TemplatesControllerTest < ActionController::TestCase end end + test 'should not allow to create template with controlled vocabulary list template attributes with no sample controlled vocabulary' do + source_isa_tag = FactoryBot.create(:source_isa_tag) + source_characteristic_isa_tag = FactoryBot.create(:source_characteristic_isa_tag) + source_attribute = { + pos: '1', + title: 'Source Name', + required: '1', + short_name: 'attribute1 short name', + ontology_version: '0.1.1', + description: 'attribute1 description', + isa_tag_id: source_isa_tag.id, + sample_attribute_type_id: @string_type.id, + is_title: '1', + _destroy: '0' + } + + bad_controlled_vocab_list_attribute = { + pos: '2', + title: 'Correct registered sample attribute', + required: '1', + short_name: 'attribute2 short name', + ontology_version: '0.1.2', + description: 'attribute2 description', + isa_tag_id: source_characteristic_isa_tag, + sample_attribute_type_id: @controlled_vocab_list_type.id, + _destroy: '0' + } + + bad_template_params = { title: 'Bad template', + project_ids: @project_ids, + level: 'study source', + organism: 'any', + version: '1.0.0', + parent_id: nil, + description: 'Template containing controlled vocabulary list attributes with no sample controlled vocabulary', + template_attributes_attributes: { + '0' => source_attribute, + '1' => bad_controlled_vocab_list_attribute + } } + + assert_no_difference('Template.count') do + post :create, params: { template: bad_template_params } + assert_response :unprocessable_entity + assert_template :new + assert_select 'div#error_explanation', text: /Controlled vocabulary must be set if attribute type is LIST/ + end + + controlled_vocab = FactoryBot.create(:apples_sample_controlled_vocab) + correct_controlled_vocab_list_attribute = { + pos: '2', + title: 'Correct registered sample attribute', + required: '1', + short_name: 'attribute2 short name', + ontology_version: '0.1.2', + description: 'attribute2 description', + isa_tag_id: source_characteristic_isa_tag, + sample_attribute_type_id: @controlled_vocab_list_type.id, + sample_controlled_vocab_id: controlled_vocab.id, + _destroy: '0' + } + + correct_source_template_params = { title: 'Correct source template', + project_ids: @project_ids, + level: 'study source', + organism: 'any', + version: '1.0.0', + parent_id: nil, + description: 'Template containing controlled vocabulary attributes with sample controlled vocabulary', + template_attributes_attributes: { + '0' => source_attribute, + '1' => correct_controlled_vocab_list_attribute + } } + + assert_difference('Template.count', 1) do + post :create, params: { template: correct_source_template_params } + assert_response :redirect + assert_redirected_to template_path(assigns(:template)) + end + end + def create_template_from_parent_template(parent_template, person= @person, linked_sample_type= nil) child_template_attributes = parent_template.template_attributes.map do |ta| FactoryBot.create(:template_attribute, parent_attribute_id: ta.id, title: ta.title, isa_tag_id: ta.isa_tag_id, sample_attribute_type: ta.sample_attribute_type, is_title: ta.is_title, required: ta.required, sample_controlled_vocab: ta.sample_controlled_vocab, pos: ta.pos) From 8273d68618250daf3910501f9784a72494719453 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Fri, 8 Mar 2024 17:20:05 +0000 Subject: [PATCH 228/344] Allow custom OIDC provider to be configured --- app/controllers/admin_controller.rb | 7 +++++ app/helpers/sessions_helper.rb | 7 +++++ app/views/admin/_omniauth.html.erb | 14 ++++++++++ app/views/gadgets/_sign_in.html.erb | 8 +++--- app/views/identities/index.html.erb | 10 +++---- config/initializers/seek_omniauth.rb | 2 +- config/initializers/seek_testing.rb | 6 +++++ config/locales/en.yml | 1 + lib/seek/config.rb | 32 ++++++++++++----------- lib/seek/config_setting_attributes.yml | 4 ++- test/integration/omniauth_test.rb | 36 ++++++++++++++++++++++++++ 11 files changed, 101 insertions(+), 26 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 261b2d2c91..24051dce05 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -94,6 +94,13 @@ def update_features_enabled Seek::Config.omniauth_github_client_id = params[:omniauth_github_client_id] Seek::Config.omniauth_github_secret = params[:omniauth_github_secret] + Seek::Config.omniauth_oidc_enabled = string_to_boolean params[:omniauth_oidc_enabled] + Seek::Config.omniauth_oidc_name = params[:omniauth_oidc_name] + params[:omniauth_oidc_issuer] = params[:omniauth_oidc_issuer].chomp('/') + '/' if params[:omniauth_oidc_issuer].present? + Seek::Config.omniauth_oidc_issuer = params[:omniauth_oidc_issuer] + Seek::Config.omniauth_oidc_client_id = params[:omniauth_oidc_client_id] + Seek::Config.omniauth_oidc_secret = params[:omniauth_oidc_secret] + Seek::Config.solr_enabled = string_to_boolean params[:solr_enabled] Seek::Config.filtering_enabled = string_to_boolean params[:filtering_enabled] Seek::Config.jws_enabled = string_to_boolean params[:jws_enabled] diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 7e3ca96996..9db08896ff 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -51,4 +51,11 @@ def show_github_login? def show_oidc_login? Seek::Config.omniauth_oidc_enabled end + + def omniauth_method_name(key) + name = nil + name = Seek::Config.omniauth_oidc_name if key == :oidc + name = t("login.#{key}") if name.blank? + name + end end diff --git a/app/views/admin/_omniauth.html.erb b/app/views/admin/_omniauth.html.erb index 8df9f04650..22f8393eed 100644 --- a/app/views/admin/_omniauth.html.erb +++ b/app/views/admin/_omniauth.html.erb @@ -53,4 +53,18 @@ <%= admin_password_setting(:omniauth_github_secret, Seek::Config.omniauth_github_secret, 'GitHub Secret', 'The secret key provided by GitHub for your installation.') %> + + <%= admin_checkbox_setting(:omniauth_oidc_enabled, 1, Seek::Config.omniauth_oidc_enabled, + "#{t('login.oidc')} authentication", "Enables use of a #{t('login.oidc')} provider for authentication.", + onchange: toggle_appear_javascript('omniauth_oidc_settings')) %> +
    + <%= admin_text_setting(:omniauth_oidc_name, Seek::Config.omniauth_oidc_name, + "#{t('login.oidc')} Name", "The name of this authentication method that will be displayed to users.") %> + <%= admin_text_setting(:omniauth_oidc_issuer, Seek::Config.omniauth_oidc_issuer, + "#{t('login.oidc')} URL", "The base URL of the #{t('login.oidc')} provider. It is expected that the discovery endpoint /.well-known/openid-configuration exists under this URL.") %> + <%= admin_text_setting(:omniauth_oidc_client_id, Seek::Config.omniauth_oidc_client_id, + "#{t('login.oidc')} Client ID", "The client ID to use to authenticate with the #{t('login.oidc')} provider.") %> + <%= admin_password_setting(:omniauth_oidc_secret, Seek::Config.omniauth_oidc_secret, + "#{t('login.oidc')} Secret", "The secret to use to authenticate with the #{t('login.oidc')} provider.") %> +
    diff --git a/app/views/gadgets/_sign_in.html.erb b/app/views/gadgets/_sign_in.html.erb index ae8d75dba2..2297062896 100644 --- a/app/views/gadgets/_sign_in.html.erb +++ b/app/views/gadgets/_sign_in.html.erb @@ -18,22 +18,22 @@ <% end %> <% if show_elixir_aai_login? %> <%= content_tag(:li, role: 'presentation', class: strategy == 'elixir_aai' ? 'active' : nil) do %> - <%= t('login.elixir_aai') %> + <%= omniauth_method_name(:elixir_aai) %> <% end %> <% end %> <% if show_ldap_login? %> <%= content_tag(:li, role: 'presentation', class: strategy == 'ldap' ? 'active' : nil) do %> - <%= t('login.ldap') %> + <%= omniauth_method_name(:ldap) %> <% end %> <% end %> <% if show_github_login? %> <%= content_tag(:li, role: 'presentation', class: strategy == 'github' ? 'active' : nil) do %> - <%= t('login.github') %> + <%= omniauth_method_name(:github) %> <% end %> <% end %> <% if show_oidc_login? %> <%= content_tag(:li, role: 'presentation', class: strategy == 'oidc' ? 'active' : nil) do %> - <%= Seek::Config.omniauth_oidc_name %> + <%= omniauth_method_name(:oidc) %> <% end %> <% end %> diff --git a/app/views/identities/index.html.erb b/app/views/identities/index.html.erb index 72fbb297c2..1261ae77fa 100644 --- a/app/views/identities/index.html.erb +++ b/app/views/identities/index.html.erb @@ -2,16 +2,16 @@
    <%= dropdown_button('Add identity', 'add') do %> <% if Seek::Config.omniauth_elixir_aai_enabled %> -
  • <%= link_to(t("login.elixir_aai"), omniauth_authorize_path(:elixir_aai), method: :post) -%>
  • +
  • <%= link_to(omniauth_method_name(:elixir_aai), omniauth_authorize_path(:elixir_aai), method: :post) -%>
  • <% end %> <% if Seek::Config.omniauth_ldap_enabled %> -
  • <%= link_to(t("login.ldap"), '#', 'data-toggle' => 'modal', 'data-target' => "#ldap-form") -%>
  • +
  • <%= link_to(omniauth_method_name(:ldap), '#', 'data-toggle' => 'modal', 'data-target' => "#ldap-form") -%>
  • <% end %> <% if Seek::Config.omniauth_github_enabled %> -
  • <%= link_to(t("login.github"), omniauth_authorize_path(:github), method: :post) -%>
  • +
  • <%= link_to(omniauth_method_name(:github), omniauth_authorize_path(:github), method: :post) -%>
  • <% end %> <% if Seek::Config.omniauth_oidc_enabled %> -
  • <%= link_to(Seek::Config.omniauth_oidc_name, omniauth_authorize_path(:oidc), method: :post) -%>
  • +
  • <%= link_to(omniauth_method_name(:oidc), omniauth_authorize_path(:oidc), method: :post) -%>
  • <% end %> <% end %>
    @@ -48,7 +48,7 @@ <% @identities.each do |identity| %> - <%= t("login.#{identity.provider}") %> + <%= omniauth_method_name(identity.provider.to_sym) %> <%= content_tag(:pre, identity.uid) %> <%= identity.created_at %> diff --git a/config/initializers/seek_omniauth.rb b/config/initializers/seek_omniauth.rb index 34907d1cbd..765b1b73b8 100644 --- a/config/initializers/seek_omniauth.rb +++ b/config/initializers/seek_omniauth.rb @@ -7,7 +7,7 @@ begin providers = Seek::Config.omniauth_providers rescue Settings::DecryptionError - providers = {} + providers = [] end providers.each do |key, options| diff --git a/config/initializers/seek_testing.rb b/config/initializers/seek_testing.rb index c2abfb51bb..74921699a7 100644 --- a/config/initializers/seek_testing.rb +++ b/config/initializers/seek_testing.rb @@ -131,6 +131,12 @@ def load_seek_testing_defaults! Settings.defaults[:omniauth_github_client_id] = 'abc' Settings.defaults[:omniauth_github_secret] = '456' + Settings.defaults[:omniauth_oidc_enabled] = true + Settings.defaults[:omniauth_oidc_name] = 'SEEK Testing OIDC' + Settings.defaults[:omniauth_oidc_issuer] = 'https://example.com/oidc/' + Settings.defaults[:omniauth_oidc_client_id] = 'def' + Settings.defaults[:omniauth_oidc_secret] = '789' + Settings.defaults[:ga4gh_trs_api_enabled] = true Settings.defaults[:life_monitor_url] = 'https://localhost:8443' diff --git a/config/locales/en.yml b/config/locales/en.yml index d04a19a1bc..1303fdfb1a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -8,6 +8,7 @@ en: elixir_aai: 'LS Login' ldap: 'LDAP' github: 'GitHub' + oidc: 'OpenID Connect' single_page: "Single Page" default_view: "Default View" diff --git a/lib/seek/config.rb b/lib/seek/config.rb index d233149a7e..6aa5f06b42 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -378,24 +378,26 @@ def omniauth_github_config def omniauth_oidc_config # Cannot use url helpers here because routes are not loaded at this point :( -Finn callback_path = 'identities/auth/oidc/callback' - - omniauth_oidc_settings.merge( - { - callback_path: "#{Rails.application.config.relative_url_root}/#{callback_path}", - name: :oidc, - response_type: 'code', - discovery: true, - client_options: omniauth_oidc_settings.merge( - redirect_uri: site_base_url.join(callback_path).to_s) - }) + { + callback_path: "#{Rails.application.config.relative_url_root}/#{callback_path}", + issuer: omniauth_oidc_issuer, + name: :oidc, + response_type: 'code', + discovery: true, + client_options: { + identifier: omniauth_oidc_client_id, + secret: omniauth_oidc_secret, + redirect_uri: site_base_url.join(callback_path).to_s + } + } end def omniauth_providers - providers = {} - providers[:ldap] = omniauth_ldap_config.merge(name: :ldap, form: SessionsController.action(:new)) if omniauth_ldap_enabled - providers[:openid_connect] = omniauth_elixir_aai_config if omniauth_elixir_aai_enabled - providers[:github] = omniauth_github_config if omniauth_github_enabled - providers[:oidc] = omniauth_oidc_config if omniauth_oidc_enabled + providers = [] + providers << [:ldap, omniauth_ldap_config.merge(name: :ldap, form: SessionsController.action(:new))] if omniauth_ldap_enabled + providers << [:github, omniauth_github_config] if omniauth_github_enabled + providers << [:openid_connect, omniauth_elixir_aai_config] if omniauth_elixir_aai_enabled + providers << [:openid_connect, omniauth_oidc_config] if omniauth_oidc_enabled providers end end diff --git a/lib/seek/config_setting_attributes.yml b/lib/seek/config_setting_attributes.yml index 4c97c2adf9..0a0c274a33 100644 --- a/lib/seek/config_setting_attributes.yml +++ b/lib/seek/config_setting_attributes.yml @@ -234,7 +234,9 @@ omniauth_github_secret: encrypt: true omniauth_oidc_enabled: omniauth_oidc_name: -omniauth_oidc_settings: +omniauth_oidc_issuer: +omniauth_oidc_client_id: +omniauth_oidc_secret: encrypt: true ga4gh_trs_api_enabled: search_results_limit: diff --git a/test/integration/omniauth_test.rb b/test/integration/omniauth_test.rb index 50ce633cb1..768572e968 100644 --- a/test/integration/omniauth_test.rb +++ b/test/integration/omniauth_test.rb @@ -42,6 +42,16 @@ def setup 'email' => 'new_github_user@example.com' } }) + + @oidc_mock_auth = OmniAuth::AuthHash.new({ + provider: 'oidc', + uid: 'google-auth2|357823572abcbde', + info: { + 'nickname' => 'john_oidc', + 'name' => 'New OIDC user', + 'email' => 'new_oidc_user@example.com' + } + }) end # This test is to support the legacy LDAP integration that matched users having the same SEEK and LDAP usernames @@ -227,6 +237,32 @@ def setup assert_match(/You have successfully registered your account, but you need to create a profile/, flash[:notice]) end + test 'should create and activate new OIDC user' do + OmniAuth.config.mock_auth[:oidc] = @oidc_mock_auth + + assert_difference('User.count', 1) do + assert_difference('Identity.count', 1) do + post omniauth_authorize_path(:oidc) + follow_redirect! # OmniAuth callback + assert_redirected_to(/#{register_people_path}/) + follow_redirect! # New profile + end + end + + assert_equal 'OIDC', assigns(:person).last_name + assert_equal 'New', assigns(:person).first_name + assert_equal 'new_oidc_user@example.com', assigns(:person).email + assert session[:user_id] + user = User.find_by_id(session[:user_id]) + assert user + assert user.active? + assert_equal 1, user.identities.count + identity = user.identities.first + assert_equal 'oidc', identity.provider + assert_equal 'google-auth2|357823572abcbde', identity.uid + assert_match(/You have successfully registered your account, but you need to create a profile/, flash[:notice]) + end + test 'should not create new LDAP user if setting does not allow' do OmniAuth.config.mock_auth[:ldap] = @ldap_mock_auth From 06db35b4765233381658b938ddd558664a4e97b0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 11 Mar 2024 11:00:21 +0100 Subject: [PATCH 229/344] Change hard coded 'Single Page' text to variable. --- app/views/isa_assays/_assay_design.html.erb | 2 +- app/views/isa_studies/_study_design.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/isa_assays/_assay_design.html.erb b/app/views/isa_assays/_assay_design.html.erb index 9240363691..04fd8d9ae2 100644 --- a/app/views/isa_assays/_assay_design.html.erb +++ b/app/views/isa_assays/_assay_design.html.erb @@ -36,7 +36,7 @@ <% else %>

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

    diff --git a/app/views/isa_studies/_study_design.html.erb b/app/views/isa_studies/_study_design.html.erb index e1dcc84bd3..2d954f4b92 100644 --- a/app/views/isa_studies/_study_design.html.erb +++ b/app/views/isa_studies/_study_design.html.erb @@ -42,7 +42,7 @@ <% else %>

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

    From b5f689d093f85f324c9b04ad2ac7ce4384a9d441 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:52:56 +0000 Subject: [PATCH 230/344] Bump json-jwt from 1.15.3 to 1.15.3.1 Bumps [json-jwt](https://github.com/nov/json-jwt) from 1.15.3 to 1.15.3.1. - [Release notes](https://github.com/nov/json-jwt/releases) - [Changelog](https://github.com/nov/json-jwt/blob/main/CHANGELOG.md) - [Commits](https://github.com/nov/json-jwt/compare/v1.15.3...v1.15.3.1) --- updated-dependencies: - dependency-name: json-jwt dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f2a665db54..cda8e4a355 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM rack (>= 0.9.0) bibtex-ruby (5.1.6) latex-decode (~> 0.0) - bindata (2.4.10) + bindata (2.5.0) bindex (0.8.1) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) @@ -322,7 +322,7 @@ GEM httpclient (2.8.3) httpi (1.1.1) rack - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) i18n-js (3.9.0) i18n (>= 0.6.6) @@ -341,7 +341,7 @@ GEM railties (>= 3.2.16) json (2.3.1) json-canonicalization (0.3.0) - json-jwt (1.15.3) + json-jwt (1.15.3.1) activesupport (>= 4.2) aes_key_wrap bindata From b78584dcee2d33ebedc5a2becccf06a2f4eda353 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 11 Mar 2024 16:27:36 +0000 Subject: [PATCH 231/344] Allow OIDC provider to be given custom login button image --- app/controllers/admin_controller.rb | 24 +++---------- app/helpers/sessions_helper.rb | 10 ++++++ app/views/admin/_omniauth.html.erb | 14 ++++++++ app/views/admin/features_enabled.html.erb | 2 +- app/views/gadgets/_sign_in.html.erb | 2 +- lib/seek/config.rb | 14 ++++++++ lib/seek/config_setting_attributes.yml | 1 + test/functional/admin_controller_test.rb | 38 +++++++++++++++++++++ test/functional/sessions_controller_test.rb | 35 +++++++++++++++---- 9 files changed, 113 insertions(+), 27 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 24051dce05..d30316d9da 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -96,6 +96,9 @@ def update_features_enabled Seek::Config.omniauth_oidc_enabled = string_to_boolean params[:omniauth_oidc_enabled] Seek::Config.omniauth_oidc_name = params[:omniauth_oidc_name] + Seek::Config.omniauth_oidc_image&.destroy! if params[:clear_omniauth_oidc_image] == '1' + Seek::Config.omniauth_oidc_image = params[:omniauth_oidc_image] + params[:omniauth_oidc_issuer] = params[:omniauth_oidc_issuer].chomp('/') + '/' if params[:omniauth_oidc_issuer].present? Seek::Config.omniauth_oidc_issuer = params[:omniauth_oidc_issuer] Seek::Config.omniauth_oidc_client_id = params[:omniauth_oidc_client_id] @@ -249,7 +252,7 @@ def update_rebrand Seek::Config.header_image_enabled = string_to_boolean params[:header_image_enabled] Seek::Config.header_image_title = params[:header_image_title] - header_image_file + set_header_image_file Seek::Config.copyright_addendum_enabled = string_to_boolean params[:copyright_addendum_enabled] Seek::Config.copyright_addendum_content = params[:copyright_addendum_content] @@ -556,7 +559,7 @@ def test_email_configuration end end - def header_image_file + def set_header_image_file if params[:header_image_file] file_io = params[:header_image_file] avatar = Avatar.new(original_filename: file_io.original_filename, image_file: file_io, skip_owner_validation: true) @@ -622,23 +625,6 @@ def clear_failed_jobs private - def created_at_data_for_model(model) - x = {} - start = '1 Nov 2008' - - x[Date.parse(start).jd] = 0 - x[Date.today.jd] = 0 - - model.order(:created_at).each do |i| - date = i.created_at.to_date - day = date.jd - x[day] ||= 0 - x[day] += 1 - end - sorted_keys = x.keys.sort - (sorted_keys.first..sorted_keys.last).map { |i| x[i].nil? ? 0 : x[i] } - end - def check_valid_email(email_address, field) if email_address.blank? || email_address =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/ true diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 9db08896ff..2f5afd0c29 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -58,4 +58,14 @@ def omniauth_method_name(key) name = t("login.#{key}") if name.blank? name end + + def oidc_login_button(original_path) + text = "Sign in with #{Seek::Config.omniauth_oidc_name}" + link = omniauth_authorize_path(:oidc, state: "return_to:#{original_path}") + if Seek::Config.omniauth_oidc_image_id && (avatar = Avatar.find_by_id(Seek::Config.omniauth_oidc_image_id)) + link_to(image_tag(avatar.public_asset_url, alt: text), link, method: :post) + else + button_link_to(text, 'lock', link, method: :post) + end + end end diff --git a/app/views/admin/_omniauth.html.erb b/app/views/admin/_omniauth.html.erb index 22f8393eed..045dbfcbbb 100644 --- a/app/views/admin/_omniauth.html.erb +++ b/app/views/admin/_omniauth.html.erb @@ -60,6 +60,20 @@
    <%= admin_text_setting(:omniauth_oidc_name, Seek::Config.omniauth_oidc_name, "#{t('login.oidc')} Name", "The name of this authentication method that will be displayed to users.") %> + <%= admin_file_setting(:omniauth_oidc_image, + "#{t('login.oidc')} Login Image", "An image to use as the login button for #{t('login.oidc')}.") %> + <% if Seek::Config.omniauth_oidc_image_id && (oidc_image = Avatar.find_by_id(Seek::Config.omniauth_oidc_image_id)) %> +
    + Current image:
    + <%= image_tag(oidc_image.public_asset_url) %> +
    + +
    +
    + <% end %> <%= admin_text_setting(:omniauth_oidc_issuer, Seek::Config.omniauth_oidc_issuer, "#{t('login.oidc')} URL", "The base URL of the #{t('login.oidc')} provider. It is expected that the discovery endpoint /.well-known/openid-configuration exists under this URL.") %> <%= admin_text_setting(:omniauth_oidc_client_id, Seek::Config.omniauth_oidc_client_id, diff --git a/app/views/admin/features_enabled.html.erb b/app/views/admin/features_enabled.html.erb index db7fb3abee..b00ebdd8ac 100644 --- a/app/views/admin/features_enabled.html.erb +++ b/app/views/admin/features_enabled.html.erb @@ -1,6 +1,6 @@

    Enable or disable features

    -<%= form_tag :action=>"update_features_enabled" do -%> +<%= form_tag({ action: 'update_features_enabled' }, multipart: true) do -%>

    SEEK services

    <%= admin_checkbox_setting(:solr_enabled, 1, Seek::Config.solr_enabled, "Search enabled", "Whether the search is enabled. If switched on you need to ensure SOLR is running and its index is up to date. You need to restart both the server, and the Background service, after changing this setting.") %> diff --git a/app/views/gadgets/_sign_in.html.erb b/app/views/gadgets/_sign_in.html.erb index 2297062896..a85828441e 100644 --- a/app/views/gadgets/_sign_in.html.erb +++ b/app/views/gadgets/_sign_in.html.erb @@ -91,7 +91,7 @@ <% if show_oidc_login? %> <%= content_tag(:div, id: 'oidc_login', role: 'tabpanel', class: strategy == 'oidc' ? 'tab-pane active' : 'tab-pane') do %>
    - <%= button_link_to Seek::Config.omniauth_oidc_name, 'lock', omniauth_authorize_path(:oidc, state: "return_to:#{original_path}"), method: :post %> + <%= oidc_login_button(original_path) %>
    <% end %> <% end %> diff --git a/lib/seek/config.rb b/lib/seek/config.rb index 6aa5f06b42..39b4b3daac 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -392,6 +392,20 @@ def omniauth_oidc_config } end + def omniauth_oidc_image + Avatar.find_by_id(omniauth_oidc_image_id) if omniauth_oidc_image_id + end + + def omniauth_oidc_image= file + return if file.blank? + current = omniauth_oidc_image + omniauth_oidc_image.destroy! if current + avatar = Avatar.new(original_filename: file.original_filename, image_file: file, skip_owner_validation: true) + avatar.save! + self.omniauth_oidc_image_id = avatar.id + avatar + end + def omniauth_providers providers = [] providers << [:ldap, omniauth_ldap_config.merge(name: :ldap, form: SessionsController.action(:new))] if omniauth_ldap_enabled diff --git a/lib/seek/config_setting_attributes.yml b/lib/seek/config_setting_attributes.yml index 0a0c274a33..33f332f4c3 100644 --- a/lib/seek/config_setting_attributes.yml +++ b/lib/seek/config_setting_attributes.yml @@ -234,6 +234,7 @@ omniauth_github_secret: encrypt: true omniauth_oidc_enabled: omniauth_oidc_name: +omniauth_oidc_image_id: omniauth_oidc_issuer: omniauth_oidc_client_id: omniauth_oidc_secret: diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index 6aec36dc10..de9227fc5c 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -595,4 +595,42 @@ def setup assert_nil Rails.cache.fetch('test-key') end + test 'set/update oidc image' do + refute Seek::Config.omniauth_oidc_image_id + assert_difference('Avatar.count', 1) do + post :update_features_enabled, params: { omniauth_oidc_image: fixture_file_upload('file_picture.png', 'image/png') } + end + + id = Seek::Config.omniauth_oidc_image_id + assert id + + assert_no_difference('Avatar.count') do + post :update_features_enabled, params: { omniauth_oidc_image: fixture_file_upload('file_picture.png', 'image/png') } + end + + new_id = Seek::Config.omniauth_oidc_image_id + assert new_id + assert_not_equal id, new_id + end + + test 'clear oidc image' do + assert_difference('Avatar.count') do + Seek::Config.omniauth_oidc_image = fixture_file_upload('file_picture.png', 'image/png') + refute_nil Seek::Config.omniauth_oidc_image_id + end + + assert_difference('Avatar.count', -1) do + post :update_features_enabled, params: { clear_omniauth_oidc_image: '1' } + end + end + + test 'clear oidc image does nothing if no image' do + assert_nil Seek::Config.omniauth_oidc_image_id + + assert_no_difference('Avatar.count') do + post :update_features_enabled, params: { clear_omniauth_oidc_image: '1' } + end + + assert flash[:error].blank? + end end diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb index 46bbd23a34..d00463b296 100644 --- a/test/functional/sessions_controller_test.rb +++ b/test/functional/sessions_controller_test.rb @@ -199,14 +199,22 @@ class SessionsControllerTest < ActionController::TestCase end with_config_value(:omniauth_ldap_enabled, true) do with_config_value(:omniauth_elixir_aai_enabled, false) do - get :new - assert_response :success - assert_select '#login-panel form', 2 - assert_select '#ldap_login input[name="username"]', 1 - assert_select '#ldap_login input[name="password"]', 1 - assert_select '#elixir_aai_login a', 0 + with_config_value(:omniauth_oidc_enabled, false) do + get :new + assert_response :success + assert_select '#login-panel form', 2 + assert_select '#ldap_login input[name="username"]', 1 + assert_select '#ldap_login input[name="password"]', 1 + assert_select '#elixir_aai_login a', 0 + assert_select '#oidc_login a', 0 + end end end + with_config_value(:omniauth_oidc_enabled, true) do + get :new + assert_response :success + assert_select '#oidc_login a', 1 + end end end @@ -224,6 +232,21 @@ class SessionsControllerTest < ActionController::TestCase assert_equal User.sha256_encrypt(test_password, sha1_user.salt), sha1_user.crypted_password end + test 'should show custom OIDC image if set' do + with_config_value(:omniauth_oidc_enabled, true) do + get :new + + assert_select '#oidc_login a', text: 'Sign in with SEEK Testing OIDC' + assert_select '#oidc_login a img.icon' + + Seek::Config.omniauth_oidc_image = fixture_file_upload('file_picture.png', 'image/png') + get :new + + assert_select '#oidc_login a img.icon', count: 0 + assert_select '#oidc_login a img[src=?]', Seek::Config.omniauth_oidc_image.public_asset_url + end + end + protected def cookie_for(user) From b6a5177634d29d5baa5a6028559493e91f06533d Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 11 Mar 2024 16:42:10 +0000 Subject: [PATCH 232/344] Improve omniauth failure error message. Fixes #756 --- app/controllers/sessions_controller.rb | 12 +++++++++++- test/integration/omniauth_test.rb | 20 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 4d1db3f1ad..87354a6776 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -45,7 +45,17 @@ def create end def omniauth_failure - flash[:error] = "#{t("login.#{params[:strategy]}")} authentication failure (Invalid username/password?)" + failure_message = case params[:message] + when 'invalid_credentials' + 'Invalid username/password' + when 'missing_credentials' + 'Missing credentials' + else + nil + end + error = "#{t("login.#{params[:strategy]}")} authentication failure" + error += " (#{failure_message})" if failure_message + flash[:error] = error respond_to do |format| format.html { render :new } end diff --git a/test/integration/omniauth_test.rb b/test/integration/omniauth_test.rb index 768572e968..0d45dd76f6 100644 --- a/test/integration/omniauth_test.rb +++ b/test/integration/omniauth_test.rb @@ -367,7 +367,23 @@ def setup assert_redirected_to(omniauth_failure_path(strategy: 'ldap', message: 'invalid_credentials')) follow_redirect! - assert_equal "LDAP authentication failure (Invalid username/password?)", flash[:error] + assert_equal "LDAP authentication failure (Invalid username/password)", flash[:error] + assert_select '#ldap_login.active' + assert_select '#password_login.active', count: 0 + end + end + end + + test 'LDAP auth failure should redirect to login page and show generic error if message not recognized' do + OmniAuth.config.mock_auth[:ldap] = 'blagjsdkgjsdfgi' + + assert_no_difference('User.count') do + assert_no_difference('Identity.count') do + post omniauth_callback_path(:ldap) + assert_redirected_to(omniauth_failure_path(strategy: 'ldap', message: 'blagjsdkgjsdfgi')) + follow_redirect! + + assert_equal "LDAP authentication failure", flash[:error] assert_select '#ldap_login.active' assert_select '#password_login.active', count: 0 end @@ -384,7 +400,7 @@ def setup assert_redirected_to(omniauth_failure_path(strategy: 'elixir_aai', message: 'invalid_credentials')) follow_redirect! - assert_equal "LS Login authentication failure (Invalid username/password?)", flash[:error] + assert_equal "LS Login authentication failure (Invalid username/password)", flash[:error] assert_select '#elixir_aai_login.active' assert_select '#ldap_login.active', count: 0 assert_select '#password_login.active', count: 0 From 623e59a8b6c41797a1a2c0156cde64fd2a98c121 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Mon, 11 Mar 2024 18:00:54 +0000 Subject: [PATCH 233/344] Redirect to root after successful omniauth login following a failure. Fixes #810 --- app/controllers/sessions_controller.rb | 2 ++ test/integration/omniauth_test.rb | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 87354a6776..f49820524b 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -56,6 +56,8 @@ def omniauth_failure error = "#{t("login.#{params[:strategy]}")} authentication failure" error += " (#{failure_message})" if failure_message flash[:error] = error + # Ideally we would return to the original "return_to", prior to the error, but it gets lost somewhere along the way. + params[:return_to] = root_path respond_to do |format| format.html { render :new } end diff --git a/test/integration/omniauth_test.rb b/test/integration/omniauth_test.rb index 0d45dd76f6..986a674bd5 100644 --- a/test/integration/omniauth_test.rb +++ b/test/integration/omniauth_test.rb @@ -2,6 +2,7 @@ class OmniauthTest < ActionDispatch::IntegrationTest include AuthenticatedTestHelper + include HtmlHelper fixtures :users, :people @@ -404,6 +405,8 @@ def setup assert_select '#elixir_aai_login.active' assert_select '#ldap_login.active', count: 0 assert_select '#password_login.active', count: 0 + # Should override return_to, to avoid it redirecting back to the login form with garbled error message. + assert_select '#elixir_aai_login a[href=?]', omniauth_authorize_path(:elixir_aai, state: 'return_to:/') end end end From 4caba756206991a9db21dab4e2891d402367dc6b Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Tue, 12 Mar 2024 10:48:55 +0000 Subject: [PATCH 234/344] Mock auth needs to be a symbol apparently --- test/integration/omniauth_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/omniauth_test.rb b/test/integration/omniauth_test.rb index 986a674bd5..8155f1eeea 100644 --- a/test/integration/omniauth_test.rb +++ b/test/integration/omniauth_test.rb @@ -376,7 +376,7 @@ def setup end test 'LDAP auth failure should redirect to login page and show generic error if message not recognized' do - OmniAuth.config.mock_auth[:ldap] = 'blagjsdkgjsdfgi' + OmniAuth.config.mock_auth[:ldap] = :blagjsdkgjsdfgi assert_no_difference('User.count') do assert_no_difference('Identity.count') do From 27aa3e54f1ac28af313cd0599441ba047d995b0f Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 13 Mar 2024 16:01:47 +0000 Subject: [PATCH 235/344] Provide redirect URI etc. info next to each omniauth provider #1415 --- app/views/admin/_omniauth.html.erb | 105 ++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/app/views/admin/_omniauth.html.erb b/app/views/admin/_omniauth.html.erb index 045dbfcbbb..67c9b21779 100644 --- a/app/views/admin/_omniauth.html.erb +++ b/app/views/admin/_omniauth.html.erb @@ -37,48 +37,93 @@ <%= admin_checkbox_setting(:omniauth_elixir_aai_enabled, 1, Seek::Config.omniauth_elixir_aai_enabled, "#{t('login.elixir_aai')} authentication", "Enables use of #{t('login.elixir_aai')} for authentication.", onchange: toggle_appear_javascript('omniauth_elixir_aai_settings')) %> -
    - <%= admin_text_setting(:omniauth_elixir_aai_client_id, Seek::Config.omniauth_elixir_aai_client_id, - "#{t('login.elixir_aai')} Client ID", "The client ID provided by #{t('login.elixir_aai')} for your installation.") %> - <%= admin_password_setting(:omniauth_elixir_aai_secret, Seek::Config.omniauth_elixir_aai_secret, - "#{t('login.elixir_aai')} Secret", "The secret key provided by #{t('login.elixir_aai')} for your installation.") %> +
    +
    +
    + <%= admin_text_setting(:omniauth_elixir_aai_client_id, Seek::Config.omniauth_elixir_aai_client_id, + "#{t('login.elixir_aai')} Client ID", "The client ID provided by #{t('login.elixir_aai')} for your installation.") %> + <%= admin_password_setting(:omniauth_elixir_aai_secret, Seek::Config.omniauth_elixir_aai_secret, + "#{t('login.elixir_aai')} Secret", "The secret key provided by #{t('login.elixir_aai')} for your installation.") %> +
    +
    + <%= panel('Client Info') do %> +

    + Provide this info to <%= t('login.elixir_aai') %> when registering your SEEK instance as a relying party. +

    +
    +
    <%= omniauth_callback_url('elixir_aai') %>
    +
    +
    +
    authorization_code
    + <% end %> +
    +
    <%= admin_checkbox_setting(:omniauth_github_enabled, 1, Seek::Config.omniauth_github_enabled, "GitHub authentication", "Enables use of GitHub for authentication.", onchange: toggle_appear_javascript('omniauth_github_settings')) %>
    - <%= admin_text_setting(:omniauth_github_client_id, Seek::Config.omniauth_github_client_id, - 'GitHub Client ID', 'The client ID provided by GitHub for your installation.') %> - <%= admin_password_setting(:omniauth_github_secret, Seek::Config.omniauth_github_secret, - 'GitHub Secret', 'The secret key provided by GitHub for your installation.') %> +
    +
    + <%= admin_text_setting(:omniauth_github_client_id, Seek::Config.omniauth_github_client_id, + 'GitHub Client ID', 'The client ID provided by GitHub for your installation.') %> + <%= admin_password_setting(:omniauth_github_secret, Seek::Config.omniauth_github_secret, + 'GitHub Secret', 'The secret key provided by GitHub for your installation.') %> +
    +
    + <%= panel('Client Info') do %> +

    + Provide this info to GitHub when registering your SEEK instance as an OAuth App. +

    +
    +
    <%= omniauth_callback_url('github') %>
    + <% end %> +
    +
    <%= admin_checkbox_setting(:omniauth_oidc_enabled, 1, Seek::Config.omniauth_oidc_enabled, "#{t('login.oidc')} authentication", "Enables use of a #{t('login.oidc')} provider for authentication.", onchange: toggle_appear_javascript('omniauth_oidc_settings')) %>
    - <%= admin_text_setting(:omniauth_oidc_name, Seek::Config.omniauth_oidc_name, - "#{t('login.oidc')} Name", "The name of this authentication method that will be displayed to users.") %> - <%= admin_file_setting(:omniauth_oidc_image, - "#{t('login.oidc')} Login Image", "An image to use as the login button for #{t('login.oidc')}.") %> - <% if Seek::Config.omniauth_oidc_image_id && (oidc_image = Avatar.find_by_id(Seek::Config.omniauth_oidc_image_id)) %> -
    - Current image:
    - <%= image_tag(oidc_image.public_asset_url) %> -
    - -
    +
    +
    + <%= admin_text_setting(:omniauth_oidc_name, Seek::Config.omniauth_oidc_name, + "#{t('login.oidc')} Name", "The name of this authentication method that will be displayed to users.") %> + <%= admin_file_setting(:omniauth_oidc_image, + "#{t('login.oidc')} Login Image", "An image to use as the login button for #{t('login.oidc')}.") %> + <% if Seek::Config.omniauth_oidc_image_id && (oidc_image = Avatar.find_by_id(Seek::Config.omniauth_oidc_image_id)) %> +
    + Current image:
    + <%= image_tag(oidc_image.public_asset_url) %> +
    + +
    +
    + <% end %> + <%= admin_text_setting(:omniauth_oidc_issuer, Seek::Config.omniauth_oidc_issuer, + "#{t('login.oidc')} URL", "The base URL of the #{t('login.oidc')} provider. It is expected that the discovery endpoint /.well-known/openid-configuration exists under this URL.") %> + <%= admin_text_setting(:omniauth_oidc_client_id, Seek::Config.omniauth_oidc_client_id, + "#{t('login.oidc')} Client ID", "The client ID to use to authenticate with the #{t('login.oidc')} provider.") %> + <%= admin_password_setting(:omniauth_oidc_secret, Seek::Config.omniauth_oidc_secret, + "#{t('login.oidc')} Secret", "The secret to use to authenticate with the #{t('login.oidc')} provider.") %> +
    +
    + <%= panel('Client Info') do %> +

    + Provide this info to the <%= t('login.oidc') %> provider when registering your SEEK instance as a relying party. +

    +
    +
    <%= omniauth_callback_url('oidc') %>
    +
    +
    +
    authorization_code
    + <% end %>
    - <% end %> - <%= admin_text_setting(:omniauth_oidc_issuer, Seek::Config.omniauth_oidc_issuer, - "#{t('login.oidc')} URL", "The base URL of the #{t('login.oidc')} provider. It is expected that the discovery endpoint /.well-known/openid-configuration exists under this URL.") %> - <%= admin_text_setting(:omniauth_oidc_client_id, Seek::Config.omniauth_oidc_client_id, - "#{t('login.oidc')} Client ID", "The client ID to use to authenticate with the #{t('login.oidc')} provider.") %> - <%= admin_password_setting(:omniauth_oidc_secret, Seek::Config.omniauth_oidc_secret, - "#{t('login.oidc')} Secret", "The secret to use to authenticate with the #{t('login.oidc')} provider.") %> +
    From 1119d4858bcd41449a210c019b8ac58953cac482 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 13 Mar 2024 16:09:43 +0000 Subject: [PATCH 236/344] Add legacy option to LS Login settings. Fixes #1030 --- app/controllers/admin_controller.rb | 1 + app/views/admin/_omniauth.html.erb | 2 + config/locales/en.yml | 1 + lib/seek/config.rb | 75 +++++++++++++++++--------- lib/seek/config_setting_attributes.yml | 1 + test/unit/config_test.rb | 6 +++ 6 files changed, 60 insertions(+), 26 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index d30316d9da..c467ba580a 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -89,6 +89,7 @@ def update_features_enabled Seek::Config.omniauth_elixir_aai_enabled = string_to_boolean params[:omniauth_elixir_aai_enabled] Seek::Config.omniauth_elixir_aai_client_id = params[:omniauth_elixir_aai_client_id] Seek::Config.omniauth_elixir_aai_secret = params[:omniauth_elixir_aai_secret] + Seek::Config.omniauth_elixir_aai_legacy_mode = string_to_boolean params[:omniauth_elixir_aai_legacy_mode] Seek::Config.omniauth_github_enabled = string_to_boolean params[:omniauth_github_enabled] Seek::Config.omniauth_github_client_id = params[:omniauth_github_client_id] diff --git a/app/views/admin/_omniauth.html.erb b/app/views/admin/_omniauth.html.erb index 67c9b21779..ddff981e1e 100644 --- a/app/views/admin/_omniauth.html.erb +++ b/app/views/admin/_omniauth.html.erb @@ -44,6 +44,8 @@ "#{t('login.elixir_aai')} Client ID", "The client ID provided by #{t('login.elixir_aai')} for your installation.") %> <%= admin_password_setting(:omniauth_elixir_aai_secret, Seek::Config.omniauth_elixir_aai_secret, "#{t('login.elixir_aai')} Secret", "The secret key provided by #{t('login.elixir_aai')} for your installation.") %> + <%= admin_checkbox_setting(:omniauth_elixir_aai_legacy_mode, 1, Seek::Config.omniauth_elixir_aai_legacy_mode, + "Use legacy configuration", "Use this if your client was registered prior to the transition from #{t('login.elixir_aai_legacy')} to #{t('login.elixir_aai')}.") %>
    <%= panel('Client Info') do %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 1303fdfb1a..41ddb55a0e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -6,6 +6,7 @@ en: login: elixir_aai: 'LS Login' + elixir_aai_legacy: 'ELIXIR AAI' ldap: 'LDAP' github: 'GitHub' oidc: 'OpenID Connect' diff --git a/lib/seek/config.rb b/lib/seek/config.rb index 39b4b3daac..7465011c4f 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -333,33 +333,47 @@ def assays_enabled end def omniauth_elixir_aai_config - # Cannot use url helpers here because routes are not loaded at this point :( -Finn - callback_path = 'identities/auth/elixir_aai/callback' - - { - callback_path: "#{Rails.application.config.relative_url_root}/#{callback_path}", + if omniauth_elixir_aai_legacy_mode + { + callback_path: omniauth_callback_path('elixir_aai'), + name: :elixir_aai, + scope: [:openid, :email], + response_type: 'code', + issuer: 'https://login.elixir-czech.org/oidc/', + # I had an issue using discovery in the past, maybe it works now? + discovery: false, + send_nonce: true, + client_signing_alg: :RS256, + # The following is obtained from: https://login.elixir-czech.org/oidc/jwk + client_jwk_signing_key: '{"keys":[{"kty":"RSA","e":"AQAB","kid":"rsa1","alg":"RS256","n":"uVHPfUHVEzpgOnDNi3e2pVsbK1hsINsTy_1mMT7sxDyP-1eQSjzYsGSUJ3GHq9LhiVndpwV8y7Enjdj0purywtwk_D8z9IIN36RJAh1yhFfbyhLPEZlCDdzxas5Dku9k0GrxQuV6i30Mid8OgRQ2q3pmsks414Afy6xugC6u3inyjLzLPrhR0oRPTGdNMXJbGw4sVTjnh5AzTgX-GrQWBHSjI7rMTcvqbbl7M8OOhE3MQ_gfVLXwmwSIoKHODC0RO-XnVhqd7Qf0teS1JiILKYLl5FS_7Uy2ClVrAYd2T6X9DIr_JlpRkwSD899pq6PR9nhKguipJE0qUXxamdY9nw"}]}', + client_options: { + identifier: omniauth_elixir_aai_client_id, + secret: omniauth_elixir_aai_secret, + redirect_uri: omniauth_redirect_uri('elixir_aai'), + scheme: 'https', + host: 'login.elixir-czech.org', + port: 443, + authorization_endpoint: '/oidc/authorize', + token_endpoint: '/oidc/token', + userinfo_endpoint: '/oidc/userinfo', + jwks_uri: '/oidc/jwk', + } + } + else + { + callback_path: omniauth_callback_path('elixir_aai'), name: :elixir_aai, scope: [:openid, :email], response_type: 'code', - issuer: 'https://login.elixir-czech.org/oidc/', - discovery: false, - send_nonce: true, - client_signing_alg: :RS256, - # The following is obtained from: https://login.elixir-czech.org/oidc/jwk - client_jwk_signing_key: '{"keys":[{"kty":"RSA","e":"AQAB","kid":"rsa1","alg":"RS256","n":"uVHPfUHVEzpgOnDNi3e2pVsbK1hsINsTy_1mMT7sxDyP-1eQSjzYsGSUJ3GHq9LhiVndpwV8y7Enjdj0purywtwk_D8z9IIN36RJAh1yhFfbyhLPEZlCDdzxas5Dku9k0GrxQuV6i30Mid8OgRQ2q3pmsks414Afy6xugC6u3inyjLzLPrhR0oRPTGdNMXJbGw4sVTjnh5AzTgX-GrQWBHSjI7rMTcvqbbl7M8OOhE3MQ_gfVLXwmwSIoKHODC0RO-XnVhqd7Qf0teS1JiILKYLl5FS_7Uy2ClVrAYd2T6X9DIr_JlpRkwSD899pq6PR9nhKguipJE0qUXxamdY9nw"}]}', + issuer: 'https://proxy.aai.lifescience-ri.eu/', + discovery: true, client_options: { - identifier: omniauth_elixir_aai_client_id, - secret: omniauth_elixir_aai_secret, - redirect_uri: site_base_url.join(callback_path).to_s, - scheme: 'https', - host: 'login.elixir-czech.org', - port: 443, - authorization_endpoint: '/oidc/authorize', - token_endpoint: '/oidc/token', - userinfo_endpoint: '/oidc/userinfo', - jwks_uri: '/oidc/jwk', + identifier: omniauth_elixir_aai_client_id, + secret: omniauth_elixir_aai_secret, + redirect_uri: omniauth_redirect_uri('elixir_aai'), } - } + } + end end def omniauth_ldap_settings(field) @@ -376,10 +390,8 @@ def omniauth_github_config end def omniauth_oidc_config - # Cannot use url helpers here because routes are not loaded at this point :( -Finn - callback_path = 'identities/auth/oidc/callback' { - callback_path: "#{Rails.application.config.relative_url_root}/#{callback_path}", + callback_path: omniauth_callback_path('oidc'), issuer: omniauth_oidc_issuer, name: :oidc, response_type: 'code', @@ -387,7 +399,7 @@ def omniauth_oidc_config client_options: { identifier: omniauth_oidc_client_id, secret: omniauth_oidc_secret, - redirect_uri: site_base_url.join(callback_path).to_s + redirect_uri: omniauth_redirect_uri('oidc') } } end @@ -414,6 +426,17 @@ def omniauth_providers providers << [:openid_connect, omniauth_oidc_config] if omniauth_oidc_enabled providers end + + private + + def omniauth_callback_path(provider) + # Cannot use url helpers here because routes are not loaded at this point :( -Finn + "#{Rails.application.config.relative_url_root}/identities/auth/#{provider}/callback" + end + + def omniauth_redirect_uri(provider) + site_base_url.join("identities/auth/#{provider}/callback").to_s + end end # The inner wiring. Ideally this should be hidden away, diff --git a/lib/seek/config_setting_attributes.yml b/lib/seek/config_setting_attributes.yml index 33f332f4c3..8f9be9b9a7 100644 --- a/lib/seek/config_setting_attributes.yml +++ b/lib/seek/config_setting_attributes.yml @@ -225,6 +225,7 @@ omniauth_elixir_aai_enabled: omniauth_elixir_aai_client_id: omniauth_elixir_aai_secret: encrypt: true +omniauth_elixir_aai_legacy_mode: omniauth_ldap_enabled: omniauth_ldap_config: encrypt: true diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index 5edd0ed1b7..38202ce426 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -618,7 +618,13 @@ class ConfigTest < ActiveSupport::TestCase config = Seek::Config.omniauth_elixir_aai_config assert_equal '/seeks/seek1/identities/auth/elixir_aai/callback', config[:callback_path] assert_equal 'http://localhost/seeks/seek1/identities/auth/elixir_aai/callback', config[:client_options][:redirect_uri] + refute Seek::Config.omniauth_elixir_aai_legacy_mode + assert_equal 'https://proxy.aai.lifescience-ri.eu/', Seek::Config.omniauth_elixir_aai_config[:issuer] end end + with_config_value(:omniauth_elixir_aai_legacy_mode, true) do + assert Seek::Config.omniauth_elixir_aai_legacy_mode + assert_equal 'https://login.elixir-czech.org/oidc/', Seek::Config.omniauth_elixir_aai_config[:issuer] + end end end From 948d35428cf508c899daf2b6ef977a929e9bb0db Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 13 Mar 2024 16:54:45 +0000 Subject: [PATCH 237/344] Don't use legacy `/identities/auth/...` route for redirect URIs going forwards --- config/routes.rb | 2 +- lib/seek/config.rb | 14 +++++++++++--- test/unit/config_test.rb | 21 ++++++++++++--------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 67aa9cd4e9..016f0963ae 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -822,7 +822,7 @@ # Omniauth post '/auth/:provider' => 'sessions#create', as: :omniauth_authorize # For security, ONLY POST should be enabled on this route. match '/auth/:provider/callback' => 'sessions#create', as: :omniauth_callback, via: [:get, :post] # Callback routes need both GET and POST enabled. - match '/identities/auth/:provider/callback' => 'sessions#create', via: [:get, :post] # Needed for legacy support.. + match '/identities/auth/:provider/callback' => 'sessions#create', as: :legacy_omniauth_callback, via: [:get, :post] # Needed for legacy support.. get '/auth/failure' => 'sessions#omniauth_failure', as: :omniauth_failure get '/activate(/:activation_code)' => 'users#activate', as: :activate diff --git a/lib/seek/config.rb b/lib/seek/config.rb index 7465011c4f..0f3350d932 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -335,7 +335,7 @@ def assays_enabled def omniauth_elixir_aai_config if omniauth_elixir_aai_legacy_mode { - callback_path: omniauth_callback_path('elixir_aai'), + callback_path: legacy_omniauth_callback_path('elixir_aai'), name: :elixir_aai, scope: [:openid, :email], response_type: 'code', @@ -349,7 +349,7 @@ def omniauth_elixir_aai_config client_options: { identifier: omniauth_elixir_aai_client_id, secret: omniauth_elixir_aai_secret, - redirect_uri: omniauth_redirect_uri('elixir_aai'), + redirect_uri: legacy_omniauth_redirect_uri('elixir_aai'), scheme: 'https', host: 'login.elixir-czech.org', port: 443, @@ -429,12 +429,20 @@ def omniauth_providers private + # Cannot use url helpers here because routes are not loaded at this point :( -Finn def omniauth_callback_path(provider) - # Cannot use url helpers here because routes are not loaded at this point :( -Finn + "#{Rails.application.config.relative_url_root}/auth/#{provider}/callback" + end + + def legacy_omniauth_callback_path(provider) "#{Rails.application.config.relative_url_root}/identities/auth/#{provider}/callback" end def omniauth_redirect_uri(provider) + site_base_url.join("auth/#{provider}/callback").to_s + end + + def legacy_omniauth_redirect_uri(provider) site_base_url.join("identities/auth/#{provider}/callback").to_s end end diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index 38202ce426..a1ae027845 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -607,8 +607,8 @@ class ConfigTest < ActiveSupport::TestCase config = Seek::Config.omniauth_elixir_aai_config assert_equal 'abc', config[:client_options][:identifier] assert_equal '123', config[:client_options][:secret] - assert_equal '/identities/auth/elixir_aai/callback', config[:callback_path] - assert_equal 'https://secure.website:3001/identities/auth/elixir_aai/callback', config[:client_options][:redirect_uri] + assert_equal '/auth/elixir_aai/callback', config[:callback_path] + assert_equal 'https://secure.website:3001/auth/elixir_aai/callback', config[:client_options][:redirect_uri] end end end @@ -616,15 +616,18 @@ class ConfigTest < ActiveSupport::TestCase with_config_value(:site_base_host, 'http://localhost') do with_relative_root('/seeks/seek1') do config = Seek::Config.omniauth_elixir_aai_config - assert_equal '/seeks/seek1/identities/auth/elixir_aai/callback', config[:callback_path] - assert_equal 'http://localhost/seeks/seek1/identities/auth/elixir_aai/callback', config[:client_options][:redirect_uri] refute Seek::Config.omniauth_elixir_aai_legacy_mode - assert_equal 'https://proxy.aai.lifescience-ri.eu/', Seek::Config.omniauth_elixir_aai_config[:issuer] + assert_equal '/seeks/seek1/auth/elixir_aai/callback', config[:callback_path] + assert_equal 'http://localhost/seeks/seek1/auth/elixir_aai/callback', config[:client_options][:redirect_uri] + assert_equal 'https://proxy.aai.lifescience-ri.eu/',config[:issuer] + with_config_value(:omniauth_elixir_aai_legacy_mode, true) do + assert Seek::Config.omniauth_elixir_aai_legacy_mode + config = Seek::Config.omniauth_elixir_aai_config + assert_equal '/seeks/seek1/identities/auth/elixir_aai/callback', config[:callback_path] + assert_equal 'http://localhost/seeks/seek1/identities/auth/elixir_aai/callback', config[:client_options][:redirect_uri] + assert_equal 'https://login.elixir-czech.org/oidc/', config[:issuer] + end end end - with_config_value(:omniauth_elixir_aai_legacy_mode, true) do - assert Seek::Config.omniauth_elixir_aai_legacy_mode - assert_equal 'https://login.elixir-czech.org/oidc/', Seek::Config.omniauth_elixir_aai_config[:issuer] - end end end From 2c5048f205d968beb13c764dba8cb3845269a1d9 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 11 Mar 2024 14:08:54 +0000 Subject: [PATCH 238/344] flag content blob to be deleted after asset deleted, and filter from assocations #1784 --- app/models/data_file.rb | 2 +- app/models/document.rb | 2 +- app/models/file_template.rb | 2 +- app/models/model.rb | 2 +- app/models/presentation.rb | 2 +- app/models/publication.rb | 2 +- app/models/sop.rb | 3 +- app/models/workflow.rb | 2 +- ...113858_add_deleted_flag_to_content_blob.rb | 5 ++ db/schema.rb | 3 +- lib/seek/acts_as_asset.rb | 2 +- lib/seek/acts_as_asset/content_blobs.rb | 15 +++++ test/unit/asset_test.rb | 55 +++++++++++++++++++ 13 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 db/migrate/20240311113858_add_deleted_flag_to_content_blob.rb diff --git a/app/models/data_file.rb b/app/models/data_file.rb index 1541a2823e..03b358677b 100644 --- a/app/models/data_file.rb +++ b/app/models/data_file.rb @@ -15,7 +15,7 @@ class DataFile < ApplicationRecord # allow same titles, but only if these belong to different users # validates_uniqueness_of :title, :scope => [ :contributor_id, :contributor_type ], :message => "error - you already have a Data file with such title." - has_one :content_blob, ->(r) { where('content_blobs.asset_version =?', r.version) }, as: :asset, foreign_key: :asset_id + has_one :content_blob, ->(r) { where('content_blobs.asset_version =? AND deleted =?', r.version, false) }, as: :asset, foreign_key: :asset_id has_one :external_asset, as: :seek_entity, dependent: :destroy belongs_to :file_template diff --git a/app/models/document.rb b/app/models/document.rb index b7f4a3619a..b8049cf835 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -10,7 +10,7 @@ class Document < ApplicationRecord acts_as_doi_parent #don't add a dependent=>:destroy, as the content_blob needs to remain to detect future duplicates - has_one :content_blob, -> (r) { where('content_blobs.asset_version = ?', r.version) }, :as => :asset, :foreign_key => :asset_id + has_one :content_blob, -> (r) { where('content_blobs.asset_version =? AND deleted =?', r.version, false) }, :as => :asset, :foreign_key => :asset_id has_and_belongs_to_many :workflows, -> { distinct } diff --git a/app/models/file_template.rb b/app/models/file_template.rb index 44955d1c00..5697355941 100644 --- a/app/models/file_template.rb +++ b/app/models/file_template.rb @@ -16,7 +16,7 @@ class FileTemplate < ApplicationRecord has_controlled_vocab_annotations :data_types, :data_formats #don't add a dependent=>:destroy, as the content_blob needs to remain to detect future duplicates - has_one :content_blob, -> (r) { where('content_blobs.asset_version = ?', r.version) }, :as => :asset, :foreign_key => :asset_id + has_one :content_blob, -> (r) { where('content_blobs.asset_version =? AND deleted =?', r.version, false) }, :as => :asset, :foreign_key => :asset_id has_many :data_files, inverse_of: :file_template has_many :placeholders, inverse_of: :file_template diff --git a/app/models/model.rb b/app/models/model.rb index 50d01fd40e..1781ef764a 100644 --- a/app/models/model.rb +++ b/app/models/model.rb @@ -30,7 +30,7 @@ class Model < ApplicationRecord has_many :model_images, inverse_of: :model belongs_to :model_image, inverse_of: :model - has_many :content_blobs, -> (r) { where('content_blobs.asset_version =?', r.version) }, :as => :asset, :foreign_key => :asset_id + has_many :content_blobs, -> (r) { where('content_blobs.asset_version =? AND deleted =?', r.version, false) }, :as => :asset, :foreign_key => :asset_id belongs_to :organism belongs_to :human_disease diff --git a/app/models/presentation.rb b/app/models/presentation.rb index fe5f58beb4..c2cd8f7c6e 100644 --- a/app/models/presentation.rb +++ b/app/models/presentation.rb @@ -8,7 +8,7 @@ class Presentation < ApplicationRecord #even though in Seek::ActsAsAsset::Search it is already set to false! acts_as_asset - has_one :content_blob, -> (r) { where('content_blobs.asset_version =?', r.version) }, :as => :asset, :foreign_key => :asset_id + has_one :content_blob, -> (r) { where('content_blobs.asset_version =? AND deleted =?', r.version, false) }, :as => :asset, :foreign_key => :asset_id validates :projects, presence: true, projects: { self: true } diff --git a/app/models/publication.rb b/app/models/publication.rb index 81c20429b4..11000df330 100755 --- a/app/models/publication.rb +++ b/app/models/publication.rb @@ -45,7 +45,7 @@ class Publication < ApplicationRecord has_many :publication_authors, dependent: :destroy, autosave: true has_many :people, through: :publication_authors - has_one :content_blob, ->(r) { where('content_blobs.asset_version =?', r.version) }, as: :asset, foreign_key: :asset_id + has_one :content_blob, ->(r) { where('content_blobs.asset_version =? AND deleted=?', r.version, false) }, as: :asset, foreign_key: :asset_id explicit_versioning(:version_column => "version", sync_ignore_columns: ['license','other_creators']) do acts_as_versioned_resource diff --git a/app/models/sop.rb b/app/models/sop.rb index 0c356196d6..7e9b268605 100644 --- a/app/models/sop.rb +++ b/app/models/sop.rb @@ -11,11 +11,10 @@ class Sop < ApplicationRecord validates :projects, presence: true, projects: { self: true } #don't add a dependent=>:destroy, as the content_blob needs to remain to detect future duplicates - has_one :content_blob, -> (r) { where('content_blobs.asset_version =?', r.version) }, :as => :asset, :foreign_key => :asset_id + has_one :content_blob, -> (r) { where('content_blobs.asset_version =? AND deleted=?', r.version, false) }, :as => :asset, :foreign_key => :asset_id has_and_belongs_to_many :workflows - has_filter assay_type: Seek::Filtering::Filter.new( value_field: 'assays.assay_type_uri', label_mapping: Seek::Filterer::MAPPINGS[:assay_type_label], diff --git a/app/models/workflow.rb b/app/models/workflow.rb index eede3c2f6a..852b647658 100644 --- a/app/models/workflow.rb +++ b/app/models/workflow.rb @@ -19,7 +19,7 @@ class Workflow < ApplicationRecord validates :projects, presence: true, projects: { self: true } #don't add a dependent=>:destroy, as the content_blob needs to remain to detect future duplicates - has_one :content_blob, -> (r) { where('content_blobs.asset_version =?', r.version) }, :as => :asset, :foreign_key => :asset_id + has_one :content_blob, -> (r) { where('content_blobs.asset_version =? AND deleted =?', r.version, false) }, :as => :asset, :foreign_key => :asset_id has_and_belongs_to_many :sops has_and_belongs_to_many :presentations diff --git a/db/migrate/20240311113858_add_deleted_flag_to_content_blob.rb b/db/migrate/20240311113858_add_deleted_flag_to_content_blob.rb new file mode 100644 index 0000000000..1a6a87a514 --- /dev/null +++ b/db/migrate/20240311113858_add_deleted_flag_to_content_blob.rb @@ -0,0 +1,5 @@ +class AddDeletedFlagToContentBlob < ActiveRecord::Migration[6.1] + def change + add_column :content_blobs, :deleted, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 7cb9c06a08..60ca2f5d19 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: 2024_02_12_145449) do +ActiveRecord::Schema.define(version: 2024_03_11_113858) do create_table "activity_logs", id: :integer, force: :cascade do |t| t.string "action" @@ -348,6 +348,7 @@ t.bigint "file_size" t.datetime "created_at" t.datetime "updated_at" + t.boolean "deleted", default: false t.index ["asset_id", "asset_type"], name: "index_content_blobs_on_asset_id_and_asset_type" end diff --git a/lib/seek/acts_as_asset.rb b/lib/seek/acts_as_asset.rb index c11efea20a..1c5de8cb75 100644 --- a/lib/seek/acts_as_asset.rb +++ b/lib/seek/acts_as_asset.rb @@ -47,7 +47,6 @@ def acts_as_asset validates :description, length: { maximum: 65_535 }, if: -> { respond_to?(:description) } validates :license, license:true, allow_blank: true, if: -> { respond_to?(:license) } - include Seek::Stats::ActivityCounts include Seek::ActsAsAsset::ISA::Associations @@ -61,6 +60,7 @@ def acts_as_asset include Seek::ActsAsAsset::InstanceMethods include Seek::Search::BackgroundReindexing include Seek::Subscribable + include Seek::ActsAsAsset::ContentBlobs::ClassMethods extend SingletonMethods end diff --git a/lib/seek/acts_as_asset/content_blobs.rb b/lib/seek/acts_as_asset/content_blobs.rb index 978b3c7953..6ff53183ba 100644 --- a/lib/seek/acts_as_asset/content_blobs.rb +++ b/lib/seek/acts_as_asset/content_blobs.rb @@ -2,7 +2,15 @@ module Seek module ActsAsAsset # Acts as Asset behaviour that relates to content module ContentBlobs + module ClassMethods + extend ActiveSupport::Concern + included do + after_destroy :mark_deleted_content_blobs + end + end + module InstanceMethods + def contains_downloadable_items? all_content_blobs.compact.any?(&:is_downloadable?) end @@ -46,6 +54,13 @@ def cache_remote_content_blob self.save! end end + + # flags that content blob has been deleted, after the associated asset has been destroyed + def mark_deleted_content_blobs + all_content_blobs.each do |cb| + cb.update_column(:deleted, true) + end + end end end end diff --git a/test/unit/asset_test.rb b/test/unit/asset_test.rb index e2d812f5f8..e00e1d0055 100644 --- a/test/unit/asset_test.rb +++ b/test/unit/asset_test.rb @@ -507,4 +507,59 @@ class AssetTest < ActiveSupport::TestCase end end end + + test 'content blobs flagged as deleted on destroy' do + [:data_file, :presentation, :sop, :document, :file_template, :workflow, :max_publication].each do |type| + asset = FactoryBot.create(type) + cb = asset.content_blob + refute cb.deleted? + refute_nil cb, "content blob nil for #{type}" + disable_authorization_checks do + asset.destroy + end + assert asset.destroyed? + cb.reload + assert cb.deleted?, "content blob not marked as deleted for #{type}" + end + + [:max_model].each do |type| + asset = FactoryBot.create(type) + cbs = asset.content_blobs + cbs.each do |cb| + refute cb.deleted? + end + refute_empty cbs, "no content blobs for #{type}" + disable_authorization_checks do + asset.destroy + end + assert asset.destroyed? + cbs.each do |cb| + cb.reload + assert cb.deleted?, "content blob not marked as deleted for #{type}" + end + end + end + + test 'delted content blob not included in associations' do + [:data_file, :presentation, :sop, :document, :file_template, :workflow, :max_publication].each do |type| + asset = FactoryBot.create(type) + cb = asset.content_blob + refute_nil cb, "content blob nil for #{type}" + cb.update_column(:deleted, true) + asset.reload + assert_nil asset.content_blob, "deleted content blob should not be returned for #{type}" + end + + [:max_model].each do |type| + asset = FactoryBot.create(type) + cbs = asset.content_blobs + refute_empty cbs, "no content blobs present for #{type}" + cbs.each do |cb| + cb.update_column(:deleted, true) + end + asset.reload + assert_empty asset.content_blobs, "deleted content blobs should not be returned for #{type}" + end + + end end From 5005cb2d337a14663dbefc9238035e2a3b149127 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 14 Mar 2024 11:52:41 +0000 Subject: [PATCH 239/344] Set LS Login legacy mode for existing users Since non-legacy use wasn't possible prior to the setting being implemented. --- lib/tasks/seek_upgrades.rake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 38cb4b50bd..6315d79c1f 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -19,6 +19,7 @@ namespace :seek do db:seed:017_minimal_starter_isa_templates recognise_isa_json_compliant_items implement_assay_streams_for_isa_assays + set_ls_login_legacy_mode ] # these are the tasks that are executes for each upgrade as standard, and rarely change @@ -225,6 +226,15 @@ namespace :seek do puts "...Created #{assay_streams_created} new assay streams" end + task(set_ls_login_legacy_mode: [:environment]) do + only_once('ls_login_legacy') do + if Seek::Config.omniauth_elixir_aai_enabled + puts "Enabling LS Login legacy mode" + Seek::Config.omniauth_elixir_aai_legacy_mode = true + end + end + end + private ## From f80240d4d4590628499ee1375390a0d5ced6bdba Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 14 Mar 2024 12:06:36 +0000 Subject: [PATCH 240/344] Prefer `omniauth_method_name` --- app/views/users/new.html.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index aca0d167ba..ac12506c1f 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -75,23 +75,23 @@ <% if show_elixir_aai_login? %>
  • <%= link_to(omniauth_authorize_path(:elixir_aai, state: "return_to:/"), method: :post) do %> - Log in using <%= t('login.elixir_aai') -%>
    + Log in using <%= omniauth_method_name(:elixir_aai) -%>
    <%= image('elixir_aai_login') %> <% end %>
  • <% end %> <% if show_ldap_login? %> -
  • <%= link_to "Log in using #{t('login.ldap')}", login_path(anchor: 'ldap_login') %>
  • +
  • <%= link_to "Log in using #{omniauth_method_name(:ldap)}", login_path(anchor: 'ldap_login') %>
  • <% end %> <% if show_github_login? %>
  • <%= link_to(omniauth_authorize_path(:github, state: "return_to:/"), method: :post) do%> - Log in using <%= t('login.github') -%> <%= image('github') %> + Log in using <%= omniauth_method_name(:github) -%> <%= image('github') %> <% end %>
  • <% end %> <% if show_oidc_login? %> -
  • <%= link_to "Log in using #{Seek::Config.omniauth_oidc_name}", omniauth_authorize_path(:oidc, state: "return_to:/") %>
  • +
  • <%= link_to "Log in using #{omniauth_method_name(:oidc)}", omniauth_authorize_path(:oidc, state: "return_to:/") %>
  • <% end %> <% end %> From 0a479c059756b77e19c07e23d99a4f457c6a345b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Mar 2024 08:03:30 +0100 Subject: [PATCH 241/344] Makes more sense --- lib/seek/json_metadata/attribute.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index f3ef709dbe..ff03848100 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -78,9 +78,9 @@ def linked_sample_type_and_attribute_type_consistency if sample_attribute_type && linked_sample_type && !seek_sample? && !seek_sample_multi? errors.add(:sample_attribute_type, 'Attribute type must be SeekSample if linked sample type set') end - if seek_sample? && linked_sample_type.nil? && is_isa_compliant_input? + if seek_sample? && linked_sample_type.nil? && !is_isa_compliant_input? errors.add(:seek_sample, 'Linked Sample Type must be set if attribute type is Registered Sample') - elsif seek_sample_multi? && linked_sample_type.nil? && is_isa_compliant_input? + elsif seek_sample_multi? && linked_sample_type.nil? && !is_isa_compliant_input? errors.add(:seek_sample_multi, 'Linked Sample Type must be set if attribute type is Registered Sample List') end end @@ -96,9 +96,9 @@ def check_value_against_base_type(value) def is_isa_compliant_input? if is_a?(SampleAttribute) - !(input_attribute? && sample_type.is_isa_json_compliant?) + input_attribute? && sample_type.is_isa_json_compliant? else - !input_attribute? + input_attribute? end end end From b2a8389d702de5abf15b47f099342e1444aefed7 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 15 Mar 2024 08:31:15 +0100 Subject: [PATCH 242/344] Don't take into account sample attributes --- lib/seek/json_metadata/attribute.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/seek/json_metadata/attribute.rb b/lib/seek/json_metadata/attribute.rb index ff03848100..388d2155fc 100644 --- a/lib/seek/json_metadata/attribute.rb +++ b/lib/seek/json_metadata/attribute.rb @@ -78,9 +78,9 @@ def linked_sample_type_and_attribute_type_consistency if sample_attribute_type && linked_sample_type && !seek_sample? && !seek_sample_multi? errors.add(:sample_attribute_type, 'Attribute type must be SeekSample if linked sample type set') end - if seek_sample? && linked_sample_type.nil? && !is_isa_compliant_input? + if seek_sample? && linked_sample_type.nil? && !is_isa_compliant_template_input? errors.add(:seek_sample, 'Linked Sample Type must be set if attribute type is Registered Sample') - elsif seek_sample_multi? && linked_sample_type.nil? && !is_isa_compliant_input? + elsif seek_sample_multi? && linked_sample_type.nil? && !is_isa_compliant_template_input? errors.add(:seek_sample_multi, 'Linked Sample Type must be set if attribute type is Registered Sample List') end end @@ -94,12 +94,10 @@ def check_value_against_base_type(value) base_type_handler.validate_value?(value) end - def is_isa_compliant_input? - if is_a?(SampleAttribute) - input_attribute? && sample_type.is_isa_json_compliant? - else - input_attribute? - end + def is_isa_compliant_template_input? + return input_attribute? if is_a?(TemplateAttribute) + + false end end end From 4ff34f8af904fc30995affe32c237b56d02a5f67 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Fri, 15 Mar 2024 11:17:57 +0100 Subject: [PATCH 243/344] add write api for extended metadata feature --- lib/seek/api/parameter_converter.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/seek/api/parameter_converter.rb b/lib/seek/api/parameter_converter.rb index cc04acd1be..bf65bd2032 100644 --- a/lib/seek/api/parameter_converter.rb +++ b/lib/seek/api/parameter_converter.rb @@ -162,6 +162,11 @@ def self.convert(*attrs, rename: nil, elevate: false, only: [], except: [], &blo end end + convert :extended_attributes, rename: :extended_metadata_attributes do |value| + value["data"] = value.delete("attribute_map") + value + end + convert :tools, rename: :tools_attributes, only: :workflows do |value| biotools_client = BioTools::Client.new value.map do |t| From babd0f180857a9ef26bec22c95ae61ca7c27f3a6 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Fri, 15 Mar 2024 11:19:00 +0100 Subject: [PATCH 244/344] test WriteAPI for Extended Metadata with Study model --- public/api/definitions/_schemas.yml | 18 +++ public/api/examples/studiesResponse.json | 28 ---- public/api/examples/studyPatch.json | 48 ------ public/api/examples/studyPatchResponse.json | 126 --------------- public/api/examples/studyPost.json | 47 ------ public/api/examples/studyPostResponse.json | 126 --------------- public/api/examples/studyResponse.json | 143 ------------------ test/factories/extended_metadata.rb | 12 ++ test/factories/studies.rb | 1 + .../json/requests/patch_max_study.json.erb | 8 + .../json/requests/post_max_study.json.erb | 8 + .../json/responses/get_max_study.json.erb | 8 + test/integration/api/study_api_test.rb | 1 + 13 files changed, 56 insertions(+), 518 deletions(-) delete mode 100644 public/api/examples/studiesResponse.json delete mode 100644 public/api/examples/studyPatch.json delete mode 100644 public/api/examples/studyPatchResponse.json delete mode 100644 public/api/examples/studyPost.json delete mode 100644 public/api/examples/studyPostResponse.json delete mode 100644 public/api/examples/studyResponse.json create mode 100644 test/factories/extended_metadata.rb diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index 2767b655e5..17df22cd06 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -167,6 +167,17 @@ assetLinkPatchList: anyOf: - $ref: "#/components/schemas/assetLink" - $ref: "#/components/schemas/assetLinkCreate" +extendedMetadata: + type: object + properties: + extended_metadata_type_id: + type: string + attribute_map: + $ref: "#/components/schemas/sampleAttributeMap" + required: + - extended_metadata_type_id + - attribute_map + additionalProperties: false snapshotSkeleton: type: object properties: @@ -818,6 +829,7 @@ sampleAttributeMap: - type: string - type: array - type: 'null' + - type: integer # --- Types --- anyType: type: string @@ -2813,6 +2825,8 @@ studyPost: $ref: "#/components/schemas/policy" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title additionalProperties: false @@ -2863,6 +2877,8 @@ studyPatch: $ref: "#/components/schemas/assetsCreatorsList" policy: $ref: "#/components/schemas/policy" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" additionalProperties: false @@ -4765,6 +4781,8 @@ studyResponse: $ref: "#/components/schemas/policy" discussion_links: $ref: "#/components/schemas/assetLinkList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" position: $ref: "#/components/schemas/nullableInteger" required: diff --git a/public/api/examples/studiesResponse.json b/public/api/examples/studiesResponse.json deleted file mode 100644 index 08c505ae2a..0000000000 --- a/public/api/examples/studiesResponse.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "data": [ - { - "id": "120", - "type": "studies", - "attributes": { - "title": "Study105" - }, - "links": { - "self": "/studies/120" - } - } - ], - "jsonapi": { - "version": "1.0" - }, - "links": { - "self": "/studies?page%5Bnumber%5D=1&page%5Bsize%5D=1000000", - "first": "/studies?page%5Bnumber%5D=1&page%5Bsize%5D=1000000", - "prev": null, - "next": null, - "last": "/studies?page%5Bnumber%5D=1&page%5Bsize%5D=1000000" - }, - "meta": { - "base_url": "http://localhost:3000", - "api_version": "0.3" - } -} \ No newline at end of file diff --git a/public/api/examples/studyPatch.json b/public/api/examples/studyPatch.json deleted file mode 100644 index 18de4e5eb8..0000000000 --- a/public/api/examples/studyPatch.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "data": { - "type": "studies", - "id": "129", - "attributes": { - "title": "A Maximal Study", - "description": "The Study of many things", - "experimentalists": "Wet lab people", - "other_creators": "Marie Curie", - "policy": { - "access": "download", - "permissions": [ - { - "resource": { - "id": "734", - "type": "projects" - }, - "access": "view" - } - ] - } - }, - "relationships": { - "investigation": { - "data": { - "id": "147", - "type": "investigations" - } - }, - "publications": { - "data": [ - { - "id": "60", - "type": "publications" - } - ] - }, - "creators": { - "data": [ - { - "id": "669", - "type": "people" - } - ] - } - } - } -} \ No newline at end of file diff --git a/public/api/examples/studyPatchResponse.json b/public/api/examples/studyPatchResponse.json deleted file mode 100644 index 3f713b4a6f..0000000000 --- a/public/api/examples/studyPatchResponse.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "data": { - "id": "129", - "type": "studies", - "attributes": { - "policy": { - "access": "download", - "permissions": [ - { - "resource": { - "id": "734", - "type": "projects" - }, - "access": "view" - } - ] - }, - "discussion_links": [ - - ], - "snapshots": [ - - ], - "title": "A Maximal Study", - "description": "The Study of many things", - "experimentalists": "Wet lab people", - "other_creators": "Marie Curie", - "position": null, - "creators": [ - { - "profile": "/people/669", - "family_name": "Last", - "given_name": "Person559", - "affiliation": "An Institution: 679", - "orcid": null - } - ] - }, - "relationships": { - "creators": { - "data": [ - { - "id": "669", - "type": "people" - } - ] - }, - "submitter": { - "data": [ - { - "id": "669", - "type": "people" - } - ] - }, - "people": { - "data": [ - { - "id": "669", - "type": "people" - } - ] - }, - "projects": { - "data": [ - { - "id": "734", - "type": "projects" - } - ] - }, - "investigation": { - "data": { - "id": "147", - "type": "investigations" - } - }, - "assays": { - "data": [ - - ] - }, - "data_files": { - "data": [ - - ] - }, - "models": { - "data": [ - - ] - }, - "sops": { - "data": [ - - ] - }, - "publications": { - "data": [ - { - "id": "60", - "type": "publications" - } - ] - }, - "documents": { - "data": [ - - ] - } - }, - "links": { - "self": "/studies/129" - }, - "meta": { - "created": "2022-06-07T14:14:08.000Z", - "modified": "2022-06-07T14:14:08.000Z", - "api_version": "0.3", - "base_url": "http://localhost:3000", - "uuid": "0547cde0-c89a-013a-0f7e-0a81884ed284" - } - }, - "jsonapi": { - "version": "1.0" - } -} \ No newline at end of file diff --git a/public/api/examples/studyPost.json b/public/api/examples/studyPost.json deleted file mode 100644 index a4348a04ed..0000000000 --- a/public/api/examples/studyPost.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "data": { - "type": "studies", - "attributes": { - "title": "A Maximal Study", - "description": "The Study of many things", - "experimentalists": "Wet lab people", - "other_creators": "Marie Curie", - "policy": { - "access": "download", - "permissions": [ - { - "resource": { - "id": "689", - "type": "projects" - }, - "access": "view" - } - ] - } - }, - "relationships": { - "investigation": { - "data": { - "id": "128", - "type": "investigations" - } - }, - "publications": { - "data": [ - { - "id": "55", - "type": "publications" - } - ] - }, - "creators": { - "data": [ - { - "id": "629", - "type": "people" - } - ] - } - } - } -} \ No newline at end of file diff --git a/public/api/examples/studyPostResponse.json b/public/api/examples/studyPostResponse.json deleted file mode 100644 index 140421fb1b..0000000000 --- a/public/api/examples/studyPostResponse.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "data": { - "id": "113", - "type": "studies", - "attributes": { - "policy": { - "access": "download", - "permissions": [ - { - "resource": { - "id": "689", - "type": "projects" - }, - "access": "view" - } - ] - }, - "discussion_links": [ - - ], - "snapshots": [ - - ], - "title": "A Maximal Study", - "description": "The Study of many things", - "experimentalists": "Wet lab people", - "other_creators": "Marie Curie", - "position": null, - "creators": [ - { - "profile": "/people/629", - "family_name": "Last", - "given_name": "Person526", - "affiliation": "An Institution: 639", - "orcid": null - } - ] - }, - "relationships": { - "creators": { - "data": [ - { - "id": "629", - "type": "people" - } - ] - }, - "submitter": { - "data": [ - { - "id": "629", - "type": "people" - } - ] - }, - "people": { - "data": [ - { - "id": "629", - "type": "people" - } - ] - }, - "projects": { - "data": [ - { - "id": "689", - "type": "projects" - } - ] - }, - "investigation": { - "data": { - "id": "128", - "type": "investigations" - } - }, - "assays": { - "data": [ - - ] - }, - "data_files": { - "data": [ - - ] - }, - "models": { - "data": [ - - ] - }, - "sops": { - "data": [ - - ] - }, - "publications": { - "data": [ - { - "id": "55", - "type": "publications" - } - ] - }, - "documents": { - "data": [ - - ] - } - }, - "links": { - "self": "/studies/113" - }, - "meta": { - "created": "2022-06-07T14:14:01.000Z", - "modified": "2022-06-07T14:14:01.000Z", - "api_version": "0.3", - "base_url": "http://localhost:3000", - "uuid": "012f7dc0-c89a-013a-0f7e-0a81884ed284" - } - }, - "jsonapi": { - "version": "1.0" - } -} \ No newline at end of file diff --git a/public/api/examples/studyResponse.json b/public/api/examples/studyResponse.json deleted file mode 100644 index 5242c49da1..0000000000 --- a/public/api/examples/studyResponse.json +++ /dev/null @@ -1,143 +0,0 @@ -{ - "data": { - "id": "128", - "type": "studies", - "attributes": { - "policy": { - "access": "no_access", - "permissions": [ - - ] - }, - "discussion_links": [ - { - "id": "60", - "label": "Slack", - "url": "http://www.slack.com/" - } - ], - "snapshots": [ - - ], - "title": "A Maximal Study", - "description": "The Study of many things", - "experimentalists": "Wet lab people", - "other_creators": "Marie Curie", - "position": null, - "creators": [ - { - "profile": "/people/655", - "family_name": "One", - "given_name": "Some", - "affiliation": "University of Somewhere", - "orcid": null - } - ] - }, - "relationships": { - "creators": { - "data": [ - { - "id": "655", - "type": "people" - } - ] - }, - "submitter": { - "data": [ - { - "id": "653", - "type": "people" - } - ] - }, - "people": { - "data": [ - { - "id": "653", - "type": "people" - }, - { - "id": "655", - "type": "people" - } - ] - }, - "projects": { - "data": [ - { - "id": "717", - "type": "projects" - } - ] - }, - "investigation": { - "data": { - "id": "141", - "type": "investigations" - } - }, - "assays": { - "data": [ - { - "id": "108", - "type": "assays" - } - ] - }, - "data_files": { - "data": [ - { - "id": "44", - "type": "data_files" - } - ] - }, - "models": { - "data": [ - { - "id": "109", - "type": "models" - } - ] - }, - "sops": { - "data": [ - { - "id": "42", - "type": "sops" - } - ] - }, - "publications": { - "data": [ - { - "id": "59", - "type": "publications" - } - ] - }, - "documents": { - "data": [ - { - "id": "52", - "type": "documents" - } - ] - } - }, - "links": { - "self": "/studies/128" - }, - "meta": { - "created": "2022-06-07T14:14:08.000Z", - "modified": "2022-06-07T14:14:08.000Z", - "api_version": "0.3", - "base_url": "http://localhost:3000", - "uuid": "04ee13c0-c89a-013a-0f7e-0a81884ed284" - } - }, - "jsonapi": { - "version": "1.0" - } -} \ No newline at end of file diff --git a/test/factories/extended_metadata.rb b/test/factories/extended_metadata.rb new file mode 100644 index 0000000000..6992c481c8 --- /dev/null +++ b/test/factories/extended_metadata.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + + factory(:simple_extended_metadata, class: ExtendedMetadata) do + association :extended_metadata_type, factory: :simple_study_extended_metadata_type, strategy: :create + after(:build) do |em| + em.set_attribute_value(:name, 'Fred Bloggs') + em.set_attribute_value(:age, 44) + em.set_attribute_value(:date, '2024-01-01') + end + end + +end \ No newline at end of file diff --git a/test/factories/studies.rb b/test/factories/studies.rb index afb01507f0..d917b2bc1e 100644 --- a/test/factories/studies.rb +++ b/test/factories/studies.rb @@ -29,6 +29,7 @@ title { "A Maximal Study" } description { "The Study of many things" } discussion_links { [FactoryBot.build(:discussion_link, label:'Slack')] } + extended_metadata { FactoryBot.build(:simple_extended_metadata)} experimentalists { "Wet lab people" } other_creators { "Marie Curie" } after(:build) do |s| diff --git a/test/fixtures/json/requests/patch_max_study.json.erb b/test/fixtures/json/requests/patch_max_study.json.erb index e501b266ed..67cbbf85ab 100644 --- a/test/fixtures/json/requests/patch_max_study.json.erb +++ b/test/fixtures/json/requests/patch_max_study.json.erb @@ -18,6 +18,14 @@ "access": "view" } ] + }, + "extended_attributes": { + "extended_metadata_type_id": "<%= @emt.id %>", + "attribute_map": { + "name": "Fred Bloggs", + "age": 44, + "date": "2024-01-01" + } } }, "relationships": { diff --git a/test/fixtures/json/requests/post_max_study.json.erb b/test/fixtures/json/requests/post_max_study.json.erb index 5846cacbd6..ecb964b667 100644 --- a/test/fixtures/json/requests/post_max_study.json.erb +++ b/test/fixtures/json/requests/post_max_study.json.erb @@ -17,6 +17,14 @@ "access": "view" } ] + }, + "extended_attributes": { + "extended_metadata_type_id": "<%= @emt.id %>", + "attribute_map": { + "name": "Fred Bloggs", + "age": 44, + "date": "2024-01-01" + } } }, "relationships": { diff --git a/test/fixtures/json/responses/get_max_study.json.erb b/test/fixtures/json/responses/get_max_study.json.erb index c76fa3af7e..c16e3cdaa0 100644 --- a/test/fixtures/json/responses/get_max_study.json.erb +++ b/test/fixtures/json/responses/get_max_study.json.erb @@ -8,6 +8,14 @@ "access": "no_access", "permissions": [] }, + "extended_attributes": { + "extended_metadata_type_id": "<%= res.extended_metadata.extended_metadata_type_id %>", + "attribute_map": { + "name": "Fred Bloggs", + "age": 44, + "date": "2024-01-01" + } + }, "title": "A Maximal Study", "description": "The Study of many things", "experimentalists": "Wet lab people", diff --git a/test/integration/api/study_api_test.rb b/test/integration/api/study_api_test.rb index 3fb3bc10f5..c03cfa5f12 100644 --- a/test/integration/api/study_api_test.rb +++ b/test/integration/api/study_api_test.rb @@ -12,6 +12,7 @@ def setup @publication = FactoryBot.create(:publication) @study = FactoryBot.create(:study, policy: FactoryBot.create(:public_policy), contributor: current_person) + @emt = FactoryBot.create(:simple_study_extended_metadata_type) end test 'should not delete a study with assays' do From ee7627c1e2ec8a31b3936540a32bd6a8d364b148 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Fri, 15 Mar 2024 12:15:36 +0100 Subject: [PATCH 245/344] add updated example json files --- public/api/examples/studiesResponse.json | 28 ++++ public/api/examples/studyPatch.json | 56 ++++++++ public/api/examples/studyPatchResponse.json | 134 +++++++++++++++++ public/api/examples/studyPost.json | 55 +++++++ public/api/examples/studyPostResponse.json | 134 +++++++++++++++++ public/api/examples/studyResponse.json | 151 ++++++++++++++++++++ 6 files changed, 558 insertions(+) create mode 100644 public/api/examples/studiesResponse.json create mode 100644 public/api/examples/studyPatch.json create mode 100644 public/api/examples/studyPatchResponse.json create mode 100644 public/api/examples/studyPost.json create mode 100644 public/api/examples/studyPostResponse.json create mode 100644 public/api/examples/studyResponse.json diff --git a/public/api/examples/studiesResponse.json b/public/api/examples/studiesResponse.json new file mode 100644 index 0000000000..297e3862e1 --- /dev/null +++ b/public/api/examples/studiesResponse.json @@ -0,0 +1,28 @@ +{ + "data": [ + { + "id": "3086", + "type": "studies", + "attributes": { + "title": "Study49" + }, + "links": { + "self": "/studies/3086" + } + } + ], + "jsonapi": { + "version": "1.0" + }, + "links": { + "self": "/studies?page%5Bnumber%5D=1&page%5Bsize%5D=1000000", + "first": "/studies?page%5Bnumber%5D=1&page%5Bsize%5D=1000000", + "prev": null, + "next": null, + "last": "/studies?page%5Bnumber%5D=1&page%5Bsize%5D=1000000" + }, + "meta": { + "base_url": "http://localhost:3000", + "api_version": "0.3" + } +} \ No newline at end of file diff --git a/public/api/examples/studyPatch.json b/public/api/examples/studyPatch.json new file mode 100644 index 0000000000..5b0327caf0 --- /dev/null +++ b/public/api/examples/studyPatch.json @@ -0,0 +1,56 @@ +{ + "data": { + "type": "studies", + "id": "3096", + "attributes": { + "title": "A Maximal Study", + "description": "The Study of many things", + "experimentalists": "Wet lab people", + "other_creators": "Marie Curie", + "policy": { + "access": "download", + "permissions": [ + { + "resource": { + "id": "11830", + "type": "projects" + }, + "access": "view" + } + ] + }, + "extended_attributes": { + "extended_metadata_type_id": "940", + "attribute_map": { + "name": "Fred Bloggs", + "age": 44, + "date": "2024-01-01" + } + } + }, + "relationships": { + "investigation": { + "data": { + "id": "4131", + "type": "investigations" + } + }, + "publications": { + "data": [ + { + "id": "1531", + "type": "publications" + } + ] + }, + "creators": { + "data": [ + { + "id": "10375", + "type": "people" + } + ] + } + } + } +} \ No newline at end of file diff --git a/public/api/examples/studyPatchResponse.json b/public/api/examples/studyPatchResponse.json new file mode 100644 index 0000000000..ef367a22eb --- /dev/null +++ b/public/api/examples/studyPatchResponse.json @@ -0,0 +1,134 @@ +{ + "data": { + "id": "3096", + "type": "studies", + "attributes": { + "policy": { + "access": "download", + "permissions": [ + { + "resource": { + "id": "11830", + "type": "projects" + }, + "access": "view" + } + ] + }, + "discussion_links": [ + + ], + "extended_attributes": { + "extended_metadata_type_id": "940", + "attribute_map": { + "age": 44, + "name": "Fred Bloggs", + "date": "2024-01-01" + } + }, + "snapshots": [ + + ], + "title": "A Maximal Study", + "description": "The Study of many things", + "experimentalists": "Wet lab people", + "other_creators": "Marie Curie", + "position": null, + "creators": [ + { + "profile": "/people/10375", + "family_name": "Last", + "given_name": "Person138", + "affiliation": "An Institution: 173", + "orcid": null + } + ] + }, + "relationships": { + "creators": { + "data": [ + { + "id": "10375", + "type": "people" + } + ] + }, + "submitter": { + "data": [ + { + "id": "10375", + "type": "people" + } + ] + }, + "people": { + "data": [ + { + "id": "10375", + "type": "people" + } + ] + }, + "projects": { + "data": [ + { + "id": "11830", + "type": "projects" + } + ] + }, + "investigation": { + "data": { + "id": "4131", + "type": "investigations" + } + }, + "assays": { + "data": [ + + ] + }, + "data_files": { + "data": [ + + ] + }, + "models": { + "data": [ + + ] + }, + "sops": { + "data": [ + + ] + }, + "publications": { + "data": [ + { + "id": "1531", + "type": "publications" + } + ] + }, + "documents": { + "data": [ + + ] + } + }, + "links": { + "self": "/studies/3096" + }, + "meta": { + "created": "2024-03-15T10:03:27.000Z", + "modified": "2024-03-15T10:03:27.000Z", + "api_version": "0.3", + "base_url": "http://localhost:3000", + "uuid": "31299a30-c4e1-013c-640d-7efdca78d793" + } + }, + "jsonapi": { + "version": "1.0" + } +} \ No newline at end of file diff --git a/public/api/examples/studyPost.json b/public/api/examples/studyPost.json new file mode 100644 index 0000000000..6b7cf7cbf1 --- /dev/null +++ b/public/api/examples/studyPost.json @@ -0,0 +1,55 @@ +{ + "data": { + "type": "studies", + "attributes": { + "title": "A Maximal Study", + "description": "The Study of many things", + "experimentalists": "Wet lab people", + "other_creators": "Marie Curie", + "policy": { + "access": "download", + "permissions": [ + { + "resource": { + "id": "11779", + "type": "projects" + }, + "access": "view" + } + ] + }, + "extended_attributes": { + "extended_metadata_type_id": "935", + "attribute_map": { + "name": "Fred Bloggs", + "age": 44, + "date": "2024-01-01" + } + } + }, + "relationships": { + "investigation": { + "data": { + "id": "4110", + "type": "investigations" + } + }, + "publications": { + "data": [ + { + "id": "1526", + "type": "publications" + } + ] + }, + "creators": { + "data": [ + { + "id": "10329", + "type": "people" + } + ] + } + } + } +} \ No newline at end of file diff --git a/public/api/examples/studyPostResponse.json b/public/api/examples/studyPostResponse.json new file mode 100644 index 0000000000..3038b57a85 --- /dev/null +++ b/public/api/examples/studyPostResponse.json @@ -0,0 +1,134 @@ +{ + "data": { + "id": "3078", + "type": "studies", + "attributes": { + "policy": { + "access": "download", + "permissions": [ + { + "resource": { + "id": "11779", + "type": "projects" + }, + "access": "view" + } + ] + }, + "discussion_links": [ + + ], + "extended_attributes": { + "extended_metadata_type_id": "935", + "attribute_map": { + "age": 44, + "name": "Fred Bloggs", + "date": "2024-01-01" + } + }, + "snapshots": [ + + ], + "title": "A Maximal Study", + "description": "The Study of many things", + "experimentalists": "Wet lab people", + "other_creators": "Marie Curie", + "position": null, + "creators": [ + { + "profile": "/people/10329", + "family_name": "Last", + "given_name": "Person99", + "affiliation": "An Institution: 127", + "orcid": null + } + ] + }, + "relationships": { + "creators": { + "data": [ + { + "id": "10329", + "type": "people" + } + ] + }, + "submitter": { + "data": [ + { + "id": "10329", + "type": "people" + } + ] + }, + "people": { + "data": [ + { + "id": "10329", + "type": "people" + } + ] + }, + "projects": { + "data": [ + { + "id": "11779", + "type": "projects" + } + ] + }, + "investigation": { + "data": { + "id": "4110", + "type": "investigations" + } + }, + "assays": { + "data": [ + + ] + }, + "data_files": { + "data": [ + + ] + }, + "models": { + "data": [ + + ] + }, + "sops": { + "data": [ + + ] + }, + "publications": { + "data": [ + { + "id": "1526", + "type": "publications" + } + ] + }, + "documents": { + "data": [ + + ] + } + }, + "links": { + "self": "/studies/3078" + }, + "meta": { + "created": "2024-03-15T10:03:23.000Z", + "modified": "2024-03-15T10:03:23.000Z", + "api_version": "0.3", + "base_url": "http://localhost:3000", + "uuid": "2f175c70-c4e1-013c-640d-7efdca78d793" + } + }, + "jsonapi": { + "version": "1.0" + } +} \ No newline at end of file diff --git a/public/api/examples/studyResponse.json b/public/api/examples/studyResponse.json new file mode 100644 index 0000000000..8ad45d9701 --- /dev/null +++ b/public/api/examples/studyResponse.json @@ -0,0 +1,151 @@ +{ + "data": { + "id": "3095", + "type": "studies", + "attributes": { + "policy": { + "access": "no_access", + "permissions": [ + + ] + }, + "discussion_links": [ + { + "id": "507", + "label": "Slack", + "url": "http://www.slack.com/" + } + ], + "extended_attributes": { + "extended_metadata_type_id": "939", + "attribute_map": { + "age": 44, + "name": "Fred Bloggs", + "date": "2024-01-01" + } + }, + "snapshots": [ + + ], + "title": "A Maximal Study", + "description": "The Study of many things", + "experimentalists": "Wet lab people", + "other_creators": "Marie Curie", + "position": null, + "creators": [ + { + "profile": "/people/10358", + "family_name": "One", + "given_name": "Some", + "affiliation": "University of Somewhere", + "orcid": null + } + ] + }, + "relationships": { + "creators": { + "data": [ + { + "id": "10358", + "type": "people" + } + ] + }, + "submitter": { + "data": [ + { + "id": "10356", + "type": "people" + } + ] + }, + "people": { + "data": [ + { + "id": "10356", + "type": "people" + }, + { + "id": "10358", + "type": "people" + } + ] + }, + "projects": { + "data": [ + { + "id": "11810", + "type": "projects" + } + ] + }, + "investigation": { + "data": { + "id": "4124", + "type": "investigations" + } + }, + "assays": { + "data": [ + { + "id": "1498", + "type": "assays" + } + ] + }, + "data_files": { + "data": [ + { + "id": "390", + "type": "data_files" + } + ] + }, + "models": { + "data": [ + { + "id": "511", + "type": "models" + } + ] + }, + "sops": { + "data": [ + { + "id": "372", + "type": "sops" + } + ] + }, + "publications": { + "data": [ + { + "id": "1530", + "type": "publications" + } + ] + }, + "documents": { + "data": [ + { + "id": "442", + "type": "documents" + } + ] + } + }, + "links": { + "self": "/studies/3095" + }, + "meta": { + "created": "2024-03-15T10:03:27.000Z", + "modified": "2024-03-15T10:03:27.000Z", + "api_version": "0.3", + "base_url": "http://localhost:3000", + "uuid": "3100d260-c4e1-013c-640d-7efdca78d793" + } + }, + "jsonapi": { + "version": "1.0" + } +} \ No newline at end of file From 37d36c528a67039e307e7a75d2f689a73501ff9c Mon Sep 17 00:00:00 2001 From: whomingbird Date: Fri, 15 Mar 2024 13:45:47 +0100 Subject: [PATCH 246/344] test WriteAPI for Extended Metadata with Project model --- public/api/definitions/_schemas.yml | 13 +- public/api/examples/projectPatch.json | 35 ++- public/api/examples/projectPatchResponse.json | 43 +++- public/api/examples/projectPost.json | 33 ++- public/api/examples/projectPostResponse.json | 43 +++- public/api/examples/projectResponse.json | 71 ++++-- public/api/examples/projectsResponse.json | 220 +++++++++--------- test/factories/extended_metadata.rb | 9 + test/factories/projects.rb | 1 + .../json/requests/patch_max_project.json.erb | 23 ++ .../json/requests/post_max_project.json.erb | 23 ++ test/integration/api/project_api_test.rb | 2 + 12 files changed, 350 insertions(+), 166 deletions(-) diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index 17df22cd06..6162429cde 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -824,12 +824,7 @@ projectMembersList: additionalProperties: false sampleAttributeMap: type: object - additionalProperties: - anyOf: - - type: string - - type: array - - type: 'null' - - type: integer + additionalProperties: true # --- Types --- anyType: type: string @@ -2353,6 +2348,8 @@ projectPost: $ref: "#/components/schemas/nullableLicense" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" topic_annotations: $ref: "#/components/schemas/ontologyTermsList" required: @@ -2404,6 +2401,8 @@ projectPatch: $ref: "#/components/schemas/nullableLicense" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" topic_annotations: $ref: "#/components/schemas/ontologyTermsList" additionalProperties: false @@ -4232,6 +4231,8 @@ projectResponse: $ref: "#/components/schemas/nullableLicense" discussion_links: $ref: "#/components/schemas/assetLinkList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" topic_annotations: $ref: "#/components/schemas/ontologyTermsList" start_date: diff --git a/public/api/examples/projectPatch.json b/public/api/examples/projectPatch.json index 1e0e2c33db..eda94f5199 100644 --- a/public/api/examples/projectPatch.json +++ b/public/api/examples/projectPatch.json @@ -1,6 +1,6 @@ { "data": { - "id": "389", + "id": "15790", "type": "projects", "attributes": { "avatar": null, @@ -14,27 +14,50 @@ "permissions": [ { "resource": { - "id": "320", + "id": "13324", "type": "people" }, "access": "manage" }, { "resource": { - "id": "389", + "id": "15790", "type": "projects" }, "access": "download" }, { "resource": { - "id": "347", + "id": "13808", "type": "institutions" }, "access": "view" } ] }, + "extended_attributes": { + "extended_metadata_type_id": "2662", + "attribute_map": { + "dad": { + "first_name": "john", + "last_name": "liddell" + }, + "mom": { + "first_name": "lily", + "last_name": "liddell" + }, + "child": [ + { + "first_name": "rabbit", + "last_name": "wonderland" + }, + { + "first_name": "mad", + "last_name": "hatter" + } + ] + } + }, "topic_annotations": [ { "label": "Biomedical science", @@ -50,7 +73,7 @@ "programmes": { "data": [ { - "id": "29", + "id": "507", "type": "programmes" } ] @@ -58,7 +81,7 @@ "organisms": { "data": [ { - "id": "29", + "id": "737", "type": "organisms" } ] diff --git a/public/api/examples/projectPatchResponse.json b/public/api/examples/projectPatchResponse.json index 4e97e50cbb..b8b5bd3f66 100644 --- a/public/api/examples/projectPatchResponse.json +++ b/public/api/examples/projectPatchResponse.json @@ -1,11 +1,34 @@ { "data": { - "id": "1022257417", + "id": "15790", "type": "projects", "attributes": { "discussion_links": [ ], + "extended_attributes": { + "extended_metadata_type_id": "2662", + "attribute_map": { + "dad": { + "first_name": "john", + "last_name": "liddell" + }, + "mom": { + "first_name": "lily", + "last_name": "liddell" + }, + "child": [ + { + "first_name": "rabbit", + "last_name": "wonderland" + }, + { + "first_name": "mad", + "last_name": "hatter" + } + ] + } + }, "avatar": null, "title": "Updated Project", "description": "with a new description!", @@ -19,21 +42,21 @@ "permissions": [ { "resource": { - "id": "1039992753", + "id": "13324", "type": "people" }, "access": "manage" }, { "resource": { - "id": "1022257417", + "id": "15790", "type": "projects" }, "access": "download" }, { "resource": { - "id": "980197315", + "id": "13808", "type": "institutions" }, "access": "view" @@ -79,7 +102,7 @@ "organisms": { "data": [ { - "id": "627234431", + "id": "737", "type": "organisms" } ] @@ -102,7 +125,7 @@ "programmes": { "data": [ { - "id": "156", + "id": "507", "type": "programmes" } ] @@ -179,14 +202,14 @@ } }, "links": { - "self": "/projects/1022257417" + "self": "/projects/15790" }, "meta": { - "created": "2022-10-26T11:45:28.000Z", - "modified": "2022-10-26T11:45:28.000Z", + "created": "2024-03-15T12:40:36.000Z", + "modified": "2024-03-15T12:40:36.000Z", "api_version": "0.3", "base_url": "http://localhost:3000", - "uuid": "9ca85d90-3751-013b-356e-000c29a94011" + "uuid": "253ae910-c4f7-013c-642b-7efdca78d793" } }, "jsonapi": { diff --git a/public/api/examples/projectPost.json b/public/api/examples/projectPost.json index bb540312fc..1b060335b3 100644 --- a/public/api/examples/projectPost.json +++ b/public/api/examples/projectPost.json @@ -13,27 +13,50 @@ "permissions": [ { "resource": { - "id": "249", + "id": "13321", "type": "people" }, "access": "manage" }, { "resource": { - "id": "304", + "id": "15784", "type": "projects" }, "access": "download" }, { "resource": { - "id": "273", + "id": "13804", "type": "institutions" }, "access": "view" } ] }, + "extended_attributes": { + "extended_metadata_type_id": "2658", + "attribute_map": { + "dad": { + "first_name": "john", + "last_name": "liddell" + }, + "mom": { + "first_name": "lily", + "last_name": "liddell" + }, + "child": [ + { + "first_name": "rabbit", + "last_name": "wonderland" + }, + { + "first_name": "mad", + "last_name": "hatter" + } + ] + } + }, "topic_annotations": [ { "label": "Biomedical science", @@ -49,7 +72,7 @@ "programmes": { "data": [ { - "id": "24", + "id": "506", "type": "programmes" } ] @@ -57,7 +80,7 @@ "organisms": { "data": [ { - "id": "24", + "id": "736", "type": "organisms" } ] diff --git a/public/api/examples/projectPostResponse.json b/public/api/examples/projectPostResponse.json index c32ba0ea8d..e6ccd4e633 100644 --- a/public/api/examples/projectPostResponse.json +++ b/public/api/examples/projectPostResponse.json @@ -1,11 +1,34 @@ { "data": { - "id": "1022257332", + "id": "15786", "type": "projects", "attributes": { "discussion_links": [ ], + "extended_attributes": { + "extended_metadata_type_id": "2658", + "attribute_map": { + "dad": { + "first_name": "john", + "last_name": "liddell" + }, + "mom": { + "first_name": "lily", + "last_name": "liddell" + }, + "child": [ + { + "first_name": "rabbit", + "last_name": "wonderland" + }, + { + "first_name": "mad", + "last_name": "hatter" + } + ] + } + }, "avatar": null, "title": "Post Project Max", "description": "A Taverna project", @@ -19,21 +42,21 @@ "permissions": [ { "resource": { - "id": "1039992680", + "id": "13321", "type": "people" }, "access": "manage" }, { "resource": { - "id": "1022257330", + "id": "15784", "type": "projects" }, "access": "download" }, { "resource": { - "id": "980197239", + "id": "13804", "type": "institutions" }, "access": "view" @@ -79,7 +102,7 @@ "organisms": { "data": [ { - "id": "627234426", + "id": "736", "type": "organisms" } ] @@ -102,7 +125,7 @@ "programmes": { "data": [ { - "id": "151", + "id": "506", "type": "programmes" } ] @@ -179,14 +202,14 @@ } }, "links": { - "self": "/projects/1022257332" + "self": "/projects/15786" }, "meta": { - "created": "2022-10-26T11:45:20.000Z", - "modified": "2022-10-26T11:45:20.000Z", + "created": "2024-03-15T12:40:36.000Z", + "modified": "2024-03-15T12:40:36.000Z", "api_version": "0.3", "base_url": "http://localhost:3000", - "uuid": "97d63130-3751-013b-356e-000c29a94011" + "uuid": "2520d160-c4f7-013c-642b-7efdca78d793" } }, "jsonapi": { diff --git a/public/api/examples/projectResponse.json b/public/api/examples/projectResponse.json index 633a388a1f..28df2bde2a 100644 --- a/public/api/examples/projectResponse.json +++ b/public/api/examples/projectResponse.json @@ -1,16 +1,39 @@ { "data": { - "id": "1022257413", + "id": "14075", "type": "projects", "attributes": { "discussion_links": [ { - "id": "98", + "id": "639", "label": "Slack", "url": "http://www.slack.com/" } ], - "avatar": "/projects/1022257413/avatars/49", + "extended_attributes": { + "extended_metadata_type_id": "1824", + "attribute_map": { + "dad": { + "first_name": "john", + "last_name": "liddell" + }, + "mom": { + "first_name": "lily", + "last_name": "liddell" + }, + "child": [ + { + "first_name": "rabbit", + "last_name": "wonderland" + }, + { + "first_name": "mad", + "last_name": "hatter" + } + ] + } + }, + "avatar": "/projects/14075/avatars/70", "title": "A Maximal Project", "description": "A Taverna project", "web_page": "http://www.taverna.org.uk", @@ -26,8 +49,8 @@ }, "members": [ { - "person_id": "1039992750", - "institution_id": "980197311" + "person_id": "12085", + "institution_id": "12386" } ], "use_default_policy": true, @@ -76,7 +99,7 @@ "people": { "data": [ { - "id": "1039992750", + "id": "12085", "type": "people" } ] @@ -84,7 +107,7 @@ "institutions": { "data": [ { - "id": "980197311", + "id": "12386", "type": "institutions" } ] @@ -92,7 +115,7 @@ "programmes": { "data": [ { - "id": "155", + "id": "314", "type": "programmes" } ] @@ -100,7 +123,7 @@ "investigations": { "data": [ { - "id": "973655503", + "id": "4396", "type": "investigations" } ] @@ -108,7 +131,7 @@ "studies": { "data": [ { - "id": "1060385511", + "id": "3327", "type": "studies" } ] @@ -116,7 +139,7 @@ "assays": { "data": [ { - "id": "1035387226", + "id": "1696", "type": "assays" } ] @@ -124,7 +147,7 @@ "data_files": { "data": [ { - "id": "883654714", + "id": "456", "type": "data_files" } ] @@ -142,7 +165,7 @@ "models": { "data": [ { - "id": "1004285824", + "id": "611", "type": "models" } ] @@ -150,7 +173,7 @@ "sops": { "data": [ { - "id": "1055250753", + "id": "438", "type": "sops" } ] @@ -158,7 +181,7 @@ "publications": { "data": [ { - "id": "231", + "id": "1597", "type": "publications" } ] @@ -166,7 +189,7 @@ "presentations": { "data": [ { - "id": "109", + "id": "155", "type": "presentations" } ] @@ -174,7 +197,7 @@ "events": { "data": [ { - "id": "1025618799", + "id": "184", "type": "events" } ] @@ -182,7 +205,7 @@ "documents": { "data": [ { - "id": "219", + "id": "508", "type": "documents" } ] @@ -190,7 +213,7 @@ "workflows": { "data": [ { - "id": "222", + "id": "244", "type": "workflows" } ] @@ -198,21 +221,21 @@ "collections": { "data": [ { - "id": "33", + "id": "122", "type": "collections" } ] } }, "links": { - "self": "/projects/1022257413" + "self": "/projects/14075" }, "meta": { - "created": "2022-10-26T11:45:28.000Z", - "modified": "2022-10-26T11:45:28.000Z", + "created": "2024-03-15T12:19:36.000Z", + "modified": "2024-03-15T12:19:36.000Z", "api_version": "0.3", "base_url": "http://localhost:3000", - "uuid": "9c750880-3751-013b-356e-000c29a94011" + "uuid": "3627d4b0-c4f4-013c-641e-7efdca78d793" } }, "jsonapi": { diff --git a/public/api/examples/projectsResponse.json b/public/api/examples/projectsResponse.json index 287bc1acec..6066b016fd 100644 --- a/public/api/examples/projectsResponse.json +++ b/public/api/examples/projectsResponse.json @@ -1,353 +1,363 @@ { "data": [ { - "id": "344", + "id": "13608", "type": "projects", "attributes": { - "title": "A Project: -336" + "title": "A Project: -222" }, "links": { - "self": "/projects/344" + "self": "/projects/13608" } }, { - "id": "345", + "id": "13609", "type": "projects", "attributes": { - "title": "A Project: -337" + "title": "A Project: -223" }, "links": { - "self": "/projects/345" + "self": "/projects/13609" } }, { - "id": "346", + "id": "13610", "type": "projects", "attributes": { - "title": "A Maximal Project" + "title": "A Project: -224" }, "links": { - "self": "/projects/346" + "self": "/projects/13610" } }, { - "id": "337", + "id": "13611", "type": "projects", "attributes": { - "title": "A Project: -329" + "title": "A Project: -225" }, "links": { - "self": "/projects/337" + "self": "/projects/13611" } }, { - "id": "338", + "id": "13612", "type": "projects", "attributes": { - "title": "A Project: -330" + "title": "A Project: -226" }, "links": { - "self": "/projects/338" + "self": "/projects/13612" } }, { - "id": "339", + "id": "13613", "type": "projects", "attributes": { - "title": "A Project: -331" + "title": "A Project: -227" }, "links": { - "self": "/projects/339" + "self": "/projects/13613" } }, { - "id": "340", + "id": "13614", "type": "projects", "attributes": { - "title": "A Project: -332" + "title": "A Project: -228" }, "links": { - "self": "/projects/340" + "self": "/projects/13614" } }, { - "id": "341", + "id": "13615", "type": "projects", "attributes": { - "title": "A Project: -333" + "title": "A Maximal Project" }, "links": { - "self": "/projects/341" + "self": "/projects/13615" } }, { - "id": "342", + "id": "13594", "type": "projects", "attributes": { - "title": "A Project: -334" + "title": "A Project: -208" }, "links": { - "self": "/projects/342" + "self": "/projects/13594" } }, { - "id": "343", + "id": "13595", "type": "projects", "attributes": { - "title": "A Project: -335" + "title": "A Project: -209" }, "links": { - "self": "/projects/343" + "self": "/projects/13595" } }, { - "id": "330", + "id": "13596", "type": "projects", "attributes": { - "title": "A Project: -322" + "title": "A Project: -210" }, "links": { - "self": "/projects/330" + "self": "/projects/13596" } }, { - "id": "331", + "id": "13597", "type": "projects", "attributes": { - "title": "A Project: -323" + "title": "A Project: -211" }, "links": { - "self": "/projects/331" + "self": "/projects/13597" } }, { - "id": "332", + "id": "13598", "type": "projects", "attributes": { - "title": "A Project: -324" + "title": "A Project: -212" }, "links": { - "self": "/projects/332" + "self": "/projects/13598" } }, { - "id": "333", + "id": "13599", "type": "projects", "attributes": { - "title": "A Project: -325" + "title": "A Project: -213" }, "links": { - "self": "/projects/333" + "self": "/projects/13599" } }, { - "id": "334", + "id": "13600", "type": "projects", "attributes": { - "title": "A Project: -326" + "title": "A Project: -214" }, "links": { - "self": "/projects/334" + "self": "/projects/13600" } }, { - "id": "335", + "id": "13601", "type": "projects", "attributes": { - "title": "A Project: -327" + "title": "A Project: -215" }, "links": { - "self": "/projects/335" + "self": "/projects/13601" } }, { - "id": "336", + "id": "13602", "type": "projects", "attributes": { - "title": "A Project: -328" + "title": "A Project: -216" }, "links": { - "self": "/projects/336" + "self": "/projects/13602" } }, { - "id": "326", + "id": "13603", "type": "projects", "attributes": { - "title": "A Project: -318" + "title": "A Project: -217" }, "links": { - "self": "/projects/326" + "self": "/projects/13603" } }, { - "id": "327", + "id": "13604", "type": "projects", "attributes": { - "title": "A Project: -319" + "title": "A Project: -218" }, "links": { - "self": "/projects/327" + "self": "/projects/13604" } }, { - "id": "328", + "id": "13605", "type": "projects", "attributes": { - "title": "A Project: -320" + "title": "A Project: -219" }, "links": { - "self": "/projects/328" + "self": "/projects/13605" } }, { - "id": "329", + "id": "13606", "type": "projects", "attributes": { - "title": "A Project: -321" + "title": "A Project: -220" }, "links": { - "self": "/projects/329" + "self": "/projects/13606" } }, { - "id": "320", + "id": "13607", "type": "projects", "attributes": { - "title": "A Project: -312" + "title": "A Project: -221" }, "links": { - "self": "/projects/320" + "self": "/projects/13607" } }, { - "id": "321", + "id": "13580", "type": "projects", "attributes": { - "title": "A Project: -313" + "title": "A Minimal Project" }, "links": { - "self": "/projects/321" + "self": "/projects/13580" } }, { - "id": "322", + "id": "13581", "type": "projects", "attributes": { - "title": "A Project: -314" + "title": "A Project: -195" }, "links": { - "self": "/projects/322" + "self": "/projects/13581" } }, { - "id": "323", + "id": "13582", "type": "projects", "attributes": { - "title": "A Project: -315" + "title": "A Project: -196" }, "links": { - "self": "/projects/323" + "self": "/projects/13582" } }, { - "id": "324", + "id": "13583", "type": "projects", "attributes": { - "title": "A Project: -316" + "title": "A Project: -197" }, "links": { - "self": "/projects/324" + "self": "/projects/13583" } }, { - "id": "325", + "id": "13584", "type": "projects", "attributes": { - "title": "A Project: -317" + "title": "A Project: -198" }, "links": { - "self": "/projects/325" + "self": "/projects/13584" } }, { - "id": "312", + "id": "13585", "type": "projects", "attributes": { - "title": "A Minimal Project" + "title": "A Project: -199" + }, + "links": { + "self": "/projects/13585" + } + }, + { + "id": "13586", + "type": "projects", + "attributes": { + "title": "A Project: -200" }, "links": { - "self": "/projects/312" + "self": "/projects/13586" } }, { - "id": "313", + "id": "13587", "type": "projects", "attributes": { - "title": "A Project: -305" + "title": "A Project: -201" }, "links": { - "self": "/projects/313" + "self": "/projects/13587" } }, { - "id": "314", + "id": "13588", "type": "projects", "attributes": { - "title": "A Project: -306" + "title": "A Project: -202" }, "links": { - "self": "/projects/314" + "self": "/projects/13588" } }, { - "id": "315", + "id": "13589", "type": "projects", "attributes": { - "title": "A Project: -307" + "title": "A Project: -203" }, "links": { - "self": "/projects/315" + "self": "/projects/13589" } }, { - "id": "316", + "id": "13590", "type": "projects", "attributes": { - "title": "A Project: -308" + "title": "A Project: -204" }, "links": { - "self": "/projects/316" + "self": "/projects/13590" } }, { - "id": "317", + "id": "13591", "type": "projects", "attributes": { - "title": "A Project: -309" + "title": "A Project: -205" }, "links": { - "self": "/projects/317" + "self": "/projects/13591" } }, { - "id": "318", + "id": "13592", "type": "projects", "attributes": { - "title": "A Project: -310" + "title": "A Project: -206" }, "links": { - "self": "/projects/318" + "self": "/projects/13592" } }, { - "id": "319", + "id": "13593", "type": "projects", "attributes": { - "title": "A Project: -311" + "title": "A Project: -207" }, "links": { - "self": "/projects/319" + "self": "/projects/13593" } } ], diff --git a/test/factories/extended_metadata.rb b/test/factories/extended_metadata.rb index 6992c481c8..167add5130 100644 --- a/test/factories/extended_metadata.rb +++ b/test/factories/extended_metadata.rb @@ -9,4 +9,13 @@ end end + factory(:family_extended_metadata, class: ExtendedMetadata) do + association :extended_metadata_type, factory: :family_extended_metadata_type, strategy: :create + after(:build) do |em| + em.set_attribute_value(:dad, {"first_name":"john", "last_name":"liddell"}) + em.set_attribute_value(:mom, {"first_name":"lily", "last_name":"liddell"}) + em.set_attribute_value(:child, {"0":{"first_name":"rabbit", "last_name":"wonderland"},"1":{"first_name":"mad", "last_name":"hatter"}}) + end + end + end \ No newline at end of file diff --git a/test/factories/projects.rb b/test/factories/projects.rb index de8b7213ef..7e0e4c9138 100644 --- a/test/factories/projects.rb +++ b/test/factories/projects.rb @@ -12,6 +12,7 @@ title { "A Maximal Project" } description { "A Taverna project" } discussion_links { [FactoryBot.build(:discussion_link, label:'Slack')] } + extended_metadata { FactoryBot.build(:family_extended_metadata)} web_page { "http://www.taverna.org.uk" } wiki_page { "http://www.mygrid.org.uk" } default_license { "Other (Open)" } diff --git a/test/fixtures/json/requests/patch_max_project.json.erb b/test/fixtures/json/requests/patch_max_project.json.erb index 1623cd0e7a..e7ca746a8b 100644 --- a/test/fixtures/json/requests/patch_max_project.json.erb +++ b/test/fixtures/json/requests/patch_max_project.json.erb @@ -35,6 +35,29 @@ } ] }, + "extended_attributes": { + "extended_metadata_type_id": "<%= @emt.id %>", + "attribute_map": { + "dad": { + "first_name": "john", + "last_name": "liddell" + }, + "mom": { + "first_name": "lily", + "last_name": "liddell" + }, + "child": [ + { + "first_name": "rabbit", + "last_name": "wonderland" + }, + { + "first_name": "mad", + "last_name": "hatter" + } + ] + } + }, "topic_annotations": [ { "label": "Biomedical science", diff --git a/test/fixtures/json/requests/post_max_project.json.erb b/test/fixtures/json/requests/post_max_project.json.erb index 127b47919b..7f158bccad 100644 --- a/test/fixtures/json/requests/post_max_project.json.erb +++ b/test/fixtures/json/requests/post_max_project.json.erb @@ -34,6 +34,29 @@ } ] }, + "extended_attributes": { + "extended_metadata_type_id": "<%= @emt.id %>", + "attribute_map": { + "dad": { + "first_name": "john", + "last_name": "liddell" + }, + "mom": { + "first_name": "lily", + "last_name": "liddell" + }, + "child": [ + { + "first_name": "rabbit", + "last_name": "wonderland" + }, + { + "first_name": "mad", + "last_name": "hatter" + } + ] + } + }, "topic_annotations": [ { "label": "Biomedical science", diff --git a/test/integration/api/project_api_test.rb b/test/integration/api/project_api_test.rb index 7947a07f1f..4e43820ef6 100644 --- a/test/integration/api/project_api_test.rb +++ b/test/integration/api/project_api_test.rb @@ -14,6 +14,8 @@ def setup @institution = FactoryBot.create(:institution) @programme = FactoryBot.create(:programme) @organism = FactoryBot.create(:organism) + @emt = FactoryBot.create(:family_extended_metadata_type) + end test 'normal user cannot create project' do From 7361723491a2c1f7367f324d2a17f91b9bdef16f Mon Sep 17 00:00:00 2001 From: whomingbird Date: Fri, 15 Mar 2024 17:14:45 +0100 Subject: [PATCH 247/344] test WriteAPI for Extended Metadata with Document model --- public/api/definitions/_schemas.yml | 6 ++ public/api/examples/documentPatch.json | 27 ++++++-- .../api/examples/documentPatchResponse.json | 65 ++++++++++++------- public/api/examples/documentPost.json | 25 +++++-- public/api/examples/documentPostResponse.json | 59 ++++++++++------- public/api/examples/documentResponse.json | 57 ++++++++++------ public/api/examples/documentsResponse.json | 12 ++-- test/factories/documents.rb | 1 + test/factories/extended_metadata.rb | 10 +++ .../json/requests/patch_max_document.json.erb | 15 +++++ .../json/requests/post_max_document.json.erb | 15 +++++ test/integration/api/document_api_test.rb | 1 + 12 files changed, 208 insertions(+), 85 deletions(-) diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index 6162429cde..e11e2158b4 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -438,6 +438,8 @@ contributedTypeAttributes: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - license @@ -1555,6 +1557,8 @@ documentPost: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - content_blobs @@ -1612,6 +1616,8 @@ documentPatch: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object diff --git a/public/api/examples/documentPatch.json b/public/api/examples/documentPatch.json index b4941efedf..411c682591 100644 --- a/public/api/examples/documentPatch.json +++ b/public/api/examples/documentPatch.json @@ -1,7 +1,7 @@ { "data": { "type": "documents", - "id": "26", + "id": "642", "attributes": { "title": "A Maximally Patched Document", "description": "A report about the thing that happened", @@ -17,19 +17,34 @@ "permissions": [ { "resource": { - "id": "251", + "id": "16398", "type": "projects" }, "access": "edit" } ] + }, + "extended_attributes": { + "extended_metadata_type_id": "3021", + "attribute_map": { + "role_email": "alice@email.com", + "role_phone": "0012345", + "role_name": { + "first_name": "alice", + "last_name": "liddell" + }, + "role_address": { + "street": "wonder", + "city": "land" + } + } } }, "relationships": { "creators": { "data": [ { - "id": "236", + "id": "13852", "type": "people" } ] @@ -37,7 +52,7 @@ "projects": { "data": [ { - "id": "251", + "id": "16398", "type": "projects" } ] @@ -45,7 +60,7 @@ "assays": { "data": [ { - "id": "34", + "id": "1905", "type": "assays" } ] @@ -53,7 +68,7 @@ "workflows": { "data": [ { - "id": "14", + "id": "339", "type": "workflows" } ] diff --git a/public/api/examples/documentPatchResponse.json b/public/api/examples/documentPatchResponse.json index cc07ae1334..e624d6f62b 100644 --- a/public/api/examples/documentPatchResponse.json +++ b/public/api/examples/documentPatchResponse.json @@ -1,6 +1,6 @@ { "data": { - "id": "26", + "id": "642", "type": "documents", "attributes": { "policy": { @@ -8,7 +8,7 @@ "permissions": [ { "resource": { - "id": "251", + "id": "16398", "type": "projects" }, "access": "edit" @@ -18,6 +18,21 @@ "discussion_links": [ ], + "extended_attributes": { + "extended_metadata_type_id": "3021", + "attribute_map": { + "role_email": "alice@email.com", + "role_phone": "0012345", + "role_name": { + "first_name": "alice", + "last_name": "liddell" + }, + "role_address": { + "street": "wonder", + "city": "land" + } + } + }, "title": "A Maximally Patched Document", "license": "CC-BY-4.0", "description": "A report about the thing that happened", @@ -31,32 +46,32 @@ { "version": 1, "revision_comments": null, - "url": "http://localhost:3000/documents/26?version=1", + "url": "http://localhost:3000/documents/642?version=1", "doi": null } ], "version": 1, "revision_comments": null, - "created_at": "2022-06-07T14:12:58.000Z", - "updated_at": "2022-06-07T14:12:58.000Z", + "created_at": "2024-03-15T16:09:58.000Z", + "updated_at": "2024-03-15T16:09:58.000Z", "doi": null, "content_blobs": [ { - "original_filename": "file-26", + "original_filename": "file-22", "url": null, - "md5sum": "f1986c5013e53627e5d5830b84adcab5", - "sha1sum": "a73f327ed87f56b2aed6f886651d32a0c96abf75", + "md5sum": "28219bc7b5e57f29a560e813a8a19f2d", + "sha1sum": "d210301cad1b23eeecc304180c62047468a78445", "content_type": "application/pdf", - "link": "http://localhost:3000/documents/26/content_blobs/106", + "link": "http://localhost:3000/documents/642/content_blobs/2825", "size": 9 } ], "creators": [ { - "profile": "/people/236", + "profile": "/people/13852", "family_name": "Last", - "given_name": "Person192", - "affiliation": "An Institution: 238", + "given_name": "Person82", + "affiliation": "An Institution: 108", "orcid": null } ], @@ -66,7 +81,7 @@ "creators": { "data": [ { - "id": "236", + "id": "13852", "type": "people" } ] @@ -74,7 +89,7 @@ "submitter": { "data": [ { - "id": "235", + "id": "13851", "type": "people" } ] @@ -82,11 +97,11 @@ "people": { "data": [ { - "id": "235", + "id": "13851", "type": "people" }, { - "id": "236", + "id": "13852", "type": "people" } ] @@ -94,7 +109,7 @@ "projects": { "data": [ { - "id": "251", + "id": "16398", "type": "projects" } ] @@ -102,7 +117,7 @@ "investigations": { "data": [ { - "id": "38", + "id": "4748", "type": "investigations" } ] @@ -110,7 +125,7 @@ "studies": { "data": [ { - "id": "36", + "id": "3623", "type": "studies" } ] @@ -118,7 +133,7 @@ "assays": { "data": [ { - "id": "34", + "id": "1905", "type": "assays" } ] @@ -131,21 +146,21 @@ "workflows": { "data": [ { - "id": "14", + "id": "339", "type": "workflows" } ] } }, "links": { - "self": "/documents/26?version=1" + "self": "/documents/642?version=1" }, "meta": { - "created": "2022-06-07T14:12:58.000Z", - "modified": "2022-06-07T14:12:58.000Z", + "created": "2024-03-15T16:09:58.000Z", + "modified": "2024-03-15T16:09:58.000Z", "api_version": "0.3", "base_url": "http://localhost:3000", - "uuid": "db5cd6a0-c899-013a-0f7e-0a81884ed284" + "uuid": "650f9950-c514-013c-6432-7efdca78d793" } }, "jsonapi": { diff --git a/public/api/examples/documentPost.json b/public/api/examples/documentPost.json index dec2284ea4..f7ffc6b21d 100644 --- a/public/api/examples/documentPost.json +++ b/public/api/examples/documentPost.json @@ -21,19 +21,34 @@ "permissions": [ { "resource": { - "id": "221", + "id": "16368", "type": "projects" }, "access": "edit" } ] + }, + "extended_attributes": { + "extended_metadata_type_id": "3006", + "attribute_map": { + "role_email": "alice@email.com", + "role_phone": "0012345", + "role_name": { + "first_name": "alice", + "last_name": "liddell" + }, + "role_address": { + "street": "wonder", + "city": "land" + } + } } }, "relationships": { "creators": { "data": [ { - "id": "208", + "id": "13824", "type": "people" } ] @@ -41,7 +56,7 @@ "projects": { "data": [ { - "id": "221", + "id": "16368", "type": "projects" } ] @@ -49,7 +64,7 @@ "assays": { "data": [ { - "id": "29", + "id": "1900", "type": "assays" } ] @@ -57,7 +72,7 @@ "workflows": { "data": [ { - "id": "9", + "id": "334", "type": "workflows" } ] diff --git a/public/api/examples/documentPostResponse.json b/public/api/examples/documentPostResponse.json index 9702d2a1c5..6206867a8f 100644 --- a/public/api/examples/documentPostResponse.json +++ b/public/api/examples/documentPostResponse.json @@ -1,6 +1,6 @@ { "data": { - "id": "20", + "id": "636", "type": "documents", "attributes": { "policy": { @@ -8,7 +8,7 @@ "permissions": [ { "resource": { - "id": "221", + "id": "16368", "type": "projects" }, "access": "edit" @@ -18,6 +18,21 @@ "discussion_links": [ ], + "extended_attributes": { + "extended_metadata_type_id": "3006", + "attribute_map": { + "role_email": "alice@email.com", + "role_phone": "0012345", + "role_name": { + "first_name": "alice", + "last_name": "liddell" + }, + "role_address": { + "street": "wonder", + "city": "land" + } + } + }, "title": "A Maximal Document", "license": "CC-BY-4.0", "description": "This is the description", @@ -30,14 +45,14 @@ { "version": 1, "revision_comments": null, - "url": "http://localhost:3000/documents/20?version=1", + "url": "http://localhost:3000/documents/636?version=1", "doi": null } ], "version": 1, "revision_comments": null, - "created_at": "2022-06-07T14:12:54.000Z", - "updated_at": "2022-06-07T14:12:54.000Z", + "created_at": "2024-03-15T16:09:56.000Z", + "updated_at": "2024-03-15T16:09:56.000Z", "doi": null, "content_blobs": [ { @@ -46,16 +61,16 @@ "md5sum": null, "sha1sum": null, "content_type": "application/pdf", - "link": "http://localhost:3000/documents/20/content_blobs/93", + "link": "http://localhost:3000/documents/636/content_blobs/2814", "size": null } ], "creators": [ { - "profile": "/people/208", + "profile": "/people/13824", "family_name": "Last", - "given_name": "Person169", - "affiliation": "An Institution: 210", + "given_name": "Person59", + "affiliation": "An Institution: 80", "orcid": null } ], @@ -65,7 +80,7 @@ "creators": { "data": [ { - "id": "208", + "id": "13824", "type": "people" } ] @@ -73,7 +88,7 @@ "submitter": { "data": [ { - "id": "207", + "id": "13823", "type": "people" } ] @@ -81,11 +96,11 @@ "people": { "data": [ { - "id": "207", + "id": "13823", "type": "people" }, { - "id": "208", + "id": "13824", "type": "people" } ] @@ -93,7 +108,7 @@ "projects": { "data": [ { - "id": "221", + "id": "16368", "type": "projects" } ] @@ -101,7 +116,7 @@ "investigations": { "data": [ { - "id": "33", + "id": "4743", "type": "investigations" } ] @@ -109,7 +124,7 @@ "studies": { "data": [ { - "id": "31", + "id": "3618", "type": "studies" } ] @@ -117,7 +132,7 @@ "assays": { "data": [ { - "id": "29", + "id": "1900", "type": "assays" } ] @@ -130,21 +145,21 @@ "workflows": { "data": [ { - "id": "9", + "id": "334", "type": "workflows" } ] } }, "links": { - "self": "/documents/20?version=1" + "self": "/documents/636?version=1" }, "meta": { - "created": "2022-06-07T14:12:54.000Z", - "modified": "2022-06-07T14:12:54.000Z", + "created": "2024-03-15T16:09:56.000Z", + "modified": "2024-03-15T16:09:56.000Z", "api_version": "0.3", "base_url": "http://localhost:3000", - "uuid": "d8c94ff0-c899-013a-0f7e-0a81884ed284" + "uuid": "63f3bb30-c514-013c-6432-7efdca78d793" } }, "jsonapi": { diff --git a/public/api/examples/documentResponse.json b/public/api/examples/documentResponse.json index c201d3803d..52748d52c0 100644 --- a/public/api/examples/documentResponse.json +++ b/public/api/examples/documentResponse.json @@ -1,6 +1,6 @@ { "data": { - "id": "25", + "id": "641", "type": "documents", "attributes": { "policy": { @@ -11,11 +11,26 @@ }, "discussion_links": [ { - "id": "16", + "id": "723", "label": "Slack", "url": "http://www.slack.com/" } ], + "extended_attributes": { + "extended_metadata_type_id": "3018", + "attribute_map": { + "role_email": "alice@email.com", + "role_phone": "0012345", + "role_name": { + "first_name": "alice", + "last_name": "liddell" + }, + "role_address": { + "street": "wonder", + "city": "land" + } + } + }, "title": "A Maximal Document", "license": null, "description": "The important report we did for ~important-milestone~", @@ -31,14 +46,14 @@ { "version": 1, "revision_comments": null, - "url": "http://localhost:3000/documents/25?version=1", + "url": "http://localhost:3000/documents/641?version=1", "doi": null } ], "version": 1, "revision_comments": null, - "created_at": "2022-06-07T14:12:57.000Z", - "updated_at": "2022-06-07T14:12:57.000Z", + "created_at": "2024-03-15T16:09:58.000Z", + "updated_at": "2024-03-15T16:09:58.000Z", "doi": null, "content_blobs": [ { @@ -47,13 +62,13 @@ "md5sum": "726de0a3f94d65056b909b9736e10349", "sha1sum": "fbcc0dee4b5415de4c82ef5b137d8231fec1331b", "content_type": "application/pdf", - "link": "http://localhost:3000/documents/25/content_blobs/104", + "link": "http://localhost:3000/documents/641/content_blobs/2823", "size": 8 } ], "creators": [ { - "profile": "/people/232", + "profile": "/people/13848", "family_name": "One", "given_name": "Some", "affiliation": "University of Somewhere", @@ -66,7 +81,7 @@ "creators": { "data": [ { - "id": "232", + "id": "13848", "type": "people" } ] @@ -74,7 +89,7 @@ "submitter": { "data": [ { - "id": "233", + "id": "13849", "type": "people" } ] @@ -82,11 +97,11 @@ "people": { "data": [ { - "id": "232", + "id": "13848", "type": "people" }, { - "id": "233", + "id": "13849", "type": "people" } ] @@ -94,7 +109,7 @@ "projects": { "data": [ { - "id": "249", + "id": "16396", "type": "projects" } ] @@ -102,7 +117,7 @@ "investigations": { "data": [ { - "id": "37", + "id": "4747", "type": "investigations" } ] @@ -110,7 +125,7 @@ "studies": { "data": [ { - "id": "35", + "id": "3622", "type": "studies" } ] @@ -118,7 +133,7 @@ "assays": { "data": [ { - "id": "33", + "id": "1904", "type": "assays" } ] @@ -126,7 +141,7 @@ "publications": { "data": [ { - "id": "12", + "id": "1698", "type": "publications" } ] @@ -134,21 +149,21 @@ "workflows": { "data": [ { - "id": "13", + "id": "338", "type": "workflows" } ] } }, "links": { - "self": "/documents/25?version=1" + "self": "/documents/641?version=1" }, "meta": { - "created": "2022-06-07T14:12:57.000Z", - "modified": "2022-06-07T14:12:57.000Z", + "created": "2024-03-15T16:09:58.000Z", + "modified": "2024-03-15T16:09:58.000Z", "api_version": "0.3", "base_url": "http://localhost:3000", - "uuid": "dae798f0-c899-013a-0f7e-0a81884ed284" + "uuid": "64ddb5c0-c514-013c-6432-7efdca78d793" } }, "jsonapi": { diff --git a/public/api/examples/documentsResponse.json b/public/api/examples/documentsResponse.json index d52761722f..b9f70b27e1 100644 --- a/public/api/examples/documentsResponse.json +++ b/public/api/examples/documentsResponse.json @@ -1,23 +1,23 @@ { "data": [ { - "id": "23", + "id": "638", "type": "documents", "attributes": { - "title": "A Maximal Document" + "title": "A Minimal Document" }, "links": { - "self": "/documents/23" + "self": "/documents/638" } }, { - "id": "22", + "id": "639", "type": "documents", "attributes": { - "title": "A Minimal Document" + "title": "A Maximal Document" }, "links": { - "self": "/documents/22" + "self": "/documents/639" } } ], diff --git a/test/factories/documents.rb b/test/factories/documents.rb index 1eb05f55c7..4a4d3d8564 100644 --- a/test/factories/documents.rb +++ b/test/factories/documents.rb @@ -39,6 +39,7 @@ title { 'A Maximal Document' } description { 'The important report we did for ~important-milestone~' } discussion_links { [FactoryBot.build(:discussion_link, label:'Slack')] } + extended_metadata { FactoryBot.build(:role_multiple_extended_metadata)} policy { FactoryBot.create(:downloadable_public_policy) } assays { [FactoryBot.create(:public_assay)] } workflows {[FactoryBot.build(:workflow, policy: FactoryBot.create(:public_policy))]} diff --git a/test/factories/extended_metadata.rb b/test/factories/extended_metadata.rb index 167add5130..39e2557964 100644 --- a/test/factories/extended_metadata.rb +++ b/test/factories/extended_metadata.rb @@ -18,4 +18,14 @@ end end + factory(:role_multiple_extended_metadata, class: ExtendedMetadata) do + association :extended_metadata_type, factory: :role_multiple_extended_metadata_type, strategy: :create + after(:build) do |em| + em.set_attribute_value(:role_email, "alice@email.com") + em.set_attribute_value(:role_phone, "0012345") + em.set_attribute_value(:role_name, {"first_name":"alice", "last_name": "liddell"}) + em.set_attribute_value(:role_address, {"street":"wonder","city": "land" }) + end + end + end \ No newline at end of file diff --git a/test/fixtures/json/requests/patch_max_document.json.erb b/test/fixtures/json/requests/patch_max_document.json.erb index c0650bef37..5d86b43a1f 100644 --- a/test/fixtures/json/requests/patch_max_document.json.erb +++ b/test/fixtures/json/requests/patch_max_document.json.erb @@ -23,6 +23,21 @@ "access": "edit" } ] + }, + "extended_attributes": { + "extended_metadata_type_id": "<%= @emt.id %>", + "attribute_map": { + "role_email": "alice@email.com", + "role_phone": "0012345", + "role_name": { + "first_name": "alice", + "last_name": "liddell" + }, + "role_address": { + "street": "wonder", + "city": "land" + } + } } }, "relationships": { diff --git a/test/fixtures/json/requests/post_max_document.json.erb b/test/fixtures/json/requests/post_max_document.json.erb index b3cb10bd4b..d43538165e 100644 --- a/test/fixtures/json/requests/post_max_document.json.erb +++ b/test/fixtures/json/requests/post_max_document.json.erb @@ -27,6 +27,21 @@ "access": "edit" } ] + }, + "extended_attributes": { + "extended_metadata_type_id": "<%= @emt.id %>", + "attribute_map": { + "role_email": "alice@email.com", + "role_phone": "0012345", + "role_name": { + "first_name": "alice", + "last_name": "liddell" + }, + "role_address": { + "street": "wonder", + "city": "land" + } + } } }, "relationships": { diff --git a/test/integration/api/document_api_test.rb b/test/integration/api/document_api_test.rb index df6a73eb8e..4c47ee0562 100644 --- a/test/integration/api/document_api_test.rb +++ b/test/integration/api/document_api_test.rb @@ -13,6 +13,7 @@ def setup @workflow = FactoryBot.create(:workflow, projects: [@project], contributor: current_person) @creator = FactoryBot.create(:person) @document = FactoryBot.create(:document, policy: FactoryBot.create(:public_policy), contributor: current_person, creators: [@creator]) + @emt = FactoryBot.create(:role_multiple_extended_metadata_type) end test 'can add content to API-created document' do From 33445ea390b276ccfa2528f3154a8c5d9fa44994 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Fri, 15 Mar 2024 17:39:26 +0100 Subject: [PATCH 248/344] update schemas.yml for the rest resources which support Extended Metadata --- public/api/definitions/_schemas.yml | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index e11e2158b4..e44814b2d7 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -518,6 +518,8 @@ modelAttributes: $ref: "#/components/schemas/nullableNonEmptyString" policy: $ref: "#/components/schemas/policy" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" other_creators: $ref: "#/components/schemas/nullableOtherCreatorsString" creators: @@ -1266,6 +1268,8 @@ collectionPost: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title additionalProperties: false @@ -1318,6 +1322,8 @@ collectionPatch: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -1437,6 +1443,8 @@ dataFilePost: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - content_blobs @@ -1502,6 +1510,8 @@ dataFilePatch: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -1669,6 +1679,8 @@ eventPost: $ref: "#/components/schemas/nullableDateTimeString" policy: $ref: "#/components/schemas/policy" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - start_date @@ -1728,6 +1740,8 @@ eventPatch: $ref: "#/components/schemas/dateTimeString" policy: $ref: "#/components/schemas/policy" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -1950,6 +1964,8 @@ modelPost: $ref: "#/components/schemas/nullableNonEmptyString" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - content_blobs @@ -2015,6 +2031,8 @@ modelPatch: $ref: "#/components/schemas/nullableNonEmptyString" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -2156,6 +2174,8 @@ presentationPost: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - content_blobs @@ -2215,6 +2235,8 @@ presentationPatch: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -2726,6 +2748,8 @@ sopPost: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - content_blobs @@ -2783,6 +2807,8 @@ sopPatch: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -3295,6 +3321,8 @@ collectionResponse: $ref: "#/components/schemas/multipleReferences" items: $ref: "#/components/schemas/multipleReferences" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - creators - submitter @@ -3470,6 +3498,8 @@ dataFileResponse: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - license @@ -3632,6 +3662,8 @@ eventResponse: $ref: "#/components/schemas/nullableDateTimeString" policy: $ref: "#/components/schemas/policy" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - description From c2687cf9c6941b4ecece25f62efd419fcc752a7d Mon Sep 17 00:00:00 2001 From: whomingbird Date: Mon, 18 Mar 2024 15:32:12 +0100 Subject: [PATCH 249/344] add extendedMetadataAttributeMap for extended metadata in schema --- public/api/definitions/_schemas.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index e44814b2d7..3f169cea92 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -173,7 +173,7 @@ extendedMetadata: extended_metadata_type_id: type: string attribute_map: - $ref: "#/components/schemas/sampleAttributeMap" + $ref: "#/components/schemas/extendedMetadataAttributeMap" required: - extended_metadata_type_id - attribute_map @@ -827,6 +827,13 @@ projectMembersList: - institution_id additionalProperties: false sampleAttributeMap: + type: object + additionalProperties: + anyOf: + - type: string + - type: array + - type: 'null' +extendedMetadataAttributeMap: type: object additionalProperties: true # --- Types --- From f1f3d900ee541dcaef0cd24a525ecbf25e214c53 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 14 Mar 2024 13:49:35 +0000 Subject: [PATCH 250/344] job to remove deleted content blobs every 24 hours #1788 --- app/jobs/regular_maintenance_job.rb | 11 ++++++++ test/unit/jobs/regular_maintenace_job_test.rb | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/app/jobs/regular_maintenance_job.rb b/app/jobs/regular_maintenance_job.rb index b245597f19..939409d9a7 100644 --- a/app/jobs/regular_maintenance_job.rb +++ b/app/jobs/regular_maintenance_job.rb @@ -8,6 +8,7 @@ class RegularMaintenanceJob < ApplicationJob RUN_PERIOD = 4.hours.freeze BLOB_GRACE_PERIOD = 8.hours.freeze + DELETE_BLOB_GRACE_PERIOD = 24.hours.freeze REPO_GRACE_PERIOD = 8.hours.freeze USER_GRACE_PERIOD = 1.week.freeze MAX_ACTIVATION_EMAILS = 3 @@ -15,6 +16,7 @@ class RegularMaintenanceJob < ApplicationJob def perform clean_content_blobs + remove_deleted_content_blobs clean_git_repositories resend_activation_emails remove_unregistered_users @@ -33,6 +35,15 @@ def clean_content_blobs end end + def remove_deleted_content_blobs + ContentBlob.where(deleted: true).where('created_at < ?', DELETE_BLOB_GRACE_PERIOD.ago).select do |blob| + Rails.logger.info("Removing content blob #{blob.id} marked for deletion") + + # play safe and only delete if asset has gone even if flagged for deletion + blob.destroy if blob.asset.nil? + end + end + # Remove GitRepositories that were never used def clean_git_repositories Git::Repository.redundant.where('git_repositories.created_at < ?', REPO_GRACE_PERIOD.ago).select do |repo| diff --git a/test/unit/jobs/regular_maintenace_job_test.rb b/test/unit/jobs/regular_maintenace_job_test.rb index 46ddbd6c0f..6d126b28b4 100644 --- a/test/unit/jobs/regular_maintenace_job_test.rb +++ b/test/unit/jobs/regular_maintenace_job_test.rb @@ -34,6 +34,34 @@ def setup assert ContentBlob.exists?(keep4.id) end + test 'remove deleted content blobs' do + assert_equal 24.hours, RegularMaintenanceJob::DELETE_BLOB_GRACE_PERIOD + to_go, to_keep1, to_keep2, to_keep3 = nil + + travel_to(25.hours.ago) do + to_go = FactoryBot.create(:content_blob, deleted: true, asset_type:'Sop', asset_id: 99999) + to_keep1 = FactoryBot.create(:content_blob, asset_type:'Sop', asset_id: 99999) + to_keep2 = FactoryBot.create(:content_blob, deleted:true, asset: FactoryBot.create(:sop)) + end + travel_to(23.hours.ago) do + to_keep3 = FactoryBot.create(:content_blob, deleted: true, asset: FactoryBot.create(:sop)) + end + + assert to_go.deleted? + assert to_keep2.deleted? + assert to_keep3.deleted? + refute to_keep1.deleted? + + assert_difference('ContentBlob.count',-1) do + RegularMaintenanceJob.perform_now + end + + refute ContentBlob.exists?(to_go.id) + assert ContentBlob.exists?(to_keep1.id) + assert ContentBlob.exists?(to_keep2.id) + assert ContentBlob.exists?(to_keep3.id) + end + test 'remove old unregistered users' do assert_equal 1.week, RegularMaintenanceJob::USER_GRACE_PERIOD to_go, keep1, keep2 = nil From 8c37c5e1e64d09dc03a6a4365886aaa0326ab483 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 14 Mar 2024 13:56:00 +0000 Subject: [PATCH 251/344] clear names for variables and methods #1788 --- app/jobs/regular_maintenance_job.rb | 14 +++++++------- test/unit/jobs/regular_maintenace_job_test.rb | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/jobs/regular_maintenance_job.rb b/app/jobs/regular_maintenance_job.rb index 939409d9a7..f5ef49efa1 100644 --- a/app/jobs/regular_maintenance_job.rb +++ b/app/jobs/regular_maintenance_job.rb @@ -7,15 +7,15 @@ class RegularMaintenanceJob < ApplicationJob RUN_PERIOD = 4.hours.freeze - BLOB_GRACE_PERIOD = 8.hours.freeze - DELETE_BLOB_GRACE_PERIOD = 24.hours.freeze + REMOVE_DANGLING_BLOB_GRACE_PERIOD = 8.hours.freeze + REMOVE_DELETED_BLOB_GRACE_PERIOD = 24.hours.freeze REPO_GRACE_PERIOD = 8.hours.freeze USER_GRACE_PERIOD = 1.week.freeze MAX_ACTIVATION_EMAILS = 3 RESEND_ACTIVATION_EMAIL_DELAY = 4.hours.freeze def perform - clean_content_blobs + remove_dangling_content_blobs remove_deleted_content_blobs clean_git_repositories resend_activation_emails @@ -26,9 +26,9 @@ def perform private - # clean up dangling content blobs that are older than BLOB_GRACE_PERIOD and not associated with an asset - def clean_content_blobs - ContentBlob.where(asset: nil).where('created_at < ?', BLOB_GRACE_PERIOD.ago).select do |blob| + # clean up dangling content blobs that are older than REMOVE_DANGLING_BLOB_GRACE_PERIOD and not associated with an asset_id + def remove_dangling_content_blobs + ContentBlob.where(asset_id: nil).where('created_at < ?', REMOVE_DANGLING_BLOB_GRACE_PERIOD.ago).select do |blob| Rails.logger.info("Cleaning up content blob #{blob.id}") blob.reload blob.destroy if blob.asset.nil? @@ -36,7 +36,7 @@ def clean_content_blobs end def remove_deleted_content_blobs - ContentBlob.where(deleted: true).where('created_at < ?', DELETE_BLOB_GRACE_PERIOD.ago).select do |blob| + ContentBlob.where(deleted: true).where('created_at < ?', REMOVE_DELETED_BLOB_GRACE_PERIOD.ago).select do |blob| Rails.logger.info("Removing content blob #{blob.id} marked for deletion") # play safe and only delete if asset has gone even if flagged for deletion diff --git a/test/unit/jobs/regular_maintenace_job_test.rb b/test/unit/jobs/regular_maintenace_job_test.rb index 6d126b28b4..0cff108369 100644 --- a/test/unit/jobs/regular_maintenace_job_test.rb +++ b/test/unit/jobs/regular_maintenace_job_test.rb @@ -9,8 +9,8 @@ def setup assert_equal 4.hours, RegularMaintenanceJob::RUN_PERIOD end - test 'cleans content blobs' do - assert_equal 8.hours, RegularMaintenanceJob::BLOB_GRACE_PERIOD + test 'removes dangling content blobs' do + assert_equal 8.hours, RegularMaintenanceJob::REMOVE_DANGLING_BLOB_GRACE_PERIOD to_go, keep1, keep2, keep3, keep4 = nil travel_to(9.hours.ago) do to_go = FactoryBot.create(:content_blob) @@ -35,7 +35,7 @@ def setup end test 'remove deleted content blobs' do - assert_equal 24.hours, RegularMaintenanceJob::DELETE_BLOB_GRACE_PERIOD + assert_equal 24.hours, RegularMaintenanceJob::REMOVE_DELETED_BLOB_GRACE_PERIOD to_go, to_keep1, to_keep2, to_keep3 = nil travel_to(25.hours.ago) do From 04b83ad56a888eb23d4eeafaec010f549915b270 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 19 Mar 2024 10:26:42 +0000 Subject: [PATCH 252/344] convert float sample attributes from string when serializing #1791 --- .../float_attribute_handler.rb | 6 ++++++ test/functional/samples_controller_test.rb | 12 ++++++------ .../samples/float_attribute_handler_test.rb | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 test/unit/samples/float_attribute_handler_test.rb diff --git a/lib/seek/samples/attribute_handlers/float_attribute_handler.rb b/lib/seek/samples/attribute_handlers/float_attribute_handler.rb index bc0eb97495..5911c56074 100644 --- a/lib/seek/samples/attribute_handlers/float_attribute_handler.rb +++ b/lib/seek/samples/attribute_handlers/float_attribute_handler.rb @@ -5,6 +5,12 @@ class FloatAttributeHandler < BaseAttributeHandler def test_value(value) Float(value) end + + def convert(value) + float_value = Float(value, exception: false) if value.is_a?(String) + float_value.nil? ? value : float_value + end + end end end diff --git a/test/functional/samples_controller_test.rb b/test/functional/samples_controller_test.rb index a6dc501c3e..4ca85aae24 100644 --- a/test/functional/samples_controller_test.rb +++ b/test/functional/samples_controller_test.rb @@ -71,7 +71,7 @@ class SamplesControllerTest < ActionController::TestCase assert_equal 'Fred Smith', sample.title assert_equal 'Fred Smith', sample.get_attribute_value('full name') assert_equal 22, sample.get_attribute_value(:age) - assert_equal '22.1', sample.get_attribute_value(:weight) + assert_equal 22.1, sample.get_attribute_value(:weight) assert_equal 'M13 9PL', sample.get_attribute_value(:postcode) assert_equal person, sample.contributor assert_equal [creator], sample.creators @@ -95,7 +95,7 @@ class SamplesControllerTest < ActionController::TestCase assert_equal 'Fred Smith', sample.title assert_equal 'Fred Smith', sample.get_attribute_value('full name') assert_equal 22, sample.get_attribute_value(:age) - assert_equal '22.1', sample.get_attribute_value(:weight) + assert_equal 22.1, sample.get_attribute_value(:weight) assert_equal 'M13 9PL', sample.get_attribute_value(:postcode) assert_equal person, sample.contributor assert_equal [creator], sample.creators @@ -1051,7 +1051,7 @@ class SamplesControllerTest < ActionController::TestCase assert_equal 'Fred Smith', sample1.title assert_equal 'Fred Smith', sample1.get_attribute_value('full name') assert_equal 22, sample1.get_attribute_value(:age) - assert_equal '22.1', sample1.get_attribute_value(:weight) + assert_equal 22.1, sample1.get_attribute_value(:weight) assert_equal 'M13 9PL', sample1.get_attribute_value(:postcode) assert_equal [assay], sample1.assays @@ -1059,7 +1059,7 @@ class SamplesControllerTest < ActionController::TestCase assert_equal 'David Tailor', sample2.title assert_equal 'David Tailor', sample2.get_attribute_value('full name') assert_equal 33, sample2.get_attribute_value(:age) - assert_equal '33.1', sample2.get_attribute_value(:weight) + assert_equal 33.1, sample2.get_attribute_value(:weight) assert_equal 'M12 8PL', sample2.get_attribute_value(:postcode) end @@ -1112,7 +1112,7 @@ class SamplesControllerTest < ActionController::TestCase assert_equal 'Alfred Marcus', first_updated_sample.get_attribute_value('full name') assert_equal 22, first_updated_sample.get_attribute_value(:age) assert_nil first_updated_sample.get_attribute_value(:postcode) - assert_equal '22.1', first_updated_sample.get_attribute_value(:weight) + assert_equal 22.1, first_updated_sample.get_attribute_value(:weight) last_updated_sample = samples[1] assert_equal type_id2, last_updated_sample.sample_type.id @@ -1120,7 +1120,7 @@ class SamplesControllerTest < ActionController::TestCase assert_equal 'David Tailor', last_updated_sample.get_attribute_value('full name') assert_equal 33, last_updated_sample.get_attribute_value(:age) assert_nil last_updated_sample.get_attribute_value(:postcode) - assert_equal '33.1', last_updated_sample.get_attribute_value(:weight) + assert_equal 33.1, last_updated_sample.get_attribute_value(:weight) end test 'batch_delete' do diff --git a/test/unit/samples/float_attribute_handler_test.rb b/test/unit/samples/float_attribute_handler_test.rb new file mode 100644 index 0000000000..92c4b19440 --- /dev/null +++ b/test/unit/samples/float_attribute_handler_test.rb @@ -0,0 +1,17 @@ +require 'test_helper' + +class FloatAttributeHandlerTest < ActiveSupport::TestCase + + test 'convert' do + handler = Seek::Samples::AttributeHandlers::FloatAttributeHandler.new({}) + + assert_equal 2.7, handler.convert("2.7") + assert_equal 2.0, handler.convert("2") + assert_equal 2.0, handler.convert(2) + assert_equal 2.0, handler.convert(2.0) + assert_equal 3.5, handler.convert(3.5) + + assert_nil handler.convert(nil) + end + +end \ No newline at end of file From c1e4b4d746ec366764d30372498deee41d7fe879 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 19 Mar 2024 11:16:14 +0000 Subject: [PATCH 253/344] expanded max sample and sample type to include int, bool and float #1791 and updated tests and api json schema --- public/api/definitions/_schemas.yml | 2 ++ test/factories/sample_types.rb | 3 ++ test/factories/samples.rb | 3 ++ .../json/requests/patch_max_sample.json.erb | 5 ++- .../json/requests/post_max_sample.json.erb | 5 ++- .../json/responses/get_max_sample.json.erb | 5 ++- .../responses/get_max_sample_type.json.erb | 36 +++++++++++++++++++ 7 files changed, 56 insertions(+), 3 deletions(-) diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index 3f169cea92..74b9601ee9 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -833,6 +833,8 @@ sampleAttributeMap: - type: string - type: array - type: 'null' + - type: number + - type: boolean extendedMetadataAttributeMap: type: object additionalProperties: true diff --git a/test/factories/sample_types.rb b/test/factories/sample_types.rb index 3b7e93c489..ca397bb140 100644 --- a/test/factories/sample_types.rb +++ b/test/factories/sample_types.rb @@ -124,6 +124,9 @@ type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'apple', sample_attribute_type: FactoryBot.create(:controlled_vocab_attribute_type), required: false, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'apples', sample_attribute_type: FactoryBot.create(:cv_list_attribute_type), required: false, sample_controlled_vocab: FactoryBot.create(:apples_sample_controlled_vocab), sample_type: type) type.sample_attributes << FactoryBot.build(:sample_multi_sample_attribute, title: 'patients', linked_sample_type: FactoryBot.create(:patient_sample_type), required: false, sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'weight', sample_attribute_type: FactoryBot.create(:float_sample_attribute_type), required: false, sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'age', sample_attribute_type: FactoryBot.create(:integer_sample_attribute_type), required: false, sample_type: type) + type.sample_attributes << FactoryBot.build(:sample_attribute, title: 'bool', sample_attribute_type: FactoryBot.create(:boolean_sample_attribute_type), required: false, sample_type: type) end after(:create) do |type| type.annotate_with(['tag1', 'tag2'], 'sample_type_tag', type.contributor) diff --git a/test/factories/samples.rb b/test/factories/samples.rb index 56eed1d565..2224b88e6c 100644 --- a/test/factories/samples.rb +++ b/test/factories/samples.rb @@ -53,6 +53,9 @@ sample.set_attribute_value(:apple,['Bramley']) sample.set_attribute_value(:apples, ['Granny Smith','Golden Delicious']) sample.set_attribute_value(:patients, [FactoryBot.create(:patient_sample).id.to_s, FactoryBot.create(:patient_sample).id.to_s]) + sample.set_attribute_value(:weight, '3.7') + sample.set_attribute_value(:age, '42') + sample.set_attribute_value(:bool, true) end end diff --git a/test/fixtures/json/requests/patch_max_sample.json.erb b/test/fixtures/json/requests/patch_max_sample.json.erb index 46baf73572..a00df23b8a 100644 --- a/test/fixtures/json/requests/patch_max_sample.json.erb +++ b/test/fixtures/json/requests/patch_max_sample.json.erb @@ -9,7 +9,10 @@ "postcode": "M13 8PL", "CAPITAL key": "changed", "apple": "Granny Smith", - "apples": ["Golden Delicious", "Bramley"] + "apples": ["Golden Delicious", "Bramley"], + "weight": 3.7, + "age": 42, + "bool": true }, "tags": [] }, diff --git a/test/fixtures/json/requests/post_max_sample.json.erb b/test/fixtures/json/requests/post_max_sample.json.erb index cd30d3db82..7444ef42fd 100644 --- a/test/fixtures/json/requests/post_max_sample.json.erb +++ b/test/fixtures/json/requests/post_max_sample.json.erb @@ -8,7 +8,10 @@ "postcode": "M13 9PL", "CAPITAL key": "key must remain capitalised", "apple": "Bramley", - "apples": ["Golden Delicious", "Granny Smith"] + "apples": ["Golden Delicious", "Granny Smith"], + "weight": 3.7, + "age": 42, + "bool": true }, "tags": ["tag1", "tag2"] }, diff --git a/test/fixtures/json/responses/get_max_sample.json.erb b/test/fixtures/json/responses/get_max_sample.json.erb index 90f59d7e2c..fdbf8ba78d 100644 --- a/test/fixtures/json/responses/get_max_sample.json.erb +++ b/test/fixtures/json/responses/get_max_sample.json.erb @@ -28,7 +28,10 @@ "title": "Fred Bloggs", "id": <%= res.get_attribute_value(:patients)[1]['id'] %> } - ] + ], + "weight": 3.7, + "age": 42, + "bool": true }, "policy": { "access": "no_access", diff --git a/test/fixtures/json/responses/get_max_sample_type.json.erb b/test/fixtures/json/responses/get_max_sample_type.json.erb index 232e49bb53..2ff663632e 100644 --- a/test/fixtures/json/responses/get_max_sample_type.json.erb +++ b/test/fixtures/json/responses/get_max_sample_type.json.erb @@ -131,6 +131,42 @@ "is_title": false, "sample_controlled_vocab_id": null, "linked_sample_type_id": "<%= res.sample_attributes[6].linked_sample_type_id %>" + }, + { + "id": "<%= res.sample_attributes[7].id %>", + "title": "weight", + "description": null, + "pid": null, + "sample_attribute_type": { + "id": "<%= res.sample_attributes[7].sample_attribute_type_id %>", + "title": "<%= res.sample_attributes[7].sample_attribute_type.title %>", + "base_type": "Float", + "regexp": ".*" + } + }, + { + "id": "<%= res.sample_attributes[8].id %>", + "title": "age", + "description": null, + "pid": null, + "sample_attribute_type": { + "id": "<%= res.sample_attributes[8].sample_attribute_type_id %>", + "title": "<%= res.sample_attributes[8].sample_attribute_type.title %>", + "base_type": "Integer", + "regexp": ".*" + } + }, + { + "id": "<%= res.sample_attributes[9].id %>", + "title": "bool", + "description": null, + "pid": null, + "sample_attribute_type": { + "id": "<%= res.sample_attributes[9].sample_attribute_type_id %>", + "title": "<%= res.sample_attributes[9].sample_attribute_type.title %>", + "base_type": "Boolean", + "regexp": ".*" + } } ], "tags": [ From d00c448cedee7b9e64659b35bae06325e14a8deb Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 19 Mar 2024 11:40:36 +0000 Subject: [PATCH 254/344] removed unnecessary assertion in test #1791 --- test/unit/samples/float_attribute_handler_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/samples/float_attribute_handler_test.rb b/test/unit/samples/float_attribute_handler_test.rb index 92c4b19440..66293a0cd8 100644 --- a/test/unit/samples/float_attribute_handler_test.rb +++ b/test/unit/samples/float_attribute_handler_test.rb @@ -8,7 +8,6 @@ class FloatAttributeHandlerTest < ActiveSupport::TestCase assert_equal 2.7, handler.convert("2.7") assert_equal 2.0, handler.convert("2") assert_equal 2.0, handler.convert(2) - assert_equal 2.0, handler.convert(2.0) assert_equal 3.5, handler.convert(3.5) assert_nil handler.convert(nil) From b680f6dc743253eb0871e43cf92a56eb3bec1f97 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 09:54:25 +0000 Subject: [PATCH 255/344] beginnings of an integration test for sitemaps #1794 --- Gemfile | 4 ++-- test/integration/sitemap_test.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 test/integration/sitemap_test.rb diff --git a/Gemfile b/Gemfile index b6c2f20f43..50155fc923 100644 --- a/Gemfile +++ b/Gemfile @@ -161,6 +161,8 @@ gem 'net-ftp' gem 'licensee' +gem "sitemap_generator", "~> 6.3" + group :production do gem 'passenger' end @@ -205,5 +207,3 @@ group :test, :development do gem 'teaspoon' gem 'teaspoon-mocha' end - -gem "sitemap_generator", "~> 6.3" diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb new file mode 100644 index 0000000000..d78ad0c4c8 --- /dev/null +++ b/test/integration/sitemap_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' +require 'rubygems' +require 'sitemap_generator' +require 'rake' + +class SessionStoreTest < ActionDispatch::IntegrationTest + + def setup + @models = FactoryBot.create_list(:model,3, policy:FactoryBot.create(:public_policy)) + @sops = FactoryBot.create_list(:sop,3, policy:FactoryBot.create(:public_policy)) + @projects = Project.all + @people = Person.all + @institutions = Institution.all + + sitemap_path = "#{Rails.root}/public/sitemap.xml" + sitemaps_dir = "#{Rails.root}/public/sitemaps" + FileUtils.rm(sitemap_path) if File.exist?(sitemap_path) + FileUtils.rm_rf(sitemaps_dir) if File.exist?(sitemaps_dir) + + SitemapGenerator::Interpreter.run(verbose: false) + end + + test 'root sitemap' do + get '/sitemaps/sitemap.xml' + end + +end From 9547c715f274e91d6dba5cefa8cb60efdd91d58c Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 10:53:16 +0000 Subject: [PATCH 256/344] some refactoring and make /sitemap.xml available with a sym link #1794 --- config/sitemap.rb | 26 ++++++++++++++++++-------- public/sitemap.xml | 1 + test/integration/sitemap_test.rb | 5 ++--- 3 files changed, 21 insertions(+), 11 deletions(-) create mode 120000 public/sitemap.xml diff --git a/config/sitemap.rb b/config/sitemap.rb index 90cfab2813..c9465e8c97 100644 --- a/config/sitemap.rb +++ b/config/sitemap.rb @@ -1,19 +1,29 @@ # https://github.com/kjvarga/sitemap_generator#sitemapgenerator + SitemapGenerator::Sitemap.sitemaps_path = "sitemaps" -SitemapGenerator::Sitemap.create_index = "auto" +# SitemapGenerator::Sitemap.create_index = "auto" SitemapGenerator::Sitemap.compress = false +SitemapGenerator::Sitemap.include_root = false SitemapGenerator::Sitemap.default_host = URI.parse(Seek::Config.site_base_url) SitemapGenerator::Sitemap.create do - Seek::Util.searchable_types.each do |type| - add polymorphic_path(type), lastmod: type.maximum(:updated_at), changefreq: 'daily', priority: 0.7 + + types = Seek::Util.searchable_types + + group(filename: :site) do + add root_path, changefreq: 'daily', priority: 1.0 + types.each do |type| + add polymorphic_path(type), lastmod: type.maximum(:updated_at), changefreq: 'daily', priority: 0.7 + end end -end -Seek::Util.searchable_types.each do |type| - SitemapGenerator::Sitemap.create(filename: type.table_name, include_root: false) do - type.authorized_for('view', nil).find_all do |obj| - add polymorphic_path(obj), lastmod: obj.updated_at, changefreq: 'daily', priority: 0.7 + types.each do |type| + group(filename: type.table_name) do + type.authorized_for('view', nil).each do |resource| + add polymorphic_path(resource), lastmod: resource.updated_at + end end end + end + diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 120000 index 0000000000..e589fe629a --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1 @@ +sitemaps/sitemap.xml \ No newline at end of file diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index d78ad0c4c8..c94e9007c0 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -12,16 +12,15 @@ def setup @people = Person.all @institutions = Institution.all - sitemap_path = "#{Rails.root}/public/sitemap.xml" sitemaps_dir = "#{Rails.root}/public/sitemaps" - FileUtils.rm(sitemap_path) if File.exist?(sitemap_path) FileUtils.rm_rf(sitemaps_dir) if File.exist?(sitemaps_dir) SitemapGenerator::Interpreter.run(verbose: false) end test 'root sitemap' do - get '/sitemaps/sitemap.xml' + get '/sitemap.xml' + assert_response :success end end From 1f1859900b79bb0efe70368efbf3fc0e28f6fbf1 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 11:45:44 +0000 Subject: [PATCH 257/344] test some of the content #1794 --- test/integration/sitemap_test.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index c94e9007c0..e4213d87a2 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -1,9 +1,8 @@ require 'test_helper' -require 'rubygems' require 'sitemap_generator' -require 'rake' -class SessionStoreTest < ActionDispatch::IntegrationTest + +class SitemapTest < ActionDispatch::IntegrationTest def setup @models = FactoryBot.create_list(:model,3, policy:FactoryBot.create(:public_policy)) @@ -21,6 +20,15 @@ def setup test 'root sitemap' do get '/sitemap.xml' assert_response :success + doc = Nokogiri::XML.parse(response.body) + doc.remove_namespaces! + assert_equal 1, doc.xpath('//sitemapindex').count + assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/site.xml"]').count + assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/models.xml"]').count + assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/sops.xml"]').count + assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/projects.xml"]').count + assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/people.xml"]').count + assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/institutions.xml"]').count end end From fb2ba4b0b829a7a7dfb3f7f21fea6b3c6ec9ffb3 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 14:09:06 +0000 Subject: [PATCH 258/344] tests to skip disabled, and only include visible #1794 --- test/integration/sitemap_test.rb | 39 ++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index e4213d87a2..9131b514b2 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -5,7 +5,13 @@ class SitemapTest < ActionDispatch::IntegrationTest def setup + disable_authorization_checks do + DataFile.destroy_all + Model.destroy_all + Sop.destroy_all + end @models = FactoryBot.create_list(:model,3, policy:FactoryBot.create(:public_policy)) + @private_model = FactoryBot.create(:model, policy: FactoryBot.create(:private_policy)) @sops = FactoryBot.create_list(:sop,3, policy:FactoryBot.create(:public_policy)) @projects = Project.all @people = Person.all @@ -14,7 +20,10 @@ def setup sitemaps_dir = "#{Rails.root}/public/sitemaps" FileUtils.rm_rf(sitemaps_dir) if File.exist?(sitemaps_dir) - SitemapGenerator::Interpreter.run(verbose: false) + with_config_value(:sops_enabled, false) do + SitemapGenerator::Interpreter.run(verbose: false) + end + end test 'root sitemap' do @@ -25,10 +34,36 @@ def setup assert_equal 1, doc.xpath('//sitemapindex').count assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/site.xml"]').count assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/models.xml"]').count - assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/sops.xml"]').count assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/projects.xml"]').count assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/people.xml"]').count assert_equal 1, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/institutions.xml"]').count + + # types without content not shown + refute DataFile.any? + assert_equal 0, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/data_files.xml"]').count + + # Disabled not shown + assert Sop.any? + assert_equal 0, doc.xpath('//sitemapindex/sitemap/loc[text()="http://localhost:3000/sitemaps/sops.xml"]').count + end + + test 'resource sitemap' do + refute DataFile.any? + assert Sop.any? + # disabled and empty are not there + refute File.exist?("#{Rails.root}/public/sitemaps/data_files.xml") + refute File.exist?("#{Rails.root}/public/sitemaps/sops.xml") + + get '/sitemaps/models.xml' + assert_response :success + + doc = Nokogiri::XML.parse(response.body) + doc.remove_namespaces! + assert_equal 3, doc.xpath('//urlset/url/loc').count + @models.each do |model| + assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/models/'+model.id.to_s+'"]').count + end + assert_equal 0, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/models/'+@private_model.id.to_s+'"]').count end end From 8507781f2333f912cb66aba695e321c5ab6c2668 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 14:20:41 +0000 Subject: [PATCH 259/344] test site.xml #1794 --- test/integration/sitemap_test.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index 9131b514b2..a8cc4f9b80 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -66,4 +66,20 @@ def setup assert_equal 0, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/models/'+@private_model.id.to_s+'"]').count end + test 'site' do + get '/sitemaps/site.xml' + assert_response :success + + doc = Nokogiri::XML.parse(response.body) + doc.remove_namespaces! + + assert_equal Seek::Util.searchable_types.count + 1, doc.xpath('//urlset/url').count + assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/"]').count + assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/institutions"]').count + + # disabled aren't shown, but types without content are + assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/data_files"]').count + assert_equal 0, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/sops"]').count + end + end From 277bfe95d570362fc4b2d5d4aae8fa4c86d789b0 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 14:23:33 +0000 Subject: [PATCH 260/344] rubocop #1794 --- config/sitemap.rb | 5 +---- test/integration/sitemap_test.rb | 13 +++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/config/sitemap.rb b/config/sitemap.rb index c9465e8c97..f683936f74 100644 --- a/config/sitemap.rb +++ b/config/sitemap.rb @@ -1,13 +1,12 @@ # https://github.com/kjvarga/sitemap_generator#sitemapgenerator -SitemapGenerator::Sitemap.sitemaps_path = "sitemaps" +SitemapGenerator::Sitemap.sitemaps_path = 'sitemaps' # SitemapGenerator::Sitemap.create_index = "auto" SitemapGenerator::Sitemap.compress = false SitemapGenerator::Sitemap.include_root = false SitemapGenerator::Sitemap.default_host = URI.parse(Seek::Config.site_base_url) SitemapGenerator::Sitemap.create do - types = Seek::Util.searchable_types group(filename: :site) do @@ -24,6 +23,4 @@ end end end - end - diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index a8cc4f9b80..f3862f54d3 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -1,18 +1,16 @@ require 'test_helper' require 'sitemap_generator' - class SitemapTest < ActionDispatch::IntegrationTest - def setup disable_authorization_checks do DataFile.destroy_all Model.destroy_all Sop.destroy_all end - @models = FactoryBot.create_list(:model,3, policy:FactoryBot.create(:public_policy)) + @models = FactoryBot.create_list(:model, 3, policy: FactoryBot.create(:public_policy)) @private_model = FactoryBot.create(:model, policy: FactoryBot.create(:private_policy)) - @sops = FactoryBot.create_list(:sop,3, policy:FactoryBot.create(:public_policy)) + @sops = FactoryBot.create_list(:sop, 3, policy: FactoryBot.create(:public_policy)) @projects = Project.all @people = Person.all @institutions = Institution.all @@ -23,7 +21,6 @@ def setup with_config_value(:sops_enabled, false) do SitemapGenerator::Interpreter.run(verbose: false) end - end test 'root sitemap' do @@ -61,9 +58,10 @@ def setup doc.remove_namespaces! assert_equal 3, doc.xpath('//urlset/url/loc').count @models.each do |model| - assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/models/'+model.id.to_s+'"]').count + assert_equal 1, doc.xpath("//urlset/url/loc[text()=\"http://localhost:3000/models/#{model.id}\"]").count end - assert_equal 0, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/models/'+@private_model.id.to_s+'"]').count + assert_equal 0, + doc.xpath("//urlset/url/loc[text()=\"http://localhost:3000/models/#{@private_model.id}\"]").count end test 'site' do @@ -81,5 +79,4 @@ def setup assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/data_files"]').count assert_equal 0, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/sops"]').count end - end From fcf49e76b546e5f5ddc60f77085093587bdb8c3d Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 14:57:18 +0000 Subject: [PATCH 261/344] trigger sitemap build during docker startup --- docker/entrypoint.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 20af6e39dc..10892304ba 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -31,6 +31,10 @@ then bundle exec rake db:seed:openseek:default_openbis_endpoint fi +# Build the sitemap +echo "TRIGGER SITEMAP BUILD" +bundle exec rake sitemap:create & + # Start Rails echo "STARTING SEEK" bundle exec puma -C docker/puma.rb & From e1ba088d633fa6770c1f6c3e092e32e70cdf9cce Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 14:59:14 +0000 Subject: [PATCH 262/344] add the changeref and priority to resource sitemap --- config/sitemap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sitemap.rb b/config/sitemap.rb index f683936f74..444cef5554 100644 --- a/config/sitemap.rb +++ b/config/sitemap.rb @@ -19,7 +19,7 @@ types.each do |type| group(filename: type.table_name) do type.authorized_for('view', nil).each do |resource| - add polymorphic_path(resource), lastmod: resource.updated_at + add polymorphic_path(resource), lastmod: resource.updated_at, changefreq: 'daily', priority: 0.7 end end end From 0fbbbf77dba42dda1e40de66b9d89e792de862f5 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 15:10:21 +0000 Subject: [PATCH 263/344] set the individual resource changefreq to weekly #1794 items themself aren't likely to change daily --- config/sitemap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sitemap.rb b/config/sitemap.rb index 444cef5554..4679101de8 100644 --- a/config/sitemap.rb +++ b/config/sitemap.rb @@ -19,7 +19,7 @@ types.each do |type| group(filename: type.table_name) do type.authorized_for('view', nil).each do |resource| - add polymorphic_path(resource), lastmod: resource.updated_at, changefreq: 'daily', priority: 0.7 + add polymorphic_path(resource), lastmod: resource.updated_at, changefreq: 'weekly', priority: 0.7 end end end From 05e8694996108239990daf23fd8d5d9f0b647712 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 15:54:41 +0000 Subject: [PATCH 264/344] avoid error being logged for routes not linked to a controller #1794 --- .../initializers/action_dispatch_http_mime_negotiation.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/initializers/action_dispatch_http_mime_negotiation.rb b/config/initializers/action_dispatch_http_mime_negotiation.rb index d37c457204..e2f8af116e 100644 --- a/config/initializers/action_dispatch_http_mime_negotiation.rb +++ b/config/initializers/action_dispatch_http_mime_negotiation.rb @@ -16,7 +16,12 @@ module MimeNegotiation alias original_format_from_path_extension format_from_path_extension def format_from_path_extension - controller_class&.ignore_format_from_extension ? nil : original_format_from_path_extension + clz = controller_class + if clz != ActionDispatch::Request::PASS_NOT_FOUND && clz&.ignore_format_from_extension + nil + else + original_format_from_path_extension + end end end end From cbf25f48ed4adb66c1db1e3b45b5d4322fd4e1be Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 16:14:58 +0000 Subject: [PATCH 265/344] clear the util cache to prevent disabled types appearing #1794 --- lib/seek/util.rb | 4 ++++ test/integration/sitemap_test.rb | 3 +++ 2 files changed, 7 insertions(+) diff --git a/lib/seek/util.rb b/lib/seek/util.rb index a1f2e4ce1c..0711fc5174 100644 --- a/lib/seek/util.rb +++ b/lib/seek/util.rb @@ -155,6 +155,10 @@ def self.lookup_class(class_name, raise: true) c end + def self.clear_cache + @cache = nil + end + private def self.persistent_class_lookup diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index f3862f54d3..aaa02a61f9 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -18,6 +18,9 @@ def setup sitemaps_dir = "#{Rails.root}/public/sitemaps" FileUtils.rm_rf(sitemaps_dir) if File.exist?(sitemaps_dir) + # avoid earlier cached types being included even if disabled + Seek::Util.clear_cache + with_config_value(:sops_enabled, false) do SitemapGenerator::Interpreter.run(verbose: false) end From f70d3c161e8bf3c873767b9e73edd679b8734f60 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 16:17:23 +0000 Subject: [PATCH 266/344] there was already a method to clear the cache #1794 --- lib/seek/util.rb | 4 ---- test/integration/sitemap_test.rb | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/seek/util.rb b/lib/seek/util.rb index 0711fc5174..a1f2e4ce1c 100644 --- a/lib/seek/util.rb +++ b/lib/seek/util.rb @@ -155,10 +155,6 @@ def self.lookup_class(class_name, raise: true) c end - def self.clear_cache - @cache = nil - end - private def self.persistent_class_lookup diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index aaa02a61f9..7432ee6787 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -19,7 +19,7 @@ def setup FileUtils.rm_rf(sitemaps_dir) if File.exist?(sitemaps_dir) # avoid earlier cached types being included even if disabled - Seek::Util.clear_cache + Seek::Util.clear_cached with_config_value(:sops_enabled, false) do SitemapGenerator::Interpreter.run(verbose: false) From bce6286d336bc1206dfbaf0021c39a2140d2d4ac Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 20 Mar 2024 17:17:33 +0000 Subject: [PATCH 267/344] try clearing the cache before and after and _enabled config is changed #1794 --- test/integration/sitemap_test.rb | 3 --- test/test_helper.rb | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index 7432ee6787..f3862f54d3 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -18,9 +18,6 @@ def setup sitemaps_dir = "#{Rails.root}/public/sitemaps" FileUtils.rm_rf(sitemaps_dir) if File.exist?(sitemaps_dir) - # avoid earlier cached types being included even if disabled - Seek::Util.clear_cached - with_config_value(:sops_enabled, false) do SitemapGenerator::Interpreter.run(verbose: false) end diff --git a/test/test_helper.rb b/test/test_helper.rb index 6c39d2f25e..601af1b578 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -71,11 +71,13 @@ def with_alternative_rendering(key, value) end def with_config_value(config, value) + Seek::Util.clear_cached if config.to_s.ends_with?('enabled') oldval = Seek::Config.send(config) Seek::Config.send("#{config}=", value) yield ensure Seek::Config.send("#{config}=", oldval) + Seek::Util.clear_cached if config.to_s.ends_with?('enabled') end def with_config_values(settings) From d96f00db8a9cdac5750a4b5174e0bad3dda79b86 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 21 Mar 2024 10:21:14 +0000 Subject: [PATCH 268/344] fix test after resolving caching problem #1794 --- test/integration/sitemap_test.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index f3862f54d3..2da8f5a9f9 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -71,7 +71,10 @@ def setup doc = Nokogiri::XML.parse(response.body) doc.remove_namespaces! - assert_equal Seek::Util.searchable_types.count + 1, doc.xpath('//urlset/url').count + # +1 for site, and -1 for disabled SOPs + expected_count = Seek::Util.searchable_types.count + + assert_equal expected_count, doc.xpath('//urlset/url').count assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/"]').count assert_equal 1, doc.xpath('//urlset/url/loc[text()="http://localhost:3000/institutions"]').count From ac7684dc17673c8176f7e46c7eea85924b88e6ad Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 21 Mar 2024 13:30:09 +0000 Subject: [PATCH 269/344] final tweaks and tidying up #1794 --- config/sitemap.rb | 1 - test/integration/sitemap_test.rb | 2 +- test/test_helper.rb | 2 ++ test/unit/bio_schema/schema_ld_generation_test.rb | 2 -- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/config/sitemap.rb b/config/sitemap.rb index 4679101de8..40ed333c78 100644 --- a/config/sitemap.rb +++ b/config/sitemap.rb @@ -1,7 +1,6 @@ # https://github.com/kjvarga/sitemap_generator#sitemapgenerator SitemapGenerator::Sitemap.sitemaps_path = 'sitemaps' -# SitemapGenerator::Sitemap.create_index = "auto" SitemapGenerator::Sitemap.compress = false SitemapGenerator::Sitemap.include_root = false SitemapGenerator::Sitemap.default_host = URI.parse(Seek::Config.site_base_url) diff --git a/test/integration/sitemap_test.rb b/test/integration/sitemap_test.rb index 2da8f5a9f9..c8af7c426a 100644 --- a/test/integration/sitemap_test.rb +++ b/test/integration/sitemap_test.rb @@ -71,7 +71,7 @@ def setup doc = Nokogiri::XML.parse(response.body) doc.remove_namespaces! - # +1 for site, and -1 for disabled SOPs + # +1 for root path, and -1 for disabled SOPs expected_count = Seek::Util.searchable_types.count assert_equal expected_count, doc.xpath('//urlset/url').count diff --git a/test/test_helper.rb b/test/test_helper.rb index 601af1b578..577f99a6fb 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -84,11 +84,13 @@ def with_config_values(settings) oldvals = {} settings.each do |config, value| oldvals[config] = Seek::Config.send(config) + Seek::Util.clear_cached if config.to_s.ends_with?('enabled') Seek::Config.send("#{config}=", value) end yield ensure oldvals.each do |config, oldval| + Seek::Util.clear_cached if config.to_s.ends_with?('enabled') Seek::Config.send("#{config}=", oldval) end end diff --git a/test/unit/bio_schema/schema_ld_generation_test.rb b/test/unit/bio_schema/schema_ld_generation_test.rb index 1806135cff..6bd1eed726 100644 --- a/test/unit/bio_schema/schema_ld_generation_test.rb +++ b/test/unit/bio_schema/schema_ld_generation_test.rb @@ -89,7 +89,6 @@ def setup 'dateModified' => @current_time.iso8601 } - Seek::Util.clear_cached with_config_values(collections_enabled: false, data_files_enabled: false, documents_enabled: false, @@ -105,7 +104,6 @@ def setup json = JSON.parse(Seek::BioSchema::DataCatalogMockModel.new.to_schema_ld) assert_equal expected, json end - Seek::Util.clear_cached end test 'person' do From 01ff4c823aa59464e3721b1724faf6cb996df070 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 25 Mar 2024 12:49:28 +0100 Subject: [PATCH 270/344] Move `sanitized_json_metadata` out of the if statement --- app/helpers/dynamic_table_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/dynamic_table_helper.rb b/app/helpers/dynamic_table_helper.rb index 973c88ef65..a698f5f7cb 100644 --- a/app/helpers/dynamic_table_helper.rb +++ b/app/helpers/dynamic_table_helper.rb @@ -45,8 +45,8 @@ def dt_rows(sample_type) registered_sample_multi_attributes = sample_type.sample_attributes.select { |sa| sa.sample_attribute_type.base_type == Seek::Samples::BaseType::SEEK_SAMPLE_MULTI } sample_type.samples.map do |s| + sanitized_json_metadata = hide_unauthorized_inputs(JSON.parse(s.json_metadata), registered_sample_attributes, registered_sample_multi_attributes) if s.can_view? - sanitized_json_metadata = hide_unauthorized_inputs(JSON.parse(s.json_metadata), registered_sample_attributes, registered_sample_multi_attributes) { 'selected' => '', 'id' => s.id, 'uuid' => s.uuid }.merge!(sanitized_json_metadata) else { 'selected' => '', 'id' => '#HIDDEN', 'uuid' => '#HIDDEN' }.merge!(sanitized_json_metadata&.transform_values { '#HIDDEN' }) From 89e5b01aaf74dbd8a1bce6f8dde1af986d71ffcb Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 25 Mar 2024 12:50:45 +0100 Subject: [PATCH 271/344] In case @assay does not have an assay stream --- app/views/assays/show.html.erb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index f76fbddc3f..fabd8c63f9 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -82,18 +82,16 @@ Child assays:
      <% @assay.child_assays.map do |ca| %> - <% unless @assay.child_assays.blank? %>
    • <%= link_to ca.title, ca %>
    • <% end %> - <% end %>

    <% else %>

    <%= t('assays.assay_stream') %>: - <%= link_to @assay.assay_stream.title, @assay.assay_stream %> + <%= link_to @assay.assay_stream.title, @assay.assay_stream if @assay.assay_stream %>

    <% end %> <% end %> From 0a459f0d553b4a2a5fc65826370677522f54c13f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 25 Mar 2024 16:55:09 +0100 Subject: [PATCH 272/344] Fix update --- app/assets/javascripts/single_page/dynamic_table.js.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index 3f4df0cbda..0827ff286a 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -478,8 +478,8 @@ function handleCellUpdate(table, cell) { // If it is a new sample, then it has to be created const status = table.row(row).data()[sampleStatusIndex]; if (status == rowStatus.empty) { - table.row(row).data()[sampleStatusIndex] = rowStatus.new; - } else if (status == "") { + table.row(row).data()[sampleStatusIndex] = rowStatus.new; + } else if (status == "" || status == null) { table.row(row).data()[sampleStatusIndex] = rowStatus.update; } } From 978019d959a4db26409589c20dcafeaa416460eb Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 25 Mar 2024 17:02:36 +0100 Subject: [PATCH 273/344] Manually disable buttons when uploading --- app/views/single_pages/sample_upload_content.html.erb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/single_pages/sample_upload_content.html.erb b/app/views/single_pages/sample_upload_content.html.erb index a2a939ec81..139062d970 100644 --- a/app/views/single_pages/sample_upload_content.html.erb +++ b/app/views/single_pages/sample_upload_content.html.erb @@ -48,8 +48,8 @@
    <% 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()" %> + <%= submit_tag "Upload", id: 'upload-xlsx-content-btn', data: { disable_with: 'Uploading ...' }, class: 'btn btn-primary', onclick: "submitUpload()" if (@can_upload and any_samples_to_upload?) %> + <%= submit_tag "Cancel", id: 'close-upload-xlsx-modal-btn', data: { id: 'cancelModalUploadExcel' }, class: 'btn btn-secondary', onclick: "closeModalForm()" %> <%= image_tag('ajax-loader.gif', id: 'sample-upload-spinner', style: 'display: none') %>
    @@ -80,6 +80,9 @@ async function makeSampleUploadAjaxCalls(newSamples, updatedSamples){ $j('#sample-upload-spinner').show(); + $j('#upload-xlsx-content-btn').prop('disabled', true); + $j('#close-upload-xlsx-modal-btn').prop('disabled', true); + try{ const postCall = await uploadAjaxCall("<%= batch_create_samples_path %>", "POST", { data: JSON.stringify(newSamples) }); const putCall = await uploadAjaxCall("<%= batch_update_samples_path %>", "PUT", { data: JSON.stringify(updatedSamples) }); @@ -92,7 +95,9 @@ location.reload(); } catch (error){ alert(`Error: ${error}`); - $j('#sample-upload-spinner').hide(); + $j('#sample-upload-spinner').hide(); + $j('#upload-xlsx-content-btn').prop('disabled', false); + $j('#close-upload-xlsx-modal-btn').prop('disabled', false); } } From 9341fb32a958b4306e03f4acc4578d961bbcbcbe Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 22 Mar 2024 16:40:09 +0000 Subject: [PATCH 274/344] control seek and supercronic logging for docker #1797 SEEK_LOG_LEVEL for the rails log level QUIET_SUPERSONIC to run supersonic is quiet mode to avoid periodic activity logs --- config/environments/production.rb | 2 +- docker-compose.yml | 3 +++ docker/puma.rb | 1 - docker/shared_functions.sh | 10 ++++++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 59d1af9c3d..be97345142 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -50,7 +50,7 @@ # Include generic and useful information about system operation, but avoid logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). - config.log_level = :info + config.log_level = ENV.fetch('SEEK_LOG_LEVEL'){ 'info' }.downcase.to_sym # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] diff --git a/docker-compose.yml b/docker-compose.yml index e784ad4286..50b2021dc7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,7 @@ services: SOLR_PORT: 8983 SOLR_HOST: solr NO_ENTRYPOINT_WORKERS: 1 + SEEK_LOG_LEVEL: info # debug, info, warn, error or fatal env_file: - docker/db.env volumes: @@ -49,6 +50,8 @@ services: RAILS_ENV: production SOLR_PORT: 8983 SOLR_HOST: solr + SEEK_LOG_LEVEL: info # debug, info, warn, error or fatal + QUIET_SUPERCRONIC: 1 # remove to show supercronic activity logs env_file: - docker/db.env volumes: diff --git a/docker/puma.rb b/docker/puma.rb index abd54fdc8b..c420d64820 100644 --- a/docker/puma.rb +++ b/docker/puma.rb @@ -18,7 +18,6 @@ stdout_redirect 'log/puma.out', 'log/puma.err' - bind 'tcp://0.0.0.0:2000' # bind 'unix:///var/run/puma.sock' # bind 'unix:///var/run/puma.sock?umask=0111' diff --git a/docker/shared_functions.sh b/docker/shared_functions.sh index c1bcc22900..f28b39c1b3 100644 --- a/docker/shared_functions.sh +++ b/docker/shared_functions.sh @@ -65,6 +65,12 @@ function setup_and_start_cron { echo "GENERATING CRONTAB" bundle exec whenever > /seek/seek.crontab - echo "STARTING SUPERCRONIC" - supercronic /seek/seek.crontab & + if [ -z $QUIET_SUPERCRONIC ] + then + echo "STARTING SUPERCRONIC" + supercronic /seek/seek.crontab & + else + echo "STARTING SUPERCRONIC (QUIET)" + supercronic -quiet /seek/seek.crontab & + fi } From 641fd77e45780188197eb83ae9a07fe825dced3b Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 22 Mar 2024 16:40:47 +0000 Subject: [PATCH 275/344] switch to the 1.15-dev image --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 50b2021dc7..1d6145c90c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: seek: # The SEEK application #build: . - image: fairdom/seek:main + image: fairdom/seek::1.15-dev container_name: seek command: docker/entrypoint.sh @@ -42,7 +42,7 @@ services: seek_workers: # The SEEK delayed job workers #build: . - image: fairdom/seek:main + image: fairdom/seek:1.15-dev container_name: seek-workers command: docker/start_workers.sh restart: always From a0c8de66013c2c081d36e192a19c82ed2a17716b Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 25 Mar 2024 13:09:53 +0000 Subject: [PATCH 276/344] changed the env variable to RAILS_LOG_LEVEL #1797 --- config/environments/production.rb | 2 +- docker-compose.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index be97345142..e0b79fbc6d 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -50,7 +50,7 @@ # Include generic and useful information about system operation, but avoid logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). - config.log_level = ENV.fetch('SEEK_LOG_LEVEL'){ 'info' }.downcase.to_sym + config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info').downcase # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] diff --git a/docker-compose.yml b/docker-compose.yml index 1d6145c90c..04783f99bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: SOLR_PORT: 8983 SOLR_HOST: solr NO_ENTRYPOINT_WORKERS: 1 - SEEK_LOG_LEVEL: info # debug, info, warn, error or fatal + RAILS_LOG_LEVEL: info # debug, info, warn, error or fatal env_file: - docker/db.env volumes: @@ -50,7 +50,7 @@ services: RAILS_ENV: production SOLR_PORT: 8983 SOLR_HOST: solr - SEEK_LOG_LEVEL: info # debug, info, warn, error or fatal + RAILS_LOG_LEVEL: info # debug, info, warn, error or fatal QUIET_SUPERCRONIC: 1 # remove to show supercronic activity logs env_file: - docker/db.env From f99a7b68fc7be872f7ee3ef5b9bd81b47e7a1708 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 19:41:12 +0000 Subject: [PATCH 277/344] Bump stringio from 3.0.1 to 3.0.1.1 Bumps [stringio](https://github.com/ruby/stringio) from 3.0.1 to 3.0.1.1. - [Release notes](https://github.com/ruby/stringio/releases) - [Changelog](https://github.com/ruby/stringio/blob/master/NEWS.md) - [Commits](https://github.com/ruby/stringio/compare/v3.0.1...v3.0.1.1) --- updated-dependencies: - dependency-name: stringio dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 50155fc923..89b42534ce 100644 --- a/Gemfile +++ b/Gemfile @@ -59,7 +59,7 @@ gem 'rdf-virtuoso', '>= 0.2.0' gem 'terrapin' gem 'lograge' gem 'psych' -gem 'stringio', '3.0.1' #locked to the default version for ruby 3.1 +gem 'stringio', '3.0.1.1' #locked to the default version for ruby 3.1 gem 'validate_url' gem "attr_encrypted", "~> 3.0.0" gem 'libreconv' diff --git a/Gemfile.lock b/Gemfile.lock index cda8e4a355..201e191f2c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -860,7 +860,7 @@ GEM sprockets (>= 3.0.0) sqlite3 (1.4.2) stackprof (0.2.25) - stringio (3.0.1) + stringio (3.0.1.1) sunspot (2.6.0) pr_geohash (~> 1.0) rsolr (>= 1.1.1, < 3) @@ -1085,7 +1085,7 @@ DEPENDENCIES sprockets-rails sqlite3 (~> 1.4) stackprof (~> 0.2.25) - stringio (= 3.0.1) + stringio (= 3.0.1.1) sunspot_matchers sunspot_rails teaspoon From 6cc5c2a03b51ba229266bdf2d484615e2b530d5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 19:40:59 +0000 Subject: [PATCH 278/344] Bump rdoc from 6.5.0 to 6.5.1.1 Bumps [rdoc](https://github.com/ruby/rdoc) from 6.5.0 to 6.5.1.1. - [Release notes](https://github.com/ruby/rdoc/releases) - [Changelog](https://github.com/ruby/rdoc/blob/master/History.rdoc) - [Commits](https://github.com/ruby/rdoc/compare/v6.5.0...v6.5.1.1) --- updated-dependencies: - dependency-name: rdoc dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 201e191f2c..d3e795ca4e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -708,7 +708,7 @@ GEM rdf-xsd (3.2.1) rdf (~> 3.2) rexml (~> 3.2) - rdoc (6.5.0) + rdoc (6.5.1.1) psych (>= 4.0.0) recaptcha (4.1.0) json From c34ab3b11d06362ab2804a35337e6887b58b05d2 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 27 Mar 2024 09:45:57 +0000 Subject: [PATCH 279/344] small upgrade task to rename supported_type where it is the legacy CustomMetadata likely only affects our test server --- lib/tasks/seek_upgrades.rake | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 6315d79c1f..5f06c3b5ab 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -20,6 +20,7 @@ namespace :seek do recognise_isa_json_compliant_items implement_assay_streams_for_isa_assays set_ls_login_legacy_mode + rename_custom_metadata_legacy_supported_type ] # these are the tasks that are executes for each upgrade as standard, and rarely change @@ -235,6 +236,13 @@ namespace :seek do end end + task(rename_custom_metadata_legacy_supported_type: [:environment]) do + if ExtendedMetadataType.where(supported_type: 'CustomMetadata').any? + puts "... Renaming ExtendedMetadata supported_type from Custom to ExtendedMetadata" + ExtendedMetadataType.where(supported_type: 'CustomMetadata').update_all(supported_type: 'ExtendedMetadata') + end + end + private ## From 3be3b8108965afc9129569c3e92778624a8461b4 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 27 Mar 2024 15:54:05 +0000 Subject: [PATCH 280/344] fix docker compose typo --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 04783f99bc..e7dec9dd7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: seek: # The SEEK application #build: . - image: fairdom/seek::1.15-dev + image: fairdom/seek:1.15-dev container_name: seek command: docker/entrypoint.sh From cc8688a039d929865cae5ec500de619f0acbba75 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 28 Mar 2024 10:48:50 +0000 Subject: [PATCH 281/344] include linked_extended_metadata_type_id in extended metadata api #1809 --- app/serializers/extended_metadata_type_serializer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/serializers/extended_metadata_type_serializer.rb b/app/serializers/extended_metadata_type_serializer.rb index 36b251edba..25e57dea0b 100644 --- a/app/serializers/extended_metadata_type_serializer.rb +++ b/app/serializers/extended_metadata_type_serializer.rb @@ -17,7 +17,8 @@ def get_extended_metadata_attribute(attribute) "sample_attribute_type": get_sample_attribute_type(attribute), "required": attribute.required, "pos": attribute.pos, - "sample_controlled_vocab_id": attribute.sample_controlled_vocab_id.nil? ? nil : attribute.sample_controlled_vocab_id.to_s + "sample_controlled_vocab_id": attribute.sample_controlled_vocab_id.nil? ? nil : attribute.sample_controlled_vocab_id.to_s, + "linked_extended_metadata_type_id": attribute.linked_extended_metadata_type_id.nil? ? nil : attribute.linked_extended_metadata_type_id.to_s } end From 43de81d5a089414558167e05d0d80c3aaa254425 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 28 Mar 2024 15:12:55 +0000 Subject: [PATCH 282/344] better syntax #1810 --- app/serializers/extended_metadata_type_serializer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/serializers/extended_metadata_type_serializer.rb b/app/serializers/extended_metadata_type_serializer.rb index 25e57dea0b..f4c423ff91 100644 --- a/app/serializers/extended_metadata_type_serializer.rb +++ b/app/serializers/extended_metadata_type_serializer.rb @@ -17,8 +17,8 @@ def get_extended_metadata_attribute(attribute) "sample_attribute_type": get_sample_attribute_type(attribute), "required": attribute.required, "pos": attribute.pos, - "sample_controlled_vocab_id": attribute.sample_controlled_vocab_id.nil? ? nil : attribute.sample_controlled_vocab_id.to_s, - "linked_extended_metadata_type_id": attribute.linked_extended_metadata_type_id.nil? ? nil : attribute.linked_extended_metadata_type_id.to_s + "sample_controlled_vocab_id": attribute.sample_controlled_vocab_id&.to_s, + "linked_extended_metadata_type_id": attribute.linked_extended_metadata_type_id&.to_s } end From 72026fd3126bde2867037cee0d7a066e4db0f5db Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 28 Mar 2024 10:03:49 +0000 Subject: [PATCH 283/344] fix job queue table includes RdfGenerationQueue #1810 --- app/views/admin/stats/_job_queue.html.erb | 2 +- test/functional/admin_controller_test.rb | 31 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/views/admin/stats/_job_queue.html.erb b/app/views/admin/stats/_job_queue.html.erb index e026c48c78..9c5ad64cc4 100644 --- a/app/views/admin/stats/_job_queue.html.erb +++ b/app/views/admin/stats/_job_queue.html.erb @@ -1,5 +1,5 @@ <% - queue = AuthLookupUpdateQueue.all.to_a | ReindexingQueue.all.to_a + queue = AuthLookupUpdateQueue.all.to_a | ReindexingQueue.all.to_a | RdfGenerationQueue.all.to_a queue = queue.sort_by(&:created_at) delayed_jobs = Delayed::Job.order(locked_at: :desc, created_at: :asc) %> diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index de9227fc5c..eba007e8ea 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -236,6 +236,37 @@ def setup end end + test 'job queue table' do + sop = FactoryBot.create(:sop) + admin = FactoryBot.create(:admin) + login_as(admin) + RdfGenerationQueue.destroy_all + ReindexingQueue.destroy_all + AuthLookupUpdateQueue.destroy_all + + with_config_value(:auth_lookup_enabled, true) do + + assert RdfGenerationQueue.queue_enabled? + assert ReindexingQueue.queue_enabled? + assert AuthLookupUpdateQueue.queue_enabled? + + RdfGenerationQueue.enqueue(sop) + ReindexingQueue.enqueue(sop) + AuthLookupUpdateQueue.enqueue(sop) + end + + + get :get_stats, xhr: true, params: { page: 'job_queue' } + assert_response :success + + assert_select 'div.job-queue-table table' do + assert_select 'tbody > tr', count: 3 + assert_select 'tbody > tr > td', text: 'RdfGenerationQueue' + assert_select 'tbody > tr > td', text: 'ReindexingQueue' + assert_select 'tbody > tr > td', text: 'AuthLookupUpdateQueue' + end + end + test 'storage usage stats' do FactoryBot.create(:rightfield_datafile) FactoryBot.create(:rightfield_annotated_datafile) From 3841537008a4e6c9d736f4679edb6031caf35ed5 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 28 Mar 2024 15:06:44 +0000 Subject: [PATCH 284/344] removed unnecessary new line #1810 --- test/functional/admin_controller_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index eba007e8ea..e28453ef84 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -255,7 +255,6 @@ def setup AuthLookupUpdateQueue.enqueue(sop) end - get :get_stats, xhr: true, params: { page: 'job_queue' } assert_response :success From 60626e04f144a225a6186bba83e725725fa5bbff Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 9 Apr 2024 13:56:46 +0100 Subject: [PATCH 285/344] update version --- config/version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/version.yml b/config/version.yml index 3bc73edb05..9fe2f4b4c9 100644 --- a/config/version.yml +++ b/config/version.yml @@ -9,4 +9,4 @@ major: 1 minor: 15 -patch: 0-main +patch: 0-pre From cfa713c28c4c261192281e271730b37b68cd18a1 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 11 Apr 2024 10:09:20 +0100 Subject: [PATCH 286/344] clean up some shorthand hash syntax causing RubyMine reported syntax errors --- test/unit/assay_test.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index f16c9a2509..da6bd75bc4 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 investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) - isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) + isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study, investigation: investigation) assert isa_json_compliant_study.is_isa_json_compliant? default_assay = FactoryBot.create(:assay, study: isa_json_compliant_study) @@ -777,7 +777,7 @@ def new_valid_assay test 'previous linked sample type' do investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) - isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) + isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: investigation ) def_study = FactoryBot.create(:study) assay_stream = FactoryBot.create(:assay_stream, study: isa_study) @@ -789,7 +789,7 @@ def new_valid_assay assert_nil def_assay.previous_linked_sample_type first_isa_assay = FactoryBot.create(:isa_json_compliant_assay, - assay_stream: , + assay_stream: assay_stream , study: isa_study) assert_equal first_isa_assay.previous_linked_sample_type, isa_study.sample_types.second @@ -797,7 +797,7 @@ def new_valid_assay linked_sample_type: first_isa_assay.sample_type) second_isa_assay = FactoryBot.create(:assay, study: isa_study, - assay_stream: , + assay_stream: assay_stream , sample_type: data_file_sample_type) assert_equal second_isa_assay.previous_linked_sample_type, first_isa_assay.sample_type @@ -806,7 +806,7 @@ def new_valid_assay test 'has_linked_child_assay?' do investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) - isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) + isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: investigation ) def_study = FactoryBot.create(:study) def_assay = FactoryBot.create(:assay, study:def_study) @@ -816,7 +816,7 @@ def new_valid_assay linked_sample_type: first_isa_assay.sample_type) second_isa_assay = FactoryBot.create(:assay, study: isa_study, - assay_stream: , + assay_stream: assay_stream , sample_type: data_file_sample_type) assert assay_stream.has_linked_child_assay? @@ -827,17 +827,17 @@ def new_valid_assay test 'next_linked_child_assay' do investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true) - isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: ) + isa_study = FactoryBot.create(:isa_json_compliant_study, investigation: investigation ) def_study = FactoryBot.create(:study) def_assay = FactoryBot.create(:assay, study:def_study) assay_stream = FactoryBot.create(:assay_stream, study: isa_study) - first_isa_assay = FactoryBot.create(:isa_json_compliant_assay, study: isa_study, assay_stream: ) + first_isa_assay = FactoryBot.create(:isa_json_compliant_assay, study: isa_study, assay_stream: assay_stream ) data_file_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: first_isa_assay.sample_type) second_isa_assay = FactoryBot.create(:assay, study: isa_study, - assay_stream: , + assay_stream: assay_stream , sample_type: data_file_sample_type) assert_equal assay_stream.first_assay_in_stream, first_isa_assay From 09869d28b73eb1181f360197b82b3482e22563e3 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 11 Apr 2024 10:09:46 +0100 Subject: [PATCH 287/344] added templates_enabled to mirror isa_json_compliance_enabled #1822 --- lib/seek/config.rb | 4 ++++ test/unit/config_test.rb | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/seek/config.rb b/lib/seek/config.rb index 0f3350d932..707abe3abb 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -332,6 +332,10 @@ def assays_enabled isa_enabled end + def templates_enabled + isa_json_compliance_enabled + end + def omniauth_elixir_aai_config if omniauth_elixir_aai_legacy_mode { diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index a1ae027845..84086c8dc5 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -7,7 +7,7 @@ class ConfigTest < ActiveSupport::TestCase assert Seek::Config.events_enabled end test 'jerm_disabled' do - assert !Seek::Config.jerm_enabled + refute Seek::Config.jerm_enabled end test 'solr enabled' do assert Seek::Config.solr_enabled @@ -630,4 +630,14 @@ class ConfigTest < ActiveSupport::TestCase end end end + + test 'templates enabled' do + with_config_value(:isa_json_compliance_enabled, false) do + refute Seek::Config.templates_enabled + end + + with_config_value(:isa_json_compliance_enabled, true) do + assert Seek::Config.templates_enabled + end + end end From 273ee5cd2ed03249824ed2ca07d1a1a674cee94a Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 11 Apr 2024 10:38:21 +0100 Subject: [PATCH 288/344] functional test to check related items in view #1822 --- test/functional/projects_controller_test.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 21a8552a8c..3dc65796dd 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -5002,6 +5002,27 @@ def test_admin_can_edit end end + test 'do not show related templates if isa_compliance disabled' do + template = FactoryBot.create(:template) + person = template.contributor + project = template.projects.first + login_as(person) + assert template.can_view? + + with_config_value(:isa_json_compliance_enabled, true) do + get :show, params:{id: project.id} + assert_response :success + assert_select 'div#related-items li a[data-model-name=Template]', count: 1 + end + + with_config_value(:isa_json_compliance_enabled, false) do + get :show, params:{id: project.id} + assert_response :success + assert_select 'div#related-items li a[data-model-name=Template]', count: 0 + end + + end + private def check_project(project) From 96c13d2959063be6c1e47c84c6f42a0313a2407e Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 11 Apr 2024 14:17:41 +0100 Subject: [PATCH 289/344] fixed and updated tests, one still fails #1822 failing test only worked due to this bug. With isa_json_compliance_enabled it fails for other reasons that prevent viewing a sample_type with an associated isa_template --- test/functional/projects_controller_test.rb | 6 ++- .../sample_types_controller_test.rb | 6 ++- test/unit/util_test.rb | 38 ++++++++++++++----- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 3dc65796dd..7047fa40cf 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -4973,7 +4973,11 @@ def test_admin_can_edit login_as(person) assert project.has_member?(person) assert project.can_edit? - get :show, params: { id: project.id } + + with_config_value :isa_json_compliance_enabled, true do + get :show, params: { id: project.id } + end + assert_response :success params = { project_ids: [project.id] } directly_linked_types = [ diff --git a/test/functional/sample_types_controller_test.rb b/test/functional/sample_types_controller_test.rb index 3490543460..a1ea542890 100644 --- a/test/functional/sample_types_controller_test.rb +++ b/test/functional/sample_types_controller_test.rb @@ -710,8 +710,10 @@ class SampleTypesControllerTest < ActionController::TestCase login_as(person.user) - get :show, params: { id: sample_type.id } - assert_response :success + with_config_value :isa_json_compliance_enabled, true do + get :show, params: { id: sample_type.id } + assert_response :success + end assert_select 'div.related-items div#templates' do assert_select 'a[href=?]', template_path(template), text: template.title diff --git a/test/unit/util_test.rb b/test/unit/util_test.rb index 67011263fe..85af79b83c 100644 --- a/test/unit/util_test.rb +++ b/test/unit/util_test.rb @@ -7,10 +7,12 @@ def teardown end test 'creatable types' do - types = Seek::Util.user_creatable_types - # How to enable Placeholder? expected = [Collection, DataFile, Document, FileTemplate, Model, Placeholder, Presentation, Publication, Sample, Sop, Assay, Investigation, Study, Event, SampleType, Strain, Workflow, Template] + types = with_config_value :isa_json_compliance_enabled, true do + Seek::Util.user_creatable_types + end + # first as strings for more readable failed assertion message assert_equal expected.map(&:to_s).sort, types.map(&:to_s).sort @@ -19,9 +21,12 @@ def teardown end test 'authorized types' do - # How to enable Placeholder? expected = [Assay, Collection, DataFile, Document, Event, FileTemplate, Investigation, Model, Placeholder, Presentation, Publication, Sample, Sop, Strain, Study, Workflow, Template].map(&:name).sort - actual = Seek::Util.authorized_types.map(&:name).sort + + actual = with_config_value :isa_json_compliance_enabled, true do + Seek::Util.authorized_types.map(&:name).sort + end + assert_equal expected, actual end @@ -32,9 +37,12 @@ def teardown end test 'searchable types' do - types = Seek::Util.searchable_types expected = [Assay, Collection, DataFile, Document, Event, FileTemplate, HumanDisease, Institution, Investigation, Model, Organism, Person, Placeholder, Presentation, Programme, Project, Publication, Sample, SampleType, Sop, Strain, Study, Workflow, Template] + types = with_config_value :isa_json_compliance_enabled, true do + Seek::Util.searchable_types + end + # first as strings for more readable failed assertion message assert_equal expected.map(&:to_s).sort, types.map(&:to_s).sort @@ -42,16 +50,26 @@ def teardown assert_equal expected.sort_by(&:to_s), types.sort_by(&:to_s) with_config_value :events_enabled, false do - Seek::Util.clear_cached - types = Seek::Util.searchable_types - assert_equal (expected - [Event]).map(&:to_s).sort, types.map(&:to_s).sort + with_config_value :isa_json_compliance_enabled, true do + Seek::Util.clear_cached + types = Seek::Util.searchable_types + assert_equal (expected - [Event]).map(&:to_s).sort, types.map(&:to_s).sort + end end with_config_value :programmes_enabled, false do - Seek::Util.clear_cached + with_config_value :isa_json_compliance_enabled, true do + Seek::Util.clear_cached + types = Seek::Util.searchable_types + assert_equal (expected - [Programme]).map(&:to_s).sort, types.map(&:to_s).sort + end + end + + with_config_value :isa_json_compliance_enabled, false do types = Seek::Util.searchable_types - assert_equal (expected - [Programme]).map(&:to_s).sort, types.map(&:to_s).sort + assert_equal (expected - [Template]).map(&:to_s).sort, types.map(&:to_s).sort end + end test 'multi-file assets' do From 405a073b259815c8881107b71db299511d73645d Mon Sep 17 00:00:00 2001 From: Finn Date: Mon, 15 Apr 2024 11:56:55 +0100 Subject: [PATCH 290/344] Update LS-Login endpoint --- lib/seek/config.rb | 2 +- test/unit/config_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/seek/config.rb b/lib/seek/config.rb index 0f3350d932..0f9e12ec2e 100644 --- a/lib/seek/config.rb +++ b/lib/seek/config.rb @@ -365,7 +365,7 @@ def omniauth_elixir_aai_config name: :elixir_aai, scope: [:openid, :email], response_type: 'code', - issuer: 'https://proxy.aai.lifescience-ri.eu/', + issuer: 'https://login.aai.lifescience-ri.eu/oidc/', discovery: true, client_options: { identifier: omniauth_elixir_aai_client_id, diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb index a1ae027845..74588a10f3 100644 --- a/test/unit/config_test.rb +++ b/test/unit/config_test.rb @@ -619,7 +619,7 @@ class ConfigTest < ActiveSupport::TestCase refute Seek::Config.omniauth_elixir_aai_legacy_mode assert_equal '/seeks/seek1/auth/elixir_aai/callback', config[:callback_path] assert_equal 'http://localhost/seeks/seek1/auth/elixir_aai/callback', config[:client_options][:redirect_uri] - assert_equal 'https://proxy.aai.lifescience-ri.eu/',config[:issuer] + assert_equal 'https://login.aai.lifescience-ri.eu/oidc/',config[:issuer] with_config_value(:omniauth_elixir_aai_legacy_mode, true) do assert Seek::Config.omniauth_elixir_aai_legacy_mode config = Seek::Config.omniauth_elixir_aai_config From e6f26831491ca61d1f304abfe19df5a814754f6c Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 11 Apr 2024 15:01:09 +0100 Subject: [PATCH 291/344] LinkingSamplesUpdateJob only saves and triggers new jobs if the metadata changes #1818 --- app/models/sample.rb | 8 ++++-- .../jobs/linking_samples_update_job_test.rb | 28 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/models/sample.rb b/app/models/sample.rb index d731bcac4c..c68ec6eb0f 100644 --- a/app/models/sample.rb +++ b/app/models/sample.rb @@ -145,8 +145,12 @@ def refresh_linking_samples sample['title'] = title if sample['id'] == id end metadata.values[p - 1] = item_linked_samples - s.json_metadata = metadata.to_json - s.save + + # only update if changed, to prevent triggered jobs that could potentially create an infinate loop + if s.json_metadata != metadata.to_json + s.json_metadata = metadata.to_json + s.save + end end end end diff --git a/test/unit/jobs/linking_samples_update_job_test.rb b/test/unit/jobs/linking_samples_update_job_test.rb index 4f9f31c87c..c38d6b2b9a 100644 --- a/test/unit/jobs/linking_samples_update_job_test.rb +++ b/test/unit/jobs/linking_samples_update_job_test.rb @@ -2,6 +2,7 @@ class LinkingSamplesUpdateJobTest < ActiveSupport::TestCase def setup + @person = FactoryBot.create(:person) create_linked_samples end @@ -17,9 +18,31 @@ def setup end end + test 'only trigger further jobs if the metadata changes' do + sample = Sample.first + sample.set_attribute_value('full name', 'Ali Mohammadi') + disable_authorization_checks { sample.save! } + assert_enqueued_jobs 2, only: LinkingSamplesUpdateJob do + LinkingSamplesUpdateJob.perform_now(sample) + end + + sample.set_attribute_value('full name', 'Ali Mohammadi') + disable_authorization_checks { sample.save! } + assert_enqueued_jobs 0, only: LinkingSamplesUpdateJob do + LinkingSamplesUpdateJob.perform_now(sample) + end + + sample.set_attribute_value('full name', 'Fred Flintstone') + disable_authorization_checks { sample.save! } + assert_enqueued_jobs 2, only: LinkingSamplesUpdateJob do + LinkingSamplesUpdateJob.perform_now(sample) + end + end + + def create_linked_samples - person = FactoryBot.create(:person) - project = person.projects.first + + project = @person.projects.first main_sample = FactoryBot.create(:patient_sample) sample_type = main_sample.sample_type @@ -38,4 +61,5 @@ def create_linked_samples linked_sample2.set_attribute_value(:patient, [main_sample.id]) disable_authorization_checks { linked_sample2.save! } end + end From e2e3724dac10626e8c358241e0d653b601721093 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 28 Mar 2024 15:52:25 +0000 Subject: [PATCH 292/344] handle nested ext metadata when getting values for search #1808 --- app/models/concerns/has_extended_metadata.rb | 4 +++- lib/seek/json_metadata/data.rb | 11 +++++++++++ test/unit/investigation_test.rb | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/has_extended_metadata.rb b/app/models/concerns/has_extended_metadata.rb index c99293ab19..48782aedbc 100644 --- a/app/models/concerns/has_extended_metadata.rb +++ b/app/models/concerns/has_extended_metadata.rb @@ -3,8 +3,10 @@ module HasExtendedMetadata included do def extended_metadata_attribute_values_for_search - extended_metadata ? extended_metadata.data.values.reject(&:blank?).uniq : [] + return [] unless extended_metadata + extended_metadata.data.extract_all_values.uniq.compact end + end class_methods do diff --git a/lib/seek/json_metadata/data.rb b/lib/seek/json_metadata/data.rb index 05aadae6f7..f9bb831c60 100644 --- a/lib/seek/json_metadata/data.rb +++ b/lib/seek/json_metadata/data.rb @@ -34,6 +34,17 @@ def mass_assign(hash, pre_process: true) end end + # extracts all values from the data as a list, including from within nested structures + def extract_all_values(data = self) + data.values.collect do |value| + if value.is_a?(Data) + extract_all_values(value) + else + value + end + end.flatten + end + private def validate_hash(hash) diff --git a/test/unit/investigation_test.rb b/test/unit/investigation_test.rb index 6b850aa69d..07abe718f5 100644 --- a/test/unit/investigation_test.rb +++ b/test/unit/investigation_test.rb @@ -221,6 +221,10 @@ class InvestigationTest < ActiveSupport::TestCase ) ) assert_equal ['James','25'].sort, item.extended_metadata_attribute_values_for_search.map(&:to_s).sort + + #nested + item = FactoryBot.create(:investigation, extended_metadata: FactoryBot.create(:role_multiple_extended_metadata)) + assert_equal ['alice@email.com','0012345','liddell','alice','wonder','land'].sort, item.extended_metadata_attribute_values_for_search.map(&:to_s).sort end test 'related sop ids' do From 227dc4ffbace4d24756369f21de99032835961ca Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 9 Apr 2024 09:42:06 +0100 Subject: [PATCH 293/344] use full namespace for Data to avoid ambiguity #1808 --- lib/seek/json_metadata/data.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/seek/json_metadata/data.rb b/lib/seek/json_metadata/data.rb index f9bb831c60..51760ad4b3 100644 --- a/lib/seek/json_metadata/data.rb +++ b/lib/seek/json_metadata/data.rb @@ -37,7 +37,7 @@ def mass_assign(hash, pre_process: true) # extracts all values from the data as a list, including from within nested structures def extract_all_values(data = self) data.values.collect do |value| - if value.is_a?(Data) + if value.is_a?(Seek::JSONMetadata::Data) extract_all_values(value) else value From 04a8ec62ed9f90cc69065ad148b003ea24f6d9bc Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 9 Apr 2024 11:48:23 +0100 Subject: [PATCH 294/344] fix the title mismatch for MIAPPE ext metadata #1811 --- app/models/study_batch_upload.rb | 2 +- test/factories/extended_metadata_types.rb | 2 +- test/unit/studies/studies_extractor_test.rb | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/study_batch_upload.rb b/app/models/study_batch_upload.rb index 6a85f47c88..ad5772bd2b 100644 --- a/app/models/study_batch_upload.rb +++ b/app/models/study_batch_upload.rb @@ -29,7 +29,7 @@ def self.extract_study_data_from_file(studies_file) def self.extract_studies_from_file(studies_file) studies = [] parsed_sheet = Seek::Templates::StudiesReader.new(studies_file) - metadata_type = ExtendedMetadataType.where(title: 'MIAPPE metadata', supported_type: 'Study').last + metadata_type = ExtendedMetadataType.where(title: 'MIAPPE metadata v1.1', supported_type: 'Study').last columns = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] study_start_row_index = 4 parsed_sheet.each_record(3, columns) do |index, data| diff --git a/test/factories/extended_metadata_types.rb b/test/factories/extended_metadata_types.rb index 9cd6bf75a3..5d33f8b4ef 100644 --- a/test/factories/extended_metadata_types.rb +++ b/test/factories/extended_metadata_types.rb @@ -142,7 +142,7 @@ end factory(:study_extended_metadata_type_for_MIAPPE, class: ExtendedMetadataType) do - title { 'MIAPPE metadata' } + title { 'MIAPPE metadata v1.1' } supported_type { 'Study' } after(:build) do |a| a.extended_metadata_attributes << FactoryBot.create(:name_extended_metadata_attribute, title:'id') diff --git a/test/unit/studies/studies_extractor_test.rb b/test/unit/studies/studies_extractor_test.rb index 5912af0d55..7ec3494358 100644 --- a/test/unit/studies/studies_extractor_test.rb +++ b/test/unit/studies/studies_extractor_test.rb @@ -39,7 +39,8 @@ class StudiesExtractorTest < ActiveSupport::TestCase test 'extract study correctly' do user_uuid = 'user_uuid' - FactoryBot.create(:study_extended_metadata_type_for_MIAPPE) + extended_metadata_type = FactoryBot.create(:study_extended_metadata_type_for_MIAPPE) + assert_equal 'MIAPPE metadata v1.1', extended_metadata_type.title, 'must match the seed data title' studies_file = ContentBlob.new studies_file.tmp_io_object = File.open("#{Rails.root}/tmp/#{user_uuid}_studies_upload/#{@studies.first.name}") studies_file.original_filename = @studies.first.name.to_s From 8a9a7ba5bdc4a1612aa752fe5bf4e641480626a1 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 9 Apr 2024 12:08:38 +0100 Subject: [PATCH 295/344] constantize the hard-coded title #1811 --- app/controllers/studies_controller.rb | 2 +- app/helpers/studies_helper.rb | 2 +- app/models/extended_metadata.rb | 1 + app/models/extended_metadata_type.rb | 3 +++ app/models/study_batch_upload.rb | 2 +- test/factories/extended_metadata_types.rb | 2 +- 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index 642494ae57..51460ca610 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -202,7 +202,7 @@ def batch_create # e.g: Study.new(title: 'title', investigation: investigations(:metabolomics_investigation), policy: FactoryBot.create(:private_policy)) # study.policy = Policy.create(name: 'default policy', access_type: 1) # render plain: params[:studies].inspect - metadata_types = ExtendedMetadataType.where(title: 'MIAPPE metadata', supported_type: 'Study').last + metadata_types = ExtendedMetadataType.where(title: ExtendedMetadataType::MIAPPE_TITLE, supported_type: 'Study').last studies_length = params[:studies][:title].length studies_uploaded = false data_file_uploaded = false diff --git a/app/helpers/studies_helper.rb b/app/helpers/studies_helper.rb index 1e95a3b95c..f9ec4ea69a 100644 --- a/app/helpers/studies_helper.rb +++ b/app/helpers/studies_helper.rb @@ -24,6 +24,6 @@ def authorised_studies(projects = nil) end def show_batch_miappe_button? - ExtendedMetadataType.where(supported_type: 'Study', title: 'MIAPPE metadata v1.1').any? + ExtendedMetadataType.where(supported_type: 'Study', title: ExtendedMetadataType::MIAPPE_TITLE).any? end end diff --git a/app/models/extended_metadata.rb b/app/models/extended_metadata.rb index 05ffa2c5e5..d657987f95 100644 --- a/app/models/extended_metadata.rb +++ b/app/models/extended_metadata.rb @@ -22,4 +22,5 @@ def extended_metadata_type=(type) def attribute_class ExtendedMetadataAttribute end + end diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb index 8ea439518e..561b5f6c06 100644 --- a/app/models/extended_metadata_type.rb +++ b/app/models/extended_metadata_type.rb @@ -13,6 +13,9 @@ class ExtendedMetadataType < ApplicationRecord scope :enabled, ->{ where(enabled: true) } scope :disabled, ->{ where(enabled: false) } + # built in type + MIAPPE_TITLE = 'MIAPPE metadata v1.1' + def attribute_by_title(title) extended_metadata_attributes.where(title: title).first end diff --git a/app/models/study_batch_upload.rb b/app/models/study_batch_upload.rb index ad5772bd2b..90cb4b789e 100644 --- a/app/models/study_batch_upload.rb +++ b/app/models/study_batch_upload.rb @@ -29,7 +29,7 @@ def self.extract_study_data_from_file(studies_file) def self.extract_studies_from_file(studies_file) studies = [] parsed_sheet = Seek::Templates::StudiesReader.new(studies_file) - metadata_type = ExtendedMetadataType.where(title: 'MIAPPE metadata v1.1', supported_type: 'Study').last + metadata_type = ExtendedMetadataType.where(title: ExtendedMetadataType::MIAPPE_TITLE, supported_type: 'Study').last columns = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] study_start_row_index = 4 parsed_sheet.each_record(3, columns) do |index, data| diff --git a/test/factories/extended_metadata_types.rb b/test/factories/extended_metadata_types.rb index 5d33f8b4ef..40cf384114 100644 --- a/test/factories/extended_metadata_types.rb +++ b/test/factories/extended_metadata_types.rb @@ -142,7 +142,7 @@ end factory(:study_extended_metadata_type_for_MIAPPE, class: ExtendedMetadataType) do - title { 'MIAPPE metadata v1.1' } + title { ExtendedMetadataType::MIAPPE_TITLE } supported_type { 'Study' } after(:build) do |a| a.extended_metadata_attributes << FactoryBot.create(:name_extended_metadata_attribute, title:'id') From a375989614ff31a34101dba5e85801dbcdb32ccd Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 9 Apr 2024 16:24:51 +0100 Subject: [PATCH 296/344] freeze the MIAPPE_TITLE --- app/models/extended_metadata_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb index 561b5f6c06..4fbb9293c0 100644 --- a/app/models/extended_metadata_type.rb +++ b/app/models/extended_metadata_type.rb @@ -14,7 +14,7 @@ class ExtendedMetadataType < ApplicationRecord scope :disabled, ->{ where(enabled: false) } # built in type - MIAPPE_TITLE = 'MIAPPE metadata v1.1' + MIAPPE_TITLE = 'MIAPPE metadata v1.1'.freeze def attribute_by_title(title) extended_metadata_attributes.where(title: title).first From 242339bd902f491e35ebaa447438cc2953858c52 Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 16 Apr 2024 10:49:05 +0100 Subject: [PATCH 297/344] Bump `ro-crate-ruby` --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 89b42534ce..1ba5c08b17 100644 --- a/Gemfile +++ b/Gemfile @@ -134,7 +134,7 @@ gem 'request_store' gem 'bundler', '>= 1.8.4' -gem 'ro-crate', '~> 0.5.1' +gem 'ro-crate', '~> 0.5.2' gem 'rugged' gem 'i18n-js' diff --git a/Gemfile.lock b/Gemfile.lock index d3e795ca4e..0de2621a2b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -737,7 +737,7 @@ GEM json (~> 2.3.0) ucf (~> 2.0.2) uuid (~> 2.3) - ro-crate (0.5.1) + ro-crate (0.5.2) addressable (>= 2.7, < 2.9) rubyzip (~> 2.0.0) rsolr (2.5.0) @@ -1069,7 +1069,7 @@ DEPENDENCIES rfc-822 rmagick (= 5.3.0) ro-bundle (~> 0.3.0) - ro-crate (~> 0.5.1) + ro-crate (~> 0.5.2) rspec-rails (~> 5.1) rubocop ruby-prof From f790e84b6046b7d8948100bd7060cb2d14a2e7a1 Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 16 Apr 2024 11:58:58 +0100 Subject: [PATCH 298/344] Don't send notifications on general ROCrate write errors. Display more helpful error messages to users. --- app/controllers/workflows_controller.rb | 8 +-- app/models/concerns/workflow_extraction.rb | 16 ++--- lib/seek/workflow_extractors/ro_crate.rb | 2 - .../ro-crate-metadata-missing-file.json | 63 +++++++++++++++++++ test/functional/workflows_controller_test.rb | 16 ++++- 5 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/files/workflows/ro-crate-metadata-missing-file.json diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 1704c8ba86..13998646c5 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -20,8 +20,8 @@ class WorkflowsController < ApplicationController api_actions :index, :show, :create, :update, :destroy, :ro_crate, :create_version rescue_from ROCrate::ReadException do |e| - logger.error("Error whilst attempting to read RO-Crate metadata for #{@workflow&.id}.") - message = "Couldn't read RO-Crate metadata. Check the file is valid." + logger.error("Error whilst attempting to read RO-Crate metadata for Workflow #{@workflow&.id}: #{e.exception.class.name} #{e.message}") + message = "Couldn't read RO-Crate metadata - check the RO-Crate is valid: #{e.message}" respond_to do |format| format.html do flash[:error] = message @@ -32,8 +32,8 @@ class WorkflowsController < ApplicationController end rescue_from ROCrate::WriteException do |e| - exception_notification(500, e) unless Rails.application.config.consider_all_requests_local - message = "Couldn't generate RO-Crate. Check the ro-crate-metadata.json file is valid." + logger.error("Error whilst attempting to generate RO-Crate for #{@workflow&.id}: #{e.exception.class.name} #{e.message}") + message = "Couldn't generate RO-Crate - check the ro-crate-metadata.json file is valid: #{e.message}" respond_to do |format| format.html do flash[:error] = message diff --git a/app/models/concerns/workflow_extraction.rb b/app/models/concerns/workflow_extraction.rb index a9cf9ed027..6df16675e3 100644 --- a/app/models/concerns/workflow_extraction.rb +++ b/app/models/concerns/workflow_extraction.rb @@ -226,17 +226,13 @@ def ro_crate end def ro_crate_zip - begin - ro_crate do |crate| - path = ro_crate_path - File.delete(path) if File.exist?(path) - ROCrate::Writer.new(crate).write_zip(path) - end - - ro_crate_path - rescue StandardError => e - raise ::ROCrate::WriteException.new("Couldn't generate RO-Crate metadata.", e) + ro_crate do |crate| + path = ro_crate_path + File.delete(path) if File.exist?(path) + ROCrate::Writer.new(crate).write_zip(path) end + + ro_crate_path end def ro_crate_identifier diff --git a/lib/seek/workflow_extractors/ro_crate.rb b/lib/seek/workflow_extractors/ro_crate.rb index 33fbde69d4..116ccd66ed 100644 --- a/lib/seek/workflow_extractors/ro_crate.rb +++ b/lib/seek/workflow_extractors/ro_crate.rb @@ -54,8 +54,6 @@ def open_crate @opened_crate = nil v - rescue StandardError => e - raise ::ROCrate::ReadException.new("Couldn't read RO-Crate metadata.", e) end def diagram_extension diff --git a/test/fixtures/files/workflows/ro-crate-metadata-missing-file.json b/test/fixtures/files/workflows/ro-crate-metadata-missing-file.json new file mode 100644 index 0000000000..4ce5414aaa --- /dev/null +++ b/test/fixtures/files/workflows/ro-crate-metadata-missing-file.json @@ -0,0 +1,63 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + } + }, + { + "@id": "./", + "@type": "Dataset", + "name": "Concat two files", + "description": "Concats two files", + "license": "https://opensource.org/licenses/MIT", + "mainEntity": { + "@id": "concat_two_files.ga" + }, + "hasPart": [ + { + "@id": "concat_two_files.ga" + }, + { + "@id": "this-file-does-not-exist" + } + + ] + }, + { + "@id": "concat_two_files.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "programmingLanguage": { + "@id": "#galaxy" + }, + "name": "concat_two_files" + }, + { + "@id": "this-file-does-not-exist", + "@type": "File" + }, + { + "@id": "#galaxy", + "@type": "ComputerLanguage", + "name": "Galaxy", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "url": { + "@id": "https://galaxyproject.org/" + } + } + ] +} diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index 845f75d45e..d183322859 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -663,7 +663,7 @@ def bad_generator.write_graph(struct) get :ro_crate, params: { id: workflow.id } assert_redirected_to workflow - assert flash[:error].include?("Couldn't generate RO-Crate") + assert flash[:error].include?('No @graph found in metadata!') end test 'create RO-Crate even with with duplicated filenames' do @@ -1799,4 +1799,18 @@ def bad_generator.write_graph(struct) assert_select 'a.btn[href=?]', "https://galaxygalaxy.org/mygalaxy/workflows/trs_import?trs_url=#{trs_url}&run_form=true", { text: 'Run on Galaxy' } end + + test 'throws error when downloading RO-Crate with missing file' do + workflow = FactoryBot.create(:local_git_workflow, policy: FactoryBot.create(:public_policy)) + gv = workflow.latest_git_version + disable_authorization_checks do + gv.add_file('ro-crate-metadata.json', File.open(File.join("#{Rails.root}/test/fixtures/files", 'workflows/ro-crate-metadata-missing-file.json'))) + gv.save! + end + + get :ro_crate, params: { id: workflow.id } + + assert_redirected_to workflow + assert flash[:error].include?("not found in crate: this-file-does-not-exist") + end end From 8948bed29add9b19c2b06a21dc14c258cd3a0ffd Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 16 Apr 2024 14:46:05 +0100 Subject: [PATCH 299/344] Use exceptions from ro-crate-ruby gem --- app/controllers/workflows_controller.rb | 14 ++------------ lib/ro_crate/exception.rb | 11 ----------- lib/ro_crate/read_exception.rb | 3 --- lib/ro_crate/write_exception.rb | 3 --- 4 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 lib/ro_crate/exception.rb delete mode 100644 lib/ro_crate/read_exception.rb delete mode 100644 lib/ro_crate/write_exception.rb diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 13998646c5..c9d7ddb28d 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -1,3 +1,5 @@ +require 'ro_crate' + class WorkflowsController < ApplicationController include Seek::IndexPager include Seek::AssetsCommon @@ -31,18 +33,6 @@ class WorkflowsController < ApplicationController end end - rescue_from ROCrate::WriteException do |e| - logger.error("Error whilst attempting to generate RO-Crate for #{@workflow&.id}: #{e.exception.class.name} #{e.message}") - message = "Couldn't generate RO-Crate - check the ro-crate-metadata.json file is valid: #{e.message}" - respond_to do |format| - format.html do - flash[:error] = message - redirect_to workflow_path(@workflow) - end - format.json { render json: { title: 'RO-Crate Write Error', detail: message }, status: :internal_server_error } - end - end - def new_git_version @git_version = @workflow.latest_git_version.next_version(mutable: !@git_repository&.remote?) diff --git a/lib/ro_crate/exception.rb b/lib/ro_crate/exception.rb deleted file mode 100644 index df4517b6e8..0000000000 --- a/lib/ro_crate/exception.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ROCrate - class Exception < RuntimeError - attr_reader :cause - - def initialize(message, original_exception) - @cause = original_exception - super("#{self.class.name} - #{message}\n#{@cause.class.name} - #{@cause.message}") - set_backtrace(@cause.backtrace) - end - end -end diff --git a/lib/ro_crate/read_exception.rb b/lib/ro_crate/read_exception.rb deleted file mode 100644 index 0aa8488921..0000000000 --- a/lib/ro_crate/read_exception.rb +++ /dev/null @@ -1,3 +0,0 @@ -module ROCrate - class ReadException < ROCrate::Exception; end -end diff --git a/lib/ro_crate/write_exception.rb b/lib/ro_crate/write_exception.rb deleted file mode 100644 index f25c7663a6..0000000000 --- a/lib/ro_crate/write_exception.rb +++ /dev/null @@ -1,3 +0,0 @@ -module ROCrate - class WriteException < ROCrate::Exception; end -end From 96fc12ebad8193df99aaf2d18a2f1e87cbc1d9f4 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 16 Apr 2024 15:24:55 +0100 Subject: [PATCH 300/344] allow wildcards and removed searchtermfilter #1827 SearchTermFilter was removing wildcards and some other cases that had previously caused problems. Wildcards are useful and the other characters no longer cause a problem with the latest version of solr, to the class as a whole became redundant --- app/controllers/search_controller.rb | 2 +- lib/seek/filtering/search_filter.rb | 3 +-- lib/seek/search/search_term_filter.rb | 17 ----------------- test/unit/search_term_filter_test.rb | 27 --------------------------- 4 files changed, 2 insertions(+), 47 deletions(-) delete mode 100644 lib/seek/search/search_term_filter.rb delete mode 100644 test/unit/search_term_filter_test.rb diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 2b100795cb..5ad9e952df 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -54,7 +54,7 @@ def determine_query @search_query = ActionController::Base.helpers.sanitize(search_params[:q] || search_params[:search_query]) @search = @search_query # used for logging, and logs the origin search query - see ApplicationController#log_event @search_query ||= '' - @search_query = Seek::Search::SearchTermFilter.filter @search_query + @search_query.strip! raise InvalidSearchException, 'Query string is empty or blank' if @search_query.blank? end diff --git a/lib/seek/filtering/search_filter.rb b/lib/seek/filtering/search_filter.rb index 8b3dbeb017..9d0c62b45c 100644 --- a/lib/seek/filtering/search_filter.rb +++ b/lib/seek/filtering/search_filter.rb @@ -5,8 +5,7 @@ def apply(collection, query) q = query.join(' ') q = ActionController::Base.helpers.sanitize(q) q ||= '' - q = Seek::Search::SearchTermFilter.filter(q) - q = q.downcase + q = q.downcase.strip collection.with_search_query(q) end diff --git a/lib/seek/search/search_term_filter.rb b/lib/seek/search/search_term_filter.rb deleted file mode 100644 index f806eaae16..0000000000 --- a/lib/seek/search/search_term_filter.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Seek - module Search - class SearchTermFilter - def self.filter(query) - query = query.strip - query = query.delete('*') - query = query.delete('?') - query = query.chop if query.end_with?(':') - - query = query.strip - query = '' if query == '-' - - query - end - end - end -end diff --git a/test/unit/search_term_filter_test.rb b/test/unit/search_term_filter_test.rb deleted file mode 100644 index ebb7a773bd..0000000000 --- a/test/unit/search_term_filter_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'test_helper' - -class SearchTermFilterTest < ActiveSupport::TestCase - test 'filter asterix and question marks' do - assert_equal 'fish', Seek::Search::SearchTermFilter.filter('*fis*h*') - assert_equal 'fish', Seek::Search::SearchTermFilter.filter('?fis?h?') - end - - test 'filter semi colon, but only from end' do - assert_equal 'fish', Seek::Search::SearchTermFilter.filter('fish:') - assert_equal 'fish', Seek::Search::SearchTermFilter.filter('fish: ') - assert_equal 'fish', Seek::Search::SearchTermFilter.filter('fish : ') - assert_equal 'fish:2', Seek::Search::SearchTermFilter.filter('fish:2') - end - - test 'filter out single hyphen' do - assert_equal '', Seek::Search::SearchTermFilter.filter('-') - assert_equal '', Seek::Search::SearchTermFilter.filter(' - ') - assert_equal 'fish-soup', Seek::Search::SearchTermFilter.filter('fish-soup') - end - - test 'trims trailing spaces' do - assert_equal 'fish', Seek::Search::SearchTermFilter.filter(' fish ') - assert_equal 'fish', Seek::Search::SearchTermFilter.filter("\tfish\t") - assert_equal 'fish', Seek::Search::SearchTermFilter.filter("\nfish\n") - end -end From 84024bd2f69448497d7c0d2947205b625cd013f9 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 12 Apr 2024 16:30:29 +0100 Subject: [PATCH 301/344] skip callbacks to speed up upgrade task --- lib/tasks/seek_upgrades.rake | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 5f06c3b5ab..e0984c963f 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -107,7 +107,14 @@ namespace :seek do task(decouple_extracted_samples_policies: [:environment]) do puts '..... creating independent policies for extracted samples (this can take a while if there are many samples) ...' affected_samples = [] + + Policy.skip_callback :commit, :after, :queue_update_auth_table + Policy.skip_callback :commit, :after, :queue_rdf_generation_job + Permission.skip_callback :commit, :after, :queue_update_auth_table + Permission.skip_callback :commit, :after, :queue_rdf_generation_job + disable_authorization_checks do + 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_id == sample.originating_data_file&.policy_id @@ -119,9 +126,15 @@ namespace :seek do 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? + #AuthLookupUpdateQueue.enqueue(affected_samples) if affected_samples.any? end puts "..... finished creating independent policies of #{affected_samples.count} extracted samples" + ensure + Policy.set_callback :commit, :after, :queue_update_auth_table + Policy.set_callback :commit, :after, :queue_rdf_generation_job + Permission.set_callback :commit, :after, :queue_update_auth_table + Permission.set_callback :commit, :after, :queue_rdf_generation_job + end task(decouple_extracted_samples_projects: [:environment]) do From 261176975f37f44bd4aaa3d90c777f17331529ae Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 17 Apr 2024 10:23:16 +0100 Subject: [PATCH 302/344] removed some commented out code --- lib/tasks/seek_upgrades.rake | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index e0984c963f..f5ca9c97b6 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -125,8 +125,6 @@ namespace :seek do 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 #{affected_samples.count} extracted samples" ensure @@ -134,7 +132,6 @@ namespace :seek do Policy.set_callback :commit, :after, :queue_rdf_generation_job Permission.set_callback :commit, :after, :queue_update_auth_table Permission.set_callback :commit, :after, :queue_rdf_generation_job - end task(decouple_extracted_samples_projects: [:environment]) do From 043f6dde5a43edec0dcd7e959276630a070c8190 Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 17 Apr 2024 17:27:26 +0100 Subject: [PATCH 303/344] Ensure scraper bot can be added to existing projects --- lib/scrapers/util.rb | 4 +++- test/unit/scraper_test.rb | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/scrapers/util.rb b/lib/scrapers/util.rb index 4bd505a8b8..55f251f587 100644 --- a/lib/scrapers/util.rb +++ b/lib/scrapers/util.rb @@ -30,7 +30,9 @@ def self.bot_project(attributes) unless project project = Project.new(attributes) disable_authorization_checks { project.save! } - bot = bot_account + end + bot = bot_account + unless project.has_member?(bot) bot.add_to_project_and_institution(project, bot_institution) bot.is_project_administrator = true, project disable_authorization_checks { bot.save! } diff --git a/test/unit/scraper_test.rb b/test/unit/scraper_test.rb index 98c18baede..262d8eb6d9 100644 --- a/test/unit/scraper_test.rb +++ b/test/unit/scraper_test.rb @@ -29,9 +29,11 @@ def setup assert_difference('Project.count', 1) do assert_difference('Person.count', 1) do assert_difference('Institution.count', 1) do - project = Scrapers::Util.bot_project(title: 'iwc') - assert_equal 'iwc', project.title - assert_includes project.people, Scrapers::Util.bot_account + assert_difference('GroupMembership.count', 1) do + project = Scrapers::Util.bot_project(title: 'iwc') + assert_equal 'iwc', project.title + assert_includes project.people, Scrapers::Util.bot_account + end end end end @@ -39,8 +41,10 @@ def setup assert_no_difference('Project.count') do assert_no_difference('Person.count') do assert_no_difference('Institution.count') do - project = Scrapers::Util.bot_project(title: 'iwc') - assert_equal 'iwc', project.title + assert_no_difference('GroupMembership.count') do + project = Scrapers::Util.bot_project(title: 'iwc') + assert_equal 'iwc', project.title + end end end end @@ -48,9 +52,25 @@ def setup assert_difference('Project.count', 1) do assert_no_difference('Person.count') do assert_no_difference('Institution.count') do - project = Scrapers::Util.bot_project(title: 'Another Project') - assert_equal 'Another Project', project.title - assert_includes project.people, Scrapers::Util.bot_account + assert_difference('GroupMembership.count', 1) do + project = Scrapers::Util.bot_project(title: 'Another Project') + assert_equal 'Another Project', project.title + assert_includes project.people, Scrapers::Util.bot_account + end + end + end + end + + # Add to existing project + FactoryBot.create(:project, title: 'An existing project 123') + assert_no_difference('Project.count') do + assert_no_difference('Person.count') do + assert_no_difference('Institution.count') do + assert_difference('GroupMembership.count', 1) do + project = Scrapers::Util.bot_project(title: 'An existing project 123') + assert_equal 'An existing project 123', project.title + assert_includes project.people, Scrapers::Util.bot_account + end end end end From 4d7720745eb2220fb0360410c384c4180753461e Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Wed, 17 Apr 2024 16:35:51 +0100 Subject: [PATCH 304/344] Avoid 500 error when filtering by parent resource whose type is disabled --- app/controllers/application_controller.rb | 4 +++- test/functional/documents_controller_test.rb | 22 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b9c458fc5c..6d98f1920e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -578,9 +578,11 @@ def get_parent_resource parent_id_param = request.path_parameters.keys.detect { |k| k.to_s.end_with?('_id') } if parent_id_param parent_type = parent_id_param.to_s.chomp('_id') - parent_class = safe_class_lookup(parent_type.camelize) + parent_class = safe_class_lookup(parent_type.camelize, raise: false) if parent_class @parent_resource = parent_class.find(params[parent_id_param]) + else + error('Parent resource not recognized','Parent resource not recognized') end end end diff --git a/test/functional/documents_controller_test.rb b/test/functional/documents_controller_test.rb index f83fd9f291..a6161967a8 100644 --- a/test/functional/documents_controller_test.rb +++ b/test/functional/documents_controller_test.rb @@ -1460,6 +1460,28 @@ class DocumentsControllerTest < ActionController::TestCase end end + test 'get index filtered by parent' do + programme = FactoryBot.create(:programme) + project = FactoryBot.create(:project, programme: programme) + document = FactoryBot.create(:public_document, projects: [project]) + with_config_value(:programmes_enabled, true) do + get :index, params: { programme_id: programme.id } + assert_response :success + assert_includes assigns(:documents), document + end + end + + test 'do not get index filtered by parent if parent feature disabled' do + programme = FactoryBot.create(:programme) + project = FactoryBot.create(:project, programme: programme) + document = FactoryBot.create(:public_document, projects: [project]) + with_config_value(:programmes_enabled, false) do + get :index, params: { programme_id: programme.id } + assert_redirected_to root_path + assert flash[:error].include?('Parent resource not recognized') + end + end + private def valid_document From b23ad52ea149997a82ce0e9c10bd7c452dd00fdc Mon Sep 17 00:00:00 2001 From: Finn Bacall Date: Thu, 18 Apr 2024 13:16:30 +0100 Subject: [PATCH 305/344] Query GitHub API (or nf-core JSON) for "topics" to use as workflow tags Fixes #1840 --- lib/scrapers/github_scraper.rb | 16 +++ lib/scrapers/nfcore_scraper.rb | 6 +- test/integration/github_scraper_test.rb | 129 +++++++++++---------- test/integration/nfcore_scraper_test.rb | 1 + test/vcr_cassettes/github/fetch_topics.yml | 79 +++++++++++++ 5 files changed, 169 insertions(+), 62 deletions(-) create mode 100644 test/vcr_cassettes/github/fetch_topics.yml diff --git a/lib/scrapers/github_scraper.rb b/lib/scrapers/github_scraper.rb index 7ed04da4c5..4b8e6c42eb 100644 --- a/lib/scrapers/github_scraper.rb +++ b/lib/scrapers/github_scraper.rb @@ -105,6 +105,7 @@ def create_resource(repo, tag) workflow.policy = Policy.projects_policy(workflow.projects) workflow.policy.access_type = Policy::ACCESSIBLE workflow.source_link_url = repo.remote.chomp('.git') + workflow.tags = topics(repo) if wiz.next_step == :provide_metadata unless @debug if new_version @@ -153,6 +154,21 @@ def github RestClient::Resource.new('https://api.github.com', {}) end + def topics(repo) + remote = repo.remote + @_topics_cache ||= {} + return @_topics_cache[remote] if @_topics_cache[remote] + remote_uri = URI(remote) + if remote_uri.hostname.include?('github.com') + user, repo, *_ = remote_uri.path.split('/')[1..-1] + repo = repo.split('.').first + + @_topics_cache[remote] = JSON.parse(github["repos/#{user}/#{repo}/topics"].get(accept: 'application/vnd.github.mercy-preview+json').body)&.dig('names') || [] + else + @_topics_cache[remote] = [] + end + end + def latest_tag(repo) tag = `cd #{repo.git_base.path}/.. && git describe --tags --abbrev=0 remotes/origin/#{main_branch(repo)}`.chomp return nil unless $?.success? diff --git a/lib/scrapers/nfcore_scraper.rb b/lib/scrapers/nfcore_scraper.rb index abadedf0cb..a85ba76619 100644 --- a/lib/scrapers/nfcore_scraper.rb +++ b/lib/scrapers/nfcore_scraper.rb @@ -5,7 +5,7 @@ class NfcoreScraper < GithubScraper def list_repositories repos = JSON.parse(RestClient.get('https://nf-co.re/pipelines.json'))['remote_workflows'] - @nfcore_pipelines = {} # Store repo metadata from pipelines.json to fetch main branch name later + @nfcore_pipelines = {} # Store repo metadata from pipelines.json to fetch main branch name and topics later repos.each do |r| r['clone_url'] = "https://github.com/#{r['full_name']}.git" @nfcore_pipelines[r['clone_url']] = r @@ -18,6 +18,10 @@ def main_branch(repo) @nfcore_pipelines.dig(repo.remote, 'default_branch') || super end + def topics(repo) + @nfcore_pipelines.dig(repo.remote, 'topics') || [] + end + def workflow_wizard(repo, tag) GitWorkflowWizard.new(workflow_class: WorkflowClass.find_by_key('nextflow'), params: { diff --git a/test/integration/github_scraper_test.rb b/test/integration/github_scraper_test.rb index 85aefb1b1f..2ce2da5892 100644 --- a/test/integration/github_scraper_test.rb +++ b/test/integration/github_scraper_test.rb @@ -7,23 +7,26 @@ class GithubScraperTest < ActionDispatch::IntegrationTest bot = Scrapers::Util.bot_account scraper = Scrapers::GithubScraper.new('test-123', project, bot, main_branch: 'master', output: StringIO.new) - repos = [FactoryBot.create(:remote_workflow_ro_crate_repository, remote: 'https://not.real.url/repo.git')] + repos = [FactoryBot.create(:remote_workflow_ro_crate_repository, remote: 'https://github.com/crs4/sort-and-change-case-workflow.git')] - scraper.stub(:list_repositories, -> () { repos.map { |r| { 'clone_url' => r.remote } } }) do - scraper.stub(:clone_repositories, -> (_) { repos }) do - assert_difference('Workflow.count', 1) do - assert_difference('Workflow::Git::Version.count', 1) do - assert_difference('Git::Annotation.count', 1) do - assert_no_difference('Git::Repository.count') do - scraped = scraper.scrape - wf = scraped.first - assert_equal bot, wf.contributor - assert_equal [project], wf.projects - assert_equal 'sort-and-change-case', wf.title - assert_equal 'sort lines and change text to upper case', wf.description - assert_equal 'Apache-2.0', wf.license - assert_equal 'sort-and-change-case.ga', wf.main_workflow_path - assert_equal 'v0.02', wf.git_version.name + VCR.use_cassette('github/fetch_topics') do + scraper.stub(:list_repositories, -> () { repos.map { |r| { 'clone_url' => r.remote } } }) do + scraper.stub(:clone_repositories, -> (_) { repos }) do + assert_difference('Workflow.count', 1) do + assert_difference('Workflow::Git::Version.count', 1) do + assert_difference('Git::Annotation.count', 1) do + assert_no_difference('Git::Repository.count') do + scraped = scraper.scrape + wf = scraped.first + assert_equal bot, wf.contributor + assert_equal [project], wf.projects + assert_equal 'sort-and-change-case', wf.title + assert_equal 'sort lines and change text to upper case', wf.description + assert_equal 'Apache-2.0', wf.license + assert_equal 'sort-and-change-case.ga', wf.main_workflow_path + assert_equal 'v0.02', wf.git_version.name + assert_equal ['case', 'sort'], wf.tags.sort + end end end end @@ -37,36 +40,38 @@ class GithubScraperTest < ActionDispatch::IntegrationTest bot = Scrapers::Util.bot_account scraper = Scrapers::GithubScraper.new('test-123', project, bot, main_branch: 'master', output: StringIO.new) - repos = [FactoryBot.create(:remote_workflow_ro_crate_repository, remote: 'https://not.real.url/repo.git')] + repos = [FactoryBot.create(:remote_workflow_ro_crate_repository, remote: 'https://github.com/crs4/sort-and-change-case-workflow.git')] existing = FactoryBot.create(:ro_crate_git_workflow, - contributor: bot, - projects: [project], - source_link_url: 'https://not.real.url/repo', - git_version_attributes: { name: 'v0.01', - git_repository_id: repos.first.id, - ref: 'refs/tags/v0.01', - commit: 'a321b6e', - main_workflow_path: 'sort-and-change-case.ga', - mutable: false }) + contributor: bot, + projects: [project], + source_link_url: 'https://github.com/crs4/sort-and-change-case-workflow', + git_version_attributes: { name: 'v0.01', + git_repository_id: repos.first.id, + ref: 'refs/tags/v0.01', + commit: 'a321b6e', + main_workflow_path: 'sort-and-change-case.ga', + mutable: false }) - scraper.stub(:list_repositories, -> () { repos.map { |r| { 'clone_url' => r.remote } } }) do - scraper.stub(:clone_repositories, -> (_) { repos }) do - assert_no_difference('Workflow.count') do - assert_difference('Workflow::Git::Version.count', 1) do - assert_difference('Git::Annotation.count', 1) do - assert_no_difference('Git::Repository.count') do - scraped = scraper.scrape - wf = scraped.first - assert_equal 2, existing.reload.git_versions.count - assert_equal existing, wf - assert_equal bot, wf.contributor - assert_equal [project], wf.projects - assert_equal 'sort-and-change-case', wf.title - assert_equal 'sort lines and change text to upper case', wf.description - assert_equal 'Apache-2.0', wf.license - assert_equal 'sort-and-change-case.ga', wf.main_workflow_path - assert_equal 'v0.02', wf.git_version.name + VCR.use_cassette('github/fetch_topics') do + scraper.stub(:list_repositories, -> () { repos.map { |r| { 'clone_url' => r.remote } } }) do + scraper.stub(:clone_repositories, -> (_) { repos }) do + assert_no_difference('Workflow.count') do + assert_difference('Workflow::Git::Version.count', 1) do + assert_difference('Git::Annotation.count', 1) do + assert_no_difference('Git::Repository.count') do + scraped = scraper.scrape + wf = scraped.first + assert_equal 2, existing.reload.git_versions.count + assert_equal existing, wf + assert_equal bot, wf.contributor + assert_equal [project], wf.projects + assert_equal 'sort-and-change-case', wf.title + assert_equal 'sort lines and change text to upper case', wf.description + assert_equal 'Apache-2.0', wf.license + assert_equal 'sort-and-change-case.ga', wf.main_workflow_path + assert_equal 'v0.02', wf.git_version.name + end end end end @@ -80,27 +85,29 @@ class GithubScraperTest < ActionDispatch::IntegrationTest bot = Scrapers::Util.bot_account scraper = Scrapers::GithubScraper.new('test-123', project, bot, main_branch: 'master', output: StringIO.new) - repos = [FactoryBot.create(:remote_workflow_ro_crate_repository, remote: 'https://not.real.url/repo.git')] + repos = [FactoryBot.create(:remote_workflow_ro_crate_repository, remote: 'https://github.com/crs4/sort-and-change-case-workflow.git')] existing = FactoryBot.create(:ro_crate_git_workflow, - contributor: bot, - projects: [project], - source_link_url: 'https://not.real.url/repo', - git_version_attributes: { name: 'v0.02', - git_repository_id: repos.first.id, - ref: 'refs/tags/v0.02', - commit: '20eabdc', - main_workflow_path: 'sort-and-change-case.ga', - mutable: false }) + contributor: bot, + projects: [project], + source_link_url: 'https://github.com/crs4/sort-and-change-case-workflow', + git_version_attributes: { name: 'v0.02', + git_repository_id: repos.first.id, + ref: 'refs/tags/v0.02', + commit: '20eabdc', + main_workflow_path: 'sort-and-change-case.ga', + mutable: false }) - scraper.stub(:list_repositories, -> () { repos.map { |r| { 'clone_url' => r.remote } } }) do - scraper.stub(:clone_repositories, -> (_) { repos }) do - assert_no_difference('Workflow.count') do - assert_no_difference('Workflow::Git::Version.count') do - assert_no_difference('Git::Annotation.count') do - assert_no_difference('Git::Repository.count') do - scraped = scraper.scrape - assert scraped.empty? + VCR.use_cassette('github/fetch_topics') do + scraper.stub(:list_repositories, -> () { repos.map { |r| { 'clone_url' => r.remote } } }) do + scraper.stub(:clone_repositories, -> (_) { repos }) do + assert_no_difference('Workflow.count') do + assert_no_difference('Workflow::Git::Version.count') do + assert_no_difference('Git::Annotation.count') do + assert_no_difference('Git::Repository.count') do + scraped = scraper.scrape + assert scraped.empty? + end end end end diff --git a/test/integration/nfcore_scraper_test.rb b/test/integration/nfcore_scraper_test.rb index 1be7345e8c..5b5321aa73 100644 --- a/test/integration/nfcore_scraper_test.rb +++ b/test/integration/nfcore_scraper_test.rb @@ -29,6 +29,7 @@ class NfcoreScraperTest < ActionDispatch::IntegrationTest assert_equal 'MIT', wf.license assert_equal 'nextflow.config', wf.main_workflow_path assert_equal '3.0', wf.git_version.name + assert_equal %w[rna rna-seq], wf.tags.sort end end end diff --git a/test/vcr_cassettes/github/fetch_topics.yml b/test/vcr_cassettes/github/fetch_topics.yml new file mode 100644 index 0000000000..2ee2a886db --- /dev/null +++ b/test/vcr_cassettes/github/fetch_topics.yml @@ -0,0 +1,79 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/crs4/sort-and-change-case-workflow/topics + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/vnd.github.mercy-preview+json + User-Agent: + - rest-client/2.1.0 (linux x86_64) ruby/3.1.4p223 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - api.github.com + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Thu, 18 Apr 2024 12:11:53 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '25' + Cache-Control: + - public, max-age=60, s-maxage=60 + Vary: + - Accept, Accept-Encoding, Accept, X-Requested-With + Etag: + - '"4312be9f94d4167301a1aefc48875953a71d3a73d1f113765668ab9b0a8e962e"' + X-Github-Media-Type: + - github.mercy-preview; format=json + X-Github-Api-Version-Selected: + - '2022-11-28' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - '0' + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Ratelimit-Limit: + - '60' + X-Ratelimit-Remaining: + - '48' + X-Ratelimit-Reset: + - '1713445012' + X-Ratelimit-Resource: + - core + X-Ratelimit-Used: + - '12' + Accept-Ranges: + - bytes + X-Github-Request-Id: + - 8624:33B0C5:33D1224:34EAAEF:66210E09 + body: + encoding: UTF-8 + string: '{"names":["sort","case"]}' + http_version: + recorded_at: Thu, 18 Apr 2024 12:11:53 GMT +recorded_with: VCR 2.9.3 From c65af3714d4fc2342339a9ec0b01d96a6df6cde0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 19 Apr 2024 09:55:39 +0200 Subject: [PATCH 306/344] Prevent error when sample_attribute_type is not set. --- app/models/template_attribute.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/template_attribute.rb b/app/models/template_attribute.rb index 3e12de3e88..74af563484 100644 --- a/app/models/template_attribute.rb +++ b/app/models/template_attribute.rb @@ -15,7 +15,7 @@ class TemplateAttribute < ApplicationRecord before_save :default_pos def controlled_vocab? - sample_attribute_type.base_type == Seek::Samples::BaseType::CV + sample_attribute_type&.base_type == Seek::Samples::BaseType::CV end def allow_isa_tag_change? From 5e9f50fac57cf9c8a2beab4f3e7ad4592efe4afa Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 19 Apr 2024 10:12:39 +0200 Subject: [PATCH 307/344] Upsert template attributes instead of replacing them --- ...017_minimal_starter_isa_templates.seeds.rb | 258 ++++++++++-------- 1 file changed, 145 insertions(+), 113 deletions(-) diff --git a/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index e78e477916..8413acf657 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -1,21 +1,38 @@ +# This code seeds the starter templates in the database. +# To keep the link between parent and child template attributes, do not change the attribute titles !!! +# Changing the titles will replace the existing attributes with new ones instead of updating them. + +# Custom upsert function for template attributes +# This will create a new template attribute if it does not exist, or update the existing one. +# This also assumes that a template_attribute is considered unique by its title and template_id. +# All other fields can be updated. +def upsert_template_attribute(title, template_id, **fields) + template_attribute = TemplateAttribute.find_or_create_by(title:, template_id:) + template_attribute.update(fields) + template_attribute +end + # Source - ISA minimal starter template source_template = Template.find_or_initialize_by(title: 'Source - ISA minimal starter template', level: 'study source', group: 'ISA minimal starter') source_temp_attributes = [] -source_temp_attributes << TemplateAttribute.new(title: 'Source Name', - description: 'Sources are considered as the starting biological material used in a study.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: true, - required: true, - isa_tag: IsaTag.find_by(title: 'source')) - -source_temp_attributes << TemplateAttribute.new(title: 'Source Characteristic 1', - description: 'A characteristic of the source.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'source_characteristic')) +source_temp_attributes << upsert_template_attribute('Source Name', + source_template.id, + description: 'Sources are considered as the starting biological material used in a study.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: true, + required: true, + isa_tag: IsaTag.find_by(title: 'source') +) + +source_temp_attributes << upsert_template_attribute('Source Characteristic 1', + source_template.id, + description: 'A characteristic of the source.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'source_characteristic')) disable_authorization_checks do source_template.update(group_order: 1, @@ -36,40 +53,45 @@ group: 'ISA minimal starter') 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 List'), - is_title: false, - required: true) - -sample_temp_attributes << TemplateAttribute.new(title: 'Name of a protocol with samples as outputs', - description: 'Type of experimental step that generates samples as outputs from the study sources.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'protocol')) - -sample_temp_attributes << TemplateAttribute.new(title: 'Name of protocol parameter 1', - description: 'A parameter for the protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'parameter_value')) - -sample_temp_attributes << TemplateAttribute.new(title: 'Sample Name', - description: 'Samples are considered as biological material sampled from sources and used in the study.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: true, - required: true, - isa_tag: IsaTag.find_by(title: 'sample')) - -sample_temp_attributes << TemplateAttribute.new(title: 'Sample Characteristic 1', - description: 'A characteristic of the sample.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - sample_controlled_vocab: nil, - isa_tag: IsaTag.find_by(title: 'sample_characteristic')) +sample_temp_attributes << upsert_template_attribute('Input', + sample_template.id, + description: 'Registered Samples in the platform used as input for this protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample List'), + is_title: false, + required: true) + +sample_temp_attributes << upsert_template_attribute('Name of a protocol with samples as outputs', + sample_template.id, + description: 'Type of experimental step that generates samples as outputs from the study sources.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'protocol')) + +sample_temp_attributes << upsert_template_attribute('Name of protocol parameter 1', + sample_template.id, + description: 'A parameter for the protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'parameter_value')) + +sample_temp_attributes << upsert_template_attribute('Sample Name', + sample_template.id, + description: 'Samples are considered as biological material sampled from sources and used in the study.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: true, + required: true, + isa_tag: IsaTag.find_by(title: 'sample')) + +sample_temp_attributes << upsert_template_attribute('Sample Characteristic 1', + sample_template.id, + description: 'A characteristic of the sample.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + sample_controlled_vocab: nil, + isa_tag: IsaTag.find_by(title: 'sample_characteristic')) disable_authorization_checks do sample_template.update(group_order: 2, @@ -92,39 +114,44 @@ level: 'assay - material', group: 'ISA minimal starter') 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 List'), - is_title: false, - required: true) - -material_temp_attributes << TemplateAttribute.new(title: 'Name of a protocol with material output', - description: 'Type of assay or experimental step performed that generates a material output.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'protocol')) - -material_temp_attributes << TemplateAttribute.new(title: 'Name of protocol parameter 1', - description: 'A parameter for the protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'parameter_value')) - -material_temp_attributes << TemplateAttribute.new(title: 'Output material Name', - description: 'Name of the major material output resulting from the application of the protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: true, - required: true, - isa_tag: IsaTag.find_by(title: 'other_material')) - -material_temp_attributes << TemplateAttribute.new(title: 'Output material characteristic 1', - description: 'Characteristic 1 of the output material.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'other_material_characteristic')) +material_temp_attributes << upsert_template_attribute('Input', + material_template.id, + description: 'Registered Samples in the platform used as input for this protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample List'), + is_title: false, + required: true) + +material_temp_attributes << upsert_template_attribute('Name of a protocol with material output', + material_template.id, + description: 'Type of assay or experimental step performed that generates a material output.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'protocol')) + +material_temp_attributes << upsert_template_attribute('Name of protocol parameter 1', + material_template.id, + description: 'A parameter for the protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'parameter_value')) + +material_temp_attributes << upsert_template_attribute('Output material Name', + material_template.id, + description: 'Name of the major material output resulting from the application of the protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: true, + required: true, + isa_tag: IsaTag.find_by(title: 'other_material')) + +material_temp_attributes << upsert_template_attribute('Output material characteristic 1', + material_template.id, + description: 'Characteristic 1 of the output material.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'other_material_characteristic')) disable_authorization_checks do material_template.update(group_order: 3, @@ -148,39 +175,44 @@ level: 'assay - data file', group: 'ISA minimal starter') 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 List'), - is_title: false, - required: true) - -data_file_temp_attributes << TemplateAttribute.new(title: 'Name of a protocol with data file output', - description: 'Type of assay or experimental step performed that generates a data file output.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'protocol')) - -data_file_temp_attributes << TemplateAttribute.new(title: 'Name of protocol parameter 1', - description: 'A parameter for the protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'data_file_comment')) - -data_file_temp_attributes << TemplateAttribute.new(title: 'Data file Name', - description: 'Name of the major data file output resulting from the application of the protocol.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: true, - required: true, - isa_tag: IsaTag.find_by(title: 'data_file')) - -data_file_temp_attributes << TemplateAttribute.new(title: 'Data file characteristic 1', - description: 'Characteristic 1 of the data file output.', - sample_attribute_type: SampleAttributeType.find_by(title: 'String'), - is_title: false, - required: true, - isa_tag: IsaTag.find_by(title: 'data_file_comment')) +data_file_temp_attributes << upsert_template_attribute('Input', + data_file_template.id, + description: 'Registered Samples in the platform used as input for this protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'Registered Sample List'), + is_title: false, + required: true) + +data_file_temp_attributes << upsert_template_attribute('Name of a protocol with data file output', + data_file_template.id, + description: 'Type of assay or experimental step performed that generates a data file output.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'protocol')) + +data_file_temp_attributes << upsert_template_attribute('Name of protocol parameter 1', + data_file_template.id, + description: 'A parameter for the protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'data_file_comment')) + +data_file_temp_attributes << upsert_template_attribute('Data file Name', + data_file_template.id, + description: 'Name of the major data file output resulting from the application of the protocol.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: true, + required: true, + isa_tag: IsaTag.find_by(title: 'data_file')) + +data_file_temp_attributes << upsert_template_attribute('Data file characteristic 1', + data_file_template.id, + description: 'Characteristic 1 of the data file output.', + sample_attribute_type: SampleAttributeType.find_by(title: 'String'), + is_title: false, + required: true, + isa_tag: IsaTag.find_by(title: 'data_file_comment')) disable_authorization_checks do data_file_template.update(group_order: 4, From 4363ce34edf23e4639c240d5fed6a09b3a1b2b38 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 18 Apr 2024 14:05:38 +0100 Subject: [PATCH 308/344] hide MIAPPE Study batch upload button is the type is disabled #1836 --- app/helpers/studies_helper.rb | 2 +- test/unit/helpers/studies_helper_test.rb | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/helpers/studies_helper.rb b/app/helpers/studies_helper.rb index f9ec4ea69a..fa7ea53613 100644 --- a/app/helpers/studies_helper.rb +++ b/app/helpers/studies_helper.rb @@ -24,6 +24,6 @@ def authorised_studies(projects = nil) end def show_batch_miappe_button? - ExtendedMetadataType.where(supported_type: 'Study', title: ExtendedMetadataType::MIAPPE_TITLE).any? + ExtendedMetadataType.where(supported_type: 'Study', title: ExtendedMetadataType::MIAPPE_TITLE, enabled: true).any? end end diff --git a/test/unit/helpers/studies_helper_test.rb b/test/unit/helpers/studies_helper_test.rb index 45083f7f00..20dcdf0029 100644 --- a/test/unit/helpers/studies_helper_test.rb +++ b/test/unit/helpers/studies_helper_test.rb @@ -10,7 +10,10 @@ class StudiesHelperTest < ActionView::TestCase FactoryBot.create(:study_extended_metadata_type_for_MIAPPE, title: 'Not MIAPPE') refute show_batch_miappe_button? - FactoryBot.create(:study_extended_metadata_type_for_MIAPPE, title: 'MIAPPE metadata v1.1') + type = FactoryBot.create(:study_extended_metadata_type_for_MIAPPE, title: 'MIAPPE metadata v1.1') assert show_batch_miappe_button? + + type.update_column(:enabled, false) + refute show_batch_miappe_button? end end From 3082c5930ab5324f5decb32d8f3bdb402aafd4ea Mon Sep 17 00:00:00 2001 From: whomingbird Date: Tue, 16 Apr 2024 11:27:03 +0100 Subject: [PATCH 309/344] add the `enabled` attribute to extended metadata type serializer --- app/serializers/extended_metadata_type_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/extended_metadata_type_serializer.rb b/app/serializers/extended_metadata_type_serializer.rb index f4c423ff91..dbce96999b 100644 --- a/app/serializers/extended_metadata_type_serializer.rb +++ b/app/serializers/extended_metadata_type_serializer.rb @@ -1,5 +1,5 @@ class ExtendedMetadataTypeSerializer < BaseSerializer - attributes :title, :supported_type + attributes :title, :supported_type,:enabled attribute :extended_metadata_attributes def extended_metadata_attributes From 5966598e1a4eec2d429cf1f538c40dd016c79683 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 16 Apr 2024 11:36:28 +0100 Subject: [PATCH 310/344] updated missing extended_attributes definition for supported types in api schema #1433 --- public/api/definitions/_schemas.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index 74b9601ee9..d7bfa1838e 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -473,6 +473,8 @@ collectionAttributes: $ref: "#/components/schemas/assetsCreatorsList" discussion_links: $ref: "#/components/schemas/assetLinkList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - license @@ -1128,6 +1130,8 @@ assayPost: $ref: "#/components/schemas/policy" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - assay_class @@ -1218,6 +1222,8 @@ assayPatch: $ref: "#/components/schemas/policy" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -1868,6 +1874,8 @@ investigationPost: $ref: "#/components/schemas/policy" discussion_links: $ref: "#/components/schemas/assetLinkPostList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title additionalProperties: false @@ -1918,6 +1926,8 @@ investigationPatch: $ref: "#/components/schemas/policy" discussion_links: $ref: "#/components/schemas/assetLinkPatchList" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" additionalProperties: false relationships: type: object @@ -3230,6 +3240,8 @@ assayResponse: $ref: "#/components/schemas/assetLinkList" position: $ref: "#/components/schemas/nullableInteger" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title - assay_class @@ -3330,8 +3342,6 @@ collectionResponse: $ref: "#/components/schemas/multipleReferences" items: $ref: "#/components/schemas/multipleReferences" - extended_attributes: - $ref: "#/components/schemas/extendedMetadata" required: - creators - submitter @@ -3810,6 +3820,8 @@ investigationResponse: $ref: "#/components/schemas/assetLinkList" position: $ref: "#/components/schemas/nullableInteger" + extended_attributes: + $ref: "#/components/schemas/extendedMetadata" required: - title additionalProperties: false From aae13ed2bc11d9f838c652c8f495248728427bd4 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Tue, 16 Apr 2024 11:37:17 +0100 Subject: [PATCH 311/344] #1792 Add a read API to fetch all available extended metadata types that support SEEK resources: ISA items, Collection, DataFile, Document, Event, Model,Presentation,Sop, Project. --- .../extended_metadata_types_controller.rb | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/controllers/extended_metadata_types_controller.rb b/app/controllers/extended_metadata_types_controller.rb index e775bd424c..595e0fa410 100644 --- a/app/controllers/extended_metadata_types_controller.rb +++ b/app/controllers/extended_metadata_types_controller.rb @@ -1,9 +1,8 @@ class ExtendedMetadataTypesController < ApplicationController - - + respond_to :json before_action :is_user_admin_auth, except: [:form_fields, :show] before_action :find_requested_item, only: [:administer_update, :show] - + include Seek::IndexPager # generated for form, to display fields for selected metadata type def form_fields @@ -30,6 +29,21 @@ def show end end + def index + @extended_metadata_types = ExtendedMetadataType.all.reject { |type| type.supported_type == 'ExtendedMetadata' } + respond_to do |format| + format.json do + render json: @extended_metadata_types, + each_serializer: SkeletonSerializer, + links: json_api_links, + meta: { + base_url: Seek::Config.site_base_host, + api_version: ActiveModel::Serializer.config.api_version + } + end + end + end + def administer_update @extended_metadata_type.update(extended_metadata_type_params) unless @extended_metadata_type.save From f2384d905d27f46ba38ae21cb01d1a5d844d8b57 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 16 Apr 2024 14:20:27 +0100 Subject: [PATCH 312/344] some text about extended metadata #1433 --- public/api/descriptions/api.md | 32 +++++++++++++++++++++++ public/api/descriptions/assays.md | 2 +- public/api/descriptions/collections.md | 2 ++ public/api/descriptions/dataFiles.md | 2 +- public/api/descriptions/documents.md | 1 + public/api/descriptions/events.md | 4 ++- public/api/descriptions/investigations.md | 2 +- public/api/descriptions/models.md | 2 ++ public/api/descriptions/presentations.md | 4 ++- public/api/descriptions/projects.md | 1 + public/api/descriptions/sops.md | 1 + public/api/descriptions/studies.md | 2 +- 12 files changed, 49 insertions(+), 6 deletions(-) diff --git a/public/api/descriptions/api.md b/public/api/descriptions/api.md index 0138724b85..5b0812d2bb 100644 --- a/public/api/descriptions/api.md +++ b/public/api/descriptions/api.md @@ -90,3 +90,35 @@ A placeholder can then be satisfied by uploading a file to the location URI. For may be satisfied by uploading a file to http://fairdomhub.org/data_files/57/content_blobs/313 using the uploadAssetContent operation The content of a resource may be downloaded by first *reading* the resource and then *downloading* the ContentBlobs from their URI. + +## Extended Metadata + +Some types support [Extended Metadata](https://docs.seek4science.org/tech/extended-metadata), which allows additional attributes to be defined according to an Extended Metadata Type. + +Types currently supported are **Investigation**, **Study**, **Assay**, +**DataFile**, **SOP**, **Presentation**, +**Document**, **Model**, +**Event**, **Collection**, **Project** + +The response and requests for each of these types include an additional optional attribute _extended_attributes_ which describes + +* _extended_metadata_type_id_ - the id of the extended metadata type which can be used to find more details about what its attributes are. +* _attribute_map_ - which is a map of key / value pairs where the key is the attribute name + + +For example, a Study may have extended metadata, defined by an Extended Metadata Type with id 12, that has attributes for +age, name, and data_of_birth. These could be shown, within its attributes, as: + +``` +"extended_attributes": { + "extended_metadata_type_id": "12", + "attribute_map": { + "age": 44, + "name": "Fred Bloggs", + "date_of_birth": "2024-01-01" + } +} +``` + +If you wish to create or update a study to make use of this extended metadata, the request payload would be described the same. +Upon creation or update there would be a validation check that the attributes are valid. \ No newline at end of file diff --git a/public/api/descriptions/assays.md b/public/api/descriptions/assays.md index f1e1ae40df..6847eb61a2 100644 --- a/public/api/descriptions/assays.md +++ b/public/api/descriptions/assays.md @@ -23,7 +23,7 @@ A response for an **Assay** such as that for a **Create**, * A singleton reference to the **Investigation** which the **Assay** is part of * References to the **Projects** that indirectly contain the **Assay** - +**Assays** have support for [Extended Metadata](/api#section/Extended-Metadata) diff --git a/public/api/descriptions/collections.md b/public/api/descriptions/collections.md index c6b8dde8ca..f40482c29f 100644 --- a/public/api/descriptions/collections.md +++ b/public/api/descriptions/collections.md @@ -5,3 +5,5 @@ The collections API can be used to create an empty collections, and add metadata Assets can be added, removed, re-ordered and annotated (in the context of the collection) via the **Collection Items** API. A **Collection Item** is a resource that links an asset to a collection. + +**Collections** have support for [Extended Metadata](/api#section/Extended-Metadata) \ No newline at end of file diff --git a/public/api/descriptions/dataFiles.md b/public/api/descriptions/dataFiles.md index 2771eabab4..fb2dcb08af 100644 --- a/public/api/descriptions/dataFiles.md +++ b/public/api/descriptions/dataFiles.md @@ -27,5 +27,5 @@ A response for a **DataFile** such as that for a **Create****Investigations** associated with the **DataFile** * References to the **Studies** associated with the **DataFile** - +**DataFiles** have support for [Extended Metadata](/api#section/Extended-Metadata) diff --git a/public/api/descriptions/documents.md b/public/api/descriptions/documents.md index 64cf82b519..34cd23c2e4 100644 --- a/public/api/descriptions/documents.md +++ b/public/api/descriptions/documents.md @@ -26,6 +26,7 @@ A response for a **Document** such as that for a **Create****Studies** associated with the **Document** * References to the **Publications** associated with the **Document** +**Documents** have support for [Extended Metadata](/api#section/Extended-Metadata) diff --git a/public/api/descriptions/events.md b/public/api/descriptions/events.md index f252713489..e321ebd421 100644 --- a/public/api/descriptions/events.md +++ b/public/api/descriptions/events.md @@ -18,4 +18,6 @@ An **Event** has the following associated information: A response for an **Event** such as that for a **Create**, **Read** or **Update** includes the additional information -* Reference to the **Person** who submitted the **Event** \ No newline at end of file +* Reference to the **Person** who submitted the **Event** + +**Events** have support for [Extended Metadata](/api#section/Extended-Metadata) \ No newline at end of file diff --git a/public/api/descriptions/investigations.md b/public/api/descriptions/investigations.md index ea1df6dcb0..6cb157cf36 100644 --- a/public/api/descriptions/investigations.md +++ b/public/api/descriptions/investigations.md @@ -21,4 +21,4 @@ A response for an **Investigation** such as that for a **Creat * References to **Sops** that belong to the **Investigation** * References to **Publications** that belong to the **Investigation** - +**Investigations** have support for [Extended Metadata](/api#section/Extended-Metadata) diff --git a/public/api/descriptions/models.md b/public/api/descriptions/models.md index 0c743885f4..e25bdb9b41 100644 --- a/public/api/descriptions/models.md +++ b/public/api/descriptions/models.md @@ -28,3 +28,5 @@ A response for a **Model** such as that for a **Create**, * A reference to the **Person** who submitted the **Model** * References to the **Investigations** associated with the **Model** * References to the **Studies** associated with the **Model** + +**Models** have support for [Extended Metadata](/api#section/Extended-Metadata) \ No newline at end of file diff --git a/public/api/descriptions/presentations.md b/public/api/descriptions/presentations.md index e6babfddb6..1b25ef58d4 100644 --- a/public/api/descriptions/presentations.md +++ b/public/api/descriptions/presentations.md @@ -25,4 +25,6 @@ A response for a **Presentation** such as that for a **Create* * ** The last time the Presentation was updated** * A reference to the **Person** who submitted the **Presentation** * References to the **Investigations** associated with the **Presentation** -* References to the **Studies** associated with the **Presentation** \ No newline at end of file +* References to the **Studies** associated with the **Presentation** + +**Presentations** have support for [Extended Metadata](/api#section/Extended-Metadata) \ No newline at end of file diff --git a/public/api/descriptions/projects.md b/public/api/descriptions/projects.md index 1ebf4984af..777c0b0581 100644 --- a/public/api/descriptions/projects.md +++ b/public/api/descriptions/projects.md @@ -30,4 +30,5 @@ A response for a **Project** such as that for a **Create** * References to **Presentations** that belong to the **Project** * References to **Events** that are held by or attended by the **Project** +**Projects** have support for [Extended Metadata](/api#section/Extended-Metadata) diff --git a/public/api/descriptions/sops.md b/public/api/descriptions/sops.md index cfd2b0454e..2f7c94156d 100644 --- a/public/api/descriptions/sops.md +++ b/public/api/descriptions/sops.md @@ -26,3 +26,4 @@ A response for a **SOP** such as that for a **Create**, **Studies** associated with the **SOP** * References to the **Publications** associated with the **SOP** +**SOPs** have support for [Extended Metadata](/api#section/Extended-Metadata) diff --git a/public/api/descriptions/studies.md b/public/api/descriptions/studies.md index 75858c3697..aebb436ab3 100644 --- a/public/api/descriptions/studies.md +++ b/public/api/descriptions/studies.md @@ -23,7 +23,7 @@ A response for a **Study** such as that for a **Create**, * References to **Sops** that belong to the **Study** * References to **Publications** that belong to the **Study** - +**Studies** have support for [Extended Metadata](/api#section/Extended-Metadata) From 82ecfb5e19422b679f6e88340f470ddd26f29ef1 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Tue, 16 Apr 2024 15:50:53 +0100 Subject: [PATCH 313/344] add read api test for extended metadata type --- public/api/definitions/_schemas.yml | 1 + .../extendedMetadataTypeResponse.json | 71 +++++++++++++++++++ .../extendedMetadataTypesResponse.json | 34 +++++++++ test/factories/extended_metadata_types.rb | 18 +++++ .../get_max_extended_metadata_type.json.erb | 71 +++++++++++++++++++ .../get_min_extended_metadata_type.json.erb | 39 ++++++++++ .../api/extended_metadata_type_api_test.rb | 9 +++ 7 files changed, 243 insertions(+) create mode 100644 public/api/examples/extendedMetadataTypeResponse.json create mode 100644 public/api/examples/extendedMetadataTypesResponse.json create mode 100644 test/fixtures/json/responses/get_max_extended_metadata_type.json.erb create mode 100644 test/fixtures/json/responses/get_min_extended_metadata_type.json.erb create mode 100644 test/integration/api/extended_metadata_type_api_test.rb diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index d7bfa1838e..5001f08f01 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -869,6 +869,7 @@ anyType: - sops - studies - workflows + - extended_metadata_types assetType: type: string minLength: 1 diff --git a/public/api/examples/extendedMetadataTypeResponse.json b/public/api/examples/extendedMetadataTypeResponse.json new file mode 100644 index 0000000000..23f238bd37 --- /dev/null +++ b/public/api/examples/extendedMetadataTypeResponse.json @@ -0,0 +1,71 @@ +{ + "data": { + "id": "233", + "type": "extended_metadata_types", + "attributes": { + "title": "A Max Extended Metadata Type", + "supported_type": "Investigation", + "enabled": true, + "extended_metadata_attributes": [ + { + "id": "502", + "title": "age", + "label": "Age", + "description": null, + "sample_attribute_type": { + "id": "539", + "title": "Integer attribute type 4", + "base_type": "Integer", + "regexp": ".*" + }, + "required": false, + "pos": null, + "sample_controlled_vocab_id": null, + "linked_extended_metadata_type_id": null + }, + { + "id": "503", + "title": "name", + "label": "Name", + "description": null, + "sample_attribute_type": { + "id": "540", + "title": "String attribute type 7", + "base_type": "String", + "regexp": ".*" + }, + "required": true, + "pos": null, + "sample_controlled_vocab_id": null, + "linked_extended_metadata_type_id": null + }, + { + "id": "504", + "title": "date", + "label": "Date", + "description": null, + "sample_attribute_type": { + "id": "541", + "title": "DateTime attribute type 4", + "base_type": "DateTime", + "regexp": ".*" + }, + "required": false, + "pos": null, + "sample_controlled_vocab_id": null, + "linked_extended_metadata_type_id": null + } + ] + }, + "links": { + "self": "/extended_metadata_types/233" + }, + "meta": { + "api_version": "0.3", + "base_url": "http://localhost:3000" + } + }, + "jsonapi": { + "version": "1.0" + } +} \ No newline at end of file diff --git a/public/api/examples/extendedMetadataTypesResponse.json b/public/api/examples/extendedMetadataTypesResponse.json new file mode 100644 index 0000000000..08b883ad75 --- /dev/null +++ b/public/api/examples/extendedMetadataTypesResponse.json @@ -0,0 +1,34 @@ +{ + "data": [ + { + "id": "231", + "type": "extended_metadata_types", + "attributes": { + "title": "A Min Extended Metadata Type" + }, + "links": { + "self": "/extended_metadata_types/231" + } + }, + { + "id": "232", + "type": "extended_metadata_types", + "attributes": { + "title": "A Max Extended Metadata Type" + }, + "links": { + "self": "/extended_metadata_types/232" + } + } + ], + "jsonapi": { + "version": "1.0" + }, + "links": { + "self": "/extended_metadata_types" + }, + "meta": { + "base_url": "http://localhost:3000", + "api_version": "0.3" + } +} \ No newline at end of file diff --git a/test/factories/extended_metadata_types.rb b/test/factories/extended_metadata_types.rb index 40cf384114..9118877bdb 100644 --- a/test/factories/extended_metadata_types.rb +++ b/test/factories/extended_metadata_types.rb @@ -21,6 +21,24 @@ association :sample_attribute_type, factory: :datetime_sample_attribute_type end + factory(:min_extended_metadata_type,class: ExtendedMetadataType) do + title { 'A Min Extended Metadata Type' } + supported_type { 'Investigation' } + after(:build) do |a| + a.extended_metadata_attributes << FactoryBot.create(:name_extended_metadata_attribute, required: true) + end + end + + factory(:max_extended_metadata_type,class: ExtendedMetadataType) do + title { 'A Max Extended Metadata Type' } + supported_type { 'Investigation' } + after(:build) do |a| + a.extended_metadata_attributes << FactoryBot.create(:age_extended_metadata_attribute) + a.extended_metadata_attributes << FactoryBot.create(:name_extended_metadata_attribute, required: true) + a.extended_metadata_attributes << FactoryBot.create(:datetime_extended_metadata_attribute) + end + end + factory(:study_extended_metadata_type_with_cv_and_cv_list_type, class: ExtendedMetadataType) do title { 'study extended metadata type with and list attributes' } supported_type { 'Study' } diff --git a/test/fixtures/json/responses/get_max_extended_metadata_type.json.erb b/test/fixtures/json/responses/get_max_extended_metadata_type.json.erb new file mode 100644 index 0000000000..96dd2c483d --- /dev/null +++ b/test/fixtures/json/responses/get_max_extended_metadata_type.json.erb @@ -0,0 +1,71 @@ +{ + "data": { + "id": "<%= res.id %>", + "type": "extended_metadata_types", + "attributes": { + "title": "A Max Extended Metadata Type", + "supported_type": "Investigation", + "enabled": true, + "extended_metadata_attributes": [ + { + "id": "<%= res.extended_metadata_attributes.first.id %>", + "title": "age", + "label": "Age", + "description": null, + "sample_attribute_type": { + "id": "<%= res.extended_metadata_attributes.first.sample_attribute_type_id %>", + "title": "<%= res.extended_metadata_attributes.first.sample_attribute_type.title %>", + "base_type": "Integer", + "regexp": ".*" + }, + "required":false, + "pos":null, + "sample_controlled_vocab_id":null, + "linked_extended_metadata_type_id":null + }, + { + "id": "<%= res.extended_metadata_attributes.second.id %>", + "title": "name", + "label": "Name", + "description": null, + "sample_attribute_type": { + "id": "<%= res.extended_metadata_attributes.second.sample_attribute_type_id %>", + "title": "<%= res.extended_metadata_attributes.second.sample_attribute_type.title %>", + "base_type": "String", + "regexp": ".*" + }, + "required":true, + "pos":null, + "sample_controlled_vocab_id":null, + "linked_extended_metadata_type_id":null + }, + { + "id": "<%= res.extended_metadata_attributes.last.id %>", + "title": "date", + "label": "Date", + "description": null, + "sample_attribute_type": { + "id": "<%= res.extended_metadata_attributes.last.sample_attribute_type_id %>", + "title": "<%= res.extended_metadata_attributes.last.sample_attribute_type.title %>", + "base_type": "DateTime", + "regexp": ".*" + }, + "required":false, + "pos":null, + "sample_controlled_vocab_id":null, + "linked_extended_metadata_type_id":null + } + ] + }, + "links": { + "self": "/extended_metadata_types/<%= res.id %>" + }, + "meta": { + "api_version": "0.3", + "base_url": "http://localhost:3000" + } + }, + "jsonapi": { + "version": "1.0" + } +} \ No newline at end of file diff --git a/test/fixtures/json/responses/get_min_extended_metadata_type.json.erb b/test/fixtures/json/responses/get_min_extended_metadata_type.json.erb new file mode 100644 index 0000000000..730941160c --- /dev/null +++ b/test/fixtures/json/responses/get_min_extended_metadata_type.json.erb @@ -0,0 +1,39 @@ +{ + "data": { + "id": "<%= res.id %>", + "type": "extended_metadata_types", + "attributes": { + "title": "A Min Extended Metadata Type", + "supported_type": "Investigation", + "enabled": true, + "extended_metadata_attributes": [ + { + "id": "<%= res.extended_metadata_attributes.first.id %>", + "title": "name", + "label": "Name", + "description": null, + "sample_attribute_type": { + "id": "<%= res.extended_metadata_attributes.first.sample_attribute_type_id %>", + "title": "<%= res.extended_metadata_attributes.first.sample_attribute_type.title %>", + "base_type": "String", + "regexp": ".*" + }, + "required":true, + "pos":null, + "sample_controlled_vocab_id":null, + "linked_extended_metadata_type_id":null + } + ] + }, + "links": { + "self": "/extended_metadata_types/<%= res.id %>" + }, + "meta": { + "api_version": "0.3", + "base_url": "http://localhost:3000" + } + }, + "jsonapi": { + "version": "1.0" + } +} \ No newline at end of file diff --git a/test/integration/api/extended_metadata_type_api_test.rb b/test/integration/api/extended_metadata_type_api_test.rb new file mode 100644 index 0000000000..8861501c89 --- /dev/null +++ b/test/integration/api/extended_metadata_type_api_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class ExtendedMetadataTypeApiTest < ActionDispatch::IntegrationTest + include ReadApiTestSuite + + def setup + user_login(FactoryBot.create(:admin)) + end +end From 301e64dfbb7ee2e09fb63658a51701962678bd18 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 16 Apr 2024 15:57:43 +0100 Subject: [PATCH 314/344] started extended metadata types documentation #1433 --- public/api/definitions/_paths.yml | 55 +++++++++++++++++++ .../api/descriptions/extendedMetadataTypes.md | 0 .../descriptions/listExtendedMetadataTypes.md | 0 .../descriptions/readExtendedMetadataType.md | 0 4 files changed, 55 insertions(+) create mode 100644 public/api/descriptions/extendedMetadataTypes.md create mode 100644 public/api/descriptions/listExtendedMetadataTypes.md create mode 100644 public/api/descriptions/readExtendedMetadataType.md diff --git a/public/api/definitions/_paths.yml b/public/api/definitions/_paths.yml index dcde4f6fd1..012b682ff1 100644 --- a/public/api/definitions/_paths.yml +++ b/public/api/definitions/_paths.yml @@ -1100,6 +1100,61 @@ $ref: "#/components/responses/forbidden" "404": $ref: "#/components/responses/notFound" +/extended_metadata_types: + get: + operationId: listExtendedMetadataTypes + summary: List data files + description: + $ref: ../descriptions/listExtendedMetadataTypes.md + tags: + - list + - extendedMetadataTypes + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/indexResponse" + examples: + response: + value: + $ref: ../examples/extendedMetadataTypesResponse.json + "501": + $ref: "#/components/responses/notImplemented" + +"/extended_metadata_types/{id}": + parameters: + - name: id + in: path + required: true + description: The extended metadata type to fetch + schema: + type: integer + example: 1152 + get: + operationId: readExtendedMetadataType + summary: Fetch an extended metadata type + description: + $ref: ../descriptions/readExtendedMetadataType.md + tags: + - read + - extendedMetadataType + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/extendedMetadataTypeResponse" + examples: + response: + value: + $ref: ../examples/extendedMetadataTypeResponse.json + "403": + $ref: "#/components/responses/forbidden" + "404": + $ref: "#/components/responses/notFound" /institutions: get: operationId: listInstitutions diff --git a/public/api/descriptions/extendedMetadataTypes.md b/public/api/descriptions/extendedMetadataTypes.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/api/descriptions/listExtendedMetadataTypes.md b/public/api/descriptions/listExtendedMetadataTypes.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/api/descriptions/readExtendedMetadataType.md b/public/api/descriptions/readExtendedMetadataType.md new file mode 100644 index 0000000000..e69de29bb2 From 8417d570f92b608ff240b91d9bd5e381f7551a53 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Tue, 16 Apr 2024 16:39:17 +0100 Subject: [PATCH 315/344] sorted out the paths and tags, and added documentation for extended metadata types #1433 --- public/api/definitions/_paths.yml | 4 ++-- public/api/definitions/openapi-v3.yml | 6 ++++++ public/api/descriptions/api.md | 4 +++- public/api/descriptions/extendedMetadataTypes.md | 7 +++++++ public/api/descriptions/listExtendedMetadataTypes.md | 2 ++ public/api/descriptions/readExtendedMetadataType.md | 3 +++ 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/public/api/definitions/_paths.yml b/public/api/definitions/_paths.yml index 012b682ff1..d1cf992c96 100644 --- a/public/api/definitions/_paths.yml +++ b/public/api/definitions/_paths.yml @@ -1103,7 +1103,7 @@ /extended_metadata_types: get: operationId: listExtendedMetadataTypes - summary: List data files + summary: List extended metadata types description: $ref: ../descriptions/listExtendedMetadataTypes.md tags: @@ -1139,7 +1139,7 @@ $ref: ../descriptions/readExtendedMetadataType.md tags: - read - - extendedMetadataType + - extendedMetadataTypes responses: "200": description: OK diff --git a/public/api/definitions/openapi-v3.yml b/public/api/definitions/openapi-v3.yml index 8eb1eb492e..b9024f98a8 100644 --- a/public/api/definitions/openapi-v3.yml +++ b/public/api/definitions/openapi-v3.yml @@ -87,6 +87,9 @@ tags: - name: documents description: $ref: ../descriptions/documents.md + - name: extendedMetadataTypes + description: + $ref: ../descriptions/extendedMetadataTypes.md - name: models description: $ref: ../descriptions/models.md @@ -158,6 +161,9 @@ x-tagGroups: - sampleTypes - samples - sampleAttributeTypes + - name: Extended Metadata Types + tags: + - extendedMetadataTypes components: securitySchemes: OAuth2: diff --git a/public/api/descriptions/api.md b/public/api/descriptions/api.md index 5b0812d2bb..17cc40faf0 100644 --- a/public/api/descriptions/api.md +++ b/public/api/descriptions/api.md @@ -121,4 +121,6 @@ age, name, and data_of_birth. These could be shown, within its attributes, as: ``` If you wish to create or update a study to make use of this extended metadata, the request payload would be described the same. -Upon creation or update there would be a validation check that the attributes are valid. \ No newline at end of file +Upon creation or update there would be a validation check that the attributes are valid. + +The API supports listing all available Extended Metadata Types, and inspecting individual types by it's id. For more information see the [Extended Metadata Type definitions](api#tag/extendedMetadataTypes). \ No newline at end of file diff --git a/public/api/descriptions/extendedMetadataTypes.md b/public/api/descriptions/extendedMetadataTypes.md index e69de29bb2..37bfe777c8 100644 --- a/public/api/descriptions/extendedMetadataTypes.md +++ b/public/api/descriptions/extendedMetadataTypes.md @@ -0,0 +1,7 @@ + +**Extended Metadata Types** define additional attributes that can be embedded within a number of different assets and items, which extends the metadata. An +**Extended Metadata Type** is tied to a particular supported type (such as Investigation, Study, Assay, DataFile) and is only available for that type. + +Only the READ operations [List](/api#operation/listExtendedMetadataTypes) and [Fetch](/api#operation/readExtendedMetadataType) are currently supported. + +For more details please visit [Extended Metadata](/api#section/Extended-Metadata) \ No newline at end of file diff --git a/public/api/descriptions/listExtendedMetadataTypes.md b/public/api/descriptions/listExtendedMetadataTypes.md index e69de29bb2..8aeaaaf499 100644 --- a/public/api/descriptions/listExtendedMetadataTypes.md +++ b/public/api/descriptions/listExtendedMetadataTypes.md @@ -0,0 +1,2 @@ +The **listExtendedMetadataTypes** operation returns a JSON object containing a list of all the +**Extended Metadata Types** to which the authenticated user has access. \ No newline at end of file diff --git a/public/api/descriptions/readExtendedMetadataType.md b/public/api/descriptions/readExtendedMetadataType.md index e69de29bb2..cec5828fe2 100644 --- a/public/api/descriptions/readExtendedMetadataType.md +++ b/public/api/descriptions/readExtendedMetadataType.md @@ -0,0 +1,3 @@ +A **readExtendedMetadataType** operation will return information about the Extended Metadata Type identified, provided the authenticated user has access to it. + +The **readExtendedMetadataType** operation returns a JSON object representing the **Extended Metadata Type**. \ No newline at end of file From 34ee973215c00232a80b4bdce17a1bbeaa5115a6 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Tue, 16 Apr 2024 17:03:53 +0100 Subject: [PATCH 316/344] add extendedMetadataTypeResponse to schemas.yml for api test --- public/api/definitions/_schemas.yml | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/public/api/definitions/_schemas.yml b/public/api/definitions/_schemas.yml index 5001f08f01..2b1bcffcdb 100644 --- a/public/api/definitions/_schemas.yml +++ b/public/api/definitions/_schemas.yml @@ -4733,6 +4733,100 @@ sampleTypeSampleAttributeResponse: - title - sample_attribute_type additionalProperties: false +extendedMetadataTypeResponse: + type: object + properties: + data: + type: object + properties: + id: + $ref: "#/components/schemas/nonEmptyString" + type: + $ref: "#/components/schemas/nonEmptyString" + attributes: + type: object + properties: + title: + $ref: "#/components/schemas/nonEmptyString" + supported_type: + $ref: "#/components/schemas/nonEmptyString" + enabled: + type: boolean + extended_metadata_attributes: + $ref: "#/components/schemas/extendedMetadataTypeExtendedMetadataAttributesResponse" + required: + - title + - supported_type + - enabled + - extended_metadata_attributes + links: + $ref: "#/components/schemas/links" + meta: + type: object + properties: + base_url: + $ref: "#/components/schemas/uriString" + api_version: + $ref: "#/components/schemas/nonEmptyString" + required: + - base_url + - api_version + additionalProperties: false + required: + - id + - type + - attributes + - links + - meta + additionalProperties: false + jsonapi: + $ref: "#/components/schemas/JsonApiVersion" + required: + - data + - jsonapi + additionalProperties: false +extendedMetadataTypeExtendedMetadataAttributesResponse: + type: array + minItems: 1 + uniqueItems: true + items: + $ref: "#/components/schemas/extendedMetadataTypeExtendedMetadataAttributeResponse" +extendedMetadataTypeExtendedMetadataAttributeResponse: + type: object + properties: + id: + $ref: "#/components/schemas/nonEmptyString" + title: + $ref: "#/components/schemas/nonEmptyString" + label: + $ref: "#/components/schemas/nonEmptyString" + description: + $ref: "#/components/schemas/nullableNonEmptyString" + sample_attribute_type: + type: object + properties: + id: + $ref: "#/components/schemas/nonEmptyString" + title: + $ref: "#/components/schemas/nonEmptyString" + base_type: + $ref: "#/components/schemas/sampleAttributeBaseType" + regexp: + $ref: "#/components/schemas/nonEmptyString" + required: + type: boolean + pos: + $ref: "#/components/schemas/nullableInteger" + sample_controlled_vocab_id: + $ref: "#/components/schemas/nullableNonEmptyString" + linked_extended_metadata_type_id: + $ref: "#/components/schemas/nullableNonEmptyString" + required: + - id + - title + - sample_attribute_type + - required + additionalProperties: false searchResponse: type: object properties: From edaac268e410a6aab81d5ed39e6ab2cf39a68f78 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Wed, 17 Apr 2024 15:01:43 +0100 Subject: [PATCH 317/344] mention that the extended metadata type list is only accessible to admins #1433 --- public/api/descriptions/listExtendedMetadataTypes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/api/descriptions/listExtendedMetadataTypes.md b/public/api/descriptions/listExtendedMetadataTypes.md index 8aeaaaf499..9ae797f97e 100644 --- a/public/api/descriptions/listExtendedMetadataTypes.md +++ b/public/api/descriptions/listExtendedMetadataTypes.md @@ -1,2 +1,4 @@ The **listExtendedMetadataTypes** operation returns a JSON object containing a list of all the -**Extended Metadata Types** to which the authenticated user has access. \ No newline at end of file +**Extended Metadata Types** to which the authenticated user has access. + +**This list is only accessible to instance administrators** \ No newline at end of file From 50fab850737078e4eb1b6a2768b94575de794dcd Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 19 Apr 2024 13:43:35 +0100 Subject: [PATCH 318/344] allow non admin to access the list api #1792 --- app/controllers/extended_metadata_types_controller.rb | 2 +- public/api/descriptions/listExtendedMetadataTypes.md | 4 +--- test/integration/api/extended_metadata_type_api_test.rb | 4 +++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/extended_metadata_types_controller.rb b/app/controllers/extended_metadata_types_controller.rb index 595e0fa410..1e9547a5a0 100644 --- a/app/controllers/extended_metadata_types_controller.rb +++ b/app/controllers/extended_metadata_types_controller.rb @@ -1,6 +1,6 @@ class ExtendedMetadataTypesController < ApplicationController respond_to :json - before_action :is_user_admin_auth, except: [:form_fields, :show] + before_action :is_user_admin_auth, except: [:form_fields, :show, :index] before_action :find_requested_item, only: [:administer_update, :show] include Seek::IndexPager diff --git a/public/api/descriptions/listExtendedMetadataTypes.md b/public/api/descriptions/listExtendedMetadataTypes.md index 9ae797f97e..8aeaaaf499 100644 --- a/public/api/descriptions/listExtendedMetadataTypes.md +++ b/public/api/descriptions/listExtendedMetadataTypes.md @@ -1,4 +1,2 @@ The **listExtendedMetadataTypes** operation returns a JSON object containing a list of all the -**Extended Metadata Types** to which the authenticated user has access. - -**This list is only accessible to instance administrators** \ No newline at end of file +**Extended Metadata Types** to which the authenticated user has access. \ No newline at end of file diff --git a/test/integration/api/extended_metadata_type_api_test.rb b/test/integration/api/extended_metadata_type_api_test.rb index 8861501c89..87877f16f2 100644 --- a/test/integration/api/extended_metadata_type_api_test.rb +++ b/test/integration/api/extended_metadata_type_api_test.rb @@ -4,6 +4,8 @@ class ExtendedMetadataTypeApiTest < ActionDispatch::IntegrationTest include ReadApiTestSuite def setup - user_login(FactoryBot.create(:admin)) + person = FactoryBot.create(:person) + refute person.is_admin? + user_login(person) end end From 6acae9c6cc79608a12f3d35c1da6a46f7bb011f5 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Fri, 19 Apr 2024 13:49:36 +0100 Subject: [PATCH 319/344] fixed typos and grammar errors #1433 --- public/api/descriptions/api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/api/descriptions/api.md b/public/api/descriptions/api.md index 17cc40faf0..797774f3d5 100644 --- a/public/api/descriptions/api.md +++ b/public/api/descriptions/api.md @@ -100,14 +100,14 @@ Types currently supported are **Investigation**, < **Document**, **Model**, **Event**, **Collection**, **Project** -The response and requests for each of these types include an additional optional attribute _extended_attributes_ which describes +The responses and requests for each of these types include an additional optional attribute _extended_attributes_ which describes * _extended_metadata_type_id_ - the id of the extended metadata type which can be used to find more details about what its attributes are. * _attribute_map_ - which is a map of key / value pairs where the key is the attribute name For example, a Study may have extended metadata, defined by an Extended Metadata Type with id 12, that has attributes for -age, name, and data_of_birth. These could be shown, within its attributes, as: +age, name, and date_of_birth. These could be shown, within its attributes, as: ``` "extended_attributes": { @@ -123,4 +123,4 @@ age, name, and data_of_birth. These could be shown, within its attributes, as: If you wish to create or update a study to make use of this extended metadata, the request payload would be described the same. Upon creation or update there would be a validation check that the attributes are valid. -The API supports listing all available Extended Metadata Types, and inspecting individual types by it's id. For more information see the [Extended Metadata Type definitions](api#tag/extendedMetadataTypes). \ No newline at end of file +The API supports listing all available Extended Metadata Types, and inspecting an individual type by its id. For more information see the [Extended Metadata Type definitions](api#tag/extendedMetadataTypes). \ No newline at end of file From 160ea3bbf9facbd99ed136ca8d470fcbefcea03e Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 19 Apr 2024 15:11:02 +0100 Subject: [PATCH 320/344] Display version name when only 1 version. Fixes #1850 --- app/views/general/_item_title.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/general/_item_title.html.erb b/app/views/general/_item_title.html.erb index 9807954105..9d17f3335a 100644 --- a/app/views/general/_item_title.html.erb +++ b/app/views/general/_item_title.html.erb @@ -37,7 +37,7 @@ gatekeeper_status_bar = item.is_asset? && item.can_manage? && (item.is_waiting_a <% else %> -
    Version <%= version -%>
    +
    <%= visible_versions.first&.name -%>
    <% end %> <% end %> @@ -72,4 +72,4 @@ gatekeeper_status_bar = item.is_asset? && item.can_manage? && (item.is_waiting_a <% end %> -<% end %> \ No newline at end of file +<% end %> From be29b37e98e93e154962a066f37c93c098a24ed7 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 19 Apr 2024 16:08:44 +0100 Subject: [PATCH 321/344] Sanitization --- app/assets/javascripts/objects_input.js | 11 ++++++++--- app/helpers/policy_helper.rb | 17 +++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/objects_input.js b/app/assets/javascripts/objects_input.js index 3bcb08fd06..ad421c1ae1 100644 --- a/app/assets/javascripts/objects_input.js +++ b/app/assets/javascripts/objects_input.js @@ -32,9 +32,8 @@ var ObjectsInput = { const template = $j(this).data('typeahead-template') || 'typeahead/hint'; opts.templateResult = HandlebarsTemplates[template]; - opts.escapeMarkup = function (m) { - return m; - } + opts.escapeMarkup = ObjectsInput.doNothing; + opts.templateSelection = ObjectsInput.escapeSelectionMarkup; if ($j(this).data('typeahead-query-url')) { opts.ajax={ @@ -47,6 +46,12 @@ var ObjectsInput = { opts ); }); + }, + doNothing: function (m) { + return m; + }, + escapeSelectionMarkup: function (m) { + return jQuery.fn.select2.defaults.defaults.escapeMarkup(m.text); } } $j(document).ready(function () { diff --git a/app/helpers/policy_helper.rb b/app/helpers/policy_helper.rb index 7c431caa93..63c456a03c 100644 --- a/app/helpers/policy_helper.rb +++ b/app/helpers/policy_helper.rb @@ -169,23 +169,28 @@ def permission_title(permission, member_prefix: false, icon: false, link: false) end option = { target: :_blank } + text = ''.html_safe # Open safe buffer case type when 'Person' - text = "#{contributor.first_name} #{contributor.last_name}" - text = link_to(h(text), contributor, option).html_safe if link + text += "#{contributor.first_name} #{contributor.last_name}" + text += link_to(text, contributor, option).html_safe if link when 'WorkGroup' institution = contributor.institution project = contributor.project if link - text = "#{member_prefix ? 'Members of ' : ''}#{link_to(h(project.title), project, option)} @ #{link_to(h(institution.title), institution, option)}".html_safe + text += 'Members of ' if member_prefix + text += link_to(project.title, project, option) + text += ' @ ' + text += link_to(institution.title, institution, option) else - text = "#{member_prefix ? 'Members of ' : ''}#{project.title} @ #{institution.title}" + text += "#{member_prefix ? 'Members of ' : ''}#{project.title} @ #{institution.title}" end else if link - text = "#{member_prefix ? 'Members of ' : ''}#{link_to(h(contributor.title), contributor, option)}".html_safe + text += 'Members of' if member_prefix + text += link_to(contributor.title, project, option) else - text = "#{member_prefix ? 'Members of ' : ''}#{contributor.title}" + text += "#{member_prefix ? 'Members of ' : ''}#{contributor.title}" end end From 6b34fe18d4903417272d224c0a774344a9064a36 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 18 Apr 2024 15:34:46 +0100 Subject: [PATCH 322/344] show batch permission changes and publish on my-items #1837 --- app/views/people/_batch_permission_changes_buttons.html.erb | 4 ++++ app/views/people/_buttons.html.erb | 5 +---- app/views/people/items.html.erb | 4 ++++ test/functional/people_controller_test.rb | 6 ++++++ 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 app/views/people/_batch_permission_changes_buttons.html.erb diff --git a/app/views/people/_batch_permission_changes_buttons.html.erb b/app/views/people/_batch_permission_changes_buttons.html.erb new file mode 100644 index 0000000000..8d1ac7331d --- /dev/null +++ b/app/views/people/_batch_permission_changes_buttons.html.erb @@ -0,0 +1,4 @@ +<% tooltip_text_sharing = "You can change the sharing policy and permissions for your items in batch. A preview of selected items will be given before you choose new permissions for them." %> +<% tooltip_text_publishing = "Publish your owned items in one click. A preview will be given before publishing" %> +<%= button_link_to "Batch permission changes", "publish", batch_sharing_permission_preview_person_path(@person), 'data-tooltip': tooltip(tooltip_text_sharing) -%> +<%= button_link_to "Publish your items", "publish", batch_publishing_preview_person_path(@person), 'data-tooltip': tooltip(tooltip_text_publishing) -%> \ No newline at end of file diff --git a/app/views/people/_buttons.html.erb b/app/views/people/_buttons.html.erb index bd48f3beff..fbd58f6d11 100644 --- a/app/views/people/_buttons.html.erb +++ b/app/views/people/_buttons.html.erb @@ -1,8 +1,5 @@ <% if mine?(@person) -%> - <% tooltip_text_sharing = "You can change the sharing policy and permissions for your items in batch. A preview of selected items will be given before you choose new permissions for them." %> - <% tooltip_text_publishing = "Publish your owned items in one click. A preview will be given before publishing" %> - <%= button_link_to "Batch permission changes", "publish", batch_sharing_permission_preview_person_path(@person), 'data-tooltip': tooltip(tooltip_text_sharing) -%> - <%= button_link_to "Publish your items", "publish", batch_publishing_preview_person_path(@person), 'data-tooltip': tooltip(tooltip_text_publishing) -%> + <%= render partial:'batch_permission_changes_buttons' %> <% if @person.is_in_any_gatekept_projects? %> <%= button_link_to "Assets awaiting approval", "waiting", waiting_approval_assets_person_path(@person), 'data-tooltip': tooltip("The assets you have requested to publish, but are awaiting the #{t('asset_gatekeeper').downcase} approval") -%> diff --git a/app/views/people/items.html.erb b/app/views/people/items.html.erb index f657873357..a54fe905ce 100644 --- a/app/views/people/items.html.erb +++ b/app/views/people/items.html.erb @@ -1,4 +1,8 @@ +
    +
    + <%= render partial:'batch_permission_changes_buttons' %> +
    <%= render partial: "general/items_related_to", object: @person, locals: { limit: 50, title: "Items related to #{link_to(h(@person.name),@person)}".html_safe } %>
    diff --git a/test/functional/people_controller_test.rb b/test/functional/people_controller_test.rb index f00b35eafc..9894c34658 100644 --- a/test/functional/people_controller_test.rb +++ b/test/functional/people_controller_test.rb @@ -1039,6 +1039,12 @@ def test_should_add_nofollow_to_links_in_show_page assert_select 'div.list_item_title a[href=?]', data_file_path(other_data_file), text: /#{other_data_file.title}/, count: 0 assert_select 'div.list_item_title a[href=?]', project_path(other_project), text: /#{other_project.title}/, count: 0 end + + assert_select 'div#buttons' do + assert_select 'a[href=?]', batch_sharing_permission_preview_person_path(me),text: /Batch permission changes/ + assert_select 'a[href=?]', batch_publishing_preview_person_path(me),text: /Publish your items/ + end + end test 'my items permissions' do From a97b579daf9894cc16cf28caf49ade9800c65a0b Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 18 Apr 2024 15:43:07 +0100 Subject: [PATCH 323/344] refactoring, remove mine? helper and user Person.me? #1837 mine? was old and incomplete and only checked if the item was a person and the same as current_user.person. Other cases would always be false --- app/helpers/application_helper.rb | 16 ---------------- app/views/avatars/index.html.erb | 2 +- app/views/institutions/_buttons.html.erb | 2 +- app/views/people/_buttons.html.erb | 6 +++--- app/views/people/show.html.erb | 2 +- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6cb40302d7..327ecc6b9f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -273,22 +273,6 @@ def contributor(contributor, _avatar = false, _size = 100, _you_text = false) contributor_name_link.html_safe end - # this helper is to be extended to include many more types of objects that can belong to the - # user - for example, SOPs and others - def mine?(thing) - return false if thing.nil? - return false unless logged_in? - - c_id = current_user.id.to_i - - case thing.class.name - when 'Person' - return (current_user.person.id == thing.id) - else - return false - end - end - def link_to_draggable(link_name, url, link_options = {}) link_to(link_name, url, link_options) end diff --git a/app/views/avatars/index.html.erb b/app/views/avatars/index.html.erb index e3dbb250ce..0120c20cba 100644 --- a/app/views/avatars/index.html.erb +++ b/app/views/avatars/index.html.erb @@ -1,4 +1,4 @@ -<% editable = mine?(@avatar_owner_instance) || @avatar_owner_instance.can_edit?(current_user) -%> +<% editable = @avatar_owner_instance.can_edit?(current_user) -%> <% item = @avatar_owner_instance.class.name.downcase diff --git a/app/views/institutions/_buttons.html.erb b/app/views/institutions/_buttons.html.erb index 36740072fa..a7ae775b07 100644 --- a/app/views/institutions/_buttons.html.erb +++ b/app/views/institutions/_buttons.html.erb @@ -1,5 +1,5 @@ <%= item_actions_dropdown do %> - <% if mine?(item) || item.can_edit? -%> + <% if item.can_edit? -%>
  • <%= icon_link_to("Edit #{t('institution')} Details", 'edit', edit_institution_path(item)) -%>
  • <% if admin_logged_in? -%>
  • <%= delete_icon(item,current_user) %>
  • diff --git a/app/views/people/_buttons.html.erb b/app/views/people/_buttons.html.erb index fbd58f6d11..590e0e578c 100644 --- a/app/views/people/_buttons.html.erb +++ b/app/views/people/_buttons.html.erb @@ -1,4 +1,4 @@ -<% if mine?(@person) -%> +<% if @person.me? -%> <%= render partial:'batch_permission_changes_buttons' %> <% if @person.is_in_any_gatekept_projects? %> <%= button_link_to "Assets awaiting approval", "waiting", waiting_approval_assets_person_path(@person), @@ -11,11 +11,11 @@ <% end %> <%= item_actions_dropdown do %> - <% if (mine?(@person) || @person.can_edit?(current_user)) -%> + <% if @person.me? || @person.can_edit?(current_user) -%>
  • <%= image_tag_for_key('edit', edit_person_path(@person), "Edit Person Profile", nil, 'Edit Profile') -%>
  • - <% if mine?(@person) -%> + <% if @person.me? -%>
  • <%= image_tag_for_key "lock", url_for({controller: :users, action: :edit, id: @person.user}), "Manage Account", nil, "Manage Account" -%>
  • diff --git a/app/views/people/show.html.erb b/app/views/people/show.html.erb index 08b248ebc2..fa91fe73ab 100644 --- a/app/views/people/show.html.erb +++ b/app/views/people/show.html.erb @@ -70,7 +70,7 @@ <% end %> - <%= render :partial => "people/project_subscriptions", :locals => { :person => @person } if (mine?(@person) || current_user.try(:person).try(:is_admin?)) %> + <%= render :partial => "people/project_subscriptions", :locals => { :person => @person } if @person.me? || current_user.try(:person).try(:is_admin?) %> <% end %> From 8b82bf5f822530bb38fdef472980bb757f43d748 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Thu, 18 Apr 2024 16:31:52 +0100 Subject: [PATCH 324/344] use locale for the tooltip text #1837 --- app/views/people/_batch_permission_changes_buttons.html.erb | 6 ++---- config/locales/en.yml | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/views/people/_batch_permission_changes_buttons.html.erb b/app/views/people/_batch_permission_changes_buttons.html.erb index 8d1ac7331d..fddb1d6502 100644 --- a/app/views/people/_batch_permission_changes_buttons.html.erb +++ b/app/views/people/_batch_permission_changes_buttons.html.erb @@ -1,4 +1,2 @@ -<% tooltip_text_sharing = "You can change the sharing policy and permissions for your items in batch. A preview of selected items will be given before you choose new permissions for them." %> -<% tooltip_text_publishing = "Publish your owned items in one click. A preview will be given before publishing" %> -<%= button_link_to "Batch permission changes", "publish", batch_sharing_permission_preview_person_path(@person), 'data-tooltip': tooltip(tooltip_text_sharing) -%> -<%= button_link_to "Publish your items", "publish", batch_publishing_preview_person_path(@person), 'data-tooltip': tooltip(tooltip_text_publishing) -%> \ No newline at end of file +<%= button_link_to "Batch permission changes", "publish", batch_sharing_permission_preview_person_path(@person), 'data-tooltip': tooltip(t('tooltips.batch_permission_changes_button')) -%> +<%= button_link_to "Publish your items", "publish", batch_publishing_preview_person_path(@person), 'data-tooltip': tooltip(t('tooltips.publish_your_items_button')) -%> \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 41ddb55a0e..31a337630a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -336,3 +336,7 @@ you can choose to have a %{project} linked to a site managed %{programme} instea all: Allow all cookies embedding: Allow necessary cookies and embedded content necessary: Allow only necessary cookies + + tooltips: + batch_permission_changes_button: "You can change the sharing policy and permissions for your items as a batch. A preview of selected items will be given before you choose new permissions for them." + publish_your_items_button: "Publish your owned items as a batch. A preview will be given before publishing" From 9b6196e696190d72c8dbe01ca98a3c03087f217b Mon Sep 17 00:00:00 2001 From: whomingbird Date: Mon, 22 Apr 2024 13:04:55 +0200 Subject: [PATCH 325/344] TAGS for Project #1746 --- app/controllers/projects_controller.rb | 1 + app/models/project.rb | 1 + app/views/projects/show.html.erb | 4 + config/routes.rb | 1 + test/functional/projects_controller_test.rb | 553 ++++++++++---------- 5 files changed, 291 insertions(+), 269 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c63bc2197c..73864029d0 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -8,6 +8,7 @@ class ProjectsController < ApplicationController include CommonSweepers include Seek::DestroyHandling include Seek::Projects::Population + include Seek::AnnotationCommon before_action :login_required, only: [:guided_join, :guided_create, :guided_import, :request_join, :request_create, :request_import, :administer_join_request, :respond_join_request, diff --git a/app/models/project.rb b/app/models/project.rb index 4431d7c9ae..2aa17d69fc 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,6 +2,7 @@ class Project < ApplicationRecord include Seek::Annotatable include HasSettings include Seek::Roles::Scope + include Seek::Taggable acts_as_yellow_pages title_trimmer diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index 53591297cd..67dbe1e55f 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -122,6 +122,10 @@ url: storage_report_project_path(@project)} %> <% end %> + <% if @project.is_taggable? %> + <%= render :partial=>"assets/tags_box", :no_tags_message=>"Add tags (comma separated) ..." -%> + <% end -%> + diff --git a/config/routes.rb b/config/routes.rb index 016f0963ae..3ef1455e5d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -349,6 +349,7 @@ get :administer_join_request post :respond_join_request get :guided_join + post :update_annotations_ajax end resources :programmes, :people, :institutions, :assays, :studies, :investigations, :models, :sops, :workflows, :data_files, :presentations, :publications, :events, :sample_types, :samples, :specimens, :strains, :search, :organisms, :human_diseases, :documents, :file_templates, :placeholders, :collections, :templates, only: [:index] diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 21a8552a8c..cf8182b395 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -154,8 +154,8 @@ def test_avatar_show_in_list assert_no_difference('Project.count') do assert_no_difference('ExtendedMetadata.count') do put :update, params: { id: project.id, project: { title: "new title", - extended_metadata_attributes: { extended_metadata_type_id: cmt.id, id: cm.id, - data: { + extended_metadata_attributes: { extended_metadata_type_id: cmt.id, id: cm.id, + data: { "age": 20, "name": 'max' } } @@ -209,6 +209,21 @@ def test_avatar_show_in_list assert_empty project.programmes end + test 'update project with tags' do + proj_admin = FactoryBot.create(:project_administrator) + login_as(proj_admin) + project = proj_admin.projects.first + + post :update_annotations_ajax, xhr: true, params: { id: project, tag_list: %w[alice rabbit] } + project.reload + assert_equal %w[alice rabbit], project.tags + + project.annotate_with('dodo', 'tag', proj_admin) + project.save! + assert_equal ['dodo'], project.tags + end + + test 'cannot create project with programme if not administrator of programme' do person = FactoryBot.create(:programme_administrator) login_as(person) @@ -1207,7 +1222,7 @@ def test_admin_can_edit new_person2 = FactoryBot.create(:person) new_person3 = FactoryBot.create(:person) - put :update, params: { id:project.id, project: { members: [{ 'person_id' => "#{new_person.id}", 'institution_id' => "#{new_institution.id}" }, + put :update, params: { id: project.id, project: { members: [{ 'person_id' => "#{new_person.id}", 'institution_id' => "#{new_institution.id}" }, { 'person_id' => "#{new_person2.id}", 'institution_id' => "#{new_institution.id}" }, { 'person_id' => "#{new_person3.id}", 'institution_id' => "#{new_institution.id}" }] } } @@ -1287,7 +1302,7 @@ def test_admin_can_edit assert_no_enqueued_jobs only: ProjectLeavingJob do assert_no_difference('GroupMembership.count') do post :update_members, params: { id: project, memberships_to_flag: { group_membership.id.to_s => { time_left_at: 1.day.ago }, - former_group_membership.id.to_s => { time_left_at: '' } } } + former_group_membership.id.to_s => { time_left_at: '' } } } assert_redirected_to project_path(project) assert_nil flash[:error] refute_nil flash[:notice] @@ -1601,8 +1616,8 @@ def test_admin_can_edit person = FactoryBot.create(:person, group_memberships: [group_membership]) data_file = FactoryBot.create(:data_file, projects: [project], contributor: person, - policy: FactoryBot.create(:policy, access_type: Policy::NO_ACCESS, - permissions: [FactoryBot.create(:permission, + policy: FactoryBot.create(:policy, access_type: Policy::NO_ACCESS, + permissions: [FactoryBot.create(:permission, contributor: project, access_type: Policy::VISIBLE)])) refute data_file.can_delete?(proj_admin) @@ -1900,10 +1915,10 @@ def test_admin_can_edit login_as(person) params = { project_ids: ['', project.id], - institution:{ + institution: { id: ['', institution.id] }, - comments: 'some comments' + comments: 'some comments' } assert_enqueued_emails(1) do assert_difference('ProjectMembershipMessageLog.count') do @@ -1928,10 +1943,10 @@ def test_admin_can_edit institution_params = { id: ['fish'], - title:'fish', - city:'Sheffield', - country:'GB', - web_page:'http://google.com' + title: 'fish', + city: 'Sheffield', + country: 'GB', + web_page: 'http://google.com' } params = { project_ids: [project.id], @@ -1965,10 +1980,10 @@ def test_admin_can_edit institution_params = { id: ['fish'], - title:'fish', - city:'Sheffield', - country:'GB', - web_page:'http://google.com' + title: 'fish', + city: 'Sheffield', + country: 'GB', + web_page: 'http://google.com' } params = { project_ids: ['', project1.id, project2.id], @@ -2503,9 +2518,9 @@ def test_admin_can_edit with_config_value(:managed_programme_id, programme.id) do params = { project: {dmp: fixture_file_upload('dmp.json', 'application/json')}, - institution: {id: ['the inst'], title: 'the inst', web_page: 'the page', city: 'London', country: 'GB'}, - programme_id: '', - programme: {title: 'the prog'} + institution: {id: ['the inst'], title: 'the inst', web_page: 'the page', city: 'London', country: 'GB'}, + programme_id: '', + programme: {title: 'the prog'} } assert_enqueued_emails(1) do assert_difference('ProjectImportationMessageLog.count') do @@ -2644,8 +2659,8 @@ def test_admin_can_edit params = { message_log_id: log.id, accept_request: '1', - institution:{id:institution.id}, - id:project.id + institution: {id:institution.id}, + id: project.id } assert_enqueued_emails(2) do @@ -2679,8 +2694,8 @@ def test_admin_can_edit params = { message_log_id: log.id, accept_request: '1', - institution:{id:institution.id}, - id:project.id + institution: {id:institution.id}, + id: project.id } assert_enqueued_emails(0) do @@ -2703,8 +2718,8 @@ def test_admin_can_edit project = person.projects.first sender = FactoryBot.create(:person) institution = Institution.new({ - title:'institution', - country:'DE' + title: 'institution', + country: 'DE' }) log = ProjectMembershipMessageLog.log_request(sender:sender, project:project, institution:institution, comments:'some comments') login_as(person) @@ -2712,11 +2727,11 @@ def test_admin_can_edit params = { message_log_id: log.id, accept_request: '1', - institution:{ - title:'institution', - country:'FR' # admin may have corrected this from DE + institution: { + title: 'institution', + country: 'FR' # admin may have corrected this from DE }, - id:project.id + id: project.id } assert_enqueued_emails(2) do @@ -2746,8 +2761,8 @@ def test_admin_can_edit project = person.projects.first sender = FactoryBot.create(:person) institution = Institution.new({ - title:'institution', - country:'DE' + title: 'institution', + country: 'DE' }) log = ProjectMembershipMessageLog.log_request(sender:sender, project:project, institution:institution, comments:'some comments') login_as(person) @@ -2755,10 +2770,10 @@ def test_admin_can_edit params = { message_log_id: log.id, accept_request: '1', - institution:{ - title:'', + institution: { + title: '', }, - id:project.id + id: project.id } assert_enqueued_emails(0) do @@ -2789,7 +2804,7 @@ def test_admin_can_edit message_log_id: log.id, reject_details: 'bad request', institution: { id:institution.id }, - id:project.id + id: project.id } assert_enqueued_emails(2) do @@ -2977,19 +2992,19 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project updated', - web_page:'http://proj.org' + project: { + title: 'new project updated', + web_page: 'http://proj.org' }, - programme:{ - title:'new programme updated' + programme: { + title: 'new programme updated' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' } } @@ -3044,19 +3059,19 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project updated', - web_page:'http://proj.org' + project: { + title: 'new project updated', + web_page: 'http://proj.org' }, - programme:{ - title:'new programme updated' + programme: { + title: 'new programme updated' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' } } @@ -3087,19 +3102,19 @@ def test_admin_can_edit institution = Institution.new({title:'institution', country:'DE'}) log = ProjectCreationMessageLog.log_request(sender: person, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ + programme: { id: programme.id }, - institution:{ - title:'new institution', - city:'Paris', - country:'FR' + institution: { + title: 'new institution', + city: 'Paris', + country: 'FR' } } @@ -3148,19 +3163,19 @@ def test_admin_can_edit institution = Institution.new({title:'institution', country:'DE'}) log = ProjectCreationMessageLog.log_request(sender: person, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ + programme: { id: programme.id }, - institution:{ - title:'new institution', - city:'Paris', - country:'FR' + institution: { + title: 'new institution', + city: 'Paris', + country: 'FR' } } @@ -3197,18 +3212,18 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'' + project: { + title: '' }, - programme:{ - title:'new programme updated' + programme: { + title: 'new programme updated' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' } } @@ -3242,18 +3257,18 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'a valid project' + project: { + title: 'a valid project' }, - programme:{ - title:'' + programme: { + title: '' }, - institution:{ - title:duplicate_institution.title, - city:'Paris', - country:'FR' + institution: { + title: duplicate_institution.title, + city: 'Paris', + country: 'FR' } } @@ -3286,17 +3301,17 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id } } @@ -3340,17 +3355,17 @@ def test_admin_can_edit log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) assert log.sent_by_self? params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id } } @@ -3391,16 +3406,16 @@ def test_admin_can_edit log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) assert log.sent_by_self? params = { - message_log_id:log.id, - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id } } @@ -3432,16 +3447,16 @@ def test_admin_can_edit log = ProjectCreationMessageLog.log_request(sender: requester, programme: programme, project: project, institution: institution) refute log.sent_by_self? params = { - message_log_id:log.id, - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, delete_request: '1' } @@ -3475,16 +3490,16 @@ def test_admin_can_edit refute log.sent_by_self? refute programme.can_manage? params = { - message_log_id:log.id, - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, delete_request: '1' } @@ -3517,17 +3532,17 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id } } @@ -3561,17 +3576,17 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution) params = { - message_log_id:log.id, - reject_details:'not very good', - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + reject_details: 'not very good', + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id } } @@ -3606,16 +3621,16 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectCreationMessageLog.log_request(sender:requester, project:project, institution:institution) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project updated', - web_page:'http://proj.org' + project: { + title: 'new project updated', + web_page: 'http://proj.org' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' } } @@ -3769,19 +3784,19 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project updated', - web_page:'http://proj.org' + project: { + title: 'new project updated', + web_page: 'http://proj.org' }, - programme:{ - title:'new programme updated' + programme: { + title: 'new programme updated' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -3840,19 +3855,19 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project updated', - web_page:'http://proj.org' + project: { + title: 'new project updated', + web_page: 'http://proj.org' }, - programme:{ - title:'new programme updated' + programme: { + title: 'new programme updated' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -3885,19 +3900,19 @@ def test_admin_can_edit people = FactoryBot.create_list(:person, 3) log = ProjectImportationMessageLog.log_request(sender: person, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ + programme: { id: programme.id }, - institution:{ - title:'new institution', - city:'Paris', - country:'FR' + institution: { + title: 'new institution', + city: 'Paris', + country: 'FR' }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -3936,18 +3951,18 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'' + project: { + title: '' }, - programme:{ - title:'new programme updated' + programme: { + title: 'new programme updated' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -3983,18 +3998,18 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'a valid project' + project: { + title: 'a valid project' }, - programme:{ - title:'' + programme: { + title: '' }, - institution:{ - title:duplicate_institution.title, - city:'Paris', - country:'FR' + institution: { + title: duplicate_institution.title, + city: 'Paris', + country: 'FR' }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -4029,17 +4044,17 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -4087,17 +4102,17 @@ def test_admin_can_edit log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) assert log.sent_by_self? params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -4142,16 +4157,16 @@ def test_admin_can_edit log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) assert log.sent_by_self? params = { - message_log_id:log.id, - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -4185,16 +4200,16 @@ def test_admin_can_edit log = ProjectImportationMessageLog.log_request(sender: requester, programme: programme, project: project, institution: institution, people: people) refute log.sent_by_self? params = { - message_log_id:log.id, - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} }, delete_request: '1' @@ -4230,16 +4245,16 @@ def test_admin_can_edit refute log.sent_by_self? refute programme.can_manage? params = { - message_log_id:log.id, - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} }, delete_request: '1' @@ -4274,17 +4289,17 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project', - web_page:'http://proj.org' + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -4320,17 +4335,17 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, programme:programme, project:project, institution:institution, people:people) params = { - message_log_id:log.id, - reject_details:'not very good', - project:{ - title:'new project', - web_page:'http://proj.org' + message_log_id: log.id, + reject_details: 'not very good', + project: { + title: 'new project', + web_page: 'http://proj.org' }, - programme:{ - id:programme.id + programme: { + id: programme.id }, - institution:{ - id:institution.id + institution: { + id: institution.id }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -4367,16 +4382,16 @@ def test_admin_can_edit requester = FactoryBot.create(:person) log = ProjectImportationMessageLog.log_request(sender:requester, project:project, institution:institution, people:people) params = { - message_log_id:log.id, + message_log_id: log.id, accept_request: '1', - project:{ - title:'new project updated', - web_page:'http://proj.org' + project: { + title: 'new project updated', + web_page: 'http://proj.org' }, - institution:{ - title:'new institution updated', - city:'Paris', - country:'FR' + institution: { + title: 'new institution updated', + city: 'Paris', + country: 'FR' }, people: people.map { |p| {first_name: p.first_name, last_name: p.last_name, email: p.email} } } @@ -4623,7 +4638,7 @@ def test_admin_can_edit assert_difference('AssetLink.discussion.count') do assert_difference('Project.count') do post :create, params: { project: { title: 'test', - discussion_links_attributes: [{url: "http://www.slack.com/"}]}} + discussion_links_attributes: [{url: "http://www.slack.com/"}]}} end end project = assigns(:project) From a643393acf715ee5af5da45897f1d54050070c4b Mon Sep 17 00:00:00 2001 From: whomingbird Date: Mon, 22 Apr 2024 16:11:53 +0200 Subject: [PATCH 326/344] fix tests --- test/unit/bio_schema/schema_ld_generation_test.rb | 1 + test/unit/index_table_column_definitions_test.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/bio_schema/schema_ld_generation_test.rb b/test/unit/bio_schema/schema_ld_generation_test.rb index 6bd1eed726..795b243b2a 100644 --- a/test/unit/bio_schema/schema_ld_generation_test.rb +++ b/test/unit/bio_schema/schema_ld_generation_test.rb @@ -325,6 +325,7 @@ def setup 'logo' => "http://localhost:3000/projects/#{@project.id}/avatars/#{@project.avatar.id}?size=250", 'image' => "http://localhost:3000/projects/#{@project.id}/avatars/#{@project.avatar.id}?size=250", 'url' => @project.web_page, + 'keywords' => '', 'member' => [ { '@type' => 'Person', '@id' => "http://localhost:3000/people/#{@person.id}", 'name' => @person.name }, { '@type' => 'ResearchOrganization', '@id' => "http://localhost:3000/institutions/#{institution.id}", diff --git a/test/unit/index_table_column_definitions_test.rb b/test/unit/index_table_column_definitions_test.rb index d2e264667e..b2ea229628 100644 --- a/test/unit/index_table_column_definitions_test.rb +++ b/test/unit/index_table_column_definitions_test.rb @@ -30,7 +30,7 @@ def setup Seek::IndexTableColumnDefinitions.allowed_columns(@data_file) assert_equal %w[title first_name last_name projects orcid description], Seek::IndexTableColumnDefinitions.allowed_columns(@person) - assert_equal %w[title web_page start_date end_date topic_annotation_values description created_at updated_at], + assert_equal %w[title web_page start_date end_date topic_annotation_values description created_at updated_at tags], Seek::IndexTableColumnDefinitions.allowed_columns(@project) end From 84ee83e30d52c738cc9700cdf71d8ab1f5707104 Mon Sep 17 00:00:00 2001 From: whomingbird Date: Mon, 22 Apr 2024 17:05:24 +0200 Subject: [PATCH 327/344] fix the project tagging issue when single_page is enabled --- app/helpers/tags_helper.rb | 4 ++++ .../assets/_resource_main_content_right.html.erb | 2 +- app/views/assets/_tags_box.html.erb | 13 ++++++------- app/views/projects/show.html.erb | 4 ++-- config/routes.rb | 1 + 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index a4d1eaa17c..01593e4a39 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -20,6 +20,10 @@ def fetch_tags_for_item(object, attribute = 'tag') [all_tags, item_tags] end + def fetch_tags_for_item_owned_by_current_user (object) + current_user ? object.annotations.with_attribute_name("tag").by_source("User", User.current_user.id).collect { |a| a.value }.uniq : [] + end + def link_for_ann(tag, options = {}) length = options[:truncate_length] length ||= 150 diff --git a/app/views/assets/_resource_main_content_right.html.erb b/app/views/assets/_resource_main_content_right.html.erb index 3490041283..36d657c979 100644 --- a/app/views/assets/_resource_main_content_right.html.erb +++ b/app/views/assets/_resource_main_content_right.html.erb @@ -62,7 +62,7 @@ <% end %> <% if resource.class.is_taggable? %> - <%= render :partial=>"assets/tags_box", :no_tags_message=>"Add tags (comma separated) ..." -%> + <%= render :partial=>"assets/tags_box", :locals => { :resource => resource }, :no_tags_message=>"Add tags (comma separated) ..." -%> <% end -%> <% if resource.respond_to? :attributions_objects -%> diff --git a/app/views/assets/_tags_box.html.erb b/app/views/assets/_tags_box.html.erb index cc1d43b6c6..48850ca3b4 100644 --- a/app/views/assets/_tags_box.html.erb +++ b/app/views/assets/_tags_box.html.erb @@ -1,11 +1,10 @@ <% - entity=controller_name.singularize - object=instance_variable_get("@#{entity}") - title||="Tags" - all_tags,all_item_tags = fetch_tags_for_item object - owner_item_tags = current_user ? object.annotations.with_attribute_name("tag").by_source("User",User.current_user.id).collect{|a| a.value}.uniq : [] - create_tag_link_text = ((owner_item_tags.empty? ? "Add your tags" : "Update your tags") + ' ').html_safe - existing_tags = owner_item_tags.map(&:text).uniq + title||="Tags" + all_tags,all_item_tags = fetch_tags_for_item resource + owner_item_tags = fetch_tags_for_item_owned_by_current_user resource + + create_tag_link_text = ((owner_item_tags.empty? ? "Add your tags" : "Update your tags") + ' ').html_safe + existing_tags = owner_item_tags.map(&:text).uniq %> <%= panel(title, :id => "tags_box", :help_text => "Tagging an item helps describe it and other people find it. You can contribute by applying your own tags to this item. The weight of a tag indicates how often it has been used.") do %>
    diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index 67dbe1e55f..c65bae0a38 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -122,8 +122,8 @@ url: storage_report_project_path(@project)} %> <% end %> - <% if @project.is_taggable? %> - <%= render :partial=>"assets/tags_box", :no_tags_message=>"Add tags (comma separated) ..." -%> + <% if @project.is_taggable?%> + <%= render :partial=>"assets/tags_box", locals: {resource: @project}, :no_tags_message=>"Add tags (comma separated) ..." -%> <% end -%>
    diff --git a/config/routes.rb b/config/routes.rb index 3ef1455e5d..19099c0331 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -762,6 +762,7 @@ resources :single_pages do member do get :dynamic_table_data + post :update_annotations_ajax end collection do get :batch_sharing_permission_preview From 1c09e6f257f095e763566c72ab33c66aec205d1a Mon Sep 17 00:00:00 2001 From: whomingbird Date: Tue, 23 Apr 2024 12:20:45 +0200 Subject: [PATCH 328/344] using modern syntax and style of RoR to render a partial --- .../_resource_main_content_right.html.erb | 28 +++++++++---------- app/views/projects/show.html.erb | 22 ++++++--------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/app/views/assets/_resource_main_content_right.html.erb b/app/views/assets/_resource_main_content_right.html.erb index 36d657c979..910babeb86 100644 --- a/app/views/assets/_resource_main_content_right.html.erb +++ b/app/views/assets/_resource_main_content_right.html.erb @@ -21,26 +21,26 @@ <% if resource.respond_to?(:snapshots) %> <% if resource.latest_citable_resource %> - <%= render :partial => "assets/citation_box", locals: { doi: resource.latest_citable_resource.doi, - snapshot: resource.latest_citable_resource } %> + <%= render partial: "assets/citation_box", locals: { doi: resource.latest_citable_resource.doi, + snapshot: resource.latest_citable_resource } %> <% elsif resource.can_manage? && Seek::Config.doi_minting_enabled %> - <%= render :partial => "assets/isa_citation_instructions", locals: { resource: resource } %> + <%= render partial: "assets/isa_citation_instructions", locals: { resource: resource } %> <% end %> <% if resource.snapshots.any? %> - <%= render :partial => "snapshots/snapshots", locals: { snapshots: resource.snapshots, resource: resource } %> + <%= render partial: "snapshots/snapshots", locals: { snapshots: resource.snapshots, resource: resource } %> <% end %> <% elsif versioned_resource.respond_to?(:doi) %> <% if versioned_resource.doi.present? %> - <%= render :partial => "assets/citation_box", locals: { doi: versioned_resource.doi } %> + <%= render partial: "assets/citation_box", locals: { doi: versioned_resource.doi } %> <% elsif versioned_resource.is_git_versioned? && versioned_resource.file_exists?(Seek::WorkflowExtractors::CFF::FILENAME) %> - <%= render :partial => "assets/citation_box", locals: { blob: versioned_resource.get_blob(Seek::WorkflowExtractors::CFF::FILENAME) } %> + <%= render partial: "assets/citation_box", locals: { blob: versioned_resource.get_blob(Seek::WorkflowExtractors::CFF::FILENAME) } %> <% elsif resource.can_manage? && Seek::Config.doi_minting_enabled && resource.supports_doi? %> - <%= render :partial => "assets/citation_instructions", locals: { resource: resource, versioned_resource: versioned_resource } %> + <%= render partial: "assets/citation_instructions", locals: { resource: resource, versioned_resource: versioned_resource } %> <% end %> <% end %> <% if Seek::Config.file_templates_enabled && resource.respond_to?(:file_template) && !resource.file_template.nil? %> - <%= render :partial => "assets/file_template_box", :locals => { :resource => resource } -%> + <%= render partial: "assets/file_template_box", locals: { resource: resource } %> <% end %> <% suffix = 'the Data file' @@ -52,23 +52,23 @@ %> <% if resource.respond_to?(:license) && !hide_license %> - <%= render :partial => "assets/license_box", :locals => { :resource => resource, :versioned_resource=> versioned_resource } -%> + <%= render partial: "assets/license_box", locals: { resource: resource, versioned_resource: versioned_resource } %> <% end %> -<%= render :partial => "assets/usage_info_box",:locals => { :resource => resource } -%> +<%= render partial: "assets/usage_info_box", locals: { resource: resource } %> <% if resource.controlled_vocab_annotations? %> <%= render partial:'assets/controlled_vocab_annotations_properties_box', :locals => { :resource => resource } -%> <% end %> <% if resource.class.is_taggable? %> - <%= render :partial=>"assets/tags_box", :locals => { :resource => resource }, :no_tags_message=>"Add tags (comma separated) ..." -%> + <%= render partial: "assets/tags_box", locals: { resource: resource }, no_tags_message: "Add tags (comma separated) ..." %> <% end -%> <% if resource.respond_to? :attributions_objects -%> - <%= render :partial => "assets/resource_attributions_box", - :locals => { :resource => resource, :attributed_to => resource.attributions_objects, - :truncate_to => truncate_length_for_boxes } -%> + <%= render partial: "assets/resource_attributions_box", + locals: { resource: resource, attributed_to: resource.attributions_objects, + truncate_to: truncate_length_for_boxes } %> <% end -%> <% if resource.respond_to?(:collections) && resource.collections.any? -%> diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index c65bae0a38..7b0259fda1 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -105,27 +105,23 @@
    - <%= render :partial => "layouts/contribution_section_box_avatar", :locals => { :object => @project } -%> + <%= render partial: "layouts/contribution_section_box_avatar", locals: { object: @project } %> - <% if @project.is_discussable? -%> - <% if @project.discussion_links.select{|link| link.url.present?}.any? %> - <%= render partial: 'assets/discussion_links_box', locals: {resource: @project } -%> - <% end -%> - <% end -%> + <% if @project.is_discussable? && @project.discussion_links.any? { |link| link.url.present? } %> + <%= render partial: "assets/discussion_links_box", locals: { resource: @project } %> + <% end %> <% if @project.controlled_vocab_annotations? %> - <%= render partial:'assets/controlled_vocab_annotations_properties_box', :locals => { :resource => @project } %> + <%= render partial: "assets/controlled_vocab_annotations_properties_box", locals: { resource: @project } %> <% end %> <% if @project.can_manage? %> - <%= render :partial => 'general/storage_usage_box', locals: { programme: @project, - url: storage_report_project_path(@project)} %> + <%= render partial: "general/storage_usage_box", locals: { programme: @project, url: storage_report_project_path(@project) } %> <% end %> - <% if @project.is_taggable?%> - <%= render :partial=>"assets/tags_box", locals: {resource: @project}, :no_tags_message=>"Add tags (comma separated) ..." -%> - <% end -%> - + <% if @project.is_taggable? %> + <%= render partial: "assets/tags_box", locals: { resource: @project }, no_tags_message: "Add tags (comma separated) ..." %> + <% end %>
    From 7c6ed35e97c685900ec0181995c852a6c827190f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Apr 2024 12:53:08 +0200 Subject: [PATCH 329/344] Comment out related templates test --- .../sample_types_controller_test.rb | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/test/functional/sample_types_controller_test.rb b/test/functional/sample_types_controller_test.rb index a1ea542890..914d05f659 100644 --- a/test/functional/sample_types_controller_test.rb +++ b/test/functional/sample_types_controller_test.rb @@ -696,30 +696,28 @@ class SampleTypesControllerTest < ActionController::TestCase assert_response :forbidden end - test 'display sample type with related templates' do - person = FactoryBot.create(:person) - - template = FactoryBot.create(:min_template, contributor: person, title:'related template') - template2 = FactoryBot.create(:min_template, contributor: person, title:'unrelated template') - - # must be associated with a spreadsheet template - sample_type = FactoryBot.create(:strain_sample_type, isa_template: template, contributor: person) - - assert_equal template, sample_type.isa_template - refute_nil sample_type.template - - login_as(person.user) - - with_config_value :isa_json_compliance_enabled, true do - get :show, params: { id: sample_type.id } - assert_response :success - end - - assert_select 'div.related-items div#templates' do - assert_select 'a[href=?]', template_path(template), text: template.title - assert_select 'a[href=?]', template_path(template2), text: template2.title, count: 0 - end - end + # test 'display sample type with related templates' do + # person = FactoryBot.create(:person) + # + # template = FactoryBot.create(:min_template, contributor: person, title:'related template') + # template2 = FactoryBot.create(:min_template, contributor: person, title:'unrelated template') + # + # # must be associated with a spreadsheet template + # sample_type = FactoryBot.create(:strain_sample_type, isa_template: template, contributor: person) + # + # assert_equal template, sample_type.isa_template + # refute_nil sample_type.template + # + # login_as(person.user) + # + # get :show, params: { id: sample_type.id } + # assert_response :success + # + # assert_select 'div.related-items div#templates' do + # assert_select 'a[href=?]', template_path(template), text: template.title + # assert_select 'a[href=?]', template_path(template2), text: template2.title, count: 0 + # end + # end test 'filter sample types with template when advanced single page is enabled' do project = FactoryBot.create(:project) From 78644c6cc31756c51b5062701d7f8afed0a0e8d3 Mon Sep 17 00:00:00 2001 From: Stuart Owen Date: Mon, 22 Apr 2024 14:24:37 +0100 Subject: [PATCH 330/344] move the db:sessions:trim out of regular_maintenance_job.rb to the whenever schedule #1851 --- app/jobs/regular_maintenance_job.rb | 7 ------- config/schedule.rb | 5 +++++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/jobs/regular_maintenance_job.rb b/app/jobs/regular_maintenance_job.rb index f5ef49efa1..139fe3c52a 100644 --- a/app/jobs/regular_maintenance_job.rb +++ b/app/jobs/regular_maintenance_job.rb @@ -20,7 +20,6 @@ def perform clean_git_repositories resend_activation_emails remove_unregistered_users - trim_session check_authlookup_consistency end @@ -58,12 +57,6 @@ def remove_unregistered_users User.where(person: nil).where('created_at < ?', USER_GRACE_PERIOD.ago).destroy_all end - # trims old sessions, using the db:sessions:trim task - def trim_session - Rails.application.load_tasks - Rake::Task['db:sessions:trim'].invoke - end - # resends an activation email, for unactivated users that haven't received an email since RESEND_ACTIVATION_EMAIL_DELAY # and a total maximum of MAX_ACTIVATION_EMAILS (which will include the first one) def resend_activation_emails diff --git a/config/schedule.rb b/config/schedule.rb index 743d0d60ed..0718d9b395 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -82,3 +82,8 @@ def offset(off_hours) command "sh /seek/script/kill-long-running-soffice.sh" end end + +# trim sessions +every 1.day, at: '1:15 am' do + rake 'db:sessions:trim' +end From 1a12ccb2974df9685249d80a390e76c4163ee313 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Apr 2024 17:20:21 +0200 Subject: [PATCH 331/344] Sanitize objects input dynamic table --- .../single_page/dynamic_table.js.erb | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb index 0827ff286a..bbad60ba00 100644 --- a/app/assets/javascripts/single_page/dynamic_table.js.erb +++ b/app/assets/javascripts/single_page/dynamic_table.js.erb @@ -19,6 +19,33 @@ const defaultCols = [{ } }]; +// Sanitizes input data to prevent XSS attacks +// https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html +function sanitizeHTML(str) { + return str.replace(/[&<>"']/g, function(match) { + return { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }[match]; + }); +} + +function sanitizeData(data) { + if (typeof data === 'string' && data !== "") { + return sanitizeHTML(data); + } else if(typeof data === 'array'){ + return data.forEach((e) => { + sanitizeData(e); + }); + } else { + // Handle other data types or nested objects/arrays if necessary + return data; + } +} + const objectInputTemp = '' + '