Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions client/src/components/History/HistoryList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*
* - Filtering and searching histories with advanced filter options
* - Pagination for large history collections
* - Bulk operations (delete, restore, add tags)
* - Bulk operations (delete, restore, purge, add tags, open in multiview)
* - Individual history selection and management
* - View mode switching (grid/list)
* - Sorting capabilities
Expand All @@ -19,7 +19,7 @@
* <HistoryList activeList="shared" />
*/

import { faBurn, faPlus, faTags, faTrash, faTrashRestore } from "@fortawesome/free-solid-svg-icons";
import { faBurn, faColumns, faPlus, faTags, faTrash, faTrashRestore } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BNav, BNavItem, BOverlay, BPagination } from "bootstrap-vue";
import { computed, onMounted, ref, watch } from "vue";
Expand Down Expand Up @@ -142,6 +142,7 @@ const noItems = computed(() => !loading.value && historiesLoaded.value.length ==
const noResults = computed(() => !loading.value && historiesLoaded.value.length === 0 && Boolean(filterText.value));
const deleteButtonTitle = computed(() => (showDeleted.value ? "Hide deleted histories" : "Show deleted histories"));
const showBulkPurge = computed(() => selectedHistories.value.some((h) => !h.purged));
const showBulkMultiview = computed(() => selectedHistories.value.length > 1);

const historyListFilters = computed(() => getHistoryListFilters(props.activeList));
const rawFilters = computed(() =>
Expand Down Expand Up @@ -457,6 +458,55 @@ async function onBulkTagsAdd(tags: string[]) {
}
}

const MULTIVIEW_MAX_HISTORIES = 10;

/**
* Opens selected histories in the MultiviewPanel
* Pins the selected histories and navigates to the multiview page
*/
async function onBulkOpenInMultiview() {
const selectedHistoriesCount = selectedHistories.value.length;

if (selectedHistoriesCount === 0) {
return;
}

if (selectedHistoriesCount > MULTIVIEW_MAX_HISTORIES) {
const confirmed = await confirm(
`You have selected ${selectedHistoriesCount} histories to open in multiview.
However, the maximum number of histories allowed in multiview is ${MULTIVIEW_MAX_HISTORIES}.
Do you want to proceed with opening the first ${MULTIVIEW_MAX_HISTORIES} histories?`,
{
id: "bulk-open-multiview-histories",
title: `You can only open ${MULTIVIEW_MAX_HISTORIES} histories in multiview`,
okTitle: "Proceed",
okVariant: "primary",
cancelVariant: "outline-primary",
centered: true,
},
);
if (!confirmed) {
return;
}
selectedHistories.value.splice(MULTIVIEW_MAX_HISTORIES);
}

historyStore.clearPinnedHistories();
for (const history of selectedHistories.value) {
historyStore.pinHistory(history.id);
}

router.push("/histories/view_multiple");

const totalSelectedHistories = selectedHistories.value.length;

resetSelection();

Toast.success(
`Opened ${totalSelectedHistories} ${totalSelectedHistories === 1 ? "history" : "histories"} in multiview.`,
);
}

/**
* Watches for changes in filter text, sort options, and sort direction
* to reload the history list with updated parameters
Expand Down Expand Up @@ -700,6 +750,18 @@ onMounted(async () => {
</span>
<LoadingSpan v-else message="Adding tags" />
</BButton>

<BButton
v-if="showBulkMultiview"
id="history-list-footer-bulk-open-multiview-button"
v-b-tooltip.hover
title="Open selected histories in multiview"
size="sm"
variant="primary"
@click="onBulkOpenInMultiview">
<FontAwesomeIcon :icon="faColumns" fixed-width />
Open in Multiview ({{ selectedHistories.length }})
</BButton>
</div>

<BPagination
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/History/Multiple/MultipleView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const showRecentTitle = computed(() => {

/** Reset to _default_ state; showing 4 latest updated histories */
function showRecent() {
historyStore.pinnedHistories = [];
historyStore.clearPinnedHistories();
Toast.info(
"Showing the 4 most recently updated histories. Pin histories to this view by clicking on Select Histories.",
"History Multiview",
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Panels/MultiviewPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async function createAndPin() {

/** Reset to _default_ state; showing 4 latest updated histories */
function pinRecent() {
historyStore.pinnedHistories = [];
historyStore.clearPinnedHistories();
Toast.info(
"Showing the 4 most recently updated histories in Multiview. Pin histories to History Multiview by selecting them in the panel.",
"History Multiview",
Expand Down
5 changes: 5 additions & 0 deletions client/src/stores/historyStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ export const useHistoryStore = defineStore("historyStore", () => {
pinnedHistories.value = pinnedHistories.value.filter((h) => !historyIds.includes(h.id));
}

function clearPinnedHistories() {
pinnedHistories.value = [];
}

function selectHistory(history: HistorySummary) {
setHistory(history);
setCurrentHistoryId(history.id);
Expand Down Expand Up @@ -473,6 +477,7 @@ export const useHistoryStore = defineStore("historyStore", () => {
setHistories,
pinHistory,
unpinHistories,
clearPinnedHistories,
selectHistory,
applyFilters,
copyHistory,
Expand Down
4 changes: 3 additions & 1 deletion client/src/utils/navigation/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ history_panel:
metadata_file_download: '${_} [data-description="download ${metadata_name}"]'

dataset_operations: '${_} .dataset-actions'

expiration_indicator_badge: '${_} .expiration-indicator .badge'

# re-usable history editor, scoped for use in different layout scenarios (multi, etc.)
Expand Down Expand Up @@ -523,6 +523,8 @@ histories:
bulk_delete_confirm: '#bulk-delete-histories .btn.btn-danger'
bulk_restore_button: 'button[id="history-list-footer-bulk-restore-button"]'
bulk_restore_confirm: '#bulk-restore-histories .btn.btn-primary'
bulk_open_multiview_button: 'button[id="history-list-footer-bulk-open-multiview-button"]'
bulk_open_multiview_limit_confirm_button: '#bulk-open-multiview-histories .btn.btn-primary'
sharing:
selectors:
unshare_user_button: '.share_with_view .multiselect__tag-icon'
Expand Down
73 changes: 73 additions & 0 deletions lib/galaxy_test/selenium/test_histories_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,79 @@ def test_delete_and_undelete_multiple_histories(self):
self.components.histories.reset_input.wait_for_and_click()
self.assert_histories_in_list([self.history2_name, self.history3_name])

@selenium_only("Not yet migrated to support Playwright backend")
@selenium_test
def test_bulk_open_in_multiview(self):
self._login()
self.navigate_to_histories_page()

# Select multiple histories
self.toggle_card_selection_in_list("#history-list", [self.history2_name, self.history3_name])

# Open selected histories in multiview
self.components.histories.bulk_open_multiview_button.wait_for_and_click()

# Wait for navigation to multiview page
self.sleep_for(self.wait_types.UX_RENDER)

# Verify we are on the multiview page
assert "/histories/view_multiple" in self.current_url

# Verify the selected histories are present in the multiview panel
present_histories = self.components.multi_history_panel.histories.all()
present_history_names = [self.get_history_name(history) for history in present_histories]
assert set(present_history_names) == {self.history2_name, self.history3_name}

@selenium_only("Not yet migrated to support Playwright backend")
@selenium_test
def test_bulk_open_in_multiview_limit_confirmation(self):
self._login()

# Create additional histories to exceed the limit (10 is the max)
additional_histories = []
for _i in range(10 + 1):
history_name = self._get_random_name()
self.create_history(history_name)
additional_histories.append(history_name)

self.navigate_to_histories_page()

# Select more than 10 histories (we'll select 11)
all_histories_to_select = additional_histories
self.toggle_card_selection_in_list("#history-list", all_histories_to_select)

# Click the bulk open multiview button
self.components.histories.bulk_open_multiview_button.wait_for_and_click()

# Wait for the confirmation dialog to appear
self.sleep_for(self.wait_types.UX_RENDER)

# Verify the confirmation dialog is displayed
confirm_dialog = self.wait_for_selector("#bulk-open-multiview-histories")
assert confirm_dialog.is_displayed()

# Verify the dialog contains information about the limit
dialog_text = confirm_dialog.text
assert "10" in dialog_text # The maximum number of histories
assert "11" in dialog_text # The number of histories selected

# Click confirm to proceed despite the limit
self.wait_for_and_click(self.components.histories.bulk_open_multiview_limit_confirm_button)

# Wait for dialog to close
self.sleep_for(self.wait_types.UX_RENDER)

# Verify we are on the multiview page
assert "/histories/view_multiple" in self.current_url

# Verify the maximum allowed histories are opened in multiview
present_histories = self.components.multi_history_panel.histories.all()
assert len(present_histories) == 10

def get_history_name(self, history):
history_name_element = history.find_element(By.CSS_SELECTOR, "[data-description='name display']")
return history_name_element.text

@selenium_only("Not yet migrated to support Playwright backend")
@selenium_test
def test_sort_by_name(self):
Expand Down
Loading