diff --git a/.gitignore b/.gitignore index 79593f013..e4ef9f124 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,7 @@ nohup.out #backup files *~ + +#ignore package +package-lock.json +package.json diff --git a/Gemfile b/Gemfile index f7b917df1..734f55ad2 100644 --- a/Gemfile +++ b/Gemfile @@ -73,12 +73,14 @@ gem 'aasm' gem 'browser' +gem 'nokogiri-diff', '~> 0.2.0' # for comparing xml documents + # collections metadata preview # run this command to work from a local copy of the gem's repo # bundle config local.cmr_metadata_preview /path/to/local/git/repository # make sure to delete the local config when done making changes to merge into master # bundle config --delete local.cmr_metadata_preview -gem 'cmr_metadata_preview', git: 'https://git.earthdata.nasa.gov/scm/cmr/cmr_metadata_preview.git', ref: 'fff65949cc6' +gem 'cmr_metadata_preview', git: 'https://git.earthdata.nasa.gov/scm/cmr/cmr_metadata_preview.git', ref: '1f6ffd54d65' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 61ce28ff4..1626e7064 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: https://git.earthdata.nasa.gov/scm/cmr/cmr_metadata_preview.git - revision: fff65949cc62d397b0675e9fe46e962b8cf43228 - ref: fff65949cc6 + revision: 1f6ffd54d6570f9f920078a84fd51750db3c21ab + ref: 1f6ffd54d65 specs: - cmr_metadata_preview (0.2.2) + cmr_metadata_preview (0.2.3) georuby rails (~> 5.2.0) sprockets (< 4.0) @@ -195,6 +195,9 @@ GEM nio4r (2.5.2) nokogiri (1.10.9) mini_portile2 (~> 2.4.0) + nokogiri-diff (0.2.0) + nokogiri (~> 1.5) + tdiff (~> 0.3, >= 0.3.2) parallel (1.19.1) parser (2.7.1.2) ast (~> 2.4.0) @@ -297,6 +300,7 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.4.2) + tdiff (0.3.4) thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) @@ -365,6 +369,7 @@ DEPENDENCIES mini_racer momentjs-rails multi_xml + nokogiri-diff (~> 0.2.0) pg pundit rack_session_access diff --git a/app/assets/javascripts/change_current_provider.coffee b/app/assets/javascripts/change_current_provider.coffee index 540b4124c..54255c96e 100644 --- a/app/assets/javascripts/change_current_provider.coffee +++ b/app/assets/javascripts/change_current_provider.coffee @@ -74,6 +74,16 @@ $(document).ready -> "Managing this service's collection associations" when 'edit-tool' 'Editing this tool' + when 'clone-tool' + 'Cloning this tool' + when 'delete-tool' + 'Deleting this tool' + when 'reinstate-tool' + action = 'revert' + 'Reinstating this tool' + when 'revert-tool' + action = 'revert' + 'Reverting this tool' $link.data('type', action) $modal.find('span.provider').text(provider) diff --git a/app/concerns/cmr_collections_helper.rb b/app/concerns/cmr_collections_helper.rb index f7ea2ad96..4e8502eec 100644 --- a/app/concerns/cmr_collections_helper.rb +++ b/app/concerns/cmr_collections_helper.rb @@ -6,7 +6,7 @@ def get_revisions(concept_id, revision_id) # try again because CMR might be a little slow to index if it is a newly published revision attempts = 0 while attempts < 20 - revisions_response = cmr_client.get_collections({ concept_id: concept_id, all_revisions: true, include_granule_counts: true }, token) + revisions_response = cmr_client.get_collections({ concept_id: concept_id, all_revisions: true, include_granule_counts: true, sort_key: '-revision_date' }, token) revisions = if revisions_response.success? revisions_response.body.fetch('items', []) else diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index 76a925f80..379c13641 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -2,6 +2,7 @@ class CollectionsController < ManageCollectionsController include ManageMetadataHelper include CMRCollectionsHelper include CollectionsHelper + include LossReportHelper before_action :set_collection before_action :ensure_correct_collection_provider, only: [:edit, :clone, :revert, :destroy] @@ -115,6 +116,35 @@ def create_update_proposal redirect_to collection_draft_proposal_path(proposal) end + def loss_report + # When a user wants to use MMT to edit metadata that currently exists in a non-UMM form, + # it's important that they're able to see if any data loss occurs in the translation to umm. + # This method is needed to reference the appropriate helper and view for the lossiness report. + # If translated_collections contains an :error field, the error message will appear. + + # this checks the 'hide_items' url parameter that is can be manually added. Its primary use is for developers + # that need to debug using the text_output + if params[:hide_items].nil? || params[:hide_items].downcase == 'true' + hide_items = true + elsif params[:hide_items].downcase == 'false' + hide_items = false + else + translated_collections = { error: 'Unknown value for the hide_items parameter. The format should be: ".../loss_report.text?hide_items=true" or ".../loss_report.text?hide_items=false"' } + end + + translated_collections ||= prepare_translated_collections + + respond_to do |format| + if translated_collections[:error] + format.text { render plain: translated_collections[:error] } + format.json { render json: JSON.pretty_generate(translated_collections) } + else + format.text { render plain: loss_report_output(translated_collections: translated_collections, hide_items: hide_items, display: 'text') } + format.json { render json: JSON.pretty_generate(loss_report_output(translated_collections: translated_collections, hide_items: hide_items, display: 'json')) } + end + end + end + private def ensure_correct_collection_provider @@ -127,6 +157,25 @@ def ensure_correct_collection_provider render :show end + def prepare_translated_collections + original_collection_native_xml = cmr_client.get_concept(params[:id],token, {}) + return { error: 'Failed to retrieve collection from CMR' } unless original_collection_native_xml.success? + + content_type = original_collection_native_xml.headers.fetch('content-type').split(';')[0] + return { error: 'This collection is already in UMM format so there is no loss report' } if content_type.include?('application/vnd.nasa.cmr.umm+json') + + translated_collection_native_xml = cmr_client.translate_collection(JSON.pretty_generate(@collection), "application/#{Rails.configuration.umm_c_version}; charset=utf-8", content_type, skip_validation=true) + return { error: 'Failed to translate collection from UMM back to native format' } unless translated_collection_native_xml.success? + + return { + original_collection_native_xml: original_collection_native_xml.body, + translated_collection_native_xml: translated_collection_native_xml.body, + original_collection_native_hash: Hash.from_xml(original_collection_native_xml.body), + translated_collection_native_hash: Hash.from_xml(translated_collection_native_xml.body), + native_format: content_type + } + end + def set_collection @concept_id = params[:id] @revision_id = params[:revision_id] @@ -149,7 +198,7 @@ def set_collection @download_xml_options.each do |download_option| # gsub here is needed because of the iso-smap and application/iso:smap+xml format options if native_format.gsub(':','').include?(download_option[:format].gsub('-', '')) - download_option[:title].concat(' (Native)') + download_option[:title].concat(' (Native)') @download_xml_options.delete(download_option) @download_xml_options.unshift(download_option) break @@ -201,7 +250,7 @@ def proposal_mode_enabled? super end end - + def select_revision selected = @revisions.select {|r| r.fetch('meta')['revision-id'] && r.fetch('meta')['deleted'] == false && r.fetch('meta')['revision-id'].to_i < @revision_id.to_i}.first selected.blank? ? nil : selected.fetch('meta')['revision-id'] diff --git a/app/controllers/manage_metadata_controller.rb b/app/controllers/manage_metadata_controller.rb index a7d98f700..cdb465b8a 100644 --- a/app/controllers/manage_metadata_controller.rb +++ b/app/controllers/manage_metadata_controller.rb @@ -110,7 +110,7 @@ def set_variable_information # if the variable is not found, try again because CMR might be a little slow to index if it is a newly published record attempts = 0 while attempts < 20 - variables_search_response = cmr_client.get_variables(concept_id: @concept_id, all_revisions: true) + variables_search_response = cmr_client.get_variables(concept_id: @concept_id, all_revisions: true, sort_key: '-revision_date') variable_data = if variables_search_response.success? variables_search_response.body.fetch('items', []) @@ -163,7 +163,7 @@ def set_service_information # if the service is not found, try again because CMR might be a little slow to index if it is a newly published record attempts = 0 while attempts < 20 - services_search_response = cmr_client.get_services(concept_id: @concept_id, all_revisions: true) + services_search_response = cmr_client.get_services(concept_id: @concept_id, all_revisions: true, sort_key: '-revision_date') service_data = if services_search_response.success? services_search_response.body.fetch('items', []) @@ -218,7 +218,7 @@ def set_tool_information # if the tool is not found, try again because CMR might be a little slow to index if it is a newly published record attempts = 0 while attempts < 20 - tools_search_response = cmr_client.get_tools(concept_id: @concept_id, all_revisions: true) + tools_search_response = cmr_client.get_tools(concept_id: @concept_id, all_revisions: true, sort_key: '-revision_date') tool_data = if tools_search_response.success? tools_search_response.body.fetch('items', []) diff --git a/app/controllers/tools_controller.rb b/app/controllers/tools_controller.rb index acfdbda29..450fd0a80 100644 --- a/app/controllers/tools_controller.rb +++ b/app/controllers/tools_controller.rb @@ -2,10 +2,10 @@ class ToolsController < BasePublishedRecordController include ManageMetadataHelper - before_action :set_tool, only: [:show, :edit] #, :clone, :destroy, :revisions, :revert, :download_json] - before_action :set_schema, only: [:show, :edit] #, :clone, :destroy] + before_action :set_tool, only: [:show, :edit, :clone, :destroy, :revisions, :revert, :download_json] + before_action :set_schema, only: [:show, :edit, :clone, :destroy] before_action :ensure_supported_version, only: [:show, :edit] - before_action :ensure_correct_provider, only: [:edit] #, :clone, :destroy] + before_action :ensure_correct_provider, only: [:edit, :clone, :destroy] before_action :set_preview, only: [:show] # If clone is not defined like this performing the clone action leads to a `action not found error` diff --git a/app/helpers/loss_report_helper.rb b/app/helpers/loss_report_helper.rb new file mode 100644 index 000000000..7819fc4f5 --- /dev/null +++ b/app/helpers/loss_report_helper.rb @@ -0,0 +1,191 @@ +module LossReportHelper + + def loss_report_output(translated_collections:, hide_items:, display:) + # depending on the input selection (json or text) a comparison string/hash is created and displayed in-browser + # this display feature could be a good candidate for dependency injection + + orig_h = translated_collections[:original_collection_native_hash] + conv_h = translated_collections[:translated_collection_native_hash] + + # ISO and DIF collections (in XML form) contain namespaces that cause errors in the below comparison. + # Specifically, when nodes are evaluated individually, (their namespace definitions remaining at the top of the xml) + # their prefixes are undefined in the scope of the evaluation and therefore raise errors. Removing the namespaces + # eliminates this issue. + if translated_collections[:native_format].include?('iso') || translated_collections[:native_format].include?('dif') + orig = Nokogiri::XML(translated_collections[:original_collection_native_xml]) { |config| config.strict.noblanks }.remove_namespaces! + conv = Nokogiri::XML(translated_collections[:translated_collection_native_xml]) { |config| config.strict.noblanks }.remove_namespaces! + else + orig = Nokogiri::XML(translated_collections[:original_collection_native_xml]) { |config| config.strict.noblanks } + conv = Nokogiri::XML(translated_collections[:translated_collection_native_xml]) { |config| config.strict.noblanks } + end + + # This array is used to keep track of the paths that lead to arrays that have already been mapped + arr_paths = Array.new + + if display == 'text' + text_output = String.new + json_output = nil + text_output += (translated_collections[:native_format] + "\n\n") + elsif display == 'json' + json_output = Hash.new + text_output = nil + json_output['format'] = translated_collections[:native_format] + end + + # Below is the Nokogiri#diff method that is used to compare Nokogiri::XML objects. + # The 'change' item is either '+' or '-'; the 'node' item is the Nokogiri::XML::Node object + orig.diff(conv, {:added => true, :removed => true}) do |change,node| + + element = node.to_xml + path = node.parent.path.split('[')[0] + arr_path = top_level_arr_path(path, orig_h, conv_h) + + # the first layer of the following if/else structure is used to separately evaluate explicit array changes. + # This is why arr_path will evaluate true if the element in question is an array + if arr_path && path_not_checked?(arr_path, arr_paths) + arr_paths << arr_path + array_comparison(arr_path, orig_h, conv_h).each { |item| add_to_report(item[0], item[1], item[2], hide_items, display, json_output, text_output) } + elsif path_not_checked?(path, arr_paths) + # this layer of if/else separates items that contain xml (this is a nokogiri oddity that occurs where + # Nokogiri does not directly map to an item that is changed thus it still contains xml - this is the + # purpose of hash_values_and_paths), items that represent xml attribute changes, and normal changes. + if is_xml?(element) + element = Hash.from_xml(element) + hash_values_and_paths(element).each do |item| + arr_path = top_level_arr_path("#{path}/#{item['path']}", orig_h, conv_h) + # this layer of if/else structure is used to separately evaluate implicit array changes in the xml. + # This is why arr_path will evaluate true if the element in question is an array + if arr_path && path_not_checked?("#{path}/#{item['path']}", arr_paths) + if path_not_checked?(arr_path, arr_paths) + arr_paths << arr_path + array_comparison(arr_path, orig_h, conv_h).each { |item| add_to_report(item[0], item[1], item[2], hide_items, display, json_output, text_output) } + end + elsif path_not_checked?("#{path}/#{item['path']}", arr_paths) + add_to_report(change, item['value'], "#{path}/#{item['path']}", hide_items, display, json_output, text_output) + end + end + elsif (attr,val = is_attribute?(element)) + add_to_report(change, val, "#{path}/#{attr}" , hide_items, display, json_output, text_output) + else + add_to_report(change, element, path, hide_items, display, json_output, text_output) + end + end + end + return text_output if display == 'text' + return json_output if display == 'json' + end + + def is_xml?(element) + # checks if the element being passed is xml + # may be beneficial to add more checks + element.include?('<' && '') + end + + def is_attribute?(element) + # this method checks if the element being passed is an attribute change; + # TODO: it may be beneficial to add more conditions to improve accuracy + if element.include?('=') && !element.include?(' = ') + attr_val = Array.new + element.split('=').each {|item| attr_val << item.strip.delete('\\"')} + attr_val + else + false + end + end + + def path_not_checked?(arr_path, arr_paths) + # this method checks the arr_paths array to see if the path being added to + # the report has already been previously evaluated and added + arr_paths.each { |path| return false if arr_path.include?(path) } + true + end + + def top_level_arr_path(path, orig_h, conv_h) + # if an array is passed that passes through an array ie. /Contacts/Contact[0]/Role/Name + # this method would return /Contacts/Contact because Contact is the outermost array (or false if the path doesn't contain an array) + pre_translation_array, pre_translation_path = hash_navigation(path, orig_h) + post_translation_array, post_translation_path = hash_navigation(path, conv_h) + + # the following line handles a scenario where hash_navigation returns false for both pre_ and post_translation_arrays + # which means that the path passed does not exist in the original or converted collections + return path_exists = false if pre_translation_array == false && post_translation_array == false + + return pre_translation_path if pre_translation_array.is_a?(Array) + return post_translation_path if post_translation_array.is_a?(Array) + + # the number of keys must be 1 because all arrays in echo10, dif10, and iso19115 are tagged similar to: + # contact and so all array-containing tags will be the plural + # of the array name. This clause serves to identify array-containing tags when their paths aren't properly + # displayed by nokogiri + if pre_translation_array.is_a?(Hash) && pre_translation_array.keys.length == 1 && pre_translation_array[pre_translation_array.keys[0]].is_a?(Array) + return "#{pre_translation_path}/#{pre_translation_array.keys[0]}" + elsif post_translation_array.is_a?(Hash) && post_translation_array.keys.length == 1 && post_translation_array[post_translation_array.keys[0]].is_a?(Array) + return "#{post_translation_path}/#{post_translation_array.keys[0]}" + end + path_contains_array = false + end + + def add_to_report(change, element, path, hide_items, display, json_output, text_output) + @counter ||= 0 and @counter += 1 + # this function serves to preclude complex nests from forming in loss_report_output the + # following 'if' structure is intended to increase readability by eliminating nests + return text_output.concat("#{@counter}.".ljust(4)+"#{change}: #{element}".ljust(60) + path + "\n") if hide_items == false && display == 'text' + return text_output.concat("#{@counter}.".ljust(4)+"#{change}: ".ljust(3) + path + "\n") if hide_items == true && display == 'text' + return json_output["#{@counter}. #{change}: #{path}"] = element if display == 'json' + end + + def hash_values_and_paths(hash) + buckets = Array.new + hash.each do |key,val| + if val.is_a? Hash then hash_values_and_paths(val).each do |item| + item['path'] = key + '/' + item['path'] + buckets << item end + else + buckets << {'path'=> key, 'value'=> val} + end + end + buckets + end + + def hash_navigation(path, hash) + # Passed a path string and the hash being navigated. This method parses the path string and + # returns the array/value at the end of the path + current_path = String.new + path.split('/').each do |key| + if hash.is_a?(Array) + return hash, current_path + elsif hash.key?(key) && hash.is_a?(Hash) + current_path += "/#{key}" + hash = hash[key] + elsif !hash.key?(key) && key != '' + return path_exists = false, "#{current_path}/#{key}" + end + end + return hash, current_path + end + + def array_comparison(path, original_hash, converted_hash) + pre_translation_array = hash_navigation(path, original_hash)[0] + post_translation_array = hash_navigation(path, converted_hash)[0] + + pre_translation_array == false ? pre_translation_array = Array.new : pre_translation_array = Array.wrap(pre_translation_array) + post_translation_array == false ? post_translation_array = Array.new : post_translation_array = Array.wrap(post_translation_array) + + output = Array.new + (pre_translation_array - post_translation_array).each do |item| + path_with_index = path + "[#{pre_translation_array.index(item)}]" + # the following line is used to eliminate indexing confusion when there is more than one occurrence of a particular item in an array + pre_translation_array[pre_translation_array.index(item)] = item.to_s + 'item indexed' + output << ['-', item, path_with_index] + end + + (post_translation_array - pre_translation_array).each do |item| + path_with_index = path + "[#{post_translation_array.index(item)}]" + # the following line is used to eliminate indexing confusion when there is more than one occurrence of a particular item in an array + post_translation_array[post_translation_array.index(item)] = item.to_s + 'item indexed' + output << ['+', item, path_with_index] + end + output + end + +end diff --git a/app/helpers/manage_metadata_helper.rb b/app/helpers/manage_metadata_helper.rb index 43f752208..c420b2c47 100644 --- a/app/helpers/manage_metadata_helper.rb +++ b/app/helpers/manage_metadata_helper.rb @@ -80,7 +80,7 @@ def resource_type end def display_header_subtitle(metadata, type) - return unless type.downcase.include?('variable') || type.downcase.include?('service') + return unless ['variable', 'service', 'tool'].any? { |type_fragment| type.downcase.include?(type_fragment) } metadata['LongName'] || 'Long Name Not Provided' end diff --git a/app/helpers/tools_helper.rb b/app/helpers/tools_helper.rb new file mode 100644 index 000000000..19a5d5e6c --- /dev/null +++ b/app/helpers/tools_helper.rb @@ -0,0 +1,12 @@ +module ToolsHelper + def render_change_provider_tool_action_link(tool_action, concept_id, revision_id = nil) + case tool_action + when 'edit' + link_to('Edit Service', edit_tool_path(concept_id, revision_id: revision_id), class: 'is-invisible', id: 'change-provider-tool-edit') + when 'clone' + link_to('Clone Service', clone_tool_path(concept_id, revision_id: revision_id), class: 'is-invisible', id: 'change-provider-tool-clone') + when 'delete' + link_to('Delete Service', tool_path(concept_id), method: :delete, class: 'is-invisible', id: 'change-provider-tool-delete') + end + end +end \ No newline at end of file diff --git a/app/models/tool_draft.rb b/app/models/tool_draft.rb index 7695bc77f..a5282947c 100644 --- a/app/models/tool_draft.rb +++ b/app/models/tool_draft.rb @@ -15,11 +15,11 @@ def create_from_tool(tool, user, native_id) draft = self.find_or_initialize_by(native_id: native_id) draft.entry_title = tool['LongName'] draft.short_name = tool['Name'] - # else - # # Cloned Record - # draft = self.new - # tool.delete('Name') - # tool.delete('LongName') + else + # Cloned Record + draft = self.new + tool.delete('Name') + tool.delete('LongName') end draft.set_user_and_provider(user) diff --git a/app/views/shared/_footer.html.erb b/app/views/shared/_footer.html.erb index 921486ff7..fcfd0f227 100644 --- a/app/views/shared/_footer.html.erb +++ b/app/views/shared/_footer.html.erb @@ -10,18 +10,18 @@ <% unless Rails.env.test? %> - - + + <% end %> diff --git a/app/views/shared/_not_current_provider_modal.html.erb b/app/views/shared/_not_current_provider_modal.html.erb index 3843bc179..5f71b9558 100644 --- a/app/views/shared/_not_current_provider_modal.html.erb +++ b/app/views/shared/_not_current_provider_modal.html.erb @@ -14,6 +14,8 @@ <%= link_to 'Yes', revert_variable_path(options[:concept_id], revision_id: options[:revision_id]), class: 'eui-btn--blue spinner is-invisible', id: "not-current-provider-revert-link#{'-' + modal_index.to_s if modal_index}" %> <% elsif options[:service] %> <%= link_to 'Yes', revert_service_path(options[:concept_id], revision_id: options[:revision_id]), class: 'eui-btn--blue spinner is-invisible', id: "not-current-provider-revert-link#{'-' + modal_index.to_s if modal_index}" %> + <% elsif options[:tool] %> + <%= link_to 'Yes', revert_tool_path(options[:concept_id], revision_id: options[:revision_id]), class: 'eui-btn--blue spinner is-invisible', id: "not-current-provider-revert-link#{'-' + modal_index.to_s if modal_index}" %> <% else %> <%= link_to 'Yes', revert_collection_path(options[:concept_id], revision_id: options[:revision_id]), class: 'eui-btn--blue spinner is-invisible', id: 'not-current-provider-revert-link' %> <% end %> @@ -33,8 +35,8 @@ <%= link_to 'Yes', service_collection_associations_path(options[:concept_id]), class: 'eui-btn--blue spinner is-invisible', id: 'not-current-provider-manage-service-associations-link' %> <% elsif options[:tool] %> <%= link_to 'Yes', edit_tool_path(options[:concept_id], revision_id: options[:revision_id]), class: 'eui-btn--blue spinner is-invisible', id: 'not-current-provider-edit-tool-link' %> - <%#= link_to 'Yes', clone_tool_path(options[:concept_id], revision_id: options[:revision_id]), class: 'eui-btn--blue spinner is-invisible', id: 'not-current-provider-clone-tool-link' %> - <%#= link_to 'Yes', tool_path(options[:concept_id]), method: :delete, class: 'eui-btn--blue spinner is-invisible', id: 'not-current-provider-delete-tool-link' %> + <%= link_to 'Yes', clone_tool_path(options[:concept_id], revision_id: options[:revision_id]), class: 'eui-btn--blue spinner is-invisible', id: 'not-current-provider-clone-tool-link' %> + <%= link_to 'Yes', tool_path(options[:concept_id]), method: :delete, class: 'eui-btn--blue spinner is-invisible', id: 'not-current-provider-delete-tool-link' %> <% end %> <% if options[:draft] %> diff --git a/app/views/tools/revisions.html.erb b/app/views/tools/revisions.html.erb new file mode 100644 index 000000000..8a861ac4c --- /dev/null +++ b/app/views/tools/revisions.html.erb @@ -0,0 +1,115 @@ +<% content_for :header_title do %> +

