Skip to content

Commit

Permalink
Merge pull request #1475 from dodona-edu/feature/wait-for-completion
Browse files Browse the repository at this point in the history
Wait for analysis job to complete when report is shared
  • Loading branch information
rien authored Apr 22, 2024
2 parents dc96168 + 95316c1 commit 150c8b8
Show file tree
Hide file tree
Showing 16 changed files with 260 additions and 175 deletions.
2 changes: 1 addition & 1 deletion api/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
MYSQL_ROOT_PASSWORD: dolos
networks:
- dolos
web:
api:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails db:prepare && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/FileTimestamp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { computed } from "vue";
interface Props {
file?: File;
timestamp?: Date;
timestamp?: Date | string;
long?: boolean;
}
Expand Down
8 changes: 8 additions & 0 deletions web/src/components/layout/PageError.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@
<div class="page-error">
<h2>Oops! Something went wrong.</h2>
<p>{{ message }}</p>
<v-btn-group v-if="isServer">
<v-btn :to="{ name: 'Upload' }">
Go to upload form
</v-btn>
</v-btn-group>
</div>
</template>

<script lang="ts" setup>
import { computed } from "vue";
import { useAppMode } from "@/composables/useAppMode";
const { isServer } = useAppMode();
interface Props {
error: Error | string;
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/layout/PageNavbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const display = useDisplay();
const drawerValue = useVModel(props, "drawer", emit);
const settingsValue = useVModel(props, "settings", emit);
const shareDialog = ref(false);
const downloadDatasetUrl = computed(() => currentReport.value?.response?.dataset?.zipfile);
const downloadDatasetUrl = computed(() => currentReport.value?.datasetURL);
const { currentReport } = storeToRefs(useReportsStore());
</script>

Expand All @@ -112,4 +112,4 @@ const { currentReport } = storeToRefs(useReportsStore());
}
}
}
</style>
</style>
66 changes: 12 additions & 54 deletions web/src/components/upload/UploadFormCard.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts" setup>
import { shallowRef, ref, computed, watch } from "vue";
import { default as axios } from "axios";
import { useReportsStore } from "@/stores";
const reports = useReportsStore();
Expand Down Expand Up @@ -165,52 +164,18 @@ const handleError = (message: string): void => {
const onSubmit = async (): Promise<void> => {
// Make sure the form is valid.
if (valid.value) {
const file = files.value?.[0];
const data = new FormData();
data.append("dataset[zipfile]", file ?? new Blob());
data.append("dataset[name]", name.value ?? "");
data.append("dataset[programming_language]", language.value ?? "");
const file = files.value?.[0]!;
// Go to the next step.
step.value = 2;
// Upload the file.
try {
const response = await axios.post(
`${import.meta.env.VITE_API_URL}/reports`,
data,
{
onUploadProgress: (e) => {
uploadProgress.value = Math.ceil((e.loaded / (e.total ?? 1)) * 100);
// Go to the next step when the upload is complete.
if (e.loaded === e.total) {
step.value = 3;
}
},
}
);
// Create a new report.
const report = {
reportId: response.data["id"] as string,
name: name.value ?? "",
date: new Date().toISOString(),
status: response.data["status"],
statusUrl: response.data["url"],
response: response.data,
};
// Add the report to the reports list in local storage.
reports.addReport(report);
const report = await reports.uploadReport(name.value ?? file.name, file, language.value ?? "");
// Set the report as active.
reportActiveId.value = response.data["id"];
// Make sure a status url was provided.
if (!report.statusUrl) {
handleError("No analysis URL was provided by the API.");
}
reportActiveId.value = report.id;
step.value = 3;
} catch (e: any) {
if (e.code == "ERR_NETWORK") {
handleError("Could not connect to the API.");
Expand Down Expand Up @@ -245,7 +210,7 @@ const startPolling = (reportId: string): void => {
}
// Get the report from the reports list.
const report = reports.getReportById(reportId);
let report = reports.getReportById(reportId);
// Stop the polling if the report no longer exists.
if (!report) {
Expand All @@ -254,17 +219,10 @@ const startPolling = (reportId: string): void => {
}
try {
const status = await reports.getReportStatus(report);
// Update the report status.
report.status = status.status;
report = await reports.reloadReport(report.id);
// Stop the polling when the report status is final.
if (
report.status === "finished" ||
report.status === "error" ||
report.status === "failed"
) {
if (report.hasFinalStatus()) {
pollingReports.value = pollingReports.value.filter(
(id) => id !== reportId
);
Expand All @@ -273,7 +231,7 @@ const startPolling = (reportId: string): void => {
// If the report is the active report
// apply some changes to the form UI.
if (report === reportActive.value) {
if (report.id === reportActive.value?.id) {
if (report.status === "finished") {
// Go to the results page.
step.value = 4;
Expand All @@ -283,7 +241,7 @@ const startPolling = (reportId: string): void => {
if (report.status === "failed" || report.status === "error") {
stderr.value = report.stderr;
handleError(`An error occurred while analyzing the dataset (${status.error})`);
handleError(`An error occurred while analyzing the dataset (${report.error})`);
}
}
} catch (e: any) {
Expand All @@ -301,8 +259,8 @@ watch(
() => reports.reports,
(reports) => {
for (const report of reports) {
if (report.status === "queued" || report.status === "running") {
startPolling(report.reportId);
if (!report.hasFinalStatus()) {
startPolling(report.id);
}
}
},
Expand Down Expand Up @@ -330,7 +288,7 @@ watch(
<v-alert v-if="error" class="mb-4" variant="tonal" type="error" density="compact">
{{ error }}
</v-alert>
<v-textarea readonly rows="15" :model-value="stderr" />
<v-textarea v-if="stderr" readonly :rows="Math.min(15, stderr?.split('\n').length)" :model-value="stderr" />

</div>

Expand Down
14 changes: 7 additions & 7 deletions web/src/components/upload/UploadStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ const props = defineProps<Props>();
</script>

<template>
<v-chip v-if="props.status === 'queued'" color="warning" small>
<v-chip v-if="props.status === 'queued'" color="warning" size="small">
Queued
</v-chip>
<v-chip v-else-if="props.status === 'running'" color="info" small>
<v-chip v-else-if="props.status === 'running'" color="info" size="small">
Running
</v-chip>
<v-chip v-else-if="props.status === 'finished'" color="success" small>
<v-chip v-else-if="props.status === 'finished'" color="success" size="small">
Finished
</v-chip>
<v-chip v-else-if="props.status === 'failed'" color="error" small>
<v-chip v-else-if="props.status === 'failed'" color="error" size="small">
Failed
</v-chip>
<v-chip v-else-if="props.status === 'error'" color="error" small>
<v-chip v-else-if="props.status === 'error'" color="error" size="small">
Error
</v-chip>
<v-chip v-else-if="props.status === 'deleted'" color="grey" small>
<v-chip v-else-if="props.status === 'deleted'" color="grey" size="small">
Deleted
</v-chip>
<v-chip v-else color="grey" small> Unknown </v-chip>
<v-chip v-else color="grey" size="small"> Unknown </v-chip>
</template>
16 changes: 6 additions & 10 deletions web/src/components/upload/UploadsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,11 @@ const headers = computed<any>(() => [
const items = computed(() =>
reports.reports.map((report) => ({
name: report.name,
date: new Date(report.date),
date: report.date,
status: report.status,
report: report,
isFromSharing: report.isFromSharing,
done:
report.status === "error" ||
report.status === "failed" ||
report.status === "finished" ||
report.status === "deleted",
isFromSharing: report.fromSharing,
done: report.hasFinalStatus(),
}))
);
Expand All @@ -62,19 +58,19 @@ const shareDialog = ref(false);
// Open the dialog for a specific report.
const openInfoDialog = (e: Event, value: any): void => {
selectedReportId.value = value.item.report.reportId;
selectedReportId.value = value.item.report.id;
infoDialog.value = true;
};
// Open the dialog for deleting a specific report.
const openDeleteDialog = (item: any): void => {
selectedReportId.value = item.report.reportId;
selectedReportId.value = item.report.id;
deleteDialog.value = true;
};
// Open the dialog for sharing a specific report.
const openShareDialog = (item: any): void => {
selectedReportId.value = item.report.reportId;
selectedReportId.value = item.report.id;
shareDialog.value = true;
};
</script>
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/upload/UploadsTableDeleteDialog.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { UploadReport } from "@/types/uploads/UploadReport";
import { Report } from "@/types/uploads/UploadReport";
import { useVModel } from "@vueuse/core";
import { ref } from "vue";
import { useSnackbar } from "../util/snackbar/useSnackbar";
Expand All @@ -8,7 +8,7 @@ import { useReportsStore } from "@/stores";
type Props = {
open: boolean;
report: UploadReport;
report: Report;
};
const props = withDefaults(defineProps<Props>(), {
open: false,
Expand All @@ -32,8 +32,8 @@ const confirm = async (): Promise<void> => {
try {
// Attempt to delete the upload.
// Only delete the upload if a report id is present and the report is not already deleted.
if (props.report.reportId && props.report.status !== "deleted") {
await axios.delete(reports.getReportUrlById(props.report.reportId));
if (props.report.id && props.report.status !== "deleted") {
await axios.delete(reports.getReportUrlById(props.report.id));
}
// Close the dialog.
Expand All @@ -58,7 +58,7 @@ const confirm = async (): Promise<void> => {
loading.value = false;
// Delete the upload from local storage.
reports.deleteReportById(props.report.reportId);
reports.deleteReportById(props.report.id);
}
};
</script>
Expand Down
17 changes: 14 additions & 3 deletions web/src/components/upload/UploadsTableInfoDialog.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts" setup>
import { useReportsStore } from "@/stores";
import { UploadReport } from "@/types/uploads/UploadReport";
import { Report } from "@/types/uploads/UploadReport";
import { useVModel } from "@vueuse/core";
import { DateTime } from "luxon";
import { computed } from "vue";
type Props = {
open: boolean;
report: UploadReport;
report: Report;
};
const props = withDefaults(defineProps<Props>(), {
open: false,
Expand All @@ -17,7 +17,7 @@ const open = useVModel(props, "open", emit);
const reports = useReportsStore();
const reportRoute = computed(() =>
reports.getReportRouteById(props.report.reportId)
reports.getReportRouteById(props.report.id)
);
const reportDate = computed(() =>
DateTime.fromISO(props.report.date ?? "").toLocaleString(
Expand Down Expand Up @@ -150,6 +150,17 @@ const isDone = computed(
<v-icon end>mdi-share-variant</v-icon>
</v-btn>

<!-- Download ZIP -->
<v-btn
variant="text"
color="primary"
v-if="props.report.datasetURL"
:href="props.report.datasetURL"
>
Dataset
<v-icon end>mdi-download</v-icon>
</v-btn>

<v-spacer />

<!-- View results -->
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/upload/UploadsTableShareDialog.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { UploadReport } from "@/types/uploads/UploadReport";
import { Report } from "@/types/uploads/UploadReport";
import { useVModel } from "@vueuse/core";
import { computed } from "vue";
import { useSnackbar } from "../util/snackbar/useSnackbar";
Expand All @@ -8,7 +8,7 @@ import { useRouter } from "vue-router";
type Props = {
open: boolean;
report: UploadReport;
report: Report;
};
const props = withDefaults(defineProps<Props>(), {
open: false,
Expand All @@ -18,7 +18,7 @@ const router = useRouter();
const reports = useReportsStore();
const reportShareRoute = computed(() =>
reports.getReportShareRouteById(props.report.reportId)
reports.getReportShareRouteById(props.report.id)
);
const open = useVModel(props, "open", emit);
Expand Down
4 changes: 2 additions & 2 deletions web/src/composables/useAppMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export function useAppMode() {
// URL to the report.
const reportUrl = computed(() => {
if (import.meta.env.VITE_MODE === "server") {
if (reports.currentReport) {
return reports.getReportUrlById(reports.currentReport.reportId);
if (reports.currentReport?.hasFinalStatus()) {
return reports.currentReport.url;
} else {
return undefined;
}
Expand Down
Loading

0 comments on commit 150c8b8

Please sign in to comment.