Skip to content

Commit 5276f8f

Browse files
Add rerun option for invocations
Fixes galaxyproject#11340
1 parent cd9175e commit 5276f8f

9 files changed

Lines changed: 119 additions & 12 deletions

File tree

client/src/api/invocations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export type InvocationInputParameter = components["schemas"]["InvocationInputPar
77
export type InvocationJobsSummary = components["schemas"]["InvocationJobsResponse"];
88
export type InvocationStep = components["schemas"]["InvocationStep"];
99
export type InvocationMessage = components["schemas"]["InvocationMessageResponseUnion"];
10+
export type WorkflowInvocationRequest = components["schemas"]["WorkflowInvocationRequestModel"];
11+
export type WorkflowInvocationRequestInputs = components["schemas"]["WorkflowInvocationRequestModel"]["inputs"];
1012

1113
export type StepJobSummary =
1214
| components["schemas"]["InvocationStepJobsResponseStepModel"]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script setup lang="ts">
2+
import { computed, ref, watch } from "vue";
3+
4+
import { Toast } from "@/composables/toast";
5+
import { useHistoryStore } from "@/stores/historyStore";
6+
import { useInvocationStore } from "@/stores/invocationStore";
7+
8+
import WorkflowRun from "./WorkflowRun.vue";
9+
10+
const props = defineProps<{
11+
invocationId: string;
12+
}>();
13+
14+
const historyStore = useHistoryStore();
15+
const invocationStore = useInvocationStore();
16+
17+
const ready = ref(false);
18+
19+
const requestData = computed(() => invocationStore.getInvocationRequestById(props.invocationId));
20+
21+
watch(
22+
requestData,
23+
async (rerunData) => {
24+
if (rerunData && !ready.value) {
25+
// switch to the history with the original workflow inputs first, then render `WorkflowRun`
26+
await historyStore.setCurrentHistory(rerunData.history_id);
27+
28+
// if we were unable to set the history, we need to show an error
29+
if (historyStore.currentHistoryId !== rerunData.history_id) {
30+
Toast.error("Unable to switch to the history with the original workflow inputs.");
31+
}
32+
ready.value = true;
33+
}
34+
},
35+
{ immediate: true }
36+
);
37+
</script>
38+
39+
<template>
40+
<WorkflowRun
41+
v-if="ready && requestData"
42+
:workflow-id="requestData.workflow_id"
43+
:instance="requestData.instance"
44+
:request-state="requestData.inputs"
45+
:append-step-labels-for-request="false"
46+
prefer-simple-form
47+
:simple-form-use-job-cache="requestData.use_cached_job" />
48+
</template>

client/src/components/Workflow/Run/WorkflowRun.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RouterLink } from "vue-router";
55
import { useRouter } from "vue-router/composables";
66
77
import { canMutateHistory } from "@/api";
8+
import type { WorkflowInvocationRequestInputs } from "@/api/invocations";
89
import { getWorkflowInfo } from "@/api/workflows";
910
import { copyWorkflow } from "@/components/Workflow/workflows.services";
1011
import { useWorkflowInstance } from "@/composables/useWorkflowInstance";
@@ -32,8 +33,9 @@ interface Props {
3233
preferSimpleForm?: boolean;
3334
simpleFormTargetHistory?: string;
3435
simpleFormUseJobCache?: boolean;
35-
requestState?: { [key: string]: unknown };
36+
requestState?: WorkflowInvocationRequestInputs;
3637
instance?: boolean;
38+
appendStepLabelsForRequest?: boolean;
3739
}
3840
3941
const props = withDefaults(defineProps<Props>(), {
@@ -43,6 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
4345
simpleFormUseJobCache: false,
4446
requestState: undefined,
4547
instance: false,
48+
appendStepLabelsForRequest: true,
4649
});
4750
4851
const loading = ref(true);
@@ -248,6 +251,7 @@ defineExpose({
248251
:use-job-cache="simpleFormUseJobCache"
249252
:can-mutate-current-history="canRunOnHistory"
250253
:request-state="requestState"
254+
:append-step-labels-for-request="appendStepLabelsForRequest"
251255
@submissionSuccess="handleInvocations"
252256
@submissionError="handleSubmissionError"
253257
@showAdvanced="showAdvanced" />

client/src/components/Workflow/Run/WorkflowRunFormSimple.vue

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { BAlert, BButton, BButtonGroup, BCard, BFormCheckbox, BOverlay } from "b
55
import { storeToRefs } from "pinia";
66
import { computed, ref, watch } from "vue";
77
8+
import type { WorkflowInvocationRequestInputs } from "@/api/invocations";
89
import { isWorkflowInput } from "@/components/Workflow/constants";
910
import { useConfig } from "@/composables/config";
1011
import { useMarkdown } from "@/composables/markdown";
@@ -29,13 +30,15 @@ interface Props {
2930
targetHistory?: string;
3031
useJobCache?: boolean;
3132
canMutateCurrentHistory: boolean;
32-
requestState?: Record<string, any>;
33+
requestState?: WorkflowInvocationRequestInputs;
34+
appendStepLabelsForRequest?: boolean;
3335
}
3436
3537
const props = withDefaults(defineProps<Props>(), {
3638
targetHistory: "current",
3739
useJobCache: false,
3840
requestState: undefined,
41+
appendStepLabelsForRequest: true,
3942
});
4043
4144
const emit = defineEmits<{
@@ -107,6 +110,12 @@ const formInputs = computed(() => {
107110
if (isWorkflowInput(step.step_type)) {
108111
const stepName = new String(step.step_index) as any;
109112
const stepLabel = step.step_label || new String(step.step_index + 1);
113+
114+
// For the `WorkflowInvocationRequestModel`, (used in `WorkflowRerun`) if there is no step_label, it does not have
115+
// `step.step_index + 1` as a label, and has `step.step_index` instead.
116+
const requestStateIndex =
117+
!props.appendStepLabelsForRequest && !step.step_label ? new String(step.step_index) : stepLabel;
118+
110119
const stepType = step.step_type;
111120
const help = step.annotation;
112121
const longFormInput = step.inputs[0];
@@ -115,9 +124,17 @@ const formInputs = computed(() => {
115124
help: help,
116125
label: stepLabel,
117126
});
118-
if (props.requestState && props.requestState[stepLabel]) {
119-
const value = props.requestState[stepLabel];
120-
stepAsInput.value = value;
127+
if (props.requestState && props.requestState[requestStateIndex]) {
128+
const value = props.requestState[requestStateIndex];
129+
// TODO: Will this break workflow landings? Done for `WorkflowRereun` since
130+
// `WorkflowInvocationRequestModel` does not provide an object with `values` property.
131+
if (stepType === "data_input" || stepType === "data_collection_input") {
132+
stepAsInput.value = {
133+
values: !Array.isArray(value) ? [value] : value,
134+
};
135+
} else {
136+
stepAsInput.value = value;
137+
}
121138
}
122139
// disable collection mapping...
123140
stepAsInput.flavor = "module";

client/src/components/Workflow/WorkflowNavigationTitle.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,12 @@ const workflowImportTitle = computed(() => {
143143
variant="primary"
144144
:title="
145145
!workflow.deleted
146-
? `<b>Rerun</b><br>${getWorkflowName()}`
146+
? `<b>Rerun</b><br>${getWorkflowName()}<br><b>with same inputs</b>`
147147
: 'This workflow has been deleted.'
148148
"
149149
:disabled="workflow.deleted"
150-
force
151150
full
151+
:invocation-id="props.invocation.id"
152152
:version="workflow.version" />
153153
</div>
154154
</div>

client/src/components/Workflow/WorkflowRunButton.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,23 @@ interface Props {
1313
version?: number;
1414
force?: boolean;
1515
variant?: string;
16+
invocationId?: string;
1617
}
1718
1819
const props = withDefaults(defineProps<Props>(), {
1920
title: undefined,
2021
version: undefined,
2122
variant: "primary",
23+
invocationId: undefined,
2224
});
2325
2426
const route = useRoute();
2527
const router = useRouter();
2628
27-
const runPath = computed(
28-
() => `/workflows/run?id=${props.id}${props.version !== undefined ? `&version=${props.version}` : ""}`
29+
const runPath = computed(() =>
30+
!props.invocationId
31+
? `/workflows/run?id=${props.id}${props.version !== undefined ? `&version=${props.version}` : ""}`
32+
: `/workflows/rerun?invocation_id=${props.invocationId}`
2933
);
3034
3135
function routeToPath() {
@@ -45,7 +49,7 @@ function routeToPath() {
4549
<BButton
4650
id="workflow-run-button"
4751
v-b-tooltip.hover.top.html.noninteractive
48-
:title="title ?? 'Run workflow'"
52+
:title="title ?? `${props.invocationId ? 'Rerun' : 'Run'} Workflow`"
4953
:data-workflow-run="id"
5054
:variant="variant"
5155
size="sm"
@@ -55,6 +59,6 @@ function routeToPath() {
5559
@click="routeToPath">
5660
<FontAwesomeIcon :icon="faPlay" fixed-width />
5761

58-
<span v-if="full" v-localize>Run Workflow</span>
62+
<span v-if="full" v-localize> {{ props.invocationId ? "Rerun" : "Run" }} Workflow </span>
5963
</BButton>
6064
</template>

client/src/entry/analysis/router.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import Sharing from "@/components/Sharing/SharingPage.vue";
8686
import HistoryStorageOverview from "@/components/User/DiskUsage/Visualizations/HistoryStorageOverview.vue";
8787
import UserDatasetPermissions from "@/components/User/UserDatasetPermissions.vue";
8888
import WorkflowPublished from "@/components/Workflow/Published/WorkflowPublished.vue";
89+
import WorkflowRerun from "@/components/Workflow/Run/WorkflowRerun.vue";
8990
import WorkflowRun from "@/components/Workflow/Run/WorkflowRun.vue";
9091
import WorkflowInvocationState from "@/components/WorkflowInvocationState/WorkflowInvocationState.vue";
9192

@@ -696,6 +697,14 @@ export function getRouter(Galaxy) {
696697
simpleFormUseJobCache: Galaxy.config.simplified_workflow_run_ui_job_cache === "on",
697698
}),
698699
},
700+
{
701+
path: "workflows/rerun",
702+
component: WorkflowRerun,
703+
redirect: redirectAnon(),
704+
props: (route) => ({
705+
invocationId: route.query.invocation_id,
706+
}),
707+
},
699708
{
700709
path: "workflows/sharing",
701710
component: Sharing,

client/src/stores/invocationStore.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { defineStore } from "pinia";
22
import { ref } from "vue";
33

44
import { GalaxyApi } from "@/api";
5-
import { type InvocationJobsSummary, type InvocationStep, type WorkflowInvocation } from "@/api/invocations";
5+
import {
6+
type InvocationJobsSummary,
7+
type InvocationStep,
8+
type WorkflowInvocation,
9+
type WorkflowInvocationRequest,
10+
} from "@/api/invocations";
611
import { type FetchParams, useKeyedCache } from "@/composables/keyedCache";
712
import type { GraphStep } from "@/composables/useInvocationGraph";
813
import { rethrowSimple } from "@/utils/simple-error";
@@ -42,6 +47,20 @@ export const useInvocationStore = defineStore("invocationStore", () => {
4247
return data;
4348
}
4449

50+
async function fetchInvocationRequest(params: FetchParams): Promise<WorkflowInvocationRequest> {
51+
const { data, error } = await GalaxyApi().GET("/api/invocations/{invocation_id}/request", {
52+
params: {
53+
path: {
54+
invocation_id: params.id,
55+
},
56+
},
57+
});
58+
if (error) {
59+
rethrowSimple(error);
60+
}
61+
return data;
62+
}
63+
4564
async function cancelWorkflowScheduling(invocationId: string) {
4665
const { data, error } = await GalaxyApi().DELETE("/api/invocations/{invocation_id}", {
4766
params: {
@@ -69,6 +88,8 @@ export const useInvocationStore = defineStore("invocationStore", () => {
6988
const { getItemById: getInvocationStepById, fetchItemById: fetchInvocationStepById } =
7089
useKeyedCache<InvocationStep>(fetchInvocationStep);
7190

91+
const { getItemById: getInvocationRequestById } = useKeyedCache<WorkflowInvocationRequest>(fetchInvocationRequest);
92+
7293
return {
7394
cancelWorkflowScheduling,
7495
fetchInvocationById,
@@ -78,6 +99,7 @@ export const useInvocationStore = defineStore("invocationStore", () => {
7899
getInvocationJobsSummaryById,
79100
getInvocationLoadError,
80101
getInvocationStepById,
102+
getInvocationRequestById,
81103
graphStepsByStoreId,
82104
isLoadingInvocation,
83105
};

lib/galaxy/webapps/galaxy/buildapp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ def app_pair(global_conf, load_app_kwds=None, wsgi_preflight=True, **kwargs):
290290
webapp.add_client_route("/workflows/edit")
291291
webapp.add_client_route("/workflows/export")
292292
webapp.add_client_route("/workflows/create")
293+
webapp.add_client_route("/workflows/rerun")
293294
webapp.add_client_route("/workflows/run")
294295
webapp.add_client_route("/workflows/import")
295296
webapp.add_client_route("/workflows/trs_import")

0 commit comments

Comments
 (0)