Skip to content

Commit

Permalink
This change is a refactoring of the email web-form on the Dataset Err…
Browse files Browse the repository at this point in the history
…or Page (for job-submissions) for separation between front/back-end code, easier development QA for reports/system emails, and component reusability.
  • Loading branch information
hujambo-dunia committed Jan 13, 2025
1 parent 25d7ab4 commit 3b1bd31
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 68 deletions.
98 changes: 98 additions & 0 deletions client/src/components/Collections/common/UserReportingError.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faBug } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton } from "bootstrap-vue";
import { computed, ref } from "vue";
import { GalaxyApi, type HDADetailed } from "@/api";
import { useMarkdown } from "@/composables/markdown";
import localize from "@/utils/localization";
import { errorMessageAsString } from "@/utils/simple-error";
import FormElement from "@/components/Form/FormElement.vue";
library.add(faBug);
interface Props {
dataset: HDADetailed;
reportingEmail: String;
}
const props = defineProps<Props>();
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
const message = ref("");
const errorMessage = ref("");
const resultMessages = ref<string[][]>([]);
const showForm = computed(() => {
const noResult = !resultMessages.value.length;
const hasError = resultMessages.value.some((msg) => msg[1] === "danger");
return noResult || hasError;
});
const FIELD_MESSAGE = {
loginRequired: localize("You must be logged in to send emails."),
datasetRequired: localize("You must provide a valid dataset to send emails."),
};
const fieldMessages = computed(() =>
[!props.dataset && FIELD_MESSAGE.datasetRequired, !props.reportingEmail && FIELD_MESSAGE.loginRequired].filter(
Boolean
)
);
async function submitReport(dataset?: HDADetailed, email?: string | null) {
if (!dataset || !email) {
return;
}
const { data, error } = await GalaxyApi().POST("/api/jobs/{job_id}/error", {
params: {
path: { job_id: props.dataset.creating_job },
},
body: {
dataset_id: props.dataset.id,
message: message.value,
email: email,
},
});
if (error) {
return void (errorMessage.value = errorMessageAsString(error));
}
resultMessages.value = data.messages;
}
</script>

<template>
<div>
<BAlert v-for="(resultMessage, index) in resultMessages" :key="index" :variant="resultMessage[1]" show>
<span v-html="renderMarkdown(resultMessage[0])" />

Check warning on line 69 in client/src/components/Collections/common/UserReportingError.vue

View workflow job for this annotation

GitHub Actions / client-unit-test (18)

'v-html' directive can lead to XSS attack
</BAlert>

<div v-if="showForm" id="dataset-error-form">
<div>
<span class="mr-2 font-weight-bold">{{ localize("Your email address") }}</span>
<span v-if="props.reportingEmail">{{ props.reportingEmail }}</span>
<span v-else>{{ FIELD_MESSAGE.loginRequired }}</span>
</div>
<div>
<span class="mr-2 font-weight-bold">{{
localize("Please provide detailed information on the activities leading to this issue:")
}}</span>
<span v-if="!props.dataset">{{ FIELD_MESSAGE.datasetRequired }}</span>
</div>
<FormElement v-if="props.dataset" id="dataset-error-message" v-model="message" :area="true" />
</div>

