Skip to content

Commit 058702d

Browse files
Merge pull request #21921 from ahmedhamidawan/add_stop_job_button
Add a stop job button that allows users to stop jobs
2 parents 4491317 + 64ad198 commit 058702d

12 files changed

Lines changed: 423 additions & 86 deletions

File tree

client/src/api/jobs.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import type { components } from "@/api/schema";
2+
import { rethrowSimple } from "@/utils/simple-error";
3+
4+
import { GalaxyApi } from "./client";
25

36
export type JobDestinationParams = components["schemas"]["JobDestinationParams"];
47
export type ShowFullJobResponse = components["schemas"]["ShowFullJobResponse"];
@@ -15,8 +18,8 @@ export type JobMessage =
1518
| components["schemas"]["RegexJobMessage"]
1619
| components["schemas"]["MaxDiscoveredFilesJobMessage"];
1720

18-
export const NON_TERMINAL_STATES = ["new", "queued", "running", "waiting", "paused", "resubmitted", "stop"];
19-
export const ERROR_STATES = ["error", "deleted", "deleting"];
21+
export const NON_TERMINAL_STATES = ["new", "queued", "running", "waiting", "paused", "resubmitted", "upload"];
22+
export const ERROR_STATES = ["error", "deleted", "deleting", "failed"];
2023
export const TERMINAL_STATES = ["ok", "skipped", "stop", "stopping"].concat(ERROR_STATES);
2124

