From fffa7e69463d7456ac1b0a15f611f2dfcece971e Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Mon, 14 Jul 2025 16:28:56 +0200 Subject: [PATCH 01/24] Remove gender, telephone, username, position, placeholder, birthdate and department columns from users table --- .../db_patches/0183_RemoveUserColulmns2.sql | 24 +++ apps/backend/src/auth/OAuthAuthorization.ts | 15 +- .../backend/src/datasources/UserDataSource.ts | 8 +- .../src/datasources/mockups/UserDataSource.ts | 40 +---- .../datasources/postgres/UserDataSource.ts | 38 +---- .../src/datasources/postgres/records.ts | 16 -- .../datasources/stfc/StfcUserDataSource.ts | 19 +-- apps/backend/src/models/User.ts | 11 -- .../src/mutations/RedeemCodesMutations.ts | 2 +- .../src/mutations/UserMutations.spec.ts | 4 - apps/backend/src/mutations/UserMutations.ts | 4 +- apps/backend/src/queries/UserQueries.ts | 4 - .../resolvers/mutations/UpdateUserMutation.ts | 21 --- .../src/resolvers/types/BasicUserDetails.ts | 6 - apps/backend/src/resolvers/types/User.ts | 21 --- apps/backend/src/resolvers/types/UserJWT.ts | 6 - .../statusActionEngine/statusActionUtils.ts | 2 - apps/e2e/cypress/e2e/eventLogs.cy.ts | 5 - apps/e2e/cypress/e2e/userAdministration.cy.ts | 10 -- .../components/user/UpdateUserInformation.tsx | 154 +----------------- .../src/context/UserContextProvider.tsx | 2 - .../fragment.basicUserInformation.graphql | 2 - .../frontend/src/graphql/user/getUser.graphql | 7 - .../src/graphql/user/getUserMe.graphql | 7 - .../src/graphql/user/updateUser.graphql | 10 -- .../hooks/visit/useBlankVisitRegistration.ts | 2 - 26 files changed, 41 insertions(+), 399 deletions(-) create mode 100644 apps/backend/db_patches/0183_RemoveUserColulmns2.sql diff --git a/apps/backend/db_patches/0183_RemoveUserColulmns2.sql b/apps/backend/db_patches/0183_RemoveUserColulmns2.sql new file mode 100644 index 0000000000..23be706aeb --- /dev/null +++ b/apps/backend/db_patches/0183_RemoveUserColulmns2.sql @@ -0,0 +1,24 @@ +-- 0181_RemoveUserColumns.sql +DO +$$ +BEGIN + IF register_patch( + '0182_RemoveUserColumns2.sql', + 'Fredrik Bolmsten', + 'Remove gender, telephone, username, position, placeholder, birthdate and department columns from users table', + '2025-05-12' + ) THEN + BEGIN + -- now drop the unwanted columns + ALTER TABLE public.users + DROP COLUMN username, + DROP COLUMN gender, + DROP COLUMN birthdate, + DROP COLUMN department, + DROP COLUMN position, + DROP COLUMN telephone; + END; + END IF; +END; +$$ +LANGUAGE plpgsql; diff --git a/apps/backend/src/auth/OAuthAuthorization.ts b/apps/backend/src/auth/OAuthAuthorization.ts index 888c7a9712..7e08f04853 100644 --- a/apps/backend/src/auth/OAuthAuthorization.ts +++ b/apps/backend/src/auth/OAuthAuthorization.ts @@ -141,21 +141,14 @@ export class OAuthAuthorization extends UserAuthorization { if (user) { const updatedUser = await this.userDataSource.update({ ...user, - birthdate: userInfo.birthdate - ? new Date(userInfo.birthdate) - : undefined, - department: userInfo.department as string, email: userInfo.email, firstname: userInfo.given_name, - gender: userInfo.gender, lastname: userInfo.family_name, oauthIssuer: client.issuer.metadata.issuer, oauthRefreshToken: tokenSet.refresh_token ?? '', oidcSub: userInfo.sub, institutionId: institutionId ?? user.institutionId, - position: userInfo.position as string, preferredname: userInfo.preferred_username, - telephone: userInfo.phone_number, user_title: userInfo.title as string, }); @@ -165,18 +158,12 @@ export class OAuthAuthorization extends UserAuthorization { (userInfo.title as string) ?? 'unspecified', userInfo.given_name, userInfo.family_name, - userInfo.email, userInfo.preferred_username ?? '', userInfo.sub, tokenSet.refresh_token ?? '', client.issuer.metadata.issuer, - userInfo.gender ?? 'unspecified', - new Date(), institutionId ?? 1, - '', - (userInfo.position as string) ?? '', - userInfo.email, - '' + userInfo.email ); const roleID = this.getUserRole(newUser); diff --git a/apps/backend/src/datasources/UserDataSource.ts b/apps/backend/src/datasources/UserDataSource.ts index 8bd99d44e3..562314bca2 100644 --- a/apps/backend/src/datasources/UserDataSource.ts +++ b/apps/backend/src/datasources/UserDataSource.ts @@ -60,18 +60,12 @@ export interface UserDataSource { user_title: string | undefined, firstname: string, lastname: string, - username: string, preferredname: string | undefined, oidc_sub: string, oauth_refreshtoken: string, oauth_issuer: string, - gender: string, - birthdate: Date, institution: number, - department: string, - position: string, - email: string, - telephone: string + email: string ): Promise; ensureDummyUserExists(userId: number): Promise; ensureDummyUsersExist(userIds: number[]): Promise; diff --git a/apps/backend/src/datasources/mockups/UserDataSource.ts b/apps/backend/src/datasources/mockups/UserDataSource.ts index d2a1ca07e5..d2be980867 100644 --- a/apps/backend/src/datasources/mockups/UserDataSource.ts +++ b/apps/backend/src/datasources/mockups/UserDataSource.ts @@ -23,9 +23,7 @@ export const basicDummyUser = new BasicUserDetails( 'john', 'org', 1, - 'boss', new Date('2019-07-17 08:25:12.23043+00'), - false, 'test@email.com', '' ); @@ -37,9 +35,7 @@ export const basicDummyUserNotOnProposal = new BasicUserDetails( 'john', 'org', 1, - 'boss', new Date('2019-07-17 08:25:12.23043+00'), - false, 'test@email.com', '' ); @@ -53,16 +49,9 @@ export const dummyUserOfficer = new User( 'Hailey', '324235', '683142616', - 'issuer', - 'male', - new Date('1990-01-25'), 3, - 'Other', - 'IT department', - 'Producer', + 'issuer', 'Dorris83@gmail.com', - '+46700568256', - false, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -87,16 +76,9 @@ export const dummyUser = new User( 'Meta', '12312414', '568567353', - 'issuer', - 'male', - new Date('1981-05-04'), 3, - 'Other', - 'IT department', - 'Architect', + 'issuer', 'Cleve30@yahoo.com', - '+38978414058', - false, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -206,16 +188,9 @@ export const dummyPlaceHolderUser = new User( 'Meta', '12312414', '568567353', - 'issuer', - 'male', - new Date('1981-05-04'), 3, - 'Other', - 'IT department', - 'Architect', + 'issuer', 'placeholder@ess.se', - '+46700568256', - true, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -229,16 +204,9 @@ export const dummyUserNotOnProposal = new User( 'Damion', '182082741', 'Apricot', - 'issuer', - 'female', - new Date('1991-11-08'), 5, - 'Other', - 'IT department', - 'Facilitator', 'Tyrique41@hotmail.com', '+46700568256', - false, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -293,9 +261,7 @@ export class UserDataSourceMock implements UserDataSource { 'John', 'ESS', 2, - 'Manager', new Date('2019-07-17 08:25:12.23043+00'), - false, 'test@email.com', '' ); diff --git a/apps/backend/src/datasources/postgres/UserDataSource.ts b/apps/backend/src/datasources/postgres/UserDataSource.ts index 738377f824..2b1ee0237e 100644 --- a/apps/backend/src/datasources/postgres/UserDataSource.ts +++ b/apps/backend/src/datasources/postgres/UserDataSource.ts @@ -79,14 +79,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institutionId, - department, - position, email, - telephone, - placeholder, oidcSub, oauthRefreshToken, oauthIssuer, @@ -98,14 +92,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institution_id: institutionId, - department, - position, email, - telephone, - placeholder, oidc_sub: oidcSub, oauth_refresh_token: oauthRefreshToken, oauth_issuer: oauthIssuer, @@ -124,14 +112,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institutionId, - department, - position, email, - telephone, - placeholder, oauthRefreshToken, oauthIssuer, } = args; @@ -142,14 +124,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institution_id: institutionId, - department, - position, email, - telephone, - placeholder, oauth_refresh_token: oauthRefreshToken, oauth_issuer: oauthIssuer, updated_at: new Date(), @@ -368,36 +344,24 @@ export default class PostgresUserDataSource implements UserDataSource { user_title: string | undefined, firstname: string, lastname: string, - username: string, preferredname: string | undefined, oidc_sub: string, oauth_refresh_token: string, oauth_issuer: string, - gender: string, - birthdate: Date, institution_id: number, - department: string, - position: string, - email: string, - telephone: string + email: string ): Promise { return database .insert({ user_title, firstname, lastname, - username, preferredname, oidc_sub, oauth_refresh_token, oauth_issuer, - gender, - birthdate, institution_id, - department, - position, email, - telephone, }) .returning(['*']) .into('users') diff --git a/apps/backend/src/datasources/postgres/records.ts b/apps/backend/src/datasources/postgres/records.ts index bf10b45f41..ee25e53f89 100644 --- a/apps/backend/src/datasources/postgres/records.ts +++ b/apps/backend/src/datasources/postgres/records.ts @@ -227,23 +227,16 @@ export interface UserRecord { readonly user_title: string; readonly firstname: string; readonly lastname: string; - readonly username: string; readonly preferredname: string; readonly oidc_sub: string | null; readonly oauth_refresh_token: string | null; readonly oauth_issuer: string | null; - readonly gender: string; - readonly birthdate: Date; - readonly department: string; - readonly position: string; readonly email: string; - readonly telephone: string; readonly created_at: Date; readonly updated_at: Date; readonly full_count: number; readonly institution_id: number; readonly institution: string; - readonly placeholder: boolean; } export interface VisitRegistrationRecord { @@ -934,20 +927,13 @@ export const createUserObject = (user: UserRecord) => { user.user_title, user.firstname, user.lastname, - user.username, user.preferredname, user.oidc_sub, user.oauth_refresh_token, user.oauth_issuer, - user.gender, - user.birthdate, user.institution_id, user.institution, - user.department, - user.position, user.email, - user.telephone, - user.placeholder, user.created_at.toISOString(), user.updated_at.toISOString() ); @@ -963,9 +949,7 @@ export const createBasicUserObject = ( user.preferredname, user.institution, user.institution_id, - user.position, user.created_at, - user.placeholder, user.email, user.country ); diff --git a/apps/backend/src/datasources/stfc/StfcUserDataSource.ts b/apps/backend/src/datasources/stfc/StfcUserDataSource.ts index 7d9b43ec5a..168300be43 100644 --- a/apps/backend/src/datasources/stfc/StfcUserDataSource.ts +++ b/apps/backend/src/datasources/stfc/StfcUserDataSource.ts @@ -62,7 +62,6 @@ export interface StfcBasicPersonDetails { orgId: number; title: string; userNumber: string; - workPhone: string; } export function toStfcBasicPersonDetails( @@ -87,7 +86,6 @@ export function toStfcBasicPersonDetails( orgId: 1, title: dto.title ?? '', userNumber: dto.userNumber ?? '', - workPhone: dto.workPhone ?? '', }; } @@ -101,9 +99,7 @@ export function toEssBasicUserDetails( stfcUser.firstNameKnownAs ?? stfcUser.givenName, stfcUser.orgName ?? '', stfcUser.orgId ?? 1, - '', new Date(), - false, stfcUser.email ?? '', stfcUser.country ?? '' ); @@ -115,20 +111,13 @@ function toEssUser(stfcUser: StfcBasicPersonDetails): User { stfcUser.title ?? '', stfcUser.givenName ?? '', stfcUser.familyName ?? '', - stfcUser.email ?? '', stfcUser.firstNameKnownAs ?? stfcUser.givenName, '', '', '', - '', - new Date('2000-01-01'), 1, - stfcUser.orgName, - stfcUser.deptName ?? '', '', stfcUser.email ?? '', - stfcUser.workPhone ?? '', - false, '2000-01-01 00:00:00.000000+00', '2000-01-01 00:00:00.000000+00' ); @@ -677,18 +666,12 @@ export class StfcUserDataSource implements UserDataSource { user_title: string | undefined, firstname: string, lastname: string, - username: string, preferredname: string | undefined, oidc_sub: string, oauth_refresh_token: string, oauth_issuer: string, - gender: string, - birthdate: Date, institution: number, - department: string, - position: string, - email: string, - telephone: string + email: string ): Promise { throw new Error('Method not implemented.'); } diff --git a/apps/backend/src/models/User.ts b/apps/backend/src/models/User.ts index d4e0d37b62..c3ef5e10e9 100644 --- a/apps/backend/src/models/User.ts +++ b/apps/backend/src/models/User.ts @@ -27,20 +27,13 @@ export class User { public user_title: string, public firstname: string, public lastname: string, - public username: string, public preferredname: string | undefined, public oidcSub: string | null, public oauthRefreshToken: string | null, public oauthIssuer: string | null, - public gender: string, - public birthdate: Date, public institutionId: number, public institution: string, - public department: string, - public position: string, public email: string, - public telephone: string, - public placeholder: boolean, public created: string, public updated: string ) {} @@ -86,9 +79,7 @@ export class BasicUserDetails { public preferredname: string, public institution: string, public institutionId: number, - public position: string, public created: Date, - public placeholder: boolean, public email: string, public country: string ) {} @@ -103,7 +94,5 @@ export type UserJWT = Pick< | 'lastname' | 'oidcSub' | 'institutionId' - | 'placeholder' - | 'position' | 'preferredname' >; diff --git a/apps/backend/src/mutations/RedeemCodesMutations.ts b/apps/backend/src/mutations/RedeemCodesMutations.ts index 7b7513c01c..4c5012d93c 100644 --- a/apps/backend/src/mutations/RedeemCodesMutations.ts +++ b/apps/backend/src/mutations/RedeemCodesMutations.ts @@ -44,7 +44,7 @@ export default class RedeemCodesMutations { const placeholderUserId = redeemCode.placeholderUserId; const placeholderUser = await this.dataSource.getUser(placeholderUserId); - if (!placeholderUser || !placeholderUser.placeholder) { + if (!placeholderUser) { return rejection('Could not find placeholder user', { code: ApolloServerErrorCodeExtended.NOT_FOUND, user: placeholderUser, diff --git a/apps/backend/src/mutations/UserMutations.spec.ts b/apps/backend/src/mutations/UserMutations.spec.ts index 24d0fd2452..b6ce1696ab 100644 --- a/apps/backend/src/mutations/UserMutations.spec.ts +++ b/apps/backend/src/mutations/UserMutations.spec.ts @@ -263,7 +263,6 @@ describe('updateUserByOidcSub', () => { { oidcSub: dummyUser.oidcSub as string, firstname: 'OfficerUpdatedJane', - department: 'Updated Department', id: dummyUser.id, } ); @@ -304,8 +303,6 @@ describe('updateUserByOidcSub', () => { test('A user can update partial profile data by OIDC sub', async () => { const result = await userMutations.updateUserByOidcSub(dummyUserWithRole, { oidcSub: dummyUser.oidcSub as string, - telephone: '+1-555-9999', - position: 'Senior Architect', id: dummyUser.id, }); @@ -363,6 +360,5 @@ describe('updateUserByOidcSub', () => { expect(isRejection(result)).toBe(false); expect((result as typeof dummyUser).lastname).toBe(dummyUser.lastname); expect((result as typeof dummyUser).email).toBe(dummyUser.email); - expect((result as typeof dummyUser).department).toBe(dummyUser.department); }); }); diff --git a/apps/backend/src/mutations/UserMutations.ts b/apps/backend/src/mutations/UserMutations.ts index d621a22c72..5c1757fe2c 100644 --- a/apps/backend/src/mutations/UserMutations.ts +++ b/apps/backend/src/mutations/UserMutations.ts @@ -99,7 +99,7 @@ export default class UserMutations { } // Check if email exist in database and if user has been invited before const user = await this.dataSource.getByEmail(args.email); - if (user && user.placeholder) { + if (user) { userId = user.id; return this.createEmailInviteResponse(userId, agent.id, role); @@ -412,8 +412,6 @@ export default class UserMutations { lastname: user.lastname, oidcSub: user.oidcSub, institutionId: user.institutionId, - placeholder: user.placeholder, - position: user.position, preferredname: user.preferredname, }, roles, diff --git a/apps/backend/src/queries/UserQueries.ts b/apps/backend/src/queries/UserQueries.ts index 6eca46fae7..fe82425820 100644 --- a/apps/backend/src/queries/UserQueries.ts +++ b/apps/backend/src/queries/UserQueries.ts @@ -68,9 +68,7 @@ export default class UserQueries { user.preferredname, user.institution, user.institutionId, - user.position, user.created, - user.placeholder, user.email, user.country ); @@ -97,9 +95,7 @@ export default class UserQueries { user.preferredname, user.institution, user.institutionId, - user.position, user.created, - user.placeholder, user.email, user.country ); diff --git a/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts b/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts index 3091fc96a5..5819adaa59 100644 --- a/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts +++ b/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts @@ -23,36 +23,15 @@ class UpdateUserArgs { @Field(() => String, { nullable: true }) public lastname?: string; - @Field(() => String, { nullable: true }) - public username?: string; - @Field(() => String, { nullable: true }) public preferredname?: string; - @Field(() => String, { nullable: true }) - public gender?: string; - - @Field({ nullable: true }) - public birthdate?: Date; - @Field(() => Int, { nullable: true }) public institutionId?: number; - @Field(() => String, { nullable: true }) - public department?: string; - - @Field(() => String, { nullable: true }) - public position?: string; - @Field(() => String, { nullable: true }) public email?: string; - @Field(() => String, { nullable: true }) - public telephone?: string; - - @Field(() => String, { nullable: true }) - public placeholder?: boolean; - @Field(() => [Int], { nullable: true }) public roles?: number[]; diff --git a/apps/backend/src/resolvers/types/BasicUserDetails.ts b/apps/backend/src/resolvers/types/BasicUserDetails.ts index b05aed8433..a064c44876 100644 --- a/apps/backend/src/resolvers/types/BasicUserDetails.ts +++ b/apps/backend/src/resolvers/types/BasicUserDetails.ts @@ -25,9 +25,6 @@ export class BasicUserDetails implements Partial { @Field(() => Int) public institutionId: number; - @Field() - public position: string; - @Authorized([ Roles.USER_OFFICER, Roles.INSTRUMENT_SCIENTIST, @@ -36,9 +33,6 @@ export class BasicUserDetails implements Partial { @Field({ nullable: true }) public email: string; - @Field(() => Boolean, { nullable: true }) - public placeholder?: boolean; - @Field(() => Date, { nullable: true }) public created?: Date; diff --git a/apps/backend/src/resolvers/types/User.ts b/apps/backend/src/resolvers/types/User.ts index 1b961e9f27..821aeac4d0 100644 --- a/apps/backend/src/resolvers/types/User.ts +++ b/apps/backend/src/resolvers/types/User.ts @@ -57,9 +57,6 @@ export class User implements Partial { @Field() public lastname: string; - @Field() - public username: string; - @Field(() => String, { nullable: true }) public preferredname: string | undefined; @@ -69,30 +66,12 @@ export class User implements Partial { @Field(() => String, { nullable: true }) public oauthRefreshToken: string | null; - @Field() - public gender: string; - - @Field() - public birthdate: Date; - @Field(() => Int) public institutionId: number; - @Field() - public department: string; - - @Field() - public position: string; - @Field() public email: string; - @Field() - public telephone: string; - - @Field() - public placeholder: boolean; - @Field() public created: string; diff --git a/apps/backend/src/resolvers/types/UserJWT.ts b/apps/backend/src/resolvers/types/UserJWT.ts index 2af650a870..ddf5cc136b 100644 --- a/apps/backend/src/resolvers/types/UserJWT.ts +++ b/apps/backend/src/resolvers/types/UserJWT.ts @@ -22,15 +22,9 @@ export class UserJWT implements Partial { @Field(() => String, { nullable: true }) public oidcSub: string | null; - @Field() - public placeholder: boolean; - @Field() public created: string; - @Field() - public position: string; - @Field() public institutionId: number; } diff --git a/apps/backend/src/statusActionEngine/statusActionUtils.ts b/apps/backend/src/statusActionEngine/statusActionUtils.ts index 4a2f3d5d66..d25e9756bf 100644 --- a/apps/backend/src/statusActionEngine/statusActionUtils.ts +++ b/apps/backend/src/statusActionEngine/statusActionUtils.ts @@ -458,9 +458,7 @@ export const getOtherAndFormatOutputForEmailSending = async ( '', '', 0, - '', new Date(), - true, otherEmail, '' ); diff --git a/apps/e2e/cypress/e2e/eventLogs.cy.ts b/apps/e2e/cypress/e2e/eventLogs.cy.ts index 2e60b56273..215115f894 100644 --- a/apps/e2e/cypress/e2e/eventLogs.cy.ts +++ b/apps/e2e/cypress/e2e/eventLogs.cy.ts @@ -68,13 +68,8 @@ context('Event log tests', () => { firstname: newFirstName, user_title: 'Dr.', lastname: 'Doe', - gender: 'male', - birthdate: new Date('2000/01/01'), institutionId: 1, - department: 'IT', - position: 'Dirrector', email: faker.internet.email(), - telephone: '555-123-4567', }); cy.login('officer'); diff --git a/apps/e2e/cypress/e2e/userAdministration.cy.ts b/apps/e2e/cypress/e2e/userAdministration.cy.ts index db3b91edf8..853cdeb169 100644 --- a/apps/e2e/cypress/e2e/userAdministration.cy.ts +++ b/apps/e2e/cypress/e2e/userAdministration.cy.ts @@ -168,12 +168,7 @@ context('User administration tests', () => { firstname: 'Benjamin', lastname: 'Beckley', preferredname: 'Ben', - gender: 'male', - birthdate: new Date('2000/04/02'), - department: 'IT deparment', - position: 'Management', email: 'ben@inbox.com', - telephone: '(288) 221-4533', }); } cy.login('user2', initialDBData.roles.user); @@ -217,12 +212,7 @@ context('User administration tests', () => { firstname: 'David', lastname: 'Dawson', preferredname: '', - gender: 'male', - birthdate: new Date('1995/04/01'), - department: 'Maxillofacial surgeon', - position: 'Management', email: 'david@teleworm.us', - telephone: '(288) 221-4533', }); } diff --git a/apps/frontend/src/components/user/UpdateUserInformation.tsx b/apps/frontend/src/components/user/UpdateUserInformation.tsx index e2c4cf1bfc..ece0d20439 100644 --- a/apps/frontend/src/components/user/UpdateUserInformation.tsx +++ b/apps/frontend/src/components/user/UpdateUserInformation.tsx @@ -1,31 +1,20 @@ -import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import SwitchAccountOutlinedIcon from '@mui/icons-material/SwitchAccountOutlined'; -import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import Chip from '@mui/material/Chip'; -import FormControl from '@mui/material/FormControl'; import Grid from '@mui/material/Grid'; -import InputLabel from '@mui/material/InputLabel'; -import Link from '@mui/material/Link'; import Stack from '@mui/material/Stack'; -import { useTheme } from '@mui/material/styles'; -import Typography from '@mui/material/Typography'; import { AdapterLuxon as DateAdapter } from '@mui/x-date-pickers/AdapterLuxon'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { updateUserValidationSchema } from '@user-office-software/duo-validation'; import { Field, Form, Formik } from 'formik'; -import { DateTime } from 'luxon'; import React, { useState, useContext } from 'react'; import FormikUIAutocomplete from 'components/common/FormikUIAutocomplete'; -import DatePicker from 'components/common/FormikUIDatePicker'; import Select from 'components/common/FormikUISelect'; import TextField from 'components/common/FormikUITextField'; import ImpersonateButton from 'components/common/ImpersonateButton'; import UOLoader from 'components/common/UOLoader'; import { UserContext } from 'context/UserContextProvider'; -import { SettingsId, UpdateUserMutationVariables } from 'generated/sdk'; -import { useFormattedDateTime } from 'hooks/admin/useFormattedDateTime'; +import { UpdateUserMutationVariables } from 'generated/sdk'; import { useInstitutionsData } from 'hooks/admin/useInstitutionData'; import { useCountries } from 'hooks/user/useCountries'; import { useUserData } from 'hooks/user/useUserData'; @@ -38,42 +27,26 @@ interface UpdateUserInformationProps { export default function UpdateUserInformation( props: UpdateUserInformationProps ) { - const theme = useTheme(); const { user } = useContext(UserContext); const { userData } = useUserData({ userId: props.id }); - const { format } = useFormattedDateTime({ - settingsFormatToUse: SettingsId.DATE_FORMAT, - }); + const { api } = useDataApiWithFeedback(); const countries = useCountries(); const { institutions, loadingInstitutions } = useInstitutionsData(); const [institutionsList, setInstitutionsList] = useState([]); const [countriesList, setCountriesList] = useState([]); - // NOTE: User should be older than 18 years. - const userMaxBirthDate = DateTime.now().minus({ years: 18 }); - if (!userData) { return ; } const initialValues = { - username: userData.username, firstname: userData.firstname, lastname: userData.lastname, preferredname: userData.preferredname || '', - gender: - userData.gender !== 'male' && userData.gender !== 'female' - ? 'other' - : userData.gender, - othergender: userData.gender, - birthdate: DateTime.fromJSDate(new Date(userData.birthdate)), institutionId: userData.institutionId, - department: userData.department, - position: userData.position, oldEmail: userData.email, email: userData.email, - telephone: userData.telephone, user_title: userData.user_title, oidcSub: userData.oidcSub, }; @@ -86,12 +59,6 @@ export default function UpdateUserInformation( { text: 'Rather not say', value: 'unspecified' }, ]; - const genderOptions: Option[] = [ - { text: 'Female', value: 'female' }, - { text: 'Male', value: 'male' }, - { text: 'Other', value: 'other' }, - ]; - if (!institutionsList.length && institutions.length) { setInstitutionsList( institutions.map((institution) => { @@ -123,8 +90,6 @@ export default function UpdateUserInformation( id: props.id, ...values, institutionId: values.institutionId ? +values.institutionId : null, - gender: - values.gender === 'other' ? values.othergender : values.gender, } as UpdateUserMutationVariables; await sendUserUpdate({ @@ -136,25 +101,6 @@ export default function UpdateUserInformation( > {({ isSubmitting, values }) => (
- - User Information - * + *': { - margin: theme.spacing(0.5), - }, - }} - > - {userData.placeholder && ( - } - size="small" - label="Placeholder user" - /> - )} - - @@ -191,75 +137,17 @@ export default function UpdateUserInformation( type="text" data-cy="preferredname" /> - - {values.gender === 'other' && ( - - )} - + {/* Remove gender and birthdate fields */} - - - ORCID iD{' '} - - - - https://orcid.org/{values.oidcSub} - - )} - - - + {/* Remove telephone field */} Date: Tue, 15 Jul 2025 10:34:46 +0200 Subject: [PATCH 02/24] fix for E2E tests --- .../db_seeds/0002_InstrumentScientiests.sql | 18 +++++++++--------- .../src/datasources/postgres/UserDataSource.ts | 13 ------------- apps/e2e/cypress/e2e/FAPs.cy.ts | 10 ---------- .../components/user/UpdateUserInformation.tsx | 1 - 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/apps/backend/db_patches/db_seeds/0002_InstrumentScientiests.sql b/apps/backend/db_patches/db_seeds/0002_InstrumentScientiests.sql index 8bcd4527c3..39c1e6fb7e 100644 --- a/apps/backend/db_patches/db_seeds/0002_InstrumentScientiests.sql +++ b/apps/backend/db_patches/db_seeds/0002_InstrumentScientiests.sql @@ -3,20 +3,20 @@ $DO$ BEGIN INSERT INTO users( - user_id, user_title, firstname, lastname, username, preferredname, oidc_sub, oauth_refresh_token, gender, birthdate, department, "position", email, - telephone, created_at, updated_at, institution_id, placeholder) - VALUES (100, '', 'Instrument', 'Scientist1', 'instr.sci1', '-', 'instr.sci1.oauthsub', 'dummy-refresh-token', 'e2e user', '2020-10-10', 'IT', '', 'instr.sci1@local.host', '', NOW(), NOW(), 1, false); + user_id, user_title, firstname, lastname, preferredname, oidc_sub, oauth_refresh_token, email, created_at, updated_at, institution_id + ) + VALUES (100, '', 'Instrument', 'Scientist1', '-', 'instr.sci1.oauthsub', 'dummy-refresh-token', 'instr.sci1@local.host', NOW(), NOW(), 1); INSERT INTO users( - user_id, user_title, firstname, lastname, username, preferredname, oidc_sub, oauth_refresh_token, gender, birthdate, department, "position", email, - telephone, created_at, updated_at, institution_id, placeholder) - VALUES (101, '', 'Instrument', 'Scientist2', 'instr.sci2', '-', 'instr.sci2.oauthsub', 'dummy-refresh-token', 'e2e user', '2020-10-10', 'IT', '', 'instr.sci2@local.host', '', NOW(), NOW(), 1, false); + user_id, user_title, firstname, lastname, preferredname, oidc_sub, oauth_refresh_token, email, created_at, updated_at, institution_id + ) + VALUES (101, '', 'Instrument', 'Scientist2', '-', 'instr.sci2.oauthsub', 'dummy-refresh-token', 'instr.sci2@local.host', NOW(), NOW(), 1); -- user account with every roles except instrument scientist and user officer INSERT INTO users( - user_id, user_title, firstname, lastname, username, preferredname, oidc_sub, oauth_refresh_token, gender, birthdate, department, "position", email, - telephone, created_at, updated_at, institution_id, placeholder) - VALUES (102, '', 'Not', 'Scientist', 'not.instr.sci', '-', 'not.instr.sci.oauthsub', 'dummy-refresh-token', 'e2e user', '2020-10-10', 'IT', '', 'not.instr.sci@local.host', '', NOW(), NOW(), 1, false); + user_id, user_title, firstname, lastname, preferredname, oidc_sub, oauth_refresh_token, email, created_at, updated_at, institution_id + ) + VALUES (102, '', 'Not', 'Scientist', '-', 'not.instr.sci.oauthsub', 'dummy-refresh-token', 'not.instr.sci@local.host', NOW(), NOW(), 1); INSERT INTO role_user( role_id, user_id) diff --git a/apps/backend/src/datasources/postgres/UserDataSource.ts b/apps/backend/src/datasources/postgres/UserDataSource.ts index 2b1ee0237e..dedd775b35 100644 --- a/apps/backend/src/datasources/postgres/UserDataSource.ts +++ b/apps/backend/src/datasources/postgres/UserDataSource.ts @@ -149,19 +149,12 @@ export default class PostgresUserDataSource implements UserDataSource { user_title: '', firstname, lastname, - username: email, preferredname: firstname, oidc_sub: '', oauth_refresh_token: '', oauth_issuer: '', - gender: '', - birthdate: '2000-01-01', institution_id: 1, - department: '', - position: '', email, - telephone: '', - placeholder: true, }) .returning(['*']) .into('users') @@ -422,17 +415,11 @@ export default class PostgresUserDataSource implements UserDataSource { user_title: '', firstname: '', lastname: '', - username: userId.toString(), preferredname: '', oidc_sub: '', oauth_refresh_token: '', - gender: '', - birthdate: '2000-01-01', institution_id: 1, - department: '', - position: '', email: userId.toString(), - telephone: '', }; } diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index 14523b7c66..887c2df8c2 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -1183,13 +1183,8 @@ context('Fap reviews tests', () => { cy.updateUserDetails({ ...loggedInUserParsed, institutionId: 1, - telephone: faker.phone.number('+4670#######'), user_title: 'Dr.', - gender: 'male', nationality: 1, - birthdate: new Date('2000/01/01'), - department: 'IT', - position: 'Dirrector', } as UpdateUserMutationVariables); } cy.visit(`/FapPage/${createdFapId}?tab=3`); @@ -1340,12 +1335,7 @@ context('Fap reviews tests', () => { cy.updateUserDetails({ ...loggedInUserParsed, institutionId: 1, - telephone: faker.phone.number('+4670#######'), user_title: 'Dr.', - gender: 'male', - birthdate: new Date('2000/01/01'), - department: 'IT', - position: 'Dirrector', } as UpdateUserMutationVariables); } diff --git a/apps/frontend/src/components/user/UpdateUserInformation.tsx b/apps/frontend/src/components/user/UpdateUserInformation.tsx index ece0d20439..1c24ea2061 100644 --- a/apps/frontend/src/components/user/UpdateUserInformation.tsx +++ b/apps/frontend/src/components/user/UpdateUserInformation.tsx @@ -137,7 +137,6 @@ export default function UpdateUserInformation( type="text" data-cy="preferredname" /> - {/* Remove gender and birthdate fields */} From 321f5ed468543c93f575a96228410d7c11340c4c Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Tue, 15 Jul 2025 11:18:55 +0200 Subject: [PATCH 03/24] clean up tests from legacy fields --- apps/backend/src/mutations/UserMutations.spec.ts | 3 --- apps/e2e/cypress/e2e/personalInformation.cy.ts | 15 --------------- apps/e2e/cypress/e2e/userAdministration.cy.ts | 15 --------------- 3 files changed, 33 deletions(-) diff --git a/apps/backend/src/mutations/UserMutations.spec.ts b/apps/backend/src/mutations/UserMutations.spec.ts index b6ce1696ab..5335394441 100644 --- a/apps/backend/src/mutations/UserMutations.spec.ts +++ b/apps/backend/src/mutations/UserMutations.spec.ts @@ -270,7 +270,6 @@ describe('updateUserByOidcSub', () => { expect(result).toEqual({ ...dummyUser, firstname: 'OfficerUpdatedJane', - department: 'Updated Department', }); }); test('A user cannot update another user by OIDC sub', async () => { @@ -308,8 +307,6 @@ describe('updateUserByOidcSub', () => { expect(result).toEqual({ ...dummyUser, - telephone: '+1-555-9999', - position: 'Senior Architect', }); }); diff --git a/apps/e2e/cypress/e2e/personalInformation.cy.ts b/apps/e2e/cypress/e2e/personalInformation.cy.ts index 113cfc0c30..c781f44fc0 100644 --- a/apps/e2e/cypress/e2e/personalInformation.cy.ts +++ b/apps/e2e/cypress/e2e/personalInformation.cy.ts @@ -12,12 +12,9 @@ context('Personal information tests', () => { const newFirstName = faker.name.firstName(); const newLastName = faker.name.lastName(); - const newDepartment = faker.commerce.department(); const otherOrg = faker.commerce.department(); const otherInstitution = 'Other'; const newPreferredName = faker.hacker.noun(); - const newPosition = faker.random.word().split(' ')[0]; - const newTelephone = faker.phone.number('0##########'); it('Should be able to see user officer role in use', () => { if (featureFlags.getEnabledFeatures().get(FeatureId.USER_MANAGEMENT)) { @@ -63,12 +60,6 @@ context('Personal information tests', () => { cy.get("[name='preferredname']").clear().type(newPreferredName); - cy.get("[name='position']").clear().type(newPosition); - - cy.get("[name='department']").clear().type(newDepartment); - - cy.get("[name='telephone']").clear().type(newTelephone); - cy.get("[name='institutionId']").clear().type(otherInstitution); cy.get('[data-cy="institution-options"] li') @@ -90,12 +81,6 @@ context('Personal information tests', () => { cy.get("[name='preferredname']") .invoke('val') .should('eq', newPreferredName); - - cy.get("[name='position']").invoke('val').should('eq', newPosition); - - cy.get("[name='department']").invoke('val').should('eq', newDepartment); - - cy.get("[name='telephone']").invoke('val').should('eq', newTelephone); }); it('User Officer should be able to see all and change roles if there are multiple', () => { diff --git a/apps/e2e/cypress/e2e/userAdministration.cy.ts b/apps/e2e/cypress/e2e/userAdministration.cy.ts index 853cdeb169..f9ff5fbbea 100644 --- a/apps/e2e/cypress/e2e/userAdministration.cy.ts +++ b/apps/e2e/cypress/e2e/userAdministration.cy.ts @@ -7,10 +7,7 @@ import initialDBData from '../support/initialDBData'; context('User administration tests', () => { const newFirstName = faker.name.firstName(); const newLastName = faker.name.lastName(); - const newDepartment = faker.commerce.department(); const newPrefferedName = faker.hacker.noun(); - const newPosition = faker.random.word().split(' ')[0]; - const newTelephone = faker.phone.number('0##########'); const newInstitution = faker.company.name(); const placeholderUser = initialDBData.users.placeholderUser; const title = faker.lorem.words(2); @@ -38,12 +35,6 @@ context('User administration tests', () => { cy.get("[name='preferredname']").clear().type(newPrefferedName); - cy.get("[name='position']").clear().type(newPosition); - - cy.get("[name='department']").clear().type(newDepartment); - - cy.get("[name='telephone']").clear().type(newTelephone); - cy.get("[name='otherInstitution']").clear().type(newInstitution); cy.contains('Update Profile').click(); @@ -59,12 +50,6 @@ context('User administration tests', () => { cy.get("[name='preferredname']") .invoke('val') .should('eq', newPrefferedName); - - cy.get("[name='position']").invoke('val').should('eq', newPosition); - - cy.get("[name='department']").invoke('val').should('eq', newDepartment); - - cy.get("[name='telephone']").invoke('val').should('eq', newTelephone); }); it('Should be able to invite user or fap reviewer by email', function () { From 2b0e5ca2c934f6317c7e826639dfeb3cf69fd51e Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Tue, 28 Oct 2025 14:32:24 +0100 Subject: [PATCH 04/24] Update apps/frontend/src/components/user/UpdateUserInformation.tsx Co-authored-by: Jekabs Karklins <58165815+jekabs-karklins@users.noreply.github.com> --- apps/frontend/src/components/user/UpdateUserInformation.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/frontend/src/components/user/UpdateUserInformation.tsx b/apps/frontend/src/components/user/UpdateUserInformation.tsx index 1c24ea2061..61b8a29c6a 100644 --- a/apps/frontend/src/components/user/UpdateUserInformation.tsx +++ b/apps/frontend/src/components/user/UpdateUserInformation.tsx @@ -179,7 +179,6 @@ export default function UpdateUserInformation( type="email" data-cy="email" /> - {/* Remove telephone field */} Date: Thu, 30 Oct 2025 15:37:51 +0100 Subject: [PATCH 05/24] Remove old redeemCodes functionality --- .../db_patches/0181_RemoveUserColumns.sql | 2 + apps/backend/src/buildContext.ts | 2 - apps/backend/src/config/Tokens.ts | 1 - .../src/config/dependencyConfigDefault.ts | 2 - .../backend/src/config/dependencyConfigE2E.ts | 2 - .../backend/src/config/dependencyConfigELI.ts | 2 - .../backend/src/config/dependencyConfigESS.ts | 2 - .../src/config/dependencyConfigSTFC.ts | 2 - .../src/config/dependencyConfigTest.ts | 2 - apps/backend/src/context/index.ts | 2 - .../src/datasources/RedeemCodesDataSource.ts | 18 --- .../backend/src/datasources/UserDataSource.ts | 2 - .../mockups/DataAccessUsersDataSource.ts | 16 +-- .../datasources/mockups/RedeemDataSource.ts | 65 ----------- .../src/datasources/mockups/UserDataSource.ts | 4 - .../postgres/RedeemCodesDataSource.ts | 78 ------------- .../datasources/postgres/UserDataSource.ts | 21 ---- .../src/datasources/postgres/records.ts | 19 ---- .../datasources/stfc/StfcUserDataSource.ts | 5 - .../eventHandlers/email/eliEmailHandler.ts | 63 +---------- apps/backend/src/eventHandlers/logging.ts | 12 +- apps/backend/src/events/applicationEvents.ts | 12 +- apps/backend/src/events/event.enum.ts | 5 - apps/backend/src/models/Feature.ts | 1 - apps/backend/src/models/RedeemCode.ts | 10 -- .../mutations/RedeemCodesMutations.spec.ts | 21 ---- .../src/mutations/RedeemCodesMutations.ts | 74 ------------- .../src/mutations/UserMutations.spec.ts | 88 +-------------- apps/backend/src/mutations/UserMutations.ts | 103 +----------------- .../CreateUserByEmailInviteMutation.ts | 43 -------- .../resolvers/mutations/RedeemCodeMutation.ts | 15 --- .../backend/src/resolvers/types/RedeemCode.ts | 21 ---- apps/e2e/cypress/e2e/invites.cy.ts | 16 --- apps/e2e/cypress/e2e/proposals.cy.ts | 21 ---- apps/e2e/cypress/support/initialDBData.ts | 7 -- .../components/common/UserManagementTable.tsx | 36 +----- .../src/components/proposal/AcceptInvite.tsx | 37 ++----- .../QuestionaryComponentProposalBasis.tsx | 36 +----- .../redeem/fragment.redeemCode.graphql | 7 -- .../src/graphql/redeem/redeemCode.graphql | 5 - 40 files changed, 27 insertions(+), 853 deletions(-) delete mode 100644 apps/backend/src/datasources/RedeemCodesDataSource.ts delete mode 100644 apps/backend/src/datasources/mockups/RedeemDataSource.ts delete mode 100644 apps/backend/src/datasources/postgres/RedeemCodesDataSource.ts delete mode 100644 apps/backend/src/models/RedeemCode.ts delete mode 100644 apps/backend/src/mutations/RedeemCodesMutations.spec.ts delete mode 100644 apps/backend/src/mutations/RedeemCodesMutations.ts delete mode 100644 apps/backend/src/resolvers/mutations/CreateUserByEmailInviteMutation.ts delete mode 100644 apps/backend/src/resolvers/mutations/RedeemCodeMutation.ts delete mode 100644 apps/backend/src/resolvers/types/RedeemCode.ts delete mode 100644 apps/frontend/src/graphql/redeem/fragment.redeemCode.graphql delete mode 100644 apps/frontend/src/graphql/redeem/redeemCode.graphql diff --git a/apps/backend/db_patches/0181_RemoveUserColumns.sql b/apps/backend/db_patches/0181_RemoveUserColumns.sql index 2917630545..3e419f5fd2 100644 --- a/apps/backend/db_patches/0181_RemoveUserColumns.sql +++ b/apps/backend/db_patches/0181_RemoveUserColumns.sql @@ -9,6 +9,8 @@ BEGIN '2025-05-12' ) THEN BEGIN + DELETE FROM features WHERE feature_id = 'EMAIL_INVITE_LEGACY'; + DROP TABLE IF EXISTS public.redeem_codes; -- first drop the FK on nationality ALTER TABLE public.users DROP CONSTRAINT IF EXISTS users_nationality_fkey; diff --git a/apps/backend/src/buildContext.ts b/apps/backend/src/buildContext.ts index 295cc67524..2e82a613ae 100644 --- a/apps/backend/src/buildContext.ts +++ b/apps/backend/src/buildContext.ts @@ -20,7 +20,6 @@ import PredefinedMessageMutations from './mutations/PredefinedMessageMutations'; import ProposalMutations from './mutations/ProposalMutations'; import ProposalPdfTemplateMutations from './mutations/ProposalPdfTemplateMutations'; import QuestionaryMutations from './mutations/QuestionaryMutations'; -import RedeemCodesMutations from './mutations/RedeemCodesMutations'; import ReviewMutations from './mutations/ReviewMutations'; import SampleMutations from './mutations/SampleMutations'; import ShipmentMutations from './mutations/ShipmentMutations'; @@ -120,7 +119,6 @@ const context: BasicResolverContext = { ), proposal: container.resolve(ProposalMutations), questionary: container.resolve(QuestionaryMutations), - redeemCodes: container.resolve(RedeemCodesMutations), review: container.resolve(ReviewMutations), sample: container.resolve(SampleMutations), fap: container.resolve(FapMutations), diff --git a/apps/backend/src/config/Tokens.ts b/apps/backend/src/config/Tokens.ts index 44cbf95bc4..59f46ba513 100644 --- a/apps/backend/src/config/Tokens.ts +++ b/apps/backend/src/config/Tokens.ts @@ -30,7 +30,6 @@ export const Tokens = { ProposalSettingsDataSource: Symbol('ProposalSettingsDataSource'), StatusActionsDataSource: Symbol('StatusActionsDataSource'), QuestionaryDataSource: Symbol('QuestionaryDataSource'), - RedeemCodesDataSource: Symbol('RedeemCodesDataSource'), RoleClaimDataSource: Symbol('RoleClaimDataSource'), ReviewDataSource: Symbol('ReviewDataSource'), FapDataSource: Symbol('FapDataSource'), diff --git a/apps/backend/src/config/dependencyConfigDefault.ts b/apps/backend/src/config/dependencyConfigDefault.ts index f271d12ed8..6f780e5f23 100644 --- a/apps/backend/src/config/dependencyConfigDefault.ts +++ b/apps/backend/src/config/dependencyConfigDefault.ts @@ -29,7 +29,6 @@ import PostgresProposalDataSource from '../datasources/postgres/ProposalDataSour import PostgresProposalInternalCommentsDataSource from '../datasources/postgres/ProposalInternalCommentsDataSource'; import PostgresProposalPdfTemplateDataSource from '../datasources/postgres/ProposalPdfTemplateDataSource'; import PostgresQuestionaryDataSource from '../datasources/postgres/QuestionaryDataSource'; -import PostgresRedeemCodesDataSource from '../datasources/postgres/RedeemCodesDataSource'; import PostgresReviewDataSource from '../datasources/postgres/ReviewDataSource'; import PostgresRoleClaimDataSource from '../datasources/postgres/RoleClaimsDataSource'; import PostgresSampleDataSource from '../datasources/postgres/SampleDataSource'; @@ -98,7 +97,6 @@ mapClass( ); mapClass(Tokens.StatusActionsDataSource, PostgresStatusActionsDataSource); mapClass(Tokens.QuestionaryDataSource, PostgresQuestionaryDataSource); -mapClass(Tokens.RedeemCodesDataSource, PostgresRedeemCodesDataSource); mapClass(Tokens.ReviewDataSource, PostgresReviewDataSource); mapClass(Tokens.FapDataSource, PostgresFapDataSource); mapClass(Tokens.SampleDataSource, PostgresSampleDataSource); diff --git a/apps/backend/src/config/dependencyConfigE2E.ts b/apps/backend/src/config/dependencyConfigE2E.ts index f99f9948cd..330f5e0699 100644 --- a/apps/backend/src/config/dependencyConfigE2E.ts +++ b/apps/backend/src/config/dependencyConfigE2E.ts @@ -25,7 +25,6 @@ import PostgresProposalDataSource from '../datasources/postgres/ProposalDataSour import PostgresProposalInternalCommentsDataSource from '../datasources/postgres/ProposalInternalCommentsDataSource'; import PostgresProposalPdfTemplateDataSource from '../datasources/postgres/ProposalPdfTemplateDataSource'; import PostgresQuestionaryDataSource from '../datasources/postgres/QuestionaryDataSource'; -import PostgresRedeemCodesDataSource from '../datasources/postgres/RedeemCodesDataSource'; import PostgresReviewDataSource from '../datasources/postgres/ReviewDataSource'; import PostgresRoleClaimDataSource from '../datasources/postgres/RoleClaimsDataSource'; import PostgresSampleDataSource from '../datasources/postgres/SampleDataSource'; @@ -89,7 +88,6 @@ mapClass( ); mapClass(Tokens.StatusActionsDataSource, PostgresStatusActionsDataSource); mapClass(Tokens.QuestionaryDataSource, PostgresQuestionaryDataSource); -mapClass(Tokens.RedeemCodesDataSource, PostgresRedeemCodesDataSource); mapClass(Tokens.ReviewDataSource, PostgresReviewDataSource); mapClass(Tokens.FapDataSource, PostgresFapDataSource); mapClass(Tokens.SampleDataSource, PostgresSampleDataSource); diff --git a/apps/backend/src/config/dependencyConfigELI.ts b/apps/backend/src/config/dependencyConfigELI.ts index 6a158f0b8c..b60749dcab 100644 --- a/apps/backend/src/config/dependencyConfigELI.ts +++ b/apps/backend/src/config/dependencyConfigELI.ts @@ -24,7 +24,6 @@ import PostgresProposalDataSource from '../datasources/postgres/ProposalDataSour import PostgresProposalInternalCommentsDataSource from '../datasources/postgres/ProposalInternalCommentsDataSource'; import PostgresProposalPdfTemplateDataSource from '../datasources/postgres/ProposalPdfTemplateDataSource'; import PostgresQuestionaryDataSource from '../datasources/postgres/QuestionaryDataSource'; -import PostgresRedeemCodesDataSource from '../datasources/postgres/RedeemCodesDataSource'; import PostgresReviewDataSource from '../datasources/postgres/ReviewDataSource'; import PostgresRoleClaimDataSource from '../datasources/postgres/RoleClaimsDataSource'; import PostgresSampleDataSource from '../datasources/postgres/SampleDataSource'; @@ -90,7 +89,6 @@ mapClass( ); mapClass(Tokens.StatusActionsDataSource, PostgresStatusActionsDataSource); mapClass(Tokens.QuestionaryDataSource, PostgresQuestionaryDataSource); -mapClass(Tokens.RedeemCodesDataSource, PostgresRedeemCodesDataSource); mapClass(Tokens.ReviewDataSource, PostgresReviewDataSource); mapClass(Tokens.FapDataSource, PostgresFapDataSource); mapClass(Tokens.SampleDataSource, PostgresSampleDataSource); diff --git a/apps/backend/src/config/dependencyConfigESS.ts b/apps/backend/src/config/dependencyConfigESS.ts index ddf4cdaa05..d6e012f402 100644 --- a/apps/backend/src/config/dependencyConfigESS.ts +++ b/apps/backend/src/config/dependencyConfigESS.ts @@ -25,7 +25,6 @@ import PostgresProposalDataSource from '../datasources/postgres/ProposalDataSour import PostgresProposalInternalCommentsDataSource from '../datasources/postgres/ProposalInternalCommentsDataSource'; import PostgresProposalPdfTemplateDataSource from '../datasources/postgres/ProposalPdfTemplateDataSource'; import PostgresQuestionaryDataSource from '../datasources/postgres/QuestionaryDataSource'; -import PostgresRedeemCodesDataSource from '../datasources/postgres/RedeemCodesDataSource'; import PostgresReviewDataSource from '../datasources/postgres/ReviewDataSource'; import PostgresRoleClaimDataSource from '../datasources/postgres/RoleClaimsDataSource'; import PostgresSampleDataSource from '../datasources/postgres/SampleDataSource'; @@ -90,7 +89,6 @@ mapClass( ); mapClass(Tokens.StatusActionsDataSource, PostgresStatusActionsDataSource); mapClass(Tokens.QuestionaryDataSource, PostgresQuestionaryDataSource); -mapClass(Tokens.RedeemCodesDataSource, PostgresRedeemCodesDataSource); mapClass(Tokens.ReviewDataSource, PostgresReviewDataSource); mapClass(Tokens.FapDataSource, PostgresFapDataSource); mapClass(Tokens.SampleDataSource, PostgresSampleDataSource); diff --git a/apps/backend/src/config/dependencyConfigSTFC.ts b/apps/backend/src/config/dependencyConfigSTFC.ts index bcc7995ff9..2ae939cf08 100644 --- a/apps/backend/src/config/dependencyConfigSTFC.ts +++ b/apps/backend/src/config/dependencyConfigSTFC.ts @@ -20,7 +20,6 @@ import PostgresPredefinedMessageDataSource from '../datasources/postgres/Predefi import PostgresProposalInternalCommentsDataSource from '../datasources/postgres/ProposalInternalCommentsDataSource'; import PostgresProposalPdfTemplateDataSource from '../datasources/postgres/ProposalPdfTemplateDataSource'; import PostgresQuestionaryDataSource from '../datasources/postgres/QuestionaryDataSource'; -import PostgresRedeemCodesDataSource from '../datasources/postgres/RedeemCodesDataSource'; import PostgresReviewDataSource from '../datasources/postgres/ReviewDataSource'; import PostgresRoleClaimDataSource from '../datasources/postgres/RoleClaimsDataSource'; import PostgresSampleDataSource from '../datasources/postgres/SampleDataSource'; @@ -88,7 +87,6 @@ mapClass( ); mapClass(Tokens.StatusActionsDataSource, PostgresStatusActionsDataSource); mapClass(Tokens.QuestionaryDataSource, PostgresQuestionaryDataSource); -mapClass(Tokens.RedeemCodesDataSource, PostgresRedeemCodesDataSource); mapClass(Tokens.ReviewDataSource, PostgresReviewDataSource); mapClass(Tokens.FapDataSource, StfcFapDataSource); mapClass(Tokens.SampleDataSource, PostgresSampleDataSource); diff --git a/apps/backend/src/config/dependencyConfigTest.ts b/apps/backend/src/config/dependencyConfigTest.ts index b3f0c39c0a..8d3a7d9b0d 100644 --- a/apps/backend/src/config/dependencyConfigTest.ts +++ b/apps/backend/src/config/dependencyConfigTest.ts @@ -26,7 +26,6 @@ import { ProposalDataSourceMock } from '../datasources/mockups/ProposalDataSourc import { PostgresProposalInternalCommentsDataSourceMock } from '../datasources/mockups/ProposalInternalCommentsDataSource'; import { ProposalPdfTemplateDataSourceMock } from '../datasources/mockups/ProposalPdfTemplateDataSource'; import { QuestionaryDataSourceMock } from '../datasources/mockups/QuestionaryDataSource'; -import { RedeemDataSourceMock } from '../datasources/mockups/RedeemDataSource'; import { ReviewDataSourceMock } from '../datasources/mockups/ReviewDataSource'; import { RoleClaimDataSourceMock } from '../datasources/mockups/RoleClaimDataSource'; import { SampleDataSourceMock } from '../datasources/mockups/SampleDataSource'; @@ -86,7 +85,6 @@ mapClass(Tokens.StatusDataSource, StatusDataSourceMock); mapClass(Tokens.ExperimentDataSource, ExperimentDataSourceMock); mapClass(Tokens.StatusActionsDataSource, StatusActionsDataSourceMock); mapClass(Tokens.QuestionaryDataSource, QuestionaryDataSourceMock); -mapClass(Tokens.RedeemCodesDataSource, RedeemDataSourceMock); mapClass(Tokens.ReviewDataSource, ReviewDataSourceMock); mapClass(Tokens.FapDataSource, FapDataSourceMock); mapClass(Tokens.SampleDataSource, SampleDataSourceMock); diff --git a/apps/backend/src/context/index.ts b/apps/backend/src/context/index.ts index 2ae3032b37..033d7c24ba 100644 --- a/apps/backend/src/context/index.ts +++ b/apps/backend/src/context/index.ts @@ -19,7 +19,6 @@ import PredefinedMessageMutations from '../mutations/PredefinedMessageMutations' import ProposalMutations from '../mutations/ProposalMutations'; import ProposalPdfTemplateMutations from '../mutations/ProposalPdfTemplateMutations'; import QuestionaryMutations from '../mutations/QuestionaryMutations'; -import RedeemCodesMutations from '../mutations/RedeemCodesMutations'; import ReviewMutations from '../mutations/ReviewMutations'; import SampleMutations from '../mutations/SampleMutations'; import ShipmentMutations from '../mutations/ShipmentMutations'; @@ -115,7 +114,6 @@ interface ResolverContextMutations { experimentSafetyPdfTemplate: ExperimentSafetyPdfTemplateMutations; proposal: ProposalMutations; questionary: QuestionaryMutations; - redeemCodes: RedeemCodesMutations; review: ReviewMutations; sample: SampleMutations; fap: FapMutations; diff --git a/apps/backend/src/datasources/RedeemCodesDataSource.ts b/apps/backend/src/datasources/RedeemCodesDataSource.ts deleted file mode 100644 index 7b2dfe6c63..0000000000 --- a/apps/backend/src/datasources/RedeemCodesDataSource.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { RedeemCode } from '../models/RedeemCode'; - -export interface RedeemCodesDataSource { - // Create - createRedeemCode( - placeholderUserId: number, - createdByUserId: number - ): Promise; - - // Read - getRedeemCodes(filter: { - code?: string; - placeholderUserId?: number; - }): Promise; - - // update - updateRedeemCode(code: Partial): Promise; -} diff --git a/apps/backend/src/datasources/UserDataSource.ts b/apps/backend/src/datasources/UserDataSource.ts index e0faa2210c..2dc26991d5 100644 --- a/apps/backend/src/datasources/UserDataSource.ts +++ b/apps/backend/src/datasources/UserDataSource.ts @@ -3,7 +3,6 @@ import { Institution } from '../models/Institution'; import { Role, Roles } from '../models/Role'; import { BasicUserDetails, User, UserRole } from '../models/User'; import { AddUserRoleArgs } from '../resolvers/mutations/AddUserRoleMutation'; -import { CreateUserByEmailInviteArgs } from '../resolvers/mutations/CreateUserByEmailInviteMutation'; import { UpdateUserByOidcSubArgs, UpdateUserByIdArgs, @@ -13,7 +12,6 @@ import { UsersArgs } from '../resolvers/queries/UsersQuery'; export interface UserDataSource { delete(id: number): Promise; addUserRole(args: AddUserRoleArgs): Promise; - createInviteUser(args: CreateUserByEmailInviteArgs): Promise; getBasicUserInfo(id: number): Promise; getBasicUsersInfo(ids: readonly number[]): Promise; getBasicUserDetailsByEmail( diff --git a/apps/backend/src/datasources/mockups/DataAccessUsersDataSource.ts b/apps/backend/src/datasources/mockups/DataAccessUsersDataSource.ts index 2f0b066253..b06f3931a2 100644 --- a/apps/backend/src/datasources/mockups/DataAccessUsersDataSource.ts +++ b/apps/backend/src/datasources/mockups/DataAccessUsersDataSource.ts @@ -18,16 +18,9 @@ export const dummyDataAccessFullUser = new User( 'Jane', 'jane.smith.oidc', 'refresh-token', - 'oauth-issuer', - 'female', - new Date('1985-01-15'), 10, - 'Data Access Institute', - 'Research Department', - 'Data Analyst', + 'oauth-issuer', 'jane.smith@dataaccess.org', - '+1-555-0100', - false, '2023-01-15 10:30:00+00', '2023-01-15 10:30:00+00' ); @@ -37,20 +30,13 @@ export const dummyDataAccessFullUser2 = new User( 'Dr.', 'Bob', 'Johnson', - 'bob.johnson', 'Bob', 'bob.johnson.oidc', 'refresh-token', 'oauth-issuer', - 'male', - new Date('1982-02-20'), 11, 'Research Center', - 'Advanced Research', - 'Senior Researcher', 'bob.johnson@research.org', - '+1-555-0101', - false, '2023-02-20 14:15:00+00', '2023-02-20 14:15:00+00' ); diff --git a/apps/backend/src/datasources/mockups/RedeemDataSource.ts b/apps/backend/src/datasources/mockups/RedeemDataSource.ts deleted file mode 100644 index 38c80b768d..0000000000 --- a/apps/backend/src/datasources/mockups/RedeemDataSource.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { RedeemCode } from '../../models/RedeemCode'; -import { RedeemCodesDataSource } from '../RedeemCodesDataSource'; -import { dummyPlaceHolderUser, dummyUser } from './UserDataSource'; - -export const VALID_REDEEM_CODE = 'valid_redeem_code'; -export const NEW_REDEEM_CODE = 'new_redeem_code'; - -const dummyRedeemCodes: RedeemCode[] = [ - { - code: VALID_REDEEM_CODE, - placeholderUserId: dummyPlaceHolderUser.id, - createdBy: dummyUser.id, - createdAt: new Date(), - claimedBy: null, - claimedAt: null, - }, -]; - -export class RedeemDataSourceMock implements RedeemCodesDataSource { - async createRedeemCode( - placeholderUserId: number, - createdByUserId: number - ): Promise { - const newRedeemCode = { - code: NEW_REDEEM_CODE, - placeholderUserId, - createdBy: createdByUserId, - createdAt: new Date(), - claimedBy: null, - claimedAt: null, - }; - - dummyRedeemCodes.push(newRedeemCode); - - return newRedeemCode; - } - async getRedeemCodes(filter: { code?: string; placeholderUserId?: number }) { - return dummyRedeemCodes.filter((c) => { - if ( - (filter.code && c.code !== filter.code) || - (filter.placeholderUserId && - c.placeholderUserId !== filter.placeholderUserId) - ) { - return false; - } else { - return true; - } - }); - } - - async updateRedeemCode(code: Partial): Promise { - const redeemCode = dummyRedeemCodes.find((c) => c.code === code.code); - - if (!redeemCode) { - throw new Error('Could not find redeem code'); - } - - const updatedRedeemCode = { - ...redeemCode, - ...code, - }; - - return updatedRedeemCode; - } -} diff --git a/apps/backend/src/datasources/mockups/UserDataSource.ts b/apps/backend/src/datasources/mockups/UserDataSource.ts index 3d54241793..d5158f051e 100644 --- a/apps/backend/src/datasources/mockups/UserDataSource.ts +++ b/apps/backend/src/datasources/mockups/UserDataSource.ts @@ -8,7 +8,6 @@ import { UserWithRole, } from '../../models/User'; import { AddUserRoleArgs } from '../../resolvers/mutations/AddUserRoleMutation'; -import { CreateUserByEmailInviteArgs } from '../../resolvers/mutations/CreateUserByEmailInviteMutation'; import { UpdateUserByIdArgs, UpdateUserByOidcSubArgs, @@ -230,9 +229,6 @@ export class UserDataSourceMock implements UserDataSource { async getByOIDCSub(oidcSub: string): Promise { return dummyUser; } - async createInviteUser(args: CreateUserByEmailInviteArgs): Promise { - return 5; - } async createInstitution(name: string, countryId?: number): Promise { return 1; } diff --git a/apps/backend/src/datasources/postgres/RedeemCodesDataSource.ts b/apps/backend/src/datasources/postgres/RedeemCodesDataSource.ts deleted file mode 100644 index 051be8e48e..0000000000 --- a/apps/backend/src/datasources/postgres/RedeemCodesDataSource.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - -import { RedeemCode } from '../../models/RedeemCode'; -import { RedeemCodesDataSource } from '../RedeemCodesDataSource'; -import database from './database'; -import { createRedeemCodeObject, RedeemCodeRecord } from './records'; - -export default class PostgresRedeemCodesDataSource - implements RedeemCodesDataSource -{ - async getRedeemCode(code: string): Promise { - return database - .select() - .from('redeem_codes') - .where({ code }) - .first() - .then((code: RedeemCodeRecord) => createRedeemCodeObject(code)); - } - - async getRedeemCodes(filter: { - code?: string; - placeholderUserId?: number; - }): Promise { - return database - .select() - .from('redeem_codes') - .modify((queryBuilder) => { - if (filter.placeholderUserId) { - queryBuilder.where('placeholder_user_id', filter.placeholderUserId); - } - if (filter.code) { - queryBuilder.where('code', filter.code); - } - }) - .then((codes: RedeemCodeRecord[]) => codes.map(createRedeemCodeObject)); - } - - async updateRedeemCode( - redeemCode: Partial & Pick - ): Promise { - await database('redeem_codes') - .update({ - claimed_at: redeemCode.claimedAt, - claimed_by: redeemCode.claimedBy, - }) - .where('code', redeemCode.code); - - return this.getRedeemCode(redeemCode.code); - } - - generateRandomCode() { - return Math.random().toString(36).substring(2, 8).toLowerCase(); - } - - async createRedeemCode( - placeholderUserId: number, - createdByUserId: number - ): Promise { - const newCode = this.generateRandomCode(); - const existingCodes = await this.getRedeemCodes({ - code: newCode, - }); - - // in case of collision, try again - if (existingCodes.length > 0) { - return this.createRedeemCode(placeholderUserId, createdByUserId); - } - - return database('redeem_codes') - .insert({ - placeholder_user_id: placeholderUserId, - created_by: createdByUserId, - code: this.generateRandomCode(), - }) - .returning('*') - .then((codes: RedeemCodeRecord[]) => createRedeemCodeObject(codes[0])); - } -} diff --git a/apps/backend/src/datasources/postgres/UserDataSource.ts b/apps/backend/src/datasources/postgres/UserDataSource.ts index d341ccab36..9831b11edc 100644 --- a/apps/backend/src/datasources/postgres/UserDataSource.ts +++ b/apps/backend/src/datasources/postgres/UserDataSource.ts @@ -13,7 +13,6 @@ import { UserRoleShortCodeMap, } from '../../models/User'; import { AddUserRoleArgs } from '../../resolvers/mutations/AddUserRoleMutation'; -import { CreateUserByEmailInviteArgs } from '../../resolvers/mutations/CreateUserByEmailInviteMutation'; import { UpdateUserByIdArgs, UpdateUserByOidcSubArgs, @@ -141,26 +140,6 @@ export default class PostgresUserDataSource implements UserDataSource { return createUserObject(userRecord); } - async createInviteUser(args: CreateUserByEmailInviteArgs): Promise { - const { firstname, lastname, email } = args; - - return database - .insert({ - user_title: '', - firstname, - lastname, - preferredname: firstname, - oidc_sub: '', - oauth_refresh_token: '', - oauth_issuer: '', - institution_id: 1, - email, - }) - .returning(['*']) - .into('users') - .then((user: UserRecord[]) => user[0].user_id); - } - async getRoles(): Promise { return database .select() diff --git a/apps/backend/src/datasources/postgres/records.ts b/apps/backend/src/datasources/postgres/records.ts index b22748fe93..23d2e89996 100644 --- a/apps/backend/src/datasources/postgres/records.ts +++ b/apps/backend/src/datasources/postgres/records.ts @@ -35,7 +35,6 @@ import { getTransformedConfigData, QuestionDataTypeConfigMapping, } from '../../models/questionTypes/QuestionRegistry'; -import { RedeemCode } from '../../models/RedeemCode'; import { Review } from '../../models/Review'; import { Role } from '../../models/Role'; import { RoleClaim } from '../../models/RoleClaim'; @@ -736,15 +735,6 @@ export interface QuantityRecord { readonly quantity_id: string; } -export interface RedeemCodeRecord { - readonly code: string; - readonly placeholder_user_id: number; - readonly created_by: number; - readonly created_at: Date; - readonly claimed_by: number | null; - readonly claimed_at: Date | null; -} - export interface StatusActionRecord { readonly status_action_id: number; readonly name: string; @@ -1302,15 +1292,6 @@ export const createExperimentSafetyPdfTemplateObject = ( ); }; -export const createRedeemCodeObject = (invite: RedeemCodeRecord) => - new RedeemCode( - invite.code, - invite.placeholder_user_id, - invite.created_by, - invite.created_at, - invite.claimed_by, - invite.claimed_at - ); export const createStatusActionsLogObject = ( statusActionLog: StatusActionsLogRecord ) => { diff --git a/apps/backend/src/datasources/stfc/StfcUserDataSource.ts b/apps/backend/src/datasources/stfc/StfcUserDataSource.ts index f5c2ec1215..6c472b8032 100644 --- a/apps/backend/src/datasources/stfc/StfcUserDataSource.ts +++ b/apps/backend/src/datasources/stfc/StfcUserDataSource.ts @@ -8,7 +8,6 @@ import { Institution } from '../../models/Institution'; import { Role, Roles } from '../../models/Role'; import { BasicUserDetails, User, UserRole } from '../../models/User'; import { AddUserRoleArgs } from '../../resolvers/mutations/AddUserRoleMutation'; -import { CreateUserByEmailInviteArgs } from '../../resolvers/mutations/CreateUserByEmailInviteMutation'; import { UpdateUserByIdArgs, UpdateUserByOidcSubArgs, @@ -336,10 +335,6 @@ export class StfcUserDataSource implements UserDataSource { throw new Error('Method not implemented.'); } - async createInviteUser(args: CreateUserByEmailInviteArgs): Promise { - throw new Error('Method not implemented.'); - } - async createInstitution(name: string): Promise { throw new Error('Method not implemented.'); } diff --git a/apps/backend/src/eventHandlers/email/eliEmailHandler.ts b/apps/backend/src/eventHandlers/email/eliEmailHandler.ts index f5f93dd1a8..1c155c6dce 100644 --- a/apps/backend/src/eventHandlers/email/eliEmailHandler.ts +++ b/apps/backend/src/eventHandlers/email/eliEmailHandler.ts @@ -6,7 +6,6 @@ import { CallDataSource } from '../../datasources/CallDataSource'; import { FapDataSource } from '../../datasources/FapDataSource'; import { InviteDataSource } from '../../datasources/InviteDataSource'; import { ProposalDataSource } from '../../datasources/ProposalDataSource'; -import { RedeemCodesDataSource } from '../../datasources/RedeemCodesDataSource'; import { ReviewDataSource } from '../../datasources/ReviewDataSource'; import { UserDataSource } from '../../datasources/UserDataSource'; import { ApplicationEvent } from '../../events/applicationEvents'; @@ -14,7 +13,7 @@ import { Event } from '../../events/event.enum'; import { EventBus } from '../../events/eventBus'; import { Invite } from '../../models/Invite'; import { ProposalEndStatus } from '../../models/Proposal'; -import { BasicUserDetails, UserRole } from '../../models/User'; +import { BasicUserDetails } from '../../models/User'; import EmailSettings from '../MailService/EmailSettings'; import { MailService } from '../MailService/MailService'; @@ -32,10 +31,6 @@ export async function eliEmailHandler(event: ApplicationEvent) { Tokens.CallDataSource ); - const redeemCodesDataSource = container.resolve( - Tokens.RedeemCodesDataSource - ); - const technicalReviewDataSource = container.resolve( Tokens.ReviewDataSource ); @@ -48,62 +43,6 @@ export async function eliEmailHandler(event: ApplicationEvent) { } switch (event.type) { - case Event.EMAIL_INVITE_LEGACY: { - const user = await userDataSource.getUser( - event.emailinviteresponse.userId - ); - const inviter = await userDataSource.getBasicUserInfo( - event.emailinviteresponse.inviterId - ); - - if (!user || !inviter) { - logger.logError('Failed email invite', { user, inviter, event }); - - return; - } - - const redeemCode = await redeemCodesDataSource.getRedeemCodes({ - placeholderUserId: user.id, - }); - - if (!redeemCode[0]?.code) { - logger.logError('Failed email invite. No redeem code found', { - user, - inviter, - event, - }); - - return; - } - - mailService - .sendMail({ - content: { - template_id: - event.emailinviteresponse.role === UserRole.USER - ? 'user-office-registration-invitation' - : 'user-office-registration-invitation-reviewer', - }, - substitution_data: { - firstname: user.preferredname, - lastname: user.lastname, - email: user.email, - inviterName: inviter.firstname, - inviterLastname: inviter.lastname, - redeemCode: redeemCode[0].code, - }, - recipients: [{ address: user.email }], - }) - .then((res: any) => { - logger.logInfo('Successful email transmission', { res }); - }) - .catch((err: string) => { - logger.logException('Failed email transmission', err); - }); - - return; - } - case Event.PROPOSAL_CO_PROPOSER_INVITES_UPDATED: { const invites = event.array; diff --git a/apps/backend/src/eventHandlers/logging.ts b/apps/backend/src/eventHandlers/logging.ts index 3d193a76b6..481f3056a5 100644 --- a/apps/backend/src/eventHandlers/logging.ts +++ b/apps/backend/src/eventHandlers/logging.ts @@ -53,14 +53,6 @@ export default function createLoggingHandler() { // NOTE: We need to have custom checks for events where response is not standard one. try { switch (event.type) { - case Event.EMAIL_INVITE_LEGACY: - await eventLogsDataSource.set( - event.loggedInUserId, - event.type, - json, - event.emailinviteresponse.userId.toString() - ); - break; case Event.PROPOSAL_CO_PROPOSER_INVITE_ACCEPTED: { const { invite, proposalPKey } = event; @@ -121,7 +113,7 @@ export default function createLoggingHandler() { case Event.PROPOSAL_INSTRUMENTS_SELECTED: { await Promise.all( event.instrumentshasproposals.proposalPks.map( - async (proposalPk) => { + async (proposalPk: number) => { const instruments = await instrumentDataSource.getInstrumentsByProposalPk( proposalPk @@ -146,7 +138,7 @@ export default function createLoggingHandler() { } case Event.PROPOSAL_FAPS_SELECTED: { await Promise.all( - event.proposalpks.proposalPks.map(async (proposalPk) => { + event.proposalpks.proposalPks.map(async (proposalPk: number) => { const faps = await fapDataSource.getFapsByProposalPk(proposalPk); const description = `Selected FAPs: ${faps diff --git a/apps/backend/src/events/applicationEvents.ts b/apps/backend/src/events/applicationEvents.ts index 2bff444905..0b737f385b 100644 --- a/apps/backend/src/events/applicationEvents.ts +++ b/apps/backend/src/events/applicationEvents.ts @@ -11,7 +11,7 @@ import { Review } from '../models/Review'; import { Sample } from '../models/Sample'; import { TechnicalReview } from '../models/TechnicalReview'; import { Technique } from '../models/Technique'; -import { User, UserRole } from '../models/User'; +import { User } from '../models/User'; import { Visit } from '../models/Visit'; import { VisitRegistration } from '../models/VisitRegistration'; import { Event } from './event.enum'; @@ -269,15 +269,6 @@ interface UserDeletedEvent extends GeneralEvent { user: User; } -interface EmailInviteOld extends GeneralEvent { - type: Event.EMAIL_INVITE_LEGACY; - emailinviteresponse: { - userId: number; - inviterId: number; - role: UserRole; - }; -} - interface FapCreatedEvent extends GeneralEvent { type: Event.FAP_CREATED; fap: Fap; @@ -474,7 +465,6 @@ export type ApplicationEvent = | ProposalClonedEvent | ProposalManagementDecisionUpdatedEvent | ProposalManagementDecisionSubmittedEvent - | EmailInviteOld | UserUpdateEvent | UserRoleUpdateEvent | FapCreatedEvent diff --git a/apps/backend/src/events/event.enum.ts b/apps/backend/src/events/event.enum.ts index e98a24165e..9ed57310d9 100644 --- a/apps/backend/src/events/event.enum.ts +++ b/apps/backend/src/events/event.enum.ts @@ -42,7 +42,6 @@ export enum Event { USER_ROLE_UPDATED = 'USER_ROLE_UPDATED', USER_DELETED = 'USER_DELETED', USER_PASSWORD_RESET_EMAIL = 'USER_PASSWORD_RESET_EMAIL', - EMAIL_INVITE_LEGACY = 'EMAIL_INVITE_LEGACY', FAP_CREATED = 'FAP_CREATED', FAP_UPDATED = 'FAP_UPDATED', FAP_MEMBERS_ASSIGNED = 'FAP_MEMBERS_ASSIGNED', @@ -239,10 +238,6 @@ export const EventLabel = new Map([ Event.USER_PASSWORD_RESET_EMAIL, 'Event occurs when user password is reset by email', ], - [ - Event.EMAIL_INVITE_LEGACY, - '[Deprecated] Event occurs when user is created using email invite', - ], [Event.FAP_CREATED, 'Event occurs when FAP is created'], [Event.FAP_UPDATED, 'Event occurs when FAP is updated'], [Event.FAP_MEMBERS_ASSIGNED, 'Event occurs when we assign member/s to a FAP'], diff --git a/apps/backend/src/models/Feature.ts b/apps/backend/src/models/Feature.ts index 445d04cf51..5e376cb38c 100644 --- a/apps/backend/src/models/Feature.ts +++ b/apps/backend/src/models/Feature.ts @@ -11,7 +11,6 @@ export enum FeatureId { SCHEDULER = 'SCHEDULER', RISK_ASSESSMENT = 'RISK_ASSESSMENT', EMAIL_INVITE = 'EMAIL_INVITE', - EMAIL_INVITE_LEGACY = 'EMAIL_INVITE_LEGACY', EMAIL_SEARCH = 'EMAIL_SEARCH', INSTRUMENT_MANAGEMENT = 'INSTRUMENT_MANAGEMENT', TECHNICAL_REVIEW = 'TECHNICAL_REVIEW', diff --git a/apps/backend/src/models/RedeemCode.ts b/apps/backend/src/models/RedeemCode.ts deleted file mode 100644 index 7fd3f62aff..0000000000 --- a/apps/backend/src/models/RedeemCode.ts +++ /dev/null @@ -1,10 +0,0 @@ -export class RedeemCode { - constructor( - public code: string, - public placeholderUserId: number, - public createdBy: number, - public createdAt: Date, - public claimedBy: number | null, - public claimedAt: Date | null - ) {} -} diff --git a/apps/backend/src/mutations/RedeemCodesMutations.spec.ts b/apps/backend/src/mutations/RedeemCodesMutations.spec.ts deleted file mode 100644 index b76c14b697..0000000000 --- a/apps/backend/src/mutations/RedeemCodesMutations.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { GraphQLError } from 'graphql'; -import 'reflect-metadata'; -import { container } from 'tsyringe'; - -import { dummyUserWithRole } from '../datasources/mockups/UserDataSource'; -import { VALID_REDEEM_CODE } from './../datasources/mockups/RedeemDataSource'; -import RedeemCodesMutations from './RedeemCodesMutations'; - -const redeemCodesMutations = container.resolve(RedeemCodesMutations); - -test('A user can redeem code valid', () => { - return expect( - redeemCodesMutations.redeemCode(dummyUserWithRole, VALID_REDEEM_CODE) - ).resolves.toHaveProperty('code', VALID_REDEEM_CODE); -}); - -test('A user can not redeem invalid code', () => { - return expect( - redeemCodesMutations.redeemCode(dummyUserWithRole, 'SOME_INVALID_CODE') - ).resolves.toBeInstanceOf(GraphQLError); -}); diff --git a/apps/backend/src/mutations/RedeemCodesMutations.ts b/apps/backend/src/mutations/RedeemCodesMutations.ts deleted file mode 100644 index 4c5012d93c..0000000000 --- a/apps/backend/src/mutations/RedeemCodesMutations.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { logger } from '@user-office-software/duo-logger'; -import { inject, injectable } from 'tsyringe'; - -import { Tokens } from '../config/Tokens'; -import { RedeemCodesDataSource } from '../datasources/RedeemCodesDataSource'; -import { UserDataSource } from '../datasources/UserDataSource'; -import { Authorized } from '../decorators'; -import { rejection, Rejection } from '../models/Rejection'; -import { Roles } from '../models/Role'; -import { UserWithRole } from '../models/User'; -import { RedeemCode } from '../resolvers/types/RedeemCode'; -import { ApolloServerErrorCodeExtended } from '../utils/utilTypes'; - -@injectable() -export default class RedeemCodesMutations { - constructor( - @inject(Tokens.UserDataSource) private dataSource: UserDataSource, - @inject(Tokens.RedeemCodesDataSource) - private redeemCodeDataSource: RedeemCodesDataSource - ) {} - - @Authorized([Roles.USER]) - async redeemCode( - user: UserWithRole | null, - code: string - ): Promise { - const redeemCodes = await this.redeemCodeDataSource.getRedeemCodes({ - code: code.toLowerCase().trim(), - }); - const redeemCode = redeemCodes[0]; - if (!redeemCode) { - return rejection('Could not find redeem code', { - code: ApolloServerErrorCodeExtended.NOT_FOUND, - }); - } - - if (redeemCode.claimedBy !== null) { - return rejection('The code is already redeemed', { - code: ApolloServerErrorCodeExtended.BAD_USER_INPUT, - }); - } - - const redeemerId = user!.id; - const placeholderUserId = redeemCode.placeholderUserId; - - const placeholderUser = await this.dataSource.getUser(placeholderUserId); - if (!placeholderUser) { - return rejection('Could not find placeholder user', { - code: ApolloServerErrorCodeExtended.NOT_FOUND, - user: placeholderUser, - }); - } - - await this.dataSource.mergeUsers(placeholderUserId, redeemerId); - - const updatedRedeemCode = await this.redeemCodeDataSource.updateRedeemCode({ - code: redeemCode.code, - claimedAt: new Date(), - claimedBy: redeemerId, - }); - - try { - await this.dataSource.delete(placeholderUserId); - } catch (err) { - logger.logException( - 'Could not delete placeholder user after merging with redeemer', - err, - { placeholderUserId, redeemerId } - ); - } - - return updatedRedeemCode; - } -} diff --git a/apps/backend/src/mutations/UserMutations.spec.ts b/apps/backend/src/mutations/UserMutations.spec.ts index 5335394441..f497a98a4f 100644 --- a/apps/backend/src/mutations/UserMutations.spec.ts +++ b/apps/backend/src/mutations/UserMutations.spec.ts @@ -2,7 +2,6 @@ import jsonwebtoken from 'jsonwebtoken'; import { container } from 'tsyringe'; import { - dummyPlaceHolderUser, dummyUser, dummyUserNotOnProposal, dummyUserOfficer, @@ -10,9 +9,8 @@ import { dummyUserNotOnProposalWithRole, dummyUserOfficerWithRole, } from '../datasources/mockups/UserDataSource'; -import { EmailInviteResponse } from '../models/EmailInviteResponse'; import { isRejection, Rejection } from '../models/Rejection'; -import { AuthJwtPayload, UserRole } from '../models/User'; +import { AuthJwtPayload } from '../models/User'; import { verifyToken } from '../utils/jwt'; import UserMutations from './UserMutations'; @@ -39,90 +37,6 @@ const badToken = jsonwebtoken.sign( const userMutations = container.resolve(UserMutations); -test('A user can invite another user by email', () => { - const emailInviteResponse = new EmailInviteResponse( - 5, - dummyUser.id, - UserRole.USER - ); - - return expect( - userMutations.createUserByEmailInvite(dummyUserWithRole, { - firstname: 'firstname', - lastname: 'lastname', - email: 'email@google.com', - userRole: UserRole.USER, - }) - ).resolves.toStrictEqual(emailInviteResponse); -}); - -test('A user must be logged in to invite another user by email', () => { - return expect( - userMutations.createUserByEmailInvite(null, { - firstname: 'firstname', - lastname: 'lastname', - email: 'email@google.com', - userRole: UserRole.USER, - }) - ).resolves.toHaveProperty('reason', 'NOT_LOGGED_IN'); -}); - -test('A user cannot invite another user by email if the user already has an account', () => { - return expect( - userMutations.createUserByEmailInvite(dummyUserNotOnProposalWithRole, { - firstname: 'firstname', - lastname: 'lastname', - email: dummyUser.email, - userRole: UserRole.USER, - }) - ).rejects.toThrow('Can not create account because account already exists'); -}); - -test('A user can reinvite another user by email if the user has not created an account', () => { - const emailInviteResponse = new EmailInviteResponse( - dummyPlaceHolderUser.id, - dummyUser.id, - UserRole.USER - ); - - return expect( - userMutations.createUserByEmailInvite(dummyUserWithRole, { - firstname: 'firstname', - lastname: 'lastname', - email: dummyPlaceHolderUser.email, - userRole: UserRole.USER, - }) - ).resolves.toStrictEqual(emailInviteResponse); -}); - -test('A user officer can invite a reviewer by email', () => { - const emailInviteResponse = new EmailInviteResponse( - dummyPlaceHolderUser.id, - dummyUserOfficer.id, - UserRole.FAP_REVIEWER - ); - - return expect( - userMutations.createUserByEmailInvite(dummyUserOfficerWithRole, { - firstname: 'firstname', - lastname: 'lastname', - email: dummyPlaceHolderUser.email, - userRole: UserRole.FAP_REVIEWER, - }) - ).resolves.toStrictEqual(emailInviteResponse); -}); - -test('A user cannot invite a reviewer by email', () => { - return expect( - userMutations.createUserByEmailInvite(dummyUserWithRole, { - firstname: 'firstname', - lastname: 'lastname', - email: 'email@google.com', - userRole: UserRole.FAP_REVIEWER, - }) - ).rejects.toThrow('Can not create user for this role'); -}); - test('A user can update its own name', () => { return expect( userMutations.update(dummyUserWithRole, { diff --git a/apps/backend/src/mutations/UserMutations.ts b/apps/backend/src/mutations/UserMutations.ts index 5c1757fe2c..63627fb2d8 100644 --- a/apps/backend/src/mutations/UserMutations.ts +++ b/apps/backend/src/mutations/UserMutations.ts @@ -1,7 +1,6 @@ import { logger } from '@user-office-software/duo-logger'; import { addUserRoleValidationSchema, - createUserByEmailInviteValidationSchema, deleteUserValidationSchema, getTokenForUserValidationSchema, updateUserRolesValidationSchema, @@ -13,22 +12,13 @@ import { Args } from 'type-graphql'; import { UserAuthorization } from '../auth/UserAuthorization'; import { Tokens } from '../config/Tokens'; -import { RedeemCodesDataSource } from '../datasources/RedeemCodesDataSource'; import { UserDataSource } from '../datasources/UserDataSource'; import { Authorized, EventBus, ValidateArgs } from '../decorators'; import { Event } from '../events/event.enum'; -import { EmailInviteResponse } from '../models/EmailInviteResponse'; import { rejection, Rejection } from '../models/Rejection'; import { Role, Roles } from '../models/Role'; -import { - AuthJwtPayload, - User, - UserRole, - UserRoleShortCodeMap, - UserWithRole, -} from '../models/User'; +import { AuthJwtPayload, User, UserWithRole } from '../models/User'; import { AddUserRoleArgs } from '../resolvers/mutations/AddUserRoleMutation'; -import { CreateUserByEmailInviteArgs } from '../resolvers/mutations/CreateUserByEmailInviteMutation'; import { UpdateUserRolesArgs, UpdateUserByOidcSubArgs, @@ -41,9 +31,7 @@ import { ApolloServerErrorCodeExtended } from '../utils/utilTypes'; export default class UserMutations { constructor( @inject(Tokens.UserAuthorization) private userAuth: UserAuthorization, - @inject(Tokens.UserDataSource) private dataSource: UserDataSource, - @inject(Tokens.RedeemCodesDataSource) - private redeemCodeDataSource: RedeemCodesDataSource + @inject(Tokens.UserDataSource) private dataSource: UserDataSource ) {} createHash(password: string): string { @@ -76,93 +64,6 @@ export default class UserMutations { return user; } - createEmailInviteResponse(userId: number, agentId: number, role: UserRole) { - return new EmailInviteResponse(userId, agentId, role); - } - - @ValidateArgs(createUserByEmailInviteValidationSchema(UserRole)) - @Authorized() - @EventBus(Event.EMAIL_INVITE_LEGACY) - async createUserByEmailInvite( - agent: UserWithRole | null, - args: CreateUserByEmailInviteArgs - ): Promise { - let userId: number | null = null; - let role: UserRole = args.userRole; - - if (!agent) { - throw rejection('Agent is not defined', { - agent, - args, - code: ApolloServerErrorCodeExtended.INVALID_TOKEN, - }); - } - // Check if email exist in database and if user has been invited before - const user = await this.dataSource.getByEmail(args.email); - if (user) { - userId = user.id; - - return this.createEmailInviteResponse(userId, agent.id, role); - } else if (user) { - throw rejection('Can not create account because account already exists', { - args, - code: ApolloServerErrorCodeExtended.BAD_REQUEST, - }); - } - - if ( - args.userRole === UserRole.FAP_REVIEWER && - (this.userAuth.isApiToken(agent) || this.userAuth.isUserOfficer(agent)) - ) { - userId = await this.dataSource.createInviteUser(args); - - const newUserRole = await this.dataSource.getRoleByShortCode( - UserRoleShortCodeMap[role] - ); - - await this.dataSource.setUserRoles(userId, [newUserRole.id]); - role = UserRole.FAP_REVIEWER; - } else if (args.userRole === UserRole.USER) { - userId = await this.dataSource.createInviteUser(args); - - const newUserRole = await this.dataSource.getRoleByShortCode( - UserRoleShortCodeMap[role] - ); - - await this.dataSource.setUserRoles(userId, [newUserRole.id]); - role = UserRole.USER; - } else if ( - args.userRole === UserRole.FAP_CHAIR && - (this.userAuth.isApiToken(agent) || this.userAuth.isUserOfficer(agent)) - ) { - // NOTE: For inviting FAP_CHAIR and FAP_SECRETARY we do not setUserRoles because they are set right after in separate call. - userId = await this.dataSource.createInviteUser(args); - role = UserRole.FAP_CHAIR; - } else if ( - args.userRole === UserRole.FAP_SECRETARY && - (this.userAuth.isApiToken(agent) || this.userAuth.isUserOfficer(agent)) - ) { - userId = await this.dataSource.createInviteUser(args); - role = UserRole.FAP_SECRETARY; - } else if ( - args.userRole === UserRole.INSTRUMENT_SCIENTIST && - (this.userAuth.isApiToken(agent) || this.userAuth.isUserOfficer(agent)) - ) { - userId = await this.dataSource.createInviteUser(args); - role = UserRole.INSTRUMENT_SCIENTIST; - } - - if (!userId) { - throw rejection('Can not create user for this role', { - args, - }); - } else { - await this.redeemCodeDataSource.createRedeemCode(userId, agent.id); - - return this.createEmailInviteResponse(userId, agent.id, role); - } - } - @ValidateArgs(updateUserValidationBackendSchema) @Authorized() @EventBus(Event.USER_UPDATED) diff --git a/apps/backend/src/resolvers/mutations/CreateUserByEmailInviteMutation.ts b/apps/backend/src/resolvers/mutations/CreateUserByEmailInviteMutation.ts deleted file mode 100644 index e5c7a6358b..0000000000 --- a/apps/backend/src/resolvers/mutations/CreateUserByEmailInviteMutation.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - Args, - ArgsType, - Ctx, - Field, - Int, - Mutation, - Resolver, -} from 'type-graphql'; - -import { ResolverContext } from '../../context'; -import { UserRole } from '../../models/User'; - -@ArgsType() -export class CreateUserByEmailInviteArgs { - @Field() - public firstname: string; - - @Field() - public lastname: string; - - @Field() - public email: string; - - @Field(() => UserRole) - userRole: UserRole; -} - -@Resolver() -export class CreateUserByEmailInviteMutation { - @Mutation(() => Int) - async createUserByEmailInvite( - @Args() args: CreateUserByEmailInviteArgs, - @Ctx() context: ResolverContext - ) { - const res = await context.mutations.user.createUserByEmailInvite( - context.user, - args - ); - - return res.userId; - } -} diff --git a/apps/backend/src/resolvers/mutations/RedeemCodeMutation.ts b/apps/backend/src/resolvers/mutations/RedeemCodeMutation.ts deleted file mode 100644 index 14fdd95c2e..0000000000 --- a/apps/backend/src/resolvers/mutations/RedeemCodeMutation.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Arg, Ctx, Mutation, Resolver } from 'type-graphql'; - -import { ResolverContext } from '../../context'; -import { RedeemCode } from '../types/RedeemCode'; - -@Resolver() -export class RedeemCodeMutation { - @Mutation(() => RedeemCode) - redeemCode( - @Arg('code', () => String) code: string, - @Ctx() context: ResolverContext - ) { - return context.mutations.redeemCodes.redeemCode(context.user, code); - } -} diff --git a/apps/backend/src/resolvers/types/RedeemCode.ts b/apps/backend/src/resolvers/types/RedeemCode.ts deleted file mode 100644 index c4418b7895..0000000000 --- a/apps/backend/src/resolvers/types/RedeemCode.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ObjectType, Field, Int } from 'type-graphql'; - -import { RedeemCode as RedeemCodeOrigin } from '../../models/RedeemCode'; - -@ObjectType() -export class RedeemCode implements Partial { - @Field(() => String) - public code: string; - - @Field(() => Int) - public placeholderUserId: number; - - @Field(() => Int) - public createdBy: number; - - @Field(() => Date) - public createdAt: Date; - - @Field(() => Date, { nullable: true }) - public claimedAt: Date | null; -} diff --git a/apps/e2e/cypress/e2e/invites.cy.ts b/apps/e2e/cypress/e2e/invites.cy.ts index 4dc4f8e812..ad0402633f 100644 --- a/apps/e2e/cypress/e2e/invites.cy.ts +++ b/apps/e2e/cypress/e2e/invites.cy.ts @@ -15,10 +15,6 @@ context('Invites tests', () => { describe('Creating invites', () => { beforeEach(() => { cy.resetDB(); - cy.updateFeature({ - action: FeatureUpdateAction.DISABLE, - featureIds: [FeatureId.EMAIL_INVITE_LEGACY], - }); cy.getAndStoreFeaturesEnabled(); cy.login('user1', initialDBData.roles.user); @@ -236,10 +232,6 @@ context('Invites tests', () => { describe('Accepting invites', () => { beforeEach(() => { cy.resetDB(true); - cy.updateFeature({ - action: FeatureUpdateAction.DISABLE, - featureIds: [FeatureId.EMAIL_INVITE_LEGACY], - }); cy.getAndStoreFeaturesEnabled(); }); @@ -283,10 +275,6 @@ context('Invites tests', () => { beforeEach(() => { cy.resetDB(); - cy.updateFeature({ - action: FeatureUpdateAction.DISABLE, - featureIds: [FeatureId.EMAIL_INVITE_LEGACY], - }); cy.getAndStoreFeaturesEnabled(); const email = initialDBData.users.user2.email; @@ -379,10 +367,6 @@ context('Invites tests', () => { describe('Invites disabled with email search enabled', () => { beforeEach(() => { cy.resetDB(true); - cy.updateFeature({ - action: FeatureUpdateAction.DISABLE, - featureIds: [FeatureId.EMAIL_INVITE_LEGACY], - }); cy.updateFeature({ action: FeatureUpdateAction.DISABLE, featureIds: [FeatureId.EMAIL_INVITE], diff --git a/apps/e2e/cypress/e2e/proposals.cy.ts b/apps/e2e/cypress/e2e/proposals.cy.ts index 9de7f5d431..233a1c96ae 100644 --- a/apps/e2e/cypress/e2e/proposals.cy.ts +++ b/apps/e2e/cypress/e2e/proposals.cy.ts @@ -1071,27 +1071,6 @@ context('Proposal tests', () => { cy.getAndStoreFeaturesEnabled(); }); - it('Should be able to redeem proposal invite user information', function () { - if (!featureFlags.getEnabledFeatures().get(FeatureId.EMAIL_INVITE)) { - this.skip(); - } - cy.login('user2'); - cy.visit('/'); - cy.finishedLoading(); - cy.get('[data-cy="proposal-table"]').should( - 'not.contain.text', - initialDBData.proposal.shortCode - ); - cy.get('[data-cy="join-proposal-btn"]').click(); - cy.get('#code').clear(); - cy.get('#code').type(initialDBData.redeemCodes.validRedeemCode.code); - cy.get('[data-cy="invitation-submit"]').click(); - cy.get('[data-cy="proposal-table"]').should( - 'contain.text', - initialDBData.proposal.shortCode - ); - }); - it('User officer should reopen proposal', () => { cy.login('user1', initialDBData.roles.user); cy.visit('/'); diff --git a/apps/e2e/cypress/support/initialDBData.ts b/apps/e2e/cypress/support/initialDBData.ts index e85b5a381a..8c9434cf17 100644 --- a/apps/e2e/cypress/support/initialDBData.ts +++ b/apps/e2e/cypress/support/initialDBData.ts @@ -439,13 +439,6 @@ export default { settingsValue: 'dd-MM-yyyy HH:mm', }, }, - redeemCodes: { - validRedeemCode: { - code: '123abc', - placeholderUserId: 5, - createdBy: 1, - }, - }, workflows: { defaultWorkflow: { id: 1, diff --git a/apps/frontend/src/components/common/UserManagementTable.tsx b/apps/frontend/src/components/common/UserManagementTable.tsx index b8fb64e5ef..4969742991 100644 --- a/apps/frontend/src/components/common/UserManagementTable.tsx +++ b/apps/frontend/src/components/common/UserManagementTable.tsx @@ -11,11 +11,9 @@ import React, { useContext, useState } from 'react'; import { ActionButtonContainer } from 'components/common/ActionButtonContainer'; import PeopleTable from 'components/user/PeopleTable'; -import { FeatureContext } from 'context/FeatureContextProvider'; import { UserContext } from 'context/UserContextProvider'; -import { BasicUserDetails, FeatureId, Invite, UserRole } from 'generated/sdk'; +import { BasicUserDetails, Invite, UserRole } from 'generated/sdk'; -import ParticipantModal from '../proposal/ParticipantModal'; import ParticipantSelector from '../proposal/ParticipantSelector'; export type UserManagementTableProps = { @@ -55,10 +53,6 @@ const UserManagementTable = ({ allowInviteByEmail = false, }: UserManagementTableProps) => { const [modalOpen, setOpen] = useState(false); - const { featuresMap } = useContext(FeatureContext); - const isLegacyInviteFlow = featuresMap.get( - FeatureId.EMAIL_INVITE_LEGACY - )?.isEnabled; const currentUser = useContext(UserContext)?.user; const removeUser = (user: BasicUserDetails) => { @@ -79,13 +73,6 @@ const UserManagementTable = ({ setOpen(false); }; - const addUsers = (addedUsers: BasicUserDetails[]) => { - handleAddParticipants({ - users: addedUsers, - invites: [], - }); - }; - const handleDeleteInvite = (invite: Invite) => { setInvites?.(invites.filter((i) => i.email !== invite.email)); }; @@ -105,28 +92,9 @@ const UserManagementTable = ({ /> ); - const LegacyInviteComponent = ( - setOpen(false)} - addParticipants={addUsers} - selectedUsers={[...users.map((user) => user.id), ...excludeUserIds]} - title={title} - selection={true} - userRole={UserRole.USER} - participant={true} - setPrincipalInvestigator={ - onUserAction - ? (user) => onUserAction('setPrincipalInvestigator', user) - : undefined - } - /> - ); - return ( - {modalOpen && - (isLegacyInviteFlow ? LegacyInviteComponent : InviteComponent)} + {modalOpen && InviteComponent} => { - if (isLegacyFlow) { - api({ toastSuccessMessage: 'Code verification successful' }) - .redeemCode({ code: values.code }) - .then(() => { - setSuccessfullyAccepted(true); - props.onAccepted?.(); - props.onClose?.(); - }); - } else { - api({ toastSuccessMessage: 'Code verification successful' }) - .acceptInvite({ code: values.code }) - .then(() => { - setSuccessfullyAccepted(true); - props.onAccepted?.(); - props.onClose?.(); - }); - } + api({ toastSuccessMessage: 'Code verification successful' }) + .acceptInvite({ code: values.code }) + .then(() => { + setSuccessfullyAccepted(true); + props.onAccepted?.(); + props.onClose?.(); + }); }} validationSchema={Yup.object().shape({ code: Yup.string().required() })} > @@ -59,7 +42,7 @@ function AcceptInvite(props: AcceptInviteProps) { - {isLegacyFlow ? 'Redeem' : 'Accept'} + {'Accept'} diff --git a/apps/frontend/src/components/questionary/questionaryComponents/ProposalBasis/QuestionaryComponentProposalBasis.tsx b/apps/frontend/src/components/questionary/questionaryComponents/ProposalBasis/QuestionaryComponentProposalBasis.tsx index 1d6e98c378..d29cd68461 100644 --- a/apps/frontend/src/components/questionary/questionaryComponents/ProposalBasis/QuestionaryComponentProposalBasis.tsx +++ b/apps/frontend/src/components/questionary/questionaryComponents/ProposalBasis/QuestionaryComponentProposalBasis.tsx @@ -2,7 +2,7 @@ import Box from '@mui/material/Box'; import InputAdornment from '@mui/material/InputAdornment'; import { useTheme } from '@mui/material/styles'; import { Field } from 'formik'; -import React, { ChangeEvent, useContext, useEffect, useState } from 'react'; +import React, { ChangeEvent, useContext, useState } from 'react'; import ErrorMessage from 'components/common/ErrorMessage'; import TextField from 'components/common/FormikUITextField'; @@ -11,15 +11,12 @@ import CoProposers from 'components/proposal/CoProposers'; import { BasicComponentProps } from 'components/proposal/IBasicComponentProps'; import PrincipalInvestigator from 'components/proposal/PrincipalInvestigator'; import { ProposalContextType } from 'components/proposal/ProposalContainer'; -import ProposalParticipantLegacy from 'components/proposal/ProposalParticipantLegacy'; import { createMissingContextErrorMessage, QuestionaryContext, } from 'components/questionary/QuestionaryContext'; -import { FeatureContext } from 'context/FeatureContextProvider'; -import { BasicUserDetails, FeatureId, Invite } from 'generated/sdk'; +import { BasicUserDetails, Invite } from 'generated/sdk'; import { SubmitActionDependencyContainer } from 'hooks/questionary/useSubmitActions'; -import { useBasicUserData } from 'hooks/user/useUserData'; import { ProposalSubmissionState } from 'models/questionary/proposal/ProposalSubmissionState'; const TextFieldNoSubmit = withPreventSubmit(TextField); @@ -41,24 +38,12 @@ function QuestionaryComponentProposalBasis(props: BasicComponentProps) { const [localAbstract, setLocalAbstract] = useState(state?.proposal.abstract); const [hasInvalidChars, setHasInvalidChars] = useState(false); const [textLen, setTextLen] = useState(state?.proposal.abstract ?? 0); - const { featuresMap } = useContext(FeatureContext); - const isLegacyInviteFlow = featuresMap.get( - FeatureId.EMAIL_INVITE_LEGACY - )?.isEnabled; if (!state || !dispatch) { throw new Error(createMissingContextErrorMessage()); } const { proposer, users } = state.proposal; - const { loading, userData } = useBasicUserData(state?.proposal.proposer?.id); - const [piData, setPIData] = useState(null); - - useEffect(() => { - if (userData !== null) { - setPIData(userData); - } - }, [userData]); const coProposersChanged = (users: BasicUserDetails[]) => { formikProps.setFieldValue( @@ -88,7 +73,6 @@ function QuestionaryComponentProposalBasis(props: BasicComponentProps) { }, }); - setPIData(user); // Remove the new PI from the co-proposers list (if present) and add the old PI (if present) to the co-proposers list coProposersChanged( users @@ -173,19 +157,9 @@ function QuestionaryComponentProposalBasis(props: BasicComponentProps) { > {counter} - {isLegacyInviteFlow ? ( - - ) : ( - - )} - + Date: Mon, 14 Jul 2025 16:28:56 +0200 Subject: [PATCH 06/24] Remove gender, telephone, username, position, placeholder, birthdate and department columns from users table --- .../db_patches/0183_RemoveUserColulmns2.sql | 24 +++ apps/backend/src/auth/OAuthAuthorization.ts | 15 +- .../backend/src/datasources/UserDataSource.ts | 8 +- .../src/datasources/mockups/UserDataSource.ts | 40 +---- .../datasources/postgres/UserDataSource.ts | 38 +---- .../src/datasources/postgres/records.ts | 16 -- .../datasources/stfc/StfcUserDataSource.ts | 19 +-- apps/backend/src/models/User.ts | 11 -- .../src/mutations/RedeemCodesMutations.ts | 2 +- .../src/mutations/UserMutations.spec.ts | 4 - apps/backend/src/mutations/UserMutations.ts | 4 +- apps/backend/src/queries/UserQueries.ts | 4 - .../resolvers/mutations/UpdateUserMutation.ts | 21 --- .../src/resolvers/types/BasicUserDetails.ts | 6 - apps/backend/src/resolvers/types/User.ts | 21 --- apps/backend/src/resolvers/types/UserJWT.ts | 6 - .../statusActionEngine/statusActionUtils.ts | 2 - apps/e2e/cypress/e2e/eventLogs.cy.ts | 5 - apps/e2e/cypress/e2e/userAdministration.cy.ts | 10 -- .../components/user/UpdateUserInformation.tsx | 154 +----------------- .../src/context/UserContextProvider.tsx | 2 - .../fragment.basicUserInformation.graphql | 2 - .../frontend/src/graphql/user/getUser.graphql | 7 - .../src/graphql/user/getUserMe.graphql | 7 - .../src/graphql/user/updateUser.graphql | 10 -- .../hooks/visit/useBlankVisitRegistration.ts | 2 - 26 files changed, 41 insertions(+), 399 deletions(-) create mode 100644 apps/backend/db_patches/0183_RemoveUserColulmns2.sql diff --git a/apps/backend/db_patches/0183_RemoveUserColulmns2.sql b/apps/backend/db_patches/0183_RemoveUserColulmns2.sql new file mode 100644 index 0000000000..23be706aeb --- /dev/null +++ b/apps/backend/db_patches/0183_RemoveUserColulmns2.sql @@ -0,0 +1,24 @@ +-- 0181_RemoveUserColumns.sql +DO +$$ +BEGIN + IF register_patch( + '0182_RemoveUserColumns2.sql', + 'Fredrik Bolmsten', + 'Remove gender, telephone, username, position, placeholder, birthdate and department columns from users table', + '2025-05-12' + ) THEN + BEGIN + -- now drop the unwanted columns + ALTER TABLE public.users + DROP COLUMN username, + DROP COLUMN gender, + DROP COLUMN birthdate, + DROP COLUMN department, + DROP COLUMN position, + DROP COLUMN telephone; + END; + END IF; +END; +$$ +LANGUAGE plpgsql; diff --git a/apps/backend/src/auth/OAuthAuthorization.ts b/apps/backend/src/auth/OAuthAuthorization.ts index 888c7a9712..7e08f04853 100644 --- a/apps/backend/src/auth/OAuthAuthorization.ts +++ b/apps/backend/src/auth/OAuthAuthorization.ts @@ -141,21 +141,14 @@ export class OAuthAuthorization extends UserAuthorization { if (user) { const updatedUser = await this.userDataSource.update({ ...user, - birthdate: userInfo.birthdate - ? new Date(userInfo.birthdate) - : undefined, - department: userInfo.department as string, email: userInfo.email, firstname: userInfo.given_name, - gender: userInfo.gender, lastname: userInfo.family_name, oauthIssuer: client.issuer.metadata.issuer, oauthRefreshToken: tokenSet.refresh_token ?? '', oidcSub: userInfo.sub, institutionId: institutionId ?? user.institutionId, - position: userInfo.position as string, preferredname: userInfo.preferred_username, - telephone: userInfo.phone_number, user_title: userInfo.title as string, }); @@ -165,18 +158,12 @@ export class OAuthAuthorization extends UserAuthorization { (userInfo.title as string) ?? 'unspecified', userInfo.given_name, userInfo.family_name, - userInfo.email, userInfo.preferred_username ?? '', userInfo.sub, tokenSet.refresh_token ?? '', client.issuer.metadata.issuer, - userInfo.gender ?? 'unspecified', - new Date(), institutionId ?? 1, - '', - (userInfo.position as string) ?? '', - userInfo.email, - '' + userInfo.email ); const roleID = this.getUserRole(newUser); diff --git a/apps/backend/src/datasources/UserDataSource.ts b/apps/backend/src/datasources/UserDataSource.ts index 29c9447fa7..538ec9ca71 100644 --- a/apps/backend/src/datasources/UserDataSource.ts +++ b/apps/backend/src/datasources/UserDataSource.ts @@ -61,18 +61,12 @@ export interface UserDataSource { user_title: string | undefined, firstname: string, lastname: string, - username: string, preferredname: string | undefined, oidc_sub: string, oauth_refreshtoken: string, oauth_issuer: string, - gender: string, - birthdate: Date, institution: number, - department: string, - position: string, - email: string, - telephone: string + email: string ): Promise; ensureDummyUserExists(userId: number): Promise; ensureDummyUsersExist(userIds: number[]): Promise; diff --git a/apps/backend/src/datasources/mockups/UserDataSource.ts b/apps/backend/src/datasources/mockups/UserDataSource.ts index 566b1d1057..90fd064b12 100644 --- a/apps/backend/src/datasources/mockups/UserDataSource.ts +++ b/apps/backend/src/datasources/mockups/UserDataSource.ts @@ -23,9 +23,7 @@ export const basicDummyUser = new BasicUserDetails( 'john', 'org', 1, - 'boss', new Date('2019-07-17 08:25:12.23043+00'), - false, 'test@email.com', '', '' @@ -38,9 +36,7 @@ export const basicDummyUserNotOnProposal = new BasicUserDetails( 'john', 'org', 1, - 'boss', new Date('2019-07-17 08:25:12.23043+00'), - false, 'test@email.com', '', '' @@ -55,16 +51,9 @@ export const dummyUserOfficer = new User( 'Hailey', '324235', '683142616', - 'issuer', - 'male', - new Date('1990-01-25'), 3, - 'Other', - 'IT department', - 'Producer', + 'issuer', 'Dorris83@gmail.com', - '+46700568256', - false, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -89,16 +78,9 @@ export const dummyUser = new User( 'Meta', '12312414', '568567353', - 'issuer', - 'male', - new Date('1981-05-04'), 3, - 'Other', - 'IT department', - 'Architect', + 'issuer', 'Cleve30@yahoo.com', - '+38978414058', - false, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -208,16 +190,9 @@ export const dummyPlaceHolderUser = new User( 'Meta', '12312414', '568567353', - 'issuer', - 'male', - new Date('1981-05-04'), 3, - 'Other', - 'IT department', - 'Architect', + 'issuer', 'placeholder@ess.se', - '+46700568256', - true, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -231,16 +206,9 @@ export const dummyUserNotOnProposal = new User( 'Damion', '182082741', 'Apricot', - 'issuer', - 'female', - new Date('1991-11-08'), 5, - 'Other', - 'IT department', - 'Facilitator', 'Tyrique41@hotmail.com', '+46700568256', - false, '2019-07-17 08:25:12.23043+00', '2019-07-17 08:25:12.23043+00' ); @@ -295,9 +263,7 @@ export class UserDataSourceMock implements UserDataSource { 'John', 'ESS', 2, - 'Manager', new Date('2019-07-17 08:25:12.23043+00'), - false, 'test@email.com', '', '' diff --git a/apps/backend/src/datasources/postgres/UserDataSource.ts b/apps/backend/src/datasources/postgres/UserDataSource.ts index be3e9ddb25..f34cd546c3 100644 --- a/apps/backend/src/datasources/postgres/UserDataSource.ts +++ b/apps/backend/src/datasources/postgres/UserDataSource.ts @@ -79,14 +79,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institutionId, - department, - position, email, - telephone, - placeholder, oidcSub, oauthRefreshToken, oauthIssuer, @@ -98,14 +92,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institution_id: institutionId, - department, - position, email, - telephone, - placeholder, oidc_sub: oidcSub, oauth_refresh_token: oauthRefreshToken, oauth_issuer: oauthIssuer, @@ -124,14 +112,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institutionId, - department, - position, email, - telephone, - placeholder, oauthRefreshToken, oauthIssuer, } = args; @@ -142,14 +124,8 @@ export default class PostgresUserDataSource implements UserDataSource { user_title, lastname, preferredname, - gender, - birthdate, institution_id: institutionId, - department, - position, email, - telephone, - placeholder, oauth_refresh_token: oauthRefreshToken, oauth_issuer: oauthIssuer, updated_at: new Date(), @@ -368,36 +344,24 @@ export default class PostgresUserDataSource implements UserDataSource { user_title: string | undefined, firstname: string, lastname: string, - username: string, preferredname: string | undefined, oidc_sub: string, oauth_refresh_token: string, oauth_issuer: string, - gender: string, - birthdate: Date, institution_id: number, - department: string, - position: string, - email: string, - telephone: string + email: string ): Promise { return database .insert({ user_title, firstname, lastname, - username, preferredname, oidc_sub, oauth_refresh_token, oauth_issuer, - gender, - birthdate, institution_id, - department, - position, email, - telephone, }) .returning(['*']) .into('users') diff --git a/apps/backend/src/datasources/postgres/records.ts b/apps/backend/src/datasources/postgres/records.ts index 7927e0770d..b22748fe93 100644 --- a/apps/backend/src/datasources/postgres/records.ts +++ b/apps/backend/src/datasources/postgres/records.ts @@ -248,23 +248,16 @@ export interface UserRecord { readonly user_title: string; readonly firstname: string; readonly lastname: string; - readonly username: string; readonly preferredname: string; readonly oidc_sub: string | null; readonly oauth_refresh_token: string | null; readonly oauth_issuer: string | null; - readonly gender: string; - readonly birthdate: Date; - readonly department: string; - readonly position: string; readonly email: string; - readonly telephone: string; readonly created_at: Date; readonly updated_at: Date; readonly full_count: number; readonly institution_id: number; readonly institution: string; - readonly placeholder: boolean; } export interface VisitRegistrationRecord { @@ -968,20 +961,13 @@ export const createUserObject = (user: UserRecord) => { user.user_title, user.firstname, user.lastname, - user.username, user.preferredname, user.oidc_sub, user.oauth_refresh_token, user.oauth_issuer, - user.gender, - user.birthdate, user.institution_id, user.institution, - user.department, - user.position, user.email, - user.telephone, - user.placeholder, user.created_at.toISOString(), user.updated_at.toISOString() ); @@ -997,9 +983,7 @@ export const createBasicUserObject = ( user.preferredname, user.institution, user.institution_id, - user.position, user.created_at, - user.placeholder, user.email, user.country, user.user_title diff --git a/apps/backend/src/datasources/stfc/StfcUserDataSource.ts b/apps/backend/src/datasources/stfc/StfcUserDataSource.ts index ff133f1bfd..317e6c19a5 100644 --- a/apps/backend/src/datasources/stfc/StfcUserDataSource.ts +++ b/apps/backend/src/datasources/stfc/StfcUserDataSource.ts @@ -63,7 +63,6 @@ export interface StfcBasicPersonDetails { orgId: number; title: string; userNumber: string; - workPhone: string; } export function toStfcBasicPersonDetails( @@ -88,7 +87,6 @@ export function toStfcBasicPersonDetails( orgId: 1, title: dto.title ?? '', userNumber: dto.userNumber ?? '', - workPhone: dto.workPhone ?? '', }; } @@ -102,9 +100,7 @@ export function toEssBasicUserDetails( stfcUser.firstNameKnownAs ?? stfcUser.givenName, stfcUser.orgName ?? '', stfcUser.orgId ?? 1, - '', new Date(), - false, stfcUser.email ?? '', stfcUser.country ?? '', stfcUser.title ?? '' @@ -117,20 +113,13 @@ function toEssUser(stfcUser: StfcBasicPersonDetails): User { stfcUser.title ?? '', stfcUser.givenName ?? '', stfcUser.familyName ?? '', - stfcUser.email ?? '', stfcUser.firstNameKnownAs ?? stfcUser.givenName, '', '', '', - '', - new Date('2000-01-01'), 1, - stfcUser.orgName, - stfcUser.deptName ?? '', '', stfcUser.email ?? '', - stfcUser.workPhone ?? '', - false, '2000-01-01 00:00:00.000000+00', '2000-01-01 00:00:00.000000+00' ); @@ -688,18 +677,12 @@ export class StfcUserDataSource implements UserDataSource { user_title: string | undefined, firstname: string, lastname: string, - username: string, preferredname: string | undefined, oidc_sub: string, oauth_refresh_token: string, oauth_issuer: string, - gender: string, - birthdate: Date, institution: number, - department: string, - position: string, - email: string, - telephone: string + email: string ): Promise { throw new Error('Method not implemented.'); } diff --git a/apps/backend/src/models/User.ts b/apps/backend/src/models/User.ts index c1cb4e38b5..c41fa8888c 100644 --- a/apps/backend/src/models/User.ts +++ b/apps/backend/src/models/User.ts @@ -27,20 +27,13 @@ export class User { public user_title: string, public firstname: string, public lastname: string, - public username: string, public preferredname: string | undefined, public oidcSub: string | null, public oauthRefreshToken: string | null, public oauthIssuer: string | null, - public gender: string, - public birthdate: Date, public institutionId: number, public institution: string, - public department: string, - public position: string, public email: string, - public telephone: string, - public placeholder: boolean, public created: string, public updated: string ) {} @@ -86,9 +79,7 @@ export class BasicUserDetails { public preferredname: string, public institution: string, public institutionId: number, - public position: string, public created: Date, - public placeholder: boolean, public email: string, public country: string, public title: string @@ -104,7 +95,5 @@ export type UserJWT = Pick< | 'lastname' | 'oidcSub' | 'institutionId' - | 'placeholder' - | 'position' | 'preferredname' >; diff --git a/apps/backend/src/mutations/RedeemCodesMutations.ts b/apps/backend/src/mutations/RedeemCodesMutations.ts index 7b7513c01c..4c5012d93c 100644 --- a/apps/backend/src/mutations/RedeemCodesMutations.ts +++ b/apps/backend/src/mutations/RedeemCodesMutations.ts @@ -44,7 +44,7 @@ export default class RedeemCodesMutations { const placeholderUserId = redeemCode.placeholderUserId; const placeholderUser = await this.dataSource.getUser(placeholderUserId); - if (!placeholderUser || !placeholderUser.placeholder) { + if (!placeholderUser) { return rejection('Could not find placeholder user', { code: ApolloServerErrorCodeExtended.NOT_FOUND, user: placeholderUser, diff --git a/apps/backend/src/mutations/UserMutations.spec.ts b/apps/backend/src/mutations/UserMutations.spec.ts index 24d0fd2452..b6ce1696ab 100644 --- a/apps/backend/src/mutations/UserMutations.spec.ts +++ b/apps/backend/src/mutations/UserMutations.spec.ts @@ -263,7 +263,6 @@ describe('updateUserByOidcSub', () => { { oidcSub: dummyUser.oidcSub as string, firstname: 'OfficerUpdatedJane', - department: 'Updated Department', id: dummyUser.id, } ); @@ -304,8 +303,6 @@ describe('updateUserByOidcSub', () => { test('A user can update partial profile data by OIDC sub', async () => { const result = await userMutations.updateUserByOidcSub(dummyUserWithRole, { oidcSub: dummyUser.oidcSub as string, - telephone: '+1-555-9999', - position: 'Senior Architect', id: dummyUser.id, }); @@ -363,6 +360,5 @@ describe('updateUserByOidcSub', () => { expect(isRejection(result)).toBe(false); expect((result as typeof dummyUser).lastname).toBe(dummyUser.lastname); expect((result as typeof dummyUser).email).toBe(dummyUser.email); - expect((result as typeof dummyUser).department).toBe(dummyUser.department); }); }); diff --git a/apps/backend/src/mutations/UserMutations.ts b/apps/backend/src/mutations/UserMutations.ts index d621a22c72..5c1757fe2c 100644 --- a/apps/backend/src/mutations/UserMutations.ts +++ b/apps/backend/src/mutations/UserMutations.ts @@ -99,7 +99,7 @@ export default class UserMutations { } // Check if email exist in database and if user has been invited before const user = await this.dataSource.getByEmail(args.email); - if (user && user.placeholder) { + if (user) { userId = user.id; return this.createEmailInviteResponse(userId, agent.id, role); @@ -412,8 +412,6 @@ export default class UserMutations { lastname: user.lastname, oidcSub: user.oidcSub, institutionId: user.institutionId, - placeholder: user.placeholder, - position: user.position, preferredname: user.preferredname, }, roles, diff --git a/apps/backend/src/queries/UserQueries.ts b/apps/backend/src/queries/UserQueries.ts index e6825399c6..eb07afe354 100644 --- a/apps/backend/src/queries/UserQueries.ts +++ b/apps/backend/src/queries/UserQueries.ts @@ -68,9 +68,7 @@ export default class UserQueries { user.preferredname, user.institution, user.institutionId, - user.position, user.created, - user.placeholder, user.email, user.country, user.title @@ -102,9 +100,7 @@ export default class UserQueries { user.preferredname, user.institution, user.institutionId, - user.position, user.created, - user.placeholder, user.email, user.country, user.title diff --git a/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts b/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts index 3091fc96a5..5819adaa59 100644 --- a/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts +++ b/apps/backend/src/resolvers/mutations/UpdateUserMutation.ts @@ -23,36 +23,15 @@ class UpdateUserArgs { @Field(() => String, { nullable: true }) public lastname?: string; - @Field(() => String, { nullable: true }) - public username?: string; - @Field(() => String, { nullable: true }) public preferredname?: string; - @Field(() => String, { nullable: true }) - public gender?: string; - - @Field({ nullable: true }) - public birthdate?: Date; - @Field(() => Int, { nullable: true }) public institutionId?: number; - @Field(() => String, { nullable: true }) - public department?: string; - - @Field(() => String, { nullable: true }) - public position?: string; - @Field(() => String, { nullable: true }) public email?: string; - @Field(() => String, { nullable: true }) - public telephone?: string; - - @Field(() => String, { nullable: true }) - public placeholder?: boolean; - @Field(() => [Int], { nullable: true }) public roles?: number[]; diff --git a/apps/backend/src/resolvers/types/BasicUserDetails.ts b/apps/backend/src/resolvers/types/BasicUserDetails.ts index b05aed8433..a064c44876 100644 --- a/apps/backend/src/resolvers/types/BasicUserDetails.ts +++ b/apps/backend/src/resolvers/types/BasicUserDetails.ts @@ -25,9 +25,6 @@ export class BasicUserDetails implements Partial { @Field(() => Int) public institutionId: number; - @Field() - public position: string; - @Authorized([ Roles.USER_OFFICER, Roles.INSTRUMENT_SCIENTIST, @@ -36,9 +33,6 @@ export class BasicUserDetails implements Partial { @Field({ nullable: true }) public email: string; - @Field(() => Boolean, { nullable: true }) - public placeholder?: boolean; - @Field(() => Date, { nullable: true }) public created?: Date; diff --git a/apps/backend/src/resolvers/types/User.ts b/apps/backend/src/resolvers/types/User.ts index 8b69c0ef6f..75e0836ba1 100644 --- a/apps/backend/src/resolvers/types/User.ts +++ b/apps/backend/src/resolvers/types/User.ts @@ -58,9 +58,6 @@ export class User implements Partial { @Field() public lastname: string; - @Field() - public username: string; - @Field(() => String, { nullable: true }) public preferredname: string | undefined; @@ -70,30 +67,12 @@ export class User implements Partial { @Field(() => String, { nullable: true }) public oauthRefreshToken: string | null; - @Field() - public gender: string; - - @Field() - public birthdate: Date; - @Field(() => Int) public institutionId: number; - @Field() - public department: string; - - @Field() - public position: string; - @Field() public email: string; - @Field() - public telephone: string; - - @Field() - public placeholder: boolean; - @Field() public created: string; diff --git a/apps/backend/src/resolvers/types/UserJWT.ts b/apps/backend/src/resolvers/types/UserJWT.ts index 2af650a870..ddf5cc136b 100644 --- a/apps/backend/src/resolvers/types/UserJWT.ts +++ b/apps/backend/src/resolvers/types/UserJWT.ts @@ -22,15 +22,9 @@ export class UserJWT implements Partial { @Field(() => String, { nullable: true }) public oidcSub: string | null; - @Field() - public placeholder: boolean; - @Field() public created: string; - @Field() - public position: string; - @Field() public institutionId: number; } diff --git a/apps/backend/src/statusActionEngine/statusActionUtils.ts b/apps/backend/src/statusActionEngine/statusActionUtils.ts index fdf1467741..c20f206a40 100644 --- a/apps/backend/src/statusActionEngine/statusActionUtils.ts +++ b/apps/backend/src/statusActionEngine/statusActionUtils.ts @@ -458,9 +458,7 @@ export const getOtherAndFormatOutputForEmailSending = async ( '', '', 0, - '', new Date(), - true, otherEmail, '', '' diff --git a/apps/e2e/cypress/e2e/eventLogs.cy.ts b/apps/e2e/cypress/e2e/eventLogs.cy.ts index cb582afac3..9957284f8e 100644 --- a/apps/e2e/cypress/e2e/eventLogs.cy.ts +++ b/apps/e2e/cypress/e2e/eventLogs.cy.ts @@ -68,13 +68,8 @@ context('Event log tests', () => { firstname: newFirstName, user_title: 'Dr.', lastname: 'Doe', - gender: 'male', - birthdate: new Date('2000/01/01'), institutionId: 1, - department: 'IT', - position: 'Dirrector', email: faker.internet.email(), - telephone: '555-123-4567', }); cy.login('officer'); diff --git a/apps/e2e/cypress/e2e/userAdministration.cy.ts b/apps/e2e/cypress/e2e/userAdministration.cy.ts index db3b91edf8..853cdeb169 100644 --- a/apps/e2e/cypress/e2e/userAdministration.cy.ts +++ b/apps/e2e/cypress/e2e/userAdministration.cy.ts @@ -168,12 +168,7 @@ context('User administration tests', () => { firstname: 'Benjamin', lastname: 'Beckley', preferredname: 'Ben', - gender: 'male', - birthdate: new Date('2000/04/02'), - department: 'IT deparment', - position: 'Management', email: 'ben@inbox.com', - telephone: '(288) 221-4533', }); } cy.login('user2', initialDBData.roles.user); @@ -217,12 +212,7 @@ context('User administration tests', () => { firstname: 'David', lastname: 'Dawson', preferredname: '', - gender: 'male', - birthdate: new Date('1995/04/01'), - department: 'Maxillofacial surgeon', - position: 'Management', email: 'david@teleworm.us', - telephone: '(288) 221-4533', }); } diff --git a/apps/frontend/src/components/user/UpdateUserInformation.tsx b/apps/frontend/src/components/user/UpdateUserInformation.tsx index e2c4cf1bfc..ece0d20439 100644 --- a/apps/frontend/src/components/user/UpdateUserInformation.tsx +++ b/apps/frontend/src/components/user/UpdateUserInformation.tsx @@ -1,31 +1,20 @@ -import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import SwitchAccountOutlinedIcon from '@mui/icons-material/SwitchAccountOutlined'; -import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import Chip from '@mui/material/Chip'; -import FormControl from '@mui/material/FormControl'; import Grid from '@mui/material/Grid'; -import InputLabel from '@mui/material/InputLabel'; -import Link from '@mui/material/Link'; import Stack from '@mui/material/Stack'; -import { useTheme } from '@mui/material/styles'; -import Typography from '@mui/material/Typography'; import { AdapterLuxon as DateAdapter } from '@mui/x-date-pickers/AdapterLuxon'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { updateUserValidationSchema } from '@user-office-software/duo-validation'; import { Field, Form, Formik } from 'formik'; -import { DateTime } from 'luxon'; import React, { useState, useContext } from 'react'; import FormikUIAutocomplete from 'components/common/FormikUIAutocomplete'; -import DatePicker from 'components/common/FormikUIDatePicker'; import Select from 'components/common/FormikUISelect'; import TextField from 'components/common/FormikUITextField'; import ImpersonateButton from 'components/common/ImpersonateButton'; import UOLoader from 'components/common/UOLoader'; import { UserContext } from 'context/UserContextProvider'; -import { SettingsId, UpdateUserMutationVariables } from 'generated/sdk'; -import { useFormattedDateTime } from 'hooks/admin/useFormattedDateTime'; +import { UpdateUserMutationVariables } from 'generated/sdk'; import { useInstitutionsData } from 'hooks/admin/useInstitutionData'; import { useCountries } from 'hooks/user/useCountries'; import { useUserData } from 'hooks/user/useUserData'; @@ -38,42 +27,26 @@ interface UpdateUserInformationProps { export default function UpdateUserInformation( props: UpdateUserInformationProps ) { - const theme = useTheme(); const { user } = useContext(UserContext); const { userData } = useUserData({ userId: props.id }); - const { format } = useFormattedDateTime({ - settingsFormatToUse: SettingsId.DATE_FORMAT, - }); + const { api } = useDataApiWithFeedback(); const countries = useCountries(); const { institutions, loadingInstitutions } = useInstitutionsData(); const [institutionsList, setInstitutionsList] = useState([]); const [countriesList, setCountriesList] = useState([]); - // NOTE: User should be older than 18 years. - const userMaxBirthDate = DateTime.now().minus({ years: 18 }); - if (!userData) { return ; } const initialValues = { - username: userData.username, firstname: userData.firstname, lastname: userData.lastname, preferredname: userData.preferredname || '', - gender: - userData.gender !== 'male' && userData.gender !== 'female' - ? 'other' - : userData.gender, - othergender: userData.gender, - birthdate: DateTime.fromJSDate(new Date(userData.birthdate)), institutionId: userData.institutionId, - department: userData.department, - position: userData.position, oldEmail: userData.email, email: userData.email, - telephone: userData.telephone, user_title: userData.user_title, oidcSub: userData.oidcSub, }; @@ -86,12 +59,6 @@ export default function UpdateUserInformation( { text: 'Rather not say', value: 'unspecified' }, ]; - const genderOptions: Option[] = [ - { text: 'Female', value: 'female' }, - { text: 'Male', value: 'male' }, - { text: 'Other', value: 'other' }, - ]; - if (!institutionsList.length && institutions.length) { setInstitutionsList( institutions.map((institution) => { @@ -123,8 +90,6 @@ export default function UpdateUserInformation( id: props.id, ...values, institutionId: values.institutionId ? +values.institutionId : null, - gender: - values.gender === 'other' ? values.othergender : values.gender, } as UpdateUserMutationVariables; await sendUserUpdate({ @@ -136,25 +101,6 @@ export default function UpdateUserInformation( > {({ isSubmitting, values }) => (
- - User Information - * + *': { - margin: theme.spacing(0.5), - }, - }} - > - {userData.placeholder && ( - } - size="small" - label="Placeholder user" - /> - )} - - @@ -191,75 +137,17 @@ export default function UpdateUserInformation( type="text" data-cy="preferredname" /> - - {values.gender === 'other' && ( - - )} - + {/* Remove gender and birthdate fields */} - - - ORCID iD{' '} - - - - https://orcid.org/{values.oidcSub} - - )} - - - + {/* Remove telephone field */} Date: Tue, 15 Jul 2025 10:34:46 +0200 Subject: [PATCH 07/24] fix for E2E tests --- .../db_seeds/0002_InstrumentScientists.sql | 18 +++++++++--------- .../src/datasources/postgres/UserDataSource.ts | 13 ------------- apps/e2e/cypress/e2e/FAPs.cy.ts | 10 ---------- .../components/user/UpdateUserInformation.tsx | 1 - 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql b/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql index 9e1863998e..ee5415df23 100644 --- a/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql +++ b/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql @@ -3,20 +3,20 @@ $DO$ BEGIN INSERT INTO users( - user_id, user_title, firstname, lastname, username, preferredname, oidc_sub, oauth_refresh_token, gender, birthdate, department, "position", email, - telephone, created_at, updated_at, institution_id, placeholder) - VALUES (100, '', 'Instrument', 'Scientist1', 'instr.sci1', '-', 'instr.sci1.oauthsub', 'dummy-refresh-token', 'e2e user', '2020-10-10', 'IT', '', 'instr.sci1@local.host', '', NOW(), NOW(), 1, false); + user_id, user_title, firstname, lastname, preferredname, oidc_sub, oauth_refresh_token, email, created_at, updated_at, institution_id + ) + VALUES (100, '', 'Instrument', 'Scientist1', '-', 'instr.sci1.oauthsub', 'dummy-refresh-token', 'instr.sci1@local.host', NOW(), NOW(), 1); INSERT INTO users( - user_id, user_title, firstname, lastname, username, preferredname, oidc_sub, oauth_refresh_token, gender, birthdate, department, "position", email, - telephone, created_at, updated_at, institution_id, placeholder) - VALUES (101, '', 'Instrument', 'Scientist2', 'instr.sci2', '-', 'instr.sci2.oauthsub', 'dummy-refresh-token', 'e2e user', '2020-10-10', 'IT', '', 'instr.sci2@local.host', '', NOW(), NOW(), 1, false); + user_id, user_title, firstname, lastname, preferredname, oidc_sub, oauth_refresh_token, email, created_at, updated_at, institution_id + ) + VALUES (101, '', 'Instrument', 'Scientist2', '-', 'instr.sci2.oauthsub', 'dummy-refresh-token', 'instr.sci2@local.host', NOW(), NOW(), 1); -- user account with every roles except instrument scientist and user officer INSERT INTO users( - user_id, user_title, firstname, lastname, username, preferredname, oidc_sub, oauth_refresh_token, gender, birthdate, department, "position", email, - telephone, created_at, updated_at, institution_id, placeholder) - VALUES (102, '', 'Not', 'Scientist', 'not.instr.sci', '-', 'not.instr.sci.oauthsub', 'dummy-refresh-token', 'e2e user', '2020-10-10', 'IT', '', 'not.instr.sci@local.host', '', NOW(), NOW(), 1, false); + user_id, user_title, firstname, lastname, preferredname, oidc_sub, oauth_refresh_token, email, created_at, updated_at, institution_id + ) + VALUES (102, '', 'Not', 'Scientist', '-', 'not.instr.sci.oauthsub', 'dummy-refresh-token', 'not.instr.sci@local.host', NOW(), NOW(), 1); -- user account with experiment safety reviewer INSERT INTO users( diff --git a/apps/backend/src/datasources/postgres/UserDataSource.ts b/apps/backend/src/datasources/postgres/UserDataSource.ts index f34cd546c3..5f24b84ad2 100644 --- a/apps/backend/src/datasources/postgres/UserDataSource.ts +++ b/apps/backend/src/datasources/postgres/UserDataSource.ts @@ -149,19 +149,12 @@ export default class PostgresUserDataSource implements UserDataSource { user_title: '', firstname, lastname, - username: email, preferredname: firstname, oidc_sub: '', oauth_refresh_token: '', oauth_issuer: '', - gender: '', - birthdate: '2000-01-01', institution_id: 1, - department: '', - position: '', email, - telephone: '', - placeholder: true, }) .returning(['*']) .into('users') @@ -422,17 +415,11 @@ export default class PostgresUserDataSource implements UserDataSource { user_title: '', firstname: '', lastname: '', - username: userId.toString(), preferredname: '', oidc_sub: '', oauth_refresh_token: '', - gender: '', - birthdate: '2000-01-01', institution_id: 1, - department: '', - position: '', email: userId.toString(), - telephone: '', }; } diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index d8c6b7f376..50746e002b 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -1251,13 +1251,8 @@ context('Fap reviews tests', () => { cy.updateUserDetails({ ...loggedInUserParsed, institutionId: 1, - telephone: faker.phone.number('+4670#######'), user_title: 'Dr.', - gender: 'male', nationality: 1, - birthdate: new Date('2000/01/01'), - department: 'IT', - position: 'Dirrector', } as UpdateUserMutationVariables); } cy.visit(`/FapPage/${createdFapId}?tab=3`); @@ -1408,12 +1403,7 @@ context('Fap reviews tests', () => { cy.updateUserDetails({ ...loggedInUserParsed, institutionId: 1, - telephone: faker.phone.number('+4670#######'), user_title: 'Dr.', - gender: 'male', - birthdate: new Date('2000/01/01'), - department: 'IT', - position: 'Dirrector', } as UpdateUserMutationVariables); } diff --git a/apps/frontend/src/components/user/UpdateUserInformation.tsx b/apps/frontend/src/components/user/UpdateUserInformation.tsx index ece0d20439..1c24ea2061 100644 --- a/apps/frontend/src/components/user/UpdateUserInformation.tsx +++ b/apps/frontend/src/components/user/UpdateUserInformation.tsx @@ -137,7 +137,6 @@ export default function UpdateUserInformation( type="text" data-cy="preferredname" /> - {/* Remove gender and birthdate fields */} From 40bc957d4cf4eca8ace5eed3fa1803a7ca58059d Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Tue, 15 Jul 2025 11:18:55 +0200 Subject: [PATCH 08/24] clean up tests from legacy fields --- apps/backend/src/mutations/UserMutations.spec.ts | 3 --- apps/e2e/cypress/e2e/personalInformation.cy.ts | 15 --------------- apps/e2e/cypress/e2e/userAdministration.cy.ts | 15 --------------- 3 files changed, 33 deletions(-) diff --git a/apps/backend/src/mutations/UserMutations.spec.ts b/apps/backend/src/mutations/UserMutations.spec.ts index b6ce1696ab..5335394441 100644 --- a/apps/backend/src/mutations/UserMutations.spec.ts +++ b/apps/backend/src/mutations/UserMutations.spec.ts @@ -270,7 +270,6 @@ describe('updateUserByOidcSub', () => { expect(result).toEqual({ ...dummyUser, firstname: 'OfficerUpdatedJane', - department: 'Updated Department', }); }); test('A user cannot update another user by OIDC sub', async () => { @@ -308,8 +307,6 @@ describe('updateUserByOidcSub', () => { expect(result).toEqual({ ...dummyUser, - telephone: '+1-555-9999', - position: 'Senior Architect', }); }); diff --git a/apps/e2e/cypress/e2e/personalInformation.cy.ts b/apps/e2e/cypress/e2e/personalInformation.cy.ts index 92f5e63d3b..c243f09c08 100644 --- a/apps/e2e/cypress/e2e/personalInformation.cy.ts +++ b/apps/e2e/cypress/e2e/personalInformation.cy.ts @@ -12,12 +12,9 @@ context('Personal information tests', () => { const newFirstName = faker.name.firstName(); const newLastName = faker.name.lastName(); - const newDepartment = faker.commerce.department(); const otherOrg = faker.commerce.department(); const otherInstitution = 'Other'; const newPreferredName = faker.hacker.noun(); - const newPosition = faker.random.word().split(' ')[0]; - const newTelephone = faker.phone.number('0##########'); it('Should be able to see user officer role in use', () => { if (featureFlags.getEnabledFeatures().get(FeatureId.USER_MANAGEMENT)) { @@ -65,12 +62,6 @@ context('Personal information tests', () => { cy.get("[name='preferredname']").clear().type(newPreferredName); - cy.get("[name='position']").clear().type(newPosition); - - cy.get("[name='department']").clear().type(newDepartment); - - cy.get("[name='telephone']").clear().type(newTelephone); - cy.get("[name='institutionId']").clear().type(otherInstitution); cy.get('[data-cy="institution-options"] li') @@ -92,12 +83,6 @@ context('Personal information tests', () => { cy.get("[name='preferredname']") .invoke('val') .should('eq', newPreferredName); - - cy.get("[name='position']").invoke('val').should('eq', newPosition); - - cy.get("[name='department']").invoke('val').should('eq', newDepartment); - - cy.get("[name='telephone']").invoke('val').should('eq', newTelephone); }); it('User Officer should be able to see all and change roles if there are multiple', () => { diff --git a/apps/e2e/cypress/e2e/userAdministration.cy.ts b/apps/e2e/cypress/e2e/userAdministration.cy.ts index 853cdeb169..f9ff5fbbea 100644 --- a/apps/e2e/cypress/e2e/userAdministration.cy.ts +++ b/apps/e2e/cypress/e2e/userAdministration.cy.ts @@ -7,10 +7,7 @@ import initialDBData from '../support/initialDBData'; context('User administration tests', () => { const newFirstName = faker.name.firstName(); const newLastName = faker.name.lastName(); - const newDepartment = faker.commerce.department(); const newPrefferedName = faker.hacker.noun(); - const newPosition = faker.random.word().split(' ')[0]; - const newTelephone = faker.phone.number('0##########'); const newInstitution = faker.company.name(); const placeholderUser = initialDBData.users.placeholderUser; const title = faker.lorem.words(2); @@ -38,12 +35,6 @@ context('User administration tests', () => { cy.get("[name='preferredname']").clear().type(newPrefferedName); - cy.get("[name='position']").clear().type(newPosition); - - cy.get("[name='department']").clear().type(newDepartment); - - cy.get("[name='telephone']").clear().type(newTelephone); - cy.get("[name='otherInstitution']").clear().type(newInstitution); cy.contains('Update Profile').click(); @@ -59,12 +50,6 @@ context('User administration tests', () => { cy.get("[name='preferredname']") .invoke('val') .should('eq', newPrefferedName); - - cy.get("[name='position']").invoke('val').should('eq', newPosition); - - cy.get("[name='department']").invoke('val').should('eq', newDepartment); - - cy.get("[name='telephone']").invoke('val').should('eq', newTelephone); }); it('Should be able to invite user or fap reviewer by email', function () { From bcac6949b78e9e7645038aebb9a0384c92e60d4d Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Tue, 28 Oct 2025 14:32:24 +0100 Subject: [PATCH 09/24] Update apps/frontend/src/components/user/UpdateUserInformation.tsx Co-authored-by: Jekabs Karklins <58165815+jekabs-karklins@users.noreply.github.com> --- apps/frontend/src/components/user/UpdateUserInformation.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/frontend/src/components/user/UpdateUserInformation.tsx b/apps/frontend/src/components/user/UpdateUserInformation.tsx index 1c24ea2061..61b8a29c6a 100644 --- a/apps/frontend/src/components/user/UpdateUserInformation.tsx +++ b/apps/frontend/src/components/user/UpdateUserInformation.tsx @@ -179,7 +179,6 @@ export default function UpdateUserInformation( type="email" data-cy="email" /> - {/* Remove telephone field */} Date: Fri, 7 Nov 2025 09:31:28 +0100 Subject: [PATCH 10/24] Remove old redeemCodes functionality --- .../src/components/user/InviteUserForm.tsx | 112 ----------------- .../src/components/user/PeopleTable.tsx | 116 +----------------- .../components/user/ProposalsPeopleTable.tsx | 27 ---- 3 files changed, 1 insertion(+), 254 deletions(-) delete mode 100644 apps/frontend/src/components/user/InviteUserForm.tsx diff --git a/apps/frontend/src/components/user/InviteUserForm.tsx b/apps/frontend/src/components/user/InviteUserForm.tsx deleted file mode 100644 index 28b6fac136..0000000000 --- a/apps/frontend/src/components/user/InviteUserForm.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; -import { createUserByEmailInviteValidationSchema } from '@user-office-software/duo-validation/lib/User'; -import { Field, Form, Formik } from 'formik'; -import React from 'react'; - -import TextField from 'components/common/FormikUITextField'; -import { BasicUserDetails, UserRole } from 'generated/sdk'; -import useDataApiWithFeedback from 'utils/useDataApiWithFeedback'; -import { FunctionType } from 'utils/utilTypes'; - -type InviteUserFormProps = { - action: FunctionType; - title: string; - userRole: UserRole; - close: FunctionType; -}; - -const InviteUserForm = ({ - action, - title, - userRole, - close, -}: InviteUserFormProps) => { - const { api } = useDataApiWithFeedback(); - - return ( - => { - const { createUserByEmailInvite: createdUserId } = await api({ - toastSuccessMessage: 'Invitation sent successfully!', - }).createUserByEmailInvite({ - firstname: values.firstname, - lastname: values.lastname, - email: values.email, - userRole: userRole, - }); - if (createdUserId) { - action({ - id: createdUserId, - firstname: values.firstname, - lastname: values.lastname, - institution: '', - } as BasicUserDetails); - close(); - } - }} - validationSchema={createUserByEmailInviteValidationSchema(UserRole)} - > - {() => ( - - - {title} - - - - - - - - - - - )} - - ); -}; - -export default InviteUserForm; diff --git a/apps/frontend/src/components/user/PeopleTable.tsx b/apps/frontend/src/components/user/PeopleTable.tsx index 2e5379fc46..f35a3f6da6 100644 --- a/apps/frontend/src/components/user/PeopleTable.tsx +++ b/apps/frontend/src/components/user/PeopleTable.tsx @@ -5,16 +5,11 @@ import MaterialTableCore, { Query, QueryResult, } from '@material-table/core'; -import Email from '@mui/icons-material/Email'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; import Typography from '@mui/material/Typography'; import { Formik } from 'formik'; -import { TFunction } from 'i18next'; import React, { useState, useEffect, useContext } from 'react'; -import { useTranslation } from 'react-i18next'; import { ActionButtonContainer } from 'components/common/ActionButtonContainer'; import MaterialTable from 'components/common/DenseMaterialTable'; @@ -35,8 +30,6 @@ import { useDataApi } from 'hooks/common/useDataApi'; import { tableIcons } from 'utils/materialIcons'; import { FunctionType } from 'utils/utilTypes'; -import InviteUserForm from './InviteUserForm'; - type InvitationButtonProps = { title: string; action: FunctionType; @@ -88,27 +81,6 @@ const localColumns = [ { title: 'Institution', field: 'institution' }, ]; -const getTitle = ({ - t, - invitationUserRole, -}: { - t: TFunction<'translation', undefined>; - invitationUserRole?: UserRole; -}): string => { - switch (invitationUserRole) { - case UserRole.USER_OFFICER: - return 'Invite User'; - case UserRole.FAP_CHAIR: - return 'Invite ' + t('Fap') + ' Chair'; - case UserRole.FAP_SECRETARY: - return 'Invite ' + t('Fap') + ' Secretary'; - case UserRole.INSTRUMENT_SCIENTIST: - return 'Invite ' + t('instrumentSci'); - default: - return 'Invite User'; - } -}; - async function getUserByEmail( email: string, api: () => ReturnType @@ -165,9 +137,7 @@ const PeopleTable = ({ userRole, data, action, - emailInvite, emailSearch, - invitationUserRole, isFreeAction, showInvitationButtons, columns, @@ -186,24 +156,15 @@ const PeopleTable = ({ userRole: userRole ? userRole : null, }); const featureContext = useContext(FeatureContext); - const isEmailInviteEnabled = !!featureContext.featuresMap.get( - FeatureId.EMAIL_INVITE - )?.isEnabled; const isEmailSearchEnabled = !!featureContext.featuresMap.get( FeatureId.EMAIL_SEARCH )?.isEnabled; const api = useDataApi(); - const [sendUserEmail, setSendUserEmail] = useState(false); - const [inviteUserModal, setInviteUserModal] = useState({ - show: false, - title: '', - userRole: UserRole.USER, - }); + const [currentPageIds, setCurrentPageIds] = useState([]); const [invitedUsers, setInvitedUsers] = useState([]); const [tableEmails, setTableEmails] = useState([]); - const { t } = useTranslation(); const tableRef = React.createRef>(); @@ -217,18 +178,6 @@ const PeopleTable = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [data?.length]); - if (sendUserEmail && invitationUserRole && action) { - return ( - setSendUserEmail(false)} - userRole={invitationUserRole} - /> - ); - } - const EmailIcon = (): JSX.Element => ; - const handleChangeCoIToPi = (user: BasicUserDetails) => { onRemove?.(user); setPrincipalInvestigator?.(user); @@ -246,14 +195,6 @@ const PeopleTable = ({ rowData: BasicUserDetails | BasicUserDetails[] ) => action.fn(rowData), }); - emailInvite && - isEmailInviteEnabled && - actionArray.push({ - icon: EmailIcon, - isFreeAction: true, - tooltip: 'Add by email', - onClick: () => setSendUserEmail(true), - }); setPrincipalInvestigator && onRemove && @@ -280,31 +221,6 @@ const PeopleTable = ({ const invitationButtons: InvitationButtonProps[] = []; - if (showInvitationButtons) { - invitationButtons.push( - { - title: 'Invite User', - action: () => - setInviteUserModal({ - show: true, - title: 'Invite User', - userRole: UserRole.USER, - }), - 'data-cy': 'invite-user-button', - }, - { - title: 'Invite Reviewer', - action: () => - setInviteUserModal({ - show: true, - title: 'Invite Reviewer', - userRole: UserRole.FAP_REVIEWER, - }), - 'data-cy': 'invite-reviewer-button', - } - ); - } - const handleColumnSelectionChange = ( selectedItems: BasicUserDetailsWithRole[], selectedItem: BasicUserDetailsWithRole | undefined @@ -477,36 +393,6 @@ const PeopleTable = ({ }, }} > - - setInviteUserModal({ - ...inviteUserModal, - show: false, - }) - } - style={{ backdropFilter: 'blur(6px)' }} - > - - - setInviteUserModal({ - ...inviteUserModal, - show: false, - }) - } - action={(invitedUser) => { - if (invitedUser) { - tableRef.current && tableRef.current.onQueryChange({}); - } - }} - /> - - ([]); const [invitedUsers, setInvitedUsers] = useState([]); const [displayError, setDisplayError] = useState(false); const [tableEmails, setTableEmails] = useState([]); - if (sendUserEmail && invitationUserRole && action) { - return ( - setSendUserEmail(false)} - userRole={invitationUserRole} - /> - ); - } - const EmailIcon = (): JSX.Element => ; - // Typescript doesn't like this not being typed explicitly const actionArray: | ( @@ -197,15 +179,6 @@ const ProposalsPeopleTable = ({ ) => action.fn(rowData), }); - emailInvite && - isEmailInviteEnabled && - actionArray.push({ - icon: EmailIcon, - isFreeAction: true, - tooltip: 'Add by email', - onClick: () => setSendUserEmail(true), - }); - function selectedParticipantsChanged( selectedItems: (BasicUserDetails & { tableData: { From 24d53b5826aa7db9bd7eae2390e65787faeb57a4 Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 7 Nov 2025 10:09:39 +0100 Subject: [PATCH 11/24] remove unused graphql call for email invite --- .../graphql/user/createUserByEmailInvite.graphql | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 apps/frontend/src/graphql/user/createUserByEmailInvite.graphql diff --git a/apps/frontend/src/graphql/user/createUserByEmailInvite.graphql b/apps/frontend/src/graphql/user/createUserByEmailInvite.graphql deleted file mode 100644 index 2b1348e493..0000000000 --- a/apps/frontend/src/graphql/user/createUserByEmailInvite.graphql +++ /dev/null @@ -1,13 +0,0 @@ -mutation createUserByEmailInvite( - $firstname: String! - $lastname: String! - $email: String! - $userRole: UserRole! -) { - createUserByEmailInvite( - firstname: $firstname - lastname: $lastname - email: $email - userRole: $userRole - ) -} From 80aba85b487af9c9d1866e245bb88f312a53dc2d Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 7 Nov 2025 11:26:43 +0100 Subject: [PATCH 12/24] Fix E2E test after removing email invite --- apps/e2e/cypress/e2e/peopleTable.cy.ts | 30 +------------------------- apps/e2e/cypress/support/user.ts | 12 ----------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/apps/e2e/cypress/e2e/peopleTable.cy.ts b/apps/e2e/cypress/e2e/peopleTable.cy.ts index c882e0a42b..8ade2296eb 100644 --- a/apps/e2e/cypress/e2e/peopleTable.cy.ts +++ b/apps/e2e/cypress/e2e/peopleTable.cy.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker'; -import { FeatureId, UserRole } from '@user-office-software-libs/shared-types'; +import { FeatureId } from '@user-office-software-libs/shared-types'; import featureFlags from '../support/featureFlags'; import initialDBData from '../support/initialDBData'; @@ -173,16 +173,6 @@ context('PageTable component tests', () => { }); it('should preserve the selected users after pagination', () => { - // NOTE: Create 5 users - new Array(5).fill(0).map((elem, index) => { - cy.createUserByEmailInvite({ - firstname: faker.name.firstName(), - lastname: faker.name.lastName(), - email: emails[index], - userRole: UserRole.USER, - }); - }); - cy.login('user1'); cy.visit('/'); @@ -344,15 +334,6 @@ context('PageTable component tests', () => { }); it('Should preserve the selected users after pagination', () => { - // NOTE: Create 10 users - new Array(10).fill(0).map((elem, index) => { - cy.createUserByEmailInvite({ - firstname: faker.name.firstName(), - lastname: faker.name.lastName(), - email: emails[index], - userRole: UserRole.USER, - }); - }); cy.login('officer'); cy.visit('/'); @@ -408,15 +389,6 @@ context('PageTable component tests', () => { describe('PeopleTable sort', () => { it('PeopleTable should sort all people when using column sort', () => { - // NOTE: Create 5 users - new Array(5).fill(0).map((elem, index) => { - cy.createUserByEmailInvite({ - firstname: faker.name.firstName(), - lastname: faker.name.lastName(), - email: emails[index], - userRole: UserRole.USER, - }); - }); let firstTableRowTextBeforeSorting: string; let firstTableRowTextAfterSorting: string; cy.login('officer'); diff --git a/apps/e2e/cypress/support/user.ts b/apps/e2e/cypress/support/user.ts index 583c884694..34b12855b1 100644 --- a/apps/e2e/cypress/support/user.ts +++ b/apps/e2e/cypress/support/user.ts @@ -1,6 +1,4 @@ import { - CreateUserByEmailInviteMutation, - CreateUserByEmailInviteMutationVariables, ExternalTokenLoginMutation, FeatureId, GetFeaturesQuery, @@ -212,15 +210,6 @@ const logout = () => { cy.visit('/'); }; -const createUserByEmailInvite = ( - createUserByEmailInviteInput: CreateUserByEmailInviteMutationVariables -): Cypress.Chainable => { - const api = getE2EApi(); - const request = api.createUserByEmailInvite(createUserByEmailInviteInput); - - return cy.wrap(request); -}; - function updateUserDetails( updateUserInput: UpdateUserMutationVariables ): Cypress.Chainable { @@ -242,7 +231,6 @@ function updateUserRoles( Cypress.Commands.add('login', login); Cypress.Commands.add('logout', logout); -Cypress.Commands.add('createUserByEmailInvite', createUserByEmailInvite); Cypress.Commands.add('updateUserRoles', updateUserRoles); Cypress.Commands.add('updateUserDetails', updateUserDetails); From 94c7b36cd49c4e29b22c3ed405cf65ab03cb9146 Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 7 Nov 2025 11:44:18 +0100 Subject: [PATCH 13/24] fix seeds by removing unused parameters --- .../db_patches/db_seeds/0002_InstrumentScientists.sql | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql b/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql index ee5415df23..0bbcac825c 100644 --- a/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql +++ b/apps/backend/db_patches/db_seeds/0002_InstrumentScientists.sql @@ -20,9 +20,8 @@ BEGIN -- user account with experiment safety reviewer INSERT INTO users( - user_id, user_title, firstname, lastname, username, preferredname, oidc_sub, oauth_refresh_token, gender, birthdate, department, "position", email, - telephone, created_at, updated_at, institution_id, placeholder) - VALUES (103, '', 'Experiment', 'Safety', 'experiment.safety', '-', 'experiment.safety.oauthsub', 'dummy-refresh-token', 'e2e user', '2020-10-10', 'IT', '', 'experiment.safety@local.host', '', NOW(), NOW(), 1, false); + user_id, user_title, firstname, lastname, preferredname, oidc_sub, oauth_refresh_token, email, created_at, updated_at, institution_id) + VALUES (103, '', 'Experiment', 'Safety', 'experiment.safety', 'experiment.safety.oauthsub', 'dummy-refresh-token', 'experiment.safety@local.host', NOW(), NOW(), 1); INSERT INTO role_user( role_id, user_id) From 9aa98cbf22aef6ffadf9a00a3d6e96a9f5e51ef3 Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 7 Nov 2025 11:53:06 +0100 Subject: [PATCH 14/24] remove seed file for redeem codes --- .../db_patches/db_seeds/0006_RedeemCodes.sql | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 apps/backend/db_patches/db_seeds/0006_RedeemCodes.sql diff --git a/apps/backend/db_patches/db_seeds/0006_RedeemCodes.sql b/apps/backend/db_patches/db_seeds/0006_RedeemCodes.sql deleted file mode 100644 index 6b484afd70..0000000000 --- a/apps/backend/db_patches/db_seeds/0006_RedeemCodes.sql +++ /dev/null @@ -1,13 +0,0 @@ -DO -$DO$ -BEGIN - - INSERT INTO proposal_user( proposal_pk, user_id ) - VALUES ( 1, 5 ); - - INSERT INTO redeem_codes( code, placeholder_user_id, created_by ) - VALUES ( '123abc', 5, 1); - -END; -$DO$ -LANGUAGE plpgsql; \ No newline at end of file From a939f89701ab9c083052e54af68256e6c022c223 Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 7 Nov 2025 13:19:07 +0100 Subject: [PATCH 15/24] fix E2E tests --- apps/e2e/cypress/e2e/invites.cy.ts | 46 ----------- apps/e2e/cypress/e2e/peopleTable.cy.ts | 41 ---------- apps/e2e/cypress/e2e/userAdministration.cy.ts | 79 ------------------- 3 files changed, 166 deletions(-) diff --git a/apps/e2e/cypress/e2e/invites.cy.ts b/apps/e2e/cypress/e2e/invites.cy.ts index ad0402633f..3df3aab31a 100644 --- a/apps/e2e/cypress/e2e/invites.cy.ts +++ b/apps/e2e/cypress/e2e/invites.cy.ts @@ -427,51 +427,5 @@ context('Invites tests', () => { 'not.exist' ); }); - - it('Should not be able to add duplicate co-proposer in modal', () => { - const lastName = initialDBData.users.user2.lastName; - const email = initialDBData.users.user2.email; - - cy.get('[data-cy="add-participant-button"]').click(); - - cy.get('[data-cy="invite-user-autocomplete"]').type(email); - cy.get('[role=presentation]').contains(lastName).click(); - - cy.get('[data-cy="invite-user-autocomplete"]').type(email); - cy.get('[role=presentation]') - .contains(`No results found for "${email}"`) - .should('exist'); - }); - - it('Should not be able to add duplicate co-proposer already on proposal', () => { - const lastName = initialDBData.users.user2.lastName; - const email = initialDBData.users.user2.email; - - cy.get('[data-cy="add-participant-button"]').click(); - - cy.get('[data-cy="invite-user-autocomplete"]').type(email); - cy.get('[role=presentation]').contains(lastName).click(); - cy.get('[data-cy="invite-user-submit-button"]') - .should('be.enabled') - .click(); - - cy.get('[data-cy="add-participant-button"]').click({ force: true }); - - cy.get('[data-cy="invite-user-autocomplete"]').type(email); - cy.get('[role=presentation]') - .contains(`No results found for "${email}"`) - .should('exist'); - }); - - it('Should not be able to add duplicate co-proposer when co-proposer is PI', () => { - const email = initialDBData.users.user1.email; - - cy.get('[data-cy="add-participant-button"]').click(); - - cy.get('[data-cy="invite-user-autocomplete"]').type(email); - cy.get('[role=presentation]') - .contains(`No results found for "${email}"`) - .should('exist'); - }); }); }); diff --git a/apps/e2e/cypress/e2e/peopleTable.cy.ts b/apps/e2e/cypress/e2e/peopleTable.cy.ts index 8ade2296eb..f9fe4351aa 100644 --- a/apps/e2e/cypress/e2e/peopleTable.cy.ts +++ b/apps/e2e/cypress/e2e/peopleTable.cy.ts @@ -451,47 +451,6 @@ context('PageTable component tests', () => { }); }); - describe('ProposalPeopleTable disallows duplicate co-proposers', () => { - it('Should not be able to add duplicate co-proposer', () => { - cy.login('user1'); - cy.visit('/'); - - cy.contains('New Proposal').click(); - cy.get('[data-cy=call-list]').find('li:first-child').click(); - - cy.get('[data-cy=add-participant-button]').click(); - - cy.get('[data-cy=email]').type(initialDBData.users.user2.email); - cy.get('[data-cy=findUser]').click(); - cy.finishedLoading(); - - cy.get('[data-cy=assign-selected-users]').click(); - - cy.get('[data-cy=title] input').type(title); - cy.get('[data-cy=abstract] textarea').first().type(abstract); - - cy.contains('Save and continue').click(); - - cy.finishedLoading(); - - cy.contains('Submit').click(); - cy.get('[data-cy=confirm-ok]').click(); - - cy.contains('Dashboard').click(); - cy.contains('New Proposal').click(); - cy.get('[data-cy=call-list]').find('li:first-child').click(); - - cy.get('[data-cy=add-participant-button]').click(); - - cy.get('[aria-label="Select All Rows"]').click(); - cy.get('[data-cy=email]').type(initialDBData.users.user2.email); - cy.get('[data-cy=findUser]').click(); - - cy.contains('User is already selected').should('exist'); - cy.contains('1 user(s) selected').should('exist'); - }); - }); - describe('PeopleTable component allows swapping between PIs and CoIs', () => { it('Should allow user to swap between PIs and CoIs', () => { cy.login('user1'); diff --git a/apps/e2e/cypress/e2e/userAdministration.cy.ts b/apps/e2e/cypress/e2e/userAdministration.cy.ts index f9ff5fbbea..4fc8486362 100644 --- a/apps/e2e/cypress/e2e/userAdministration.cy.ts +++ b/apps/e2e/cypress/e2e/userAdministration.cy.ts @@ -52,85 +52,6 @@ context('User administration tests', () => { .should('eq', newPrefferedName); }); - it('Should be able to invite user or fap reviewer by email', function () { - if (!featureFlags.getEnabledFeatures().get(FeatureId.USER_MANAGEMENT)) { - this.skip(); - } - const userFirstName = faker.name.firstName(); - const userLastName = faker.name.lastName(); - const userEmail = faker.internet.email(); - const reviewerFirstName = faker.name.firstName(); - const reviewerLastName = faker.name.lastName(); - const reviewerEmail = faker.internet.email(); - cy.contains('People').click(); - - cy.get('[data-cy="invite-user-button"]').click(); - - cy.get('[data-cy="firstname"] input').clear().type(userFirstName); - cy.get('[data-cy="lastname"] input').clear().type(userLastName); - cy.get('[data-cy="email"] input').clear().type(userEmail); - - cy.get('[data-cy="invitation-submit"]').click(); - - cy.notification({ - variant: 'success', - text: 'Invitation sent successfully', - }); - - cy.get('[data-cy="co-proposers"]').contains(userFirstName); - cy.get('[data-cy="co-proposers"]') - .contains(userLastName) - .parent() - .find('[aria-label="Edit user"]') - .click(); - - cy.finishedLoading(); - - cy.get('[name="email"]').should('have.value', userEmail); - - cy.get('[role="tablist"]').contains('Settings').click(); - - cy.finishedLoading(); - - cy.get('[data-cy="user-roles-table"] table tbody tr') - .first() - .contains('User'); - - cy.contains('People').click(); - - cy.get('[data-cy="invite-reviewer-button"]').click(); - - cy.get('[data-cy="firstname"] input').clear().type(reviewerFirstName); - cy.get('[data-cy="lastname"] input').clear().type(reviewerLastName); - cy.get('[data-cy="email"] input').clear().type(reviewerEmail); - - cy.get('[data-cy="invitation-submit"]').click(); - - cy.notification({ - variant: 'success', - text: 'Invitation sent successfully', - }); - - cy.get('[data-cy="co-proposers"]').contains(reviewerFirstName); - cy.get('[data-cy="co-proposers"]') - .contains(reviewerLastName) - .parent() - .find('[aria-label="Edit user"]') - .click(); - - cy.finishedLoading(); - - cy.get('[name="email"]').should('have.value', reviewerEmail); - - cy.get('[role="tablist"]').contains('Settings').click(); - - cy.finishedLoading(); - - cy.get('[data-cy="user-roles-table"] table tbody tr') - .first() - .contains('FAP Reviewer'); - }); - it('Should be able to delete user information', function () { if (!featureFlags.getEnabledFeatures().get(FeatureId.USER_MANAGEMENT)) { this.skip(); From 65116094b0ec808d850222c9cdd05813a7baaf2a Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 7 Nov 2025 16:38:02 +0100 Subject: [PATCH 16/24] update validation package --- apps/backend/package-lock.json | 15 +++++++-------- apps/backend/package.json | 2 +- apps/frontend/package-lock.json | 14 +++++++------- apps/frontend/package.json | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/apps/backend/package-lock.json b/apps/backend/package-lock.json index 9fbb583133..e46f9de69e 100644 --- a/apps/backend/package-lock.json +++ b/apps/backend/package-lock.json @@ -25,7 +25,7 @@ "@user-office-software/duo-localisation": "^1.2.0", "@user-office-software/duo-logger": "^2.3.2", "@user-office-software/duo-message-broker": "^1.8.0", - "@user-office-software/duo-validation": "^5.1.20", + "@user-office-software/duo-validation": "^5.1.22", "@user-office-software/openid": "^1.4.0", "await-to-js": "^2.1.1", "bcryptjs": "^2.4.3", @@ -2251,10 +2251,9 @@ } }, "node_modules/@messageformat/runtime": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.2.tgz", - "integrity": "sha512-dkIPDCjXcfhSHgNE1/qV6TeczQZR59Yx0xXeafVKgK3QVWoxc38ljwpksUpnzCGvN151KUbCJTDZVmahtf1YZw==", - "license": "MIT", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz", + "integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==", "dependencies": { "make-plural": "^7.0.0" } @@ -3851,9 +3850,9 @@ } }, "node_modules/@user-office-software/duo-validation": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@user-office-software/duo-validation/-/duo-validation-5.1.20.tgz", - "integrity": "sha512-Rm+vP2w413rj3NUV1jn8N7VYoVR0EhsWsGpoZVh1FLbGBvdOvfZ05+cY9jY56DDukBShndT9R95bPTBlfx7cFg==", + "version": "5.1.22", + "resolved": "https://registry.npmjs.org/@user-office-software/duo-validation/-/duo-validation-5.1.22.tgz", + "integrity": "sha512-MuDTz4Wbf0XRxhPznUbVpH3WRph4fH6YHUDxp6IknL6BE0/JimEpf9ScPeGWWLNMqMARGs2PAVY0l7LmMqNhyA==", "license": "ISC", "dependencies": { "luxon": "^2.5.0", diff --git a/apps/backend/package.json b/apps/backend/package.json index a4e155571e..706c0136d6 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -47,7 +47,7 @@ "@user-office-software/duo-localisation": "^1.2.0", "@user-office-software/duo-logger": "^2.3.2", "@user-office-software/duo-message-broker": "^1.8.0", - "@user-office-software/duo-validation": "^5.1.20", + "@user-office-software/duo-validation": "^5.1.22", "@user-office-software/openid": "^1.4.0", "await-to-js": "^2.1.1", "bcryptjs": "^2.4.3", diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json index 259c021ab6..8a6d61c9c3 100644 --- a/apps/frontend/package-lock.json +++ b/apps/frontend/package-lock.json @@ -26,7 +26,7 @@ "@types/yup": "^0.29.13", "@uiw/react-codemirror": "^4.22.1", "@user-office-software/duo-localisation": "^1.2.0", - "@user-office-software/duo-validation": "5.1.19", + "@user-office-software/duo-validation": "5.1.22", "formik": "^2.4.6", "graphql": "^16.8.1", "graphql-request": "^6.1.0", @@ -5099,9 +5099,9 @@ "integrity": "sha512-9axYsEtg5rr7FDibUTIaGxd1+sRpvreytLuhEQBZdzA+MXL5TxZcDGAqSQarrTklHP4mcJTWvDzyMV/yCKmYOw==" }, "node_modules/@user-office-software/duo-validation": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@user-office-software/duo-validation/-/duo-validation-5.1.19.tgz", - "integrity": "sha512-Lt5uRHrtLbJvs79A2oESebzIKn1KaW+sAKD5vlio7y5YM/13NoyTixYFTqBhuK5Gs8xDT6gaGcVf0VKESQ5mjQ==", + "version": "5.1.22", + "resolved": "https://registry.npmjs.org/@user-office-software/duo-validation/-/duo-validation-5.1.22.tgz", + "integrity": "sha512-MuDTz4Wbf0XRxhPznUbVpH3WRph4fH6YHUDxp6IknL6BE0/JimEpf9ScPeGWWLNMqMARGs2PAVY0l7LmMqNhyA==", "license": "ISC", "dependencies": { "luxon": "^2.5.0", @@ -15196,9 +15196,9 @@ "integrity": "sha512-9axYsEtg5rr7FDibUTIaGxd1+sRpvreytLuhEQBZdzA+MXL5TxZcDGAqSQarrTklHP4mcJTWvDzyMV/yCKmYOw==" }, "@user-office-software/duo-validation": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@user-office-software/duo-validation/-/duo-validation-5.1.19.tgz", - "integrity": "sha512-Lt5uRHrtLbJvs79A2oESebzIKn1KaW+sAKD5vlio7y5YM/13NoyTixYFTqBhuK5Gs8xDT6gaGcVf0VKESQ5mjQ==", + "version": "5.1.22", + "resolved": "https://registry.npmjs.org/@user-office-software/duo-validation/-/duo-validation-5.1.22.tgz", + "integrity": "sha512-MuDTz4Wbf0XRxhPznUbVpH3WRph4fH6YHUDxp6IknL6BE0/JimEpf9ScPeGWWLNMqMARGs2PAVY0l7LmMqNhyA==", "requires": { "luxon": "^2.5.0", "sanitize-html": "^2.7.1", diff --git a/apps/frontend/package.json b/apps/frontend/package.json index abfc28d3cb..61eda8f445 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -26,7 +26,7 @@ "@types/yup": "^0.29.13", "@uiw/react-codemirror": "^4.22.1", "@user-office-software/duo-localisation": "^1.2.0", - "@user-office-software/duo-validation": "5.1.19", + "@user-office-software/duo-validation": "5.1.22", "formik": "^2.4.6", "graphql": "^16.8.1", "graphql-request": "^6.1.0", From d1f92a65a7cbc2f4fd896363aa04fbf689c46513 Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 14 Nov 2025 15:49:47 +0100 Subject: [PATCH 17/24] Fix E2E tests for removal of old people search --- apps/e2e/cypress/e2e/mobileViews.cy.ts | 29 +- apps/e2e/cypress/e2e/peopleTable.cy.ts | 352 ++----------------------- apps/e2e/cypress/e2e/visits.cy.ts | 21 +- 3 files changed, 51 insertions(+), 351 deletions(-) diff --git a/apps/e2e/cypress/e2e/mobileViews.cy.ts b/apps/e2e/cypress/e2e/mobileViews.cy.ts index d008f82cd9..d65561dd31 100644 --- a/apps/e2e/cypress/e2e/mobileViews.cy.ts +++ b/apps/e2e/cypress/e2e/mobileViews.cy.ts @@ -34,27 +34,22 @@ context('Mobile views tests', () => { cy.get('[role="presentation"] [role="dialog"]').as('modal'); - cy.get('@modal').contains('No Previous Collaborators'); + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user2.email + ); - cy.get('@modal') - .find('[data-cy="assign-selected-users"]') - .should('be.visible'); + cy.get('[role=presentation]') + .contains(initialDBData.users.user2.lastName) + .click(); - cy.finishedLoading(); - - cy.get('[data-cy=email]').type(initialDBData.users.user2.email); + cy.get('[data-cy="invite-user-submit-button"]') + .should('be.enabled') + .click(); - cy.get('[data-cy="findUser"]').click(); cy.finishedLoading(); - cy.get('@modal') - .find('tr[index="0"]') - .contains(initialDBData.users.user2.firstName); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal') - .find('[data-cy="assign-selected-users"]') - .should('be.visible'); + cy.get('[data-cy="co-proposers"]').contains( + initialDBData.users.user2.lastName + ); }); }); diff --git a/apps/e2e/cypress/e2e/peopleTable.cy.ts b/apps/e2e/cypress/e2e/peopleTable.cy.ts index f9fe4351aa..d244459511 100644 --- a/apps/e2e/cypress/e2e/peopleTable.cy.ts +++ b/apps/e2e/cypress/e2e/peopleTable.cy.ts @@ -31,35 +31,23 @@ context('PageTable component tests', () => { cy.get('[role="presentation"] [role="dialog"]').as('modal'); - cy.get('@modal').contains('No Previous Collaborators'); - - cy.finishedLoading(); - - cy.get('[data-cy=email]').type(initialDBData.users.user2.email); - - cy.get('[data-cy="findUser"]').click(); - cy.finishedLoading(); - - cy.get('@modal') - .find('tr[index="0"]') - .contains(initialDBData.users.user2.firstName); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('[data-cy="email"]').type(initialDBData.users.officer.email); + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user2.email + ); - cy.get('[data-cy="findUser"]').click(); - cy.finishedLoading(); + cy.get('[role=presentation]') + .contains(initialDBData.users.user2.lastName) + .click(); - cy.get('[role="presentation"] .MuiAlert-message').contains( - 'We cannot find that email' + cy.get('[role=presentation]').contains( + initialDBData.users.user2.lastName ); - cy.get('@modal').contains('1 User(s) Selected'); + cy.get('[data-cy="invite-user-autocomplete"]').type(emails[0]); - cy.get('[data-cy="assign-selected-users"]').click(); + cy.contains(`Invite ${emails[0]} via email`).should('exist'); - cy.get('@modal').should('not.exist'); + cy.get('[data-cy="invite-user-submit-button"]').click(); cy.get('[data-cy=title] input').type(title).should('have.value', title); @@ -85,305 +73,11 @@ context('PageTable component tests', () => { cy.get('[data-cy=add-participant-button]').click(); - cy.get('[role="presentation"]') - .find('tr[index="0"]') - .contains(initialDBData.users.user2.firstName); - }); - - it('Should preserve the selected users', () => { - cy.login('user1'); - cy.visit('/'); - - cy.contains('New Proposal').click(); - cy.get('[data-cy=call-list]').find('li:first-child').click(); - - cy.get('[data-cy=add-participant-button]').click(); - - cy.get('[role="presentation"] [role="dialog"]').as('modal'); - - cy.get('@modal').contains('0 user(s) selected'); - - cy.get('[data-cy=email]').type(initialDBData.users.placeholderUser.email); - - cy.get('[data-cy="findUser"]').click(); - cy.finishedLoading(); - - cy.get('@modal').find('tr[index="0"] input').uncheck(); - - cy.get('@modal').contains('0 user(s) selected'); - - cy.get('[data-cy=email]').type('ben@inbox.com'); - - cy.get('[data-cy="findUser"]').click(); - cy.finishedLoading(); - - cy.get('@modal') - .find('tr[index="0"]') - .contains(initialDBData.users.user2.firstName); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal').find('[aria-label="Search"]').type('foo bar'); - - cy.finishedLoading(); - - cy.get('@modal').contains('No Previous Collaborators'); - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal').find('[aria-label="Search"] ~ * > button').click(); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal') - .contains(initialDBData.users.user2.firstName) - .parent() - .find('input') - .should('be.checked'); - - cy.finishedLoading(); - - cy.get('@modal') - .find('[aria-label="Search"]') - .type(initialDBData.users.placeholderUser.firstName); - - cy.finishedLoading(); - - cy.get('@modal').contains('1 user(s) selected'); - cy.get('@modal') - .contains(initialDBData.users.placeholderUser.firstName) - .parent() - .find('input[type="checkbox"]') - .should('not.be.checked'); - - cy.get('@modal') - .find('[aria-label="Search"]') - .clear() - .type(initialDBData.users.user2.firstName); - - cy.finishedLoading(); - - cy.get('@modal').contains('1 user(s) selected'); - cy.get('@modal') - .contains(initialDBData.users.user2.firstName) - .parent() - .find('input') - .should('be.checked'); - - cy.get('[data-cy="assign-selected-users"]').click(); - }); - - it('should preserve the selected users after pagination', () => { - cy.login('user1'); - cy.visit('/'); - - cy.finishedLoading(); - cy.contains('New Proposal').click(); - cy.get('[data-cy=call-list]').find('li:first-child').click(); - - cy.get('[data-cy=add-participant-button]').click(); - - cy.get('[role="presentation"] [role="dialog"]').as('modal'); - - cy.finishedLoading(); - - cy.get('@modal').contains('10 rows').click(); - cy.get('[data-value=5]').click(); - - let index = 0; - for (const email of emails) { - cy.finishedLoading(); - cy.get('[data-cy=email]').clear().type(email); - - cy.get('[data-cy="findUser"]').click(); - cy.finishedLoading(); - - if (index >= 5) { - cy.get('[role="presentation"] .MuiAlert-message').contains( - 'We cannot find that email' - ); - - cy.get('[data-cy=email]').clear(); - } - - cy.get('@modal').find('tr[index="0"] input').uncheck(); - index++; - } - - cy.finishedLoading(); - cy.get('[data-cy=email]').clear().type('ben@inbox.com'); - - cy.get('[data-cy="findUser"]').click(); - cy.finishedLoading(); - - cy.get('@modal').contains('5 user(s) selected'); - cy.get('@modal').contains(/1-5 of [0-9]+/); - - cy.get('@modal').find('tr[index="1"] input').uncheck(); - - cy.get('@modal').contains('4 user(s) selected'); - - cy.finishedLoading(); - cy.get('@modal').find('button[aria-label="Next Page"]').click(); - - cy.finishedLoading(); - - cy.get('@modal').find('tr[index="0"] input').check(); - cy.get('@modal').contains('5 user(s) selected'); + cy.get('[data-cy="invite-user-autocomplete"]').click(); - cy.get('@modal').find('button[aria-label="Previous Page"]').click(); - - cy.finishedLoading(); - - cy.get('@modal').find('tr[index="0"] input:checked'); - cy.get('@modal').contains('5 user(s) selected'); - - cy.get('@modal').find('thead th input').check(); - cy.get('@modal').contains('6 user(s) selected'); - cy.get('@modal').find('thead th input').uncheck(); - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal').find('button[aria-label="Next Page"]').click(); - cy.finishedLoading(); - cy.get('@modal').find('tr[index="0"] input:checked'); - cy.get('@modal').contains('1 user(s) selected'); - cy.get('[data-cy="assign-selected-users"]').click(); - }); - }); - - describe('PeopleTable component preserves selected users', () => { - beforeEach(() => { - cy.createProposal({ callId: initialDBData.call.id }); - }); - - it('Should preserve the selected users', () => { - cy.login('officer'); - cy.visit('/'); - - cy.get('[data-cy=view-proposal]').click(); - - cy.get('[data-cy=toggle-edit-proposal]').click(); - - cy.get('[data-cy=questionary-stepper]').contains('New proposal').click(); - - cy.get('[data-cy=add-participant-button]').click(); - - cy.get('[role="presentation"] [role="dialog"]').as('modal'); - - cy.get('@modal').contains('0 user(s) selected'); - - cy.finishedLoading(); - - cy.get('@modal') - .contains(initialDBData.users.user2.firstName) - .parent() - .find('input') - .check(); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.finishedLoading(); - - cy.get('@modal').find('[aria-label="Search"]').type('foo bar'); - - cy.finishedLoading(); - - cy.get('@modal').contains('No Users'); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal').find('[aria-label="Search"] ~ * > button').click(); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal') - .contains(initialDBData.users.user2.firstName) - .parent() - .find('input') - .should('be.checked'); - - cy.finishedLoading(); - - cy.get('@modal') - .find('[aria-label="Search"]') - .type(initialDBData.users.placeholderUser.firstName); - - cy.finishedLoading(); - - cy.get('@modal').contains('1 user(s) selected'); - cy.get('@modal') - .contains(initialDBData.users.placeholderUser.firstName) - .parent() - .find('input') - .should('not.be.checked'); - - cy.get('@modal') - .find('[aria-label="Search"]') - .clear() - .type(initialDBData.users.user2.firstName); - - cy.finishedLoading(); - - cy.get('@modal').contains('1 user(s) selected'); - cy.get('@modal') - .contains(initialDBData.users.user2.firstName) - .parent() - .find('input') - .should('be.checked'); - - cy.get('[data-cy="assign-selected-users"]').click(); - }); - - it('Should preserve the selected users after pagination', () => { - cy.login('officer'); - cy.visit('/'); - - cy.get('[data-cy=view-proposal]').click(); - - cy.get('[data-cy=toggle-edit-proposal]').click(); - - cy.get('[data-cy=questionary-stepper]').contains('New proposal').click(); - - cy.get('[data-cy=add-participant-button]').click(); - - cy.get('[role="presentation"] [role="dialog"]').as('modal'); - - cy.finishedLoading(); - - cy.get('@modal').contains('10 rows').click(); - cy.get('[data-value=5]').click(); - - cy.get('@modal').contains('0 user(s) selected'); - cy.get('@modal').contains(/1-5 of [0-9]+/); - - cy.get('@modal').find('tr[index="1"] input').check(); - - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal').find('button[aria-label="Next Page"]').click(); - - cy.finishedLoading(); - - cy.get('@modal').find('tr[index="0"] input').check(); - cy.get('@modal').contains('2 user(s) selected'); - - cy.get('@modal').find('button[aria-label="Previous Page"]').click(); - - cy.finishedLoading(); - - cy.get('@modal').find('tr[index="1"] input:checked'); - cy.get('@modal').contains('2 user(s) selected'); - - cy.get('@modal').find('thead th input').check(); - cy.get('@modal').contains('6 user(s) selected'); - cy.get('@modal').find('thead th input').uncheck(); - cy.get('@modal').contains('1 user(s) selected'); - - cy.get('@modal').find('button[aria-label="Next Page"]').click(); - - cy.finishedLoading(); - - cy.get('@modal').find('tr[index="0"] input:checked'); - cy.get('@modal').contains('1 user(s) selected'); + cy.get('[role=presentation]').contains( + initialDBData.users.user2.lastName + ); }); }); @@ -461,15 +155,17 @@ context('PageTable component tests', () => { cy.get('[data-cy=add-participant-button]').click(); - cy.get('[role="presentation"] [role="dialog"]'); - - cy.get('[data-cy=email]').type(initialDBData.users.user2.email); - - cy.get('[data-cy="findUser"]').click(); + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user2.email + ); - cy.finishedLoading(); + cy.get('[role=presentation]') + .contains(initialDBData.users.user2.lastName) + .click(); - cy.get('[data-cy="assign-selected-users"]').click(); + cy.get('[data-cy="invite-user-submit-button"]') + .should('be.enabled') + .click(); cy.finishedLoading(); diff --git a/apps/e2e/cypress/e2e/visits.cy.ts b/apps/e2e/cypress/e2e/visits.cy.ts index d7a0ee58ec..d37414202d 100644 --- a/apps/e2e/cypress/e2e/visits.cy.ts +++ b/apps/e2e/cypress/e2e/visits.cy.ts @@ -271,14 +271,23 @@ context('visits tests', () => { // add visitors cy.get('[data-cy=add-participant-button]').click(); cy.finishedLoading(); - cy.get('[name=email]').type('david@teleworm.us{enter}'); + + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user2.email + ); + cy.get('[role=presentation][data-popper-placement]') + .contains(initialDBData.users.user2.lastName) + .click(); + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user3.email + ); + cy.get('[role=presentation]') + .contains(initialDBData.users.user3.lastName) + .click(); cy.finishedLoading(); - cy.get('[data-cy=co-proposers]') - .contains('Beckley') - .parent() - .find('[type=checkbox]') + cy.get('[data-cy="invite-user-submit-button"]') + .should('be.enabled') .click(); - cy.get('[data-cy=assign-selected-users]').click(); // specify team lead cy.get('[data-cy=team-lead-user-dropdown]').click(); From 5943bcfeeb5ff36b97522a2bf1e2c59fff9b43a6 Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Tue, 18 Nov 2025 09:19:05 +0100 Subject: [PATCH 18/24] Change proposals E2E test to use the new people selector --- apps/e2e/cypress/e2e/proposals.cy.ts | 34 +++++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/e2e/cypress/e2e/proposals.cy.ts b/apps/e2e/cypress/e2e/proposals.cy.ts index 233a1c96ae..64261c5fcb 100644 --- a/apps/e2e/cypress/e2e/proposals.cy.ts +++ b/apps/e2e/cypress/e2e/proposals.cy.ts @@ -173,9 +173,16 @@ context('Proposal tests', () => { cy.visit('/'); cy.get('[data-testid="PeopleIcon"]').click(); cy.get('[data-cy="add-participant-button"]').click(); - cy.get('#Email-input').type(initialDBData.users.user3.email); - cy.get('[data-cy="findUser"]').click(); - cy.get('[data-cy="assign-selected-users"]').click(); + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user3.email + ); + cy.get('[role=presentation]') + .contains(initialDBData.users.user3.lastName) + .click(); + cy.get('[data-cy="invite-user-autocomplete"]').type('{enter}'); + cy.get('[data-cy="invite-user-submit-button"]') + .should('be.enabled') + .click(); cy.get('[data-cy="save-data-access-users-modal"]').click(); cy.logout(); cy.login('user3', initialDBData.roles.user); @@ -233,14 +240,19 @@ context('Proposal tests', () => { cy.get('[data-cy=edit-proposer-button]').click(); cy.finishedLoading(); - - cy.get('[data-cy=email]').type('ben@inbox.com'); - - cy.get('[data-cy=findUser]').click(); - - cy.contains('Benjamin') - .parent() - .find("[aria-label='Select user']") + cy.get('[data-cy="invite-user-autocomplete"]').click(); + cy.get('[data-cy="invite-user-autocomplete"]') + .find('.MuiAutocomplete-clearIndicator') + .click(); + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user2.email + ); + cy.get('[role=presentation]') + .contains(initialDBData.users.user2.lastName) + .click(); + cy.get('[data-cy="invite-user-autocomplete"]').type('{enter}'); + cy.get('[data-cy="invite-user-submit-button"]') + .should('be.enabled') .click(); cy.get('[data-cy="save-and-continue-button"]').focus().click(); From 15c8e106005ed296f20b5d891e29905ab1a57df0 Mon Sep 17 00:00:00 2001 From: Yoganandan Pandiyan Date: Tue, 18 Nov 2025 10:17:05 +0100 Subject: [PATCH 19/24] Removed aforementioned fields in more places --- .../src/mutations/UserMutations.spec.ts | 20 ------------------- apps/backend/src/mutations/UserMutations.ts | 1 - .../resolvers/mutations/UpsertUserMutation.ts | 15 -------------- .../graphql/user/upsertUserByOidcSub.graphql | 10 ---------- 4 files changed, 46 deletions(-) diff --git a/apps/backend/src/mutations/UserMutations.spec.ts b/apps/backend/src/mutations/UserMutations.spec.ts index 9ef3fa9cf8..bb0226e697 100644 --- a/apps/backend/src/mutations/UserMutations.spec.ts +++ b/apps/backend/src/mutations/UserMutations.spec.ts @@ -285,14 +285,9 @@ describe('upsertUserByOidcSub', () => { lastName: 'User', email: 'new.user@example.com', userTitle: null, - username: null, preferredName: null, - gender: null, - birthDate: null, institutionRoRId: '', department: null, - position: '', - telephone: null, institutionName: '', institutionCountry: '', } @@ -310,14 +305,9 @@ describe('upsertUserByOidcSub', () => { lastName: 'UpsertedDoe', email: 'upserted.jane.doe@example.com', userTitle: null, - username: null, preferredName: null, - gender: null, - birthDate: null, institutionRoRId: '', department: null, - position: '', - telephone: null, institutionName: '', institutionCountry: '', } @@ -340,16 +330,11 @@ describe('upsertUserByOidcSub', () => { lastName: 'Scientist', email: 'john.scientist@dummy-research.org', userTitle: 'Dr.', - username: 'jscientist', preferredName: 'Johnny', - gender: 'male', - birthDate: '1985-05-15', institutionRoRId: existingRorId, // This should find Dummy Research Institute in our mock institutionName: 'CERN', // This should match the existing institution institutionCountry: 'Switzerland', department: 'Physics Department', - position: 'Senior Researcher', - telephone: '+41-22-767-6111', } ); @@ -376,16 +361,11 @@ describe('upsertUserByOidcSub', () => { lastName: 'Researcher', email: 'maria.researcher@newinstitute.edu', userTitle: 'Prof.', - username: 'mresearcher', preferredName: 'Maria', - gender: 'female', - birthDate: '1980-03-22', institutionRoRId: newRorId, // This ROR ID doesn't exist in mock institutionName: 'New Research Institute', institutionCountry: 'Germany', department: 'Materials Science', - position: 'Principal Investigator', - telephone: '+49-30-12345678', } ); diff --git a/apps/backend/src/mutations/UserMutations.ts b/apps/backend/src/mutations/UserMutations.ts index 59fbb330ec..fb369a3171 100644 --- a/apps/backend/src/mutations/UserMutations.ts +++ b/apps/backend/src/mutations/UserMutations.ts @@ -422,7 +422,6 @@ export default class UserMutations { institutionRoRId, institutionName, institutionCountry, - position, email, } = args; diff --git a/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts b/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts index 9ddab35e3c..2200aca6b6 100644 --- a/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts +++ b/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts @@ -13,21 +13,12 @@ export class UpsertUserByOidcSubArgs { @Field(() => String) public lastName: string; - @Field(() => String, { nullable: true }) - public username: string | null; - @Field(() => String, { nullable: true }) public preferredName: string | null; @Field(() => String) public oidcSub: string; - @Field(() => String, { nullable: true }) - public gender: string | null; - - @Field(() => String, { nullable: true }) - public birthDate: string | null; - @Field(() => String) public institutionRoRId: string; @@ -40,14 +31,8 @@ export class UpsertUserByOidcSubArgs { @Field(() => String, { nullable: true }) public department: string | null; - @Field(() => String) - public position: string; - @Field(() => String) public email: string; - - @Field(() => String, { nullable: true }) - public telephone: string | null; } @Resolver() diff --git a/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql b/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql index ef0aeb201d..5bd1588463 100644 --- a/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql +++ b/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql @@ -2,35 +2,25 @@ mutation upsertUserByOidcSub( $userTitle: String $firstName: String! $lastName: String! - $username: String $preferredName: String $oidcSub: String! - $gender: String - $birthDate: String $institutionRoRId: String! $institutionName: String! $institutionCountry: String! $department: String - $position: String! $email: String! - $telephone: String ) { upsertUserByOidcSub( userTitle: $userTitle firstName: $firstName lastName: $lastName - username: $username preferredName: $preferredName oidcSub: $oidcSub - gender: $gender - birthDate: $birthDate institutionRoRId: $institutionRoRId institutionName: $institutionName institutionCountry: $institutionCountry department: $department - position: $position email: $email - telephone: $telephone ) { id } From d41343f0dbf6263d31c4fadd82c32a23b3c68196 Mon Sep 17 00:00:00 2001 From: Yoganandan Pandiyan Date: Tue, 18 Nov 2025 13:50:30 +0100 Subject: [PATCH 20/24] Removed department from the user object --- apps/backend/src/mutations/UserMutations.spec.ts | 4 ---- apps/backend/src/resolvers/mutations/UpsertUserMutation.ts | 3 --- apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql | 2 -- 3 files changed, 9 deletions(-) diff --git a/apps/backend/src/mutations/UserMutations.spec.ts b/apps/backend/src/mutations/UserMutations.spec.ts index bb0226e697..22c5c48ea0 100644 --- a/apps/backend/src/mutations/UserMutations.spec.ts +++ b/apps/backend/src/mutations/UserMutations.spec.ts @@ -287,7 +287,6 @@ describe('upsertUserByOidcSub', () => { userTitle: null, preferredName: null, institutionRoRId: '', - department: null, institutionName: '', institutionCountry: '', } @@ -307,7 +306,6 @@ describe('upsertUserByOidcSub', () => { userTitle: null, preferredName: null, institutionRoRId: '', - department: null, institutionName: '', institutionCountry: '', } @@ -334,7 +332,6 @@ describe('upsertUserByOidcSub', () => { institutionRoRId: existingRorId, // This should find Dummy Research Institute in our mock institutionName: 'CERN', // This should match the existing institution institutionCountry: 'Switzerland', - department: 'Physics Department', } ); @@ -365,7 +362,6 @@ describe('upsertUserByOidcSub', () => { institutionRoRId: newRorId, // This ROR ID doesn't exist in mock institutionName: 'New Research Institute', institutionCountry: 'Germany', - department: 'Materials Science', } ); diff --git a/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts b/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts index 2200aca6b6..071c0943eb 100644 --- a/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts +++ b/apps/backend/src/resolvers/mutations/UpsertUserMutation.ts @@ -28,9 +28,6 @@ export class UpsertUserByOidcSubArgs { @Field(() => String) public institutionCountry: string; - @Field(() => String, { nullable: true }) - public department: string | null; - @Field(() => String) public email: string; } diff --git a/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql b/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql index 5bd1588463..9d74607b00 100644 --- a/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql +++ b/apps/frontend/src/graphql/user/upsertUserByOidcSub.graphql @@ -7,7 +7,6 @@ mutation upsertUserByOidcSub( $institutionRoRId: String! $institutionName: String! $institutionCountry: String! - $department: String $email: String! ) { upsertUserByOidcSub( @@ -19,7 +18,6 @@ mutation upsertUserByOidcSub( institutionRoRId: $institutionRoRId institutionName: $institutionName institutionCountry: $institutionCountry - department: $department email: $email ) { id From 7b45628aea7b8cfd0921c61466fc9f791898f85f Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Thu, 15 Jan 2026 14:03:39 +0100 Subject: [PATCH 21/24] fix order of db patches --- .../{0183_RemoveUserColulmns2.sql => 0204_RemoveUserColulmns.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/backend/db_patches/{0183_RemoveUserColulmns2.sql => 0204_RemoveUserColulmns.sql} (100%) diff --git a/apps/backend/db_patches/0183_RemoveUserColulmns2.sql b/apps/backend/db_patches/0204_RemoveUserColulmns.sql similarity index 100% rename from apps/backend/db_patches/0183_RemoveUserColulmns2.sql rename to apps/backend/db_patches/0204_RemoveUserColulmns.sql From 2796584ab06c09445f589d2194d178bba62e47ce Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Fri, 16 Jan 2026 11:31:23 +0100 Subject: [PATCH 22/24] fix e2e test for peopletable to use new people select --- apps/e2e/cypress/e2e/peopleTable.cy.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/e2e/cypress/e2e/peopleTable.cy.ts b/apps/e2e/cypress/e2e/peopleTable.cy.ts index 8ddcddc580..e6284bcaa5 100644 --- a/apps/e2e/cypress/e2e/peopleTable.cy.ts +++ b/apps/e2e/cypress/e2e/peopleTable.cy.ts @@ -27,13 +27,16 @@ context('PageTable component tests', () => { cy.contains('New Proposal').click(); cy.get('[data-cy=call-list]').find('li:first-child').click(); - cy.get('[data-cy=add-participant-button]').click(); - - cy.get('[data-cy=participant-modal]').as('modal'); + cy.get('[data-cy="add-participant-button"]').click(); cy.get('[data-cy="invite-user-autocomplete"]').type( initialDBData.users.user2.email ); + cy.get('[role=presentation]') + .contains(initialDBData.users.user2.lastName) + .click(); + cy.get('[data-cy="invite-user-autocomplete"]').type('{enter}'); + cy.get('[data-cy="invite-user-submit-button"]'); cy.get('[role=presentation]') .contains(initialDBData.users.user2.lastName) From bb4bdb5304daa1c4907140b179cbd57a3e3ed23d Mon Sep 17 00:00:00 2001 From: Simon Fernandes Date: Fri, 16 Jan 2026 12:02:01 +0000 Subject: [PATCH 23/24] Update STFC test to use new PI selector --- apps/e2e/cypress/e2e/proposals.cy.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/e2e/cypress/e2e/proposals.cy.ts b/apps/e2e/cypress/e2e/proposals.cy.ts index 64261c5fcb..996a33691e 100644 --- a/apps/e2e/cypress/e2e/proposals.cy.ts +++ b/apps/e2e/cypress/e2e/proposals.cy.ts @@ -1162,13 +1162,19 @@ context('Proposal tests', () => { cy.finishedLoading(); - cy.get('[data-cy=email]').type('ben@inbox.com'); - - cy.get('[data-cy=findUser]').click(); - - cy.contains('Benjamin') - .parent() - .find("[aria-label='Select user']") + cy.get('[data-cy="invite-user-autocomplete"]').click(); + cy.get('[data-cy="invite-user-autocomplete"]') + .find('.MuiAutocomplete-clearIndicator') + .click(); + cy.get('[data-cy="invite-user-autocomplete"]').type( + initialDBData.users.user2.email + ); + cy.get('[role=presentation]') + .contains(initialDBData.users.user2.lastName) + .click(); + cy.get('[data-cy="invite-user-autocomplete"]').type('{enter}'); + cy.get('[data-cy="invite-user-submit-button"]') + .should('be.enabled') .click(); cy.get('[data-cy="save-and-continue-button"]').focus().click(); From 303217001201b5665cf91617b444b7a93a827465 Mon Sep 17 00:00:00 2001 From: Fredrik Bolmsten Date: Tue, 20 Jan 2026 12:43:02 +0100 Subject: [PATCH 24/24] Add back missing tests --- apps/e2e/cypress/e2e/invites.cy.ts | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/apps/e2e/cypress/e2e/invites.cy.ts b/apps/e2e/cypress/e2e/invites.cy.ts index 71d16acac3..309f313944 100644 --- a/apps/e2e/cypress/e2e/invites.cy.ts +++ b/apps/e2e/cypress/e2e/invites.cy.ts @@ -427,6 +427,51 @@ context('Invites tests', () => { 'not.exist' ); }); + it('Should not be able to add duplicate co-proposer in modal', () => { + const lastName = initialDBData.users.user2.lastName; + const email = initialDBData.users.user2.email; + + cy.get('[data-cy="add-participant-button"]').click(); + + cy.get('[data-cy="invite-user-autocomplete"]').type(email); + cy.get('[role=presentation]').contains(lastName).click(); + + cy.get('[data-cy="invite-user-autocomplete"]').type(email); + cy.get('[role=presentation]') + .contains(`No results found for "${email}"`) + .should('exist'); + }); + + it('Should not be able to add duplicate co-proposer already on proposal', () => { + const lastName = initialDBData.users.user2.lastName; + const email = initialDBData.users.user2.email; + + cy.get('[data-cy="add-participant-button"]').click(); + + cy.get('[data-cy="invite-user-autocomplete"]').type(email); + cy.get('[role=presentation]').contains(lastName).click(); + cy.get('[data-cy="invite-user-submit-button"]') + .should('be.enabled') + .click(); + + cy.get('[data-cy="add-participant-button"]').click({ force: true }); + + cy.get('[data-cy="invite-user-autocomplete"]').type(email); + cy.get('[role=presentation]') + .contains(`No results found for "${email}"`) + .should('exist'); + }); + + it('Should not be able to add duplicate co-proposer when co-proposer is PI', () => { + const email = initialDBData.users.user1.email; + + cy.get('[data-cy="add-participant-button"]').click(); + + cy.get('[data-cy="invite-user-autocomplete"]').type(email); + cy.get('[role=presentation]') + .contains(`No results found for "${email}"`) + .should('exist'); + }); }); describe('Accepting co-proposer invites without code', () => {