From d813f0a0c7cf379c5ca77a30aa3a5a204893abd3 Mon Sep 17 00:00:00 2001 From: Chris T Date: Wed, 23 Mar 2022 10:18:03 -0400 Subject: [PATCH 1/7] [BI-1363] program config: add page --- src/router/index.ts | 10 +++++++ src/views/program/ProgramConfiguration.vue | 35 ++++++++++++++++++++++ src/views/program/ProgramManagement.vue | 5 ++++ 3 files changed, 50 insertions(+) create mode 100644 src/views/program/ProgramConfiguration.vue diff --git a/src/router/index.ts b/src/router/index.ts index 8e71272eb..e8076a244 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -66,6 +66,7 @@ import OntologyArchivedTable from "@/components/ontology/OntologyArchivedTable.v import PageNotFound from "@/views/PageNotFound.vue"; import Germplasm from "@/views/germplasm/Germplasm.vue"; import GermplasmLists from "@/views/germplasm/GermplasmLists.vue"; +import ProgramConfiguration from "@/views/program/ProgramConfiguration.vue"; Vue.use(VueRouter); @@ -228,6 +229,15 @@ const routes = [ layout: layouts.userSideBar }, component: ProgramUserManagement + }, + { + path: 'configure', + name: 'program-configuration', + meta: { + title: 'Program Configuration', + layout: layouts.userSideBar + }, + component: ProgramConfiguration } ] }, diff --git a/src/views/program/ProgramConfiguration.vue b/src/views/program/ProgramConfiguration.vue new file mode 100644 index 000000000..576a322a4 --- /dev/null +++ b/src/views/program/ProgramConfiguration.vue @@ -0,0 +1,35 @@ + + + + + \ No newline at end of file diff --git a/src/views/program/ProgramManagement.vue b/src/views/program/ProgramManagement.vue index 426808f93..cb363250c 100644 --- a/src/views/program/ProgramManagement.vue +++ b/src/views/program/ProgramManagement.vue @@ -34,6 +34,11 @@ tag="li" active-class="is-active"> Users + + Configuration + From 1d263d9093a558c2089b169368a6a0db22a04a0c Mon Sep 17 00:00:00 2001 From: Chris T Date: Thu, 24 Mar 2022 11:05:59 -0400 Subject: [PATCH 2/7] [BI-1363] share ontology: share and revoke ontologies --- src/breeding-insight/dao/SharedOntologyDAO.ts | 50 +++++ src/breeding-insight/model/SharedProgram.ts | 32 +++ .../model/SharedProgramRequest.ts | 27 +++ .../service/SharedOntologyService.ts | 85 ++++++++ src/views/program/ProgramConfiguration.vue | 200 +++++++++++++++++- 5 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 src/breeding-insight/dao/SharedOntologyDAO.ts create mode 100644 src/breeding-insight/model/SharedProgram.ts create mode 100644 src/breeding-insight/model/SharedProgramRequest.ts create mode 100644 src/breeding-insight/service/SharedOntologyService.ts diff --git a/src/breeding-insight/dao/SharedOntologyDAO.ts b/src/breeding-insight/dao/SharedOntologyDAO.ts new file mode 100644 index 000000000..c9ffa571d --- /dev/null +++ b/src/breeding-insight/dao/SharedOntologyDAO.ts @@ -0,0 +1,50 @@ +/* + * 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 {BiResponse, Metadata, Response} from "@/breeding-insight/model/BiResponse"; +import * as api from "@/util/api"; +import {SharedProgramRequest} from "@/breeding-insight/model/SharedProgramRequest"; + +export class SharedOntologyDAO { + + static async get(programId: string): Promise { + const { data } = await api.call({ + url: `${process.env.VUE_APP_BI_API_V1_PATH}/programs/${programId}/ontology/shared/programs`, + method: 'get' + }) as Response; + + return new BiResponse(data); + } + + static async share(programId: string, sharedProgramsRequest: SharedProgramRequest[]): Promise { + const { data } = await api.call({ + url: `${process.env.VUE_APP_BI_API_V1_PATH}/programs/${programId}/ontology/shared/programs`, + method: 'post', + data: sharedProgramsRequest + }) as Response; + return new BiResponse(data); + } + + static async revoke(programId: string, sharedProgramId: string) { + const { data } = await api.call({ + url: `${process.env.VUE_APP_BI_API_V1_PATH}/programs/${programId}/ontology/shared/programs/${sharedProgramId}`, + method: 'delete' + }) as Response; + + return new BiResponse(data); + } +} \ No newline at end of file diff --git a/src/breeding-insight/model/SharedProgram.ts b/src/breeding-insight/model/SharedProgram.ts new file mode 100644 index 000000000..55b2e9f9b --- /dev/null +++ b/src/breeding-insight/model/SharedProgram.ts @@ -0,0 +1,32 @@ +/* + * 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 SharedProgram { + programName: string; + programId: string; + shared: boolean; + accepted: boolean | undefined; + editable: boolean | undefined; + + constructor({programName, programId, shared, accepted, editable}: SharedProgram) { + this.programName = programName; + this.programId = programId; + this.shared = shared; + this.accepted = accepted; + this.editable = editable; + } +} \ No newline at end of file diff --git a/src/breeding-insight/model/SharedProgramRequest.ts b/src/breeding-insight/model/SharedProgramRequest.ts new file mode 100644 index 000000000..df7223ab2 --- /dev/null +++ b/src/breeding-insight/model/SharedProgramRequest.ts @@ -0,0 +1,27 @@ +/* + * 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 SharedProgramRequest { + programName: string; + programId: string; + + constructor({programName, programId}: SharedProgramRequest) { + this.programName = programName; + this.programId = programId; + } +} \ No newline at end of file diff --git a/src/breeding-insight/service/SharedOntologyService.ts b/src/breeding-insight/service/SharedOntologyService.ts new file mode 100644 index 000000000..dcba5f1cf --- /dev/null +++ b/src/breeding-insight/service/SharedOntologyService.ts @@ -0,0 +1,85 @@ +/* + * 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 {SharedProgram} from "@/breeding-insight/model/SharedProgram"; +import {BiResponse, Metadata} from "@/breeding-insight/model/BiResponse"; +import {TraitDAO} from "@/breeding-insight/dao/TraitDAO"; +import {ValidationErrorService} from "@/breeding-insight/service/ValidationErrorService"; +import {SharedOntologyDAO} from "@/breeding-insight/dao/SharedOntologyDAO"; +import {SharedProgramRequest} from "@/breeding-insight/model/SharedProgramRequest"; + +export class SharedOntologyService { + + static async get(programId: string): Promise<[SharedProgram[], Metadata]> { + if (!programId) throw 'Program ID required'; + + try { + const { result: { data }, metadata } = await SharedOntologyDAO.get(programId); + const sharedPrograms: SharedProgram[] = []; + for (const datum of data) { + sharedPrograms.push(new SharedProgram(datum)); + } + return [sharedPrograms, metadata]; + } catch (error) { + if (error.response && error.response.status === 404) { + throw error.response.message; + } else { + throw 'An unknown error has occurred'; + } + } + + } + + static async share(programId: string, shareRequests: SharedProgramRequest[]): Promise<[SharedProgram[], Metadata]> { + + try { + const { result: { data }, metadata } = await SharedOntologyDAO.share(programId, shareRequests); + // Parse into SharedPrograms + console.log(data); + const sharedPrograms: SharedProgram[] = []; + for (const datum of data) { + sharedPrograms.push(new SharedProgram(datum)); + } + return [sharedPrograms, metadata]; + } catch (error) { + // TODO: There is also a potentional validation error thrown here + if (error.response && error.response.status === 404) { + throw error.response.message; + } else { + throw 'An unknown error has occurred'; + } + } + } + + static async revokeAll(programId: string, programsToRemove: string[]) { + + if (!programId) throw 'Program ID required'; + + // TODO: Collect errors + for (const programToRemove of programsToRemove) { + try { + await SharedOntologyDAO.revoke(programId, programToRemove); + } catch (error) { + if (error.response && error.response.status === 404) { + throw error.response.message; + } else { + throw 'An unknown error has occurred'; + } + } + } + } +} \ No newline at end of file diff --git a/src/views/program/ProgramConfiguration.vue b/src/views/program/ProgramConfiguration.vue index 576a322a4..00570affc 100644 --- a/src/views/program/ProgramConfiguration.vue +++ b/src/views/program/ProgramConfiguration.vue @@ -17,19 +17,217 @@ \ No newline at end of file From 5f0cc636d39a1cb6ea5bafc1eda0313c7cf5cd78 Mon Sep 17 00:00:00 2001 From: Chris T Date: Mon, 28 Mar 2022 10:14:34 -0400 Subject: [PATCH 3/7] [BI-1363] shared ontology: add styling --- .../service/SharedOntologyService.ts | 1 - src/components/modals/GenericModal.vue | 47 ++++ .../program/SharedOntologyConfiguration.vue | 246 ++++++++++++++++++ src/views/program/ProgramConfiguration.vue | 191 +------------- 4 files changed, 298 insertions(+), 187 deletions(-) create mode 100644 src/components/modals/GenericModal.vue create mode 100644 src/components/program/SharedOntologyConfiguration.vue diff --git a/src/breeding-insight/service/SharedOntologyService.ts b/src/breeding-insight/service/SharedOntologyService.ts index dcba5f1cf..49531acf6 100644 --- a/src/breeding-insight/service/SharedOntologyService.ts +++ b/src/breeding-insight/service/SharedOntologyService.ts @@ -49,7 +49,6 @@ export class SharedOntologyService { try { const { result: { data }, metadata } = await SharedOntologyDAO.share(programId, shareRequests); // Parse into SharedPrograms - console.log(data); const sharedPrograms: SharedProgram[] = []; for (const datum of data) { sharedPrograms.push(new SharedProgram(datum)); diff --git a/src/components/modals/GenericModal.vue b/src/components/modals/GenericModal.vue new file mode 100644 index 000000000..20a7abf34 --- /dev/null +++ b/src/components/modals/GenericModal.vue @@ -0,0 +1,47 @@ + + + + + \ No newline at end of file diff --git a/src/components/program/SharedOntologyConfiguration.vue b/src/components/program/SharedOntologyConfiguration.vue new file mode 100644 index 000000000..d8ba10678 --- /dev/null +++ b/src/components/program/SharedOntologyConfiguration.vue @@ -0,0 +1,246 @@ + + + + + \ No newline at end of file diff --git a/src/views/program/ProgramConfiguration.vue b/src/views/program/ProgramConfiguration.vue index 00570affc..f9750447b 100644 --- a/src/views/program/ProgramConfiguration.vue +++ b/src/views/program/ProgramConfiguration.vue @@ -17,94 +17,7 @@ @@ -118,9 +31,13 @@ import {Program} from "@/breeding-insight/model/Program"; import InfoModal from '@/components/modals/InfoModal'; import BaseModal from "@/components/modals/BaseModal.vue"; import {SharedProgramRequest} from "@/breeding-insight/model/SharedProgramRequest"; +import GenericModal from "@/components/modals/GenericModal.vue"; +import SharedOntologyConfiguration from "@/components/program/SharedOntologyConfiguration.vue"; @Component({ components: { + SharedOntologyConfiguration, + GenericModal, InfoModal, BaseModal }, computed: { @@ -131,103 +48,5 @@ import {SharedProgramRequest} from "@/breeding-insight/model/SharedProgramReques }) export default class ProgramConfiguration extends ProgramsBase { - private activeProgram?: Program; - - private sharedProgramLoading: boolean = false; - private shareProgramProcessing: boolean = false; - private showShareModal: boolean = false; - - private matchedPrograms: {string, SharedProgram} = {}; - private sharedPrograms: SharedProgram[] = []; - private editableMatchedPrograms: SharedProgram[] = []; - - async mounted() { - // Get shared ontologies - await this.getSharedPrograms(); - } - - // Get shared ontologies - async getSharedPrograms() { - try { - // Loading with show - this.sharedProgramLoading = true; - const [data, metadata] = await SharedOntologyService.get(this.activeProgram!.id); - data.forEach(datum => this.matchedPrograms[datum.programId] = datum); - // Filter for shared programs - this.sharedPrograms = Object.values(this.matchedPrograms).filter(matchedProgram => matchedProgram.shared); - } catch (e) { - // Check error statuses, show errors - this.$emit('show-error-notification', e); - } finally { - // Loading wheel hide - this.sharedProgramLoading = false; - } - } - - shareShowModalEvent() { - this.editableMatchedPrograms = Object.values(this.matchedPrograms).map(matchedProgram => new SharedProgram(matchedProgram)); - this.showShareModal = true; - } - - isEditable(program: SharedProgram) { - return !program.shared || program.editable; - } - - async processSelections() { - - // Process the selections - const programIdsToRevoke: string[] = []; - const newSharePrograms: SharedProgramRequest[] = []; - for (const program of this.editableMatchedPrograms) { - const originalProgram = this.matchedPrograms[program.programId]; - - if (program.shared === originalProgram.shared) { - // If no change skip - continue; - } else if (originalProgram.shared && !program.shared) { - // If program used to be shared and is no longer, add to revoke list - programIdsToRevoke.push(program.programId); - } else if (!originalProgram.shared && program.shared) { - // If program was not shared, and it now shared, add to share list - newSharePrograms.push( - new SharedProgramRequest({programName: program.programName, programId: program.programId})); - } - } - - // Show progress wheel on save button - if (programIdsToRevoke.length > 0 || newSharePrograms.length > 0) { - this.shareProgramProcessing = true; - } - - - // Revoke ontologies - if (programIdsToRevoke.length > 0) { - try { - await SharedOntologyService.revokeAll(this.activeProgram!.id, programIdsToRevoke); - } catch (e) { - this.$emit('show-error-notification', e); - return; - } - } - - // Shared ontologies - if (newSharePrograms.length > 0) { - try { - await SharedOntologyService.share(this.activeProgram!.id, newSharePrograms); - } catch (e) { - this.$emit('show-error-notification', e); - return; - } - } - - // Show progress wheel on save button - if (programIdsToRevoke.length > 0 || newSharePrograms.length > 0) { - this.shareProgramProcessing = false; - this.$emit('show-success-notification', 'Changes to shared ontology successfully saved'); - this.showShareModal = false; - } - - this.getSharedPrograms(); - } } \ No newline at end of file From f2e64c343211c5663bb34aafe89a11c8daa11599 Mon Sep 17 00:00:00 2001 From: Chris T Date: Mon, 28 Mar 2022 14:54:36 -0400 Subject: [PATCH 4/7] [BI-1363] share ontology: add error handling --- .../service/SharedOntologyService.ts | 6 ++++- .../service/ValidationErrorService.ts | 23 +++++++++++++++++++ .../program/SharedOntologyConfiguration.vue | 6 +++-- src/views/program/ProgramConfiguration.vue | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/breeding-insight/service/SharedOntologyService.ts b/src/breeding-insight/service/SharedOntologyService.ts index 49531acf6..a1578a8c2 100644 --- a/src/breeding-insight/service/SharedOntologyService.ts +++ b/src/breeding-insight/service/SharedOntologyService.ts @@ -21,6 +21,7 @@ import {TraitDAO} from "@/breeding-insight/dao/TraitDAO"; import {ValidationErrorService} from "@/breeding-insight/service/ValidationErrorService"; import {SharedOntologyDAO} from "@/breeding-insight/dao/SharedOntologyDAO"; import {SharedProgramRequest} from "@/breeding-insight/model/SharedProgramRequest"; +import {ValidationError} from "@/breeding-insight/model/errors/ValidationError"; export class SharedOntologyService { @@ -55,9 +56,12 @@ export class SharedOntologyService { } return [sharedPrograms, metadata]; } catch (error) { - // TODO: There is also a potentional validation error thrown here if (error.response && error.response.status === 404) { throw error.response.message; + } else if (error.response && error.response.status == 422) { + const parsedErrors: ValidationError | string = ValidationErrorService.parseError(error); + // Stringify and format msg + throw ValidationErrorService.stringify(parsedErrors, {includeRowNum: false, includeField: false}).join(" "); } else { throw 'An unknown error has occurred'; } diff --git a/src/breeding-insight/service/ValidationErrorService.ts b/src/breeding-insight/service/ValidationErrorService.ts index b63f82db3..8826e08a8 100644 --- a/src/breeding-insight/service/ValidationErrorService.ts +++ b/src/breeding-insight/service/ValidationErrorService.ts @@ -40,4 +40,27 @@ export class ValidationErrorService { } } } + + static stringify(errors: ValidationError | string, {includeRowNum, includeField}: {includeRowNum: boolean, includeField: boolean}) { + + const formattedErrors = []; + const isValidationError = errors instanceof ValidationError; + if (isValidationError){ + const validationErrors = errors as ValidationError; + if (validationErrors.rowErrors) { + for (const error of validationErrors.rowErrors){ + if (error.errors) { + for (const fieldError of error.errors){ + let msg = includeField ? `${fieldError.field}: ${fieldError.errorMessage}` : fieldError.errorMessage; + msg += includeRowNum ? ` in row ${error.rowIndex}` : ''; + formattedErrors.push(msg); + } + } + } + } + return formattedErrors; + } else { + return [errors]; + } + } } \ No newline at end of file diff --git a/src/components/program/SharedOntologyConfiguration.vue b/src/components/program/SharedOntologyConfiguration.vue index d8ba10678..b471b3a4c 100644 --- a/src/components/program/SharedOntologyConfiguration.vue +++ b/src/components/program/SharedOntologyConfiguration.vue @@ -219,6 +219,7 @@ export default class SharedOntologyConfiguration extends ProgramsBase { await SharedOntologyService.revokeAll(this.activeProgram!.id, programIdsToRevoke); } catch (e) { this.$emit('show-error-notification', e); + this.shareProgramProcessing = false; return; } } @@ -229,15 +230,16 @@ export default class SharedOntologyConfiguration extends ProgramsBase { await SharedOntologyService.share(this.activeProgram!.id, newSharePrograms); } catch (e) { this.$emit('show-error-notification', e); + this.shareProgramProcessing = false; return; } } // Show progress wheel on save button + this.shareProgramProcessing = false; + this.showShareModal = false; if (programIdsToRevoke.length > 0 || newSharePrograms.length > 0) { - this.shareProgramProcessing = false; this.$emit('show-success-notification', 'Changes to shared ontology successfully saved'); - this.showShareModal = false; } this.getSharedPrograms(); diff --git a/src/views/program/ProgramConfiguration.vue b/src/views/program/ProgramConfiguration.vue index f9750447b..65e804f25 100644 --- a/src/views/program/ProgramConfiguration.vue +++ b/src/views/program/ProgramConfiguration.vue @@ -17,7 +17,7 @@ From 3dca74ee03c964e891d20fbfec120b75864da2e9 Mon Sep 17 00:00:00 2001 From: Chris T Date: Mon, 28 Mar 2022 14:57:46 -0400 Subject: [PATCH 5/7] [BI-1363] share ontology: clean up --- .../program/SharedOntologyConfiguration.vue | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/program/SharedOntologyConfiguration.vue b/src/components/program/SharedOntologyConfiguration.vue index b471b3a4c..7e9abe971 100644 --- a/src/components/program/SharedOntologyConfiguration.vue +++ b/src/components/program/SharedOntologyConfiguration.vue @@ -67,7 +67,9 @@ v-if="editableMatchedPrograms.length > 0" class="mb-6" > -
Available Programs
+
+ Select/Unselect Programs to Share Ontology +