2225
interface JobDef {
@@ -40,3 +43,22 @@ export interface ResponseVal {
4043
jobResponse: JobResponse;
4144
toolName: string;
4245
}
46+
47+
/**
48+
* Delete/Stop a job.
49+
* @param jobId The ID of the job to delete.
50+
* @param message An optional message to be set on the job and output dataset(s) to explain the reason for stopping.
51+
* @returns A promise that resolves to a boolean indicating whether the job was successfully deleted or job was already in a terminal state.
52+
*/
53+
export async function deleteJob(jobId: string, message?: string): Promise<boolean> {
54+
const { data, error } = await GalaxyApi().DELETE("/api/jobs/{job_id}", {
55+
params: { path: { job_id: jobId } },
56+
data: { message },
57+
});
58+
59+
if (error) {
60+
rethrowSimple(error);
61+
}
62+
63+
return data;
64+
}
Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
<script setup lang="ts">
2-
import { faDownload, faInfoCircle, faRedo, faTable } from "@fortawesome/free-solid-svg-icons";
2+
import { faDownload, faInfoCircle, faTable } from "@fortawesome/free-solid-svg-icons";
33
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
44
import { computed } from "vue";
5-
import { useRouter } from "vue-router/composables";
5+
import { useRoute } from "vue-router/composables";
66
77
import type { HDCASummary } from "@/api";
88
import { getAppRoot } from "@/onload/loadConfig";
99
10-
const router = useRouter();
10+
import GButton from "@/components/BaseComponents/GButton.vue";
11+
import GButtonGroup from "@/components/BaseComponents/GButtonGroup.vue";
12+
import RerunJobButton from "@/components/JobInformation/RerunJobButton.vue";
13+
14+
const route = useRoute();
1115
1216
const props = defineProps<{
1317
dsc: HDCASummary; // typescript recognizes HDCADetailed IS_A HDCASummary
1418
}>();
1519
1620
const downloadUrl = computed(() => `${getAppRoot()}api/dataset_collections/${props.dsc.id}/download`);
17-
const rerunUrl = computed(() =>
18-
props.dsc.job_source_type == "Job" ? `/root?job_id=${props.dsc.job_source_id}` : null,
19-
);
2021
const showCollectionDetailsUrl = computed(() =>
2122
props.dsc.job_source_type == "Job" ? `/jobs/${props.dsc.job_source_id}/view` : null,
2223
);
@@ -26,62 +27,60 @@ const hasSampleSheet = computed(() => {
2627
return props.dsc.collection_type && props.dsc.collection_type.startsWith("sample_sheet");
2728
});
2829
29-
const sheetUrl = computed(() => {
30-
return `${getAppRoot()}collection/${props.dsc.id}/sheet`;
31-
});
32-
33-
function onDownload() {
34-
window.location.href = downloadUrl.value;
35-
}
30+
const sheetUrl = computed(() => `/collection/${props.dsc.id}/sheet`);
3631
</script>
3732
<template>
3833
<section>
3934
<nav class="content-operations d-flex justify-content-between bg-secondary">
40-
<b-button-group>
41-
<b-button
35+
<GButtonGroup class="collection-operations-btn-group">
36+
<GButton
4237
title="Download Collection"
4338
:disabled="disableDownload"
44-
class="rounded-0 text-decoration-none"
45-
size="sm"
46-
variant="link"
47-
:href="downloadUrl"
48-
@click="onDownload">
49-
<FontAwesomeIcon class="mr-1" :icon="faDownload" />
39+
size="small"
40+
color="blue"
41+
transparent
42+
:href="downloadUrl">
43+
<FontAwesomeIcon fixed-width :icon="faDownload" />
5044
<span>Download</span>
51-
</b-button>
52-
<b-button
45+
</GButton>
46+
<GButton
5347
v-if="showCollectionDetailsUrl"
54-
class="collection-job-details-btn px-1"
48+
class="collection-job-details-btn"
5549
title="Show Details"
56-
size="sm"
57-
variant="link"
58-
:href="showCollectionDetailsUrl"
59-
@click.prevent.stop="router.push(showCollectionDetailsUrl)">
60-
<FontAwesomeIcon class="mr-1" :icon="faInfoCircle" />
50+
size="small"
51+
color="blue"
52+
transparent
53+
:pressed="route.fullPath === showCollectionDetailsUrl"
54+
:to="showCollectionDetailsUrl">
55+
<FontAwesomeIcon fixed-width :icon="faInfoCircle" />
6156
<span>Show Details</span>
62-
</b-button>
63-
<b-button
64-
v-if="rerunUrl"
65-
title="Rerun job"
66-
class="rounded-0 text-decoration-none"
67-
size="sm"
68-
variant="link"
69-
:href="rerunUrl"
70-
@click.prevent.stop="router.push(rerunUrl)">
71-
<FontAwesomeIcon class="mr-1" :icon="faRedo" />
72-
<span>Run Job Again</span>
73-
</b-button>
74-
<b-button
57+
</GButton>
58+
<RerunJobButton
59+
v-if="props.dsc.job_source_type === 'Job' && props.dsc.job_source_id"
60+
:job-id="props.dsc.job_source_id" />
61+
<GButton
7562
v-if="hasSampleSheet && sheetUrl"
76-
class="rounded-0 text-decoration-none"
77-
size="sm"
78-
variant="link"
79-
:href="sheetUrl"
80-
@click.prevent.stop="router.push(sheetUrl)">
81-
<FontAwesomeIcon class="mr-1" :icon="faTable" />
63+
title="View Sample Sheet"
64+
size="small"
65+
color="blue"
66+
transparent
67+
:pressed="route.fullPath === sheetUrl"
68+
:to="sheetUrl">
69+
<FontAwesomeIcon fixed-width :icon="faTable" />
8270
<span>View Sheet</span>
83-
</b-button>
84-
</b-button-group>
71+
</GButton>
72+
</GButtonGroup>
8573
</nav>
8674
</section>
8775
</template>
76+
77+
<style scoped lang="scss">
78+
.collection-operations-btn-group {
79+
display: flex;
80+
flex-wrap: wrap;
81+
:deep(.g-button) {
82+
border-radius: 0;
83+
white-space: nowrap;
84+
}
85+
}
86+
</style>

client/src/components/JobInformation/JobInformation.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import "@tests/vitest/mockHelpPopovers";
22

3+
import { createTestingPinia } from "@pinia/testing";
34
import { getLocalVue } from "@tests/vitest/helpers";
45
import { mount } from "@vue/test-utils";
56
import flushPromises from "flush-promises";
@@ -61,6 +62,7 @@ describe("JobInformation/JobInformation.vue", () => {
6162
wrapper = mount(JobInformation, {
6263
propsData,
6364
localVue,
65+
pinia: createTestingPinia({ createSpy: vi.fn }),
6466
});
6567
await flushPromises();
6668
jobInfoTable = wrapper.find("#job-information");

client/src/components/JobInformation/JobInformation.vue

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import { getJobDuration } from "./utilities";
1111
1212
import Heading from "../Common/Heading.vue";
1313
import DecodedId from "../DecodedId.vue";
14+
import JobState from "../JobStates/JobState.vue";
1415
import CodeRow from "./CodeRow.vue";
16+
import RerunJobButton from "./RerunJobButton.vue";
1517
import CopyToClipboard from "@/components/CopyToClipboard.vue";
1618
import HelpText from "@/components/Help/HelpText.vue";
1719
import UtcDate from "@/components/UtcDate.vue";
@@ -47,6 +49,13 @@ const jobIsTerminal = computed(() => (job.value?.state ? jobStateIsTerminal(job.
4749
const jobIsRunning = computed(() => (job.value?.state ? jobStateIsRunning(job.value.state) : false));
4850
const routeToInvocation = computed(() => `/workflows/invocations/${fetchedInvocationId.value}`);
4951
52+
/** Whether the job can be rerun; actually decided based on if the tool `is_workflow_compatible`,
53+
* but that would require an extra fetch. Just going by known non-rerunnable tool ids for now.
54+
*/
55+
const jobIsRerunnable = computed(
56+
() => !!job.value?.tool_id && !job.value.tool_id.startsWith("upload") && job.value.tool_id !== "__DATA_FETCH__",
57+
);
58+
5059
// Curious as to why we're trying to access tool_version and traceback like this, when they don't exist on
5160
// `ShowFullJobResponse`? Possibly historical reasons or maybe the `JobProvider` can return different types (doesn't seem like it)?
5261
const toolVersion = computed(() =>
@@ -144,7 +153,15 @@ watch(
144153
:stderr_position="stderr_position"
145154
:stderr_length="stderr_length"
146155
@update:result="updateConsoleOutputs" />
147-
<Heading id="job-information-heading" h1 separator inline size="md"> Job Information </Heading>
156+
<div class="d-flex justify-content-between flex-gapx-1">
157+
<Heading id="job-information-heading" class="flex-grow-1" h1 separator inline size="md">
158+
Job Information
159+
<JobState v-if="job" class="job-information-state-badge" :job="job" />
160+
</Heading>
161+
<div v-if="job && jobIsRerunnable">
162+
<RerunJobButton :job-id="props.jobId" outline />
163+
</div>
164+
</div>
148165
<table id="job-information" class="tabletip info_data_table">
149166
<tbody>
150167
<tr v-if="job && job.tool_id">
@@ -261,9 +278,15 @@ watch(
261278
</table>
262279
</div>
263280
</template>
264-
<style scoped>
281+
<style scoped lang="scss">
282+
@import "@/style/scss/theme/blue.scss";
283+
265284
.tooltipJobInfo {
266285
text-decoration-line: underline;
267286
text-decoration-style: dashed;
268287
}
288+
289+
.job-information-state-badge {
290+
font-size: $h5-font-size;
291+
}
269292
</style>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
import { faRedo } from "@fortawesome/free-solid-svg-icons";
3+
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
4+
import { computed } from "vue";
5+
import { useRoute } from "vue-router/composables";
6+
7+
import GButton from "../BaseComponents/GButton.vue";
8+
9+
const route = useRoute();
10+
11+
const props = defineProps<{
12+
jobId: string;
13+
outline?: boolean;
14+
}>();
15+
16+
const rerunUrl = computed(() => `/?job_id=${props.jobId}`);
17+
</script>
18+
19+
<template>
20+
<GButton
21+
title="Rerun job"
22+
size="small"
23+
color="blue"
24+
:outline="props.outline"
25+
:transparent="!props.outline"
26+
:pressed="route.fullPath === rerunUrl"
27+
:to="rerunUrl">
28+
<FontAwesomeIcon fixed-width :icon="faRedo" />
29+
<span>Run Job Again</span>
30+
</GButton>
31+
</template>

0 commit comments

Comments
 (0)