From c826c51c4a3aa82a7f9ca399e5ec654abd069e61 Mon Sep 17 00:00:00 2001 From: Nick <53413353+nickpalladino@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:41:55 -0400 Subject: [PATCH 1/4] Switch to using bi-api calls rather than doing check on front end --- src/breeding-insight/dao/UserDAO.ts | 35 ++++++++++ src/breeding-insight/model/FilterRequest.ts | 26 ++++++++ src/breeding-insight/model/SearchRequest.ts | 26 ++++++++ src/breeding-insight/service/UserService.ts | 40 ++++++++++++ src/components/program/ProgramUsersTable.vue | 67 +++++++++----------- 5 files changed, 156 insertions(+), 38 deletions(-) create mode 100644 src/breeding-insight/model/FilterRequest.ts create mode 100644 src/breeding-insight/model/SearchRequest.ts diff --git a/src/breeding-insight/dao/UserDAO.ts b/src/breeding-insight/dao/UserDAO.ts index f93f69b5c..9fa3425c9 100644 --- a/src/breeding-insight/dao/UserDAO.ts +++ b/src/breeding-insight/dao/UserDAO.ts @@ -21,6 +21,7 @@ import {BiResponse} from "@/breeding-insight/model/BiResponse"; import {Role} from "@/breeding-insight/model/Role"; import {PaginationQuery} from "@/breeding-insight/model/PaginationQuery"; import {UserSort} from "@/breeding-insight/model/Sort"; +import {SearchRequest} from "@/breeding-insight/model/SearchRequest"; export class UserDAO { @@ -101,6 +102,40 @@ export class UserDAO { } + static getById(id: string): Promise { + return new Promise((resolve, reject) => { + api.call({ url: `${process.env.VUE_APP_BI_API_V1_PATH}/users/${id}`, method: 'get' }) + .then((response: any) => { + const biResponse = new BiResponse(response.data); + resolve(biResponse); + }).catch((error) => reject(error)); + }); + } + + static search(searchRequest: SearchRequest, {page, pageSize}: PaginationQuery, {field, order}: UserSort): Promise { + + return new Promise(((resolve, reject) => { + const config = { + url: `${process.env.VUE_APP_BI_API_V1_PATH}/users/search`, + method: 'post', + data: searchRequest, + params: { + sortField: field, + sortOrder: order, + page, + pageSize + } + } + api.call(config) + .then((response: any) => { + const biResponse = new BiResponse(response.data); + resolve(biResponse); + }).catch((error) => { + reject(error); + }) + })); + } + static updateSystemRoles(id: string, systemRoles: Array) { return new Promise((resolve, reject) => { diff --git a/src/breeding-insight/model/FilterRequest.ts b/src/breeding-insight/model/FilterRequest.ts new file mode 100644 index 000000000..77a9edc40 --- /dev/null +++ b/src/breeding-insight/model/FilterRequest.ts @@ -0,0 +1,26 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class FilterRequest { + field: string; + value: string; + + constructor(field: string, value: string) { + this.field = field; + this.value = value; + } +} diff --git a/src/breeding-insight/model/SearchRequest.ts b/src/breeding-insight/model/SearchRequest.ts new file mode 100644 index 000000000..845327f3a --- /dev/null +++ b/src/breeding-insight/model/SearchRequest.ts @@ -0,0 +1,26 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {FilterRequest} from "@/breeding-insight/model/FilterRequest"; + +export class SearchRequest { + filters: FilterRequest[] = []; + + constructor(filters: FilterRequest[]) { + this.filters = filters; + } +} diff --git a/src/breeding-insight/service/UserService.ts b/src/breeding-insight/service/UserService.ts index b80eb6dee..459f3f0b8 100644 --- a/src/breeding-insight/service/UserService.ts +++ b/src/breeding-insight/service/UserService.ts @@ -24,6 +24,7 @@ import {Program} from "@/breeding-insight/model/Program"; import {ProgramUser} from "@/breeding-insight/model/ProgramUser"; import {PaginationQuery} from "@/breeding-insight/model/PaginationQuery"; import {ProgramSort, SortOrder, UserSort, UserSortField} from "@/breeding-insight/model/Sort"; +import {SearchRequest} from "@/breeding-insight/model/SearchRequest"; export class UserService { @@ -153,6 +154,45 @@ export class UserService { })); } + static getById(id: string): Promise { + return new Promise((resolve, reject) => { + UserDAO.getById(id).then((biResponse: BiResponse) => { + const result: any = biResponse.result; + const role: Role | undefined = this.parseSystemRoles(result.systemRoles); + const programRoles: ProgramUser[] | undefined = this.parseProgramRoles(result.programRoles); + const user = new User(result.id, result.name, result.orcid, result.email, role, programRoles); + resolve(user); + }).catch((error) => { + error['errorMessage'] = this.errorGetUser; + reject(error); + }); + }); + } + + static search(searchRequest: SearchRequest, + paginationQuery: PaginationQuery = new PaginationQuery(1, 50, true), + sort: UserSort = new UserSort(UserSortField.Name, SortOrder.Ascending)): Promise<[User[], Metadata]> { + return new Promise<[User[], Metadata]>(((resolve, reject) => { + + UserDAO.search(searchRequest, paginationQuery, sort).then((biResponse) => { + + // Parse our users into the vue users param + let users = biResponse.result.data.map((user: any) => { + const role: Role | undefined = this.parseSystemRoles(user.systemRoles); + const programRoles: ProgramUser[] | undefined = this.parseProgramRoles(user.programRoles); + return new User(user.id, user.name, user.orcid, user.email, role, programRoles); + }); + + resolve([users, biResponse.metadata]); + + }).catch((error) => { + error['errorMessage'] = this.errorSearchUsers; + reject(error) + }); + + })); + } + static updateSystemRoles(user: User): Promise { return new Promise((resolve, reject) => { diff --git a/src/components/program/ProgramUsersTable.vue b/src/components/program/ProgramUsersTable.vue index ef66b26c6..c7aec7e00 100644 --- a/src/components/program/ProgramUsersTable.vue +++ b/src/components/program/ProgramUsersTable.vue @@ -191,6 +191,7 @@ import { UPDATE_PROGRAM_USER_SORT } from "@/store/sorting/mutation-types"; + import {SearchRequest} from "@/breeding-insight/model/SearchRequest"; @Component({ components: { ExpandableTable, NewDataForm, BasicInputField, BasicSelectField, TableColumn, @@ -218,7 +219,6 @@ export default class ProgramUsersTable extends Vue { private activeProgram?: Program; private activeUser?: User; public users: ProgramUser[] = []; - public systemUsers: User[] = []; private deactivateActive: boolean = false; private newUserActive: boolean = false; @@ -252,7 +252,6 @@ export default class ProgramUsersTable extends Vue { mounted() { this.getRoles(); - this.getSystemUsers(); this.paginationChanged(); } @@ -338,22 +337,23 @@ export default class ProgramUsersTable extends Vue { this.newUser.program = this.activeProgram; try { - this.newUser = this.checkExistingUserByEmail(this.newUser, this.systemUsers); + this.newUser = this.checkExistingUserByEmail(this.newUser); } catch (err) { this.$emit('show-error-notification', err); this.newUserFormState.bus.$emit(DataFormEventBusHandler.SAVE_COMPLETE_EVENT); return; } + // if we found a system user by email in checkExistingUserByEmail then system user exists + const systemUserExisted = this.newUser.id !== undefined; + ProgramUserService.create(this.newUser).then((user: ProgramUser) => { this.paginationController.updatePage(1); this.paginationController.updateOnAdd(); this.getUsers(); - this.getSystemUsers(); // See if the user already existed - //TODO: Reconsider when user search feature is added - if (this.getSystemUserById(user, this.systemUsers)) { + if (systemUserExisted) { this.$emit('show-success-notification', 'Success! Existing user ' + user.name + ' added to program.'); } else { this.$emit('show-success-notification', 'Success! ' + this.newUser.name + ' added.'); @@ -361,13 +361,11 @@ export default class ProgramUsersTable extends Vue { if(this.newUser.email === this.activeUser!.email) this.updateActiveUser(); - this.getSystemUsers(); this.newUser = new ProgramUser(); this.newUserActive = false; }).catch((error) => { this.$emit('show-error-notification', error.errorMessage); this.getUsers(); - this.getSystemUsers(); }).finally(() => this.newUserFormState.bus.$emit(DataFormEventBusHandler.SAVE_COMPLETE_EVENT)) } @@ -379,47 +377,40 @@ export default class ProgramUsersTable extends Vue { Vue.prototype.$ability.update(rules); } - //TODO: Reconsider when user search feature is added - getSystemUsers() { + //TODO: Do we still want this since orcid entry is removed? + checkExistingUserByEmail(user: ProgramUser): ProgramUser { + user.id = undefined; - UserService.getAll().then(([users, metadata]) => { - this.systemUsers = users; + // api call to check if user exists for email + // this was done on front-end before because we didn't have search endpoint at the time + const filter = new FilterRequest('email', user.email); + const request = new SearchRequest(filter); + UserService.search(request).then(([users, metadata]) => { + // email exists + if (users.length === 1) { + user.id = users[0].id; + } }).catch((error) => { // Display error that users cannot be loaded this.$emit('show-error-notification', 'Error while trying to load system users'); throw error; }); - } - - //TODO: Reconsider when user search feature is added - //TODO: Do we still want this since orcid entry is removed? - checkExistingUserByEmail(user: ProgramUser, systemUsers: User[]): ProgramUser { - user.id = undefined; - let usersFound = 0; - for (const systemUser of systemUsers){ - if (user.email === systemUser.email){ - usersFound += 1; - if (systemUser.id){ - user.id = systemUser.id; - } - } - } - - if (usersFound > 1){ - throw "Email matches two different users."; - } - return user; } - //TODO: Reconsider when user search feature is added - getSystemUserById(user: ProgramUser, systemUsers: User[]): User | undefined { - for (const systemUser of systemUsers){ - if (user.id === systemUser.id){ - return systemUser; + getSystemUserById(user: ProgramUser): User | undefined { + + UserService.getById(user.id).then(([users, metadata]) => { + if (users.length === 1) { + return users[0]; } - } + }).catch((error) => { + // Display error that users cannot be loaded + this.$emit('show-error-notification', 'Error while trying to load system user'); + throw error; + }); + return undefined; } From db352c364afca37c5a9384c233c36da7b9516e95 Mon Sep 17 00:00:00 2001 From: Nick <53413353+nickpalladino@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:40:32 -0400 Subject: [PATCH 2/4] Fix error message --- src/breeding-insight/service/UserService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/breeding-insight/service/UserService.ts b/src/breeding-insight/service/UserService.ts index 459f3f0b8..00fbc8f45 100644 --- a/src/breeding-insight/service/UserService.ts +++ b/src/breeding-insight/service/UserService.ts @@ -32,6 +32,7 @@ export class UserService { static errorCreateUser: string = 'Unable to create user'; static errorDeleteUser: string = 'Unable to archive user'; static errorGetUsers: string = 'Error while trying to load roles'; + static errorGetUser: string = 'Error user not found'; static errorDeleteUserNotFound: string = 'Unable to find user to deactivate'; static errorDeleteUserNotAllowed: string = 'You are not allowed to deactivate this user.'; static errorPermissionsEditUser: string = "You don't have permissions to edit the roles of this user."; @@ -147,7 +148,7 @@ export class UserService { resolve([users, biResponse.metadata]); }).catch((error) => { - error['errorMessage'] = this.errorGetUsers; + error['errorMessage'] = this.errorGetUser; reject(error) }); From d8489a33f0e1860a1ed28d599b985b9de8ace3ee Mon Sep 17 00:00:00 2001 From: Nick <53413353+nickpalladino@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:47:52 -0400 Subject: [PATCH 3/4] Fixed async issues --- src/components/program/ProgramUsersTable.vue | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/program/ProgramUsersTable.vue b/src/components/program/ProgramUsersTable.vue index c7aec7e00..05a9d512d 100644 --- a/src/components/program/ProgramUsersTable.vue +++ b/src/components/program/ProgramUsersTable.vue @@ -192,6 +192,7 @@ UPDATE_PROGRAM_USER_SORT } from "@/store/sorting/mutation-types"; import {SearchRequest} from "@/breeding-insight/model/SearchRequest"; + import {FilterRequest} from "@/breeding-insight/model/FilterRequest"; @Component({ components: { ExpandableTable, NewDataForm, BasicInputField, BasicSelectField, TableColumn, @@ -332,12 +333,12 @@ export default class ProgramUsersTable extends Vue { } - saveUser() { + async saveUser() { this.newUser.program = this.activeProgram; try { - this.newUser = this.checkExistingUserByEmail(this.newUser); + this.newUser = await this.checkExistingUserByEmail(this.newUser); } catch (err) { this.$emit('show-error-notification', err); this.newUserFormState.bus.$emit(DataFormEventBusHandler.SAVE_COMPLETE_EVENT); @@ -378,23 +379,25 @@ export default class ProgramUsersTable extends Vue { } //TODO: Do we still want this since orcid entry is removed? - checkExistingUserByEmail(user: ProgramUser): ProgramUser { + async checkExistingUserByEmail(user: ProgramUser): Promise { user.id = undefined; - // api call to check if user exists for email // this was done on front-end before because we didn't have search endpoint at the time const filter = new FilterRequest('email', user.email); const request = new SearchRequest(filter); - UserService.search(request).then(([users, metadata]) => { + + try { + const [users, metadata] = await UserService.search(request); // email exists if (users.length === 1) { user.id = users[0].id; } - }).catch((error) => { + } catch (error) { // Display error that users cannot be loaded this.$emit('show-error-notification', 'Error while trying to load system users'); throw error; - }); + } + return user; } From 599d7df12b58dd82ae3c12b88f5d680d4bf666d9 Mon Sep 17 00:00:00 2001 From: Nick <53413353+nickpalladino@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:09:23 -0400 Subject: [PATCH 4/4] Update error messages --- src/breeding-insight/service/UserService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/breeding-insight/service/UserService.ts b/src/breeding-insight/service/UserService.ts index 00fbc8f45..ce3433c0a 100644 --- a/src/breeding-insight/service/UserService.ts +++ b/src/breeding-insight/service/UserService.ts @@ -31,8 +31,9 @@ export class UserService { static duplicateEmailMessage : string = 'Email is already in use.'; static errorCreateUser: string = 'Unable to create user'; static errorDeleteUser: string = 'Unable to archive user'; - static errorGetUsers: string = 'Error while trying to load roles'; - static errorGetUser: string = 'Error user not found'; + static errorGetUsers: string = 'Error while trying to load users'; + static errorSearchUsers: string = 'Error while searching users'; + static errorGetUser: string = 'User not found'; static errorDeleteUserNotFound: string = 'Unable to find user to deactivate'; static errorDeleteUserNotAllowed: string = 'You are not allowed to deactivate this user.'; static errorPermissionsEditUser: string = "You don't have permissions to edit the roles of this user."; @@ -148,7 +149,7 @@ export class UserService { resolve([users, biResponse.metadata]); }).catch((error) => { - error['errorMessage'] = this.errorGetUser; + error['errorMessage'] = this.errorGetUsers; reject(error) });