From 7726df7dce4120f9a3438a37ce12a888ece03d37 Mon Sep 17 00:00:00 2001 From: Mouwrice Date: Sat, 21 May 2022 19:52:42 +0200 Subject: [PATCH 01/29] fix: fixed pagination and student selection --- frontend/components/Students/Students.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/components/Students/Students.tsx b/frontend/components/Students/Students.tsx index da8120bd..e5e8cc21 100644 --- a/frontend/components/Students/Students.tsx +++ b/frontend/components/Students/Students.tsx @@ -222,6 +222,9 @@ export const Students: React.FC<{ const id_to_index: Record = {}; const navigator = (page: number) => { + if (page !== pagination.page) { + setSelectedStudent(-1); + } if (params !== undefined) { search(params, page).then(); } From 3d43c0a5293eebcd81b5b4cb11802a7f18ee17db Mon Sep 17 00:00:00 2001 From: Mouwrice Date: Sat, 21 May 2022 19:56:06 +0200 Subject: [PATCH 02/29] fix: fixed pagination and student selection --- frontend/components/Students/Students.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/components/Students/Students.tsx b/frontend/components/Students/Students.tsx index e5e8cc21..82642d75 100644 --- a/frontend/components/Students/Students.tsx +++ b/frontend/components/Students/Students.tsx @@ -222,9 +222,7 @@ export const Students: React.FC<{ const id_to_index: Record = {}; const navigator = (page: number) => { - if (page !== pagination.page) { - setSelectedStudent(-1); - } + setSelectedStudent(-1); if (params !== undefined) { search(params, page).then(); } From 2b8218d68387867082d5a31d1997f70003fccf7f Mon Sep 17 00:00:00 2001 From: Bram Devlaminck Date: Sat, 21 May 2022 20:10:30 +0200 Subject: [PATCH 03/29] feat: clean database startup is now used --- backend/config.json | 2 +- database/startupScripts/generate_data.sql | 83 +---------------------- 2 files changed, 3 insertions(+), 82 deletions(-) diff --git a/backend/config.json b/backend/config.json index 4b6c95c6..4684e874 100644 --- a/backend/config.json +++ b/backend/config.json @@ -95,7 +95,7 @@ ], "preferred": "/api-osoc", "authScheme": "auth/osoc2", - "defaultUserId": 3, + "defaultUserId": 1, "port": 4096, "pageSize": 10 }, diff --git a/database/startupScripts/generate_data.sql b/database/startupScripts/generate_data.sql index 9cf40de7..3863ba12 100644 --- a/database/startupScripts/generate_data.sql +++ b/database/startupScripts/generate_data.sql @@ -1,84 +1,5 @@ -/* Insert data into person table */ INSERT INTO person(email, "name") -VALUES('alicestudent@gmail.com', 'Alice Smith'), -('bob.admin@osoc.com', 'Bob Jones'), ('Trudycoach@gmail.com', 'Trudy Taylor'), -('osoc2@mail.com', 'Osoc TeamTwo'); +VALUES('osoc2@mail.com', 'Osoc TeamTwo'); -/* Insert data into student table */ -INSERT INTO student(person_id, gender, phone_number, nickname, alumni) -VALUES((SELECT person_id FROM person WHERE "name" LIKE 'Alice%'), -'Female', '0032476553498', 'Unicorn', TRUE); - -/* Insert data into login_user table */ INSERT INTO login_user(person_id, password, is_admin, is_coach, account_status) -VALUES((SELECT person_id FROM person WHERE "name" LIKE 'Bob%'), '$2b$08$ffQO2UCEFHUHcn9d.XHHg.hFKn7oF5AOW82J.hsOqq8gV0TzMEuzq', TRUE, FALSE , 'ACTIVATED'), -((SELECT person_id FROM person WHERE "name" LIKE 'Trudy%'), '$2b$08$XDDmyKZnWsai9wVAW7r.GOOv7pGKa7oHLlBhVAqTmPgiscMzynpVq', FALSE, TRUE, 'PENDING'), -((SELECT person_id FROM person WHERE email = 'osoc2@mail.com'), '$2b$08$MCblaKGOOBV7NpiW62GEc.km732o6XWDJxU6SfU3NMENxMuCWFlJq', TRUE, FALSE, 'ACTIVATED'); - -/* Insert data into osoc table */ -INSERT INTO osoc(year)VALUES(2022); - -/* Insert data into job_application table */ -INSERT INTO job_application(student_id, osoc_id, student_volunteer_info, responsibilities, fun_fact, student_coach, - edus, edu_level, edu_duration, edu_year, edu_institute, email_status, created_at)VALUES - ((SELECT student_id FROM student WHERE phone_number = '0032476553498'), (SELECT osoc_id FROM osoc WHERE year = 2022), - 'Yes, I can work with a student employment agreement in Belgium', 'Very responsible', 'I am a very funny fact', TRUE, '{"Informatics"}', - 'Universitarian', 3, '2022', 'Ghent University', 'APPLIED', '2022-03-14 23:10:00+01'); - - /* Insert data into evaluation table */ - INSERT INTO evaluation(login_user_id, job_application_id, decision, motivation, is_final)VALUES - ((SELECT login_user_id FROM login_user WHERE is_admin = TRUE AND person_id = 2), (SELECT job_application_id FROM job_application), - 'YES', 'Simply the best', TRUE); - -/* Insert data into role table */ -INSERT INTO role(name)VALUES('Developer'); - - /* Insert data into project table */ - INSERT INTO project(name, osoc_id, partner, start_date, end_date)VALUES('OSOC Platform', - (SELECT osoc_id FROM osoc WHERE year = 2022), 'UGent', DATE '2022-07-01', DATE '2022-08-15'); - -/* Insert data into project_user table */ -INSERT INTO project_user(login_user_id, project_id)VALUES((SELECT login_user_id FROM login_user WHERE is_admin = TRUE AND person_id = 2), -(SELECT project_id FROM project WHERE name = 'OSOC Platform')); - -/* Insert data into project_role table */ -INSERT INTO project_role(project_id, role_id, positions) VALUES((SELECT project_id FROM project WHERE name = 'OSOC Platform'), -(SELECT role_id FROM role WHERE name = 'Developer'), 2); - -/* Insert data into contract table */ -INSERT INTO contract(student_id, project_role_id, information, created_by_login_user_id, contract_status) VALUES -((SELECT student_id FROM student WHERE phone_number = '0032476553498'), -(SELECT project_role_id FROM project_role WHERE positions = 2), 'Developer contract for osoc platform', -(SELECT login_user_id FROM login_user WHERE is_admin = TRUE AND person_id = 2), 'DRAFT'); - -/* Insert data into applied_role table */ -INSERT INTO applied_role(job_application_id, role_id)VALUES -((SELECT job_application_id from job_application WHERE fun_fact = 'I am a very funny fact'), -(SELECT role_id FROM role WHERE name = 'Developer')); - -/* Insert data into language table */ -INSERT INTO language(name)VALUES('Dutch'); - -/* Insert data into job_application_skill table */ -INSERT INTO job_application_skill(job_application_id, skill, language_id, level, is_preferred, is_best) VALUES -((SELECT job_application_id from job_application WHERE fun_fact = 'I am a very funny fact'), 'Typing', -(SELECT language_id FROM language WHERE name = 'Dutch'), 2, TRUE, TRUE); - -/* Insert data into attachment table */ -INSERT INTO attachment(job_application_id, data, type)VALUES -((SELECT job_application_id from job_application WHERE fun_fact = 'I am a very funny fact'), -'{https://github.com/SELab-2/OSOC-2}', '{CV_URL}'); - -INSERT INTO attachment(job_application_id, data, type)VALUES -((SELECT job_application_id from job_application WHERE fun_fact = 'I am a very funny fact'), -'{I really need the money}', '{MOTIVATION_STRING}'); - -/* Insert into the login_user_osoc table */ -INSERT INTO login_user_osoc(login_user_id, osoc_id) VALUES ((SELECT login_user_id FROM login_user WHERE is_admin = TRUE AND person_id = 4), -(SELECT osoc_id FROM osoc WHERE year = 2022)); - -/* Insert data into template table */ -INSERT INTO template_email(owner_id, name, content)VALUES -((SELECT login_user_id FROM login_user WHERE is_admin = TRUE AND person_id = 2), 'Some Template', '

I am a template

'); -INSERT INTO template_email(owner_id, name, content, cc, subject)VALUES -((SELECT login_user_id FROM login_user WHERE is_admin = TRUE AND person_id = 2), 'Some Advanced Template', '

I am an advanced template

', 'nobody@me.com', 'A non-suspicious email!'); +VALUES((SELECT person_id FROM person WHERE email = 'osoc2@mail.com'), '$2b$08$MCblaKGOOBV7NpiW62GEc.km732o6XWDJxU6SfU3NMENxMuCWFlJq', TRUE, FALSE, 'ACTIVATED'); \ No newline at end of file From 9773587287391961db1a21a4891856712d864166 Mon Sep 17 00:00:00 2001 From: Bram Devlaminck Date: Sat, 21 May 2022 20:27:01 +0200 Subject: [PATCH 04/29] feat: remove the id from the url on page change --- frontend/components/Students/Students.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/components/Students/Students.tsx b/frontend/components/Students/Students.tsx index 82642d75..3117d791 100644 --- a/frontend/components/Students/Students.tsx +++ b/frontend/components/Students/Students.tsx @@ -223,7 +223,14 @@ export const Students: React.FC<{ const navigator = (page: number) => { setSelectedStudent(-1); + // get the current url and delete the id field + const paramsQuery = new URLSearchParams(window.location.search); + paramsQuery.delete("id"); + // push the url + router.push(`/students?${paramsQuery.toString()}`).then(); + if (params !== undefined) { + window.scrollTo(0, 0); search(params, page).then(); } }; From be6279e6d0c57a39a1fbc71d93796a53b6773c68 Mon Sep 17 00:00:00 2001 From: Bram Devlaminck Date: Sat, 21 May 2022 20:58:48 +0200 Subject: [PATCH 05/29] chore: remove todo's --- backend/orm_functions/project_role.ts | 2 +- backend/routes/project.ts | 1 - backend/tests/request.test.ts | 1 - docker-compose.yml | 6 +++--- frontend/components/Projects/Projects.tsx | 2 +- frontend/components/Students/Students.tsx | 2 +- frontend/pages/reset/[pid].tsx | 1 - 7 files changed, 6 insertions(+), 9 deletions(-) diff --git a/backend/orm_functions/project_role.ts b/backend/orm_functions/project_role.ts index 44b659d3..307a4c3f 100644 --- a/backend/orm_functions/project_role.ts +++ b/backend/orm_functions/project_role.ts @@ -87,7 +87,7 @@ export async function updateProjectRole(projectRole: UpdateProjectRole) { * * @param projectRoleId the projectRole we are deleting from the project * role-table - * @returns TODO: what does this return? + * @returns a promise with the deleted record */ export async function deleteProjectRole(projectRoleId: number) { const result = await prisma.project_role.delete({ diff --git a/backend/routes/project.ts b/backend/routes/project.ts index 06f17961..bfb207a1 100644 --- a/backend/routes/project.ts +++ b/backend/routes/project.ts @@ -900,7 +900,6 @@ export function getRouter(): express.Router { util.route(router, "delete", "/:id/coach", unAssignCoach); util.route(router, "post", "/:id/coach", assignCoach); - // TODO add project conflicts util.addAllInvalidVerbs(router, [ "/", "/all", diff --git a/backend/tests/request.test.ts b/backend/tests/request.test.ts index 633d541c..ebc0c396 100644 --- a/backend/tests/request.test.ts +++ b/backend/tests/request.test.ts @@ -264,7 +264,6 @@ test("Can parse login request", () => { unvalid.body.pass = "Pass #2"; unvalid.body.name = "Name.email.be"; - // TODO return Promise.all([ expect(Rq.parseLoginRequest(valid)).resolves.toStrictEqual({ name: "alice.student@hotmail.be", diff --git a/docker-compose.yml b/docker-compose.yml index 86b2a1d6..5ebad4ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: - '4096:4096' # 4096 is the port used by express, we map this to 4096 depends_on: - db - deploy: # TODO: maybe change the restart policy later? + deploy: restart_policy: condition: on-failure delay: 5s @@ -19,7 +19,7 @@ services: - '3000:3000' # 3000 is used by next, we keep using this one depends_on: - backend - deploy: # TODO: maybe change the restart policy later? + deploy: restart_policy: condition: on-failure delay: 5s @@ -31,7 +31,7 @@ services: - data:/var/lib/postgresql/data ports: - '5432:5432' - deploy: # TODO: maybe change the restart policy later? + deploy: restart_policy: condition: on-failure delay: 5s diff --git a/frontend/components/Projects/Projects.tsx b/frontend/components/Projects/Projects.tsx index 97851f3d..4d99ca2f 100644 --- a/frontend/components/Projects/Projects.tsx +++ b/frontend/components/Projects/Projects.tsx @@ -107,7 +107,7 @@ export const Projects: React.FC = () => { : 0; setPagination({ page: currentPageInt, - count: 0, //TODO: what value should this be? I thought this would have to be currentPageInt * pageSize + 1 + count: 0, }); search(params, currentPageInt).then(); }; diff --git a/frontend/components/Students/Students.tsx b/frontend/components/Students/Students.tsx index da8120bd..503c3940 100644 --- a/frontend/components/Students/Students.tsx +++ b/frontend/components/Students/Students.tsx @@ -269,7 +269,7 @@ export const Students: React.FC<{ : 0; setPagination({ page: currentPageInt, - count: 0, //TODO: what value should this be? I thought this would have to be currentPageInt * pageSize + 1 + count: 0, }); search(params, currentPageInt).then(); }; diff --git a/frontend/pages/reset/[pid].tsx b/frontend/pages/reset/[pid].tsx index 2fda1bf0..61c52d7e 100644 --- a/frontend/pages/reset/[pid].tsx +++ b/frontend/pages/reset/[pid].tsx @@ -123,7 +123,6 @@ const Pid: NextPage = () => { .catch((error) => console.log(error)); if (response.success) { setBackendError(""); - // TODO -- Notification router.push("/login").then(); } else { setBackendError(response.reason); From 427bb40dd0f8123ab80d7c50a4b0c3352ad35ae0 Mon Sep 17 00:00:00 2001 From: Bram Devlaminck Date: Sun, 22 May 2022 09:49:13 +0200 Subject: [PATCH 06/29] feat: backend has code to check for no final decision filter --- backend/orm_functions/student.ts | 9 ++++++++- backend/types.ts | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/orm_functions/student.ts b/backend/orm_functions/student.ts index 4731c21d..f8f211ae 100644 --- a/backend/orm_functions/student.ts +++ b/backend/orm_functions/student.ts @@ -12,6 +12,7 @@ import { } from "./orm_types"; import { getOsocYearsForLoginUser } from "./login_user"; import { deletePersonFromDB } from "./person"; +import { Decision } from "../types"; /** * @@ -179,7 +180,13 @@ export async function filterStudents( // manually create filter object for evaluation because evaluation doesn't need to exist // and then the whole object needs to be undefined let evaluationFilter; - if (statusFilter) { + if ((statusFilter as Decision) === Decision.NONE) { + evaluationFilter = { + none: { + is_final: true, + }, + }; + } else if (statusFilter) { evaluationFilter = { some: { decision: statusFilter, diff --git a/backend/types.ts b/backend/types.ts index 4402932e..a55abe5c 100644 --- a/backend/types.ts +++ b/backend/types.ts @@ -1567,6 +1567,7 @@ export enum Decision { YES = "YES", MAYBE = "MAYBE", NO = "NO", + NONE = "NONE", } export enum AccountStatus { From ba42bff8e6914df9454f727e224def903add994e Mon Sep 17 00:00:00 2001 From: Huan Date: Sun, 22 May 2022 11:26:13 +0200 Subject: [PATCH 07/29] feat: added project tests --- frontend/__tests__/createProject.test.tsx | 53 ++++++ frontend/__tests__/project.test.tsx | 164 ++++++++++++++++++ frontend/components/Filters/ProjectFilter.tsx | 15 +- .../components/ProjectCard/ProjectCard.tsx | 1 + frontend/package-lock.json | 52 +++++- frontend/package.json | 2 + frontend/pages/projects/create.tsx | 94 +++++----- frontend/types.ts | 4 +- 8 files changed, 330 insertions(+), 55 deletions(-) create mode 100644 frontend/__tests__/createProject.test.tsx create mode 100644 frontend/__tests__/project.test.tsx diff --git a/frontend/__tests__/createProject.test.tsx b/frontend/__tests__/createProject.test.tsx new file mode 100644 index 00000000..3c1c2e86 --- /dev/null +++ b/frontend/__tests__/createProject.test.tsx @@ -0,0 +1,53 @@ +import fetchMock from "jest-fetch-mock"; +import { act, render, screen } from "@testing-library/react"; +import Create from "../pages/projects/create"; +import fireEvent from "@testing-library/user-event"; + +jest.mock("next/router", () => require("next-router-mock")); + +fetchMock.enableMocks(); +jest.mock("next/router"); + +const response = JSON.stringify({ + success: true, + data: [], + pagination: { count: 0 }, +}); + +describe("project create test", () => { + beforeEach(async () => { + fetchMock.resetMocks(); + fetchMock.mockOnce(response); + fetchMock.mockOnce(response); + fetchMock.mockOnce(response); + await act(() => { + render(); + }); + }); + + const testInput = async (inputName: string, inputVal: string) => { + await act(async () => { + await fireEvent.type(screen.getByTestId(inputName), inputVal); + }); + }; + test("test inputs", async () => { + const input = "aaa"; + const date1 = "2022-05-21"; + const date2 = "2022-05-22"; + await testInput("nameInput", input); + await testInput("partnerInput", input); + await testInput("descriptionInput", input); + await testInput("startDateInput", date1); + await testInput("endDateInput", date2); + await act(async () => { + fetchMock.mockOnce(response); + await screen.getByTestId("confirmButton").click(); + }); + const lastLength = fetchMock.mock.calls.length - 1; + expect(fetchMock.mock.calls[lastLength][0]).toBe(`undefined/project`); + console.log(fetchMock.mock.calls[lastLength][1]?.body); + expect(fetchMock.mock.calls[lastLength][1]?.body).toBe( + `{"name":"${input}","partner":"${input}","start":"${date1}","end":"${date2}","osocId":0,"positions":0,"roles":{"roles":[]},"description":"${input}","coaches":{"coaches":[]}}` + ); + }); +}); diff --git a/frontend/__tests__/project.test.tsx b/frontend/__tests__/project.test.tsx new file mode 100644 index 00000000..980bcb3e --- /dev/null +++ b/frontend/__tests__/project.test.tsx @@ -0,0 +1,164 @@ +import "@testing-library/jest-dom"; +import fetchMock from "jest-fetch-mock"; +import { act, render, screen } from "@testing-library/react"; +import fireEvent from "@testing-library/user-event"; +import Projects from "../pages/projects"; +import { defaultUser } from "../defaultUser"; +import { Project } from "../types"; + +jest.mock("next/router", () => require("next-router-mock")); + +fetchMock.enableMocks(); +jest.mock("next/router"); +const student = defaultUser; +const defaultProject: Project = { + coaches: [], + end_date: "", + id: -1, + name: "", + osoc_id: -1, + partner: "", + positions: -1, + start_date: "", + description: "", + contracts: [], + roles: [ + { + name: "DEV", + positions: 1, + }, + ], +}; + +const responseStudents = JSON.stringify({ + success: true, + data: [student], + pagination: { count: 0 }, +}); + +const responseProject = JSON.stringify({ + success: true, + data: [defaultProject], + pagination: { count: 0 }, +}); + +const response = JSON.stringify({ + success: true, + data: [], + pagination: { count: 0 }, +}); + +const pageSize = 5; +describe("project filter tests", () => { + beforeEach(async () => { + fetchMock.resetMocks(); + fetchMock.mockOnce(responseStudents); + fetchMock.mockOnce(response); + fetchMock.mockOnce(responseProject); + + await act(() => { + render(); + }); + }); + + test("test filter inputs presents", async () => { + expect(screen.getByTestId("nameSort")).toBeInTheDocument(); + expect(screen.getByTestId("nameInput")).toBeInTheDocument(); + expect(screen.getByTestId("clientSort")).toBeInTheDocument(); + expect(screen.getByTestId("clientInput")).toBeInTheDocument(); + expect(screen.getByTestId("osocInput")).toBeInTheDocument(); + expect(screen.getByTestId("assignedButton")).toBeInTheDocument(); + expect( + screen.getByTestId("searchButtonProjectFilter") + ).toBeInTheDocument(); + }); + + const testSortAndInput = async ( + sort: string, + input: string, + sortReqVal: string, + inputReqVal: string + ) => { + await act(async () => { + fetchMock.mockOnce(response); + screen.getByTestId(sort).click(); + }); + let lastLength = fetchMock.mock.calls.length - 1; + expect(fetchMock.mock.calls[lastLength][0]).toBe( + `undefined/project/filter?${sortReqVal}=asc¤tPage=0&pageSize=${pageSize}` + ); + await act(async () => { + fetchMock.mockOnce(response); + screen.getByTestId(sort).click(); + }); + lastLength = fetchMock.mock.calls.length - 1; + expect(fetchMock.mock.calls[lastLength][0]).toBe( + `undefined/project/filter?${sortReqVal}=desc¤tPage=0&pageSize=${pageSize}` + ); + await act(async () => { + fetchMock.mockOnce(response); + screen.getByTestId(sort).click(); + }); + lastLength = fetchMock.mock.calls.length - 1; + expect(fetchMock.mock.calls[lastLength][0]).toBe( + `undefined/project/filter?currentPage=0&pageSize=${pageSize}` + ); + await testInput(input, inputReqVal); + }; + const testInput = async (input: string, inputReqVal: string) => { + let lastLength: number; + + const test_val = "testvalue"; + fetchMock.resetMocks(); + await act(async () => { + await fireEvent.type(screen.getByTestId(input), test_val); + fetchMock.mockOnce(response); + screen.getByTestId("searchButtonProjectFilter").click(); + }); + lastLength = fetchMock.mock.calls.length - 1; + expect(fetchMock.mock.calls[lastLength][0]).toBe( + `undefined/project/filter?${inputReqVal}=${test_val}¤tPage=0&pageSize=${pageSize}` + ); + await act(async () => { + await fireEvent.clear(screen.getByTestId(input)); + fetchMock.mockOnce(response); + screen.getByTestId("searchButtonProjectFilter").click(); + }); + lastLength = fetchMock.mock.calls.length - 1; + console.log(fetchMock.mock.calls); + expect(fetchMock.mock.calls[lastLength][0]).toBe( + `undefined/project/filter?currentPage=0&pageSize=${pageSize}` + ); + }; + test("test filters functionality", async () => { + await testSortAndInput( + "nameSort", + "nameInput", + "projectNameSort", + "projectNameFilter" + ); + await testSortAndInput( + "clientSort", + "clientInput", + "clientNameSort", + "clientNameFilter" + ); + await testInput("osocInput", "osocYear"); + await act(async () => { + await fireEvent.click(screen.getByTestId("assignedButton")); + fetchMock.mockOnce(response); + }); + let lastLength = fetchMock.mock.calls.length - 1; + expect(fetchMock.mock.calls[lastLength][0]).toBe( + `undefined/project/filter?fullyAssignedFilter=true¤tPage=0&pageSize=${pageSize}` + ); + await act(async () => { + await fireEvent.click(screen.getByTestId("assignedButton")); + fetchMock.mockOnce(response); + }); + lastLength = fetchMock.mock.calls.length - 1; + expect(fetchMock.mock.calls[lastLength][0]).toBe( + `undefined/project/filter?currentPage=0&pageSize=${pageSize}` + ); + }); +}); diff --git a/frontend/components/Filters/ProjectFilter.tsx b/frontend/components/Filters/ProjectFilter.tsx index eceeeb68..4c739b03 100644 --- a/frontend/components/Filters/ProjectFilter.tsx +++ b/frontend/components/Filters/ProjectFilter.tsx @@ -133,7 +133,7 @@ export const ProjectFilter: React.FC<{ return (
-
+
Project Name
-
+
Client
- +
); }; diff --git a/frontend/components/ProjectCard/ProjectCard.tsx b/frontend/components/ProjectCard/ProjectCard.tsx index f1ab66cb..146a2c5e 100644 --- a/frontend/components/ProjectCard/ProjectCard.tsx +++ b/frontend/components/ProjectCard/ProjectCard.tsx @@ -30,6 +30,7 @@ export const ProjectCard: React.FC<{ const { sessionKey } = getSession ? await getSession() : { sessionKey: "" }; + console.log("test"); const response = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/project/${project.id}/assignee`, { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 14628a16..11c82c35 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "react": "18.1.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", + "react-dnd-test-utils": "^16.0.1", "react-dom": "18.1.0", "socket.io-client": "^4.5.1", "validator": "^13.7.0" @@ -1522,7 +1523,7 @@ "version": "13.2.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.2.0.tgz", "integrity": "sha512-Bprbz/SZVONCJy5f7hcihNCv313IJXdYiv0nSJklIs1SQCIHHNlnGNkosSXnGZTmesyGIcBGNppYhXcc11pb7g==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.5.0", @@ -1695,7 +1696,7 @@ "version": "18.0.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.4.tgz", "integrity": "sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -6434,11 +6435,44 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd-test-backend/-/react-dnd-test-backend-16.0.1.tgz", "integrity": "sha512-sUsQKMmoachP3AGaHLZD4JZWq2dHPoiLRG3YVuC0gXJtGENk3PVR+uRIrml7BEnnTyv+RVeoNK2ziL38Ggh8cw==", - "dev": true, + "devOptional": true, "dependencies": { "dnd-core": "^16.0.1" } }, + "node_modules/react-dnd-test-utils": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-test-utils/-/react-dnd-test-utils-16.0.1.tgz", + "integrity": "sha512-hKt6HYUAuN1oVtpVIpHwxeZLqtjwfzK2wFyfQUs7Pbi6muxXK+ZDOHEUmaI3WcoWb0nhZOyX3+FwmBsrA288Eg==", + "peerDependencies": { + "@testing-library/react": ">= 11", + "@types/node": "*", + "@types/react": ">= 16", + "@types/react-dom": ">= 16", + "react": ">= 16.14", + "react-dnd": ">= 11.1.3", + "react-dnd-html5-backend": ">= 11.1.3", + "react-dnd-test-backend": ">= 10.0.0", + "react-dom": ">= 16.14" + }, + "peerDependenciesMeta": { + "@testing-library/react": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dnd-html5-backend": { + "optional": true + }, + "react-dnd-test-backend": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", @@ -8658,7 +8692,7 @@ "version": "13.2.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.2.0.tgz", "integrity": "sha512-Bprbz/SZVONCJy5f7hcihNCv313IJXdYiv0nSJklIs1SQCIHHNlnGNkosSXnGZTmesyGIcBGNppYhXcc11pb7g==", - "dev": true, + "devOptional": true, "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.5.0", @@ -8818,7 +8852,7 @@ "version": "18.0.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.4.tgz", "integrity": "sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==", - "dev": true, + "devOptional": true, "requires": { "@types/react": "*" } @@ -12341,11 +12375,17 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd-test-backend/-/react-dnd-test-backend-16.0.1.tgz", "integrity": "sha512-sUsQKMmoachP3AGaHLZD4JZWq2dHPoiLRG3YVuC0gXJtGENk3PVR+uRIrml7BEnnTyv+RVeoNK2ziL38Ggh8cw==", - "dev": true, + "devOptional": true, "requires": { "dnd-core": "^16.0.1" } }, + "react-dnd-test-utils": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-test-utils/-/react-dnd-test-utils-16.0.1.tgz", + "integrity": "sha512-hKt6HYUAuN1oVtpVIpHwxeZLqtjwfzK2wFyfQUs7Pbi6muxXK+ZDOHEUmaI3WcoWb0nhZOyX3+FwmBsrA288Eg==", + "requires": {} + }, "react-dom": { "version": "18.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index b817ebcf..15b22236 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "react": "18.1.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", + "react-dnd-test-utils": "^16.0.1", "react-dom": "18.1.0", "socket.io-client": "^4.5.1", "validator": "^13.7.0" @@ -28,6 +29,7 @@ "devDependencies": { "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", + "react-dnd-test-utils": "^16.0.1", "@types/node": "17.0.35", "@types/react": "18.0.9", "@types/react-beautiful-dnd": "^13.1.2", diff --git a/frontend/pages/projects/create.tsx b/frontend/pages/projects/create.tsx index 80c088dd..bd91694c 100644 --- a/frontend/pages/projects/create.tsx +++ b/frontend/pages/projects/create.tsx @@ -137,50 +137,49 @@ const Create: NextPage = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const handleConfirm = () => { - if (getSession !== undefined) { - getSession().then(async ({ sessionKey }) => { - if (sessionKey != "") { - const response = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/project`, - { - method: "POST", - headers: { - Authorization: `auth/osoc2 ${sessionKey}`, - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify({ - name: projectName, - partner: partner, - start: startDate, - end: endDate, - osocId: osocId, - positions: getTotalPositions(), - roles: { roles: getRoleList() }, - description: description, - coaches: { coaches: selectedCoaches }, - }), - } - ) - .then((response) => response.json()) - .catch((error) => console.log(error)); - if (response !== undefined && response.success) { - if (notify) { - notify( - "Project succesfully created!", - NotificationType.SUCCESS, - 2000 - ); - } - socket.emit("projectCreated"); - } else if (notify && response !== null) { - notify(response.reason, NotificationType.ERROR, 5000); - } - router.push("/projects").then(); - } - }); + const handleConfirm = async () => { + const { sessionKey } = getSession + ? await getSession() + : { sessionKey: "" }; + + console.log(startDate); + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/project`, + { + method: "POST", + headers: { + Authorization: `auth/osoc2 ${sessionKey}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + name: projectName, + partner: partner, + start: startDate, + end: endDate, + osocId: osocId, + positions: getTotalPositions(), + roles: { roles: getRoleList() }, + description: description, + coaches: { coaches: selectedCoaches }, + }), + } + ) + .then((response) => response.json()) + .catch((error) => console.log(error)); + if (response !== undefined && response.success) { + if (notify) { + notify( + "Project succesfully created!", + NotificationType.SUCCESS, + 2000 + ); + } + socket.emit("projectCreated"); + } else if (notify && response !== null) { + notify(response.reason, NotificationType.ERROR, 5000); } + router.push("/projects").then(); }; const getTotalPositions = () => { @@ -315,6 +314,7 @@ const Create: NextPage = () => {

- +
); }; diff --git a/frontend/types.ts b/frontend/types.ts index 5a80b950..3e406c54 100644 --- a/frontend/types.ts +++ b/frontend/types.ts @@ -294,7 +294,7 @@ export interface Contract { } export interface Project { - coaches: [ProjectLoginUser]; + coaches: [ProjectLoginUser] | []; end_date: string; id: number; name: string; @@ -303,7 +303,7 @@ export interface Project { positions: number; start_date: string; description: string | null; - contracts: [Contract]; + contracts: [Contract] | []; roles: [ { name: string; From aefad988968966ed385792f67c880f1e4868f3c1 Mon Sep 17 00:00:00 2001 From: Bram Devlaminck Date: Sun, 22 May 2022 11:29:28 +0200 Subject: [PATCH 08/29] feat: websockets for users is now complete --- backend/types.ts | 2 ++ backend/websocket_events/osoc.ts | 4 +++ frontend/components/User/User.tsx | 57 ++++++++++++++++++++++++------- frontend/pages/users.tsx | 16 +++++++-- frontend/types.ts | 2 ++ 5 files changed, 67 insertions(+), 14 deletions(-) diff --git a/backend/types.ts b/backend/types.ts index 4402932e..24063ead 100644 --- a/backend/types.ts +++ b/backend/types.ts @@ -1527,6 +1527,7 @@ export interface ServerToClientEvents { projectWasCreatedOrDeleted: () => void; projectWasModified: (projectId: number) => void; osocWasCreatedOrDeleted: () => void; + yearPermissionUpdated: (loginUserId: number) => void; } /** @@ -1547,6 +1548,7 @@ export interface ClientToServerEvents { projectDeleted: () => void; osocDeleted: () => void; osocCreated: () => void; + yearPermissionUpdate: (loginUserId: number) => void; } /** diff --git a/backend/websocket_events/osoc.ts b/backend/websocket_events/osoc.ts index 88d5223c..8e695645 100644 --- a/backend/websocket_events/osoc.ts +++ b/backend/websocket_events/osoc.ts @@ -29,7 +29,11 @@ export function registerOsocHandlers( const OsocCreatedOrDeleted = () => { socket.broadcast.emit("osocWasCreatedOrDeleted"); }; + const yearPermissionUpdated = (loginUserId: number) => { + socket.broadcast.emit("yearPermissionUpdated", loginUserId); + }; socket.on("osocDeleted", OsocCreatedOrDeleted); socket.on("osocCreated", OsocCreatedOrDeleted); + socket.on("yearPermissionUpdate", yearPermissionUpdated); } diff --git a/frontend/components/User/User.tsx b/frontend/components/User/User.tsx index 71ec5737..84ddbd8c 100644 --- a/frontend/components/User/User.tsx +++ b/frontend/components/User/User.tsx @@ -5,7 +5,13 @@ import CoachIconColor from "../../public/images/coach_icon_color.png"; import CoachIcon from "../../public/images/coach_icon.png"; import ForbiddenIconColor from "../../public/images/forbidden_icon_color.png"; import ForbiddenIcon from "../../public/images/forbidden_icon.png"; -import React, { SyntheticEvent, useContext, useEffect, useState } from "react"; +import React, { + SyntheticEvent, + useCallback, + useContext, + useEffect, + useState, +} from "react"; import Image from "next/image"; import SessionContext from "../../contexts/sessionProvider"; import { @@ -51,6 +57,16 @@ export const User: React.FC<{ setStatus(user.account_status); }, [user]); + /** + * remove the listeners when dismounting the component + */ + useEffect(() => { + return () => { + socket.off("yearPermissionUpdated"); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { if (!isAdmin && !isCoach) { setStatus(() => AccountStatus.DISABLED); @@ -63,7 +79,7 @@ export const User: React.FC<{ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const fetchUserEditions = async () => { + const fetchUserEditions = useCallback(async () => { const { sessionKey } = getSession ? await getSession() : { sessionKey: "" }; @@ -85,7 +101,20 @@ export const User: React.FC<{ } setUserEditions(new Set(ids)); } - }; + }, [getSession, userId]); + + /** + * websocket listeners that update the visible years for a loginUser + */ + useEffect(() => { + socket.off("yearPermissionUpdated"); + socket.on("yearPermissionUpdated", (loginUserId: number) => { + console.log("update"); + if (user.login_user_id === loginUserId) { + fetchUserEditions().then(); + } + }); + }, [user, socket, fetchUserEditions]); const setUserRole = async ( route: string, @@ -304,15 +333,19 @@ export const User: React.FC<{ Accept: "application/json", Authorization: `auth/osoc2 ${sessionKey}`, }, - }).catch((reason) => { - console.log(reason); - if (method === "POST") { - userEditions.delete(id); - } else { - userEditions.add(id); - } - setUserEditions(new Set(userEditions)); - }); + }) + .then(() => { + socket.emit("yearPermissionUpdate", user.login_user_id); + }) + .catch((reason) => { + console.log(reason); + if (method === "POST") { + userEditions.delete(id); + } else { + userEditions.add(id); + } + setUserEditions(new Set(userEditions)); + }); }; return ( diff --git a/frontend/pages/users.tsx b/frontend/pages/users.tsx index e33e4aae..e5944591 100644 --- a/frontend/pages/users.tsx +++ b/frontend/pages/users.tsx @@ -25,7 +25,7 @@ import { NotificationContext } from "../contexts/notificationProvider"; const Users: NextPage = () => { const [users, setUsers] = useState>(); const [loading, isLoading] = useState(false); // Check if we are executing a request - const { getSession } = useContext(SessionContext); + const { getSession, sessionKey } = useContext(SessionContext); const router = useRouter(); const [pagination, setPagination] = useState({ page: 0, @@ -48,11 +48,15 @@ const Users: NextPage = () => { const { socket } = useSockets(); const { notify } = useContext(NotificationContext); + /** + * remove the listeners when dismounting the component + */ useEffect(() => { return () => { socket.off("loginUserUpdated"); socket.off("registrationReceived"); - }; // disconnect from the socket on dismount + socket.off("osocWasCreatedOrDeleted"); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -77,6 +81,7 @@ const Users: NextPage = () => { useEffect(() => { socket.off("loginUserUpdated"); // remove the earlier added listeners socket.off("registrationReceived"); + socket.off("osocWasCreatedOrDeleted"); // add new listener socket.on("loginUserUpdated", () => { const scrollPosition = window.scrollY; @@ -109,6 +114,13 @@ const Users: NextPage = () => { pagination.page ).then(() => window.scrollTo(0, scrollPosition)); }); + // when an osoc edition is deleted or removed this should be updated in the dropdown + socket.on("osocWasCreatedOrDeleted", () => { + const scrollPosition = window.scrollY; + fetchAllOsocEditions(sessionKey).then(() => + window.scrollTo(0, scrollPosition) + ); + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [socket, searchParams]); diff --git a/frontend/types.ts b/frontend/types.ts index 5a80b950..355917a5 100644 --- a/frontend/types.ts +++ b/frontend/types.ts @@ -238,6 +238,7 @@ export interface ServerToClientEvents { projectWasCreatedOrDeleted: () => void; projectWasModified: (projectId: number) => void; osocWasCreatedOrDeleted: () => void; + yearPermissionUpdated: (loginUserId: number) => void; } /** @@ -256,6 +257,7 @@ export interface ClientToServerEvents { projectModified: (projectId: number) => void; osocDeleted: () => void; osocCreated: () => void; + yearPermissionUpdate: (loginUserId: number) => void; } export interface ProjectPerson { From 8afa6fa31f8bc41888f148556014cbce96e768a0 Mon Sep 17 00:00:00 2001 From: Mouwrice Date: Sun, 22 May 2022 11:44:30 +0200 Subject: [PATCH 09/29] fix: hide buttons for non admins --- .../components/ProjectCard/ProjectCard.tsx | 21 ++++++++++++------- frontend/components/Projects/Projects.tsx | 17 ++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/frontend/components/ProjectCard/ProjectCard.tsx b/frontend/components/ProjectCard/ProjectCard.tsx index f1ab66cb..587349a2 100644 --- a/frontend/components/ProjectCard/ProjectCard.tsx +++ b/frontend/components/ProjectCard/ProjectCard.tsx @@ -23,6 +23,7 @@ export const ProjectCard: React.FC<{ const [droppedStudent, setDroppedStudent] = useState(); const [showDeleteModal, setShowDeleteModal] = useState(false); const { socket } = useSockets(); + const { isAdmin } = useContext(SessionContext); const postAssign = async (student: Student, role: string) => { if (student === null || student.student === null) return; @@ -325,14 +326,18 @@ export const ProjectCard: React.FC<{

{project.description}

-
- - -
+ {isAdmin ? ( +
+ + +
+ ) : null}
); }; diff --git a/frontend/components/Projects/Projects.tsx b/frontend/components/Projects/Projects.tsx index 97851f3d..03caf249 100644 --- a/frontend/components/Projects/Projects.tsx +++ b/frontend/components/Projects/Projects.tsx @@ -24,6 +24,7 @@ export const Projects: React.FC = () => { page: 0, count: 0, }); + const { isAdmin } = useContext(SessionContext); // 5 projects per page const pageSize = 5; @@ -226,13 +227,15 @@ export const Projects: React.FC = () => { return (
- + {isAdmin ? ( + + ) : null} Date: Sun, 22 May 2022 11:45:56 +0200 Subject: [PATCH 10/29] fix: permission for coaches for project interaction --- backend/routes/project.ts | 8 ++++---- backend/tests/routes_unit/project.test.ts | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/routes/project.ts b/backend/routes/project.ts index 06f17961..0667e6e0 100644 --- a/backend/routes/project.ts +++ b/backend/routes/project.ts @@ -203,7 +203,7 @@ export async function getProject( ): Promise { const parsedRequest = await rq.parseSingleProjectRequest(req); const checkedId = await util - .isAdmin(parsedRequest) + .checkSessionKey(parsedRequest) .then(checkYearPermissionProject) .then((v) => util.isValidID(v.data, "project")); @@ -506,7 +506,7 @@ export async function unAssignCoach( ): Promise { return rq .parseRemoveCoachRequest(req) - .then((parsed) => util.checkSessionKey(parsed)) + .then((parsed) => util.isAdmin(parsed)) .then(async (checked) => { const project = await ormPr.getProjectById(checked.data.id); @@ -555,7 +555,7 @@ export async function assignCoach( ): Promise { return rq .parseAssignCoachRequest(req) - .then((parsed) => util.checkSessionKey(parsed)) + .then((parsed) => util.isAdmin(parsed)) .then(async (checked) => { const project = await ormPr.getProjectById(checked.data.id); @@ -701,7 +701,7 @@ export async function assignStudent( // authenticate, parse, ... const checked = await rq .parseDraftStudentRequest(req) - .then((parsed) => util.isAdmin(parsed)); + .then((parsed) => util.checkSessionKey(parsed)); // check if edition is ready const latestOsoc = await ormOsoc diff --git a/backend/tests/routes_unit/project.test.ts b/backend/tests/routes_unit/project.test.ts index 7317500e..531ae9f9 100644 --- a/backend/tests/routes_unit/project.test.ts +++ b/backend/tests/routes_unit/project.test.ts @@ -933,7 +933,7 @@ test("Can get single project", async () => { await expect(project.getProject(req)).resolves.toStrictEqual(res); expectCall(reqMock.parseSingleProjectRequest, req); - expectCall(utilMock.isAdmin, req.body); + expectCall(utilMock.checkSessionKey, req.body); expect(utilMock.isValidID).toHaveBeenCalledTimes(1); expectCall(ormPrMock.getProjectById, 0); expectCall(ormCMock.contractsByProject, 0); @@ -956,7 +956,7 @@ test("Can't get single project (role error)", async () => { await expect(project.getProject(req)).rejects.toStrictEqual(undefined); expectCall(reqMock.parseSingleProjectRequest, req); - expectCall(utilMock.isAdmin, req.body); + expectCall(utilMock.checkSessionKey, req.body); expect(utilMock.isValidID).toHaveBeenCalledTimes(1); expectCall(ormPrMock.getProjectById, 0); expectCall(ormCMock.contractsByProject, 0); @@ -978,7 +978,7 @@ test("Can't get single project (ID error)", async () => { await expect(project.getProject(req)).rejects.toStrictEqual(undefined); expectCall(reqMock.parseSingleProjectRequest, req); - expectCall(utilMock.isAdmin, req.body); + expectCall(utilMock.checkSessionKey, req.body); expect(utilMock.isValidID).toHaveBeenCalledTimes(1); expectCall(ormPrMock.getProjectById, 0); expect(ormCMock.contractsByProject).not.toHaveBeenCalled(); @@ -1242,7 +1242,7 @@ test("Can un-assign coaches", async () => { // await project.unAssignCoach(req); await expect(project.unAssignCoach(req)).resolves.toStrictEqual({}); expectCall(reqMock.parseRemoveCoachRequest, req); - expectCall(utilMock.checkSessionKey, req.body); + expectCall(utilMock.isAdmin, req.body); expectCall(ormPUMock.getUsersFor, 0); expectCall(ormPUMock.deleteProjectUser, { loginUserId: req.body.loginUserId, @@ -1263,7 +1263,7 @@ test("Can't un-assign coaches (invalid id)", async () => { reason: "The coach with ID 7 is not assigned to project 0", }); expectCall(reqMock.parseRemoveCoachRequest, req); - expectCall(utilMock.checkSessionKey, req.body); + expectCall(utilMock.isAdmin, req.body); expectCall(ormPUMock.getUsersFor, 0); expect(ormPUMock.deleteProjectUser).not.toHaveBeenCalled(); }); @@ -1282,7 +1282,7 @@ test("Can assign coaches", async () => { project_id: 0, }); expectCall(reqMock.parseAssignCoachRequest, req); - expectCall(utilMock.checkSessionKey, req.body); + expectCall(utilMock.isAdmin, req.body); expectCall(ormPUMock.getUsersFor, 0); expectCall(ormPUMock.createProjectUser, { projectId: 0, loginUserId: 7 }); }); @@ -1300,7 +1300,7 @@ test("Can't assign coaches (already assigned)", async () => { reason: "The coach with ID 0 is already assigned to project 0", }); expectCall(reqMock.parseAssignCoachRequest, req); - expectCall(utilMock.checkSessionKey, req.body); + expectCall(utilMock.isAdmin, req.body); expectCall(ormPUMock.getUsersFor, 0); expect(ormPUMock.createProjectUser).not.toHaveBeenCalled(); }); @@ -1401,7 +1401,7 @@ test("Can assign students", async () => { role: "dev", }); expectCall(reqMock.parseDraftStudentRequest, req); - expectCall(utilMock.isAdmin, req.body); + expectCall(utilMock.checkSessionKey, req.body); expect(ormOMock.getLatestOsoc).toHaveBeenCalledTimes(1); expectCall(ormCMock.contractsForStudent, req.body.studentId); expectCall(ormCMock.createContract, { @@ -1460,7 +1460,7 @@ test("Can't assign students (no places)", async () => { reason: "There are no more free spaces for that role", }); expectCall(reqMock.parseDraftStudentRequest, req); - expectCall(utilMock.isAdmin, req.body); + expectCall(utilMock.checkSessionKey, req.body); expect(ormOMock.getLatestOsoc).toHaveBeenCalledTimes(1); expectCall(ormCMock.contractsForStudent, req.body.studentId); expect(ormCMock.createContract).not.toHaveBeenCalled(); @@ -1514,7 +1514,7 @@ test("Can't assign students (no such role)", async () => { reason: "That role doesn't exist", }); expectCall(reqMock.parseDraftStudentRequest, req); - expectCall(utilMock.isAdmin, req.body); + expectCall(utilMock.checkSessionKey, req.body); expect(ormOMock.getLatestOsoc).toHaveBeenCalledTimes(1); expectCall(ormCMock.contractsForStudent, req.body.studentId); expect(ormCMock.createContract).not.toHaveBeenCalled(); @@ -1571,7 +1571,7 @@ test("Can't assign students (already used)", async () => { reason: "This student does already have a contract", }); expectCall(reqMock.parseDraftStudentRequest, req); - expectCall(utilMock.isAdmin, req.body); + expectCall(utilMock.checkSessionKey, req.body); expect(ormOMock.getLatestOsoc).toHaveBeenCalledTimes(1); expectCall(ormCMock.contractsForStudent, req.body.studentId); expect(ormCMock.createContract).not.toHaveBeenCalled(); From aa021310bbf43ecd3f096c7aac6b57264dc37bcb Mon Sep 17 00:00:00 2001 From: Jonathan Vanbrabant Date: Sun, 22 May 2022 12:00:03 +0200 Subject: [PATCH 11/29] fix: date error in project create test --- frontend/__tests__/createProject.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/__tests__/createProject.test.tsx b/frontend/__tests__/createProject.test.tsx index 3c1c2e86..cd04f030 100644 --- a/frontend/__tests__/createProject.test.tsx +++ b/frontend/__tests__/createProject.test.tsx @@ -32,8 +32,8 @@ describe("project create test", () => { }; test("test inputs", async () => { const input = "aaa"; - const date1 = "2022-05-21"; - const date2 = "2022-05-22"; + const date1 = new Date().toString(); + const date2 = new Date().toString(); await testInput("nameInput", input); await testInput("partnerInput", input); await testInput("descriptionInput", input); From 220834d7a39592e1a6ce0aae5694395d4e3f77ce Mon Sep 17 00:00:00 2001 From: Mouwrice Date: Sun, 22 May 2022 12:01:27 +0200 Subject: [PATCH 12/29] feat: student filter none final decision --- frontend/components/Filters/StudentFilter.tsx | 47 ++++++++++++++++-- frontend/public/images/close_icon.png | Bin 0 -> 7516 bytes frontend/public/images/close_icon_red.png | Bin 0 -> 17069 bytes frontend/types.ts | 1 + 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 frontend/public/images/close_icon.png create mode 100644 frontend/public/images/close_icon_red.png diff --git a/frontend/components/Filters/StudentFilter.tsx b/frontend/components/Filters/StudentFilter.tsx index 034bfe80..6582e602 100644 --- a/frontend/components/Filters/StudentFilter.tsx +++ b/frontend/components/Filters/StudentFilter.tsx @@ -18,6 +18,8 @@ import ExclamationIconColor from "../../public/images/exclamation_mark_color.png import ExclamationIcon from "../../public/images/exclamation_mark.png"; import ForbiddenIconColor from "../../public/images/forbidden_icon_color.png"; import ForbiddenIcon from "../../public/images/forbidden_icon.png"; +import CrossIconColor from "../../public/images/close_icon_red.png"; +import CrossIcon from "../../public/images/close_icon.png"; import { NotificationContext } from "../../contexts/notificationProvider"; export const StudentFilter: React.FC<{ @@ -287,6 +289,33 @@ export const StudentFilter: React.FC<{ searchManual(params); }; + const toggleFilterNone = async (e: SyntheticEvent) => { + e.preventDefault(); + let newVal; + if (statusFilter !== StudentStatus.NONE) { + newVal = StudentStatus.NONE; + } else { + newVal = StudentStatus.EMPTY; + } + setStatusFilter(newVal); + + setEmailStatusActive(false); + setRolesActive(false); + const params: StudentFilterParams = { + nameFilter: nameFilter, + emailFilter: emailFilter, + nameSort: nameSort, + emailSort: emailSort, + alumni: alumni, + studentCoach: studentCoach, + statusFilter: newVal, + osocYear: osocYear, + emailStatus: emailStatus, + selectedRoles: selectedRoles, + }; + searchManual(params); + }; + const toggleStatusApplied = async (e: SyntheticEvent) => { e.preventDefault(); @@ -808,7 +837,6 @@ export const StudentFilter: React.FC<{ } width={30} height={30} - alt={"Disabled"} onClick={toggleFilterYes} />

YES

@@ -825,7 +853,6 @@ export const StudentFilter: React.FC<{ } width={30} height={30} - alt={"Disabled"} onClick={toggleFilterMaybe} />

MAYBE

@@ -842,11 +869,25 @@ export const StudentFilter: React.FC<{ } width={30} height={30} - alt={"Disabled"} onClick={toggleFilterNo} />

NO

+ +
+ +

NONE

+