<%= fetch_entry_id(@tool, 'tools') %>

+

<%= display_header_subtitle(@tool, 'tool') %>

+<% end %> + +<% if @errors && !@errors.empty? %> +
+
+
+
    + <% @errors.each do |error| %> +
  • + <%= "#{error[:field]}, " if error[:field] %> + <%= error[:error] %> + <% if error[:request_id] %> + Click here to submit feedback + <% end %> +
  • + <% end %> +
+
+
+
+<% end %> + +
+
+

Revision History

+
+ <% if @error %> +
+
+

+ This tool could not be updated. You may <%= link_to 'edit', edit_tool_path(revision_id: @revision_id) %> the tool to resolve these issues. +

+
+
+ <% end %> +
+
+ + + + + + + + + + + <% @revisions.each_with_index do |revision, index| %> + <% revision_id = revision['meta']['revision-id'] %> + + + + + + + <% end %> + +
DescriptionRevision DateAction byActions
+ <% title = "View revision #{revision_id}" %> + <%= revision_id %> - + <% if revision['meta']['deleted'] == true %> + Deleted + <% elsif index == 0 %> + Published + <%= link_to 'View', tool_path(revision_id: revision_id), title: title %> + <% else %> + Revision + <%= link_to 'View', tool_path(revision_id: revision_id), title: title %> + <% end %> + + <%= revision['meta']['revision-date'] %> + + <%= revision['meta']['user-id'] %> + + <% if @revisions.first['meta']['deleted'] == true %> + <% phrase = 'Reinstate' %> + <% confirm_phrase = 'Are you sure you want to reinstate this record?' %> + <% action = 'reinstate-tool' %> + <% else %> + <% phrase = 'Revert to this Revision' %> + <% confirm_phrase = 'Are you sure you want to revert to this revision?' %> + <% action = 'revert-tool' %> + <% end %> + + <% unless index == 0 || revision['meta']['deleted'] == true %> + <% if current_provider?(@provider_id) %> + <%= link_to phrase, "#revert-revisions-modal-#{revision_id}", class: 'display-modal' %> + <% elsif available_provider?(@provider_id) %> + <%= link_to phrase, "#not-current-provider-modal-#{revision_id}", class: 'display-modal not-current-provider', data: { 'provider': @provider_id, record_action: action } %> + <% end %> +
+ +

<%= confirm_phrase %>

+

+ No + <%= link_to 'Yes', revert_tool_path(revision_id: revision_id), class: 'eui-btn--blue spinner' %> +

+
+ <%= render partial: "shared/not_current_provider_modal", locals: { + modal_index: revision_id, + options: { + revisions: true, + tool: @tool, + concept_id: @concept_id, + revision_id: revision_id + } + } %> + <% end %> +
+
+
+
diff --git a/app/views/tools/show.html.erb b/app/views/tools/show.html.erb index 9cf647aed..19d7fa998 100644 --- a/app/views/tools/show.html.erb +++ b/app/views/tools/show.html.erb @@ -23,9 +23,7 @@ "#", id: "change-current-provider-banner-link", data: { "provider": @provider_id, action_link: "change-provider-tool-#{@record_action}" }) %>

