Skip to content

Commit

Permalink
Merge pull request #776 from upb-uc4/feature/gdpr_api
Browse files Browse the repository at this point in the history
Report Management
  • Loading branch information
bastihav committed Jan 6, 2021
2 parents 87ce758 + d2c23dd commit 41653fd
Show file tree
Hide file tree
Showing 10 changed files with 461 additions and 7 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# [WIP 0.15.1](https://github.com/upb-uc4/ui-web/compare/v0.15.0...v0.15.1) (2020-XX-XX)
# [WIP 0.15.1](https://github.com/upb-uc4/ui-web/compare/v0.15.0...v0.15.1) (2021-XX-XX)
## Feature
- add ability to fetch all information according to gdpr [#776](https://github.com/upb-uc4/ui-web/pull/776)
- add course admission API [#774](https://github.com/upb-uc4/ui-web/pull/774)

# [0.15.0](https://github.com/upb-uc4/ui-web/compare/v0.14.0...v0.15.0) (2020-12-18)
## Feature
- add hyperledger versions [#754](https://github.com/upb-uc4/ui-web/pull/754)
Expand Down
113 changes: 113 additions & 0 deletions src/api/ReportManagement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { AxiosError, AxiosResponse } from "axios";
import Course from "./api_models/course_management/Course";
import APIError from "./api_models/errors/APIError";
import handleAuthenticationError from "./AuthenticationHelper";
import Common from "./Common";
import APIResponse from "./helpers/models/APIResponse";

export default class ReportManagement extends Common {
protected static endpoint = "/report-management";

constructor() {
super(ReportManagement.endpoint);
}

static async getVersion(): Promise<string> {
return super.getVersion();
}

async getArchive(username: string): Promise<APIResponse<File | string>> {
return await this._axios
.get(`/reports/${username}/archive`, {
responseType: "arraybuffer",
})
.then((response: AxiosResponse) => {
if (response.status === 200) {
let blob: Blob = new Blob([response.data], { type: response.headers["content-type"] });
const file: File = new File([blob], "archive.zip", { type: response.headers["content-type"] });
return {
error: {} as APIError,
networkError: false,
statusCode: response.status,
returnValue: file,
};
} else if (response.status === 202) {
return {
error: {} as APIError,
networkError: false,
statusCode: response.status,
returnValue: response.headers["x-uc4-timestamp"],
};
} else {
return Promise.reject("Something went wrong in the archive request.");
}
})
.catch(async (error: AxiosError) => {
if (error.response) {
if (
await handleAuthenticationError({
statusCode: error.response.status,
error: error.response.data as APIError,
returnValue: {} as File,
networkError: false,
})
) {
return await this.getArchive(username);
}
return {
returnValue: {} as File,
statusCode: error.response.status,
error: error.response.data as APIError,
networkError: false,
};
} else {
return {
returnValue: {} as File,
statusCode: 0,
error: {} as APIError,
networkError: true,
};
}
});
}

async deleteArchive(username: string): Promise<APIResponse<boolean>> {
return await this._axios
.delete(`/reports/${username}/archive`)
.then((response: AxiosResponse) => {
return {
error: {} as APIError,
networkError: false,
statusCode: response.status,
returnValue: true,
};
})
.catch(async (error: AxiosError) => {
if (error.response) {
if (
await handleAuthenticationError({
statusCode: error.response.status,
error: error.response.data as APIError,
returnValue: false,
networkError: false,
})
) {
return await this.deleteArchive(username);
}
return {
returnValue: false,
statusCode: error.response.status,
error: error.response.data as APIError,
networkError: false,
};
} else {
return {
returnValue: false,
statusCode: 0,
error: {} as APIError,
networkError: true,
};
}
});
}
}
154 changes: 154 additions & 0 deletions src/components/settings/RequestDataSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<template>
<section class="border-t-2 py-8 border-gray-400">
<div class="lg:flex">
<div class="w-full lg:w-1/3 lg:block mr-12 flex flex-col mb-4">
<div class="flex mb-2 align-baseline">
<label class="block text-gray-700 text-lg font-medium">Request Stored Data</label>
</div>
<label class="block text-gray-600">
Place a request to retrieve and download all your data stored within the system.
</label>
</div>

<div class="w-full lg:w-2/3">
<div class="lg:flex mb-6">
<div v-if="busy" class="mx-auto">
<loading-spinner />
</div>
<div v-else class="w-full">
<div v-if="!isPending">
<button id="requestData" class="btn btn-blue-primary w-48" @click="requestData">Request Data</button>
</div>
<div v-else>
<div v-if="gotTimestamp" class="flex flex-col w-full">
<div class="flex w-full">
<input id="timestamp" disabled class="form-input input-text w-1/2" :value="timestamp" />
<button
id="refreshRequest"
class="btn btn-icon-blue ml-8 w-12"
title="Refresh the requested data"
@click="refresh"
>
<i class="inline fas fa-redo-alt p-2" />
</button>
<button
id="deletePendingRequest"
class="btn btn-icon-red ml-2 w-12s"
title="Delete your request"
@click="deleteData"
>
<i class="inline fas fa-trash-alt p-2" />
</button>
</div>
<p class="text-xs text-gray-600 w-1/2 mt-2">
You have already requested your stored data. It can take up to 5 minutes until the data is ready. You
can hit the refresh button to check if the data is prepared.
</p>
</div>
<div v-else-if="gotData" class="flex">
<div
class="flex flex-col items-center border p-4 rounded-l-lg rounded-b-lg hover:bg-gray-400 cursor-pointer border-gray-500"
title="Download your data"
>
<a id="downloadData" class="ml-3 text-sm btn-blue-tertiary" :href="dataUrl" download="archive.zip">
<i class="inline fas fa-file-alt p-2 text-gray-700 text-5xl" />
<p class="text-gray-700">Your data</p>
</a>
</div>
<div
id="deleteData"
class="border-r border-t border-b border-gray-500 rounded-r-lg h-8 hover:bg-gray-400 cursor-pointer"
title="Delete your requested data locally"
@click="deleteData"
>
<i class="inline fas fa-times p-2 text-red-700 text-md" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>

<script lang="ts">
import { onBeforeMount, ref } from "vue";
import LoadingSpinner from "@/components/common/loading/Spinner.vue";
import ReportManagement from "@/api/ReportManagement";
import { useStore } from "@/use/store/store";
import GenericResponseHandler from "@/use/helpers/GenericResponseHandler";
export default {
name: "RequestDataSection",
components: { LoadingSpinner },
setup() {
const busy = ref(false);
const isPending = ref(false);
const gotTimestamp = ref(false);
const gotData = ref(false);
const timestamp = ref("");
let data = {} as File;
let dataUrl = ref("");
async function requestData() {
busy.value = true;
const username = (await useStore().getters.user).username;
const reportManagement = new ReportManagement();
const response = await reportManagement.getArchive(username);
const handler = new GenericResponseHandler("archive");
const value = handler.handleResponse(response);
if (typeof value === "string" && value != "") {
gotTimestamp.value = true;
isPending.value = true;
timestamp.value = new Date(value).toLocaleString();
} else if (typeof value === "object" && value.size != 0) {
isPending.value = true;
gotData.value = true;
gotTimestamp.value = false;
data = value;
let blob = new Blob([data], { type: "pem" });
dataUrl.value = URL.createObjectURL(blob);
}
busy.value = false;
}
async function refresh() {
await requestData();
}
async function deleteData() {
busy.value = true;
const username = (await useStore().getters.user).username;
const reportManagement = new ReportManagement();
const response = await reportManagement.deleteArchive(username);
const handler = new GenericResponseHandler("archive");
const value = handler.handleResponse(response);
if (value) {
isPending.value = false;
gotData.value = false;
gotTimestamp.value = false;
data = {} as File;
}
busy.value = false;
}
return {
busy,
isPending,
gotTimestamp,
gotData,
timestamp,
requestData,
refresh,
deleteData,
dataUrl,
};
},
};
</script>
8 changes: 3 additions & 5 deletions src/use/helpers/GenericResponseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export default class GenericResponseHandler implements ResponseHandler<boolean>
return response.returnValue;
}
switch (response.statusCode) {
case 400: {
case 400:
case 500:
case 503: {
showAPIToast(response.statusCode);
return response.returnValue;
}
Expand All @@ -32,10 +34,6 @@ export default class GenericResponseHandler implements ResponseHandler<boolean>
showAPIToast(response.statusCode, this.dataType);
return response.returnValue;
}
case 500: {
showAPIToast(response.statusCode);
return response.returnValue;
}
case 200: {
return response.returnValue;
}
Expand Down
3 changes: 3 additions & 0 deletions src/use/helpers/Toasts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export function showAPIToast(code: number, msg?: string) {
case 404:
toast.error("Could not find " + msg + ".");
break;
case 503:
toast.error("Service unavailable. Please consider reporting this.");
break;
default:
toast.error("Something went wrong. Please try again later.");
break;
Expand Down
12 changes: 12 additions & 0 deletions src/use/helpers/Versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CertificateManagement from "@/api/CertificateManagement";
import CourseManagement from "@/api/CourseManagement";
import ExaminationRegulationManagement from "@/api/ExaminationRegulationManagement";
import MatriculationManagement from "@/api/MatriculationManagement";
import ReportManagement from "@/api/ReportManagement";
import UserManagement from "@/api/UserManagement";

export interface version {
Expand Down Expand Up @@ -36,6 +37,8 @@ export async function getVersions(): Promise<version[]> {
const examinationRegulationManagementVersion = await ExaminationRegulationManagement.getVersion();
const hlfExamRegVersion = await ExaminationRegulationManagement.getHyperledgerVersion();

const reportManagementVersion = await ReportManagement.getVersion();

const admissionsManagementVersion = await AdmissionManagement.getVersion();
const hlfAdmissionVersion = await AdmissionManagement.getHyperledgerVersion();

Expand Down Expand Up @@ -101,6 +104,15 @@ export async function getVersions(): Promise<version[]> {
"/product_code/examreg_service/CHANGELOG.md",
hlVersions: hlfExamRegVersion,
});
versions.push({
name: "Report Management",
version: reportManagementVersion,
link:
"https://github.com/upb-uc4/University-Credits-4.0/blob/" +
"report-" +
reportManagementVersion +
"/product_code/report_service/CHANGELOG.md",
});

versions.push({
name: "Admissions Management",
Expand Down
3 changes: 3 additions & 0 deletions src/views/common/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<div>
<security-section />
<certificate-section />
<request-data-section />
</div>
</div>
</template>
Expand All @@ -20,12 +21,14 @@
import { Role } from "@/entities/Role";
import { checkPrivilege } from "@/use/helpers/PermissionHelper";
import CertificateSection from "@/components/settings/CertificateSection.vue";
import RequestDataSection from "@/components/settings/RequestDataSection.vue";
export default {
name: "Settings",
components: {
SecuritySection,
CertificateSection,
RequestDataSection,
},
async beforeRouteEnter(_from: any, _to: any, next: any) {
Expand Down
Loading

0 comments on commit 41653fd

Please sign in to comment.