Skip to content

Commit 8e848f1

Browse files
Merge pull request #20702 from itisAliRH/history-navigation-improvements
History Components Navigation/Heading Improvements
2 parents feaa3b0 + f701912 commit 8e848f1

20 files changed

+570
-397
lines changed

client/src/components/Citation/CitationsList.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { createTestingPinia } from "@pinia/testing";
12
import { getLocalVue } from "@tests/jest/helpers";
23
import { mount, type Wrapper } from "@vue/test-utils";
34
import flushPromises from "flush-promises";
5+
import VueRouter from "vue-router";
46

57
import CitationItem from "./CitationItem.vue";
68
import MountTarget from "./CitationsList.vue";
79

810
const localVue = getLocalVue(true);
11+
localVue.use(VueRouter);
912

1013
jest.mock("@/composables/config", () => ({
1114
useConfig: jest.fn(() => ({
@@ -38,12 +41,19 @@ describe("CitationsList", () => {
3841
let wrapper: Wrapper<Vue>;
3942

4043
beforeEach(async () => {
44+
const pinia = createTestingPinia();
45+
46+
const router = new VueRouter();
47+
router.push("/histories/citations?id=test-id");
48+
4149
wrapper = mount(MountTarget as object, {
4250
propsData: {
4351
id: "test-id",
4452
source: "histories",
4553
},
4654
localVue,
55+
pinia,
56+
router,
4757
});
4858

4959
await flushPromises();

client/src/components/Citation/CitationsList.vue

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { computed, onMounted, onUpdated, ref } from "vue";
66
77
import { getCitations } from "@/components/Citation/services";
88
import { useConfig } from "@/composables/config";
9+
import { useHistoryStore } from "@/stores/historyStore";
910
import { copy } from "@/utils/clipboard";
1011
1112
import type { Citation } from ".";
1213
import { Cite } from "./cite";
1314
1415
import CitationItem from "@/components/Citation/CitationItem.vue";
15-
import Heading from "@/components/Common/Heading.vue";
16+
import BreadcrumbHeading from "@/components/Common/BreadcrumbHeading.vue";
1617
1718
const outputFormats = Object.freeze({
1819
CITATION: "bibliography",
@@ -31,6 +32,7 @@ const props = withDefaults(defineProps<Props>(), {
3132
});
3233
3334
const { config } = useConfig(true);
35+
const historyStore = useHistoryStore();
3436
3537
const emit = defineEmits(["rendered", "show", "shown", "hide", "hidden"]);
3638
@@ -53,6 +55,16 @@ onMounted(async () => {
5355
}
5456
});
5557
58+
const breadcrumbItems = computed(() => [
59+
{ title: "Histories", to: "/histories/list" },
60+
{
61+
title: historyStore.getHistoryNameById(props.id),
62+
to: `/histories/view?id=${props.id}`,
63+
superText: historyStore.currentHistoryId === props.id ? "current" : undefined,
64+
},
65+
{ title: "Tool References" },
66+
]);
67+
5668
/** The fetched Citations in addition to the Galaxy citation from config */
5769
const citations = computed<Citation[]>(() => {
5870
const allCitations = [...fetchedCitations.value];
@@ -120,7 +132,8 @@ function citationsToBibtexAsText() {
120132

121133
<template>
122134
<div>
123-
<Heading h1 separator inline size="lg">History Tool References</Heading>
135+
<BreadcrumbHeading :items="breadcrumbItems" />
136+
124137
<div v-if="isLoading" class="text-center">
125138
<BSpinner />
126139
<p class="ml-2">Loading references...</p>

client/src/components/Common/BreadcrumbHeading.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
23
import { BLink } from "bootstrap-vue";
34
import type { RawLocation } from "vue-router";
45
import { useRouter } from "vue-router/composables";
@@ -33,9 +34,11 @@ function isPathActive(path: RawLocation): boolean {
3334
:title="`Go back to ${localize(item.title)}`"
3435
:to="item.to"
3536
class="breadcrumb-heading-header-active">
37+
<FontAwesomeIcon v-if="item.icon" :icon="item.icon" />
3638
{{ localize(item.title) }}
3739
</BLink>
3840
<span v-else :key="'else-' + index" class="breadcrumb-heading-header-inactive">
41+
<FontAwesomeIcon v-if="item.icon" :icon="item.icon" />
3942
{{ localize(item.title) }}
4043
</span>
4144

client/src/components/Common/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
12
import type { RawLocation } from "vue-router";
23

34
// TODO: Not sure if this is the best place for this type
@@ -9,14 +10,24 @@ export type ColorVariant = "primary" | "secondary" | "success" | "danger" | "war
910
* displayed alongside the item.
1011
*/
1112
export interface BreadcrumbItem {
12-
/** The display text for the breadcrumb item */
13+
/**
14+
* The label of the breadcrumb item.
15+
*/
1316
title: string;
14-
/** Optional The URL or route to navigate to when the breadcrumb item is clicked.
17+
18+
/**
19+
* Optional The URL or route to navigate to when the breadcrumb item is clicked.
1520
* the item will not be clickable if this is not provided or the current route matches this location.
1621
*/
1722
to?: RawLocation;
23+
1824
/**
1925
* Optional additional text displayed above the item.
2026
*/
2127
superText?: string;
28+
29+
/**
30+
* Optional icon to display alongside the breadcrumb item.
31+
*/
32+
icon?: IconDefinition;
2233
}

client/src/components/Grid/GridInvocation.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface Props {
2222
ownerGrid?: boolean;
2323
filteredFor?: { type: "History" | "StoredWorkflow"; id: string; name: string };
2424
invocationsList?: WorkflowInvocation[];
25+
hideHeading?: boolean;
2526
}
2627
2728
const props = withDefaults(defineProps<Props>(), {
@@ -30,6 +31,7 @@ const props = withDefaults(defineProps<Props>(), {
3031
ownerGrid: true,
3132
filteredFor: undefined,
3233
invocationsList: undefined,
34+
hideHeading: false,
3335
});
3436
3537
const { currentUser } = storeToRefs(useUserStore());
@@ -100,7 +102,7 @@ function refreshTable() {
100102

101103
<template>
102104
<div class="d-flex flex-column">
103-
<div v-if="forStoredWorkflow || forHistory" class="d-flex">
105+
<div v-if="!hideHeading && (forStoredWorkflow || forHistory)" class="d-flex">
104106
<Heading h1 separator inline truncate size="lg" class="flex-grow-1 mb-2">{{ effectiveTitle }}</Heading>
105107
</div>
106108
<GridList

client/src/components/History/Archiving/HistoryArchiveWizard.test.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const { server, http } = useServerMock();
2727
const TEST_HISTORY_ID = "test-history-id";
2828
const TEST_HISTORY = {
2929
id: TEST_HISTORY_ID,
30-
name: "fake-history-name",
3130
archived: false,
3231
};
3332

@@ -63,13 +62,6 @@ describe("HistoryArchiveWizard.vue", () => {
6362
);
6463
});
6564

66-
it("should render the history name in the header", async () => {
67-
const wrapper = await mountComponentWithHistory(TEST_HISTORY as HistorySummary);
68-
69-
const header = wrapper.find("h1");
70-
expect(header.text()).toContain(TEST_HISTORY.name);
71-
});
72-
7365
it("should render only the simple archival mode when no writeable file sources are available", async () => {
7466
const wrapper = await mountComponentWithHistory(TEST_HISTORY as HistorySummary);
7567

client/src/components/History/Archiving/HistoryArchiveWizard.vue

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<script setup lang="ts">
2-
import { library } from "@fortawesome/fontawesome-svg-core";
32
import { faArchive } from "@fortawesome/free-solid-svg-icons";
4-
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
53
import { BAlert, BCard, BTab, BTabs } from "bootstrap-vue";
64
import { computed, ref } from "vue";
75
import { RouterLink } from "vue-router";
@@ -12,12 +10,11 @@ import { useFileSources } from "@/composables/fileSources";
1210
import { useToast } from "@/composables/toast";
1311
import { useHistoryStore } from "@/stores/historyStore";
1412
13+
import BreadcrumbHeading from "@/components/Common/BreadcrumbHeading.vue";
1514
import HistoryArchiveExportSelector from "@/components/History/Archiving/HistoryArchiveExportSelector.vue";
1615
import HistoryArchiveSimple from "@/components/History/Archiving/HistoryArchiveSimple.vue";
1716
import LoadingSpan from "@/components/LoadingSpan.vue";
1817
19-
library.add(faArchive);
20-
2118
const historyStore = useHistoryStore();
2219
const { config } = useConfig(true);
2320
const toast = useToast();
@@ -65,16 +62,25 @@ async function onArchiveHistory(exportRecordId?: string) {
6562
isArchiving.value = false;
6663
}
6764
}
65+
66+
const breadcrumbItems = computed(() => [
67+
{ title: "Histories", to: "/histories/list" },
68+
{
69+
title: history.value?.name || "Archive History",
70+
to: `/histories/view?id=${props.historyId}`,
71+
superText: historyStore.currentHistoryId === props.historyId ? "current" : undefined,
72+
},
73+
{ title: "Archive", icon: faArchive },
74+
]);
6875
</script>
6976

7077
<template>
7178
<div class="history-archive-wizard">
72-
<FontAwesomeIcon icon="archive" size="2x" class="text-primary float-left mr-2" />
73-
<h1 class="h-lg">
74-
Archive
75-
<LoadingSpan v-if="!history" spinner-only />
76-
<b v-else>{{ history.name }}</b>
77-
</h1>
79+
<BreadcrumbHeading :items="breadcrumbItems" />
80+
81+
<BAlert v-if="!history" show>
82+
<LoadingSpan spinner-only />
83+
</BAlert>
7884

7985
<BAlert v-if="isHistoryAlreadyArchived" id="history-archived-alert" show variant="success">
8086
This history has been archived. You can access it from the

client/src/components/History/CurrentHistory/HistoryNavigation.test.ts

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,14 @@ import { getLocalVue } from "@tests/jest/helpers";
22
import { shallowMount } from "@vue/test-utils";
33
import { createPinia } from "pinia";
44

5+
import type { RegisteredUser } from "@/api";
56
import { useUserStore } from "@/stores/userStore";
67

78
import HistoryNavigation from "./HistoryNavigation.vue";
89

910
const localVue = getLocalVue();
1011

11-
// all options
12-
const expectedOptions = [
13-
"Show Histories Side-by-Side",
14-
"Resume Paused Jobs",
15-
"Copy this History",
16-
"Delete this History",
17-
"Export Tool References",
18-
"Export History to File",
19-
"Archive History",
20-
"Extract Workflow",
21-
"Show Invocations",
22-
"Share & Manage Access",
23-
];
24-
25-
// options enabled for logged-out users
26-
const anonymousOptions = [
27-
"Resume Paused Jobs",
28-
"Delete this History",
29-
"Export Tool References",
30-
"Export History to File",
31-
];
32-
33-
// options disabled for logged-out users
34-
const anonymousDisabledOptions = expectedOptions.filter((option) => !anonymousOptions.includes(option));
35-
36-
async function createWrapper(propsData: object, userData?: any) {
12+
async function createWrapper(propsData: object, userData?: Partial<RegisteredUser>) {
3713
const pinia = createPinia();
3814

3915
const wrapper = shallowMount(HistoryNavigation as object, {
@@ -43,7 +19,7 @@ async function createWrapper(propsData: object, userData?: any) {
4319
});
4420

4521
const userStore = useUserStore();
46-
userStore.currentUser = { ...userStore.currentUser, ...userData };
22+
userStore.currentUser = { ...(userStore.currentUser as RegisteredUser), ...userData };
4723

4824
return wrapper;
4925
}
@@ -65,12 +41,6 @@ describe("History Navigation", () => {
6541
expect(createButton.attributes().disabled).toBeFalsy();
6642
const switchButton = wrapper.find("*[data-description='switch to another history']");
6743
expect(switchButton.attributes().disabled).toBeFalsy();
68-
69-
const dropDown = wrapper.find("*[data-description='history options']");
70-
const optionElements = dropDown.findAll("bdropdownitem-stub");
71-
const optionTexts = optionElements.wrappers.map((el) => el.text());
72-
73-
expect(optionTexts).toStrictEqual(expectedOptions);
7444
});
7545

7646
it("disables options for anonymous users", async () => {
@@ -83,28 +53,5 @@ describe("History Navigation", () => {
8353
expect(createButton.attributes().disabled).toBeTruthy();
8454
const switchButton = wrapper.find("*[data-description='switch to another history']");
8555
expect(switchButton.attributes().disabled).toBeTruthy();
86-
87-
const dropDown = wrapper.find("*[data-description='history options']");
88-
const enabledOptionElements = dropDown.findAll("bdropdownitem-stub:not([disabled])");
89-
const enabledOptionTexts = enabledOptionElements.wrappers.map((el) => el.text());
90-
expect(enabledOptionTexts).toStrictEqual(anonymousOptions);
91-
92-
const disabledOptionElements = dropDown.findAll("bdropdownitem-stub[disabled]");
93-
const disabledOptionTexts = disabledOptionElements.wrappers.map((el) => el.text());
94-
expect(disabledOptionTexts).toStrictEqual(anonymousDisabledOptions);
95-
});
96-
97-
it("prompts anonymous users to log in", async () => {
98-
const wrapper = await createWrapper({
99-
history: { id: "current_history_id" },
100-
histories: [],
101-
});
102-
103-
const dropDown = wrapper.find("*[data-description='history options']");
104-
const disabledOptionElements = dropDown.findAll("bdropdownitem-stub[disabled]");
105-
106-
disabledOptionElements.wrappers.forEach((option) => {
107-
expect((option.attributes("title") as string).toLowerCase()).toContain("log in");
108-
});
10956
});
11057
});

0 commit comments

Comments
 (0)