- <%# TODO: this method does not exist yet. It should be created and used when %> - <%# additional actions are added to published Tool records %> - <%#= render_change_provider_tool_action_link(@record_action, @concept_id, @revision_id) %> + <%= render_change_provider_tool_action_link(@record_action, @concept_id, @revision_id) %> <% end %> @@ -94,34 +92,29 @@ <% end %> <% end %> - <%# TODO: All links commented out and disabled links added for MMT-2238 %> - <%# links should be re-enabled with the appropriate ticket %> - <%= link_to 'Clone Tool Record', '#', class: 'eui-btn--link bar-after disabled' %> - <%# if current_provider?(@provider_id) %> - <%#= link_to 'Clone Tool Record', clone_tool_path(revision_id: @revision_id), class: 'eui-btn--link bar-after' %> - <%# elsif available_provider?(@provider_id) %> - <%#= link_to 'Clone Tool Record', '#not-current-provider-modal', class: 'display-modal not-current-provider eui-btn--link bar-after', data: { 'provider': @provider_id, record_action: 'clone-tool' } %> - <%# end %> + <% if current_provider?(@provider_id) %> + <%= link_to 'Clone Tool Record', clone_tool_path(revision_id: @revision_id), class: 'eui-btn--link bar-after' %> + <% elsif available_provider?(@provider_id) %> + <%= link_to 'Clone Tool Record', '#not-current-provider-modal', class: 'display-modal not-current-provider eui-btn--link bar-after', data: { 'provider': @provider_id, record_action: 'clone-tool' } %> + <% end %> - <%= link_to 'Download JSON', '#', class: 'eui-btn--link disabled' %> - <%#= link_to 'Download JSON', download_json_tool_path(@concept_id, revision_id: @revision_id), class: 'eui-btn--link', target: '_blank' %> + <%= link_to 'Download JSON', download_json_tool_path(@concept_id, revision_id: @revision_id), class: 'eui-btn--link', target: '_blank' %> - <%= link_to 'Delete Tool Record', '#', class: 'display-modal delete-tool eui-btn--link bar-before disabled' %> - <%# if current_provider?(@provider_id) %> - <%#= link_to 'Delete Tool Record', "#delete-record-modal", class: 'display-modal delete-tool eui-btn--link bar-before' %> - <%# elsif available_provider?(@provider_id) %> - <%#= link_to 'Delete Tool Record', '#not-current-provider-modal', class: 'display-modal not-current-provider eui-btn--link bar-before', data: { 'provider': @provider_id, record_action: 'delete-tool', num_associated_collections: "#{@num_associated_collections}" } %> - <%# end %> + <% if current_provider?(@provider_id) %> + <%= link_to 'Delete Tool Record', "#delete-record-modal", class: 'display-modal delete-tool eui-btn--link bar-before' %> + <% elsif available_provider?(@provider_id) %> + <%= link_to 'Delete Tool Record', '#not-current-provider-modal', class: 'display-modal not-current-provider eui-btn--link bar-before', data: { 'provider': @provider_id, record_action: 'delete-tool' } %> + <% end %> - + <%= render partial: 'shared/not_current_provider_modal', locals: { options: { tool: @tool, @@ -133,8 +126,7 @@ } %>

- <%= link_to 'Revisions', '#', class: 'eui-btn--link disabled' %> - <%#= link_to "Revisions (#{@revisions.size})", tool_revisions_path, class: 'eui-btn--link disabled' %> + <%= link_to "Revisions (#{@revisions.size})", tool_revisions_path, class: 'eui-btn--link' %>