<BButton
id="dataset-error-submit"
v-b-tooltip.hover
:title="fieldMessages.join('\n')"
variant="primary"
class="mt-3"
@click="submitReport(props.dataset, props.reportingEmail)">
<FontAwesomeIcon :icon="faBug" class="mr-1" />
Report
</BButton>
</div>
</template>
72 changes: 4 additions & 68 deletions client/src/components/DatasetInformation/DatasetError.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faBug } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BCard } from "bootstrap-vue";
import { BAlert, BCard } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref } from "vue";
import { onMounted, ref } from "vue";
import { GalaxyApi, type HDADetailed } from "@/api";
import { fetchDatasetDetails } from "@/api/datasets";
import { type JobDetails, type JobInputSummary } from "@/api/jobs";
import { useMarkdown } from "@/composables/markdown";
import { useUserStore } from "@/stores/userStore";
import localize from "@/utils/localization";
import { errorMessageAsString } from "@/utils/simple-error";
import UserReportingError from "../Collections/common/UserReportingError.vue";
import DatasetErrorDetails from "@/components/DatasetInformation/DatasetErrorDetails.vue";
import FormElement from "@/components/Form/FormElement.vue";
import GalaxyWizard from "@/components/GalaxyWizard.vue";
library.add(faBug);
Expand All @@ -25,28 +22,15 @@ interface Props {
}
const props = defineProps<Props>();
const userStore = useUserStore();
const { currentUser } = storeToRefs(userStore);
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
const message = ref("");
const jobLoading = ref(true);
const errorMessage = ref("");
const datasetLoading = ref(false);
const jobDetails = ref<JobDetails>();
const jobProblems = ref<JobInputSummary>();
const resultMessages = ref<string[][]>([]);
const dataset = ref<HDADetailed>();
const showForm = computed(() => {
const noResult = !resultMessages.value.length;
const hasError = resultMessages.value.some((msg) => msg[1] === "danger");
return noResult || hasError;
});
async function getDatasetDetails() {
datasetLoading.value = true;
try {
Expand Down Expand Up @@ -93,31 +77,6 @@ async function getJobProblems(jobId: string) {
jobProblems.value = data;
}
async function submit(dataset?: HDADetailed, userEmailJob?: string | null) {
if (!dataset) {
errorMessage.value = "No dataset found.";
return;
}
const { data, error } = await GalaxyApi().POST("/api/jobs/{job_id}/error", {
params: {
path: { job_id: dataset.creating_job },
},
body: {
dataset_id: dataset.id,
message: message.value,
email: userEmailJob,
},
});
if (error) {
errorMessage.value = errorMessageAsString(error);
return;
}
resultMessages.value = data.messages;
}
function onMissingJobId() {
errorMessage.value = "No job ID found for this dataset.";
}
Expand Down Expand Up @@ -205,30 +164,7 @@ onMounted(async () => {
</p>

<h4 class="mb-3 h-md">Issue Report</h4>
<BAlert v-for="(resultMessage, index) in resultMessages" :key="index" :variant="resultMessage[1]" show>
<span v-html="renderMarkdown(resultMessage[0])" />
</BAlert>

<div v-if="showForm" id="dataset-error-form">
<span class="mr-2 font-weight-bold">{{ localize("Your email address") }}</span>
<span v-if="currentUser?.email">{{ currentUser.email }}</span>
<span v-else>{{ localize("You must be logged in to receive emails") }}</span>

<FormElement
id="dataset-error-message"
v-model="message"
:area="true"
title="Please provide detailed information on the activities leading to this issue:" />

<BButton
id="dataset-error-submit"
variant="primary"
class="mt-3"
@click="submit(dataset, jobDetails?.user_email)">
<FontAwesomeIcon :icon="faBug" class="mr-1" />
Report
</BButton>
</div>
<UserReportingError :dataset="dataset" :reporting-email="currentUser?.email" />
</div>
</div>
</template>
93 changes: 93 additions & 0 deletions lib/galaxy/config/templates/mail/error-report-dataset.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<html>
<body>
<h1>Galaxy Tool Error Report</h1>
<span class="sub">
<i>from </i>
<span style="font-family: monospace;">
<a href="${host}">${host}</a>
</span>
</span>
<h3>Error Localization</h3>
<table style="margin:1em">
<tbody>
<tr>
<td>Dataset</td>
<td>
<a href="${hda_show_params_link}">${dataset_id} (${dataset_id_encoded})</a>
</td>
</tr>
<tr style="background-color: #f2f2f2">
<td>History</td>
<td>
<a href="${history_view_link}">${history_id} (${history_id_encoded})</a>
</td>
</tr>
<tr>
<td>Failed Job</td>
<td>${hid}: ${history_item_name} (${hda_id_encoded})</td>
</tr>
</tbody>
</table>
<h3>User Provided Information</h3>

The user
<span style="font-family: monospace;">${email_str}</span> provided the following information:

<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${message}
</pre>
<h3>Detailed Job Information</h3>

Job environment and execution information is available at the job
<a href="${hda_show_params_link}">info page</a>.

<table style="margin:1em">
<tbody>
<tr>
<td>Job ID</td>
<td>${job_id} (${job_id_encoded})</td>
</tr>
<tr style="background-color: #f2f2f2">
<td>Tool ID</td>
<td>${job_tool_id}</td>
</tr>
<tr>
<td>Tool Version</td>
<td>${tool_version}</td>
</tr>
<tr style="background-color: #f2f2f2">
<td>Job PID or DRM id</td>
<td>${job_runner_external_id}</td>
</tr>
<tr>
<td>Job Tool Version</td>
<td>${job_tool_version}</td>
</tr>
</tbody>
</table>
<h3>Job Execution and Failure Information</h3>
<h4>Command Line</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_command_line}
</pre>
<h4>stderr</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_stderr}
</pre>
<h4>stdout</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_stdout}
</pre>
<h4>Job Information</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_info}
</pre>
<h4>Job Traceback</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_traceback}
</pre>

This is an automated message. Do not reply to this address.

</body>
</html>
44 changes: 44 additions & 0 deletions lib/galaxy/config/templates/mail/error-report-dataset.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
GALAXY TOOL ERROR REPORT
------------------------

This error report was sent from the Galaxy instance hosted on the server
"${host}"
-----------------------------------------------------------------------------
This is in reference to dataset id ${dataset_id} (${dataset_id_encoded}) from history id ${history_id} (${history_id_encoded})
-----------------------------------------------------------------------------
You should be able to view the history containing the related history item (${hda_id_encoded})

${hid}: ${history_item_name}

by logging in as a Galaxy admin user to the Galaxy instance referenced above
and pointing your browser to the following link.

${history_view_link}
-----------------------------------------------------------------------------
The user ${email_str} provided the following information:

${message}
-----------------------------------------------------------------------------
info url: ${hda_show_params_link}
job id: ${job_id} (${job_id_encoded})
tool id: ${job_tool_id}
tool version: ${tool_version}
job pid or drm id: ${job_runner_external_id}
job tool version: ${job_tool_version}
-----------------------------------------------------------------------------
job command line:
${job_command_line}
-----------------------------------------------------------------------------
job stderr:
${job_stderr}
-----------------------------------------------------------------------------
job stdout:
${job_stdout}
-----------------------------------------------------------------------------
job info:
${job_info}
-----------------------------------------------------------------------------
job traceback:
${job_traceback}
-----------------------------------------------------------------------------
(This is an automated message).

0 comments on commit 3b1bd31

Please sign in to comment.