From 6d52583bad1a0b6672891980995709afdc78b373 Mon Sep 17 00:00:00 2001 From: Josh Adam Date: Tue, 6 Aug 2024 11:00:01 -0500 Subject: [PATCH] [STRY0011998] Linelist Export: UI (#664) * chore: Working on setting up dropdown * chore: Working dropdown for old data export * start adding linelist ui * Change sortable_list component to render single list for future flexibility * Add add all and remove all buttons to modal * add disabled submit, samples into collapse * Add disable to linelist export button, add linelist export btn to group samples * refactor sortable list component to use has_many, fix export description dialog to more universal * fix current broken tests * start adding data export ui tests * add test previews for sortable_lists component * add fr translations, cleanup * more cleanup * change styling of list elements in sortable lists * more styling changes to list component * fix styling for component preview * change format param to linelist_format in anticipation of other future format params * fix rubucop warning * Add enable and disable states for add and remove all buttons in dialog, fix sortable scrolling * fix rebase * cleanup * remove hover from list_component, remove forceFallback from controller * attempt to fix flaky test * fix other flaky tests * try using only tbody as selector for flaky tests * change selector in groups group links as well * fix tests in data-exports-tests * remove unnecessary unless conditional and fix disabled statement --------- Co-authored-by: Chris Huynh --- app/components/viral/dropdown_component.rb | 18 +- .../sortable_list/list_component.html.erb | 23 + .../viral/sortable_list/list_component.rb | 25 + .../viral/sortable_lists_component.html.erb | 13 + .../viral/sortable_lists_component.rb | 15 + app/controllers/data_exports_controller.rb | 12 +- app/helpers/view_helper.rb | 1 + .../metadata_selection_controller.js | 88 ++++ .../controllers/sortable_list_controller.js | 26 + app/jobs/data_exports/create_job.rb | 4 +- app/models/data_export.rb | 4 +- .../_new_linelist_export_dialog.html.erb | 185 +++++++ .../_new_sample_export_dialog.html.erb | 9 +- app/views/data_exports/_summary.html.erb | 6 + app/views/data_exports/show.html.erb | 2 +- app/views/groups/samples/index.html.erb | 30 +- app/views/projects/samples/index.html.erb | 33 +- config/importmap.rb | 1 + config/locales/en.yml | 44 +- config/locales/fr.yml | 39 +- .../viral_sortable_lists_component_preview.rb | 11 + .../default.html.erb | 8 + .../three_lists.html.erb | 22 + .../three_lists_without_grouping.html.erb | 23 + .../two_lists.html.erb | 16 + .../data_exports_controller_test.rb | 25 +- test/fixtures/data_exports.yml | 4 +- test/models/data_export_test.rb | 16 +- .../data_exports/create_service_test.rb | 6 +- test/system/data_exports_test.rb | 468 +++++++++++++----- test/system/groups/group_links_test.rb | 26 +- test/system/projects/group_links_test.rb | 26 +- test/system/projects/samples_test.rb | 11 +- 33 files changed, 1005 insertions(+), 235 deletions(-) create mode 100644 app/components/viral/sortable_list/list_component.html.erb create mode 100644 app/components/viral/sortable_list/list_component.rb create mode 100644 app/components/viral/sortable_lists_component.html.erb create mode 100644 app/components/viral/sortable_lists_component.rb create mode 100644 app/javascript/controllers/data_exports/metadata_selection_controller.js create mode 100644 app/javascript/controllers/sortable_list_controller.js create mode 100644 app/views/data_exports/_new_linelist_export_dialog.html.erb create mode 100644 test/components/previews/viral_sortable_lists_component_preview.rb create mode 100644 test/components/previews/viral_sortable_lists_component_preview/default.html.erb create mode 100644 test/components/previews/viral_sortable_lists_component_preview/three_lists.html.erb create mode 100644 test/components/previews/viral_sortable_lists_component_preview/three_lists_without_grouping.html.erb create mode 100644 test/components/previews/viral_sortable_lists_component_preview/two_lists.html.erb diff --git a/app/components/viral/dropdown_component.rb b/app/components/viral/dropdown_component.rb index d5bbf7c390..6251efc962 100644 --- a/app/components/viral/dropdown_component.rb +++ b/app/components/viral/dropdown_component.rb @@ -14,13 +14,15 @@ class DropdownComponent < Viral::Component # rubocop:disable Metrics/ParameterLists def initialize(label: nil, tooltip: '', icon: nil, caret: false, trigger: TRIGGER_DEFAULT, skidding: 0, - distance: 10, dropdown_styles: '', **system_arguments) + distance: 10, dropdown_styles: '', action_link: false, action_link_value: nil, **system_arguments) @distance = distance @dropdown_styles = dropdown_styles @label = label @icon_name = icon @caret = caret @skidding = skidding + @action_link = action_link + @action_link_value = action_link_value @trigger = TRIGGER_MAPPINGS[trigger] @system_arguments = default_system_arguments(system_arguments) @@ -32,11 +34,19 @@ def initialize(label: nil, tooltip: '', icon: nil, caret: false, trigger: TRIGGE # rubocop:enable Metrics/ParameterLists def default_system_arguments(args) + data = { 'viral--dropdown-target': 'trigger' } + + if @action_link + data = data.merge({ + action: 'turbo:morph-element->action-link#idempotentConnect', + turbo_stream: true, + controller: 'action-link', + action_link_required_value: @action_link_value + }) + end args.merge({ id: "dd-#{SecureRandom.hex(10)}", - data: { - 'viral--dropdown-target': 'trigger' - }, + data:, tag: :button }) end diff --git a/app/components/viral/sortable_list/list_component.html.erb b/app/components/viral/sortable_list/list_component.html.erb new file mode 100644 index 0000000000..fbc508702a --- /dev/null +++ b/app/components/viral/sortable_list/list_component.html.erb @@ -0,0 +1,23 @@ +
+ <%= title %> +
    + <% list_items.each do |list_item| %> +
  • <%= list_item %>
  • + <% end %> +
+
diff --git a/app/components/viral/sortable_list/list_component.rb b/app/components/viral/sortable_list/list_component.rb new file mode 100644 index 0000000000..c7dea2e11b --- /dev/null +++ b/app/components/viral/sortable_list/list_component.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Viral + module SortableList + # This component creates the individual lists for the sortable_lists_component. + class ListComponent < Viral::Component + attr_reader :id, :group, :title, :list_items + + # If creating multiple lists to utilize the same list values, assign them the same group + def initialize(id: nil, group: nil, title: nil, list_items: [], **system_arguments) + @id = id + @group = group + @title = title + @list_items = list_items + @system_arguments = system_arguments + @system_arguments[:list_classes] = + class_names(system_arguments[:list_classes], + 'border border-slate-300 rounded-md block + dark:bg-slate-800 dark:border-slate-600 max-h-[225px] min-h-[225px]') + @system_arguments[:container_classes] = + class_names(system_arguments[:container_classes], 'text-slate-900 dark:text-white') + end + end + end +end diff --git a/app/components/viral/sortable_lists_component.html.erb b/app/components/viral/sortable_lists_component.html.erb new file mode 100644 index 0000000000..9b8e487a5f --- /dev/null +++ b/app/components/viral/sortable_lists_component.html.erb @@ -0,0 +1,13 @@ +
+
+ <%= title %> +
+
+ <%= description %> +
+
+
+ <% lists.each do |list| %> + <%= list %> + <% end %> +
diff --git a/app/components/viral/sortable_lists_component.rb b/app/components/viral/sortable_lists_component.rb new file mode 100644 index 0000000000..b06757ba80 --- /dev/null +++ b/app/components/viral/sortable_lists_component.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Viral + # This component creates the sortable_lists. + class SortableListsComponent < Viral::Component + attr_reader :title, :description + + renders_many :lists, Viral::SortableList::ListComponent + + def initialize(title: nil, description: nil) + @title = title + @description = description + end + end +end diff --git a/app/controllers/data_exports_controller.rb b/app/controllers/data_exports_controller.rb index 51ad24e751..6a139bb514 100644 --- a/app/controllers/data_exports_controller.rb +++ b/app/controllers/data_exports_controller.rb @@ -7,6 +7,7 @@ class DataExportsController < ApplicationController # rubocop:disable Metrics/Cl before_action :data_export, only: %i[destroy show] before_action :data_exports, only: %i[index destroy] + before_action :namespace, only: :new before_action :current_page before_action :set_default_tab, only: :show @@ -17,7 +18,7 @@ def index; end def show authorize! @data_export, to: :read_export? - return if @data_export.manifest.empty? + return if @data_export.manifest.empty? || @data_export.export_type == 'linelist' @manifest = JSON.parse(@data_export.manifest) end @@ -93,7 +94,8 @@ def destroy # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def data_export_params params.require(:data_export).permit(:name, :export_type, :email_notification, - export_parameters: [:format, :namespace_id, { ids: [], metadata_fields: [] }]) + export_parameters: [:linelist_format, :namespace_id, + { ids: [], metadata_fields: [] }]) end def data_export @@ -104,6 +106,12 @@ def data_exports @data_exports = DataExport.where(user: current_user) end + def namespace + return unless params[:export_type] == 'linelist' + + @namespace = Namespace.find(params[:namespace_id]) + end + def current_page @current_page = t(:'general.default_sidebar.data_exports') end diff --git a/app/helpers/view_helper.rb b/app/helpers/view_helper.rb index 15492726c2..aac463ca8e 100644 --- a/app/helpers/view_helper.rb +++ b/app/helpers/view_helper.rb @@ -23,6 +23,7 @@ module ViewHelper prefixed_text_input: 'Viral::Form::Prefixed::TextInputComponent', pill: 'Viral::PillComponent', select: 'Viral::Form::SelectComponent', + sortable_lists: 'Viral::SortableListsComponent', tabs: 'Viral::TabsComponent', text_input: 'Viral::Form::TextInputComponent', time_ago: 'Viral::TimeAgoComponent', diff --git a/app/javascript/controllers/data_exports/metadata_selection_controller.js b/app/javascript/controllers/data_exports/metadata_selection_controller.js new file mode 100644 index 0000000000..a48aa8bd77 --- /dev/null +++ b/app/javascript/controllers/data_exports/metadata_selection_controller.js @@ -0,0 +1,88 @@ +import { Controller } from "@hotwired/stimulus"; +import { createHiddenInput } from "utilities/form"; + +export default class extends Controller { + + static targets = ["field", "submitBtn", "addAll", "removeAll"]; + + static values = { + selectedList: { + type: String, + }, + availableList: { + type: String, + } + }; + + #disabledClasses = ["pointer-events-none", "cursor-not-allowed", "text-slate-300", "dark:text-slate-700"]; + #enabledClasses = ["underline", "hover:no-underline"] + + connect() { + this.availableList = document.getElementById(this.availableListValue) + this.selectedList = document.getElementById(this.selectedListValue) + this.fullListItems = this.availableList.querySelectorAll("li") + this.selectedList.addEventListener("mouseover", () => { this.#checkButtonStates() }) + this.availableList.addEventListener("mouseover", () => { this.#checkButtonStates() }) + } + + addAll() { + for (const item of this.fullListItems) { + this.selectedList.append(item) + } + this.#checkButtonStates() + } + + removeAll() { + for (const item of this.fullListItems) { + this.availableList.append(item) + } + this.#checkButtonStates() + } + + #checkButtonStates() { + const selected_metadata = this.selectedList.querySelectorAll("li") + const available_metadata = this.availableList.querySelectorAll("li") + if (selected_metadata.length == 0) { + this.#setSubmitButtonDisableState(true) + this.#setAddOrRemoveButtonDisableState(this.removeAllTarget, true) + this.#setAddOrRemoveButtonDisableState(this.addAllTarget, false) + } else if (available_metadata.length == 0) { + this.#setSubmitButtonDisableState(false) + this.#setAddOrRemoveButtonDisableState(this.removeAllTarget, false) + this.#setAddOrRemoveButtonDisableState(this.addAllTarget, true) + } else { + this.#setSubmitButtonDisableState(false) + this.#setAddOrRemoveButtonDisableState(this.removeAllTarget, false) + this.#setAddOrRemoveButtonDisableState(this.addAllTarget, false) + } + } + + #setSubmitButtonDisableState(disableState) { + this.submitBtnTarget.disabled = !(!disableState && this.selectedList.querySelectorAll("li").length > 0); + } + + #setAddOrRemoveButtonDisableState(button, disableState) { + if (disableState && !button.classList.contains("pointer-events-none")) { + button.classList.remove(...this.#enabledClasses) + button.classList.add(...this.#disabledClasses) + button.setAttribute("aria-disabled", "true") + } else if (!disableState && button.classList.contains("pointer-events-none")) { + button.classList.remove(...this.#disabledClasses) + button.classList.add(...this.#enabledClasses) + button.removeAttribute("aria-disabled") + } + } + + constructMetadataParams() { + const metadata_fields = this.selectedList.querySelectorAll("li") + + for (const metadata_field of metadata_fields) { + this.fieldTarget.appendChild( + createHiddenInput( + `data_export[export_parameters][metadata_fields][]`, + metadata_field.innerText + ) + ); + } + } +} diff --git a/app/javascript/controllers/sortable_list_controller.js b/app/javascript/controllers/sortable_list_controller.js new file mode 100644 index 0000000000..6f2f36e2d6 --- /dev/null +++ b/app/javascript/controllers/sortable_list_controller.js @@ -0,0 +1,26 @@ +import { Controller } from "@hotwired/stimulus" +import { Sortable } from "sortablejs"; + +export default class extends Controller { + static values = { + groupName: String, + scrollSensitivity: { + type: Number, + default: 50, + }, + scrollSpeed: { + type: Number, + default: 8, + } + } + connect() { + this.sortable = new Sortable(this.element, { + scroll: true, + scrollSensitivity: this.scrollSensitivityValue, // The number of px from div edge where scroll begins + scrollSpeed: this.scrollSpeedValue, + bubbleScroll: true, + group: this.groupNameValue, + animation: 100 + }) + } +} diff --git a/app/jobs/data_exports/create_job.rb b/app/jobs/data_exports/create_job.rb index 7214c9046f..ee22617348 100644 --- a/app/jobs/data_exports/create_job.rb +++ b/app/jobs/data_exports/create_job.rb @@ -31,7 +31,7 @@ def create_export(data_export) def attach_export(data_export, export) filename = if data_export.export_type == 'linelist' - "#{data_export.id}.#{data_export.export_parameters['format']}" + "#{data_export.id}.#{data_export.export_parameters['linelist_format']}" else "#{data_export.id}.zip" end @@ -199,7 +199,7 @@ def create_linelist_spreadsheet(data_export) else Sample.where(id: data_export.export_parameters['ids']) end - if data_export.export_parameters['format'] == 'csv' + if data_export.export_parameters['linelist_format'] == 'csv' write_csv_export(data_export, samples, namespace_type) else write_xlsx_export(data_export, samples, namespace_type) diff --git a/app/models/data_export.rb b/app/models/data_export.rb index d1731c8f77..c1101780f5 100644 --- a/app/models/data_export.rb +++ b/app/models/data_export.rb @@ -49,8 +49,8 @@ def validate_linelist_export_parameters end def validate_linelist_format - if export_parameters.key?('format') - return if %w[xlsx csv].include?(export_parameters['format']) + if export_parameters.key?('linelist_format') + return if %w[xlsx csv].include?(export_parameters['linelist_format']) errors.add(:export_parameters, I18n.t('activerecord.errors.models.data_export.attributes.export_parameters.invalid_file_format')) diff --git a/app/views/data_exports/_new_linelist_export_dialog.html.erb b/app/views/data_exports/_new_linelist_export_dialog.html.erb new file mode 100644 index 0000000000..e395046533 --- /dev/null +++ b/app/views/data_exports/_new_linelist_export_dialog.html.erb @@ -0,0 +1,185 @@ +<%= viral_dialog(open: open, size: :large) do |dialog| %> + <%= dialog.with_header(title: t(".title")) %> + <%= dialog.with_section do %> + <%= turbo_frame_tag "sample_export_dialog_content" do %> +
+ data-data-exports--metadata-selection-available-list-value=<%= t(".available") %> + data-infinite-scroll-selection-outlet='#samples-table' + data-infinite-scroll-field-name-value="data_export[export_parameters][ids][]" + data-infinite-scroll-paged-field-name-value="sample_ids[]" + data-infinite-scroll-singular-value="<%= t(".description.singular") %>" + data-infinite-scroll-plural-value="<%= t(".description.plural") %>" + class="font-normal text-slate-500 dark:text-slate-400" + > + <%= form_with( + url: list_data_exports_path, + data: { "infinite-scroll-target": "pageForm" } + ) do %> +
+ <% end %> + +
+

+ <%= t(".description.zero_html") %> +

+
+

"> + +

+ +
" + class="hidden" + aria-labelledby="<%= "accordion-collapse-heading-samples" %>" + > +
+ <%= turbo_frame_tag "list_select_samples" do %> + <%= render partial: "shared/loading/samples_list_skeleton" %> + <% end %> +
+
+
+ <%= viral_sortable_lists( + title: t(".metadata"), + description: t(".metadata_description", + available: t(".available").downcase, + selected: t(".selected").downcase + )) do |sortable_lists| %> + <%= sortable_lists.with_list( + id: t(".available"), + title: t(".available"), + list_items: @namespace.metadata_fields.sort_by{|metadata_field| metadata_field.downcase}, + group: 'metadata_selection', + container_classes: 'block mb-1 pr-2 text-sm font-medium', + list_classes: 'overflow-y-auto max-w-[356px] min-w-[356px] w-full' + ) %> + <%= sortable_lists.with_list( + id: t(".selected"), + title: t(".selected"), + group: 'metadata_selection', + container_classes: 'block mb-1 text-sm font-medium', + list_classes: 'overflow-y-auto max-w-[356px] min-w-[356px] w-full' + ) %> + <% end %> +
+ + +
+ <%= form_for(:data_export, url: data_exports_path, method: :post) do |form| %> +
+
+ <%= form.label :name, t(".format") %> +
+
+ + +
+
+ + +
+
+
+
+ <%= form.label :name, t(".name_label") %> + <%= form.text_field :name, class: "form-control" %> +
+
+ <%= form.check_box :email_notification, + { + checked: false, + class: + "w-4 h-4 mr-2.5 text-primary-600 bg-slate-100 border-slate-300 rounded + focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-slate-800 + focus:ring-2 dark:bg-slate-700 dark:border-slate-600 + ", + }, + true, + false %> + <%= form.label :email_notification, + t(".email_label"), + class: "mr-2 text-sm font-medium text-slate-900 dark:text-white" %> +
+
+ <%= form.hidden_field :export_type, value: "linelist" %> + > +
+ + +
+ <%= form.submit t(".submit_button"), + data: { + turbo_frame: "_top", + action: %(click->infinite-scroll#clear click->data-exports--metadata-selection#constructMetadataParams), + "data-exports--metadata-selection-target": "submitBtn" + }, + class: "button button--state-primary button--size-default", + disabled: true %> +
+
+ <% end %> +
+
+ <% end %> + <% end %> +<% end %> diff --git a/app/views/data_exports/_new_sample_export_dialog.html.erb b/app/views/data_exports/_new_sample_export_dialog.html.erb index 68975018cb..8e536235c0 100644 --- a/app/views/data_exports/_new_sample_export_dialog.html.erb +++ b/app/views/data_exports/_new_sample_export_dialog.html.erb @@ -57,11 +57,10 @@ { checked: false, class: - " - w-4 h-4 mr-2.5 text-primary-600 bg-slate-100 border-slate-300 rounded - focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-slate-800 - focus:ring-2 dark:bg-slate-700 dark:border-slate-600 - ", + "w-4 h-4 mr-2.5 text-primary-600 bg-slate-100 border-slate-300 rounded + focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-slate-800 + focus:ring-2 dark:bg-slate-700 dark:border-slate-600 + ", }, true, false %> diff --git a/app/views/data_exports/_summary.html.erb b/app/views/data_exports/_summary.html.erb index b5011fcdd3..3080615783 100644 --- a/app/views/data_exports/_summary.html.erb +++ b/app/views/data_exports/_summary.html.erb @@ -23,6 +23,12 @@
<%= t(:".type") %>
<%= t(:"data_exports.types.#{@data_export.export_type}") %>
+ <% if @data_export.export_type == 'linelist' %> +
+
<%= t(:".format") %>
+
<%= @data_export.export_parameters['linelist_format'] %>
+
+ <% end %>
<%= t(:".status") %>
diff --git a/app/views/data_exports/show.html.erb b/app/views/data_exports/show.html.erb index 92a51841bf..2efc0af6ce 100644 --- a/app/views/data_exports/show.html.erb +++ b/app/views/data_exports/show.html.erb @@ -60,7 +60,7 @@ controls: "data-export-tabs") do %> <%= t(:"data_exports.show.tabs.summary")%> <% end %> - <% if @data_export.status == 'ready' %> + <% if @data_export.status == 'ready' && @data_export.export_type != 'linelist' %> <%= tabs.with_tab( url: data_export_path(@data_export, tab: "preview"), selected: @tab == "preview", diff --git a/app/views/groups/samples/index.html.erb b/app/views/groups/samples/index.html.erb index 3394dfdba7..21234ae524 100644 --- a/app/views/groups/samples/index.html.erb +++ b/app/views/groups/samples/index.html.erb @@ -17,15 +17,27 @@ <% end %> <% end %> <% if allowed_to?(:export_sample_data?, @group) %> - <%= link_to t(".create_export_button"), - new_data_export_path(export_type: "sample", namespace_id: @group.id), - data: { - action: "turbo:morph-element->action-link#idempotentConnect", - turbo_stream: true, - controller: "action-link", - action_link_required_value: 1, - }, - class: "button button--size-default button--state-default action-link" %> + <%= viral_dropdown(label: t(".create_export_button.label"), aria: { label: t('.create_export_button.label') }, caret: true, action_link: true, action_link_value: 1, classes: "font-normal button button--size-default button--state-default action-link") do |dropdown| %> + <% if @group.metadata_fields.empty? %> + <% dropdown.with_item( + label: t(".create_export_button.linelist_export"), + url: new_data_export_path(export_type: "linelist", namespace_id: @group.id), + data: { + turbo_stream: true + }, + class: "flex items-center px-4 py-2 bg-slate-100 text-slate-600 dark:bg-slate-600 dark:text-slate-300 + border-slate-100 dark:border-slate-600 pointer-events-none cursor-not-allowed") %> + <% else %> + <% dropdown.with_item( + label: t(".create_export_button.linelist_export"), + url: new_data_export_path(export_type: "linelist", namespace_id: @group.id), + data: { + turbo_stream: true + } + ) %> + <% end %> + <% dropdown.with_item(label: t(".create_export_button.sample_export"), url: new_data_export_path(export_type: "sample", namespace_id: @group.id), data: { turbo_stream: true }) %> + <% end %> <% end %>
<% end %> diff --git a/app/views/projects/samples/index.html.erb b/app/views/projects/samples/index.html.erb index 29c19431ac..6f8a002ef0 100644 --- a/app/views/projects/samples/index.html.erb +++ b/app/views/projects/samples/index.html.erb @@ -40,18 +40,27 @@ class: "button button--size-default button--state-default action-link" %> <% end %> <% if allowed_to?(:export_sample_data?, @project) %> - <%= link_to t(".create_export_button"), - new_data_export_path( - export_type: "sample", - namespace_id: @project.namespace.id, - ), - data: { - action: "turbo:morph-element->action-link#idempotentConnect", - turbo_stream: true, - controller: "action-link", - action_link_required_value: 1, - }, - class: "button button--size-default button--state-default action-link" %> + <%= viral_dropdown(label: t(".create_export_button.label"), aria: { label: t('.create_export_button.label') }, caret: true, action_link: true, action_link_value: 1, classes: "font-normal button button--size-default button--state-default action-link") do |dropdown| %> + <% if @project.namespace.metadata_fields.empty? %> + <% dropdown.with_item( + label: t(".create_export_button.linelist_export"), + url: new_data_export_path(export_type: "linelist", namespace_id: @project.namespace.id), + data: { + turbo_stream: true + }, + class: "flex items-center px-4 py-2 bg-slate-100 text-slate-600 dark:bg-slate-600 dark:text-slate-300 + border-slate-100 dark:border-slate-600 pointer-events-none cursor-not-allowed") %> + <% else %> + <% dropdown.with_item( + label: t(".create_export_button.linelist_export"), + url: new_data_export_path(export_type: "linelist", namespace_id: @project.namespace.id), + data: { + turbo_stream: true + } + ) %> + <% end %> + <% dropdown.with_item(label: t(".create_export_button.sample_export"), url: new_data_export_path(export_type: "sample", namespace_id: @project.namespace.id), data: { turbo_stream: true }) %> + <% end %> <% end %> <% if allowed_to?(:update_sample?, @project) && @has_samples %> <%= link_to t("projects.samples.index.import_metadata_button"), diff --git a/config/importmap.rb b/config/importmap.rb index e8a4b0be47..d29b16f9cd 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -18,3 +18,4 @@ pin 'xlsx', to: 'https://ga.jspm.io/npm:xlsx@0.18.5/xlsx.mjs' pin 'validator', to: 'https://ga.jspm.io/npm:validator@13.11.0/index.js' pin_all_from 'app/javascript/utilities', under: 'utilities' +pin 'sortablejs', to: 'https://ga.jspm.io/npm:sortablejs@1.15.2/modular/sortable.esm.js' diff --git a/config/locales/en.yml b/config/locales/en.yml index 0d9aaf9e65..89b8873948 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -767,7 +767,10 @@ en: leave_success: "You have successfully left the group %{name}" samples: index: - create_export_button: Create Export + create_export_button: + label: Create Export + sample_export: Sample Export + linelist_export: Linelist Export title: Samples subtitle: These are the samples in %{namespace_type} %{namespace_name} workflows: @@ -1160,7 +1163,10 @@ en: transfer_button: Transfer samples import_metadata_button: Import metadata clone_button: Clone samples - create_export_button: Create Export + create_export_button: + label: Create Export + sample_export: Sample Export + linelist_export: Linelist Export delete_samples_button: Delete Samples actions: button_add_aria_label: Add new sample button @@ -1674,6 +1680,7 @@ en: summary: created_at: Created expires_at: Expires + format: Format id: ID name: Name once_ready: Available once export is ready @@ -1703,21 +1710,42 @@ en: samples: Samples submit_button: Submit description: - zero_html: This export will contain no samples. After submission, you will be redirected to the export page. While the export status is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - singular: This export will contain 1 sample. After submission, you will be redirected to the export page. While the export status is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - plural: This export will contain COUNT_PLACEHOLDER samples. After submission, you will be redirected to the export page. While the export status is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - title: Data Export + zero_html: This export will contain the files for no samples. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + singular: This export will contain the files for the 1 selected sample. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + plural: This export will contain the files for the selected COUNT_PLACEHOLDER samples. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + title: Sample Export new_analysis_export_dialog: email_label: Receive an email notification when your export is ready to download? name_label: "Export Name (Optional):" samples: Samples submit_button: Submit description: - analysis_html: This export will contain the contents of workflow execution %{id}. After submission, you will be redirected to the export page. While the export is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - title: Data Export + analysis_html: This export will contain the contents of workflow execution %{id}. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + title: Analysis Export + new_linelist_export_dialog: + add_all: Add all + available: Available + csv: '.csv' + description: + zero_html: This export will contain the metadata for no samples. + singular: This export will contain the metadata for the 1 selected sample. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + plural: This export will contain the metatada for the selected COUNT_PLACEHOLDER samples. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + email_label: Receive an email notification when your export is ready to download? + format: Format + metadata: Metadata + metadata_description: Select the metadata fields for export by moving the metadata fields from the %{available} list to the %{selected} list. The export's metadata ordering will be determined by the ordering of the %{selected} list. + name_label: "Export Name (Optional):" + remove_all: Remove all + samples: Samples + select_metadata: Select Metadata + selected: Selected + submit_button: Submit + title: Linelist Export + xlsx: '.xslx' types: sample: SAMPLE analysis: ANALYSIS + linelist: LINELIST status: processing: PROCESSING ready: READY diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 9df0052192..8336c6089f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -767,7 +767,10 @@ fr: leave_success: "You have successfully left the group %{name}" samples: index: - create_export_button: Create Export + create_export_button: + label: Create Export + sample_export: Sample Export + linelist_export: Linelist Export title: Samples subtitle: These are the samples in %{namespace_type} %{namespace_name} workflows: @@ -1674,6 +1677,7 @@ fr: summary: created_at: Created expires_at: Expires + format: Format id: ID name: Name once_ready: Available once export is ready @@ -1703,21 +1707,42 @@ fr: samples: Samples submit_button: Submit description: - zero_html: This export will contain no samples. After submission, you will be redirected to the export page. While the export status is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - singular: This export will contain 1 sample. After submission, you will be redirected to the export page. While the export status is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - plural: This export will contain COUNT_PLACEHOLDER samples. After submission, you will be redirected to the export page. While the export status is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - title: Data Export + zero_html: This export will contain the files for no samples. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + singular: This export will contain the files for the 1 selected sample. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + plural: This export will contain the files for the selected COUNT_PLACEHOLDER samples. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + title: Sample Export new_analysis_export_dialog: email_label: Receive an email notification when your export is ready to download? name_label: "Export Name (Optional):" samples: Samples submit_button: Submit description: - analysis_html: This export will contain the contents of workflow execution %{id}. After submission, you will be redirected to the export page. While the export is processing the contents of the export will be added to a downloadable zip file. Once the export status is ready, the zip file will be available for download. You will have 3 business days to download the export file before it's deleted. - title: Data Export + analysis_html: This export will contain the contents of workflow execution %{id}. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + title: Analysis Export + new_linelist_export_dialog: + add_all: Add all + available: Available + csv: '.csv' + description: + zero_html: This export will contain the metadata for no samples. + singular: This export will contain the metadata for the 1 selected sample. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + plural: This export will contain the metatada for the selected COUNT_PLACEHOLDER samples. After submission, you will be redirected to the export page. While the export status is processing, the contents of the export are being created. Once the export status is ready, the export will be available for download. You will have 3 business days to download the export before it's deleted. + email_label: Receive an email notification when your export is ready to download? + format: Format + metadata: Metadata + metadata_description: Select the metadata fields for export by moving the metadata fields from the %{available} list to the %{selected} list. The export's metadata ordering will be determined by the ordering of the %{selected} list. + name_label: "Export Name (Optional):" + remove_all: Remove all + samples: Samples + select_metadata: Select Metadata + selected: Selected + submit_button: Submit + title: Linelist Export + xlsx: '.xslx' types: sample: SAMPLE analysis: ANALYSIS + linelist: LINELIST status: processing: PROCESSING ready: READY diff --git a/test/components/previews/viral_sortable_lists_component_preview.rb b/test/components/previews/viral_sortable_lists_component_preview.rb new file mode 100644 index 0000000000..5a601ede5f --- /dev/null +++ b/test/components/previews/viral_sortable_lists_component_preview.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class ViralSortableListsComponentPreview < ViewComponent::Preview + def default; end + + def two_lists; end + + def three_lists; end + + def three_lists_without_grouping; end +end diff --git a/test/components/previews/viral_sortable_lists_component_preview/default.html.erb b/test/components/previews/viral_sortable_lists_component_preview/default.html.erb new file mode 100644 index 0000000000..7179b84413 --- /dev/null +++ b/test/components/previews/viral_sortable_lists_component_preview/default.html.erb @@ -0,0 +1,8 @@ +<%= viral_sortable_lists(title: 'A title', description: 'This is a description') do |sortable_lists| %> + <%= sortable_lists.with_list( + title: 'This is a title', + list_items: ['First List Item', 'Second List Item', 'Third List Item', 'Move us around!'], + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> +<% end %> diff --git a/test/components/previews/viral_sortable_lists_component_preview/three_lists.html.erb b/test/components/previews/viral_sortable_lists_component_preview/three_lists.html.erb new file mode 100644 index 0000000000..97850ea361 --- /dev/null +++ b/test/components/previews/viral_sortable_lists_component_preview/three_lists.html.erb @@ -0,0 +1,22 @@ +<%= viral_sortable_lists(title: 'A title', description: 'This will have three lists that share values') do |sortable_lists| %> + <%= sortable_lists.with_list( + title: 'This is a title for List 1', + list_items: ['List 1 Item 1', 'List 1 Item 2', 'List 1 Item 3', 'Move us around!'], + group: 'preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> + <%= sortable_lists.with_list( + title: 'This is a title for List 2', + list_items: ['List 2 Item 1', 'List 2 Item 2', 'List 2 Item 3'], + group: 'preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> + <%= sortable_lists.with_list( + title: 'This is a title for List 3 and this list is empty', + group: 'preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> +<% end %> diff --git a/test/components/previews/viral_sortable_lists_component_preview/three_lists_without_grouping.html.erb b/test/components/previews/viral_sortable_lists_component_preview/three_lists_without_grouping.html.erb new file mode 100644 index 0000000000..6e2d937824 --- /dev/null +++ b/test/components/previews/viral_sortable_lists_component_preview/three_lists_without_grouping.html.erb @@ -0,0 +1,23 @@ +<%= viral_sortable_lists(title: 'A title', description: 'List 1 and 2 can be shared, List 3 cannot') do |sortable_lists| %> + <%= sortable_lists.with_list( + title: 'This is a title for List 1', + list_items: ['List 1 Item 1', 'List 1 Item 2', 'List 1 Item 3', 'Move us around!'], + group: 'preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> + <%= sortable_lists.with_list( + title: 'This is a title for List 2', + list_items: ['List 2 Item 1', 'List 2 Item 2', 'List 2 Item 3'], + group: 'preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> + <%= sortable_lists.with_list( + title: 'List 3 and you cannot share items with this list', + list_items: ['List 3 Item 1', 'List 3 Item 2', 'List 3 Item 3'], + group: 'not_preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> +<% end %> diff --git a/test/components/previews/viral_sortable_lists_component_preview/two_lists.html.erb b/test/components/previews/viral_sortable_lists_component_preview/two_lists.html.erb new file mode 100644 index 0000000000..ab3eefa237 --- /dev/null +++ b/test/components/previews/viral_sortable_lists_component_preview/two_lists.html.erb @@ -0,0 +1,16 @@ +<%= viral_sortable_lists(title: 'A title', description: 'This will have two lists that share values') do |sortable_lists| %> + <%= sortable_lists.with_list( + title: 'This is a title for List 1', + list_items: ['List 1 Item 1', 'List 1 Item 2', 'List 1 Item 3', 'Move us around!'], + group: 'preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> + <%= sortable_lists.with_list( + title: 'This is a title for List 2', + list_items: ['List 2 Item 1', 'List 2 Item 2', 'List 2 Item 3'], + group: 'preview_group', + container_classes: 'basis-full block mb-1 pr-2 text-sm font-medium mt-2', + list_classes: 'overflow-y-auto !max-h-[200px] w-full' + ) %> +<% end %> diff --git a/test/controllers/data_exports_controller_test.rb b/test/controllers/data_exports_controller_test.rb index 4965395bfe..28380e34ae 100644 --- a/test/controllers/data_exports_controller_test.rb +++ b/test/controllers/data_exports_controller_test.rb @@ -46,7 +46,7 @@ class DataExportsControllerTest < ActionDispatch::IntegrationTest 'export_parameters' => { 'ids' => [@sample1.id], 'namespace_id' => @project1.namespace.id, - 'format' => 'csv', + 'linelist_format' => 'csv', 'metadata_fields' => ['metadatafield1'] } }, @@ -63,7 +63,7 @@ class DataExportsControllerTest < ActionDispatch::IntegrationTest 'export_parameters' => { 'ids' => [@sample1.id], 'namespace_id' => @project1.namespace.id, - 'format' => 'xlsx', + 'linelist_format' => 'xlsx', 'metadata_fields' => ['metadatafield1'] } }, @@ -121,7 +121,16 @@ class DataExportsControllerTest < ActionDispatch::IntegrationTest assert_response :success end - # TODO: add new linelist modal test when UI added + test 'should view new export modal with export_type linelist and namespace.type project' do + get new_data_export_path(export_type: 'linelist', 'namespace_id' => @project1.namespace.id) + assert_response :success + end + + test 'should view new export modal with export_type linelist and namespace.type group' do + group = groups(:group_one) + get new_data_export_path(export_type: 'linelist', 'namespace_id' => group.id) + assert_response :success + end test 'should create new export with only necessary params' do post data_exports_path, params: { @@ -189,14 +198,14 @@ class DataExportsControllerTest < ActionDispatch::IntegrationTest assert_response :unprocessable_entity end - test 'should not create new linelist export with invalid format param' do + test 'should not create new linelist export with invalid linelist_format param' do post data_exports_path(format: :turbo_stream), params: { data_export: { export_type: 'linelist', export_parameters: { 'ids' => [@sample1.id], 'namespace_id' => @project1.namespace.id, - 'format' => 'invalid_format', + 'linelist_format' => 'invalid_format', 'metadata_fields' => ['metadatafield1'] } } } @@ -209,7 +218,7 @@ class DataExportsControllerTest < ActionDispatch::IntegrationTest data_export: { export_type: 'linelist', export_parameters: { 'ids' => [@sample1.id], - 'format' => 'xlsx', + 'linelist_format' => 'xlsx', 'metadata_fields' => ['metadatafield1'] } } } @@ -223,7 +232,7 @@ class DataExportsControllerTest < ActionDispatch::IntegrationTest export_type: 'linelist', export_parameters: { 'ids' => [@sample1.id], 'namespace_id' => 'invalid_id', - 'format' => 'csv', + 'linelist_format' => 'csv', 'metadata_fields' => ['metadatafield1'] } } } @@ -237,7 +246,7 @@ class DataExportsControllerTest < ActionDispatch::IntegrationTest export_type: 'linelist', export_parameters: { 'ids' => [@sample1.id], 'namespace_id' => 'invalid_id', - 'format' => 'csv' } + 'linelist_format' => 'csv' } } } assert_response :unprocessable_entity diff --git a/test/fixtures/data_exports.yml b/test/fixtures/data_exports.yml index e3e207d23b..5de960f895 100644 --- a/test/fixtures/data_exports.yml +++ b/test/fixtures/data_exports.yml @@ -92,7 +92,7 @@ data_export_eight: status: ready export_parameters: { ids: [<%= ActiveRecord::FixtureSet.identify(:sample32, :uuid) %>], - format: 'csv', + linelist_format: 'csv', namespace_id: <%= ActiveRecord::FixtureSet.identify(:project29_namespace, :uuid) %>, metadata_fields: ['metadatafield1', 'metadatafield2'] } @@ -108,7 +108,7 @@ data_export_nine: status: ready export_parameters: { ids: [<%= ActiveRecord::FixtureSet.identify(:sample32, :uuid) %>, <%= ActiveRecord::FixtureSet.identify(:sample33, :uuid) %>, <%= ActiveRecord::FixtureSet.identify(:sample34, :uuid) %>], - format: 'xlsx', + linelist_format: 'xlsx', namespace_id: <%= ActiveRecord::FixtureSet.identify(:group_twelve, :uuid) %>, metadata_fields: ['metadatafield1', 'non_existant_field', 'metadatafield2'] } diff --git a/test/models/data_export_test.rb b/test/models/data_export_test.rb index 6ce5dd45c1..a832b1226d 100644 --- a/test/models/data_export_test.rb +++ b/test/models/data_export_test.rb @@ -85,7 +85,7 @@ def setup test 'linelist export with missing metadata fields' do data_export = DataExport.new(user: @user, status: 'processing', export_type: 'linelist', - export_parameters: { ids: [@sample1.id], format: 'xlsx', + export_parameters: { ids: [@sample1.id], linelist_format: 'xlsx', namespace_id: @project1.namespace.id }) assert_not data_export.valid? assert_equal I18n.t('activerecord.errors.models.data_export.attributes.export_parameters.missing_metadata_fields'), @@ -94,7 +94,7 @@ def setup test 'sample export with missing namespace_id' do data_export = DataExport.new(user: @user, status: 'processing', export_type: 'sample', - export_parameters: { ids: [@sample1.id], format: 'xlsx', + export_parameters: { ids: [@sample1.id], linelist_format: 'xlsx', metadata_fields: ['a_metadata_field'] }) assert_not data_export.valid? assert_equal I18n.t('activerecord.errors.models.data_export.attributes.export_parameters.missing_namespace_id'), @@ -103,7 +103,7 @@ def setup test 'sample export with invalid namespace_id' do data_export = DataExport.new(user: @user, status: 'processing', export_type: 'sample', - export_parameters: { ids: [@sample1.id], format: 'csv', + export_parameters: { ids: [@sample1.id], linelist_format: 'csv', metadata_fields: ['a_metadata_field'], namespace_id: 'invalid_namespace_id' }) assert_not data_export.valid? @@ -113,7 +113,7 @@ def setup test 'linelist export with missing namespace_id' do data_export = DataExport.new(user: @user, status: 'processing', export_type: 'linelist', - export_parameters: { ids: [@sample1.id], format: 'xlsx', + export_parameters: { ids: [@sample1.id], linelist_format: 'xlsx', metadata_fields: ['a_metadata_field'] }) assert_not data_export.valid? assert_equal I18n.t('activerecord.errors.models.data_export.attributes.export_parameters.missing_namespace_id'), @@ -122,7 +122,7 @@ def setup test 'linelist export with invalid namespace_type' do data_export = DataExport.new(user: @user, status: 'processing', export_type: 'linelist', - export_parameters: { ids: [@sample1.id], format: 'csv', + export_parameters: { ids: [@sample1.id], linelist_format: 'csv', metadata_fields: ['a_metadata_field'], namespace_id: 'invalid_namespace_id' }) assert_not data_export.valid? @@ -130,7 +130,7 @@ def setup data_export.errors[:export_parameters].first end - test 'linelist export with missing format' do + test 'linelist export with missing linelist_format' do data_export = DataExport.new(user: @user, status: 'processing', export_type: 'linelist', export_parameters: { ids: [@sample1.id], metadata_fields: ['a_metadata_field'], @@ -140,9 +140,9 @@ def setup data_export.errors[:export_parameters].first end - test 'linelist export with invalid format' do + test 'linelist export with invalid linelist_format' do data_export = DataExport.new(user: @user, status: 'processing', export_type: 'linelist', - export_parameters: { ids: [@sample1.id], format: 'invalid_format', + export_parameters: { ids: [@sample1.id], linelist_format: 'invalid_format', metadata_fields: ['a_metadata_field'], namespace_id: @project1.namespace.id }) assert_not data_export.valid? diff --git a/test/services/data_exports/create_service_test.rb b/test/services/data_exports/create_service_test.rb index d6156a13de..93743c91f5 100644 --- a/test/services/data_exports/create_service_test.rb +++ b/test/services/data_exports/create_service_test.rb @@ -188,7 +188,7 @@ def setup 'export_type' => 'linelist', 'export_parameters' => { 'ids' => [sample32.id, sample33.id], - 'format' => 'csv', + 'linelist_format' => 'csv', 'namespace_id' => group12.id, 'metadata_fields' => %w[metadatafield1 metadatafield2] } @@ -204,7 +204,7 @@ def setup 'export_type' => 'linelist', 'export_parameters' => { 'ids' => [@sample1.id, @sample2.id], - 'format' => 'xlsx', + 'linelist_format' => 'xlsx', 'namespace_id' => @project1.namespace.id, 'metadata_fields' => %w[metadatafield1 metadatafield2] } @@ -241,7 +241,7 @@ def setup 'export_parameters' => { 'ids' => [@sample1.id, @sample2.id], 'namespace_id' => group4.id, - 'format' => 'xlsx', + 'linelist_format' => 'xlsx', 'metadata_fields' => %w[metadatafield1 metadatafield2] } } diff --git a/test/system/data_exports_test.rb b/test/system/data_exports_test.rb index c099529f74..1198813c0a 100644 --- a/test/system/data_exports_test.rb +++ b/test/system/data_exports_test.rb @@ -9,10 +9,12 @@ def setup @data_export2 = data_exports(:data_export_two) @data_export6 = data_exports(:data_export_six) @data_export7 = data_exports(:data_export_seven) - @namespace = groups(:group_one) - @project = projects(:project1) + @data_export9 = data_exports(:data_export_nine) + @group1 = groups(:group_one) + @project1 = projects(:project1) @sample1 = samples(:sample1) @sample2 = samples(:sample2) + @sample30 = samples(:sample30) @workflow_execution = workflow_executions(:irida_next_example_completed_with_output) login_as @user end @@ -21,7 +23,7 @@ def setup freeze_time visit data_exports_path - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 6 assert_selector 'tr:first-child td:first-child ', text: @data_export1.id assert_selector 'tr:first-child td:nth-child(2)', text: @data_export1.name @@ -58,7 +60,7 @@ def setup test 'data exports with status ready will have download in action dropdown' do visit data_exports_path - within %(#data-exports-table-body) do + within first('tbody') do within %(tr:nth-child(2) td:last-child) do assert_text I18n.t('data_exports.index.actions.delete') end @@ -73,7 +75,7 @@ def setup test 'can delete data exports on listing page' do visit data_exports_path - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 6 click_link I18n.t('data_exports.index.actions.delete'), match: :first end @@ -81,7 +83,7 @@ def setup click_button I18n.t(:'components.confirmation.confirm') end - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 5 click_link I18n.t('data_exports.index.actions.delete'), match: :first end @@ -89,7 +91,7 @@ def setup click_button I18n.t(:'components.confirmation.confirm') end - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 4 click_link I18n.t('data_exports.index.actions.delete'), match: :first end @@ -97,7 +99,7 @@ def setup click_button I18n.t(:'components.confirmation.confirm') end - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 3 click_link I18n.t('data_exports.index.actions.delete'), match: :first end @@ -105,7 +107,7 @@ def setup click_button I18n.t(:'components.confirmation.confirm') end - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 2 click_link I18n.t('data_exports.index.actions.delete'), match: :first end @@ -113,7 +115,7 @@ def setup click_button I18n.t(:'components.confirmation.confirm') end - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 1 click_link I18n.t('data_exports.index.actions.delete'), match: :first end @@ -130,60 +132,50 @@ def setup freeze_time visit data_exports_path - within %(#data-exports-table-body) do + within first('tbody') do within %(tr:first-child td:first-child) do click_link @data_export1.id end end - within %(#data-export-listing) do - assert_selector 'div:first-child dd', text: @data_export1.id - assert_selector 'div:nth-child(2) dd', text: @data_export1.name - assert_selector 'div:nth-child(3) dd', text: I18n.t(:"data_exports.types.#{@data_export1.export_type}") - assert_selector 'div:nth-child(4) dd', text: I18n.t(:"data_exports.status.#{@data_export1.status}") - assert_selector 'div:nth-child(5) dd', - text: I18n.l(@data_export1.created_at.localtime, format: :full_date) - assert_selector 'div:last-child dd', - text: I18n.l(@data_export1.expires_at.localtime, format: :full_date) - end + assert_selector 'div:first-child dd', text: @data_export1.id + assert_selector 'div:nth-child(2) dd', text: @data_export1.name + assert_selector 'div:nth-child(3) dd', text: I18n.t(:"data_exports.types.#{@data_export1.export_type}") + assert_selector 'div:nth-child(4) dd', text: I18n.t(:"data_exports.status.#{@data_export1.status}") + assert_selector 'div:nth-child(5) dd', + text: I18n.l(@data_export1.created_at.localtime, format: :full_date) + assert_selector 'div:last-child dd', + text: I18n.l(@data_export1.expires_at.localtime, format: :full_date) end test 'name is not shown on data export page if data_export.name is nil' do visit data_export_path(@data_export2) - within %(#data-export-listing) do - assert_no_text I18n.t('data_exports.summary.name') - end + assert_no_text I18n.t('data_exports.summary.name') end test 'expire has once_ready text on data export page if data_export.status is processing' do visit data_export_path(@data_export2) - within %(#data-export-listing) do - assert_selector 'div:last-child dd', - text: I18n.t('data_exports.summary.once_ready') - end + assert_selector 'div:last-child dd', + text: I18n.t('data_exports.summary.once_ready') end test 'data export status pill colors' do # processing visit data_export_path(@data_export2) - within %(#data-export-listing) do - within %(div:nth-child(3) dd) do - assert_selector 'span.bg-slate-100.text-slate-800.text-xs.font-medium.rounded-full', - text: I18n.t(:"data_exports.status.#{@data_export2.status}") - end + within %(div:nth-child(3) dd) do + assert_selector 'span.bg-slate-100.text-slate-800.text-xs.font-medium.rounded-full', + text: I18n.t(:"data_exports.status.#{@data_export2.status}") end # ready visit data_export_path(@data_export1) - within %(#data-export-listing) do - within %(div:nth-child(4) dd) do - assert_selector 'span.bg-green-100.text-green-800.text-xs.font-medium.rounded-full', - text: I18n.t(:"data_exports.status.#{@data_export1.status}") - end + within %(div:nth-child(4) dd) do + assert_selector 'span.bg-green-100.text-green-800.text-xs.font-medium.rounded-full', + text: I18n.t(:"data_exports.status.#{@data_export1.status}") end end @@ -204,7 +196,7 @@ def setup test 'can remove export from export page' do visit data_exports_path - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 6 assert_text @data_export2.id end @@ -217,57 +209,53 @@ def setup click_button I18n.t(:'components.confirmation.confirm') end - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 5 assert_no_text @data_export2.id end end test 'member with access level >= analyst can see create export button on samples pages' do - login_as users(:john_doe) - # project samples page - visit namespace_project_samples_url(@namespace, @project) - assert_selector 'a', text: I18n.t('projects.samples.index.create_export_button'), count: 1 + visit namespace_project_samples_url(@group1, @project1) + assert_selector 'button', text: I18n.t('projects.samples.index.create_export_button.label'), count: 1 # group samples page - visit group_samples_url(@namespace) - assert_selector 'a', text: I18n.t('projects.samples.index.create_export_button'), count: 1 + visit group_samples_url(@group1) + assert_selector 'button', text: I18n.t('projects.samples.index.create_export_button.label'), count: 1 end test 'user with access level == guest cannot see create export button on sample pages' do login_as users(:ryan_doe) # project samples page - visit namespace_project_samples_url(@namespace, @project) + visit namespace_project_samples_url(@group1, @project1) assert_no_selector 'a', text: I18n.t('projects.samples.index.create_export_button') # group samples page - visit group_samples_url(@namespace) + visit group_samples_url(@group1) assert_no_selector 'a', text: I18n.t('projects.samples.index.create_export_button'), count: 1 end test 'create export from project samples page' do - login_as users(:john_doe) - visit data_exports_path - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 6 assert_no_text 'test data export' end # project samples page - visit namespace_project_samples_url(@namespace, @project) - assert_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + visit namespace_project_samples_url(@group1, @project1) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') within %(#samples-table) do find("input[type='checkbox'][value='#{@sample1.id}']").click end - assert_no_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') - - click_link I18n.t('projects.samples.index.create_export_button'), match: :first + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + click_link I18n.t('projects.samples.index.create_export_button.sample_export'), match: :first within 'dialog[open].dialog--size-lg' do within %(turbo-frame[id="list_select_samples"]) do assert_text @sample1.name @@ -284,35 +272,31 @@ def setup click_button I18n.t('data_exports.new_sample_export_dialog.submit_button') end - within %(#data-export-listing) do - assert_selector 'dl', count: 1 - assert_selector 'div:nth-child(2) dd', text: 'test data export' - end + assert_selector 'dl', count: 1 + assert_selector 'div:nth-child(2) dd', text: 'test data export' end test 'create export from group samples page' do - login_as users(:john_doe) - visit data_exports_path - within %(#data-exports-table-body) do + within first('tbody') do assert_selector 'tr', count: 6 assert_no_text 'test data export' end # project samples page - visit group_samples_url(@namespace) + visit group_samples_url(@group1) assert_text 'Displaying items 1-20 of 26' - assert_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') within %(#samples-table) do find("input[type='checkbox'][value='#{@sample1.id}']").click find("input[type='checkbox'][value='#{@sample2.id}']").click end - assert_no_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') - - click_link I18n.t('projects.samples.index.create_export_button'), match: :first + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + click_link I18n.t('projects.samples.index.create_export_button.sample_export'), match: :first within 'dialog[open].dialog--size-lg' do within %(turbo-frame[id="list_select_samples"]) do assert_text @sample1.name @@ -331,41 +315,39 @@ def setup click_button I18n.t('data_exports.new_sample_export_dialog.submit_button') end - within %(#data-export-listing) do - assert_selector 'dl', count: 1 - assert_selector 'div:nth-child(2) dd', text: 'test data export' - end + assert_selector 'dl', count: 1 + assert_selector 'div:nth-child(2) dd', text: 'test data export' end test 'checking off samples on different page does not affect current page\'s export samples' do - login_as users(:john_doe) subgroup12a = groups(:subgroup_twelve_a) project29 = projects(:project29) sample32 = samples(:sample32) visit namespace_project_samples_url(subgroup12a, project29) - assert_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') within %(#samples-table) do find("input[type='checkbox'][value='#{sample32.id}']").click end - assert_no_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') - visit namespace_project_samples_url(@namespace, @project) - assert_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + visit namespace_project_samples_url(@group1, @project1) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') within %(#samples-table) do find("input[type='checkbox'][value='#{@sample1.id}']").click end - assert_no_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') - click_link I18n.t('projects.samples.index.create_export_button'), match: :first + click_button I18n.t('projects.samples.index.create_export_button.label') + click_link I18n.t('projects.samples.index.create_export_button.sample_export'), match: :first within 'dialog[open].dialog--size-lg' do within %(turbo-frame[id="list_select_samples"]) do assert_text @sample1.name @@ -382,19 +364,17 @@ def setup attachment2 = attachments(:attachment2) visit data_export_path(@data_export1, tab: 'preview') - within %(#data-export-listing) do - assert_text @data_export1.file.filename.to_s - assert_text I18n.t('data_exports.preview.manifest_json') - assert_text @project.namespace.puid - assert_text @sample1.puid - assert_text attachment1.puid - assert_text attachment2.puid - assert_text attachment1.file.filename.to_s - assert_text attachment2.file.filename.to_s + assert_text @data_export1.file.filename.to_s + assert_text I18n.t('data_exports.preview.manifest_json') + assert_text @project1.namespace.puid + assert_text @sample1.puid + assert_text attachment1.puid + assert_text attachment2.puid + assert_text attachment1.file.filename.to_s + assert_text attachment2.file.filename.to_s - assert_selector 'svg.Viral-Icon__Svg.icon-folder_open', count: 4 - assert_selector 'svg.Viral-Icon__Svg.icon-document_text', count: 3 - end + assert_selector 'svg.Viral-Icon__Svg.icon-folder_open', count: 4 + assert_selector 'svg.Viral-Icon__Svg.icon-document_text', count: 3 end test 'clicking links in preview tab for data export' do @@ -402,18 +382,14 @@ def setup attachment2 = attachments(:attachment2) visit data_export_path(@data_export1, tab: 'preview') - within %(#data-export-listing) do - click_link @project.namespace.puid - end + click_link @project1.namespace.puid - assert_text @project.namespace.puid - assert_text @project.name + assert_text @project1.namespace.puid + assert_text @project1.name visit data_export_path(@data_export1, tab: 'preview') - within %(#data-export-listing) do - click_link @sample1.puid - end + click_link @sample1.puid assert_text @sample1.name assert_text @sample1.puid @@ -427,7 +403,6 @@ def setup end test 'create analysis export' do - login_as users(:john_doe) visit workflow_execution_path(@workflow_execution) click_link I18n.t('workflow_executions.show.create_export_button'), match: :first @@ -444,38 +419,33 @@ def setup click_button I18n.t('data_exports.new_analysis_export_dialog.submit_button') end - within %(#data-export-listing) do - assert_selector 'dl', count: 1 - assert_selector 'div:nth-child(2) dd', text: 'test data export' - end + assert_selector 'dl', count: 1 + assert_selector 'div:nth-child(2) dd', text: 'test data export' end test 'create export state between completed and non-completed workflow executions' do - login_as users(:john_doe) submitted_workflow_execution = workflow_executions(:irida_next_example_submitted) visit workflow_execution_path(submitted_workflow_execution) assert_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + text: I18n.t('projects.samples.index.create_export_button.label') visit workflow_execution_path(@workflow_execution) assert_no_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', - text: I18n.t('projects.samples.index.create_export_button') + text: I18n.t('projects.samples.index.create_export_button.label') end test 'data export type analysis on summary tab' do visit data_export_path(@data_export7, tab: 'summary') - within %(#data-export-listing) do - assert_selector 'div:first-child dd', text: @data_export7.id - assert_selector 'div:nth-child(2) dd', text: @data_export7.name - assert_selector 'div:nth-child(3) dd', text: I18n.t(:"data_exports.types.#{@data_export7.export_type}") - assert_selector 'div:nth-child(4) dd', text: I18n.t(:"data_exports.status.#{@data_export7.status}") - assert_selector 'div:nth-child(5) dd', - text: I18n.l(@data_export7.created_at.localtime, format: :full_date) - assert_selector 'div:last-child dd', - text: I18n.l(@data_export7.expires_at.localtime, format: :full_date) - end + assert_selector 'div:first-child dd', text: @data_export7.id + assert_selector 'div:nth-child(2) dd', text: @data_export7.name + assert_selector 'div:nth-child(3) dd', text: I18n.t(:"data_exports.types.#{@data_export7.export_type}") + assert_selector 'div:nth-child(4) dd', text: I18n.t(:"data_exports.status.#{@data_export7.status}") + assert_selector 'div:nth-child(5) dd', + text: I18n.l(@data_export7.created_at.localtime, format: :full_date) + assert_selector 'div:last-child dd', + text: I18n.l(@data_export7.expires_at.localtime, format: :full_date) end test 'zip file contents in preview tab for workflow execution data export' do @@ -484,16 +454,248 @@ def setup sample46 = samples(:sample46) visit data_export_path(@data_export7, tab: 'preview') - within %(#data-export-listing) do - assert_text @data_export7.file.filename.to_s - assert_text I18n.t('data_exports.preview.manifest_json') + assert_text @data_export7.file.filename.to_s + assert_text I18n.t('data_exports.preview.manifest_json') + + assert_text we_output.file.filename.to_s + assert_text swe_output.file.filename.to_s + assert_text sample46.puid + + assert_selector 'svg.Viral-Icon__Svg.icon-folder_open', count: 1 + assert_selector 'svg.Viral-Icon__Svg.icon-document_text', count: 3 + end + + test 'projects with samples containing no metadata should have linelist export link disabled' do + project = projects(:project2) + sample3 = samples(:sample3) + + visit namespace_project_samples_url(@group1, project) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + + within %(#samples-table) do + find("input[type='checkbox'][value='#{sample3.id}']").click + end + + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + + assert_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.linelist_export') + end + + test 'groups with samples containing no metadata should have linelist export link disabled' do + group = groups(:group_sixteen) + sample43 = samples(:sample43) + + visit group_samples_url(group) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + + within %(#samples-table) do + find("input[type='checkbox'][value='#{sample43.id}']").click + end + + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + + assert_selector 'a.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.linelist_export') + end + + test 'new linelist export dialog' do + visit namespace_project_samples_url(@group1, @project1) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + + within %(#samples-table) do + find("input[type='checkbox'][value='#{@sample30.id}']").click + end + + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + click_link I18n.t('projects.samples.index.create_export_button.linelist_export') - assert_text we_output.file.filename.to_s - assert_text swe_output.file.filename.to_s - assert_text sample46.puid + within 'dialog[open].dialog--size-lg' do + assert_text I18n.t('data_exports.new_linelist_export_dialog.title') + assert_text ActionController::Base.helpers.strip_tags( + I18n.t('data_exports.new_linelist_export_dialog.description.singular') + ) + assert_text I18n.t('data_exports.new_linelist_export_dialog.metadata') + assert_text I18n.t('data_exports.new_linelist_export_dialog.metadata_description', + available: I18n.t('data_exports.new_linelist_export_dialog.available').downcase, + selected: I18n.t('data_exports.new_linelist_export_dialog.selected').downcase) + assert_text I18n.t('data_exports.new_linelist_export_dialog.available') + assert_text I18n.t('data_exports.new_linelist_export_dialog.selected') + assert_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.remove_all') + assert_no_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.add_all') + assert_text I18n.t('data_exports.new_linelist_export_dialog.format') + assert_text I18n.t('data_exports.new_linelist_export_dialog.csv') + assert_text I18n.t('data_exports.new_linelist_export_dialog.xlsx') + assert_text I18n.t('data_exports.new_linelist_export_dialog.name_label') + assert_text I18n.t('data_exports.new_linelist_export_dialog.email_label') + + assert_no_selector 'turbo-frame[id="list_select_samples"]' + assert_no_text @sample30.name + assert_no_text @sample30.puid + click_button I18n.t('data_exports.new_linelist_export_dialog.samples') + assert_selector 'turbo-frame[id="list_select_samples"]' + within %(turbo-frame[id="list_select_samples"]) do + assert_text @sample30.name + assert_text @sample30.puid + end + + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.available')}']" do + assert_text 'metadatafield1' + assert_text 'metadatafield2' + assert_selector 'li', count: 2 + end + + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.selected')}']" do + assert_no_text 'metadatafield1' + assert_no_text 'metadatafield2' + assert_no_selector 'li' + end + end + end + + test 'add all and remove all buttons in new linelist export dialog' do + visit namespace_project_samples_url(@group1, @project1) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') - assert_selector 'svg.Viral-Icon__Svg.icon-folder_open', count: 1 - assert_selector 'svg.Viral-Icon__Svg.icon-document_text', count: 3 + within %(#samples-table) do + find("input[type='checkbox'][value='#{@sample30.id}']").click end + + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + click_link I18n.t('projects.samples.index.create_export_button.linelist_export') + + within 'dialog[open].dialog--size-lg' do + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.available')}']" do + assert_text 'metadatafield1' + assert_text 'metadatafield2' + assert_selector 'li', count: 2 + end + + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.selected')}']" do + assert_no_text 'metadatafield1' + assert_no_text 'metadatafield2' + assert_no_selector 'li' + end + + assert_selector 'input[disabled]' + assert_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.remove_all') + assert_no_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.add_all') + + click_button I18n.t('data_exports.new_linelist_export_dialog.add_all') + + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.selected')}']" do + assert_text 'metadatafield1' + assert_text 'metadatafield2' + assert_selector 'li', count: 2 + end + + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.available')}']" do + assert_no_text 'metadatafield1' + assert_no_text 'metadatafield2' + assert_no_selector 'li' + end + + assert_no_selector 'input[disabled]' + assert_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.add_all') + assert_no_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.remove_all') + + click_button I18n.t('data_exports.new_linelist_export_dialog.remove_all') + + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.available')}']" do + assert_text 'metadatafield1' + assert_text 'metadatafield2' + assert_selector 'li', count: 2 + end + + within "ul[id='#{I18n.t('data_exports.new_linelist_export_dialog.selected')}']" do + assert_no_text 'metadatafield1' + assert_no_text 'metadatafield2' + assert_no_selector 'li' + end + + assert_selector 'input[disabled]' + assert_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.remove_all') + assert_no_selector 'button.pointer-events-none.cursor-not-allowed', + text: I18n.t('data_exports.new_linelist_export_dialog.add_all') + end + end + + test 'create csv export from project samples page' do + visit namespace_project_samples_url(@group1, @project1) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + + within %(#samples-table) do + find("input[type='checkbox'][value='#{@sample30.id}']").click + end + + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + click_link I18n.t('projects.samples.index.create_export_button.linelist_export') + + within 'dialog[open].dialog--size-lg' do + click_button I18n.t('data_exports.new_linelist_export_dialog.add_all') + find('input#data_export_name').fill_in with: 'test csv export' + click_button I18n.t('data_exports.new_linelist_export_dialog.submit_button') + end + + assert_selector 'dl', count: 1 + assert_selector 'div:nth-child(2) dd', text: 'test csv export' + assert_selector 'div:nth-child(4) dd', text: 'csv' + end + + test 'create xlsx export from group samples page' do + visit group_samples_url(@group1) + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + + within %(#samples-table) do + find("input[type='checkbox'][value='#{@sample1.id}']").click + end + + assert_no_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') + click_button I18n.t('projects.samples.index.create_export_button.label') + click_link I18n.t('projects.samples.index.create_export_button.linelist_export') + + within 'dialog[open].dialog--size-lg' do + click_button I18n.t('data_exports.new_linelist_export_dialog.add_all') + find('input#data_export_name').fill_in with: 'test xlsx export' + find('input#xlsx-format').click + click_button I18n.t('data_exports.new_linelist_export_dialog.submit_button') + end + + assert_selector 'dl', count: 1 + assert_selector 'div:nth-child(2) dd', text: 'test xlsx export' + assert_selector 'div:nth-child(4) dd', text: 'xlsx' + end + + test 'linelist export with ready status does not have preview tab' do + visit data_export_path(@data_export9) + + assert_selector 'div:nth-child(4) dd', text: 'xlsx' + + assert_text I18n.t('data_exports.show.tabs.summary') + assert_no_text I18n.t('data_exports.show.tabs.preview') end end diff --git a/test/system/groups/group_links_test.rb b/test/system/groups/group_links_test.rb index a10d24c6fd..c81c8f93a9 100644 --- a/test/system/groups/group_links_test.rb +++ b/test/system/groups/group_links_test.rb @@ -308,7 +308,7 @@ def setup assert_text 'Displaying 2 items' assert_selector '#members-tabs table tbody tr', count: 2 assert_selector '#members-tabs table thead th:first-child svg.icon-arrow_up' - within first('#members-tabs table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link14.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link14.group_access_level) @@ -317,9 +317,9 @@ def setup text: Member::AccessLevel.human_access(@group_link5.group_access_level) end - click_on I18n.t('groups.group_links.index.table_header.group') + click_on 'Group' assert_selector '#members-tabs table thead th:first-child svg.icon-arrow_down' - within first('#members-tabs table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link5.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link5.group_access_level) @@ -328,9 +328,9 @@ def setup text: Member::AccessLevel.human_access(@group_link14.group_access_level) end - click_on I18n.t('groups.group_links.index.table_header.source') + click_on 'Source' assert_selector '#members-tabs table thead th:nth-child(2) svg.icon-arrow_up' - within first('#members-tabs table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link5.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link5.group_access_level) @@ -339,9 +339,9 @@ def setup text: Member::AccessLevel.human_access(@group_link14.group_access_level) end - click_on I18n.t('groups.group_links.index.table_header.source') + click_on 'Source' assert_selector '#members-tabs table thead th:nth-child(2) svg.icon-arrow_down' - within first('#members-tabs table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link5.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link5.group_access_level) @@ -350,9 +350,9 @@ def setup text: Member::AccessLevel.human_access(@group_link14.group_access_level) end - click_on I18n.t('groups.group_links.index.table_header.access_level') + click_on 'Access Level' assert_selector '#members-tabs table thead th:nth-child(4) svg.icon-arrow_up' - within first('#members-tabs table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link5.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link5.group_access_level) @@ -361,9 +361,9 @@ def setup text: Member::AccessLevel.human_access(@group_link14.group_access_level) end - click_on I18n.t('groups.group_links.index.table_header.access_level') + click_on 'Access Level' assert_selector '#members-tabs table thead th:nth-child(4) svg.icon-arrow_down' - within first('#members-tabs table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link14.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link14.group_access_level) @@ -372,9 +372,9 @@ def setup text: Member::AccessLevel.human_access(@group_link5.group_access_level) end - click_on I18n.t('groups.group_links.index.table_header.expiration') + click_on 'Expiration' assert_selector '#members-tabs table thead th:nth-child(5) svg.icon-arrow_up' - within first('#members-tabs table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link5.group.name assert_selector 'tr:first-child td:nth-child(5)', text: Member::AccessLevel.human_access(@group_link5.expires_at) diff --git a/test/system/projects/group_links_test.rb b/test/system/projects/group_links_test.rb index c85081c418..66c26b6ca0 100644 --- a/test/system/projects/group_links_test.rb +++ b/test/system/projects/group_links_test.rb @@ -311,7 +311,7 @@ def setup assert_text 'Displaying 4 items' assert_selector '#project-members table tbody tr', count: 4 assert_selector '#project-members table thead th:first-child svg.icon-arrow_up' - within first('#project-members table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link14.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link14.group_access_level) @@ -326,9 +326,9 @@ def setup text: Member::AccessLevel.human_access(@group_link6.group_access_level) end - click_on I18n.t('projects.group_links.index.table_header.group') + click_on 'Group' assert_selector '#project-members table thead th:first-child svg.icon-arrow_down' - within first('#project-members table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link6.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link6.group_access_level) @@ -343,9 +343,9 @@ def setup text: Member::AccessLevel.human_access(@group_link14.group_access_level) end - click_on I18n.t('projects.group_links.index.table_header.source') + click_on 'Source' assert_selector '#project-members table thead th:nth-child(2) svg.icon-arrow_up' - within first('#project-members table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link5.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link5.group_access_level) @@ -360,9 +360,9 @@ def setup text: Member::AccessLevel.human_access(@group_link2.group_access_level) end - click_on I18n.t('projects.group_links.index.table_header.source') + click_on 'Source' assert_selector '#project-members table thead th:nth-child(2) svg.icon-arrow_down' - within first('#project-members table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link2.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link2.group_access_level) @@ -377,9 +377,9 @@ def setup text: Member::AccessLevel.human_access(@group_link14.group_access_level) end - click_on I18n.t('projects.group_links.index.table_header.access_level') + click_on 'Access Level' assert_selector '#project-members table thead th:nth-child(4) svg.icon-arrow_up' - within first('#project-members table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link5.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link5.group_access_level) @@ -394,9 +394,9 @@ def setup text: Member::AccessLevel.human_access(@group_link14.group_access_level) end - click_on I18n.t('projects.group_links.index.table_header.access_level') + click_on 'Access Level' assert_selector '#project-members table thead th:nth-child(4) svg.icon-arrow_down' - within first('#project-members table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link6.group.name assert_selector 'tr:first-child td:nth-child(4)', text: Member::AccessLevel.human_access(@group_link6.group_access_level) @@ -411,9 +411,9 @@ def setup text: Member::AccessLevel.human_access(@group_link5.group_access_level) end - click_on I18n.t('projects.group_links.index.table_header.expiration') + click_on 'Expiration' assert_selector '#project-members table thead th:nth-child(5) svg.icon-arrow_up' - within first('#project-members table tbody') do + within first('tbody') do assert_selector 'tr:first-child td:first-child', text: @group_link2.group.name assert_selector 'tr:first-child td:nth-child(5)', text: Member::AccessLevel.human_access(@group_link2.expires_at) diff --git a/test/system/projects/samples_test.rb b/test/system/projects/samples_test.rb index 796fc06eba..d1d3d548e4 100644 --- a/test/system/projects/samples_test.rb +++ b/test/system/projects/samples_test.rb @@ -2168,7 +2168,9 @@ class SamplesTest < ApplicationSystemTestCase assert_no_button I18n.t(:'projects.samples.index.clone_button') assert_no_button I18n.t(:'projects.samples.index.transfer_button') - assert_no_button I18n.t(:'projects.samples.index.create_export_button') + assert_text I18n.t('projects.samples.index.create_export_button.label') + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') end test 'action links are disabled when a group does not contain any projects with samples' do @@ -2178,7 +2180,9 @@ class SamplesTest < ApplicationSystemTestCase assert_no_button I18n.t(:'projects.samples.index.clone_button') assert_no_button I18n.t(:'projects.samples.index.transfer_button') - assert_no_button I18n.t(:'projects.samples.index.create_export_button') + assert_text I18n.t('projects.samples.index.create_export_button.label') + assert_selector 'button.pointer-events-none.cursor-not-allowed.bg-slate-100.text-slate-600', + text: I18n.t('projects.samples.index.create_export_button.label') end def retrieve_puids @@ -2309,7 +2313,8 @@ def retrieve_puids assert_text I18n.t('projects.samples.index.no_samples') end - assert_selector 'a.cursor-not-allowed.pointer-events-none', count: 5 + assert_selector 'a.cursor-not-allowed.pointer-events-none', count: 4 + assert_selector 'button.cursor-not-allowed.pointer-events-none', count: 1 end test 'delete single attachment with remove link while all attachments selected followed by multiple deletion' do