Skip to content

Commit e83fb88

Browse files
authored
Merge pull request #23046 from itisAliRH/feat/tool-form-latest-version-badge-empty-params
Show newer available tool version badge in tool form and empty-parameter state more clearly
2 parents 8a3067d + 17839dd commit e83fb88

6 files changed

Lines changed: 133 additions & 6 deletions

File tree

client/src/components/Form/FormCardSticky.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,25 @@ describe("FormCardSticky.vue", () => {
6666
expect(wrapper.text()).toContain("(Galaxy Version 23.0)");
6767
});
6868

69+
it("renders clickable newer version badge with a tooltip when requested", async () => {
70+
const wrapper = mountComponent({ isNotLatestVersion: true });
71+
const badge = wrapper.find("[data-description='newer tool version']");
72+
expect(badge.exists()).toBe(true);
73+
expect(badge.text()).toBe("Newer version available");
74+
expect(badge.attributes("title")).toBe("Switch to the latest available tool version");
75+
expect(badge.element.tagName).toBe("BUTTON");
76+
expect(badge.attributes("type")).toBe("button");
77+
78+
await badge.trigger("click");
79+
80+
expect(wrapper.emitted("newer-version-click")).toHaveLength(1);
81+
});
82+
83+
it("does not render newer version badge by default", () => {
84+
const wrapper = mountComponent();
85+
expect(wrapper.find("[data-description='newer tool version']").exists()).toBe(false);
86+
});
87+
6988
it("renders slot content: buttons, default, footer", () => {
7089
const wrapper = mountComponent(
7190
{},

client/src/components/Form/FormCardSticky.vue

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { faWrench, type IconDefinition } from "@fortawesome/free-solid-svg-icons";
33
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
4+
import { BBadge } from "bootstrap-vue";
45
56
import { absPath } from "@/utils/redirect";
67
@@ -17,11 +18,17 @@ withDefaults(
1718
logo?: string;
1819
name?: string;
1920
version?: string;
21+
isNotLatestVersion?: boolean;
2022
}>(),
2123
{
24+
isNotLatestVersion: false,
2225
isLoading: false,
2326
},
2427
);
28+
29+
const emit = defineEmits<{
30+
(e: "newer-version-click"): void;
31+
}>();
2532
</script>
2633

2734
<template>
@@ -41,10 +48,25 @@ withDefaults(
4148
v-if="version"
4249
class="text-muted"
4350
data-description="galaxy tool version"
44-
:data-version="version"
45-
>(Galaxy Version {{ version }})</span
46-
>
51+
:data-version="version">
52+
(Galaxy Version {{ version }})
53+
</span>
54+
55+
<BBadge
56+
v-if="isNotLatestVersion"
57+
v-g-tooltip.hover.focus
58+
tag="button"
59+
type="button"
60+
class="border-0 cursor-pointer"
61+
pill
62+
variant="warning"
63+
title="Switch to the latest available tool version"
64+
data-description="newer tool version"
65+
@click="emit('newer-version-click')">
66+
Newer version available
67+
</BBadge>
4768
</div>
69+
4870
<div class="d-flex flex-nowrap align-items-start flex-gapx-1">
4971
<slot name="buttons" />
5072
</div>

client/src/components/Tool/ToolCard.test.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expectConfigurationRequest, getLocalVue } from "@tests/vitest/helpers";
1+
import { expectConfigurationRequest, getLocalVue, injectTestRouter } from "@tests/vitest/helpers";
22
import { setupMockConfig } from "@tests/vitest/mockConfig";
33
import { mount } from "@vue/test-utils";
44
import flushPromises from "flush-promises";
@@ -14,16 +14,28 @@ const { server, http } = useServerMock();
1414

1515
vi.mock("@/api/schema");
1616

17+
vi.mock("@/composables/userLocalStorageFromHashedId", async () => {
18+
const { ref } = await import("vue");
19+
return {
20+
useUserLocalStorageFromHashId: (_key, initialValue) => ref(initialValue),
21+
};
22+
});
23+
1724
const config = { enable_tool_source_display: false };
1825
setupMockConfig(config);
1926

2027
const localVue = getLocalVue();
28+
const router = injectTestRouter(localVue);
2129

2230
describe("ToolCard", () => {
2331
let wrapper;
2432
let userStore;
2533

2634
beforeEach(async () => {
35+
if (router.currentRoute.fullPath !== "/") {
36+
await router.push("/");
37+
}
38+
2739
// some child component must be bypassing useConfig - so we need to explicitly
2840
// stup the API endpoint also. If you can drop this without request problems in log,
2941
// this hack can be removed.
@@ -58,6 +70,7 @@ describe("ToolCard", () => {
5870
disabled: false,
5971
},
6072
localVue,
73+
router,
6174
pinia,
6275
});
6376
userStore = useUserStore();
@@ -91,4 +104,48 @@ describe("ToolCard", () => {
91104
expect(backdropActive.length).toBe(1);
92105
await flushPromises();
93106
});
107+
108+
it("shows newer version badge when latest version is not active and navigates to the latest alias", async () => {
109+
await wrapper.setProps({
110+
version: "1.0",
111+
options: {
112+
...wrapper.props("options"),
113+
version: "1.0",
114+
versions: ["1.0", "2.0"],
115+
},
116+
});
117+
118+
const badge = wrapper.find("[data-description='newer tool version']");
119+
expect(badge.text()).toBe("Newer version available");
120+
121+
await badge.trigger("click");
122+
123+
expect(router.currentRoute.fullPath).toBe("/?tool_id=identifier&version=latest");
124+
});
125+
126+
it("does not show newer version badge for the latest lineage version", async () => {
127+
await wrapper.setProps({
128+
version: "2.0",
129+
options: {
130+
...wrapper.props("options"),
131+
version: "2.0",
132+
versions: ["1.0", "2.0"],
133+
},
134+
});
135+
136+
expect(wrapper.find("[data-description='newer tool version']").exists()).toBe(false);
137+
});
138+
139+
it("does not show newer version badge for single-version tools", async () => {
140+
await wrapper.setProps({
141+
version: "1.0",
142+
options: {
143+
...wrapper.props("options"),
144+
version: "1.0",
145+
versions: ["1.0"],
146+
},
147+
});
148+
149+
expect(wrapper.find("[data-description='newer tool version']").exists()).toBe(false);
150+
});
94151
});

client/src/components/Tool/ToolCard.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { BAlert, BPopover } from "bootstrap-vue";
55
import { storeToRefs } from "pinia";
66
import { computed, onBeforeMount, ref, watch } from "vue";
77
8+
import { useToolRouting } from "@/composables/route";
89
import { useStorageLocationConfiguration } from "@/composables/storageLocation";
910
import { useConfigStore } from "@/stores/configurationStore";
1011
import { useToolsServiceCredentialsDefinitionsStore } from "@/stores/toolsServiceCredentialsDefinitionsStore";
@@ -88,6 +89,7 @@ const props = defineProps({
8889
const emit = defineEmits(["onChangeVersion", "updatePreferredObjectStoreId"]);
8990
9091
const { setToolServiceCredentialsDefinitionFor } = useToolsServiceCredentialsDefinitionsStore();
92+
const { routeToTool } = useToolRouting();
9193
9294
function onChangeVersion(v) {
9395
emit("onChangeVersion", v);
@@ -127,6 +129,12 @@ const visibleVersions = computed(() => {
127129
return filtered;
128130
});
129131
const showVersions = computed(() => visibleVersions.value.length > 1);
132+
const latestVersion = computed(() => versions.value[versions.value.length - 1]);
133+
const isNotLatestVersion = computed(() => Boolean(latestVersion.value && props.version !== latestVersion.value));
134+
135+
function onNewerVersionClick() {
136+
routeToTool(props.id);
137+
}
130138
131139
const storageLocationModalTitle = computed(() => {
132140
if (isOnlyPreference.value) {
@@ -166,7 +174,9 @@ onBeforeMount(() => {
166174
:error-message="errorText || ''"
167175
:description="props.description"
168176
:name="props.title"
169-
:version="props.version">
177+
:version="props.version"
178+
:is-not-latest-version="isNotLatestVersion"
179+
@newer-version-click="onNewerVersionClick">
170180
<template v-slot:buttons>
171181
<GButtonGroup class="tool-card-buttons">
172182
<ToolFavoriteButton v-if="hasUser" :id="props.id" />

client/src/components/Tool/ToolForm.test.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getLocalVue, injectTestRouter, suppressBootstrapVueWarnings } from "@te
55
import { mount } from "@vue/test-utils";
66
import flushPromises from "flush-promises";
77
import { createPinia } from "pinia";
8-
import { beforeEach, describe, expect, it } from "vitest";
8+
import { beforeEach, describe, expect, it, vi } from "vitest";
99

1010
import { HttpResponse, useServerMock } from "@/api/client/__mocks__";
1111
import MockCurrentHistory from "@/components/providers/MockCurrentHistory";
@@ -20,6 +20,13 @@ const localVue = getLocalVue();
2020
const router = injectTestRouter(localVue);
2121
const pinia = createPinia();
2222

23+
vi.mock("@/composables/userLocalStorageFromHashedId", async () => {
24+
const { ref } = await import("vue");
25+
return {
26+
useUserLocalStorageFromHashId: (_key, initialValue) => ref(initialValue),
27+
};
28+
});
29+
2330
describe("ToolForm", () => {
2431
let wrapper;
2532
let userStore;
@@ -95,6 +102,8 @@ describe("ToolForm", () => {
95102
expect(button.attributes("data-title")).toBe("Run tool: tool_name (version)");
96103
const dropdown = wrapper.findAll(".dropdown-item");
97104
expect(dropdown.length).toBe(2);
105+
const noToolParametersAlert = wrapper.find("[data-description='no tool parameters']");
106+
expect(noToolParametersAlert.text()).toContain("This tool requires no input parameters and can be run as is.");
98107
const help = wrapper.find(".form-help");
99108
expect(help.text()).toBe("help_text");
100109
const creator = wrapper.find(".creative-work-creator");

client/src/components/Tool/ToolForm.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
@onChangeVersion="onChangeVersion">
4444
<div class="mt-2 mb-4">
4545
<Heading v-localize h2 separator bold size="sm"> Tool Parameters </Heading>
46+
47+
<GAlert v-if="showNoToolParametersAlert" show variant="info" data-description="no tool parameters">
48+
This tool requires no input parameters and can be run as is.
49+
</GAlert>
50+
4651
<FormDisplay
4752
:id="toolId"
4853
:inputs="formConfig.inputs"
@@ -134,6 +139,7 @@ import GModal from "../BaseComponents/GModal.vue";
134139
import ToolRecommendation from "../ToolRecommendation.vue";
135140
import ToolCard from "./ToolCard.vue";
136141
import ToolFormTags from "./ToolFormTags.vue";
142+
import GAlert from "@/components/BaseComponents/GAlert.vue";
137143
import ButtonSpinner from "@/components/Common/ButtonSpinner.vue";
138144
import Heading from "@/components/Common/Heading.vue";
139145
import FormSelect from "@/components/Form/Elements/FormSelect.vue";
@@ -154,6 +160,7 @@ export default {
154160
ToolFormTags,
155161
ToolRecommendation,
156162
Heading,
163+
GAlert,
157164
GModal,
158165
},
159166
props: {
@@ -278,6 +285,9 @@ export default {
278285
initialized() {
279286
return this.formData !== undefined;
280287
},
288+
showNoToolParametersAlert() {
289+
return !this.loading && this.formConfig?.inputs?.length === 0;
290+
},
281291
canMutateHistory() {
282292
return this.currentHistory && canMutateHistory(this.currentHistory);
283293
},

0 commit comments

Comments
 (0)