mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-01 15:37:32 +08:00
Merge branch 'release_24.2' into dev
This commit is contained in:
@@ -83,11 +83,15 @@ if (props.instance) {
|
||||
}
|
||||
|
||||
function handleInvocations(incomingInvocations: any) {
|
||||
invocations.value = incomingInvocations;
|
||||
// make sure any new histories are added to historyStore
|
||||
invocations.value.forEach((invocation: any) => {
|
||||
historyStore.getHistoryById(invocation.history_id);
|
||||
});
|
||||
if (incomingInvocations.length === 1) {
|
||||
router.push(`/workflows/invocations/${incomingInvocations[0].id}?success=true`);
|
||||
} else {
|
||||
invocations.value = incomingInvocations;
|
||||
// make sure any new histories are added to historyStore
|
||||
invocations.value.forEach((invocation: any) => {
|
||||
historyStore.getHistoryById(invocation.history_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmissionError(error: string) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import type { WorkflowInvocation } from "@/api/invocations";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
import Webhooks from "@/utils/webhooks";
|
||||
import { startWatchingHistory } from "@/watch/watchHistoryProvided";
|
||||
|
||||
@@ -23,18 +22,12 @@ onMounted(() => {
|
||||
startWatchingHistory();
|
||||
});
|
||||
|
||||
const historyStore = useHistoryStore();
|
||||
|
||||
const targetHistories = props.invocations.reduce((histories, invocation) => {
|
||||
if (invocation.history_id && !histories.includes(invocation.history_id)) {
|
||||
histories.push(invocation.history_id);
|
||||
}
|
||||
return histories;
|
||||
}, [] as string[]);
|
||||
const wasNewHistoryTarget =
|
||||
props.invocations.length > 0 &&
|
||||
!!props.invocations[0]?.history_id &&
|
||||
historyStore.currentHistoryId !== props.invocations[0].history_id;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -51,7 +44,6 @@ const wasNewHistoryTarget =
|
||||
<WorkflowInvocationState
|
||||
v-else-if="props.invocations.length === 1 && props.invocations[0]"
|
||||
:invocation-id="props.invocations[0].id"
|
||||
:new-history-target="wasNewHistoryTarget"
|
||||
is-full-page
|
||||
success />
|
||||
<div id="webhook-view"></div>
|
||||
|
||||
@@ -22,7 +22,6 @@ interface Props {
|
||||
invocationUpdateTime?: string;
|
||||
historyId: string;
|
||||
showDetails?: boolean;
|
||||
newHistoryTarget?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -71,12 +70,12 @@ const { renderMarkdown } = useMarkdown({
|
||||
<FontAwesomeIcon :icon="faHdd" />History:
|
||||
<SwitchToHistoryLink :history-id="props.historyId" />
|
||||
<BBadge
|
||||
v-if="props.newHistoryTarget && useHistoryStore().currentHistoryId !== props.historyId"
|
||||
v-if="useHistoryStore().currentHistoryId !== props.historyId"
|
||||
v-b-tooltip.hover.noninteractive
|
||||
data-description="new history badge"
|
||||
role="button"
|
||||
variant="info"
|
||||
title="Results generated in a new history. Click on history name to switch to that history.">
|
||||
title="Results generated in a different history. Click on history name to switch to that history.">
|
||||
<FontAwesomeIcon :icon="faExclamation" />
|
||||
</BBadge>
|
||||
</span>
|
||||
|
||||
@@ -33,6 +33,10 @@ const invocationById = {
|
||||
...invocationData,
|
||||
id: "non-terminal-jobs",
|
||||
},
|
||||
"non-terminal-populated-state": {
|
||||
...invocationData,
|
||||
id: "non-terminal-populated-state",
|
||||
},
|
||||
};
|
||||
|
||||
// Jobs summary constants
|
||||
@@ -51,6 +55,10 @@ const invocationJobsSummaryById = {
|
||||
running: 1,
|
||||
},
|
||||
},
|
||||
"non-terminal-populated-state": {
|
||||
...invocationDataJobsSummary,
|
||||
populated_state: "new",
|
||||
},
|
||||
};
|
||||
|
||||
// Mock the invocation store to return the expected invocation data given the invocation ID
|
||||
@@ -163,6 +171,15 @@ describe("WorkflowInvocationState check invocation and job terminal states", ()
|
||||
assertJobsSummaryFetched(1);
|
||||
});
|
||||
|
||||
it("determines that job states are not terminal with non-terminal populated state for summary", async () => {
|
||||
const wrapper = await mountWorkflowInvocationState("non-terminal-populated-state");
|
||||
expect(isInvocationAndJobTerminal(wrapper)).toBe(false);
|
||||
|
||||
// Only the jobs summary should be polled, the invocation is initially fetched only since it is in scheduled/terminal state
|
||||
assertInvocationFetched(1);
|
||||
assertJobsSummaryFetched(1);
|
||||
});
|
||||
|
||||
it("determines that errored invocation fetches are handled correctly", async () => {
|
||||
const wrapper = await mountWorkflowInvocationState("error-invocation");
|
||||
expect(isInvocationAndJobTerminal(wrapper)).toBe(false);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { BAlert, BBadge, BButton, BTab, BTabs } from "bootstrap-vue";
|
||||
import { computed, onUnmounted, ref, watch } from "vue";
|
||||
|
||||
import { type InvocationJobsSummary, type InvocationStep, type WorkflowInvocationElementView } from "@/api/invocations";
|
||||
import { type InvocationStep, type WorkflowInvocationElementView } from "@/api/invocations";
|
||||
import { useAnimationFrameResizeObserver } from "@/composables/sensors/animationFrameResizeObserver";
|
||||
import { useInvocationStore } from "@/stores/invocationStore";
|
||||
import { useWorkflowStore } from "@/stores/workflowStore";
|
||||
@@ -36,7 +36,6 @@ interface Props {
|
||||
isSubworkflow?: boolean;
|
||||
isFullPage?: boolean;
|
||||
success?: boolean;
|
||||
newHistoryTarget?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -80,8 +79,8 @@ 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 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 any disabled tabs will become available.`;
|
||||
} else if (stateCounts.value && stateCounts.value.runningCount != 0) {
|
||||
return `The workflow invocation still contains ${stateCounts.value.runningCount} running job(s). Once these jobs have completed any disabled tabs will become available.`;
|
||||
} else {
|
||||
return "Steps for this workflow are still running. Any disabled tabs will be available once complete.";
|
||||
}
|
||||
@@ -109,18 +108,23 @@ const invocationSchedulingTerminal = computed(() => {
|
||||
);
|
||||
});
|
||||
const jobStatesTerminal = computed(() => {
|
||||
// If the job states summary is null, we haven't fetched it yet
|
||||
// If the `populated_state` for the summary is `new`, we haven't finished scheduling all jobs
|
||||
if (jobStatesSummary.value === null || jobStatesSummary.value.populated_state === "new") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (invocationSchedulingTerminal.value && jobCount.value === 0) {
|
||||
// no jobs for this invocation (think subworkflow or just inputs)
|
||||
// no jobs for this invocation (think it has just subworkflows/inputs)
|
||||
return true;
|
||||
}
|
||||
return !!jobStatesSummary.value && isTerminal(jobStatesSummary.value as InvocationJobsSummary);
|
||||
});
|
||||
const jobStatesSummary = computed(() => {
|
||||
const jobsSummary = invocationStore.getInvocationJobsSummaryById(props.invocationId);
|
||||
return (!jobsSummary ? null : jobsSummary) as InvocationJobsSummary;
|
||||
return isTerminal(jobStatesSummary.value);
|
||||
});
|
||||
const jobStatesSummary = computed(() => invocationStore.getInvocationJobsSummaryById(props.invocationId));
|
||||
const invocationStateSuccess = computed(() => {
|
||||
return invocationState.value == "scheduled" && runningCount.value === 0 && invocationAndJobTerminal.value;
|
||||
return (
|
||||
invocationState.value == "scheduled" && stateCounts.value?.runningCount === 0 && invocationAndJobTerminal.value
|
||||
);
|
||||
});
|
||||
|
||||
type StepStateType = { [state: string]: number };
|
||||
@@ -151,27 +155,31 @@ const stepStatesStr = computed<string>(() => {
|
||||
return `${stepStates.value?.scheduled || 0} of ${stepCount.value} steps successfully scheduled.`;
|
||||
});
|
||||
|
||||
const okCount = computed<number>(() => {
|
||||
return jobStatesSummaryOkCount(jobStatesSummary.value);
|
||||
});
|
||||
|
||||
const errorCount = computed<number>(() => {
|
||||
return jobStatesSummaryErrorCount(jobStatesSummary.value);
|
||||
});
|
||||
|
||||
const runningCount = computed<number>(() => {
|
||||
return jobStatesSummaryRunningCount(jobStatesSummary.value);
|
||||
const stateCounts = computed<{
|
||||
okCount: number;
|
||||
errorCount: number;
|
||||
runningCount: number;
|
||||
newCount: number;
|
||||
} | null>(() => {
|
||||
if (jobStatesSummary.value === null) {
|
||||
return null;
|
||||
}
|
||||
const okCount = jobStatesSummaryOkCount(jobStatesSummary.value);
|
||||
const errorCount = jobStatesSummaryErrorCount(jobStatesSummary.value);
|
||||
const runningCount = jobStatesSummaryRunningCount(jobStatesSummary.value);
|
||||
const newCount = jobCount.value - okCount - runningCount - errorCount;
|
||||
return { okCount, errorCount, runningCount, newCount };
|
||||
});
|
||||
|
||||
const jobCount = computed<number>(() => {
|
||||
return jobStatesSummaryJobCount(jobStatesSummary.value);
|
||||
});
|
||||
|
||||
const newCount = computed<number>(() => {
|
||||
return jobCount.value - okCount.value - runningCount.value - errorCount.value;
|
||||
});
|
||||
|
||||
const jobStatesStr = computed(() => {
|
||||
if (jobStatesSummary.value === null) {
|
||||
return "No jobs summary available yet.";
|
||||
}
|
||||
|
||||
let jobStr = `${numTerminal(jobStatesSummary.value) || 0} of ${jobCount.value} jobs complete`;
|
||||
if (!invocationSchedulingTerminal.value) {
|
||||
jobStr += " (total number of jobs will change until all steps fully scheduled)";
|
||||
@@ -274,8 +282,7 @@ async function onCancel() {
|
||||
v-if="props.isFullPage"
|
||||
:workflow-id="invocation.workflow_id"
|
||||
:invocation-update-time="invocation.update_time"
|
||||
:history-id="invocation.history_id"
|
||||
:new-history-target="props.newHistoryTarget">
|
||||
:history-id="invocation.history_id">
|
||||
<template v-slot:middle-content>
|
||||
<div class="progress-bars mx-1">
|
||||
<ProgressBar
|
||||
@@ -301,12 +308,13 @@ async function onCancel() {
|
||||
:loading="!invocationSchedulingTerminal"
|
||||
class="steps-progress" />
|
||||
<ProgressBar
|
||||
v-if="stateCounts"
|
||||
:note="jobStatesStr"
|
||||
:total="jobCount"
|
||||
:ok-count="okCount"
|
||||
:running-count="runningCount"
|
||||
:new-count="newCount"
|
||||
:error-count="errorCount"
|
||||
:ok-count="stateCounts.okCount"
|
||||
:running-count="stateCounts.runningCount"
|
||||
:new-count="stateCounts.newCount"
|
||||
:error-count="stateCounts.errorCount"
|
||||
:loading="!invocationAndJobTerminal"
|
||||
class="jobs-progress" />
|
||||
</div>
|
||||
|
||||
@@ -655,6 +655,7 @@ export function getRouter(Galaxy) {
|
||||
props: (route) => ({
|
||||
invocationId: route.params.invocationId,
|
||||
isFullPage: true,
|
||||
success: route.query.success,
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user