diff --git a/config/environments/production.rb b/config/environments/production.rb index dc5511069..662e251d8 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -135,7 +135,7 @@ config.subscriptions_enabled = true # Feature toggle for UMM-T - config.umm_t_enabled = false + config.umm_t_enabled = true config.cmr_env = 'ops' config.echo_env = 'ops' diff --git a/config/environments/uat.rb b/config/environments/uat.rb index 009094283..43a54ba41 100644 --- a/config/environments/uat.rb +++ b/config/environments/uat.rb @@ -115,7 +115,7 @@ config.csplog_enabled = false # Feature toggle for UMM-T - config.umm_t_enabled = false + config.umm_t_enabled = true config.cmr_env = 'uat' config.echo_env = 'uat' diff --git a/config/locales/en.yml b/config/locales/en.yml index bf9d5b6cb..3201d3cf6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -198,6 +198,17 @@ en: flash: success: 'Tool Draft Published Successfully!' error: 'Tool Draft was not published successfully' + destroy: + flash: + success: 'Tool Deleted Successfully!' + error: 'Tool was not deleted successfully' + clone: + flash: + notice: 'Records must have a unique Name and Long Name within a provider. Click here to enter a new Name and Long Name.' + revert: + flash: + success: 'Tool Revision Created Successfully!' + error: 'Tool revision was not created successfully' collection_associations: destroy: flash: diff --git a/config/routes.rb b/config/routes.rb index 4b61880ee..f7af1e8bc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,7 @@ get '/collections/:id/download_xml/:format(/:revision_id)' => 'collections#download_xml', as: 'download_collection_xml' get '/collections/:id/create_delete_proposal' => 'collections#create_delete_proposal', as: 'create_delete_proposal_collection' get '/collections/:id/create_update_proposal' => 'collections#create_update_proposal', as: 'create_update_proposal_collection' + get '/collections/:id/loss_report' => 'collections#loss_report', as: 'loss_report_collections' resource :variable_generation_processes_search, only: [:new] @@ -103,7 +104,11 @@ get '/services/:id/clone' => 'services#clone', as: 'clone_service' get '/services/:id/download_json(/:revision_id)' => 'services#download_json', as: 'download_json_service' - resources :tools, only: [:show, :create, :edit] + resources :tools, only: [:show, :create, :edit, :destroy] + get '/tools/:id/clone' => 'tools#clone', as: 'clone_tool' + get '/tools/:id/revisions' => 'tools#revisions', as: 'tool_revisions' + get '/tools/:id/download_json(/:revision_id)' => 'tools#download_json', as: 'download_json_tool' + get '/tools/:id/revert/:revision_id' => 'tools#revert', as: 'revert_tool' resources :variable_drafts, controller: 'variable_drafts', draft_type: 'VariableDraft' do member do diff --git a/lib/tasks/local_cmr.rake b/lib/tasks/local_cmr.rake index 4e5689b9b..ed0ff1f54 100644 --- a/lib/tasks/local_cmr.rake +++ b/lib/tasks/local_cmr.rake @@ -153,11 +153,11 @@ namespace :cmr do File.join(Rails.root.to_s, 'vendor', 'assets', 'javascripts', 'eui-1.0.0', 'eui.js') ] - # TODO: move to version 3 of jquery - # it is not currently understood how this section works to select jquery - # currently the preview gem is not running with version 3, but 1 + # Find the path to jquery jquery = Rails.application.config.assets.paths.select { |p| p.to_s.include?('jquery-rails') } - dependencies.unshift(File.join(jquery.first, 'jquery.js')) if jquery.any? + # Include a specific file. jquery-rails has files for each major version + # stored in the above location + dependencies.unshift(File.join(jquery.first, 'jquery3.js')) if jquery.any? js_to_uglify = dependencies.sort.map do |file| puts "- Reading #{file}" diff --git a/lib/test_cmr/load_data.rb b/lib/test_cmr/load_data.rb index be1775de0..46fd53fbf 100644 --- a/lib/test_cmr/load_data.rb +++ b/lib/test_cmr/load_data.rb @@ -678,6 +678,8 @@ def reset_provider(provider_id) end guid = "prov-guid-#{Time.now.to_i}" + # Wait for the cascade delete to finish or else we may create races in CMR + wait_for_indexing # Recreate provider in Ingest resp = connection.post do |req| diff --git a/spec/features/collection_drafts/forms/data_centers_form_spec.rb b/spec/features/collection_drafts/forms/data_centers_form_spec.rb index 1d41ce3a5..00082826e 100644 --- a/spec/features/collection_drafts/forms/data_centers_form_spec.rb +++ b/spec/features/collection_drafts/forms/data_centers_form_spec.rb @@ -27,7 +27,7 @@ within '#draft_data_centers_1' do select 'Originator', from: 'Role' - add_data_center('ESA/ED') + add_data_center_with_retry('ESA/ED') add_contact_information(type: 'data_center', single: false, button_type: 'Data Center') end diff --git a/spec/features/collection_drafts/forms/saving_data_centers_data_contacts_spec.rb b/spec/features/collection_drafts/forms/saving_data_centers_data_contacts_spec.rb index 6a31955e2..5fa7a27f0 100644 --- a/spec/features/collection_drafts/forms/saving_data_centers_data_contacts_spec.rb +++ b/spec/features/collection_drafts/forms/saving_data_centers_data_contacts_spec.rb @@ -66,7 +66,7 @@ within '.multiple.data-centers > .multiple-item-1' do select 'Originator', from: 'Role' - add_data_center('ESA/ED') + add_data_center_with_retry('ESA/ED') end within '.nav-top' do diff --git a/spec/features/collection_drafts/forms/validation_spec.rb b/spec/features/collection_drafts/forms/validation_spec.rb index ac0c80194..800af7f4f 100644 --- a/spec/features/collection_drafts/forms/validation_spec.rb +++ b/spec/features/collection_drafts/forms/validation_spec.rb @@ -290,6 +290,12 @@ fill_in 'draft_temporal_extents_0_single_date_times_0', with: '2015-07-01T00:00:00Z' end + # Bamboo spontaneously started failling this test with the apparent + # cause being that 'done' was not being clicked. Clicking something + # outside of the datepicker widget allows the done click to be + # processed correctly. Previously, it looks like the click for done + # was only exiting the single date time field. + find('#draft_temporal_extents_0_precision_of_seconds').click within '.nav-top' do click_on 'Done' end diff --git a/spec/features/collections/downloading_xml_spec.rb b/spec/features/collections/downloading_xml_spec.rb index 81ed7e69b..60107c477 100644 --- a/spec/features/collections/downloading_xml_spec.rb +++ b/spec/features/collections/downloading_xml_spec.rb @@ -26,16 +26,17 @@ before do @file = "#{Rails.root}/#{@concept_id}.echo10" click_on 'ECHO 10' - end - after do # Seems to need a brief (>0.01) pause to actually find the file. sleep(0.1) + end + + after do FileUtils.rm @file if File.exist?(@file) end it 'downloads the file' do - expect(File.exist?(@file)) + expect(File.exist?(@file)).to eq(true) end end end @@ -45,7 +46,7 @@ before do login visit manage_collections_path - + short_name = 'SPL4SMAU' fill_in 'keyword', with: short_name click_on 'Search Collections' @@ -76,16 +77,17 @@ before do @file = "#{Rails.root}/#{@concept_id}.iso-smap" click_on 'ISO 19115 (SMAP) (Native)' - end - after do # Seems to need a brief (>0.01) pause to actually find the file. sleep(0.1) + end + + after do FileUtils.rm @file if File.exist?(@file) end it 'downloads the file' do - expect(File.exist?(@file)) + expect(File.exist?(@file)).to eq(true) end end end diff --git a/spec/features/collections/loss_report_spec.rb b/spec/features/collections/loss_report_spec.rb new file mode 100644 index 000000000..2439a065b --- /dev/null +++ b/spec/features/collections/loss_report_spec.rb @@ -0,0 +1,67 @@ + +describe 'Displaying the loss report in browser' do + context 'when accessing the loss report' do + + let(:echo_concept_id) { echo_concept_id = cmr_client.get_collections({'EntryTitle': 'Anthropogenic Biomes of the World, Version 2: 1700'}).body.dig('items',0,'meta','concept-id') } + let(:dif_concept_id) { dif_concept_id = cmr_client.get_collections({'EntryTitle': '2000 Pilot Environmental Sustainability Index (ESI)'}).body.dig('items',0,'meta','concept-id') } + let(:iso_concept_id) { iso_concept_id = cmr_client.get_collections({'EntryTitle': 'SMAP L4 Global 3-hourly 9 km Surface and Rootzone Soil Moisture Analysis Update V002'}).body.dig('items',0,'meta','concept-id') } + + before do + login + end + + context 'when displaying json' do + it 'properly displays the echo json report' do + visit loss_report_collections_path(echo_concept_id, format:'json') + expect(page.text.gsub(/\s+/, "")).to have_text(File.read('spec/fixtures/loss_report_samples/loss_report_echo_sample.json').gsub(/\s+/, "")) + end + it 'properly displays the dif json report' do + visit loss_report_collections_path(dif_concept_id, format:'json') + expect(page.text.gsub(/\s+/, "")).to have_text(File.read('spec/fixtures/loss_report_samples/loss_report_dif_sample.json').gsub(/\s+/, "")) + end + + it 'properly displays the iso json report' do + visit loss_report_collections_path(iso_concept_id, format:'json') + sample_paths = JSON.parse(File.read('spec/fixtures/loss_report_samples/loss_report_iso_sample.json')).keys.map! { |path| path.split(': ').last } + sample_values = JSON.parse(File.read('spec/fixtures/loss_report_samples/loss_report_iso_sample.json')).values + page_paths = JSON.parse(page.text).keys.map! { |path| path.split(': ').last } + page_values = JSON.parse(page.text).values + + # the reason this iso example will have 2 discrepancies (seen in the bottom two 'expect' lines) is because + # every time an iso collection is translated a few 'id' attributes are generated by CMR and they are always different. + # This means that the sample reports will contain different 'id' values and therefore cannot be compared directly in this example. + # In order to bypass this issue we ignore the 'id' changes by expecting two 'id' values that are different, hence the + # '2' expectation. + + expect(sample_paths - page_paths).to be_empty + expect(page_paths - sample_paths).to be_empty + expect((sample_values - page_values).length).to be(2) + expect((page_values - sample_values).length).to be(2) + end + end + + context 'when displaying text' do + + it 'properly displays the echo text report' do + visit loss_report_collections_path(echo_concept_id, format:'text') + expect(page.text.gsub(/\s+/, "")).to have_text(File.read('spec/fixtures/loss_report_samples/loss_report_echo_sample.text').gsub(/\s+/, "")) + end + it 'properly displays the dif text report' do + visit loss_report_collections_path(dif_concept_id, format:'text') + expect(page.text.gsub(/\s+/, "")).to have_text(File.read('spec/fixtures/loss_report_samples/loss_report_dif_sample.text').gsub(/\s+/, "")) + end + it 'properly displays the iso text report' do + visit loss_report_collections_path(iso_concept_id, format:'text') + + # the following two lines extract all the paths from the Capybara page and from the sample report. + # from there, the two arrays of paths are compared to ensure the page does not hold different paths than the sample + # this is necessary because of how CMR translates ISO records. See above 'json iso report' for more details + sample_paths = File.read('spec/fixtures/loss_report_samples/loss_report_iso_sample.text').split(/\s|\n/).reject! { |path| !path.include?("/") } + page_paths = page.text.split("\s").reject! { |path| !path.include?("/") } + + expect(sample_paths - page_paths).to be_empty + expect(page_paths - sample_paths).to be_empty + end + end + end +end diff --git a/spec/features/collections/revision_list_spec.rb b/spec/features/collections/revision_list_spec.rb index 9d3c57bb9..60ba78a42 100644 --- a/spec/features/collections/revision_list_spec.rb +++ b/spec/features/collections/revision_list_spec.rb @@ -1,15 +1,19 @@ describe 'Revision list', js: true do context 'when viewing a published collection' do + before :all do + native_id = 'collection_revision_native_id' + _ingest_response, _concept_response = publish_collection_draft(native_id: native_id, revision_count: 10, short_name: 'b_test_01') + @ingest_response, @concept_response = publish_collection_draft(native_id: native_id, short_name: 'c_test_01') + end + before do login - ingest_response, @concept_response = publish_collection_draft(revision_count: 2) - - visit collection_path(ingest_response['concept-id']) + visit collection_path(@ingest_response['concept-id']) end it 'displays the number of revisions' do - expect(page).to have_content('Revisions (2)') + expect(page).to have_content('Revisions (10)') end context 'when clicking on the revision link' do @@ -27,15 +31,15 @@ end it 'displays when the revision was made' do - expect(page).to have_content(today_string, count: 2) + expect(page).to have_content(today_string, count: 10) end it 'displays what user made the revision' do - expect(page).to have_content('typical', count: 2) + expect(page).to have_content('typical', count: 10) end it 'displays the correct phrasing for reverting records' do - expect(page).to have_content('Revert to this Revision', count: 1) + expect(page).to have_content('Revert to this Revision', count: 9) end context 'when viewing an old revision' do diff --git a/spec/features/manage_cmr/subscriptions/delete_subscriptions_spec.rb b/spec/features/manage_cmr/subscriptions/delete_subscriptions_spec.rb index 8c4f366fd..bb2599082 100644 --- a/spec/features/manage_cmr/subscriptions/delete_subscriptions_spec.rb +++ b/spec/features/manage_cmr/subscriptions/delete_subscriptions_spec.rb @@ -53,13 +53,28 @@ context 'when failing to delete a subscription' do before do - # Generate an error message by deleting it underneath the 'user' - cmr_client.delete_subscription('MMT_2', @native_id, 'token') click_on 'Delete' - VCR.use_cassette('urs/rarxd5taqea', record: :none) do + # Using 'allow_any_instance_of' causes the after delete to fail as well. + # Need localhost to mock the CMR delete response to be an error. + VCR.configure do |c| + c.ignore_localhost = false + end + + VCR.use_cassette('subscriptions/failed_delete', erb: { concept_id: @ingest_response['concept_id'] }) do click_on 'Yes' end + + VCR.configure do |c| + c.ignore_localhost = true + end + end + + # TODO: Remove after CMR-6332 + after do + delete_response = cmr_client.delete_subscription('MMT_2', @native_id, 'token') + + raise unless delete_response.success? end it 'fails to delete the record' do diff --git a/spec/features/services/download_json_spec.rb b/spec/features/services/download_json_spec.rb index 7f9f8bb42..52299efd0 100644 --- a/spec/features/services/download_json_spec.rb +++ b/spec/features/services/download_json_spec.rb @@ -13,5 +13,23 @@ it 'renders the download link' do expect(page).to have_link('Download JSON', href: download_json_service_path(@ingest_response['concept-id'])) end + + context 'when downloading the json' do + before do + @file = "#{Rails.root}/#{@ingest_response['concept-id']}.json" + click_on 'Download JSON' + + # Seems to need a brief (>0.01) pause to actually find the file. + sleep(0.1) + end + + after do + FileUtils.rm @file if File.exist?(@file) + end + + it 'downloads the file' do + expect(File.exist?(@file)).to eq(true) + end + end end end diff --git a/spec/features/services/reverting_service_spec.rb b/spec/features/services/reverting_service_spec.rb index 5f0510878..d8024aa28 100644 --- a/spec/features/services/reverting_service_spec.rb +++ b/spec/features/services/reverting_service_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - describe 'Reverting to previous services', reset_provider: true, js: true do before :all do # service for simple reverting service test diff --git a/spec/features/services/revision_list_spec.rb b/spec/features/services/revision_list_spec.rb index 80a1a2c18..b560aad7f 100644 --- a/spec/features/services/revision_list_spec.rb +++ b/spec/features/services/revision_list_spec.rb @@ -1,17 +1,22 @@ -require 'rails_helper' - describe 'Service revision list', reset_provider: true, js: true do context 'when viewing a published service' do + before :all do + # CMR does not return revisions sorted by revision_id. It sorts + # by name first (and maybe other things). If the sort_key is working + # correctly, the last revision (c_test_01), should be visible on the page + native_id = 'service_revision_native_id' + _ingest_response, _concept_response = publish_service_draft(native_id: native_id, revision_count: 10, name: 'b_test_01') + @ingest_response, @concept_response = publish_service_draft(native_id: native_id, name: 'c_test_01') + end + before do login - ingest_response, @concept_response = publish_service_draft(revision_count: 2) - - visit service_path(ingest_response['concept-id']) + visit service_path(@ingest_response['concept-id']) end it 'displays the number of revisions' do - expect(page).to have_content('Revisions (2)') + expect(page).to have_content('Revisions (10)') end context 'when clicking on the revision link' do @@ -29,15 +34,19 @@ end it 'displays when the revision was made' do - expect(page).to have_content(today_string, count: 2) + expect(page).to have_content(today_string, count: 10) end it 'displays what user made the revision' do - expect(page).to have_content('typical', count: 2) + expect(page).to have_content('typical', count: 10) + end + + it 'displays the most recent revisions' do + expect(page).to have_content('11 - Published') end it 'displays the correct phrasing for reverting records' do - expect(page).to have_content('Revert to this Revision', count: 1) + expect(page).to have_content('Revert to this Revision', count: 9) end context 'when viewing an old revision' do diff --git a/spec/features/tool_drafts/create_tool_draft_from_cloning_spec.rb b/spec/features/tool_drafts/create_tool_draft_from_cloning_spec.rb new file mode 100644 index 000000000..76a02c85c --- /dev/null +++ b/spec/features/tool_drafts/create_tool_draft_from_cloning_spec.rb @@ -0,0 +1,62 @@ +describe 'Creating a tool draft from cloning a tool', reset_provider: true, js: true do + before :all do + @ingest_response, _concept_response, @native_id = publish_tool_draft + end + + after :all do + delete_response = cmr_client.delete_tool('MMT_2', @native_id, 'token') + + raise unless delete_response.success? + end + + context 'when cloning a published tool' do + before do + login + + visit tool_path(@ingest_response['concept-id']) + + click_on 'Clone Tool Record' + end + + it 'displays the draft preview page' do + within '.eui-breadcrumbs' do + expect(page).to have_content('Tool Drafts') + end + + expect(page).to have_content('Publish Tool Draft') + expect(page).to have_content('Delete Tool Draft') + expect(page).to have_content('Metadata Fields') + expect(page).to have_content('Tool Information') + end + + it 'removes the Name and Long Name from the metadata' do + within '#tool_draft_draft_name_preview' do + expect(page).to have_css('p', text: 'No value for Name provided.') + end + + within '#tool_draft_draft_long_name_preview' do + expect(page).to have_css('p', text: 'No value for Long Name provided.') + end + end + + it 'creates a new native id for the draft' do + draft = ToolDraft.last + expect(draft.native_id).to eq("mmt_tool_#{draft.id}") + end + + it 'displays a message that the draft needs a unique Name' do + expect(page).to have_content('Records must have a unique Name and Long Name within a provider. Click here to enter a new Name and Long Name.') + end + + context 'when clicking the banner message to enter a new Name' do + before do + click_on 'Click here to enter a new Name and Long Name.' + end + + it 'displays the empty Name and Long Name fields' do + expect(page).to have_field('Name', with: '') + expect(page).to have_field('Long Name', with: '') + end + end + end +end diff --git a/spec/features/tools/delete_tool_spec.rb b/spec/features/tools/delete_tool_spec.rb new file mode 100644 index 000000000..d81a0bf79 --- /dev/null +++ b/spec/features/tools/delete_tool_spec.rb @@ -0,0 +1,95 @@ +describe 'Delete tool', reset_provider: true, js: true do + before :all do + @ingested_tool, _concept_response, _native_id_1 = publish_tool_draft + + @ingested_tool_for_delete_messages, _concept_response, @native_id_2 = publish_tool_draft + end + + # Remove this section after CMR-6332 is resolved + after :all do + delete_response = cmr_client.delete_tool('MMT_2', @native_id_2, 'token') + # First tool should be deleted in the delete test + + raise unless delete_response.success? + end + + before do + login + end + + context 'when viewing a published tool' do + before do + visit tool_path(@ingested_tool['concept-id']) + end + + it 'displays a delete link' do + expect(page).to have_content('Delete Tool Record') + end + + context 'when clicking the delete link' do + before do + click_on 'Delete Tool Record' + end + + it 'displays a confirmation modal' do + expect(page).to have_content('Are you sure you want to delete this tool record?') + end + + context 'when clicking Yes' do + before do + within '#delete-record-modal' do + click_on 'Yes' + end + end + + it 'redirects to the revisions page and displays a confirmation message' do + expect(page).to have_content('Revision History') + + expect(page).to have_content('Tool Deleted Successfully!') + end + end + end + end + + context 'when deleting the tool will fail' do + before do + visit tool_path(@ingested_tool_for_delete_messages['concept-id']) + end + + context 'when CMR provides a message' do + before do + error_body = '{"errors": ["You do not have permission to perform that action."]}' + error_response = Cmr::Response.new(Faraday::Response.new(status: 401, body: JSON.parse(error_body), response_headers: {})) + allow_any_instance_of(Cmr::CmrClient).to receive(:delete_tool).and_return(error_response) + + click_on 'Delete Tool Record' + + within '#delete-record-modal' do + click_on 'Yes' + end + end + + it 'displays the CMR error message' do + expect(page).to have_css('.eui-banner--danger', text: 'You do not have permission to perform that action.') + end + end + + context 'when CMR does not provide a message' do + before do + error_body = '{"message": "useless message"}' + error_response = Cmr::Response.new(Faraday::Response.new(status: 401, body: JSON.parse(error_body), response_headers: {})) + allow_any_instance_of(Cmr::CmrClient).to receive(:delete_tool).and_return(error_response) + + click_on 'Delete Tool Record' + + within '#delete-record-modal' do + click_on 'Yes' + end + end + + it 'displays the CMR error message' do + expect(page).to have_css('.eui-banner--danger', text: 'Tool was not deleted successfully') + end + end + end +end diff --git a/spec/features/tools/download_json_spec.rb b/spec/features/tools/download_json_spec.rb new file mode 100644 index 000000000..b52ee6006 --- /dev/null +++ b/spec/features/tools/download_json_spec.rb @@ -0,0 +1,42 @@ +describe 'Downloading Tool JSON', js: true do + before :all do + @ingest_response, _concept_response, @native_id = publish_tool_draft + end + + # TODO: remove after CMR-6332 + after :all do + delete_response = cmr_client.delete_tool('MMT_2', @native_id, 'token') + + raise unless delete_response.success? + end + + context 'when viewing the tool preview page' do + before do + login + + visit tool_path(@ingest_response['concept-id']) + end + + it 'renders the download link' do + expect(page).to have_link('Download JSON', href: download_json_tool_path(@ingest_response['concept-id'])) + end + + context 'when downloading the json' do + before do + @file = "#{Rails.root}/#{@ingest_response['concept-id']}.json" + click_on 'Download JSON' + + # Seems to need a brief (>0.01) pause to actually find the file. + sleep(0.1) + end + + after do + FileUtils.rm @file if File.exist?(@file) + end + + it 'downloads the file' do + expect(File.exist?(@file)).to eq(true) + end + end + end +end diff --git a/spec/features/tools/reverting_tool_spec.rb b/spec/features/tools/reverting_tool_spec.rb new file mode 100644 index 000000000..a2ba6fa09 --- /dev/null +++ b/spec/features/tools/reverting_tool_spec.rb @@ -0,0 +1,169 @@ +describe 'Reverting to previous tools', reset_provider: true, js: true do + before :all do + # tool for simple reverting tool test + @simple_revert_ingest_response, @simple_revert_concept_response, @native_id = publish_tool_draft(revision_count: 2) + + # tool for reverting tool with many revisions + @multiple_revisions_ingest_response, @multiple_revisions_concept_response, @native_id2 = publish_tool_draft(revision_count: 4, long_name: 'Reverting Tools Test', number_revision_long_names: true) + end + + after :all do + delete_response = cmr_client.delete_tool('MMT_2', @native_id, 'token') + delete_response2 = cmr_client.delete_tool('MMT_2', @native_id2, 'token') + + raise unless delete_response.success? && delete_response2.success? + end + + before do + login + end + + context 'when the latest revision is a published tool' do + before do + visit tool_path(@simple_revert_ingest_response['concept-id']) + + click_on 'Revisions' + end + + it 'displays the correct phrasing for reverting records' do + expect(page).to have_content('Revert to this Revision', count: 1) + end + + context 'when reverting the tool' do + before do + click_on 'Revert to this Revision' + click_on 'Yes' + + wait_for_jQuery + wait_for_cmr + end + + it 'displays all the correct revision information' do + expect(page).to have_content('Revision Created Successfully!') + + expect(page).to have_content('Published', count: 1) + expect(page).to have_content('Revision View', count: 2) + expect(page).to have_content('Revert to this Revision', count: 2) + end + end + + context 'when reverting to a revision before the previous revision from a different provider context' do + context 'when visiting the revisions page from a different provider' do + before do + login(provider: 'MMT_1', providers: %w(MMT_1 MMT_2)) + + visit tool_revisions_path(@multiple_revisions_ingest_response['concept-id']) + end + + it 'displays all the correct revision information' do + within 'main header' do + expect(page).to have_content('Reverting Tools Test -- revision 04') + end + + expect(page).to have_content('Published', count: 1) + expect(page).to have_content('Revision View', count: 3) + expect(page).to have_content('Revert to this Revision', count: 3) + end + + context 'when reverting to the earliest revision' do + before do + visit tool_revisions_path(@multiple_revisions_ingest_response['concept-id']) + + within '#tool-revisions-table tbody tr:last-child' do + # make sure we are clicking on the correct link + expect(page).to have_content('1 - Revision') + + click_on 'Revert to this Revision' + end + end + + it 'displays a modal informing the user they need to switch providers' do + expect(page).to have_content('Reverting this tool requires you change your provider context to MMT_2') + end + + context 'when clicking Yes' do + before do + find('.not-current-provider-link').click + wait_for_jQuery + end + + it 'reverts the tool to the correct revision and displays the correct revision information and switches provider context' do + within 'main header' do + expect(page).to have_content('Reverting Tools Test -- revision 01') + end + + expect(page).to have_content('Published', count: 1) + expect(page).to have_content('Revision View', count: 4) + expect(page).to have_content('Revert to this Revision', count: 4) + + expect(User.first.provider_id).to eq('MMT_2') + end + end + end + end + end + + context 'when reverting the tool fails ingestion into CMR' do + before do + # Do something to the revision so it fails + # Add a new field to the metadata, similar to a field name changing + # and old metadata still having the old field name + new_concept = @simple_revert_concept_response.deep_dup + new_concept.body['BadField'] = 'Not going to work' + + allow_any_instance_of(Cmr::CmrClient).to receive(:get_concept).and_return(new_concept) + + click_on 'Revert to this Revision', match: :first + click_on 'Yes' + + wait_for_jQuery + wait_for_cmr + end + + it 'displays an error message' do + expect(page).to have_content('extraneous key [BadField] is not permitted') + end + end + end + + context 'when the latest revision is a deleted tool' do + before do + ingest_response, _concept_response, @native_id3 = publish_tool_draft + + cmr_client.delete_tool('MMT_2', @native_id3, 'token') + wait_for_cmr + + visit tool_revisions_path(ingest_response['concept-id']) + end + + it 'displays the correct phrasing for reverting records' do + expect(page).to have_content('Reinstate', count: 1) + end + + context 'when reverting the tool' do + before do + click_on 'Reinstate' + click_on 'Yes' + + wait_for_jQuery + wait_for_cmr + end + + # TODO: remove after CMR-6332 + after do + delete_response = cmr_client.delete_tool('MMT_2', @native_id3, 'token') + + raise unless delete_response.success? + end + + it 'displays all the correct revision information' do + expect(page).to have_content('Revision Created Successfully!') + + expect(page).to have_content('Published', count: 1) + expect(page).to have_content('Deleted', count: 1) + expect(page).to have_content('Revision View', count: 1) + expect(page).to have_content('Revert to this Revision', count: 1) + end + end + end +end diff --git a/spec/features/tools/revision_list_spec.rb b/spec/features/tools/revision_list_spec.rb new file mode 100644 index 000000000..aca40ffef --- /dev/null +++ b/spec/features/tools/revision_list_spec.rb @@ -0,0 +1,96 @@ +describe 'Tool revision list', reset_provider: true, js: true do + context 'when viewing a published tool' do + before :all do + # CMR does not return revisions sorted by revision_id. It sorts + # by name first (and maybe other things). If the sort_key is working + # correctly, the last revision (c_test_01), should be visible on the page + _ingest_response, _concept_response, @native_id = publish_tool_draft(revision_count: 10, name: 'b_test_01') + @ingest_response, @concept_response, _native_id = publish_tool_draft(native_id: @native_id, name: 'c_test_01') + end + + # TODO: remove after CMR-6332 + after :all do + delete_response = cmr_client.delete_tool('MMT_2', @native_id, 'token') + + raise unless delete_response.success? + end + + before do + login + + visit tool_path(@ingest_response['concept-id']) + end + + it 'displays the number of revisions' do + expect(page).to have_content('Revisions (10)') + end + + context 'when clicking on the revision link' do + before do + wait_for_cmr + click_on 'Revisions' + end + + it 'displays the revision page' do + expect(page).to have_content('Revision History') + end + + it 'displays the tool long name' do + expect(page).to have_content(@concept_response.body['LongName']) + end + + it 'displays when the revision was made' do + expect(page).to have_content(today_string, count: 10) + end + + it 'displays what user made the revision' do + expect(page).to have_content('typical', count: 10) + end + + it 'displays the most recent revisions' do + expect(page).to have_content('11 - Published') + end + + it 'displays the correct phrasing for reverting records' do + expect(page).to have_content('Revert to this Revision', count: 9) + end + + context 'when viewing an old revision' do + link_text = 'You are viewing an older revision of this tool. Click here to view the latest published version.' + before do + all('a', text: 'View').last.click + end + + it 'displays a message that the revision is old' do + expect(page).to have_link(link_text) + end + + it 'does not display a link to manage collection associations' do + expect(page).to have_no_link('Manage Collection Associations') + end + + context 'when clicking the message' do + before do + click_on link_text + end + + it 'displays the latest revision to the user' do + expect(page).to have_no_link(link_text) + end + end + end + end + + context 'when searching for the tool' do + before do + full_search(record_type: 'Tools', keyword: @concept_response.body['LongName'], provider: 'MMT_2') + end + + it 'only displays the latest revision' do + within '#tool-search-results' do + expect(page).to have_content(@concept_response.body['LongName'], count: 1) + end + end + end + end +end diff --git a/spec/features/tools/tool_permissions_spec.rb b/spec/features/tools/tool_permissions_spec.rb new file mode 100644 index 000000000..1c617a181 --- /dev/null +++ b/spec/features/tools/tool_permissions_spec.rb @@ -0,0 +1,227 @@ +describe 'Tools permissions', reset_provider: true, js: true do + let(:modal_text) { 'requires you change your provider context to MMT_2' } + + context 'when viewing a tool' do + before do + login + end + + context "when the tool's provider is in the users available providers" do + before :all do + @ingested_tool, _concept_response, @native_id_1 = publish_tool_draft + end + + after :all do + delete_response = cmr_client.delete_tool('MMT_2', @native_id_1, 'token') + + raise unless delete_response.success? + end + + before do + login(provider: 'MMT_1', providers: %w(MMT_1 MMT_2)) + + visit tool_path(@ingested_tool['concept-id']) + end + + it 'displays the action links' do + expect(page).to have_link('Edit Tool Record') + expect(page).to have_link('Clone Tool Record') + expect(page).to have_link('Delete Tool Record') + end + + context 'when clicking the edit link' do + before do + click_on 'Edit Tool Record' + end + + it 'displays a modal informing the user they need to switch providers' do + expect(page).to have_content("Editing this tool #{modal_text}") + end + + context 'when clicking Yes' do + before do + # click_on 'Yes' + find('.not-current-provider-link').click + wait_for_jQuery + end + + it 'switches the provider context' do + expect(User.first.provider_id).to eq('MMT_2') + end + + it 'creates a draft from the tool' do + expect(page).to have_content('Tool Draft Created Successfully!') + expect(Draft.where(provider_id: 'MMT_2').size).to eq(1) + end + end + end + + context 'when clicking the clone link' do + before do + click_on 'Clone Tool Record' + end + + it 'displays a modal informing the user they need to switch providers' do + expect(page).to have_content("Cloning this tool #{modal_text}") + end + + context 'when clicking Yes' do + before do + find('.not-current-provider-link').click + wait_for_jQuery + end + + it 'switches the provider context' do + expect(User.first.provider_id).to eq('MMT_2') + end + + it 'creates a draft from the tool' do + expect(page).to have_content('Records must have a unique Name and Long Name within a provider. Click here to enter a new Name and Long Name.') + expect(Draft.where(provider_id: 'MMT_2').size).to eq(1) + end + end + end + + context 'when clicking the delete link' do + before do + @ingested_tool_for_delete_modal, _concept_response, @native_id_2 = publish_tool_draft + login(provider: 'MMT_1', providers: %w(MMT_1 MMT_2)) + visit tool_path(@ingested_tool_for_delete_modal['concept-id']) + + click_on 'Delete Tool Record' + end + + it 'displays a modal informing the user they need to switch providers' do + expect(page).to have_content("Deleting this tool #{modal_text}") + + # Remove this section after CMR-6332 is resolved + delete_response = cmr_client.delete_tool('MMT_2', @native_id_2, 'token') + raise unless delete_response.success? + end + + context 'when clicking Yes' do + before do + find('.not-current-provider-link').click + wait_for_jQuery + end + + it 'switches the provider context' do + expect(User.first.provider_id).to eq('MMT_2') + end + + it 'deletes the record' do + expect(page).to have_content('Tool Deleted Successfully!') + end + end + end + + context 'when trying to visit the action paths directly' do + context 'when visiting the edit path directly' do + before do + edit_link = page.current_path + '/edit' + visit edit_link + end + + it 'displays warning banner link to change provider' do + expect(page).to have_css('.eui-banner--warn') + expect(page).to have_content('You need to change your current provider to edit this tool') + end + + context 'when clicking the warning banner link' do + before do + click_link('You need to change your current provider to edit this tool') + wait_for_jQuery + end + + it 'switches the provider context' do + expect(User.first.provider_id).to eq('MMT_2') + end + + it 'creates a draft from the tool' do + expect(page).to have_content('Tool Draft Created Successfully!') + expect(Draft.where(provider_id: 'MMT_2').size).to eq(1) + end + end + end + + context 'when visiting the clone path directly' do + before do + clone_link = page.current_path + '/clone' + visit clone_link + end + + it 'displays warning banner link to change provider' do + expect(page).to have_css('.eui-banner--warn') + expect(page).to have_content('You need to change your current provider to clone this tool') + end + + context 'when clicking the warning banner link' do + before do + click_link('You need to change your current provider to clone this tool') + wait_for_jQuery + end + + it 'switches the provider context' do + expect(User.first.provider_id).to eq('MMT_2') + end + + it 'creates a draft from the tool' do + expect(page).to have_content('Records must have a unique Name and Long Name within a provider. Click here to enter a new Name and Long Name.') + expect(Draft.where(provider_id: 'MMT_2').size).to eq(1) + end + end + end + end + end + + context 'when the tools provider is not in the users available providers' do + before do + @ingested_not_available_provider_tool, _concept_response = publish_tool_draft(provider_id: 'SEDAC') + + visit tool_path(@ingested_not_available_provider_tool['concept-id']) + end + + it 'does not display the action links' do + expect(page).to have_no_link('Edit Tool Record') + expect(page).to have_no_link('Clone Tool Record') + expect(page).to have_no_link('Delete Tool Record') + end + + context 'when trying to visit the action paths directly' do + context 'when visiting the edit path directly' do + before do + edit_link = page.current_path + '/edit' + visit edit_link + end + + it 'displays the no permissions banner message' do + expect(page).to have_css('.eui-banner--danger') + expect(page).to have_content("You don't have the appropriate permissions to edit this tool") + end + + it 'displays the Access Denied message' do + expect(page).to have_content('Access Denied') + expect(page).to have_content('It appears you do not have access to edit this content.') + end + end + + context 'when visiting the clone path directly' do + before do + clone_link = page.current_path + '/clone' + visit clone_link + end + + it 'displays the no permissions banner message' do + expect(page).to have_css('.eui-banner--danger') + expect(page).to have_content("You don't have the appropriate permissions to clone this tool") + end + + it 'displays the Access Denied message' do + expect(page).to have_content('Access Denied') + expect(page).to have_content('It appears you do not have access to clone this content.') + end + end + end + end + end +end diff --git a/spec/features/variables/download_json_spec.rb b/spec/features/variables/download_json_spec.rb index 6ac90d28d..739d26e62 100644 --- a/spec/features/variables/download_json_spec.rb +++ b/spec/features/variables/download_json_spec.rb @@ -13,5 +13,23 @@ it 'renders the download link' do expect(page).to have_link('Download JSON', href: download_json_variable_path(@ingest_response['concept-id'])) end + + context 'when downloading the json' do + before do + @file = "#{Rails.root}/#{@ingest_response['concept-id']}.json" + click_on 'Download JSON' + + # Seems to need a brief (>0.01) pause to actually find the file. + sleep(0.1) + end + + after do + FileUtils.rm @file if File.exist?(@file) + end + + it 'downloads the file' do + expect(File.exist?(@file)).to eq(true) + end + end end end diff --git a/spec/fixtures/loss_report_samples/loss_report_dif_sample.json b/spec/fixtures/loss_report_samples/loss_report_dif_sample.json new file mode 100644 index 000000000..9c84a71c2 --- /dev/null +++ b/spec/fixtures/loss_report_samples/loss_report_dif_sample.json @@ -0,0 +1,18 @@ +{ + "format": "application/dif10+xml", + "1. -: /DIF/Temporal_Coverage/Temporal_Range_Type": "Long Range", + "2. -: /DIF/Related_URL[1]": { + "URL_Content_Type": { + "Type": "VIEW DATA SET LANDING PAGE" + }, + "URL": "http://dx.doi.org/10.7927/H4NK3BZJ", + "Description": "data set DOI and homepage" + }, + "3. +: /DIF/Related_URL[1]": { + "URL_Content_Type": { + "Type": "DATA SET LANDING PAGE" + }, + "URL": "http://dx.doi.org/10.7927/H4NK3BZJ", + "Description": "data set DOI and homepage" + } +} \ No newline at end of file diff --git a/spec/fixtures/loss_report_samples/loss_report_dif_sample.text b/spec/fixtures/loss_report_samples/loss_report_dif_sample.text new file mode 100644 index 000000000..449df6003 --- /dev/null +++ b/spec/fixtures/loss_report_samples/loss_report_dif_sample.text @@ -0,0 +1,5 @@ +application/dif10+xml + +1. -: /DIF/Temporal_Coverage/Temporal_Range_Type +2. -: /DIF/Related_URL[1] +3. +: /DIF/Related_URL[1] diff --git a/spec/fixtures/loss_report_samples/loss_report_echo_sample.json b/spec/fixtures/loss_report_samples/loss_report_echo_sample.json new file mode 100644 index 000000000..42c2c5f88 --- /dev/null +++ b/spec/fixtures/loss_report_samples/loss_report_echo_sample.json @@ -0,0 +1,121 @@ +{ + "format": "application/echo10+xml", + "1. -: /Collection/Orderable": "true", + "2. -: /Collection/Visible": "true", + "3. -: /Collection/MaintenanceAndUpdateFrequency": "As needed", + "4. +: /Collection/Temporal/EndsAtPresentFlag": "false", + "5. +: /Collection/Temporal/RangeDateTime/BeginningDateTime": "1970-01-01T00:00:00.000Z", + "6. +: /Collection/Platforms/Platform/ShortName": "Not provided", + "7. +: /Collection/Platforms/Platform/LongName": "Not provided", + "8. +: /Collection/Platforms/Platform/Type": "Not provided", + "9. -: /Collection/AssociatedDIFs/DIF/EntryId": "CIESIN_SEDAC_ANTHROMES_v2_1700", + "10. -: /Collection/InsertTime": "2014-05-13T00:00:00Z", + "11. +: /Collection/InsertTime": "2014-05-13T00:00:00.000Z", + "12. -: /Collection/LastUpdate": "2015-08-04T00:00:00Z", + "13. +: /Collection/LastUpdate": "2015-08-04T00:00:00.000Z", + "14. -: /Collection/LongName": "Anthropogenic Biomes of the World, Version 2: 1700", + "15. +: /Collection/LongName": "Not provided", + "16. -: /Collection/CollectionState": "Final", + "17. +: /Collection/CollectionState": "NOT PROVIDED", + "18. -: /Collection/Price": "0", + "19. +: /Collection/Price": " 0.00", + "20. -: /Collection/SpatialKeywords/Keyword[0]": "Africa", + "21. -: /Collection/SpatialKeywords/Keyword[1]": "Asia", + "22. +: /Collection/SpatialKeywords/Keyword[0]": "AFRICA", + "23. +: /Collection/SpatialKeywords/Keyword[1]": "GAZA STRIP", + "24. -: /Collection/Contacts/Contact[0]": { + "Role": "Archive", + "HoursOfService": "9:00 A.M. to 5:00 P.M., Monday to Friday", + "OrganizationName": "Socioeconomic Data and Applications Center (SEDAC)", + "OrganizationAddresses": { + "Address": { + "StreetAddress": "CIESIN, Columbia University, 61 Route 9W, P.O. Box 1000", + "City": "Palisades", + "StateProvince": "NY", + "PostalCode": "10964", + "Country": "USA" + } + }, + "OrganizationPhones": { + "Phone": [ + { + "Number": "+1 845-365-8920", + "Type": "Telephone" + }, + { + "Number": "+1 845-365-8922", + "Type": "Fax" + } + ] + }, + "OrganizationEmails": { + "Email": "ciesin.info@ciesin.columbia.edu" + }, + "ContactPersons": { + "ContactPerson": { + "FirstName": "SEDAC", + "MiddleName": "User", + "LastName": "Services" + } + } + }, + "25. +: /Collection/Contacts/Contact[0]": { + "Role": "PROCESSOR", + "OrganizationName": "SEDAC" + }, + "26. +: /Collection/Contacts/Contact[1]": { + "Role": "ARCHIVER", + "OrganizationName": "SEDAC" + }, + "27. +: /Collection/Contacts/Contact[2]": { + "Role": "ARCHIVER", + "HoursOfService": "9:00 A.M. to 5:00 P.M., Monday to Friday", + "OrganizationName": "Socioeconomic Data and Applications Center (SEDAC)", + "OrganizationAddresses": { + "Address": { + "StreetAddress": "CIESIN, Columbia University, 61 Route 9W, P.O. Box 1000", + "City": "Palisades", + "StateProvince": "NY", + "PostalCode": "10964", + "Country": "USA" + } + }, + "OrganizationPhones": { + "Phone": [ + { + "Number": "+1 845-365-8920", + "Type": "Telephone" + }, + { + "Number": "+1 845-365-8922", + "Type": "Fax" + } + ] + }, + "OrganizationEmails": { + "Email": "ciesin.info@ciesin.columbia.edu" + }, + "ContactPersons": { + "ContactPerson": { + "FirstName": "SEDAC", + "MiddleName": "User", + "LastName": "Services", + "JobPosition": "TECHNICAL CONTACT" + } + } + }, + "28. -: /Collection/SpatialInfo/SpatialCoverageType": "Horizontal", + "29. +: /Collection/SpatialInfo/SpatialCoverageType": "HORIZONTAL", + "30. -: /Collection/OnlineResources/OnlineResource/Type": "DOI URL", + "31. +: /Collection/OnlineResources/OnlineResource/Type": "CollectionURL : DATA SET LANDING PAGE", + "32. -: /Collection/Spatial/SpatialCoverageType": "Horizontal", + "33. +: /Collection/Spatial/SpatialCoverageType": "HORIZONTAL", + "34. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/WestBoundingCoordinate": "-180.000000", + "35. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/WestBoundingCoordinate": "-180.0", + "36. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/NorthBoundingCoordinate": "90.000000", + "37. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/NorthBoundingCoordinate": "90.0", + "38. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/EastBoundingCoordinate": "180.000000", + "39. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/EastBoundingCoordinate": "180.0", + "40. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/SouthBoundingCoordinate": "-90.000000", + "41. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/SouthBoundingCoordinate": "-90.0" +} \ No newline at end of file diff --git a/spec/fixtures/loss_report_samples/loss_report_echo_sample.text b/spec/fixtures/loss_report_samples/loss_report_echo_sample.text new file mode 100644 index 000000000..3a798c2df --- /dev/null +++ b/spec/fixtures/loss_report_samples/loss_report_echo_sample.text @@ -0,0 +1,43 @@ +application/echo10+xml + +1. -: /Collection/Orderable +2. -: /Collection/Visible +3. -: /Collection/MaintenanceAndUpdateFrequency +4. +: /Collection/Temporal/EndsAtPresentFlag +5. +: /Collection/Temporal/RangeDateTime/BeginningDateTime +6. +: /Collection/Platforms/Platform/ShortName +7. +: /Collection/Platforms/Platform/LongName +8. +: /Collection/Platforms/Platform/Type +9. -: /Collection/AssociatedDIFs/DIF/EntryId +10. -: /Collection/InsertTime +11. +: /Collection/InsertTime +12. -: /Collection/LastUpdate +13. +: /Collection/LastUpdate +14. -: /Collection/LongName +15. +: /Collection/LongName +16. -: /Collection/CollectionState +17. +: /Collection/CollectionState +18. -: /Collection/Price +19. +: /Collection/Price +20. -: /Collection/SpatialKeywords/Keyword[0] +21. -: /Collection/SpatialKeywords/Keyword[1] +22. +: /Collection/SpatialKeywords/Keyword[0] +23. +: /Collection/SpatialKeywords/Keyword[1] +24. -: /Collection/Contacts/Contact[0] +25. +: /Collection/Contacts/Contact[0] +26. +: /Collection/Contacts/Contact[1] +27. +: /Collection/Contacts/Contact[2] +28. -: /Collection/SpatialInfo/SpatialCoverageType +29. +: /Collection/SpatialInfo/SpatialCoverageType +30. -: /Collection/OnlineResources/OnlineResource/Type +31. +: /Collection/OnlineResources/OnlineResource/Type +32. -: /Collection/Spatial/SpatialCoverageType +33. +: /Collection/Spatial/SpatialCoverageType +34. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/WestBoundingCoordinate +35. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/WestBoundingCoordinate +36. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/NorthBoundingCoordinate +37. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/NorthBoundingCoordinate +38. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/EastBoundingCoordinate +39. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/EastBoundingCoordinate +40. -: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/SouthBoundingCoordinate +41. +: /Collection/Spatial/HorizontalSpatialDomain/Geometry/BoundingRectangle/SouthBoundingCoordinate diff --git a/spec/fixtures/loss_report_samples/loss_report_iso_sample.json b/spec/fixtures/loss_report_samples/loss_report_iso_sample.json new file mode 100644 index 000000000..02a2a052d --- /dev/null +++ b/spec/fixtures/loss_report_samples/loss_report_iso_sample.json @@ -0,0 +1,793 @@ +{ + "format": "application/iso:smap+xml", + "1. -: /DS_Series/schemaLocation": "http://www.isotc211.org/2005/gmi http://cdn.earthdata.nasa.gov/iso/schema/1.0/ISO19115-2_EOS.xsd", + "2. -: /DS_Series/seriesMetadata/MI_Metadata/fileIdentifier/FileName": "L4_SM_aup", + "3. -: /DS_Series/seriesMetadata/MI_Metadata/characterSet/MD_CharacterSetCode": "utf8", + "4. -: /DS_Series/seriesMetadata/MI_Metadata/hierarchyLevel/MD_ScopeCode": "series", + "5. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/attributeDescription": null, + "6. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/contentType": null, + "7. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/processingLevelCode/MD_Identifier/code/CharacterString": "Not provided", + "8. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/processingLevelCode/MD_Identifier/codeSpace/CharacterString": "gov.nasa.esdis.umm.processinglevelid", + "9. -: /DS_Series/seriesMetadata/MI_Metadata/metadataStandardName/CharacterString": "ISO 19115-2 Geographic information - Metadata - Part 2: Extensions for imagery and gridded data", + "10. +: /DS_Series/seriesMetadata/MI_Metadata/dataQualityInfo/DQ_DataQuality/scope/DQ_Scope/level/MD_ScopeCode": "series", + "11. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/id": "dc714eaf5-01b3-4705-9031-f35c87e98529", + "12. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/identifier/MD_Identifier/code/CharacterString": "Not provided", + "13. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/identifier/MD_Identifier/codeSpace/CharacterString": "gov.nasa.esdis.umm.platformshortname", + "14. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/description/CharacterString": "Not provided", + "15. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/instrument/nilReason": "inapplicable", + "16. -: /DS_Series/seriesMetadata/MI_Metadata/metadataStandardVersion/CharacterString": "ISO 19115-2:2009-02-15", + "17. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[0]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "SMAP L4 Global 3-hourly 9 km Surface and Rootzone Soil Moisture Analysis Update" + }, + "date": { + "CI_Date": { + "date": { + "Date": "2016-04-29" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + }, + "edition": { + "CharacterString": "Vv2010" + }, + "identifier": [ + { + "MD_Identifier": { + "code": { + "CharacterString": "SPL4SMAU" + }, + "codeSpace": { + "CharacterString": "http://gmao.gsfc.nasa.gov" + }, + "description": { + "CharacterString": "The ECS Short Name" + } + } + }, + { + "MD_Identifier": { + "code": { + "CharacterString": "002" + }, + "codeSpace": { + "CharacterString": "gov.nasa.esdis" + }, + "description": { + "CharacterString": "The ECS Version ID" + } + } + }, + { + "MD_Identifier": { + "code": { + "Anchor": "doi:10.5067/JJY2V0GJNFRZ" + }, + "codeSpace": { + "CharacterString": "gov.nasa.esdis" + }, + "description": { + "CharacterString": "A Digital Object Identifier (DOI) that provides a persistent interoperable means to locate the SMAP Level 4 Radar data product." + } + } + } + ], + "citedResponsibleParty": [ + { + "CI_ResponsibleParty": { + "organisationName": { + "CharacterString": "National Aeronautics and Space Administration" + }, + "role": { + "CI_RoleCode": "resourceProvider" + } + } + }, + { + "CI_ResponsibleParty": { + "organisationName": { + "CharacterString": "Global Modeling and Assimilation Office" + }, + "role": { + "CI_RoleCode": "originator" + } + } + } + ], + "presentationForm": { + "CI_PresentationFormCode": "documentDigital" + }, + "series": { + "CI_Series": { + "name": { + "CharacterString": "L4_SM" + } + } + }, + "otherCitationDetails": { + "CharacterString": "The first Validated Release of the SMAP Level 4 Science Processing Software." + } + } + }, + "abstract": { + "CharacterString": "The SMAP L4_SM data product provides global, 3-hourly surface and root zone soil moisture at 9 km resolution. The L4_SM data product consists of three Collections: geophysical, analysis update and land-model-constants." + }, + "purpose": { + "CharacterString": "The SMAP L4_SM data product provides spatially and temporally complete surface and root zone soil moisture information for science and applications users." + }, + "credit": { + "CharacterString": "The software that generates the L4_SM data product and the data system that automates its production were designed and implemented at the NASA Global Modeling and Assimilation Office, Goddard Space Flight Center, Greenbelt, Maryland, USA." + }, + "status": { + "MD_ProgressCode": "onGoing" + }, + "pointOfContact": { + "CI_ResponsibleParty": { + "organisationName": { + "CharacterString": "PVC" + }, + "role": { + "CI_RoleCode": "distributor" + } + } + }, + "resourceMaintenance": { + "MD_MaintenanceInformation": { + "maintenanceAndUpdateFrequency": { + "MD_MaintenanceFrequencyCode": "As Needed" + }, + "dateOfNextUpdate": { + "Date": "2016-11-01" + }, + "updateScope": { + "MD_ScopeCode": "series" + } + } + }, + "resourceFormat": { + "MD_Format": { + "name": { + "CharacterString": "HDF5" + }, + "version": { + "CharacterString": "Version 1.8.9" + } + } + }, + "descriptiveKeywords": [ + { + "MD_Keywords": { + "keyword": [ + { + "CharacterString": "EARTH SCIENCE > LAND SURFACE > SOILS > SOIL MOISTURE/WATER CONTENT" + }, + { + "CharacterString": "EARTH SCIENCE > LAND SURFACE > SOILS > SOIL MOISTURE/WATER CONTENT > NONE > NONE > SURFACE SOIL MOISTURE" + }, + { + "CharacterString": "EARTH SCIENCE > LAND SURFACE > SOILS > SOIL MOISTURE/WATER CONTENT > NONE > NONE > ROOT ZONE SOIL MOISTURE" + } + ], + "type": { + "MD_KeywordTypeCode": "theme" + }, + "thesaurusName": { + "CI_Citation": { + "title": { + "CharacterString": "NASA/GCMD Earth Science Keywords" + }, + "date": { + "gco:nilReason": "missing" + } + } + } + } + }, + { + "MD_Keywords": { + "keyword": { + "CharacterString": "Earth Remote Sensing Instruments > Active Remote Sensing > NONE > SMAP L-BAND RADAR > SMAP L-Band Radar" + }, + "type": { + "MD_KeywordTypeCode": "theme" + }, + "thesaurusName": { + "CI_Citation": { + "title": { + "CharacterString": "NASA/GCMD Earth Science Keywords" + }, + "date": { + "gco:nilReason": "missing" + } + } + } + } + }, + { + "MD_Keywords": { + "keyword": { + "CharacterString": "Earth Observation Satellites > NASA Decadal Survey > SMAP > Soil Moisture Active and Passive Observatory" + }, + "type": { + "MD_KeywordTypeCode": "theme" + }, + "thesaurusName": { + "CI_Citation": { + "title": { + "CharacterString": "NASA/GCMD Earth Science Keywords" + }, + "date": { + "gco:nilReason": "missing" + } + } + } + } + }, + { + "MD_Keywords": { + "keyword": { + "CharacterString": "GEOGRAPHIC REGION > GLOBAL" + }, + "type": { + "MD_KeywordTypeCode": "theme" + }, + "thesaurusName": { + "CI_Citation": { + "title": { + "CharacterString": "NASA/GCMD Earth Science Keywords" + }, + "date": { + "gco:nilReason": "missing" + } + } + } + } + } + ], + "aggregationInfo": { + "MD_AggregateInformation": { + "aggregateDataSetIdentifier": { + "MD_Identifier": { + "code": { + "CharacterString": "SMAP" + } + } + }, + "associationType": { + "DS_AssociationTypeCode": "largerWorkCitation" + }, + "initiativeType": { + "DS_InitiativeTypeCode": "mission" + } + } + }, + "language": { + "CharacterString": "eng" + }, + "characterSet": { + "MD_CharacterSetCode": "utf8" + }, + "topicCategory": { + "MD_TopicCategoryCode": "geoscientificInformation" + }, + "environmentDescription": { + "CharacterString": "Data product generated by the SMAP mission in HDF5 format with metadata that conforms to the ISO 19115 model." + }, + "extent": { + "EX_Extent": { + "description": { + "CharacterString": "Global land excluding inland water and permanent ice." + }, + "geographicElement": { + "EX_GeographicBoundingBox": { + "extentTypeCode": { + "Boolean": "1" + }, + "westBoundLongitude": { + "Decimal": "-180" + }, + "eastBoundLongitude": { + "Decimal": "180" + }, + "southBoundLatitude": { + "Decimal": "-85.04456" + }, + "northBoundLatitude": { + "Decimal": "85.04456" + } + } + }, + "temporalElement": { + "EX_TemporalExtent": { + "extent": { + "TimePeriod": { + "gml:id": "swathTemporalExtent", + "beginPosition": "2015-03-31T01:30:00.000Z", + "endPosition": "2021-01-01T01:29:59.999Z" + } + } + } + } + } + } + } + }, + "18. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[1]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "Soil Moisture Active Passive Mission Level 4 Surface and Root Zone Soil Moisture (L4_SM) Product Specification Document" + }, + "date": { + "CI_Date": { + "date": { + "Date": "2015-10-31" + }, + "dateType": { + "CI_DateTypeCode": "publication" + } + } + }, + "edition": { + "CharacterString": "1.4" + }, + "identifier": { + "MD_Identifier": { + "code": { + "CharacterString": "L4_SM" + }, + "codeSpace": { + "CharacterString": "http://gmao.gsfc.nasa.gov" + }, + "description": { + "CharacterString": "A short name used by the Soil Moisture Active Passive (SMAP) mission to identify the Level 4 Radar product." + } + } + }, + "presentationForm": { + "CI_PresentationFormCode": "documentDigital" + }, + "series": { + "CI_Series": { + "name": { + "CharacterString": "L4_SM" + } + } + } + } + }, + "abstract": { + "CharacterString": "The SMAP L4_SM data product provides global, 3-hourly surface and root zone soil moisture at 9 km resolution. The L4_SM data product consists of three Collections: geophysical, analysis update and land-model-constants." + }, + "language": { + "CharacterString": "eng" + } + } + }, + "19. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[2]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "DataSetId" + }, + "date": { + "CI_Date": { + "date": { + "DateTime": "2016-09-12T11:50:19.050Z" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + } + } + }, + "abstract": { + "CharacterString": "DataSetId" + }, + "aggregationInfo": { + "MD_AggregateInformation": { + "aggregateDataSetIdentifier": { + "MD_Identifier": { + "code": { + "CharacterString": "SMAP L4 Global 3-hourly 9 km Surface and Rootzone Soil Moisture Analysis Update V002" + } + } + }, + "associationType": null + } + }, + "language": { + "CharacterString": "eng" + } + } + }, + "20. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[3]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "InsertTime" + }, + "date": { + "CI_Date": { + "date": { + "DateTime": "2016-09-08T09:16:24.835Z" + }, + "dateType": { + "CI_DateTypeCode": "creation" + } + } + } + } + }, + "abstract": { + "CharacterString": "InsertTime" + }, + "purpose": { + "CharacterString": "InsertTime" + }, + "language": { + "CharacterString": "eng" + } + } + }, + "21. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[4]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "UpdateTime" + }, + "date": { + "CI_Date": { + "date": { + "DateTime": "2016-09-12T11:50:19.050Z" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + } + } + }, + "abstract": { + "CharacterString": "UpdateTime" + }, + "purpose": { + "CharacterString": "UpdateTime" + }, + "language": { + "CharacterString": "eng" + } + } + }, + "22. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[5]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "DIFID" + }, + "date": { + "CI_Date": { + "date": { + "DateTime": "2016-09-12T11:50:19.050Z" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + }, + "identifier": { + "MD_Identifier": { + "code": { + "CharacterString": "SPL4SMAU" + } + } + } + } + }, + "abstract": { + "CharacterString": "DIFID" + }, + "purpose": { + "CharacterString": "DIFID" + }, + "language": { + "CharacterString": "eng" + } + } + }, + "23. +: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[0]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "SMAP L4 Global 3-hourly 9 km Surface and Rootzone Soil Moisture Analysis Update" + }, + "date": [ + { + "CI_Date": { + "date": { + "DateTime": "2016-04-29T00:00:00.000Z" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + }, + { + "CI_Date": { + "date": { + "DateTime": "2016-09-12T11:50:19.050Z" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + }, + { + "CI_Date": { + "date": { + "DateTime": "2016-09-08T09:16:24.835Z" + }, + "dateType": { + "CI_DateTypeCode": "creation" + } + } + } + ], + "edition": { + "CharacterString": "Vv2010" + }, + "identifier": [ + { + "MD_Identifier": { + "code": { + "CharacterString": "SPL4SMAU" + }, + "description": { + "CharacterString": "The ECS Short Name" + } + } + }, + { + "MD_Identifier": { + "code": { + "CharacterString": "002" + }, + "description": { + "CharacterString": "The ECS Version ID" + } + } + }, + { + "MD_Identifier": { + "code": { + "CharacterString": "doi:10.5067/JJY2V0GJNFRZ" + }, + "codeSpace": { + "CharacterString": "gov.nasa.esdis.umm.doi" + }, + "description": { + "CharacterString": "DOI" + } + } + } + ], + "presentationForm": { + "CI_PresentationFormCode": "documentDigital" + }, + "series": { + "CI_Series": { + "name": { + "CharacterString": "L4_SM" + } + } + }, + "otherCitationDetails": { + "CharacterString": "The first Validated Release of the SMAP Level 4 Science Processing Software." + } + } + }, + "abstract": { + "CharacterString": "The SMAP L4_SM data product provides global, 3-hourly surface and root zone soil moisture at 9 km resolution. The L4_SM data product consists of three Collections: geophysical, analysis update and land-model-constants." + }, + "purpose": { + "gco:nilReason": "missing", + "CharacterString": "The SMAP L4_SM data product provides spatially and temporally complete surface and root zone soil moisture information for science and applications users." + }, + "status": { + "MD_ProgressCode": "onGoing" + }, + "pointOfContact": { + "CI_ResponsibleParty": { + "organisationName": { + "CharacterString": "PVC" + }, + "role": { + "CI_RoleCode": "distributor" + } + } + }, + "descriptiveKeywords": [ + { + "MD_Keywords": { + "keyword": [ + { + "CharacterString": "EARTH SCIENCE > LAND SURFACE > SOILS > SOIL MOISTURE/WATER CONTENT > NONE > NONE > NONE" + }, + { + "CharacterString": "EARTH SCIENCE > LAND SURFACE > SOILS > SOIL MOISTURE/WATER CONTENT > NONE > NONE > SURFACE SOIL MOISTURE" + }, + { + "CharacterString": "EARTH SCIENCE > LAND SURFACE > SOILS > SOIL MOISTURE/WATER CONTENT > NONE > NONE > ROOT ZONE SOIL MOISTURE" + } + ], + "type": { + "MD_KeywordTypeCode": "theme" + }, + "thesaurusName": { + "gco:nilReason": "unknown" + } + } + }, + { + "MD_Keywords": { + "keyword": { + "CharacterString": "Aircraft > Not provided > Not provided > " + } + } + } + ], + "language": { + "CharacterString": "eng" + }, + "topicCategory": { + "MD_TopicCategoryCode": "geoscientificInformation" + }, + "extent": { + "EX_Extent": { + "geographicElement": { + "EX_GeographicBoundingBox": { + "extentTypeCode": { + "Boolean": "1" + }, + "westBoundLongitude": { + "Decimal": "-180.0" + }, + "eastBoundLongitude": { + "Decimal": "180.0" + }, + "southBoundLatitude": { + "Decimal": "-85.04456" + }, + "northBoundLatitude": { + "Decimal": "85.04456" + } + } + }, + "temporalElement": { + "EX_TemporalExtent": { + "extent": { + "TimePeriod": { + "gml:id": "dd8cd38ba-0984-4af1-9a10-b6e303388cc4", + "beginPosition": "2015-03-31T01:30:00.000Z", + "endPosition": "2021-01-01T01:29:59.999Z" + } + } + } + } + } + }, + "processingLevel": { + "MD_Identifier": { + "code": { + "CharacterString": "Not provided" + }, + "codeSpace": { + "CharacterString": "gov.nasa.esdis.umm.processinglevelid" + } + } + } + } + }, + "24. +: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[1]": { + "MD_DataIdentification": { + "citation": { + "CI_Citation": { + "title": { + "CharacterString": "DataSetId" + }, + "date": [ + { + "CI_Date": { + "date": { + "DateTime": "2016-04-29T00:00:00.000Z" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + }, + { + "CI_Date": { + "date": { + "DateTime": "2016-09-12T11:50:19.050Z" + }, + "dateType": { + "CI_DateTypeCode": "revision" + } + } + }, + { + "CI_Date": { + "date": { + "DateTime": "2016-09-08T09:16:24.835Z" + }, + "dateType": { + "CI_DateTypeCode": "creation" + } + } + } + ], + "citedResponsibleParty": { + "CI_ResponsibleParty": { + "organisationName": { + "CharacterString": "Global Modeling and Assimilation Office" + }, + "role": { + "CI_RoleCode": "originator" + } + } + } + } + }, + "abstract": { + "CharacterString": "DataSetId" + }, + "resourceFormat": { + "MD_Format": { + "name": { + "CharacterString": "HDF5" + }, + "version": { + "gco:nilReason": "unknown" + } + } + }, + "aggregationInfo": { + "MD_AggregateInformation": { + "aggregateDataSetIdentifier": { + "MD_Identifier": { + "code": { + "CharacterString": "SMAP L4 Global 3-hourly 9 km Surface and Rootzone Soil Moisture Analysis Update V002" + } + } + }, + "associationType": { + "DS_AssociationTypeCode": "largerWorkCitation" + } + } + }, + "language": { + "CharacterString": "eng" + } + } + }, + "25. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/organisationName/CharacterString": "NSIDC DAAC > National Snow and Ice Data Center DAAC", + "26. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/contactInfo/CI_Contact/address/CI_Address/electronicMailAddress/CharacterString": "nsidc@nsidc.org", + "27. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/contactInfo/CI_Contact/onlineResource/CI_OnlineResource/linkage/URL": "http://nsidc.org/daac/", + "28. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/role/CI_RoleCode": "pointOfContact", + "29. +: /DS_Series/seriesMetadata/MI_Metadata/contact/href": "#alaskaSARContact", + "30. -: /DS_Series/seriesMetadata/MI_Metadata/dateStamp/Date": "2016-04-29", + "31. +: /DS_Series/seriesMetadata/MI_Metadata/dateStamp/Date": "2013-01-02" +} \ No newline at end of file diff --git a/spec/fixtures/loss_report_samples/loss_report_iso_sample.text b/spec/fixtures/loss_report_samples/loss_report_iso_sample.text new file mode 100644 index 000000000..4a98bd38d --- /dev/null +++ b/spec/fixtures/loss_report_samples/loss_report_iso_sample.text @@ -0,0 +1,33 @@ +application/iso:smap+xml + +1. -: /DS_Series/schemaLocation +2. -: /DS_Series/seriesMetadata/MI_Metadata/fileIdentifier/FileName +3. -: /DS_Series/seriesMetadata/MI_Metadata/characterSet/MD_CharacterSetCode +4. -: /DS_Series/seriesMetadata/MI_Metadata/hierarchyLevel/MD_ScopeCode +5. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/attributeDescription +6. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/contentType +7. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/processingLevelCode/MD_Identifier/code/CharacterString +8. +: /DS_Series/seriesMetadata/MI_Metadata/contentInfo/MD_ImageDescription/processingLevelCode/MD_Identifier/codeSpace/CharacterString +9. -: /DS_Series/seriesMetadata/MI_Metadata/metadataStandardName/CharacterString +10. +: /DS_Series/seriesMetadata/MI_Metadata/dataQualityInfo/DQ_DataQuality/scope/DQ_Scope/level/MD_ScopeCode +11. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/id +12. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/identifier/MD_Identifier/code/CharacterString +13. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/identifier/MD_Identifier/codeSpace/CharacterString +14. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/description/CharacterString +15. +: /DS_Series/seriesMetadata/MI_Metadata/acquisitionInformation/MI_AcquisitionInformation/platform/EOS_Platform/instrument/nilReason +16. -: /DS_Series/seriesMetadata/MI_Metadata/metadataStandardVersion/CharacterString +17. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[0] +18. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[1] +19. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[2] +20. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[3] +21. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[4] +22. -: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[5] +23. +: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[0] +24. +: /DS_Series/seriesMetadata/MI_Metadata/identificationInfo[1] +25. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/organisationName/CharacterString +26. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/contactInfo/CI_Contact/address/CI_Address/electronicMailAddress/CharacterString +27. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/contactInfo/CI_Contact/onlineResource/CI_OnlineResource/linkage/URL +28. -: /DS_Series/seriesMetadata/MI_Metadata/contact/CI_ResponsibleParty/role/CI_RoleCode +29. +: /DS_Series/seriesMetadata/MI_Metadata/contact/href +30. -: /DS_Series/seriesMetadata/MI_Metadata/dateStamp/Date +31. +: /DS_Series/seriesMetadata/MI_Metadata/dateStamp/Date diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 91abc19b6..9c81587a1 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -196,6 +196,7 @@ config.include Helpers::UmmTDraftHelpers config.include Helpers::UserHelpers + # Precompile assets before running the test suite # config.before(:suite) do # Rails.application.load_tasks diff --git a/spec/support/draft_helpers.rb b/spec/support/draft_helpers.rb index bdf313501..41f3d8d05 100644 --- a/spec/support/draft_helpers.rb +++ b/spec/support/draft_helpers.rb @@ -48,6 +48,21 @@ def add_data_center(value) end end + # Bamboo started failing some tests where it seemed that the select2 was not + # opening properly. This is notably slower, so it should only be used when + # necessary. + def add_data_center_with_retry(value) + ActiveSupport::Notifications.instrument 'mmt.performance', activity: 'Helpers::DraftHelpers#add_data_center_with_retry' do + find('.select2-container .select2-selection').click + begin + find(:xpath, '//body').find('.select2-dropdown li.select2-results__option', text: value) + rescue Capybara::ElementNotFound + find('.select2-container .select2-selection').click + end + find(:xpath, '//body').find('.select2-dropdown li.select2-results__option', text: value).click + end + end + def add_person ActiveSupport::Notifications.instrument 'mmt.performance', activity: 'Helpers::DraftHelpers#add_person' do fill_in 'First Name', with: 'First Name' diff --git a/spec/vcr/subscriptions/failed_delete.yml b/spec/vcr/subscriptions/failed_delete.yml new file mode 100644 index 000000000..ba6eb1f5f --- /dev/null +++ b/spec/vcr/subscriptions/failed_delete.yml @@ -0,0 +1,398 @@ +--- +http_interactions: +- request: + method: get + uri: https://sit.urs.earthdata.nasa.gov/api/users?uids%5B%5D=rarxd5taqea + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v0.8.11 + Authorization: + - Bearer access_token + response: + status: + code: 200 + message: + headers: + server: + - nginx/1.10.2 + date: + - Tue, 02 May 2017 18:45:16 GMT + content-type: + - application/json; charset=utf-8 + transfer-encoding: + - chunked + connection: + - close + x-frame-options: + - SAMEORIGIN + x-xss-protection: + - 1; mode=block + x-content-type-options: + - nosniff + etag: + - W/"a0ea6aa58a4b79e873d6cb66d9d5bfb7" + cache-control: + - max-age=0, private, must-revalidate + x-request-id: + - 37c1c4e1-f4a7-4af5-a94b-0b51d798342b + x-runtime: + - '0.024303' + strict-transport-security: + - max-age=31536000 + body: + encoding: UTF-8 + string: '{"users":[{"uid":"rarxd5taqea","first_name":"Rvrhzxhtra","last_name":"Vetxvbpmxf","email_address":"uozydogeyyyujukey@tjbh.eyyy","country":"Macedonia","study_area":null,"registered_date":"2012-08-29T11:02:42.000Z","allow_auth_app_emails":true}]}' + http_version: + recorded_at: Tue, 02 May 2017 18:45:16 GMT +- request: + method: delete + uri: http://localhost:3002/providers/MMT_2/subscriptions/test_native_id + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Content-Type: + - application/vnd.nasa.cmr.umm+json + Client-Id: + - MMT + Echo-Token: + - ABC-2 + response: + status: + code: 500 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:19:54 GMT + content-type: + - application/xml + cmr-request-id: + - 88dd6d01-aa41-46fc-828d-705d7dec1bbb + x-request-id: + - 88dd6d01-aa41-46fc-828d-705d7dec1bbb + content-length: + - '148' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"errors" => ["Concept with native-id [test_native_id] and concept-id + [<%= concept_id %>] is already deleted."] }' + http_version: + recorded_at: Thu, 16 Jul 2020 17:19:54 GMT +- request: + method: get + uri: http://localhost:3011/permissions?provider=MMT_2&target=NON_NASA_DRAFT_APPROVER&user_id=testuser + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Accept: + - application/json; charset=utf-8 + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + cmr-request-id: + - 866be3e9-3242-45b5-9d0c-bd8df58c0ccf + x-request-id: + - 866be3e9-3242-45b5-9d0c-bd8df58c0ccf + vary: + - Accept-Encoding, User-Agent + content-length: + - '30' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"NON_NASA_DRAFT_APPROVER":[]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +- request: + method: get + uri: http://localhost:3003/subscriptions.umm_json?concept_id=<%= concept_id %> + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + content-type: + - application/vnd.nasa.cmr.umm_results+json;version=1.0; charset=utf-8 + access-control-expose-headers: + - CMR-Hits, CMR-Request-Id, X-Request-Id, CMR-Scroll-Id, CMR-Timed-Out, CMR-Shapefile-Original-Point-Count, + CMR-Shapefile-Simplified-Point-Count + access-control-allow-origin: + - "*" + cmr-hits: + - '1' + cmr-took: + - '10' + cmr-request-id: + - ea5ee97c-0b1f-493d-87da-48a3816d4030 + x-request-id: + - ea5ee97c-0b1f-493d-87da-48a3816d4030 + content-length: + - '553' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"hits":1,"took":10,"items":[{"meta":{"revision-id":14,"deleted":false,"format":"application/vnd.nasa.cmr.umm+json","provider-id":"MMT_2","user-id":"typical","native-id":"test_native_id","concept-id":"<%= concept_id %>","revision-date":"2020-07-16T17:30:48Z","concept-type":"subscription"},"umm":{"Name":"Test_Subscription_38f5c2b5-4192-4866-bcd4-413452e27b65","CollectionConceptId":"C520536-TEST","Query":"bounding_box=-10,-5,10,5&attribute\\[\\]=float,PERCENTAGE,25.5,30","SubscriberId":"rarxd5taqea","EmailAddress":"uozydogeyyyujukey@tjbh.eyyy"}}]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +- request: + method: get + uri: http://localhost:3011/permissions?provider=MMT_2&target=SUBSCRIPTION_MANAGEMENT&user_id=testuser + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Accept: + - application/json; charset=utf-8 + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + cmr-request-id: + - 0daa280f-6586-46b6-b186-bffcb8409009 + x-request-id: + - 0daa280f-6586-46b6-b186-bffcb8409009 + vary: + - Accept-Encoding, User-Agent + content-length: + - '45' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"SUBSCRIPTION_MANAGEMENT":["read","update"]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +- request: + method: get + uri: http://localhost:3011/permissions?provider=MMT_2&target=NON_NASA_DRAFT_APPROVER&user_id=testuser + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Accept: + - application/json; charset=utf-8 + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + cmr-request-id: + - 380e3f0f-b47d-4b32-9317-f28a2141f249 + x-request-id: + - 380e3f0f-b47d-4b32-9317-f28a2141f249 + vary: + - Accept-Encoding, User-Agent + content-length: + - '30' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"NON_NASA_DRAFT_APPROVER":[]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +- request: + method: get + uri: http://localhost:3011/permissions?system_object=ANY_ACL&user_id=testuser + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Accept: + - application/json; charset=utf-8 + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + cmr-request-id: + - 2e2d7e7c-57f1-41e1-b8c6-7daedce0a6da + x-request-id: + - 2e2d7e7c-57f1-41e1-b8c6-7daedce0a6da + vary: + - Accept-Encoding, User-Agent + content-length: + - '20' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"ANY_ACL":["read"]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +- request: + method: get + uri: http://localhost:3011/permissions?provider=MMT_2&target=PROVIDER_OBJECT_ACL&user_id=testuser + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Accept: + - application/json; charset=utf-8 + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + cmr-request-id: + - 24edd338-d818-4bad-9c2f-26aa216b5d48 + x-request-id: + - 24edd338-d818-4bad-9c2f-26aa216b5d48 + vary: + - Accept-Encoding, User-Agent + content-length: + - '59' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"PROVIDER_OBJECT_ACL":["read","create","update","delete"]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +- request: + method: get + uri: http://localhost:3003/subscriptions.umm_json?concept_id=<%= concept_id %> + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + content-type: + - application/vnd.nasa.cmr.umm_results+json;version=1.0; charset=utf-8 + access-control-expose-headers: + - CMR-Hits, CMR-Request-Id, X-Request-Id, CMR-Scroll-Id, CMR-Timed-Out, CMR-Shapefile-Original-Point-Count, + CMR-Shapefile-Simplified-Point-Count + access-control-allow-origin: + - "*" + cmr-hits: + - '1' + cmr-took: + - '11' + cmr-request-id: + - f64a50a3-8a1d-4bfc-be6e-23ed95ce6d73 + x-request-id: + - f64a50a3-8a1d-4bfc-be6e-23ed95ce6d73 + content-length: + - '553' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"hits":1,"took":10,"items":[{"meta":{"revision-id":14,"deleted":false,"format":"application/vnd.nasa.cmr.umm+json","provider-id":"MMT_2","user-id":"typical","native-id":"test_native_id","concept-id":"<%= concept_id %>","revision-date":"2020-07-16T17:30:48Z","concept-type":"subscription"},"umm":{"Name":"Test_Subscription_38f5c2b5-4192-4866-bcd4-413452e27b65","CollectionConceptId":"C520536-TEST","Query":"bounding_box=-10,-5,10,5&attribute\\[\\]=float,PERCENTAGE,25.5,30","SubscriberId":"rarxd5taqea","EmailAddress":"uozydogeyyyujukey@tjbh.eyyy"}}]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +- request: + method: get + uri: http://localhost:3011/permissions?provider=MMT_2&target=SUBSCRIPTION_MANAGEMENT&user_id=testuser + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v1.0.1 + Client-Id: + - MMT + Accept: + - application/json; charset=utf-8 + Echo-Token: + - ABC-2 + response: + status: + code: 200 + message: OK + headers: + date: + - Thu, 16 Jul 2020 17:30:49 GMT + cmr-request-id: + - 9faa6495-6144-42e8-9624-beed6443069a + x-request-id: + - 9faa6495-6144-42e8-9624-beed6443069a + vary: + - Accept-Encoding, User-Agent + content-length: + - '45' + server: + - Jetty(9.4.z-SNAPSHOT) + body: + encoding: UTF-8 + string: '{"SUBSCRIPTION_MANAGEMENT":["read","update"]}' + http_version: + recorded_at: Thu, 16 Jul 2020 17:30:49 GMT +recorded_with: VCR 5.1.0