diff --git a/.env.development b/.env.development index 0f3dd2fa4..b1aa0d498 100644 --- a/.env.development +++ b/.env.development @@ -15,6 +15,7 @@ VUE_APP_LOG_LEVEL=${WEB_LOG_LEVEL} # The reference source VUE_APP_BI_REFERENCE_SOURCE=${BRAPI_REFERENCE_SOURCE} +VUE_APP_MAX_GENOTYPE_UPLOAD_MB=${MAX_GENOTYPE_UPLOAD_MB} # feature flags VUE_APP_BRAPI_VENDOR_SUBMISSION_ENABLED=${BRAPI_VENDOR_SUBMISSION_ENABLED} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..bf18a5c45 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,105 @@ +# AGENTS.md + +Instructions for automated coding agents working in this repository. + +## Scope + +These instructions apply to the whole repository. + +## Project Overview + +`bi-web` is a Vue 2.7 frontend for Breeding Insight. It uses Vue CLI 4, +TypeScript, Vuex, Vue Router, Buefy/Bulma, Vuelidate, CASL abilities, and an +axios wrapper for API calls. + +Important directories: + +- `src/views`: route-level pages. +- `src/components`: reusable Vue components and layout templates. +- `src/store`: Vuex root store and feature modules. +- `src/breeding-insight/model`: application domain models. +- `src/breeding-insight/dao`: raw API access using `src/util/api`. +- `src/breeding-insight/service`: business logic and response mapping. +- `src/breeding-insight/brapi/model`: generated BrAPI/OpenAPI models. +- `tests/unit`: Jest and Vue Test Utils unit tests. +- `tests/e2e`: Cypress end-to-end tests. +- `task`: Node task wrappers used by npm scripts. + +## Setup + +- Use npm and `package-lock.json`; do not introduce Yarn or pnpm. +- Use `npm install` for local setup, or `npm ci` for clean/install-in-CI style + runs. +- The Dockerfile uses Node 14 and npm 8.12.1. Avoid dependency or syntax changes + that require a newer runtime unless the runtime is intentionally updated. +- `npm run githooks` configures the repository's commit message hook. +- Local environment overrides belong in ignored `.env.local` or `.env.*.local` + files, not in tracked env files. + +## Common Commands + +- `npm run serve`: start the dev server. The default port is `8080`; `PORT` + overrides it. +- `npm run build`: production build. The task wrapper runs npm audit checks + before building. +- `npm run lint`: run Vue CLI ESLint. +- `npm run test:unit`: run existing Jest unit tests only when explicitly + requested. +- `npm run test:e2e`: run existing Cypress e2e tests only when explicitly + requested. +- `npm run test:accessibility`: run pa11y accessibility checks. This expects + the dev server to be running and `task/.pa11yTargets.json` to exist; use it + only when explicitly requested. + +This repository does not add or maintain tests as part of normal feature or bug +work. Do not create new tests, update existing tests, add test-only selectors, +or spend time repairing unrelated test failures unless the user explicitly asks +for test work. Prefer `npm run lint`, `npm run build`, or a focused manual smoke +check for verification when practical. + +## Coding Conventions + +- Preserve the existing Apache 2.0 notice header when editing files that have + it. Add the same header to new `.ts`, `.js`, and `.vue` source files. +- Keep TypeScript strictness in mind. Prefer explicit domain models and local + service/DAO patterns over loose `any` unless surrounding code already forces + it. +- Use the `@/` alias for imports from `src` when consistent with nearby files. +- Vue components commonly use class-style components with + `vue-property-decorator`; match the surrounding component style. +- ESLint requires long-form Vue directives: use `v-bind:` and `v-on:` instead + of shorthand in templates. +- Keep API calls centralized through `src/util/api`. DAOs should make HTTP + requests, while services should handle application mapping and user-facing + error logic. +- `src/breeding-insight/brapi/model` files are generated by Swagger/OpenAPI. + Do not hand-edit them unless the task explicitly calls for it. +- Reuse existing layouts in `src/components/layouts` and visual conventions + from the authenticated `/style-guide` page. Prefer existing Buefy/Bulma + patterns and the configured Feather icon pack. + +## Configuration Notes + +`vue.config.js` derives frontend runtime values from environment variables: + +- `VUE_APP_BI_API_ROOT` defaults to `http://localhost`. +- `VUE_APP_BI_API_V1_PATH` is computed as `${VUE_APP_BI_API_ROOT}/v1`. +- `VUE_APP_LOG_LEVEL` defaults to `error`. +- `VUE_APP_BRAPI_VENDOR_SUBMISSION_ENABLED` and + `VUE_APP_ALTERNATE_AUTHENTICATION_ENABLED` are enabled only when set to the + string `true`. + +`.env.development` maps these values from shell environment variables such as +`API_BASE_URL`, `SANDBOX_MODE`, and `WEB_LOG_LEVEL`. + +## Working Safely + +- Check `git status --short` before editing and do not overwrite unrelated + local changes. +- Do not commit generated output, `node_modules`, `dist`, local env files, IDE + files, Cypress screenshots/videos, or task logs. +- Do not change `package-lock.json` unless dependency changes are intentional. +- Keep changes scoped to the requested behavior. Avoid broad refactors unless + they are required to make the requested change safely. +- If backend behavior is relevant, make the frontend assumption explicit in the + code review or final notes; this repository only contains the web client. diff --git a/package.json b/package.json index 65b6f0db8..1ad7c02f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bi-web", - "version": "v1.3.0", + "version": "v1.4.0+989", "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.3.0" + "versionInfo": "https://github.com/Breeding-Insight/bi-web/commit/d54d28db116a99e278f5c055d8868cd8d790df29" } diff --git a/src/breeding-insight/dao/GenoDAO.ts b/src/breeding-insight/dao/GenoDAO.ts index dca9a1220..6649fc5e1 100644 --- a/src/breeding-insight/dao/GenoDAO.ts +++ b/src/breeding-insight/dao/GenoDAO.ts @@ -17,17 +17,18 @@ import * as api from '@/util/api'; import { BiResponse, Response } from '@/breeding-insight/model/BiResponse'; +import { PaginationQuery } from '@/breeding-insight/model/PaginationQuery'; +import { GenotypeImportFilters, GenotypeImportSort } from '@/breeding-insight/model/Sort'; export class GenoDAO { - static async uploadData(programId: string, experimentId: string, file: File): Promise { + static async uploadData(programId: string, submissionId: string, file: File): Promise { var formData = new FormData(); formData.append("file", file); - formData.append("filename", file.name); const {data} = await api.call({ - url: `${process.env.VUE_APP_BI_API_V1_PATH}/programs/${programId}/experiments/${experimentId}/geno/import`, + url: `${process.env.VUE_APP_BI_API_V1_PATH}/programs/${programId}/submissions/${submissionId}/geno/import`, method: 'post', data: formData} ) as Response; @@ -42,4 +43,25 @@ export class GenoDAO { return new BiResponse(data); } -} \ No newline at end of file + + static async fetchGenotypeImports( + programId: string, + {page, pageSize}: PaginationQuery, + {field, order}: GenotypeImportSort, + filters: GenotypeImportFilters + ): Promise { + const {data} = await api.call({ + url: `${process.env.VUE_APP_BI_API_V1_PATH}/programs/${programId}/geno/imports`, + method: 'get', + params: { + ...filters, + page, + pageSize, + sortField: field, + sortOrder: order + } + }) as Response; + + return new BiResponse(data); + } +} diff --git a/src/breeding-insight/model/ExperimentExportOptions.ts b/src/breeding-insight/model/ExperimentExportOptions.ts index 41a9f3c2b..bd694ba3b 100644 --- a/src/breeding-insight/model/ExperimentExportOptions.ts +++ b/src/breeding-insight/model/ExperimentExportOptions.ts @@ -24,7 +24,7 @@ export class ExperimentExportOptions { public fileExtension: string = FileTypeOption.xls.id; public datasetId: string; public environments: string[] = []; - public allEnvironments: boolean = false; + public allEnvironments: boolean = true; public includeTimestamps: string = 'No'; public timestampsTrueFalseString(): string { diff --git a/src/breeding-insight/model/GenotypeImport.ts b/src/breeding-insight/model/GenotypeImport.ts new file mode 100644 index 000000000..76b2147c1 --- /dev/null +++ b/src/breeding-insight/model/GenotypeImport.ts @@ -0,0 +1,44 @@ +/* + * 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 GenotypeImport { + genotypeImportId?: string; + sampleSubmissionId?: string; + projectNameForSampleSubmission?: string; + sampleSubmissionCreatedBy?: string; + genotypingFileName?: string; + genotypingImportDate?: string; + genotypingImportBy?: string; + + constructor({ + genotypeImportId, + sampleSubmissionId, + projectNameForSampleSubmission, + sampleSubmissionCreatedBy, + genotypingFileName, + genotypingImportDate, + genotypingImportBy + }: GenotypeImport = {}) { + this.genotypeImportId = genotypeImportId; + this.sampleSubmissionId = sampleSubmissionId; + this.projectNameForSampleSubmission = projectNameForSampleSubmission; + this.sampleSubmissionCreatedBy = sampleSubmissionCreatedBy; + this.genotypingFileName = genotypingFileName; + this.genotypingImportDate = genotypingImportDate; + this.genotypingImportBy = genotypingImportBy; + } +} diff --git a/src/breeding-insight/model/Sort.ts b/src/breeding-insight/model/Sort.ts index 08e81aa2d..6c6652204 100644 --- a/src/breeding-insight/model/Sort.ts +++ b/src/breeding-insight/model/Sort.ts @@ -168,6 +168,7 @@ export enum GermplasmSortField { Pedigree = "pedigree", FemaleParent = "femaleParentGID", MaleParent = "maleParentGID", + ExternalUID = "externalUID", CreatedDate = "createdDate", UserName = "createdByUserName" } @@ -217,4 +218,24 @@ export class GermplasmListSort { this.field = field; this.order = order; } -} \ No newline at end of file +} + +export enum GenotypeImportSortField { + ProjectNameForSampleSubmission = 'projectNameForSampleSubmission', + SampleSubmissionCreatedBy = 'sampleSubmissionCreatedBy', + GenotypingFileName = 'genotypingFileName', + GenotypingImportDate = 'genotypingImportDate', + GenotypingImportBy = 'genotypingImportBy' +} + +export type GenotypeImportFilters = Partial>; + +export class GenotypeImportSort { + field: GenotypeImportSortField; + order: SortOrder; + + constructor(field: GenotypeImportSortField, order: SortOrder) { + this.field = field; + this.order = order; + } +} diff --git a/src/breeding-insight/model/import/germplasm/ExternalUID.ts b/src/breeding-insight/model/import/germplasm/ExternalUID.ts index 4ca4d54fa..74b53c61b 100644 --- a/src/breeding-insight/model/import/germplasm/ExternalUID.ts +++ b/src/breeding-insight/model/import/germplasm/ExternalUID.ts @@ -18,17 +18,16 @@ export class ExternalUID { /** - * Get ExternalUID value from germplasm BrAPI external references array based - * on the seedSource value + * Get ExternalUID value from germplasm BrAPI external references array using + * the canonical External UID reference source. * * @param externalReferences - * @param source */ - public static getExternalUIDFromExternalReferences(externalReferences: Array, source : string) : string | undefined { - if (externalReferences === undefined || source === undefined) { + public static getExternalUIDFromExternalReferences(externalReferences: Array) : string | undefined { + if (externalReferences === undefined) { return undefined; } - const externalUID = externalReferences.find( ({ referenceSource }) => referenceSource === source ); + const externalUID = externalReferences.find( ({ referenceSource }) => referenceSource === "External UID" ); if (externalUID !== undefined) { return externalUID.referenceID; } else diff --git a/src/breeding-insight/service/GenoService.ts b/src/breeding-insight/service/GenoService.ts index e332c2ec7..25191a13a 100644 --- a/src/breeding-insight/service/GenoService.ts +++ b/src/breeding-insight/service/GenoService.ts @@ -16,21 +16,24 @@ */ import { GenoDAO } from '@/breeding-insight/dao/GenoDAO'; -import { BiResponse } from '@/breeding-insight/model/BiResponse'; +import { BiResponse, Metadata } from '@/breeding-insight/model/BiResponse'; import { ImportResponse } from '@/breeding-insight/model/import/ImportResponse'; import { GermplasmGenotype } from '@/breeding-insight/model/GermplasmGenotype'; +import { GenotypeImport } from '@/breeding-insight/model/GenotypeImport'; +import { PaginationQuery } from '@/breeding-insight/model/PaginationQuery'; +import { GenotypeImportFilters, GenotypeImportSort } from '@/breeding-insight/model/Sort'; export class GenoService { - static async uploadData(programId: string, experimentId: string, file: File): Promise { + static async uploadData(programId: string, submissionId: string, file: File): Promise { if (!programId) { throw 'Program ID not provided'; } - if (!experimentId) { - throw 'Experiment ID not provided'; + if (!submissionId) { + throw 'Submission ID not provided'; } - const response: BiResponse = await GenoDAO.uploadData(programId, experimentId, file); + const response: BiResponse = await GenoDAO.uploadData(programId, submissionId, file); const data: any = response.result; return new ImportResponse(data); } @@ -47,4 +50,26 @@ export class GenoService { return resp.result as GermplasmGenotype; } -} \ No newline at end of file + + static async fetchGenotypeImports( + programId: string, + paginationQuery: PaginationQuery, + sort: GenotypeImportSort, + filters: GenotypeImportFilters + ): Promise<[GenotypeImport[], Metadata]> { + if (!programId) { + throw 'Program ID not provided'; + } + + const response = await GenoDAO.fetchGenotypeImports(programId, paginationQuery, sort, filters); + const responseData = response.result && response.result.data ? response.result.data : response.result; + + if (!Array.isArray(responseData)) { + return [[], response.metadata]; + } + + const genotypeImports = responseData.map((record: GenotypeImport) => new GenotypeImport(record)); + + return [genotypeImports, response.metadata]; + } +} diff --git a/src/breeding-insight/utils/GermplasmUtils.ts b/src/breeding-insight/utils/GermplasmUtils.ts index 5415fe46d..2c66c6b99 100644 --- a/src/breeding-insight/utils/GermplasmUtils.ts +++ b/src/breeding-insight/utils/GermplasmUtils.ts @@ -18,6 +18,7 @@ import moment from "moment"; import {Germplasm} from "@/breeding-insight/brapi/model/germplasm"; import {MOMENT_BRAPI_DATE_FORMAT} from "@/breeding-insight/utils/BrAPIDateTime"; +import {ExternalReferences} from "@/breeding-insight/brapi/model/externalReferences"; // The moment.js interpretable format for Date values sent and received via the BI API. export const MOMENT_DATE_PERSISTED_FORMAT = 'DD/MM/YYYY h:mm:ss'; @@ -25,8 +26,8 @@ export const MOMENT_DATE_PERSISTED_FORMAT = 'DD/MM/YYYY h:mm:ss'; export class GermplasmUtils { static getExternalUID(germplasm: Germplasm): string | undefined { let val; - if (germplasm.externalReferences && germplasm.seedSource) { - val = germplasm.externalReferences!.filter(ref => ref.referenceSource == germplasm.seedSource!) + if (germplasm.externalReferences) { + val = germplasm.externalReferences!.filter(ref => ref.referenceSource == "External UID") .map(ref => ref.referenceID); return val ? val[0]: ""; } diff --git a/src/components/VueFeatherIconPack.vue b/src/components/VueFeatherIconPack.vue index 6453c108c..2f6c49cd5 100644 --- a/src/components/VueFeatherIconPack.vue +++ b/src/components/VueFeatherIconPack.vue @@ -27,6 +27,8 @@ import { ArrowDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, + DownloadIcon, + UploadIcon, LogOutIcon, UserIcon} from "vue-feather-icons"; @@ -39,7 +41,9 @@ export default { ArrowUpIcon, ArrowDownIcon, LogOutIcon, - UserIcon + UserIcon, + DownloadIcon, + UploadIcon }, props: { icon: [String, Array], diff --git a/src/components/germplasm/GermplasmGenotypeView.vue b/src/components/germplasm/GermplasmGenotypeView.vue index 12d67a736..42324201a 100644 --- a/src/components/germplasm/GermplasmGenotypeView.vue +++ b/src/components/germplasm/GermplasmGenotypeView.vue @@ -27,7 +27,7 @@
-
- @@ -182,6 +183,13 @@ export default class GermplasmGenotypeView extends GermplasmBase { } this.currentCallSetId = callsetId; + + //Currently for memory usage fetching data visualization is deprecated and so calls aren't retrieved, which results in a null error. + //This skips functionality dependent on calls while keeping the code for future implementations o + if (!this.genotypeData!.calls!) { + this.loading = false; + return; + } const callsByCallset: Map> = new Map(Object.entries(this.genotypeData!.calls!)); const callSet = callsByCallset.get(callsetId); diff --git a/src/components/germplasm/GermplasmTable.vue b/src/components/germplasm/GermplasmTable.vue index b47df5684..ff9d9a385 100644 --- a/src/components/germplasm/GermplasmTable.vue +++ b/src/components/germplasm/GermplasmTable.vue @@ -47,6 +47,9 @@ v-bind:germplasmGID="Pedigree.parsePedigreeStringWithUnknowns(props.row.data.pedigree,props.row.data.additionalInfo.femaleParentUnknown,props.row.data.additionalInfo.maleParentUnknown, props.row.data.accessionNumber).maleParent" > + + {{ GermplasmUtils.getExternalUID(props.row.data) }} + {{ GermplasmUtils.getCreatedDate(props.row.data) }} @@ -134,6 +137,7 @@ export default class GermplasmTable extends Vue { 'seedSource': GermplasmSortField.SeedSource, 'femaleParentGID': GermplasmSortField.FemaleParent, 'maleParentGID': GermplasmSortField.MaleParent, + 'externalUID' : GermplasmSortField.ExternalUID, 'createdDate': GermplasmSortField.CreatedDate, 'createdByUserName': GermplasmSortField.UserName, }; diff --git a/src/components/layouts/UserSideBarLayout.vue b/src/components/layouts/UserSideBarLayout.vue index 2498cf42c..9c7b9f5e7 100644 --- a/src/components/layouts/UserSideBarLayout.vue +++ b/src/components/layouts/UserSideBarLayout.vue @@ -140,6 +140,16 @@ Sample Management +
  • + + Genotyping + +
  • + + + + diff --git a/src/views/import/ImportGeno.vue b/src/views/import/ImportGeno.vue index f21358354..cb84d499d 100644 --- a/src/views/import/ImportGeno.vue +++ b/src/views/import/ImportGeno.vue @@ -25,7 +25,7 @@
    Before You Import...
    - Ensure that Sample IDs match to an Exp Unit ID within the chosen experiment + Ensure that the Sample IDs in the .vcf import file match to Sample IDs within the chosen sample submission.
    @@ -34,7 +34,7 @@
    - Your import is being processed. You can view its progress by going to the Jobs page. + Leaving this page will interrupt file processing. Please wait until you receive a success message.
    @@ -46,17 +46,18 @@ v-bind:save-button-label="'Import'" v-bind:show-cancel-button="false" v-on:submit="save" - v-on:cancel="cancel" + v-on:cancel="clearForm" v-on:show-error-notification="$emit('show-error-notification', $event)" > @@ -83,22 +87,20 @@ import { Component } from 'vue-property-decorator'; import ProgramsBase from '@/components/program/ProgramsBase.vue'; import { DataFormEventBusHandler } from '@/components/forms/DataFormEventBusHandler'; -import { Trial } from '@/breeding-insight/model/Trial'; import { mapGetters } from 'vuex'; import { Program } from '@/breeding-insight/model/Program'; -import { BrAPIService, BrAPIType } from '@/breeding-insight/service/BrAPIService'; -import { SortOrder } from '@/breeding-insight/model/Sort'; import NewDataForm from '@/components/forms/NewDataForm.vue'; import BasicInputField from '@/components/forms/BasicInputField.vue'; import BasicSelectField from '@/components/forms/BasicSelectField.vue'; import FileSelector from '@/components/file-import/FileSelector.vue'; -import { BrAPIUtils } from '@/breeding-insight/utils/BrAPIUtils'; import { required } from 'vuelidate/lib/validators'; import { ImportResponse } from '@/breeding-insight/model/import/ImportResponse'; import { GenoService } from '@/breeding-insight/service/GenoService'; import { DEACTIVATE_ALL_NOTIFICATIONS } from '@/store/mutation-types'; import { ImportMappingConfig } from '@/breeding-insight/model/import/ImportMapping'; import { ImportService } from '@/breeding-insight/service/ImportService'; +import { SampleSubmission } from '@/breeding-insight/model/SampleSubmission'; +import { SampleSubmissionService } from '@/breeding-insight/service/SampleSubmissionService'; @Component({ components: { @@ -112,64 +114,109 @@ import { ImportService } from '@/breeding-insight/service/ImportService'; }) export default class ImportExperiment extends ProgramsBase { private activeProgram?: Program; - private experimentOptions: Array = []; + private submissionOptions: Array = []; private importState: DataFormEventBusHandler = new DataFormEventBusHandler(); private currentImport?: ImportResponse = new ImportResponse({}); private systemImportTemplateId?: string; + private fileSelectorKey: number = 0; + private defaultMaxGenotypeUploadMb: number = 800; upload: Upload = new Upload({}); uploadValidations = { - experimentId: {required}, + submissionId: {required}, file: {required} } + private getMaxGenotypeUploadLimitMb(): number { + const configuredLimit = Number(process.env.VUE_APP_MAX_GENOTYPE_UPLOAD_MB); + if (!Number.isFinite(configuredLimit) || configuredLimit <= 0) { + return this.defaultMaxGenotypeUploadMb; + } + return configuredLimit; + } + + private getMaxGenotypeUploadBytes(): number { + return this.getMaxGenotypeUploadLimitMb() * 1024 * 1024; + } + + private showMaxGenotypeUploadError() { + this.$emit( + 'show-error-notification', + `Uploaded file exceeds the maximum file size limit of ${this.getMaxGenotypeUploadLimitMb()} MB.` + ); + } + + handleFileSelected(file: File) { + if (file.size > this.getMaxGenotypeUploadBytes()) { + this.clearFile(); + this.fileSelectorKey += 1; + this.showMaxGenotypeUploadError(); + return; + } + + this.upload.file = file; + } + mounted() { - this.loadExperiments(); + this.loadSampleSubmissions(); this.getSystemImportTemplateMapping(); } - async loadExperiments () { - let expResponse = await BrAPIService.get(BrAPIType.EXPERIMENT, this.activeProgram!.id!, { field: undefined, order: SortOrder.Ascending }, { page: 0, pageSize: 1000 }, {"metadata": false}); - if (expResponse.result && expResponse.result.data) { - this.experimentOptions = expResponse.result.data.map((exp: Trial) => { - let breedingInsightId = BrAPIUtils.getBreedingInsightId(exp.externalReferences!, "/trials"); - return new ExperimentOption({ - id: breedingInsightId!, - name: exp.trialName! + async loadSampleSubmissions() { + const submissions = await SampleSubmissionService.getProgramSampleSubmissions(this.activeProgram!.id!); + this.submissionOptions = submissions.map((submission: SampleSubmission) => { + return new SubmissionOption({ + id: submission.id!, + name: submission.name! }); }); - this.$log.debug(JSON.stringify(this.experimentOptions)); + this.$log.debug(JSON.stringify(this.submissionOptions)); } - } async save() { try { this.$store.commit( DEACTIVATE_ALL_NOTIFICATIONS ); - this.currentImport = await GenoService.uploadData(this.activeProgram!.id!, this.upload.experimentId!, this.upload.file!); + + if (this.upload.file!.size > this.getMaxGenotypeUploadBytes()) { + this.showMaxGenotypeUploadError(); + return; + } + + this.currentImport = await GenoService.uploadData(this.activeProgram!.id!, this.upload.submissionId!, this.upload.file!); const response: ImportResponse = await this.getDataUpload(); if (response.progress!.statuscode == 500) { this.$emit('show-error-notification', 'An unknown error has occurred when processing your import.'); } else if (response.progress!.statuscode !== 200) { - this.$emit('show-error-notification', `Error: ${response.progress!.message}`); + this.$emit('show-error-notification', `${response.progress!.message}`); } else { - this.$emit('show-success-notification', `Genotypic data has uploaded and is being processed. Check the 'Jobs' page for processing status`); + this.$emit('show-success-notification', `Imported genotype data has been added to ${this.activeProgram!.name!}`); + this.clearForm(); } } catch (e) { - if (e.response && e.response.statusText && e.response.status != 500) { + if (e.response && e.response.status == 413) { + this.$emit('show-error-notification', 'Uploaded file exceeds the maximum allowed file size.'); + } else if (e.response && e.response.statusText && e.response.status != 500) { this.$emit('show-error-notification', e.response.statusText); } else { this.$emit('show-error-notification', 'An unknown error has occurred when uploading your import.'); } } finally { + if (this.upload.file) { + this.clearFile(); + } this.importState.bus.$emit(DataFormEventBusHandler.SAVE_COMPLETE_EVENT); } } - cancel() { + clearForm() { this.upload = new Upload({}); } + clearFile() { + this.upload.clearFile(); + } + async getSystemImportTemplateMapping() { let importMappings: ImportMappingConfig[]; try { @@ -208,24 +255,28 @@ export default class ImportExperiment extends ProgramsBase { } -class ExperimentOption { +class SubmissionOption { id: string; name: string; - constructor({id, name}: ExperimentOption) { + constructor({id, name}: SubmissionOption) { this.id = id; this.name = name; } } class Upload { - experimentId?: string; + submissionId?: string; file?: File; - constructor ({experimentId, file}: Upload) { - this.experimentId = experimentId; + constructor ({submissionId, file}: Upload) { + this.submissionId = submissionId; this.file = file; } + + clearFile() { + this.file = undefined; + } } diff --git a/src/views/import/ImportGermplasm.vue b/src/views/import/ImportGermplasm.vue index 6fe45e0ca..aeecdb74d 100644 --- a/src/views/import/ImportGermplasm.vue +++ b/src/views/import/ImportGermplasm.vue @@ -31,7 +31,7 @@