Skip to content

Commit

Permalink
custom fetcherror + group fix
Browse files Browse the repository at this point in the history
  • Loading branch information
pieterjanin committed May 18, 2024
1 parent 0561d52 commit 6ff26f5
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 20 deletions.
14 changes: 6 additions & 8 deletions frontend/src/components/project/RequirementsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
<v-list-item-title>{{ req.value }}</v-list-item-title>

<template v-if="unmet_extensions.includes(req.value)" v-slot:append>
<v-tooltip :text="$t('project.unmet_mandatory')">
<v-tooltip>
<p>{{ $t("project.unmet_mandatory") }}</p>
<template v-slot:activator="{ props }">
<v-icon
v-bind="props"
Expand All @@ -36,10 +37,7 @@

<template v-if="unmet_extensions.includes(req.value)" v-slot:append>
<v-tooltip>
<h3>
{{ $t("project.unmet_forbidden") }}
</h3>

<p>{{ $t("project.unmet_forbidden") }}</p>
<ul>
<li
v-for="illegal_file in unmetRequirements.find(
Expand Down Expand Up @@ -70,7 +68,7 @@
</template>

<script setup lang="ts">
import { computed, toRefs } from "vue";
import { computed, ref, toRefs } from "vue";
import type { Requirement, UnmetRequirement } from "@/models/Project";
const props = defineProps<{
Expand All @@ -80,8 +78,8 @@ const props = defineProps<{
const { requirements, unmetRequirements } = toRefs(props);
const mandatory = computed(() => requirements.value.filter((r) => r.mandatory));
const forbidden = computed(() => requirements.value.filter((r) => !r.mandatory));
const mandatory = ref<Requirement[]>(requirements.value.filter((r) => r.mandatory));
const forbidden = ref<Requirement[]>(requirements.value.filter((r) => !r.mandatory));
const unmet_extensions = computed(() => unmetRequirements.value.map((r) => r.requirement.value));
</script>
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/submission/SubmitForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { computed, ref, toRefs } from "vue";
import FilesInput from "@/components/form_elements/FilesInput.vue";
import { useRouter } from "vue-router";
import { useCreateSubmissionMutation } from "@/queries/Submission";
import { UnmetRequirementsError, useCreateSubmissionMutation } from "@/queries/Submission";
import { useProjectGroupQuery } from "@/queries/Group";
import { useI18n } from "vue-i18n";
import RequirementsCard from "@/components/project/RequirementsCard.vue";
Expand Down Expand Up @@ -57,10 +57,10 @@ async function formOnSubmit(event: SubmitEvent) {
await mutateAsync(formData);
await router.push(`/groups/${group.value?.id}/submissions`);
} catch (error) {
if (error instanceof Error) {
unmetRequirements.value = error.cause.map((r) => {
return { requirement: { mandatory: r.type === "mandatory", value: r.requirement }, files: r.files };
});
if (error instanceof UnmetRequirementsError) {
unmetRequirements.value = error.unmetRequirements;
} else {
throw error;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/queries/Group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function useProjectGroupQuery(
return useQuery<Group | null, Error>({
queryKey: computed(() => PROJECT_USER_GROUP_QUERY_KEY(toValue(projectId)!)),
queryFn: () => getGroupWithProjectId(groups.value!, toValue(projectId)!),
enabled: !!toValue(projectId) && groups.value !== undefined,
enabled: !!toValue(projectId),
});
}

Expand Down
46 changes: 44 additions & 2 deletions frontend/src/queries/Submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { UseMutationReturnType, UseQueryReturnType } from "@tanstack/vue-qu
import { createSubmission, getFiles, getSubmission, getSubmissions } from "@/services/submission";
import type Submission from "@/models/Submission";
import type FileInfo from "@/models/File";
import { FetchError } from "@/services";
import type { UnmetRequirement } from "@/models/Project";

function SUBMISSION_QUERY_KEY(submissionId: number): (string | number)[] {
return ["submission", submissionId];
Expand Down Expand Up @@ -59,6 +61,15 @@ export function useFilesQuery(
});
}

export class UnmetRequirementsError extends Error {
unmetRequirements: UnmetRequirement[];

constructor(message: string, unmetRequirements: UnmetRequirement[], ...params: any[]) {
super(message, ...params);
this.unmetRequirements = unmetRequirements;
}
}

/**
* Mutation composable for creating a submission
*/
Expand All @@ -67,13 +78,44 @@ export function useCreateSubmissionMutation(
): UseMutationReturnType<Submission, Error, MaybeRefOrGetter<FormData>, void> {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (formData) => createSubmission(toValue(groupId)!, toValue(formData)),
mutationFn: async (formData) => {
try {
return await createSubmission(toValue(groupId)!, toValue(formData));
} catch (error) {
if (
error instanceof FetchError &&
Array.isArray(error.body?.detail) &&
error.body.detail.length > 0 &&
(error.body.detail[0].type === "mandatory" ||
error.body.detail[0].type === "forbidden")
) {
// file requirements were not met,
// server returned files that did not meet the requirements
const unmetArr = error.body.detail.map((r) => ({
requirement: {
mandatory: r.type === "mandatory",
value: r.requirement,
},
files: r.files,
}));
throw new UnmetRequirementsError(
"Submission did not meet project requirements",
unmetArr
);
} else {
throw error;
}
}
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: SUBMISSIONS_QUERY_KEY(toValue(groupId)!) });
},
onError: (error) => {
console.error("Submission creation failed", error);
alert("Could not create submission. Please try again.");

if (!(error instanceof UnmetRequirementsError)) {
alert("Could not create submission. Please try again.");
}
},
});
}
17 changes: 13 additions & 4 deletions frontend/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ const defaultOptions: FetchOptions = {
toJson: true,
};

export class FetchError extends Error {
body: any;

constructor(message?: string, body?: any, ...params: any[]) {
super(message, ...params);
this.body = body;
}
}

/**
* Fetch data from the API
* @param endpoint API endpoint
* @param requestOptions Custom request options
* @param omitContentType Omit the Content-Type header
* @param options Custom fetch options
* @returns Response from the API
*/
export async function authorized_fetch<T>(
Expand All @@ -29,7 +38,7 @@ export async function authorized_fetch<T>(
const { token, isLoggedIn } = storeToRefs(useAuthStore());
const { refresh } = useAuthStore();
if (!isLoggedIn) {
throw new Error("User is not logged in");
throw new FetchError("User is not logged in");
}
const { "Content-Type": contentType, ...strippedHeaders } = {
Authorization: `${token.value!.token_type} ${token.value!.token}`,
Expand All @@ -47,10 +56,10 @@ export async function authorized_fetch<T>(
});
if (response.status === 401) {
await refresh();
throw new Error("Not authenticated");
throw new FetchError("Not authenticated", response.status);
} else if (!response.ok) {
const error = await response.json();
throw new Error(error.detail, { cause: error.detail });
throw new FetchError(error.detail, error);
}
return mergedOptions.toJson ? response.json() : response;
}

0 comments on commit 6ff26f5

Please sign in to comment.