Skip to content

Commit 57e16ee

Browse files
Merge pull request galaxyproject#20624 from ahmedhamidawan/add_error_tab_in_invocation_view
Add a "Debug" (email report) tab to Workflow Invocations
2 parents 28a92f7 + 4857b78 commit 57e16ee

26 files changed

Lines changed: 973 additions & 289 deletions

client/src/api/schema/schema.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,23 @@ export interface paths {
27732773
patch?: never;
27742774
trace?: never;
27752775
};
2776+
"/api/invocations/{invocation_id}/error": {
2777+
parameters: {
2778+
query?: never;
2779+
header?: never;
2780+
path?: never;
2781+
cookie?: never;
2782+
};
2783+
get?: never;
2784+
put?: never;
2785+
/** Submits a bug report for a workflow run via the API. */
2786+
post: operations["report_error_api_invocations__invocation_id__error_post"];
2787+
delete?: never;
2788+
options?: never;
2789+
head?: never;
2790+
patch?: never;
2791+
trace?: never;
2792+
};
27762793
"/api/invocations/{invocation_id}/jobs_summary": {
27772794
parameters: {
27782795
query?: never;
@@ -18244,6 +18261,25 @@ export interface components {
1824418261
/** Markdown */
1824518262
markdown: string;
1824618263
};
18264+
/** ReportInvocationErrorPayload */
18265+
ReportInvocationErrorPayload: {
18266+
/**
18267+
* Email
18268+
* @description Email address for communication with the user. Only required for anonymous users.
18269+
*/
18270+
email?: string | null;
18271+
/**
18272+
* Invocation ID
18273+
* @description The ID of the invocation related to the error.
18274+
* @example 0123456789ABCDEF
18275+
*/
18276+
invocation_id: string;
18277+
/**
18278+
* Message
18279+
* @description The optional message sent with the error report.
18280+
*/
18281+
message?: string | null;
18282+
};
1824718283
/** ReportJobErrorPayload */
1824818284
ReportJobErrorPayload: {
1824918285
/**
@@ -31203,6 +31239,52 @@ export interface operations {
3120331239
};
3120431240
};
3120531241
};
31242+
report_error_api_invocations__invocation_id__error_post: {
31243+
parameters: {
31244+
query?: never;
31245+
header?: {
31246+
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
31247+
"run-as"?: string | null;
31248+
};
31249+
path: {
31250+
/** @description The encoded database identifier of the Invocation. */
31251+
invocation_id: string;
31252+
};
31253+
cookie?: never;
31254+
};
31255+
requestBody: {
31256+
content: {
31257+
"application/json": components["schemas"]["ReportInvocationErrorPayload"];
31258+
};
31259+
};
31260+
responses: {
31261+
/** @description Successful Response */
31262+
204: {
31263+
headers: {
31264+
[name: string]: unknown;
31265+
};
31266+
content?: never;
31267+
};
31268+
/** @description Request Error */
31269+
"4XX": {
31270+
headers: {
31271+
[name: string]: unknown;
31272+
};
31273+
content: {
31274+
"application/json": components["schemas"]["MessageExceptionModel"];
31275+
};
31276+
};
31277+
/** @description Server Error */
31278+
"5XX": {
31279+
headers: {
31280+
[name: string]: unknown;
31281+
};
31282+
content: {
31283+
"application/json": components["schemas"]["MessageExceptionModel"];
31284+
};
31285+
};
31286+
};
31287+
};
3120631288
invocation_jobs_summary_api_invocations__invocation_id__jobs_summary_get: {
3120731289
parameters: {
3120831290
query?: never;

client/src/api/workflows.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,15 @@ export async function loadWorkflows({
8686
return { data, totalMatches };
8787
}
8888

89-
export async function getWorkflowInfo(workflowId: string) {
89+
export async function getWorkflowInfo(workflowId: string, version?: number) {
9090
const { data, error } = await GalaxyApi().GET("/api/workflows/{workflow_id}", {
9191
params: {
9292
path: {
9393
workflow_id: workflowId,
9494
},
95+
query: {
96+
version: version,
97+
},
9598
},
9699
});
97100

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<script setup lang="ts">
2+
import { faBug } from "@fortawesome/free-solid-svg-icons";
3+
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
4+
import { BAlert } from "bootstrap-vue";
5+
import { storeToRefs } from "pinia";
6+
import { computed, ref } from "vue";
7+
8+
import { isRegisteredUser } from "@/api";
9+
import { useMarkdown } from "@/composables/markdown";
10+
import { useUserStore } from "@/stores/userStore";
11+
import localize from "@/utils/localization";
12+
import { errorMessageAsString } from "@/utils/simple-error";
13+
14+
import GButton from "../BaseComponents/GButton.vue";
15+
import GLink from "../BaseComponents/GLink.vue";
16+
import FormElement from "../Form/FormElement.vue";
17+
18+
const props = defineProps<{
19+
submit: (message: string) => Promise<string[][] | undefined>;
20+
requireLogin?: boolean;
21+
}>();
22+
23+
const { currentUser } = storeToRefs(useUserStore());
24+
const userEmail = computed<string | null>(() => {
25+
const user = currentUser.value;
26+
if (isRegisteredUser(user)) {
27+
return user.email;
28+
} else {
29+
return null;
30+
}
31+
});
32+
33+
const message = ref("");
34+
const resultMessages = ref<string[][]>([]);
35+
36+
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
37+
38+
const showForm = computed(() => {
39+
const noResult = !resultMessages.value.length;
40+
const hasError = resultMessages.value.some((msg) => msg[1] === "danger");
41+
42+
return noResult || hasError;
43+
});
44+
45+
async function submitEmail() {
46+
try {
47+
const messages = await props.submit(message.value);
48+
if (messages) {
49+
resultMessages.value = messages;
50+
}
51+
} catch (e) {
52+
const error = errorMessageAsString(e, localize("An error occurred while submitting the report."));
53+
resultMessages.value = [[localize(error), "danger"]];
54+
}
55+
}
56+
</script>
57+
58+
<template>
59+
<div>
60+
<h4 class="mb-3 h-md">Issue Report</h4>
61+
<BAlert v-if="props.requireLogin && !userEmail" variant="info" show>
62+
<span v-localize> You must be logged in to submit a report. </span>
63+
<GLink to="/login/start"> Please log in to continue. </GLink>
64+
</BAlert>
65+
66+
<div v-else>
67+
<BAlert v-for="(resultMessage, index) in resultMessages" :key="index" :variant="resultMessage[1]" show>
68+
<!-- eslint-disable-next-line vue/no-v-html -->
69+
<span v-html="renderMarkdown(resultMessage[0] ?? '')" />
70+
</BAlert>
71+
72+
<div v-if="showForm" id="email-report-form">
73+
<span class="mr-2 font-weight-bold">{{ localize("Your email address") }}</span>
74+
<span v-if="userEmail">{{ userEmail }}</span>
75+
<span v-else>{{ localize("You must be logged in to receive emails") }}</span>
76+
77+
<FormElement
78+
id="email-report-message"
79+
v-model="message"
80+
:area="true"
81+
title="Please provide detailed information on the activities leading to the issue(s):" />
82+
83+
<GButton id="email-report-submit" color="blue" class="mt-3" @click="submitEmail">
84+
<FontAwesomeIcon :icon="faBug" class="mr-1" />
85+
Report
86+
</GButton>
87+
</div>
88+
</div>
89+
</div>
90+
</template>

client/src/components/DatasetInformation/DatasetError.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ const localVue = getLocalVue();
1414

1515
const DATASET_ID = "dataset_id";
1616

17+
jest.mock("@/composables/config", () => ({
18+
useConfig: jest.fn(() => ({
19+
config: {},
20+
isConfigLoaded: true,
21+
})),
22+
}));
23+
1724
const { server, http } = useServerMock();
1825

1926
type RegexJobMessage = components["schemas"]["RegexJobMessage"];
@@ -132,10 +139,10 @@ describe("DatasetError", () => {
132139
})
133140
);
134141

135-
const FormAndSubmitButton = "#dataset-error-form";
142+
const FormAndSubmitButton = "#email-report-form";
136143
expect(wrapper.find(FormAndSubmitButton).exists()).toBe(true);
137144

138-
const submitButton = "#dataset-error-submit";
145+
const submitButton = "#email-report-submit";
139146
expect(wrapper.find(submitButton).exists()).toBe(true);
140147

141148
await wrapper.find(submitButton).trigger("click");

client/src/components/DatasetInformation/DatasetError.vue

Lines changed: 11 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,39 @@
11
<script setup lang="ts">
2-
import { library } from "@fortawesome/fontawesome-svg-core";
3-
import { faBug } from "@fortawesome/free-solid-svg-icons";
4-
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
52
import { BAlert, BCard } from "bootstrap-vue";
63
import { storeToRefs } from "pinia";
74
import { computed, onMounted, ref } from "vue";
85
9-
import { GalaxyApi, type HDADetailed, isRegisteredUser } from "@/api";
6+
import { GalaxyApi, type HDADetailed } from "@/api";
107
import { fetchDatasetDetails } from "@/api/datasets";
118
import type { JobDetails, JobInputSummary } from "@/api/jobs";
129
import { useConfig } from "@/composables/config";
13-
import { useMarkdown } from "@/composables/markdown";
1410
import { useUserStore } from "@/stores/userStore";
1511
import localize from "@/utils/localization";
1612
import { errorMessageAsString } from "@/utils/simple-error";
1713
14+
import EmailReportForm from "../Common/EmailReportForm.vue";
1815
import LoadingSpan from "../LoadingSpan.vue";
19-
import GButton from "@/components/BaseComponents/GButton.vue";
2016
import DatasetErrorDetails from "@/components/DatasetInformation/DatasetErrorDetails.vue";
21-
import FormElement from "@/components/Form/FormElement.vue";
2217
import GalaxyWizard from "@/components/GalaxyWizard.vue";
2318
24-
library.add(faBug);
25-
2619
interface Props {
2720
datasetId: string;
2821
}
2922
3023
const props = defineProps<Props>();
3124
3225
const userStore = useUserStore();
33-
const { currentUser, isAnonymous } = storeToRefs(userStore);
26+
const { isAnonymous } = storeToRefs(userStore);
3427
35-
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
3628
const { config, isConfigLoaded } = useConfig();
3729
38-
const message = ref("");
3930
const jobLoading = ref(true);
4031
const errorMessage = ref("");
4132
const datasetLoading = ref(false);
4233
const jobDetails = ref<JobDetails>();
4334
const jobProblems = ref<JobInputSummary>();
44-
const resultMessages = ref<string[][]>([]);
4535
const dataset = ref<HDADetailed>();
4636
47-
const showForm = computed(() => {
48-
const noResult = !resultMessages.value.length;
49-
const hasError = resultMessages.value.some((msg) => msg[1] === "danger");
50-
51-
return noResult || hasError;
52-
});
53-
5437
const showWizard = computed(() => isConfigLoaded && config.value?.llm_api_configured && !isAnonymous.value);
5538
5639
async function getDatasetDetails() {
@@ -99,20 +82,20 @@ async function getJobProblems(jobId: string) {
9982
jobProblems.value = data;
10083
}
10184
102-
async function submit(dataset?: HDADetailed, userEmailJob?: string | null) {
103-
if (!dataset) {
85+
async function submit(message: string): Promise<string[][] | undefined> {
86+
if (!dataset.value) {
10487
errorMessage.value = "No dataset found.";
10588
return;
10689
}
10790
10891
const { data, error } = await GalaxyApi().POST("/api/jobs/{job_id}/error", {
10992
params: {
110-
path: { job_id: dataset.creating_job },
93+
path: { job_id: dataset.value.creating_job },
11194
},
11295
body: {
113-
dataset_id: dataset.id,
114-
message: message.value,
115-
email: userEmailJob,
96+
dataset_id: dataset.value.id,
97+
message: message,
98+
email: jobDetails.value?.user_email,
11699
},
117100
});
118101
@@ -121,22 +104,13 @@ async function submit(dataset?: HDADetailed, userEmailJob?: string | null) {
121104
return;
122105
}
123106
124-
resultMessages.value = data.messages;
107+
return data.messages;
125108
}
126109
127110
function onMissingJobId() {
128111
errorMessage.value = "No job ID found for this dataset.";
129112
}
130113
131-
const userEmail = computed<string | null>(() => {
132-
const user = currentUser.value;
133-
if (isRegisteredUser(user)) {
134-
return user.email;
135-
} else {
136-
return null;
137-
}
138-
});
139-
140114
onMounted(async () => {
141115
await getDatasetDetails();
142116
@@ -226,31 +200,7 @@ onMounted(async () => {
226200
</b>
227201
</p>
228202

229-
<h4 class="mb-3 h-md">Issue Report</h4>
230-
<BAlert v-for="(resultMessage, index) in resultMessages" :key="index" :variant="resultMessage[1]" show>
231-
<span v-html="renderMarkdown(resultMessage[0] ?? '')" />
232-
</BAlert>
233-
234-
<div v-if="showForm" id="dataset-error-form">
235-
<span class="mr-2 font-weight-bold">{{ localize("Your email address") }}</span>
236-
<span v-if="userEmail">{{ userEmail }}</span>
237-
<span v-else>{{ localize("You must be logged in to receive emails") }}</span>
238-
239-
<FormElement
240-
id="dataset-error-message"
241-
v-model="message"
242-
:area="true"
243-
title="Please provide detailed information on the activities leading to this issue:" />
244-
245-
<GButton
246-
id="dataset-error-submit"
247-
color="blue"
248-
class="mt-3"
249-
@click="submit(dataset, jobDetails?.user_email)">
250-
<FontAwesomeIcon :icon="faBug" class="mr-1" />
251-
Report
252-
</GButton>
253-
</div>
203+
<EmailReportForm :submit="submit" />
254204
</div>
255205
</div>
256206
</template>

client/src/components/Workflow/Editor/AreaHighlight.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ const bounds = ref<Rectangle>({ x: 0, y: 0, width: 0, height: 0 });
88
const blink = ref(false);
99
1010
const style = computed(() => {
11-
console.log("computed");
12-
1311
return {
1412
top: bounds.value.y + "px",
1513
left: bounds.value.x + "px",

client/src/components/Workflow/Invocation/Graph/InvocationGraph.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ function stepClicked(nodeId: number | null) {
233233
ref="loadedJobInfo"
234234
:key="activeNodeId"
235235
:invocation="props.invocation"
236-
:workflow="props.workflow"
237236
:workflow-step="props.workflow.steps[activeNodeId]"
238237
in-graph-view
239238
:graph-step="steps[activeNodeId]"

0 commit comments

Comments
 (0)