diff --git a/package.json b/package.json index 64dcce36..717d6dfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bi-web", - "version": "v1.1.0", + "version": "v1.2.0+873", "private": true, "scripts": { "build": "node $npm_package_config_task_path/build.js --dev-audit-level=critical --prod-audit-level=none", @@ -96,5 +96,5 @@ "vue-cli-plugin-axios": "0.0.4", "vue-template-compiler": "^2.7.14" }, - "versionInfo": "https://github.com/Breeding-Insight/bi-web/releases/tag/v1.1.0" + "versionInfo": "https://github.com/Breeding-Insight/bi-web/commit/5c4d461021cff1e8ce8e69a4b2de0d43468358d7" } diff --git a/src/breeding-insight/dao/UserDAO.ts b/src/breeding-insight/dao/UserDAO.ts index f93f69b5..9fa3425c 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 00000000..77a9edc4 --- /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 00000000..845327f3 --- /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 b80eb6de..ce3433c0 100644 --- a/src/breeding-insight/service/UserService.ts +++ b/src/breeding-insight/service/UserService.ts @@ -24,13 +24,16 @@ 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 { 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 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."; @@ -153,6 +156,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 ef66b26c..05a9d512 100644 --- a/src/components/program/ProgramUsersTable.vue +++ b/src/components/program/ProgramUsersTable.vue @@ -191,6 +191,8 @@ import { 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, @@ -218,7 +220,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 +253,6 @@ export default class ProgramUsersTable extends Vue { mounted() { this.getRoles(); - this.getSystemUsers(); this.paginationChanged(); } @@ -333,27 +333,28 @@ export default class ProgramUsersTable extends Vue { } - saveUser() { + async saveUser() { this.newUser.program = this.activeProgram; try { - this.newUser = this.checkExistingUserByEmail(this.newUser, this.systemUsers); + this.newUser = await 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 +362,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 +378,42 @@ 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? + 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.getAll().then(([users, metadata]) => { - this.systemUsers = users; - }).catch((error) => { + try { + const [users, metadata] = await UserService.search(request); + // 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; } diff --git a/src/views/experiments-and-observations/ExperimentDetails.vue b/src/views/experiments-and-observations/ExperimentDetails.vue index 34e7273f..52d9cfc3 100644 --- a/src/views/experiments-and-observations/ExperimentDetails.vue +++ b/src/views/experiments-and-observations/ExperimentDetails.vue @@ -222,7 +222,7 @@ export default class ExperimentDetails extends ProgramsBase { new ActionMenuItem('experiment-import-file', 'import-file', 'Import file', this.$ability.can('create', 'Import')), new ActionMenuItem('experiment-download-file', 'download-file', 'Download file'), new ActionMenuItem('experiment-add-collaborator', 'add-collaborator', 'Add Collaborator', this.$ability.can('manage', 'Collaborator')), - // new ActionMenuItem('experiment-create-sub-entity-dataset', 'create-sub-entity-dataset', 'Create Sub-Entity Dataset'), + new ActionMenuItem('experiment-create-sub-entity-dataset', 'create-sub-entity-dataset', 'Create Sub-Entity Dataset'), new ActionMenuItem('experiment-delete', 'delete-experiment', 'Delete experiment', this.$ability.can('delete', 'Experiment')) ]; diff --git a/src/views/import/Dataset.vue b/src/views/import/Dataset.vue index 6ef10f36..78bd4fba 100644 --- a/src/views/import/Dataset.vue +++ b/src/views/import/Dataset.vue @@ -123,6 +123,7 @@ New Germplasm count: {{ statistics.Germplasm.newObjectCount }}

New Pedigree Connections: {{ statistics["Pedigree Connections"].newObjectCount }}

diff --git a/task/serve.js b/task/serve.js index 1579e94d..630fa327 100755 --- a/task/serve.js +++ b/task/serve.js @@ -25,10 +25,13 @@ const port = process.env.PORT || JSON.parse(fs.readFileSync('package.json', 'utf (async () => { let spinner = ora({prefixText: ' ', color: 'yellow'}); try { + // TODO: figure out the issue here + /* spinner = spinner.start('sort package.json'); await execa('npx', ['sort-package-json@3.0.0'], {preferLocal: true}); spinner = spinner.clear() .succeed('package.json sorted'); + */ console.log(`App running at http://localhost:${port}`); await execa.command(`vue-cli-service\ serve --port ${port}`);