diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationMetrics.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationMetrics.vue index 40f46da6db23..ff06ca1adf7d 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationMetrics.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationMetrics.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import { BButtonGroup, BCol, BContainer, BRow } from "bootstrap-vue"; +import { BAlert, BButtonGroup, BCol, BContainer, BRow } from "bootstrap-vue"; import type { VisualizationSpec } from "vega-embed"; import { computed, ref, watch } from "vue"; import { type ComputedRef } from "vue"; @@ -8,16 +8,16 @@ import { type components, GalaxyApi } from "@/api"; import { getAppRoot } from "@/onload/loadConfig"; import { errorMessageAsString } from "@/utils/simple-error"; +import LoadingSpan from "../LoadingSpan.vue"; import HelpText from "@/components/Help/HelpText.vue"; const VegaWrapper = () => import("./VegaWrapper.vue"); -const props = defineProps({ - invocationId: { - type: String, - required: true, - }, -}); +interface Props { + invocationId: string; + notTerminal?: boolean; +} +const props = defineProps<Props>(); const groupBy = ref<"tool_id" | "step_id">("tool_id"); const timing = ref<"seconds" | "minutes" | "hours">("seconds"); @@ -44,7 +44,11 @@ async function fetchMetrics() { } } -watch(props, () => fetchMetrics(), { immediate: true }); +watch( + () => props.invocationId, + () => fetchMetrics(), + { immediate: true } +); function itemToX(item: components["schemas"]["WorkflowJobMetric"]) { if (groupBy.value === "tool_id") { @@ -380,6 +384,9 @@ const groupByInTitles = computed(() => { <template> <div> + <BAlert v-if="props.notTerminal" variant="warning" show> + <LoadingSpan message="Metrics will update and change as the workflow progresses." /> + </BAlert> <BContainer> <BRow align-h="end" class="mb-2"> <BButtonGroup> diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.test.ts b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.test.ts index 84abb4dd163e..16eeb0808b6b 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.test.ts +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.test.ts @@ -16,6 +16,7 @@ const selectors = { bAlertStub: "balert-stub", spanElement: "span", invocationReportTab: '[titleitemclass="invocation-report-tab"]', + invocationExportTab: '[titleitemclass="invocation-export-tab"]', fullPageHeading: "anonymous-stub[h1='true']", }; @@ -177,16 +178,20 @@ describe("WorkflowInvocationState check invocation and job terminal states", () }); }); -describe("WorkflowInvocationState check 'Report' tab disabled state and header", () => { - it("determines that 'Report' tab is disabled for non-terminal invocation", async () => { +describe("WorkflowInvocationState check 'Report' and 'Export' tab disabled state and header", () => { + it("for non-terminal invocation", async () => { const wrapper = await mountWorkflowInvocationState("non-terminal-id"); const reportTab = wrapper.find(selectors.invocationReportTab); expect(reportTab.attributes("disabled")).toBe("true"); + const exportTab = wrapper.find(selectors.invocationExportTab); + expect(exportTab.attributes("disabled")).toBe("true"); }); - it("determines that 'Report' tab is not disabled for terminal invocation", async () => { + it("for terminal invocation", async () => { const wrapper = await mountWorkflowInvocationState(invocationData.id); const reportTab = wrapper.find(selectors.invocationReportTab); expect(reportTab.attributes("disabled")).toBeUndefined(); + const exportTab = wrapper.find(selectors.invocationExportTab); + expect(exportTab.attributes("disabled")).toBeUndefined(); }); }); diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue index 83248eec6e61..741702b1cdc2 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue @@ -67,22 +67,22 @@ watch( ); const workflowStore = useWorkflowStore(); -const reportTabDisabled = computed( +const tabsDisabled = computed( () => !invocationStateSuccess.value || !invocation.value || !workflowStore.getStoredWorkflowByInstanceId(invocation.value.workflow_id) ); -/** Tooltip message for the report tab when it is disabled */ -const disabledReportTooltip = computed(() => { +/** Tooltip message for the a tab when it is disabled */ +const disabledTabTooltip = computed(() => { const state = invocationState.value; if (state != "scheduled") { - return `This workflow is not currently scheduled. The current state is ${state}. Once the workflow is fully scheduled and jobs have complete this option will become available.`; + return `This workflow is not currently scheduled. The current state is ${state}. Once the workflow is fully scheduled and jobs have complete any disabled tabs will become available.`; } else if (runningCount.value != 0) { - return `The workflow invocation still contains ${runningCount.value} running job(s). Once these jobs have completed this option will become available.`; + return `The workflow invocation still contains ${runningCount.value} running job(s). Once these jobs have completed any disabled tabs will become available.`; } else { - return "Steps for this workflow are still running. A report will be available once complete."; + return "Steps for this workflow are still running. Any disabled tabs will be available once complete."; } }); @@ -332,45 +332,43 @@ async function onCancel() { </BTab> --> <BTab v-if="!props.isSubworkflow" + title="Report" title-item-class="invocation-report-tab" - :disabled="reportTabDisabled" + :disabled="tabsDisabled" :lazy="reportLazy" :active.sync="reportActive"> - <template v-slot:title> - <span>Report</span> - <BBadge - v-if="reportTabDisabled" - v-b-tooltip.hover.noninteractive - :title="disabledReportTooltip" - variant="warning"> - <FontAwesomeIcon :icon="faExclamation" /> - </BBadge> - </template> <InvocationReport v-if="invocationStateSuccess" :invocation-id="invocation.id" /> </BTab> - <BTab title="Export" title-item-class="invocation-export-tab" lazy> + <BTab title="Export" title-item-class="invocation-export-tab" :disabled="tabsDisabled" lazy> <div v-if="invocationAndJobTerminal"> <WorkflowInvocationExportOptions :invocation-id="invocation.id" /> </div> - <BAlert v-else variant="info" show> - <LoadingSpan message="Waiting to complete invocation" /> - </BAlert> </BTab> <BTab title="Metrics" :lazy="true"> - <WorkflowInvocationMetrics :invocation-id="invocation.id"></WorkflowInvocationMetrics> + <WorkflowInvocationMetrics :invocation-id="invocation.id" :not-terminal="!invocationAndJobTerminal" /> </BTab> <template v-slot:tabs-end> - <BButton - v-if="!props.isFullPage && !invocationAndJobTerminal" - v-b-tooltip.noninteractive.hover - class="ml-auto my-1" - title="Cancel scheduling of workflow invocation" - data-description="cancel invocation button" - size="sm" - @click="onCancel"> - <FontAwesomeIcon :icon="faTimes" fixed-width /> - Cancel Workflow - </BButton> + <div class="ml-auto d-flex align-items-center"> + <BBadge + v-if="tabsDisabled" + v-b-tooltip.hover.noninteractive + class="mr-1" + :title="disabledTabTooltip" + variant="primary"> + <FontAwesomeIcon :icon="faExclamation" /> + </BBadge> + <BButton + v-if="!props.isFullPage && !invocationAndJobTerminal" + v-b-tooltip.noninteractive.hover + class="my-1" + title="Cancel scheduling of workflow invocation" + data-description="cancel invocation button" + size="sm" + @click="onCancel"> + <FontAwesomeIcon :icon="faTimes" fixed-width /> + Cancel Workflow + </BButton> + </div> </template> </BTabs> </div> @@ -387,9 +385,10 @@ async function onCancel() { <style lang="scss"> // To show the tooltip on the disabled report tab badge -.invocation-report-tab { +.invocation-report-tab, +.invocation-export-tab { .nav-link.disabled { - pointer-events: auto !important; + background-color: #e9edf0; } } </style> diff --git a/lib/galaxy_test/selenium/test_workflow_run.py b/lib/galaxy_test/selenium/test_workflow_run.py index c47b835f47c3..36a4a60af080 100644 --- a/lib/galaxy_test/selenium/test_workflow_run.py +++ b/lib/galaxy_test/selenium/test_workflow_run.py @@ -37,6 +37,7 @@ class TestWorkflowRun(SeleniumTestCase, UsesHistoryItemAssertions, RunsWorkflows def test_workflow_export_file_rocrate(self): self._setup_simple_invocation_for_export_testing() invocations = self.components.invocations + self.workflow_run_wait_for_ok(hid=2) invocations.export_tab.wait_for_and_click() self.screenshot("invocation_export_formats") invocations.export_output_format(type="ro-crate").wait_for_and_click() @@ -60,6 +61,7 @@ def test_workflow_export_file_rocrate(self): def test_workflow_export_file_native(self): self._setup_simple_invocation_for_export_testing() invocations = self.components.invocations + self.workflow_run_wait_for_ok(hid=2) invocations.export_tab.wait_for_and_click() self.screenshot("invocation_export_formats") invocations.export_output_format(type="default-file").wait_for_and_click()