diff --git a/.idea/modules/frontend/gpds.frontend.iml b/.idea/modules/frontend/gpds.frontend.iml index c4d56279daf10da4cf6872c8057ec3d0071bcc49..58c4911e778cbb7018cdaeb736a573a83324d2ec 100644 --- a/.idea/modules/frontend/gpds.frontend.iml +++ b/.idea/modules/frontend/gpds.frontend.iml @@ -11,4 +11,4 @@ <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> </component> -</module> \ No newline at end of file +</module> diff --git a/frontend/karma-junit-tests-report/TESTS-HeadlessChrome_0.0.0_(Linux_0.0.0).xml b/frontend/karma-junit-tests-report/TESTS-HeadlessChrome_0.0.0_(Linux_0.0.0).xml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2327dddfaf2dc6e019094e2968b3b63bf5e8dd7c..14a78b22911465fa5c6da75af3ffdcc86d47e33d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6672,6 +6672,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3814d1f71e95bd6f9a0ac10834666b48f5a735bf..1d47fc2f21fe70e5dabe27dd6f2c8eff30d7f977 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,8 +27,11 @@ "bootstrap": "4.1.3", "core-js": "2.5.7", "font-awesome": "4.7.0", + "moment": "2.24.0", "leaflet": "1.3.4", "leaflet.markercluster": "1.4.1", + "ngx-moment": "3.3.0", + "popper.js": "1.14.6", "rxjs": "6.3.3", "trait-ontology-widget": "git+https://github.com/gnpis/trait-ontology-widget.git#v2.2.1", "zone.js": "0.8.26" diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 12a89ab14258d673e2293065a49fa586b51c343a..baefb2f81feef7dc3b34054582c20fd86b921c24 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -6,7 +6,7 @@ import { StudyCardComponent } from './study-card/study-card.component'; import { SiteCardComponent } from './site-card/site-card.component'; const routes: Routes = [ - { path: 'germplasm/:id', component: GermplasmCardComponent }, + { path: 'germplasm', component: GermplasmCardComponent }, { path: 'studies/:id', component: StudyCardComponent }, { path: 'sites/:id', component: SiteCardComponent }, { path: '', component: ResultPageComponent }, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f3486f6fcb9ba85c2cabf2a77e9fe73d513e47b0..ce2119956592d018f78cc0ae550b234dda1dc0d6 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,6 +1,5 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; - import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { FormComponent } from './form/form.component'; @@ -11,7 +10,7 @@ import { SiteCardComponent } from './site-card/site-card.component'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { NavbarComponent } from './navbar/navbar.component'; import { MapComponent } from './map/map.component'; -import { NgbAlertModule, NgbDropdownModule, NgbPaginationModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbAlertModule, NgbDropdownModule, NgbPaginationModule, NgbPopoverModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { SuggestionFieldComponent } from './form/suggestion-field/suggestion-field.component'; import { DocumentComponent } from './result-page/document/document.component'; @@ -23,6 +22,7 @@ import { CardRowComponent } from './card-row/card-row.component'; import { CardSectionComponent } from './card-section/card-section.component'; import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component'; import { CardTableComponent } from './card-table/card-table.component'; +import { MomentModule } from 'ngx-moment'; @NgModule({ declarations: [ @@ -47,14 +47,15 @@ import { CardTableComponent } from './card-table/card-table.component'; imports: [ BrowserModule, AppRoutingModule, + HttpClientModule, NgbTypeaheadModule, NgbPaginationModule, NgbAlertModule, NgbDropdownModule, + NgbPopoverModule, FormsModule, ReactiveFormsModule, - HttpClientModule, - + MomentModule ], providers: [ { provide: HTTP_INTERCEPTORS, useExisting: ErrorInterceptorService, multi: true } diff --git a/frontend/src/app/brapi.service.spec.ts b/frontend/src/app/brapi.service.spec.ts index 898d0c90a492a020fb63d66c71df9f865ac6a6ee..aecbcbbe4287507d1d5241348d4dd127d88c7a0f 100644 --- a/frontend/src/app/brapi.service.spec.ts +++ b/frontend/src/app/brapi.service.spec.ts @@ -1,7 +1,4 @@ -import { TestBed } from '@angular/core/testing'; - import { BrapiService } from './brapi.service'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { BrapiContacts, BrapiGermplasm, @@ -13,23 +10,21 @@ import { BrapiTrial } from './models/brapi.model'; import { DataDiscoverySource } from './models/data-discovery.model'; +import { + BrapiDescriptor, + BrapiDonor, + BrapiGermplasmAttributes, + BrapiGermplasmPedigree, + BrapiGermplasmProgeny, + BrapiSet, + BrapiSibling +} from './models/brapi.germplasm.model'; +import { Germplasm, GermplasmData, GermplasmResult, Institute, Origin, Site } from './models/gnpis.germplasm.model'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; describe('BrapiService', () => { - let brapiService: BrapiService; - let http: HttpTestingController; - - beforeEach(() => TestBed.configureTestingModule({ - imports: [HttpClientTestingModule] - })); - - beforeEach(() => { - brapiService = TestBed.get(BrapiService); - http = TestBed.get(HttpTestingController); - }); - - afterAll(() => http.verify()); - const location: BrapiLocation = { locationDbId: '1', locationName: 'loc1', @@ -141,6 +136,163 @@ describe('BrapiService', () => { 'schema:image': null }; + const brapiSite: Site = { + latitude: null, + longitude: null, + siteId: null, + siteName: null, + siteType: null + }; + + const brapiSibling: BrapiSibling = { + germplasmDbId: 'frere1', + defaultDisplayName: 'frere1' + }; + + const brapiDescriptor: BrapiDescriptor = { + name: 'caracteristique1', + pui: '12', + value: '32' + }; + + const brapiGermplasmPedigree: GermplasmResult<BrapiGermplasmPedigree> = { + result: { + germplasmDbId: 'test', + defaultDisplayName: '12', + pedigree: null, + crossingPlan: null, + crossingYear: null, + familyCode: null, + parent1DbId: '11', + parent1Name: 'parent', + parent1Type: 'SELF', + parent2DbId: null, + parent2Name: null, + parent2Type: null, + siblings: [brapiSibling] + } + }; + + const brapiGermplasmProgeny: GermplasmResult<BrapiGermplasmProgeny> = { + result: { + germplasmDbId: 'test', + defaultDisplayName: '11', + progeny: [brapiSibling] + } + + }; + + const institute: Institute = { + instituteName: 'urgi', + instituteCode: 'inra', + acronym: 'urgi', + organisation: 'inra', + instituteType: 'labo', + webSite: 'www.labo.fr', + address: '12', + logo: null + }; + const origin: Origin = { + institute: institute, + germplasmPUI: '12', + accessionNumber: '12', + accessionCreationDate: '1993', + materialType: 'feuille', + collectors: null, + registrationYear: '1996', + deregistrationYear: '1912', + distributionStatus: null + }; + + const brapiDonor: BrapiDonor = { + donorInstitute: institute, + germplasmPUI: '12', + accessionNumber: '12', + donorInstituteCode: 'urgi' + }; + + const brapiSet: BrapiSet = { + germplasmCount: 12, + germplasmRef: null, + id: 12, + name: 'truc', + type: 'plan' + }; + + const brapiGermplasmAttributes: GermplasmResult<GermplasmData<BrapiGermplasmAttributes[]>> = { + result: { + data: [{ + attributeName: 'longueur', + value: '30' + }] + } + }; + + const germplasmTest: Germplasm = { + url: 'www.cirad.fr', + source: 'cirad', + germplasmDbId: 'test', + defaultDisplayName: 'test', + accessionNumber: 'test', + germplasmName: 'test', + germplasmPUI: 'doi:1256', + pedigree: 'tree', + seedSource: 'inra', + synonyms: null, + commonCropName: null, + instituteCode: 'grc12', + instituteName: 'institut', + biologicalStatusOfAccessionCode: null, + countryOfOriginCode: null, + typeOfGermplasmStorageCode: null, + taxonIds: null, + genus: 'genre', + species: 'esp', + speciesAuthority: 'L', + subtaxa: null, + subtaxaAuthority: null, + donors: [brapiDonor], + acquisitionDate: null, + genusSpecies: null, + genusSpeciesSubtaxa: null, + taxonSynonyms: ['pomme', 'api'], + taxonCommonNames: ['pomme', 'api'], + geneticNature: null, + comment: null, + photo: null, + holdingInstitute: institute, + holdingGenbank: institute, + presenceStatus: null, + children: null, + descriptors: [brapiDescriptor], + originSite: null, + collectingSite: null, + evaluationSites: null, + collector: origin, + breeder: origin, + distributors: [origin], + panel: [brapiSet], + collection: [brapiSet], + population: [brapiSet] + }; + + const germplasmResultTest = { + result: germplasmTest + }; + + let brapiService: BrapiService; + let http: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule] + }); + brapiService = TestBed.get(BrapiService); + http = TestBed.get(HttpTestingController); + }); + + afterEach(() => http.verify()); + it('should fetch the study', () => { let fetchedStudy: BrapiResult<BrapiStudy>; const studyDbId: string = searchStudy.result.studyDbId; @@ -154,7 +306,7 @@ describe('BrapiService', () => { }); - it('should fetch the germplasm', () => { + it('should fetch the germplasm of studies call', () => { let fetchedGermplasm: BrapiResults<BrapiGermplasm>; const studyDbId: string = searchStudy.result.studyDbId; @@ -211,4 +363,46 @@ describe('BrapiService', () => { expect(actualLocation).toEqual(location); }); + it('should fetch the pedigree', () => { + + let fetchedGermplasmPedigree: GermplasmResult<BrapiGermplasmPedigree>; + const germplasmDbId: string = brapiGermplasmPedigree.result.germplasmDbId; + brapiService.germplasmPedigree(germplasmDbId).subscribe(response => { + fetchedGermplasmPedigree = response; + }); + http.expectOne(`brapi/v1/germplasm/${germplasmDbId}/pedigree`) + .flush(brapiGermplasmPedigree); + + expect(fetchedGermplasmPedigree).toEqual(brapiGermplasmPedigree); + + }); + + it('should fetch the germplasm progeny', () => { + + let fetchedGermplasmProgeny: GermplasmResult<BrapiGermplasmProgeny>; + const germplasmDbId: string = brapiGermplasmProgeny.result.germplasmDbId; + brapiService.germplasmProgeny(germplasmDbId).subscribe(response => { + fetchedGermplasmProgeny = response; + }); + http.expectOne(`brapi/v1/germplasm/${germplasmDbId}/progeny`) + .flush(brapiGermplasmProgeny); + + expect(fetchedGermplasmProgeny).toEqual(brapiGermplasmProgeny); + + }); + + it('should fetch the germplasm attributes', () => { + + let fetchedGermplasmAttributes: GermplasmResult<GermplasmData<BrapiGermplasmAttributes[]>>; + const germplasmDbId: string = germplasmTest.germplasmDbId; + brapiService.germplasmAttributes(germplasmDbId).subscribe(response => { + fetchedGermplasmAttributes = response; + }); + http.expectOne(`brapi/v1/germplasm/${germplasmDbId}/attributes`) + .flush(brapiGermplasmAttributes); + + expect(fetchedGermplasmAttributes).toEqual(brapiGermplasmAttributes); + + }); + }); diff --git a/frontend/src/app/brapi.service.ts b/frontend/src/app/brapi.service.ts index be27a43a0e39d9dfe0ea5a302973ee38a6e48a0b..bed9d9a83435a0485e9454ff4c1e317d51f0f891 100644 --- a/frontend/src/app/brapi.service.ts +++ b/frontend/src/app/brapi.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; +import { Germplasm, GermplasmData, GermplasmResult } from './models/gnpis.germplasm.model'; import { BrapiGermplasm, BrapiLocation, @@ -10,6 +11,7 @@ import { BrapiStudy, BrapiTrial } from './models/brapi.model'; +import { BrapiGermplasmAttributes, BrapiGermplasmPedigree, BrapiGermplasmProgeny } from './models/brapi.germplasm.model'; export const BASE_URL = 'brapi/v1'; @@ -21,41 +23,48 @@ export class BrapiService { constructor(private http: HttpClient) { } - germplasm(germplasmDbId: string): Observable<object> { - return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}`); + germplasm(germplasmDbId: string): Observable<Germplasm> { + return this.http + .get<Germplasm>(`${BASE_URL}/germplasm/${germplasmDbId}`); } - germplasmPedigree(germplasmDbId: string): Observable<object> { - return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}/pedigree`); + germplasmPedigree(germplasmDbId: string): Observable<GermplasmResult<BrapiGermplasmPedigree>> { + return this.http + .get<GermplasmResult<BrapiGermplasmPedigree>>(`${BASE_URL}/germplasm/${germplasmDbId}/pedigree`); } - germplasmProgeny(germplasmDbId: string): Observable<object> { - return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}/progeny`); + germplasmProgeny(germplasmDbId: string): Observable<GermplasmResult<BrapiGermplasmProgeny>> { + return this.http.get<GermplasmResult<BrapiGermplasmProgeny>>(`${BASE_URL}/germplasm/${germplasmDbId}/progeny`); } - germplasmAttributes(germplasmDbId: string): Observable<object> { - return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}/attributes`); + germplasmAttributes(germplasmDbId: string): Observable<GermplasmResult<GermplasmData<BrapiGermplasmAttributes[]>>> { + return this.http + .get<GermplasmResult<GermplasmData<BrapiGermplasmAttributes[]>>>(`${BASE_URL}/germplasm/${germplasmDbId}/attributes`); } study(studyDbId: string): Observable<BrapiResult<BrapiStudy>> { const options = { headers: { 'Accept': 'application/ld+json,application/json' } }; - return this.http.get<BrapiResult<BrapiStudy>>(`${BASE_URL}/studies/${studyDbId}`, options); + return this.http + .get<BrapiResult<BrapiStudy>>(`${BASE_URL}/studies/${studyDbId}`, options); } studyGermplasms(studyDbId: string): Observable<BrapiResults<BrapiGermplasm>> { - return this.http.get<BrapiResults<BrapiGermplasm>>(`${BASE_URL}/studies/${studyDbId}/germplasm`); + return this.http + .get<BrapiResults<BrapiGermplasm>>(`${BASE_URL}/studies/${studyDbId}/germplasm`); } studyObservationVariables(studyDbId: string): Observable<BrapiResults<BrapiObservationVariable>> { - return this.http.get<BrapiResults<BrapiObservationVariable>>(`${BASE_URL}/studies/${studyDbId}/observationVariables`); + return this.http + .get<BrapiResults<BrapiObservationVariable>>(`${BASE_URL}/studies/${studyDbId}/observationVariables`); } location(locationId: string): Observable<BrapiResult<BrapiLocation>> { - return this.http.get<BrapiResult<BrapiLocation>>(`${BASE_URL}/locations/${locationId}`); + return this.http + .get<BrapiResult<BrapiLocation>>(`${BASE_URL}/locations/${locationId}`); } studyTrials(trialsId: string): Observable<BrapiResult<BrapiTrial>> { - return this.http.get<BrapiResult<BrapiTrial>>(`${BASE_URL}/trials/${trialsId}`); + return this.http + .get<BrapiResult<BrapiTrial>>(`${BASE_URL}/trials/${trialsId}`); } - } diff --git a/frontend/src/app/germplasm-card/germplasm-card.component.html b/frontend/src/app/germplasm-card/germplasm-card.component.html index e6305478c6c8d9851a5f0a55b01f6591d582cb7b..5fa9f5ebf56e732ea59b0da1eedccb941fdba4d1 100644 --- a/frontend/src/app/germplasm-card/germplasm-card.component.html +++ b/frontend/src/app/germplasm-card/germplasm-card.component.html @@ -1,409 +1,663 @@ -<!-- -<h3> - Germplasm: {{ germplasm.result.defaultDisplayName }} -</h3> +<gpds-loading-spinner [loading]="loading" class="display-spinner-front rounded"></gpds-loading-spinner> -<h4>Identification</h4> -<div class="container-fluid"> - <div class="row"> - <img src="" class="img-fluid float-left w-25 p-2 table-sm" alt="" > +<ng-container *ngIf="germplasmGnpis"> + <h3> + Germplasm: {{ germplasmGnpis.germplasmName }} + </h3> - <table class="table float-right w-75 p-2 table-sm"> - <tr> - <td><b>Accession number</b></td> - <td>{{ germplasm.result.accessionNumber }}</td> - </tr> - <tr *ngIf="germplasm.result.acquisitionDate!=null"> - <td><b>Acquisition date</b></td> - <td>{{ germplasm.result.acquisitionDate }}</td> - </tr> - <tr> - <td><b>Accession name</b></td> - <td>{{ germplasm.result.defaultDisplayName }}</td> - </tr> - <tr> - <td><b>Permanent Unique Identifier</b></td> - <td> https://{{ germplasm.result.germplasmPUI }}</td> - </tr> - <tr *ngIf="germplasm.result.seedSource != null"> - <td><b>Seed source</b></td> - <td><a *ngFor="let syn of germplasm.result.seedSource"> {{ syn }}</a></td> - </tr> - <tr> - <tr *ngIf="germplasm.result.geneticNature != null"> - <td><b>Genetic nature</b></td> - <td><a *ngFor="let syn of germplasm.result.geneticNature"> {{ syn }}</a></td> - </tr> - <tr *ngIf="germplasm.result.synonyms != null"> - <td><b>Accession synonyms</b></td> - <td><a *ngFor="let syn of germplasm.result.synonyms"> {{ syn }}</a></td> - </tr> - <tr> - <td><b>Taxon name</b></td> - <td>{{ germplasm.result.genus }} {{ germplasm.result.species}} {{ germplasm.result.speciesAuthority}} {{ germplasm.result.subtaxa}}</td> - </tr> - <tr> - <td><b>Taxon common names</b></td> - <td>{{ germplasm.result.commonCropName }}</td> - </tr> - <tr> - <td><b>Taxon synonyms</b></td> - <td>{{ germplasm.result.species }}</td> - </tr> - <tr *ngIf="germplasm.result.pedigree!=null"> - <td><b>Pedigree</b></td> - <td>{{ germplasm.result.pedigree }}</td> - </tr> - <tr> - <td><b>Biological status</b></td> - <td>{{ germplasm.result.biologicalStatusOfAccessionCode }}</td> - </tr> - <tr> - <td><b>Source</b></td> - <td>{{ germplasm.result.source }}</td> - </tr> - <tr *ngIf="!germplasm.result.source.equals('URGI')"> - <td><b>External link</b></td> - <td><a>{{ germplasm.result.url }}</a></td> - </tr> - <tr> - <td><b>Comments</b></td> - <td>{{ germplasm.result.comment }}</td> - </tr> - </table> - </div> + <div class="container-fluid"> + <div class="container"> + <div class="row"> + <div class="col-md-auto field" *ngIf="germplasmGnpis.photo && germplasmGnpis.photo.thumbnailFileName != null"> + <figure class="figure"> + <img + src="{{ IMAGES_SIREGAL_URL }}/{{ germplasmGnpis.holdingGenbank.instituteCode }}/{{ germplasmGnpis.photo.thumbnailFileName }}" + class="img-fluid"> + <figcaption class="figure-caption"> + <a class="btn popovers" data-boundary="window" placement="right" [ngbPopover]="imageTemplate" + [popoverTitle]="Details" container="body"> + Click to see more details. + </a> + </figcaption> + </figure> - <div class="row"> - <h4>Holding</h4> - <table class="table table-sm"> - <tr> - <td><b>Institution name</b></td> - <td>{{ germplasm.result.holdingInstitute.instituteName }} {{ germplasm.result.holdingInstitute.organisation }}</td> - </tr> - <tr> - <td><b>Link</b></td> - <td>{{ germplasm.result.holdingInstitute.webSite }}</td> - </tr> - <tr> - <td><b>Adresse</b></td> - <td>{{ germplasm.result.holdingInstitute.address }}</td> - </tr> - <tr> - <td><b>Logo</b></td> - <td>{{ germplasm.result.holdingInstitute.logo }}</td> - </tr> - </table> + <ng-template #imageTemplate> + <div class="card ngb-popover-window "> + <img class="card-img-top" + src="{{ IMAGES_SIREGAL_URL }}/{{ germplasmGnpis.holdingGenbank.instituteCode }}/{{ germplasmGnpis.photo.fileName }}" + alt="" width="500px"> + <div class="card-body"> + <table class="table"> + <tr *ngIf="germplasmGnpis.photo.photoName"> + <th class="fieldName">Name</th> + <td class="field">{{ germplasmGnpis.photo.photoName }}</td> + </tr> + <tr *ngIf="germplasmGnpis.photo.description"> + <th class="fieldName">Description</th> + <td class="field">{{ germplasmGnpis.photo.description }}</td> + </tr> + <tr *ngIf="germplasmGnpis.photo.copyright"> + <th class="fieldName">Copyright</th> + <td class="field">{{ germplasmGnpis.photo.copyright }}</td> + </tr> + </table> + </div> + </div> + </ng-template> - <h4>Holding gene bank</h4> - <table class="table table-sm"> - <tr> - <td><b>Institution name</b></td> - <td>{{ germplasm.result.holdingGenbank }} {{ germplasm.result.organisation }}</td> - </tr> - <tr> - <td><b>Link</b></td> - <td>{{ germplasm.result.webSite }}</td> - </tr> - <tr> - <td><b>Adresse</b></td> - <td>{{ germplasm.result.address }}</td> - </tr> - <tr> - <td><b>Logo</b></td> - <td>{{ germplasm.result.logo }}</td> - </tr> - </table> - </div> + </div> + <div class="col"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Identification + </th> + </tr> + </thead> + <tr *ngIf="germplasmGnpis.accessionNumber"> + <th class="fieldName" scope="row">Accession number</th> + <td class="field">{{ germplasmGnpis.accessionNumber }}</td> + </tr> + <tr *ngIf="germplasmGnpis.acquisitionDate"> + <th class="fieldName" scope="row">Acquisition date</th> + <td + class="field">{{ germplasmGnpis.acquisitionDate | amParse:'YYYYMMDD' | amDateFormat:'YYYY-MM-DD' }}</td> + </tr> + <tr *ngIf="germplasmGnpis.germplasmName"> + <th class="fieldName" scope="row">Germplasm name</th> + <td class="field">{{ germplasmGnpis.germplasmName }}</td> + </tr> + <tr *ngIf="germplasmGnpis.germplasmPUI"> + <th class="fieldName" scope="row">Permanent Unique Identifier</th> + <td class="ellipsis field"> {{ germplasmGnpis.germplasmPUI }}</td> + </tr> + <tr *ngIf="germplasmGnpis.seedSource"> + <th class="fieldName" scope="row">Seed source</th> + <td class="field">{{ germplasmGnpis.seedSource }}</td> + </tr> + <tr *ngIf="germplasmGnpis.geneticNature"> + <th class="fieldName" scope="row">Genetic nature</th> + <td class="field">{{ germplasmGnpis.geneticNature }}</td> + </tr> + <tr *ngIf="germplasmGnpis.synonyms && germplasmGnpis.synonyms.length > 0"> + <th class="fieldName" scope="row">Accession synonyms</th> + <td class="field">{{ germplasmGnpis.synonyms.join(', ') }}</td> + </tr> + <tr> + <th class="fieldName" scope="row">Taxon</th> + <td class="field"> + <i>{{ germplasmGnpis.genus }} {{ germplasmGnpis.species }} {{ germplasmGnpis.subtaxa }}</i> + {{ germplasmGnpis.speciesAuthority ? '(' + germplasmGnpis.speciesAuthority + ')' : '' }} + </td> + </tr> - <ng-container *ngIf="germplasm.result.breeder != null"> - <h4>Breeder</h4> - <div class="row"> - <table class="table table-sm"> - <tr> - <td><b>accessionCreationDate</b></td> - <td>{{ germplasm.result.breeder.accessionCreationDate }}</td> - </tr> - <tr> - <td><b>accessionNumber</b></td> - <td>{{ germplasm.result.breeder.accessionNumber }}</td> - </tr> - <tr> - <td><b>collectors</b></td> - <td>{{ germplasm.result.breeder.collectors }}</td> - </tr> - <tr> - <td><b>deregistrationYear</b></td> - <td>{{ germplasm.result.breeder.deregistrationYear }}</td> - </tr> - <tr> - <td><b>distributionStatus</b></td> - <td>{{ germplasm.result.breeder.distributionStatus }}</td> - </tr> - <tr> - <td><b>germplasmPUI</b></td> - <td>{{ germplasm.result.breeder.germplasmPUI }}</td> - </tr> - <ng-container> - <tr> - <table> - <tr> - <td><b>institute name</b></td> - <td>{{ germplasm.result.breeder.instituteName }}</td> + <tr *ngIf="germplasmGnpis.taxonCommonNames && germplasmGnpis.taxonCommonNames.length > 0"> + <th class="fieldName" scope="row">Taxon common names</th> + <td class="ellipsis field"> {{ germplasmGnpis.taxonCommonNames.join(', ') }} + </td> + </tr> + <tr *ngIf="germplasmGnpis.taxonSynonyms && germplasmGnpis.taxonSynonyms.length > 0"> + <th class="fieldName" scope="row">Taxon synonyms</th> + <td class="scroll field"><i>{{ germplasmGnpis.taxonSynonyms.join(', ') }}</i></td> + </tr> + <tr *ngIf="germplasmGnpis.pedigree"> + <th class="fieldName" scope="row">Pedigree</th> + <td class="field">{{ germplasmGnpis.pedigree }}</td> + </tr> + <tr *ngIf="germplasmGnpis.biologicalStatusOfAccessionCode"> + <th class="fieldName" scope="row">Biological status</th> + <td class="field">{{ germplasmGnpis.biologicalStatusOfAccessionCode }}</td> + </tr> + <!--<tr> + <td>Source</td> + <td>{{ germplasmGnpis.source }}</td> + </tr> + <tr *ngIf="germplasmGnpis.source!='URGI'"> + <td>Source link</td> + <td><a>{{ germplasmGnpis.url }}</a></td> + </tr>--> + <tr *ngIf="germplasmGnpis.comment"> + <th class="fieldName" scope="row">Comments</th> + <td class="field">{{ germplasmGnpis.comment }}</td> + </tr> + </table> + </div> + </div> + </div> + + <div class="container"> + <div class="row" *ngIf="germplasmGnpis.holdingInstitute"> + <div class="col"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Holding + </th> + </tr> + </thead> + <tr> + <th class="fieldName" scope="row">Institution</th> + <td class="ellipsis field"><a class="btn popovers" data-boundary="window" placement="top" + [ngbPopover]="holdingInstituteTemplate" + [popoverTitle]="germplasmGnpis.holdingInstitute.instituteName" + container="body"> + {{ germplasmGnpis.holdingInstitute.instituteName }}</a></td> + </tr> + <tr *ngIf="germplasmGnpis.holdingGenbank.instituteName"> + <th class="fieldName" scope="row">Stock center name</th> + <ng-container *ngIf="germplasmGnpis.holdingGenbank.webSite"> + <td class="field"> + <a href="germplasmGnpis.holdingGenbank.webSite">{{ germplasmGnpis.holdingGenbank.instituteName }}</a> + </td> + </ng-container> + <ng-container *ngIf="!germplasmGnpis.holdingGenbank.webSite"> + <td class="field">{{ germplasmGnpis.holdingGenbank.instituteName }}</td> + </ng-container> + + </tr> + <tr *ngIf="germplasmGnpis.presenceStatus"> + <th class="fieldName" scope="row">Presence status</th> + <td class="field">{{ germplasmGnpis.presenceStatus }}</td> + </tr> + </table> + + + <ng-template #holdingInstituteTemplate> + <table class="popoverTable"> + <tr *ngIf="germplasmGnpis.holdingInstitute.instituteCode"> + <th class="fieldName" scope="row">FAO code</th> + <td class="field">{{ germplasmGnpis.holdingInstitute.instituteCode }}</td> </tr> - <tr> - <td><b>institute name</b></td> - <td>{{ germplasm.result.breeder.instituteName }}</td> + <tr *ngIf="germplasmGnpis.holdingInstitute.instituteName"> + <th class="fieldName" scope="row">Institute name</th> + <td class="field">{{ germplasmGnpis.holdingInstitute.instituteName }}</td> </tr> - <tr> - <td><b>institute name</b></td> - <td>{{ germplasm.result.breeder.instituteName }}</td> + <tr *ngIf="germplasmGnpis.holdingInstitute.acronym"> + <th class="fieldName" scope="row">Acronym</th> + <td class="field">{{ germplasmGnpis.holdingInstitute.acronym }}</td> </tr> - <tr> - <td><b>institute name</b></td> - <td>{{ germplasm.result.breeder.instituteName }}</td> + <tr *ngIf="germplasmGnpis.holdingInstitute.organisation"> + <th class="fieldName" scope="row">Organisation</th> + <td class="field">{{ germplasmGnpis.holdingInstitute.organisation }}</td> </tr> - <tr> - <td><b>institute name</b></td> - <td>{{ germplasm.result.breeder.instituteName }}</td> + <tr *ngIf="germplasmGnpis.holdingInstitute.instituteType"> + <th class="fieldName" scope="row">Institute type</th> + <td class="field">{{ germplasmGnpis.holdingInstitute.instituteType }}</td> </tr> - <tr> - <td><b>institute name</b></td> - <td>{{ germplasm.result.breeder.instituteName }}</td> + <tr class="ellipsis" *ngIf="germplasmGnpis.holdingInstitute.webSite"> + <th class="fieldName" scope="row">Link</th> + <td class="ellipsis field"><a + href="{{ germplasmGnpis.holdingInstitute.webSite }}">{{ germplasmGnpis.holdingInstitute.webSite }}</a> + </td> + </tr> + <tr *ngIf="germplasmGnpis.holdingInstitute.address"> + <th class="fieldName" scope="row">Address</th> + <td class="field">{{ germplasmGnpis.holdingInstitute.address }}</td> </tr> + </table> + </ng-template> + </div> + </div> + + <div class="row" + *ngIf="germplasmGnpis.breeder && germplasmGnpis.breeder.institute && germplasmGnpis.breeder.institute.instituteName"> + <div class="col"> + <table class="table table-sm"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Breeder + </th> + </tr> + </thead> + <tr> + <th class="fieldName" scope="row">Institution</th> + <td class="ellipsis field"> + <a class="btn popovers" placement="top" [ngbPopover]="BreederInstituteTemplate" + [popoverTitle]="germplasmGnpis.breeder.institute.instituteName"> + {{ germplasmGnpis.breeder.institute.instituteName }} {{ germplasmGnpis.breeder.institute.instituteCode }}</a> + </td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.accessionCreationDate"> + <th class="fieldName" scope="row">Accession Creation date</th> + <td + class="field">{{ germplasmGnpis.breeder.accessionCreationDate | amParse:'YYYYMMDD' | amDateFormat:'YYYY-MM-DD' }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.accessionNumber"> + <th class="fieldName" scope="row">Accession number</th> + <td class="field">{{ germplasmGnpis.breeder.accessionNumber }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.deregistrationYear"> + <th class="fieldName" scope="row">Deregistration year</th> + <td class="field">{{ germplasmGnpis.breeder.deregistrationYear }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.registrationYear"> + <th class="fieldName" scope="row">Registration year</th> + <td class="field">{{ germplasmGnpis.breeder.registrationYear }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.distributionStatus"> + <th class="fieldName" scope="row">distributionStatus</th> + <td class="field">{{ germplasmGnpis.breeder.distributionStatus }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.germplasmPUI"> + <th class="fieldName" scope="row">germplasmPUI</th> + <td class="field">{{ germplasmGnpis.breeder.germplasmPUI }}</td> + </tr> + + </table> + </div> + </div> + + <div class="row" *ngIf="germplasmGnpis.collector || germplasmGnpis.collectingSite"> + <div class="col"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Collecting + </th> + </tr> + </thead> + <tr + *ngIf="germplasmGnpis.collector && germplasmGnpis.collector.institute && germplasmGnpis.collector.institute.instituteName"> + <th class="fieldName" scope="row">Institution</th> + <td class="ellipsis field"><a class="btn popovers" placement="top" + [ngbPopover]="CollectorInstituteTemplate" + [popoverTitle]="germplasmGnpis.collector.institute.instituteName"> + {{ germplasmGnpis.collector.institute.instituteName }}</a></td> + </tr> + <tr *ngIf="germplasmGnpis.collector && germplasmGnpis.collector.accessionCreationDate"> + <th class="fieldName" scope="row">Accession Creation date</th> + <td + class="field">{{ germplasmGnpis.collector.accessionCreationDate | amParse:'YYYYMMDD' | amDateFormat:'YYYY-MM-DD' }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector && germplasmGnpis.collector.accessionNumber"> + <th class="fieldName" scope="row">Accession number</th> + <td class="field">{{ germplasmGnpis.collector.accessionNumber }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector && germplasmGnpis.collector.collectors"> + <th class="fieldName" scope="row">collectors</th> + <td class="field">{{ germplasmGnpis.collector.collectors }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector && germplasmGnpis.collector.distributionStatus"> + <th class="fieldName" scope="row">distributionStatus</th> + <td class="field">{{ germplasmGnpis.collector.distributionStatus }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector && germplasmGnpis.collector.germplasmPUI"> + <th class="fieldName" scope="row">germplasmPUI</th> + <td class="field">{{ germplasmGnpis.collector.germplasmPUI }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector && germplasmGnpis.collector.materialType"> + <th class="fieldName" scope="row">Material type</th> + <td class="field">{{ germplasmGnpis.collector.materialType }}</td> + </tr> + + <tr *ngIf="germplasmGnpis.collectingSite && germplasmGnpis.collectingSite.siteName"> + <th class="fieldName" scope="row">Collecting site</th> + <td class="field"><a + [routerLink]="['/sites/', germplasmGnpis.collectingSite.siteId]">{{ germplasmGnpis.collectingSite.siteName }}</a> + </td> + </tr> + </table> + </div> + </div> + + <div class="row" *ngIf="germplasmGnpis.donors && germplasmGnpis.donors.length > 0"> + <div class="col"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Donation + </th> + </tr> + </thead> + <tr> + <th class="thead-light fieldName" scope="col">Institute</th> + <th class="thead-light fieldName" scope="col">Date</th> + </tr> + <tr *ngFor="let donor of germplasmGnpis.donors"> + <td class="ellipsis field"><a class="btn popovers ellipsis" placement="top" + [ngbPopover]="DonorInstituteTemplate" + [popoverTitle]="donor.donorInstitute.instituteName"> + {{ donor.donorInstitute.instituteName }}</a></td> + <td class="field" + *ngIf="donor.donationDate">{{ donor.donationDate | amParse:'YYYYMMDD' | amDateFormat:'YYYY-MM-DD' }}</td> + + <ng-template #DonorInstituteTemplate> + <table> + <tr *ngIf="donor.donorInstitute.instituteCode"> + <th class="fieldName" scope="row">Code</th> + <td class="field">{{ donor.donorInstitute.instituteCode }}</td> + </tr> + <tr *ngIf="donor.donorInstitute.acronym"> + <th class="fieldName" scope="row">Acronym</th> + <td class="field">{{ donor.donorInstitute.acronym }}</td> + </tr> + <tr *ngIf="donor.donorInstitute.organisation"> + <th class="fieldName" scope="row">Organisation</th> + <td class="field">{{ donor.donorInstitute.organisation }}</td> + </tr> + <tr *ngIf="donor.donorInstitute.instituteType"> + <th class="fieldName" scope="row">Type</th> + <td class="field">{{ donor.donorInstitute.instituteType }}</td> + </tr> + <tr *ngIf="donor.donorInstitute.webSite"> + <th class="fieldName" scope="row">Link</th> + <td class="ellipsis field"><a + href="{{ donor.donorInstitute.webSite }}">{{ donor.donorInstitute.webSite }}</a></td> + </tr> + <tr *ngIf="donor.donorInstitute.address"> + <th class="fieldName" scope="row">Address</th> + <td class="field">{{ donor.donorInstitute.address }}</td> + </tr> + </table> + </ng-template> + </tr> + </table> + </div> + </div> + + <div class="row"> + <div class="col"> + <ng-container *ngIf="germplasmGnpis.distributors && germplasmGnpis.distributors.length > 0"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <thead class="text-white"> <tr> - <td><b>institute name</b></td> - <td>{{ germplasm.result.breeder.instituteName }}</td> + <th class="headerTitle" scope="col" colspan="2"> + Distribution + </th> </tr> + </thead> + <ng-container *ngFor="let distributor of germplasmGnpis.distributors"> + <tr> + <th class="fieldName" scope="row">{{ distributor.institute.instituteName }}</th> + <td class="field">{{ distributor.distributionStatus }}</td> + </tr> + </ng-container> </table> - </tr> - </ng-container> - </table> - </div> - </ng-container> + </ng-container> + </div> + </div> - <ng-container *ngIf="germplasm.result.taxonIds != null"> - <div class="row"> - <h4>Collecting site</h4> - <table class="table table-sm"> - <tr> - <td><b>Ids</b></td> - <td>{{ germplasm.result.taxonIds.sourcename }}</td> - </tr> - <tr> - <td><b></b></td> - <td></td> - </tr> - </table> - </div> - </ng-container> + <ng-template #BreederInstituteTemplate> + <table> + <tr *ngIf="germplasmGnpis.breeder.institute.instituteCode"> + <th class="fieldName" scope="row">Code</th> + <td class="field">{{ germplasmGnpis.breeder.institute.instituteCode }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.institute.organisation"> + <th class="fieldName" scope="row">Organisation</th> + <td class="field">{{ germplasmGnpis.breeder.institute.organisation }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.institute.acronym"> + <th class="fieldName" scope="row">Acronym</th> + <td class="field">{{ germplasmGnpis.breeder.institute.acronym }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.institute.instituteType"> + <th class="fieldName" scope="row">Type</th> + <td class="field">{{ germplasmGnpis.breeder.institute.instituteType }}</td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.institute.webSite"> + <th class="fieldName" scope="row">Link</th> + <td class="ellipsis field"><a + href="{{ germplasmGnpis.breeder.institute.webSite }}">{{ germplasmGnpis.breeder.institute.webSite }}</a> + </td> + </tr> + <tr *ngIf="germplasmGnpis.breeder.institute.address"> + <th class="fieldName" scope="row">Address</th> + <td class="field">{{ germplasmGnpis.breeder.institute.address }}</td> + </tr> + </table> + </ng-template> - <ng-container *ngIf="germplasm.result.donors != null"> - <div class="row"> - <h4>Donors</h4> - <ng-container *ngFor="let donor of germplasm.result.donors"> - <table class="table table-sm"> - <tr> - <td><b>Donation accession number</b></td> - <td>{{ donor.donorAccessionNumber }}</td> + <ng-template #CollectorInstituteTemplate> + <table> + <tr *ngIf="germplasmGnpis.collector.instituteCode"> + <th class="fieldName" scope="row">FAO code</th> + <td class="field">{{ germplasmGnpis.collector.instituteCode }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector.instituteName"> + <th class="fieldName" scope="row">Institute name</th> + <td class="field">{{ germplasmGnpis.collector.instituteName }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector.acronym"> + <th class="fieldName" scope="row">Acronym</th> + <td class="field">{{ germplasmGnpis.collector.acronym }}</td> + </tr> + <tr *ngIf="germplasmGnpis.collector.organisation"> + <th class="fieldName" scope="row">Organisation</th> + <td class="field">{{ germplasmGnpis.collector.organisation }}</td> </tr> - <tr> - <td><b>Donation germplasm PUI</b></td> - <td>{{ donor.donorGermplasmPUI }}</td> + <tr *ngIf="germplasmGnpis.collector.institute.webSite"> + <th class="fieldName" scope="row">Link</th> + <td class="ellipsis field"><a + href="{{ germplasmGnpis.collector.institute.webSite }}">{{ germplasmGnpis.collector.institute.webSite }}</a> + </td> </tr> - <tr> - <td><b>Institute code</b></td> - <td>{{ donor.donorInstituteCode }}</td> + <tr *ngIf="germplasmGnpis.collector.institute.address"> + <th class="fieldName" scope="row">Address</th> + <td class="field">{{ germplasmGnpis.collector.institute.address }}</td> </tr> </table> - </ng-container> - </div> - </ng-container> + </ng-template> - <div class="row"> - <h4>Origin</h4> - <table class="table table-sm"> - <tr> - <td><b>Geographical origin</b></td> - <td>{{ germplasm.result.countryOfOriginCode }}</td> - </tr> - <tr> - <td><b>Collecting</b></td> - <td>{{ germplasm.result.siteName }}</td> - </tr> - <tr> - <td><b>Donation</b></td> - <td>{{ germplasm.result.instituteName }}</td> - </tr> - <tr> - <td><b>Date</b> - <td>{{ germplasm.result.instituteName }}</td> - </tr> - <tr> - <td><b>Institution name</b></td> - <td>{{ germplasm.result.instituteName }}</td> - </tr> - </table> - </div> - <ng-container *ngIf="germplasmPedigree != null"> - <div class="row"> - <h4>Genealogy</h4> - <h5>Ascendants</h5> - <table class="table table-sm"> - <tr> - <td><b>Crossing plan</b></td> - <td>{{ germplasmPedigree.result.crossingPlan }}</td> - </tr> - <tr> - <td><b>Crossing year</b></td> - <td>{{ germplasmPedigree.result.crossingYear }}</td> - </tr> - <tr> - <td><b>Affiliation</b></td> - <td>{{ germplasm.result.instituteName }}</td> - </tr> - <tr> - <td><b>Parent accessions</b></td> - <td><table class="table"> - <ng-container [ngSwitch]="germplasmPedigree.result.parent1Type"> - <tr *ngSwitchCase="'FEMALE'"> - <td>Mother</td> - <td>{{ germplasmPedigree.result.parent1Name }}</td> + <ng-container *ngIf="testPedigree() || testProgeny()"> + <div class="row"> + <div class="col"> + <h4>Genealogy</h4> + </div> + </div> + <div class="row"> + <div class="col"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}" *ngIf="testPedigree()"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Ascendants + </th> </tr> - <tr *ngSwitchCase="'MALE'"> - <td>Father</td> - <td>{{ germplasmPedigree.result.parent1Name }}</td> + </thead> + <tr *ngIf="germplasmPedigree.result.crossingPlan"> + <th class="fieldName" scope="row">Crossing plan</th> + <td class="field">{{ germplasmPedigree.result.crossingPlan }}</td> </tr> - </ng-container> - <ng-container [ngSwitch]="germplasmPedigree.result.parent2Type"> - <tr *ngSwitchCase="'FEMALE'"> - <td>Mother</td> - <td>{{ germplasmPedigree.result.parent2Name }}</td> + <tr *ngIf="germplasmPedigree.result.crossingYear"> + <th class="fieldName" scope="row">Crossing year</th> + <td class="field">{{ germplasmPedigree.result.crossingYear }}</td> </tr> - <tr *ngSwitchCase="'MALE'"> - <td>Father</td> - <td>{{ germplasmPedigree.result.parent2Name }}</td> + <tr *ngIf="germplasmPedigree.result.familyCode"> + <th class="fieldName" scope="row">Family code</th> + <td class="field">{{ germplasmPedigree.result.familyCode }}</td> </tr> - </ng-container> - </table></td> - </tr> - </table> - </div> - - <div class="row"> - <h5>Siblings</h5> - <table class="table table-sm"> - <tr> - <td><b>Siblings accessions</b></td> - <td><a *ngFor="let sibling of germplasmPedigree.result.siblings"> {{ sibling.defaultDisplayName }}</a></td> - </tr> - <tr> - <td><b>Geographical origin</b></td> - <td>{{ germplasm.result.countryOfOriginCode }}</td> - </tr> - </table> - </div> - </ng-container> + <tr *ngIf="(germplasmPedigree.result.parent1Type || germplasmPedigree.result.parent2Type) + && (germplasmPedigree.result.parent1Type !='UNDEFINED' || germplasmPedigree.result.parent2Type !='UNDEFINED')"> + <th scope="row">Parent accessions</th> + <td> + <table> + <ng-container [ngSwitch]="germplasmPedigree.result.parent1Type"> + <tr *ngSwitchCase="'FEMALE'"> + <th class="fieldName" scope="row">Mother</th> + <td class="field"><a + [routerLink]="'/germplasm'" + [queryParams]="{id:germplasmPedigree.result.parent1DbId}">{{ germplasmPedigree.result.parent1Name }}</a> + </td> + </tr> + <tr *ngSwitchCase="'MALE'"> + <th class="fieldName" scope="row">Father</th> + <td class="field"><a + [routerLink]="'/germplasm'" + [queryParams]="{id:germplasmPedigree.result.parent1DbId}">{{ germplasmPedigree.result.parent1Name }}</a> + </td> + </tr> + <tr *ngSwitchCase="'SELF'"> + <td>Self</td> + </tr> + <tr *ngSwitchCase="'POPULATION'"> + <th class="fieldName" scope="row">Population</th> + <td class="field">{{ germplasmPedigree.result.parent1Name }}</td> + </tr> + </ng-container> + <ng-container [ngSwitch]="germplasmPedigree.result.parent2Type"> + <tr *ngSwitchCase="'FEMALE'"> + <th class="fieldName" scope="row">Mother</th> + <td class="field"><a + [routerLink]="'/germplasm'" + [queryParams]="{id:germplasmPedigree.result.parent2DbId}">{{ germplasmPedigree.result.parent2Name }}</a></td> + </tr> + <tr *ngSwitchCase="'MALE'"> + <th class="fieldName" scope="row">Father</th> + <td class="field"><a + [routerLink]="'/germplasm'" + [queryParams]="{id:germplasmPedigree.result.parent2DbId}">{{ germplasmPedigree.result.parent2Name }}</a></td> + </tr> + <tr *ngSwitchCase="'SELF'"> + <td>Self</td> + </tr> + <tr *ngSwitchCase="'POPULATION'"> + <th class="fieldName" scope="row">Population</th> + <td class="field">{{ germplasmPedigree.result.parent2Name }}</td> + </tr> + </ng-container> + </table> + </td> + </tr> + </table> + </div> + </div> - <ng-container *ngIf="germplasm.result.distributors != null"> - <div class="row"> - <h4>Distribution</h4> - <table class="table table-sm"> - <ng-container *ngFor="let distributor of germplasm.result.distributors"> - <tr><b>Institution name</b> {{ distributor.institute.instituteName }}</tr> - <tr><b>Distribution status</b> {{ distributor.institute.instituteName }}</tr> + <ng-container *ngIf="germplasmPedigree.result.siblings && germplasmPedigree.result.siblings.length > 0"> + <div class="row "> + <div class="col"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Siblings + </th> + </tr> + </thead> + <tbody> + <tr> + <th class="fieldName">Accession numbers</th> + <td class="scroll field"> + <ng-container *ngFor="let sibling of germplasmPedigree.result.siblings"> + <a routerLink="/germplasm" [queryParams]="{id:sibling.germplasmDbId }"> + {{ sibling.defaultDisplayName }}</a> + </ng-container> + </td> + </tr> + </tbody> + </table> + </div> + </div> </ng-container> - </table> - </div> - </ng-container> + <ng-container *ngIf="testProgeny()"> + <div class="row "> + <div class="col"> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <thead class="text-white"> + <tr> + <th class="headerTitle" scope="col" colspan="2"> + Descendants + </th> + </tr> + </thead> + <tbody> + <tr> + <th class="fieldName">Accession numbers</th> + <td class="scroll field"> + <ng-container *ngFor="let child of germplasmProgeny.result.progeny"> + <a routerLink="/germplasm" [queryParams]="{id:child.germplasmDbId }"> + {{ child.defaultDisplayName }}</a> + </ng-container> + </td> + </tr> + </tbody> + </table> + </div> + </div> + </ng-container> + </ng-container> + <ng-container *ngIf="germplasmAttributes && germplasmAttributes.length > 0"> + <div class="row"> + <div class="col"> + <h4 class="headerTitle">Evaluation Data</h4> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> - <ng-container *ngIf="germplasm.result.collector != null"> - <div class="row"> - <h4>Collector</h4> - <table class="table table-sm"> - <tr> - <td><b>accessionNumber</b></td> - <td>{{ germplasm.result.collector.accessionNumber }}</td> - </tr> - <tr> - <td><b>collectors</b></td> - <td>{{ germplasm.result.collector.collectors }}</td> - </tr> - <tr> - <td><b>deregistrationYear</b></td> - <td>{{ germplasm.result.collector.deregistrationYear }}</td> - </tr> - <tr> - <td><b>distributionStatus</b></td> - <td>{{ germplasm.result.collector.distributionStatus }}</td> - </tr> - <tr> - <td><b>germplasmPUI</b></td> - <td>{{ germplasm.result.collector.germplasmPUI }}</td> - </tr> - <tr> - <td><b>institute</b></td> - <td>{{ germplasm.result.collector.instituteName }}</td> - </tr> - <tr> - <td><b>institute</b></td> - <td>{{ germplasm.result.collector.materialType }}</td> - </tr> - <tr> - <td><b>institute</b></td> - <td>{{ germplasm.result.collector.registrationYear }}</td> - </tr> - </table> - </div> - </ng-container> + <ng-container *ngFor="let descriptor of germplasmAttributes"> + <tr> + <th class="fieldName" scope="row">{{ descriptor.attributeName }}</th> + <td class="field">{{ descriptor.value }}</td> + </tr> + </ng-container> + </table> + </div> + </div> + </ng-container> - <ng-container *ngIf="germplasm.result.collection != null"> - <div class="row"> - <h4>Collection</h4> - <table class="table table-sm"> - <ng-container *ngFor="let co of germplasm.result.collection"> - <tr><b>Institution name</b> {{ co.germplasmCount }}</tr> - <tr><b>Distribution status</b> {{ co.germplasmRef }}</tr> - <tr><b>Institution name</b> {{ co.id }}</tr> - <tr><b>Institution name</b> {{ co.name }}</tr> - <tr><b>Institution name</b> {{ co.type }}</tr> - </ng-container> - </table> - </div> - </ng-container> + <ng-container *ngIf="germplasmGnpis.collection && germplasmGnpis.collection.length > 0"> + <div class="row"> + <div class="col"> + <h4 class="headerTitle">Collection</h4> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <ng-container *ngFor="let collection of germplasmGnpis.collection"> + <tr> + <th class="fieldName" scope="row"> + {{ collection.type ? collection.name + ' (' + collection.type + ')' : collection.name }} + </th> + <td class="ellipsis field"><a routerLink="" [queryParams]="{germplasmLists: collection.name, types: 'Germplasm'}"> + {{ collection.germplasmCount }} accessions</a> + </td> + </tr> + </ng-container> + </table> + </div> + </div> + </ng-container> - <ng-container *ngIf="germplasm.result.panel != null"> - <div class="row"> - <h4>Panel</h4> - <table class="table table-sm"> - <ng-container *ngFor="let co of germplasm.result.panel"> - <tr><b>Institution name</b> {{ co.germplasmCount }}</tr> - <tr><b>Distribution status</b> {{ co.germplasmRef }}</tr> - <tr><b>Institution name</b> {{ co.id }}</tr> - <tr><b>Institution name</b> {{ co.name }}</tr> - <tr><b>Institution name</b> {{ co.type }}</tr> - </ng-container> - </table> - </div> - </ng-container> + <ng-container *ngIf="germplasmGnpis.panel && germplasmGnpis.panel.length > 0"> + <div class="row"> + <div class="col"> + <h4 class="headerTitle">Panel</h4> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <ng-container *ngFor="let panel of germplasmGnpis.panel"> + <tr> + <th class="fieldName" scope="row"> + {{ panel.type ? panel.name + ' (' + panel.type + ')' : panel.name }} + </th> + <td class="field"><a routerLink="" [queryParams]="{germplasmLists: panel.name, types: 'Germplasm'}"> + {{ panel.germplasmCount }} accessions</a></td> + </tr> + </ng-container> + </table> + </div> + </div> + </ng-container> - <ng-container *ngIf="germplasmAttributes.result.data != null"> - <h4>Evaluation Data</h4> - <div class="row"> - <table class="table table-sm"> - <ng-container *ngFor="let descriptor of germplasmAttributes.result.data"> - <tr><td><b>{{ descriptor.attributeName }}</b></td> <td>{{ descriptor.value }}</td></tr> - </ng-container> - </table> + <ng-container *ngIf="germplasmGnpis.population && germplasmGnpis.population.length > 0"> + <div class="row"> + <div class="col"> + <h4 class="headerTitle">Population</h4> + <table class="table table-sm .table-responsive{-sm|-md|-lg|-xl}"> + <ng-container *ngFor="let population of germplasmGnpis.population"> + <tr> + <th class="fieldName" scope="row"> + {{ population.type ? population.name + ' (' + population.type + ')' : population.name }} + </th> + <td class="field"><a routerLink="" [queryParams]="{germplasmLists: population.name, types: 'Germplasm'}"> + {{ population.germplasmCount }} accessions</a></td> + </tr> + </ng-container> + </table> + </div> + </div> + </ng-container> </div> - </ng-container> - - <div class="row"> - <h4>Cross-reference</h4> - <table class="container-fluid"> - </table> </div> -</div> ---> +</ng-container> diff --git a/frontend/src/app/germplasm-card/germplasm-card.component.scss b/frontend/src/app/germplasm-card/germplasm-card.component.scss index 4ae2839c7736da395b2e7305e7e120bee2d8eea3..017b0c22841a99886f56cfab64b1ae23cfb1d1d0 100644 --- a/frontend/src/app/germplasm-card/germplasm-card.component.scss +++ b/frontend/src/app/germplasm-card/germplasm-card.component.scss @@ -1,2 +1,3 @@ @import "theme"; +@import '../../styles.scss'; diff --git a/frontend/src/app/germplasm-card/germplasm-card.component.spec.ts b/frontend/src/app/germplasm-card/germplasm-card.component.spec.ts index 5f67adbd4d5297b39e713e291fc4c5923ca58528..b94e00d4acaf7cd9d48b4566166f2e1dadadbcd5 100644 --- a/frontend/src/app/germplasm-card/germplasm-card.component.spec.ts +++ b/frontend/src/app/germplasm-card/germplasm-card.component.spec.ts @@ -1,26 +1,246 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - +import { async, TestBed } from '@angular/core/testing'; import { GermplasmCardComponent } from './germplasm-card.component'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentTester, speculoosMatchers } from 'ngx-speculoos'; import { GnpisService } from '../gnpis.service'; import { BrapiService } from '../brapi.service'; -import { ActivatedRoute, RouterModule } from '@angular/router'; +import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of } from 'rxjs'; +import { + BrapiDescriptor, + BrapiDonor, + BrapiGermplasmAttributes, + BrapiGermplasmPedigree, + BrapiGermplasmProgeny, + BrapiSet, + BrapiSibling +} from '../models/brapi.germplasm.model'; +import { Germplasm, GermplasmData, GermplasmResult, Institute, Origin, Site } from '../models/gnpis.germplasm.model'; +import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; +import { MomentModule } from 'ngx-moment'; +import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component'; describe('GermplasmCardComponent', () => { - let component: GermplasmCardComponent; - let fixture: ComponentFixture<GermplasmCardComponent>; + + beforeEach(() => jasmine.addMatchers(speculoosMatchers)); + + class GermplasmCardComponentTester extends ComponentTester<GermplasmCardComponent> { + constructor() { + super(GermplasmCardComponent); + } + + get title() { + return this.element('h3'); + } + + get headerTitle() { + return this.elements('.headerTitle'); + } + } + + const brapiService = jasmine.createSpyObj( + 'BrapiService', [ + 'germplasm', + 'germplasmProgeny', + 'germplasmPedigree', + 'germplasmAttributes' + ] + ); + + const gnpisService = jasmine.createSpyObj( + 'GnpisService', [ + 'germplasm', + 'germplasmByPuid' + ] + ); + + const brapiSite: Site = { + latitude: null, + longitude: null, + siteId: null, + siteName: null, + siteType: null + }; + + const brapiSibling: BrapiSibling = { + germplasmDbId: 'frere1', + defaultDisplayName: 'frere1' + }; + + const brapiDescriptor: BrapiDescriptor = { + name: 'caracteristique1', + pui: '12', + value: '32' + }; + + const brapiGermplasmPedigree: GermplasmResult<BrapiGermplasmPedigree> = { + result : { + germplasmDbId: '12', + defaultDisplayName: '12', + pedigree: null, + crossingPlan: null, + crossingYear: null, + familyCode: null, + parent1DbId: '11', + parent1Name: 'parent', + parent1Type: 'SELF', + parent2DbId: null, + parent2Name: null, + parent2Type: null, + siblings: [brapiSibling] + } + }; + + const brapiGermplasmProgeny: GermplasmResult<BrapiGermplasmProgeny> = { + result: { + germplasmDbId: '11', + defaultDisplayName: '11', + progeny: [brapiSibling] + } + }; + + const brapiInstitute: Institute = { + instituteName: 'urgi', + instituteCode: 'inra', + acronym: 'urgi', + organisation: 'inra', + instituteType: 'labo', + webSite: 'www.labo.fr', + address: '12', + logo: null + }; + + const brapiOrigin: Origin = { + institute: brapiInstitute, + germplasmPUI: '12', + accessionNumber: '12', + accessionCreationDate: '1993', + materialType: 'feuille', + collectors: null, + registrationYear: '1996', + deregistrationYear: '1912', + distributionStatus: null + }; + + const brapiDonor: BrapiDonor = { + donorInstitute: brapiInstitute, + germplasmPUI: '12', + accessionNumber: '12', + donorInstituteCode: 'urgi' + }; + + const brapiSet: BrapiSet = { + germplasmCount: 12, + germplasmRef: null, + id: 12, + name: 'truc', + type: 'plan' + }; + + const brapiGermplasmAttributes: GermplasmResult<GermplasmData<BrapiGermplasmAttributes[]>> = { + result: { + data: [ { + attributeName: 'longueur', + value: '30' + }] + } + }; + + const germplasmTest: Germplasm = { + url: 'www.cirad.fr', + source: 'cirad', + germplasmDbId: 'test', + defaultDisplayName: 'test', + accessionNumber: 'test', + germplasmName: 'test', + germplasmPUI: 'doi:1256', + pedigree: 'tree', + seedSource: 'inra', + synonyms: null, + commonCropName: null, + instituteCode: 'grc12', + instituteName: 'institut', + biologicalStatusOfAccessionCode: null, + countryOfOriginCode: null, + typeOfGermplasmStorageCode: null, + taxonIds: null, + genus: 'genre', + species: 'esp', + speciesAuthority: 'L', + subtaxa: null, + subtaxaAuthority: null, + donors: [brapiDonor], + acquisitionDate: null, + genusSpecies: null, + genusSpeciesSubtaxa: null, + taxonSynonyms: ['pomme', 'api'], + taxonCommonNames: ['pomme', 'api'], + geneticNature: null, + comment: null, + photo: null, + holdingInstitute: brapiInstitute, + holdingGenbank: brapiInstitute, + presenceStatus: null, + children: null, + descriptors: [brapiDescriptor], + originSite: null, + collectingSite: null, + evaluationSites: null, + collector: brapiOrigin, + breeder: brapiOrigin, + distributors: [brapiOrigin], + panel: [brapiSet], + collection: [brapiSet], + population: [brapiSet] + }; + + const germplasmResultTest = { + result: germplasmTest + }; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [GermplasmCardComponent], - providers: [HttpClientTestingModule] - }) - .compileComponents(); + imports: [RouterTestingModule, NgbPopoverModule, MomentModule], + declarations: [ + GermplasmCardComponent, LoadingSpinnerComponent + ], + providers: [ + // { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: BrapiService, useValue: brapiService }, + { provide: GnpisService, useValue: gnpisService }, + { provide: ActivatedRoute, + useValue: { + snapshot: { + queryParams: convertToParamMap({ + id: 'test' + }) + } + } + } + ] + }); })); - beforeEach(() => { - fixture = TestBed.createComponent(GermplasmCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); + + gnpisService.germplasm.and.returnValue(of(germplasmTest)); + gnpisService.germplasmByPuid.and.returnValue(of(germplasmTest)); + brapiService.germplasmProgeny.and.returnValue(of(brapiGermplasmProgeny)); + brapiService.germplasmPedigree.and.returnValue(of(brapiGermplasmPedigree)); + brapiService.germplasmAttributes.and.returnValue(of(brapiGermplasmAttributes)); + + it('should fetch germplasm information', async(() => { + const tester = new GermplasmCardComponentTester(); + const component = tester.componentInstance; + tester.detectChanges(); + component.loaded.then(() => { + expect(component.germplasmGnpis).toBeTruthy(); + tester.detectChanges(); + expect(tester.title).toContainText('Germplasm: test'); + expect(tester.headerTitle[0]).toContainText('Identification'); + expect(tester.headerTitle[1]).toContainText('Holding'); + expect(tester.headerTitle[2]).toContainText('Breeder'); + expect(tester.headerTitle[3]).toContainText('Collecting'); + }); + })); }); + diff --git a/frontend/src/app/germplasm-card/germplasm-card.component.ts b/frontend/src/app/germplasm-card/germplasm-card.component.ts index 1183fd8a9e5726a653bcb21ff523be7067690078..14fa0e9e6173dc701a1d52fe9aef089519e15861 100644 --- a/frontend/src/app/germplasm-card/germplasm-card.component.ts +++ b/frontend/src/app/germplasm-card/germplasm-card.component.ts @@ -2,51 +2,105 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { BrapiService } from '../brapi.service'; import { GnpisService } from '../gnpis.service'; +import { Germplasm, GermplasmResult } from '../models/gnpis.germplasm.model'; +import { BrapiGermplasmAttributes, BrapiGermplasmPedigree, BrapiGermplasmProgeny } from '../models/brapi.germplasm.model'; @Component({ selector: 'gpds-germplasm-card', templateUrl: './germplasm-card.component.html', styleUrls: ['./germplasm-card.component.scss'] - - }) + export class GermplasmCardComponent implements OnInit { + constructor(private brapiService: BrapiService, private gnpisService: GnpisService, private route: ActivatedRoute) { } - germplasm: object = {}; - germplasmGnpis: object = {}; - germplasmPedigree: object = {}; - germplasmProgeny: object = {}; - germplasmAttributes: object = {}; + germplasmGnpis: Germplasm; + germplasmPedigree: GermplasmResult<BrapiGermplasmPedigree>; + germplasmProgeny: GermplasmResult<BrapiGermplasmProgeny>; + germplasmAttributes: BrapiGermplasmAttributes[]; + germplasmId: string; + germplasmPuid: string; + IMAGES_SIREGAL_URL = 'https://urgi.versailles.inra.fr/files/siregal/images/accession'; + + loaded: Promise<any>; + loading = true; + ngOnInit() { - const germplasmId = this.route.snapshot.paramMap.get('id'); - this.brapiService.germplasm(germplasmId) - .subscribe(germplasm => { - this.germplasm = germplasm; + // console.log(this.route.snapshot); + // console.log(this.route); + this.germplasmId = this.route.snapshot.queryParams.id; + this.germplasmPuid = this.route.snapshot.queryParams.pui; + const germplasm$ = this.getGermplasm(this.germplasmId, this.germplasmPuid); + germplasm$.then(result => { + const germplasmId = this.germplasmId ? this.germplasmId : result.germplasmDbId; + const germplasmProgeny$ = this.brapiService.germplasmProgeny(germplasmId).toPromise(); + germplasmProgeny$ + .then(germplasmProgeny => { + this.germplasmProgeny = germplasmProgeny; + }); + + const germplasmPedigree$ = this.brapiService.germplasmPedigree(germplasmId).toPromise(); + germplasmPedigree$ + .then(germplasmPedigree => { + this.germplasmPedigree = germplasmPedigree; + }); + + const germplasmAttributes$ = this.brapiService.germplasmAttributes(germplasmId).toPromise(); + germplasmAttributes$ + .then(germplasmAttributes => { + if (germplasmAttributes.result) { + this.germplasmAttributes = germplasmAttributes.result.data; + } + + }); }); - this.brapiService.germplasmProgeny(germplasmId) - .subscribe(germplasmProgeny => { - this.germplasmProgeny = germplasmProgeny; - }); + this.loaded = Promise.all([germplasm$]); + this.loaded.then(() => { + this.loading = false; + }); - this.brapiService.germplasmPedigree(germplasmId) - .subscribe(germplasmPedigree => { - this.germplasmPedigree = germplasmPedigree; - }); + } - this.brapiService.germplasmAttributes(germplasmId) - .subscribe(germplasmAttributes => { - this.germplasmAttributes = germplasmAttributes; - }); + getGermplasm(id: string, pui: string): Promise<Germplasm> { + let germplasm$: Promise<Germplasm>; + if (id) { + germplasm$ = this.gnpisService.germplasm(id).toPromise(); + germplasm$ + .then(germplasmGnpis => { + this.germplasmGnpis = germplasmGnpis; + }); + } else { + germplasm$ = this.gnpisService.germplasmByPuid(pui).toPromise(); + germplasm$ + .then(germplasmGnpis => { + this.germplasmGnpis = germplasmGnpis; + }); + } + return germplasm$; + } - this.brapiService.germplasm(germplasmId) - .subscribe(germplasmGnpis => - this.germplasmGnpis = germplasmGnpis); + testProgeny() { + return (this.germplasmProgeny + && this.germplasmProgeny.result + && this.germplasmProgeny.result.progeny + && this.germplasmProgeny.result.progeny.length > 0); } + testPedigree() { + return (this.germplasmPedigree + && this.germplasmPedigree.result + && (this.germplasmPedigree.result.parent1Name + || this.germplasmPedigree.result.parent2Name + || this.germplasmPedigree.result.crossingPlan + || this.germplasmPedigree.result.crossingYear + || this.germplasmPedigree.result.familyCode) + ); + } } + diff --git a/frontend/src/app/gnpis.service.spec.ts b/frontend/src/app/gnpis.service.spec.ts index 6ffe4c0cc04c92de9a9582301bf609184bd7bc0d..512deeceddd8c1b12f011cbe8015d69d99e09566 100644 --- a/frontend/src/app/gnpis.service.spec.ts +++ b/frontend/src/app/gnpis.service.spec.ts @@ -1,13 +1,13 @@ -import { TestBed } from '@angular/core/testing'; +import { Germplasm, Institute, Origin, Site } from './models/gnpis.germplasm.model'; -import { BASE_URL, GnpisService } from './gnpis.service'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { BASE_URL, BASE_URL_GERMPLASM, GnpisService } from './gnpis.service'; import { BrapiMetaData, BrapiResults } from './models/brapi.model'; import { DataDiscoveryCriteria, DataDiscoverySource } from './models/data-discovery.model'; +import { BrapiDescriptor, BrapiDonor, BrapiSet } from './models/brapi.germplasm.model'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; describe('GnpisService', () => { - let service: GnpisService; - let httpMock; const source1: DataDiscoverySource = { '@id': 'id1', @@ -26,13 +26,115 @@ describe('GnpisService', () => { 'schema:image': 'image2', }; + const site: Site = { + latitude: null, + longitude: null, + siteId: 1, + siteName: 'Nantes', + siteType: null + }; + + const brapiDescriptor: BrapiDescriptor = { + name: 'caracteristique1', + pui: '12', + value: '32' + }; + + const brapiInstitute: Institute = { + instituteName: 'urgi', + instituteCode: 'inra', + acronym: 'urgi', + organisation: 'inra', + instituteType: 'labo', + webSite: 'www.labo.fr', + address: '12', + logo: null + }; + + const brapiOrigin: Origin = { + institute: brapiInstitute, + germplasmPUI: '12', + accessionNumber: '12', + accessionCreationDate: '1993', + materialType: 'feuille', + collectors: null, + registrationYear: '1996', + deregistrationYear: '1912', + distributionStatus: null + }; + + const brapiDonor: BrapiDonor = { + donorInstitute: brapiInstitute, + germplasmPUI: '12', + accessionNumber: '12', + donorInstituteCode: 'urgi' + }; + + const brapiSet: BrapiSet = { + germplasmCount: 12, + germplasmRef: null, + id: 12, + name: 'truc', + type: 'plan' + }; + + const germplasmTest: Germplasm = { + url: 'www.cirad.fr', + source: 'cirad', + germplasmDbId: 'test', + defaultDisplayName: 'test', + accessionNumber: 'test', + germplasmName: 'test', + germplasmPUI: 'doi:1256', + pedigree: 'tree', + seedSource: 'inra', + synonyms: null, + commonCropName: null, + instituteCode: 'grc12', + instituteName: 'institut', + biologicalStatusOfAccessionCode: null, + countryOfOriginCode: null, + typeOfGermplasmStorageCode: null, + taxonIds: null, + genus: 'genre', + species: 'esp', + speciesAuthority: 'L', + subtaxa: null, + subtaxaAuthority: null, + donors: [brapiDonor], + acquisitionDate: null, + genusSpecies: null, + genusSpeciesSubtaxa: null, + taxonSynonyms: ['pomme', 'api'], + taxonCommonNames: ['pomme', 'api'], + geneticNature: null, + comment: null, + photo: null, + holdingInstitute: brapiInstitute, + holdingGenbank: brapiInstitute, + presenceStatus: null, + children: null, + descriptors: [brapiDescriptor], + originSite: site, + collectingSite: null, + evaluationSites: null, + collector: brapiOrigin, + breeder: brapiOrigin, + distributors: [brapiOrigin], + panel: [brapiSet], + collection: [brapiSet], + population: [brapiSet] + }; + + let gnpisService: GnpisService; + let http: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [HttpClientTestingModule] }); - - httpMock = TestBed.get(HttpTestingController); + gnpisService = TestBed.get(GnpisService); + http = TestBed.get(HttpTestingController); const sources: BrapiResults<DataDiscoverySource> = { result: { @@ -40,8 +142,7 @@ describe('GnpisService', () => { }, metadata: {} as BrapiMetaData }; - service = TestBed.get(GnpisService); - const req = httpMock.expectOne({ + const req = http.expectOne({ method: 'GET', url: `${BASE_URL}/sources` }); @@ -49,6 +150,10 @@ describe('GnpisService', () => { }); + + afterEach(() => http.verify()); + + it('should suggest with criteria', () => { const expectedSuggestions = ['a', 'b', 'c']; const field = 'foo'; @@ -56,12 +161,12 @@ describe('GnpisService', () => { const criteria = { crops: ['d'] } as DataDiscoveryCriteria; const fetchSize = 3; - service.suggest(field, fetchSize, text, criteria).subscribe(suggestions => { + gnpisService.suggest(field, fetchSize, text, criteria).subscribe(suggestions => { expect(suggestions.length).toBe(3); expect(suggestions).toBe(expectedSuggestions); }); - const req = httpMock.expectOne({ + const req = http.expectOne({ url: `${BASE_URL}/suggest?field=${field}&text=${text}&fetchSize=${fetchSize}`, method: 'POST' }); @@ -69,13 +174,19 @@ describe('GnpisService', () => { expect(req.request.body).toBe(criteria); }); - it('should fetch sources', () => { - service.sourceByURI$.subscribe(sourceByURI => { - expect(sourceByURI).toEqual({ - 'id1': source1, - 'id2': source2 - }); + + afterAll(() => http.verify()); + + it('should fetch the germplasm', () => { + let fetchedGermplasm: Germplasm; + const germplasmDbId: string = germplasmTest.germplasmDbId; + gnpisService.germplasm(germplasmDbId).subscribe(response => { + fetchedGermplasm = response; }); + http.expectOne(`${BASE_URL_GERMPLASM}/germplasm?id=${germplasmDbId}`) + .flush(germplasmTest); + + expect(fetchedGermplasm).toEqual(germplasmTest); }); it('should search documents with criteria', () => { @@ -105,13 +216,13 @@ describe('GnpisService', () => { const criteria = { crops: ['d'] } as DataDiscoveryCriteria; - service.search(criteria).subscribe(result => { + gnpisService.search(criteria).subscribe(result => { expect(result.result.data.length).toBe(2); expect(result.result.data[0]['schema:includedInDataCatalog']).toEqual(source1); expect(result.result.data[1]['schema:includedInDataCatalog']).toEqual(source2); }); - const req = httpMock.expectOne({ + const req = http.expectOne({ url: `${BASE_URL}/search`, method: 'POST' }); @@ -119,5 +230,14 @@ describe('GnpisService', () => { expect(req.request.body).toBe(criteria); }); + + it('should fetch sources', () => { + gnpisService.sourceByURI$.subscribe(sourceByURI => { + expect(sourceByURI).toEqual({ + 'id1': source1, + 'id2': source2 + }); + }); + }); }) ; diff --git a/frontend/src/app/gnpis.service.ts b/frontend/src/app/gnpis.service.ts index 967550c891b5d5ad5ee02d68005f89ce33475bdd..6b78cfe969af2fe6d3ad53ddca004d9f23ab3393 100644 --- a/frontend/src/app/gnpis.service.ts +++ b/frontend/src/app/gnpis.service.ts @@ -1,11 +1,14 @@ import { Injectable } from '@angular/core'; import { Observable, ReplaySubject, zip } from 'rxjs'; import { HttpClient } from '@angular/common/http'; +import { Germplasm } from './models/gnpis.germplasm.model'; import { DataDiscoveryCriteria, DataDiscoveryFacet, DataDiscoveryResults, DataDiscoverySource } from './models/data-discovery.model'; import { BrapiResults } from './models/brapi.model'; import { map } from 'rxjs/operators'; + export const BASE_URL = 'gnpis/v1/datadiscovery'; +export const BASE_URL_GERMPLASM = 'gnpis/v1'; @Injectable({ providedIn: 'root' @@ -89,11 +92,18 @@ export class GnpisService { })); } + germplasm(germplasmDbId: string): Observable<Germplasm> { + return this.http.get<Germplasm>(`${BASE_URL_GERMPLASM}/germplasm?id=${germplasmDbId}`); + } + + germplasmByPuid(pui: string): Observable<Germplasm> { + return this.http.get<Germplasm>(`${BASE_URL_GERMPLASM}/germplasm?pui=${pui}`); + } + /** * Get data source by URI */ getSource(sourceURI: string): Observable<DataDiscoverySource> { return this.sourceByURI$.pipe(map(sourceByURI => sourceByURI[sourceURI])); } - } diff --git a/frontend/src/app/models/brapi.germplasm.model.ts b/frontend/src/app/models/brapi.germplasm.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..54ed2d04026b9b78b22ed7e298a976b6ebeddb7d --- /dev/null +++ b/frontend/src/app/models/brapi.germplasm.model.ts @@ -0,0 +1,55 @@ +import { Institute } from './gnpis.germplasm.model'; + + +export interface BrapiSibling { + germplasmDbId: string; + defaultDisplayName: string; +} + +export interface BrapiDescriptor { + name: string; + pui: string; + value: string; +} + +export interface BrapiGermplasmPedigree { + germplasmDbId: string; + defaultDisplayName: string; + pedigree: string; + crossingPlan: string; + crossingYear: string; + familyCode: string; + parent1DbId: string; + parent1Name: string; + parent1Type: string; + parent2DbId: string; + parent2Name: string; + parent2Type: string; + siblings: BrapiSibling[]; +} + +export interface BrapiGermplasmProgeny { + germplasmDbId: string; + defaultDisplayName: string; + progeny: BrapiSibling[]; +} + +export interface BrapiGermplasmAttributes { + attributeName: string; + value: string; +} + +export interface BrapiDonor { + donorInstitute: Institute; + germplasmPUI: string; + accessionNumber: string; + donorInstituteCode: string; +} + +export interface BrapiSet { + germplasmCount: number; + germplasmRef: string; + id: number; + name: string; + type: string; +} diff --git a/frontend/src/app/models/gnpis.germplasm.model.ts b/frontend/src/app/models/gnpis.germplasm.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..a18cbb87f9d78d12db5d14e026033e6ba354594d --- /dev/null +++ b/frontend/src/app/models/gnpis.germplasm.model.ts @@ -0,0 +1,92 @@ +import { BrapiDescriptor, BrapiDonor, BrapiSet } from './brapi.germplasm.model'; + +export interface Site { + latitude: number; + longitude: number; + siteId: number; + siteName: string; + siteType: string; +} + +export interface Germplasm { + source: string; + url: string; + germplasmDbId: string; + defaultDisplayName: string; + accessionNumber: string; + germplasmName: string; + germplasmPUI: string; + pedigree: string; + seedSource: string; + synonyms: string; + commonCropName: string; + instituteCode: string; + instituteName: string; + biologicalStatusOfAccessionCode: string; + countryOfOriginCode: string; + typeOfGermplasmStorageCode: string; + taxonIds: string; + genus: string; + species: string; + speciesAuthority: string; + subtaxa: string; + subtaxaAuthority: string; + donors: BrapiDonor[]; + acquisitionDate: string; + genusSpecies: string; + genusSpeciesSubtaxa: string; + taxonSynonyms: string[]; + taxonCommonNames: string[]; + geneticNature: string; + comment: string; + photo: string; + holdingInstitute: Institute; + holdingGenbank: Institute; + presenceStatus: string; + children: string; + descriptors: BrapiDescriptor[]; + originSite: Site; + collectingSite: Site; + evaluationSites: Site[]; + collector: Origin; + breeder: Origin; + distributors: Origin[]; + panel: BrapiSet[]; + collection: BrapiSet[]; + population: BrapiSet[]; +} + +export interface Origin { + institute: Institute; + germplasmPUI: string; + accessionNumber: string; + accessionCreationDate: string; + materialType: string; + collectors: string; + registrationYear: string; + deregistrationYear: string; + distributionStatus: string; +} + +export interface Institute { + instituteName: string; + instituteCode: string; + acronym: string; + organisation: string; + instituteType: string; + webSite: string; + address: string; + logo: string; +} + +export interface GermplasmData<T> { + data: T; +} + +export interface GermplasmResult<T> { + result: T; +} + + + + diff --git a/frontend/src/app/navbar/navbar.component.scss b/frontend/src/app/navbar/navbar.component.scss index 8522efe86cbc27b85db6410c6f6450ba969307b6..2a8c98cf98252cad58c7c6135e62433dd1871efc 100644 --- a/frontend/src/app/navbar/navbar.component.scss +++ b/frontend/src/app/navbar/navbar.component.scss @@ -14,6 +14,7 @@ .navbar .navbar-nav .nav-link, .navbar .navbar-brand { color: $theme-navbar-color; height: $theme-navbar-height; + &:hover { color: $theme-navbar-hover-color; background-color: $theme-navbar-hover-bg-color; diff --git a/frontend/src/app/result-page/document/document.component.html b/frontend/src/app/result-page/document/document.component.html index cfb1efcae7863332cdab0b26a4fa19c2457d8a67..9b1175eb96f50f02d16f3088d1de992c038761a8 100644 --- a/frontend/src/app/result-page/document/document.component.html +++ b/frontend/src/app/result-page/document/document.component.html @@ -10,7 +10,7 @@ <a class="title" *ngIf="getURL()" [href]="getURL()"> {{ document["schema:name"] }} </a> - <a class="title" *ngIf="getRouterLink()" [routerLink]="getRouterLink()"> + <a class="title" *ngIf="getRouterLink()" [routerLink]="getRouterLink()" [queryParams]="getQueryParam()"> {{ document["schema:name"] }} </a> </h5> diff --git a/frontend/src/app/result-page/document/document.component.spec.ts b/frontend/src/app/result-page/document/document.component.spec.ts index 05928ff743a8f770521bd591f05cb7f1846c2e61..5f2d83663fc8f62cc63dbd834ad215a90f594d77 100644 --- a/frontend/src/app/result-page/document/document.component.spec.ts +++ b/frontend/src/app/result-page/document/document.component.spec.ts @@ -95,7 +95,8 @@ describe('DocumentComponent', () => { expect(component).toBeTruthy(); expect(tester.title).toContainText('doc_name'); - expect(tester.title.nativeElement['routerLink']).toEqual('/germplasm/g1'); + expect(tester.title.nativeElement['routerLink']).toEqual('/germplasm'); + expect(component.getQueryParam().id).toEqual('g1'); }); diff --git a/frontend/src/app/result-page/document/document.component.ts b/frontend/src/app/result-page/document/document.component.ts index 749101e45a1d04a31bf9828bfb7ab63bd0a9939a..69d83161a159cc6fe050e42254e48197f4666d06 100644 --- a/frontend/src/app/result-page/document/document.component.ts +++ b/frontend/src/app/result-page/document/document.component.ts @@ -30,9 +30,12 @@ export class DocumentComponent implements OnInit { if (!this.getURL()) { for (const type of this.document['@type']) { const cardUrl = DocumentComponent.CARD_TYPE[type]; - if (cardUrl) { + if (cardUrl === 'studies') { return `/${cardUrl}/${this.document['schema:identifier']}`; } + if (cardUrl === 'germplasm') { + return `/${cardUrl}`; + } } } return ''; @@ -46,6 +49,18 @@ export class DocumentComponent implements OnInit { return this.document['schema:includedInDataCatalog']['schema:url']; } + getQueryParam() { + if (this.document['schema:identifier']) { + return { + id: this.document['schema:identifier'] + }; + } else { + return { + pui: this.document['@id'] + }; + } + } + getBadgeType(type: DataDiscoveryType) { return DocumentComponent.BADGE_TYPE[type]; } diff --git a/frontend/src/app/study-card/study-card.component.html b/frontend/src/app/study-card/study-card.component.html index e85da88328d5d9bcf220f0a13c78f7cf27198efc..c010cf49b71b9adabdd0194b7943a72aa08536d4 100644 --- a/frontend/src/app/study-card/study-card.component.html +++ b/frontend/src/app/study-card/study-card.component.html @@ -1,4 +1,4 @@ -<gpds-loading-spinner [loading]="loading"></gpds-loading-spinner> +<gpds-loading-spinner class="display-spinner-front rounded" [loading]="loading"></gpds-loading-spinner> <ng-container *ngIf="study"> <h3> @@ -134,7 +134,7 @@ <ng-template let-row> <tr> <td> - <a [routerLink]="['/germplasm', row.germplasmDbId]"> + <a [routerLink]="'/germplasm'" [queryParams]="{id:row.germplasmDbId}"> {{ row.accessionNumber }} </a> </td> diff --git a/frontend/src/app/study-card/study-card.component.scss b/frontend/src/app/study-card/study-card.component.scss index 55181845b07a912a715c27dd619fd2bb24d19400..c344730e9220180bc60b5188f46d5b683fb3932e 100644 --- a/frontend/src/app/study-card/study-card.component.scss +++ b/frontend/src/app/study-card/study-card.component.scss @@ -1,9 +1,8 @@ +@import "theme"; +@import '../../styles.scss'; h3 { font-weight: bold; color: #0f6191; } -a { - text-decoration: underline; -} diff --git a/frontend/src/assets/gpds/theme.scss b/frontend/src/assets/gpds/theme.scss index 168116f3953fb0a269af34063b9f1c647c2a2871..ad6939ccf8e2828e65a04e67dfdd3e90abc075a7 100644 --- a/frontend/src/assets/gpds/theme.scss +++ b/frontend/src/assets/gpds/theme.scss @@ -13,13 +13,76 @@ $link-hover-color: $_theme-black; // override default shadows behavior $enable-shadows: true; + @import "~bootstrap/scss/functions"; @import "~bootstrap/scss/variables"; @import "~bootstrap/scss/mixins"; @import "~bootstrap/scss/tables"; +@import "~bootstrap/scss/card"; +@import "~bootstrap/scss/transitions"; +@import "~bootstrap/scss/tooltip"; +@import "~bootstrap/scss/popover"; + // public custom variables used in this theme, and in component styles + +//custom tables +.table { + border-bottom: 2px solid #0f6191; + border-top: 2px solid #0f6191; + table-layout: fixed; + +} + +a.btn.popovers { + text-decoration: underline; + //overflow: hidden; + //text-overflow: ellipsis; +} + +.popover { + max-width: 50%; +} + +/*table th, table td { overflow: hidden; }*/ + +.imagePopover { + max-width: fit-content; +} + +.scroll { + display: block; + max-height: 200px; + overflow-y: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; +} + +h4 { + color: #0f6191; +} + +.table thead { + background-color: #0f6191; +} + +.ellipsis { + overflow: hidden; + text-overflow: ellipsis; +} + +.field { + +} + +.fieldName { + +} + +.headerTitle { + +} + // custom button $theme-btn-color: $white; $theme-btn-bg-color: $_theme-black; diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 7f2ba9f3f138f058bbcae81b0c5054fdda28aafe..2af3b3a2b30c0274e288fa835ea0892f9b9d52fd 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -9,3 +9,14 @@ $fa-font-path: '~font-awesome/fonts'; color: $white !important; } } + +a { + text-decoration: underline; +} + +.display-spinner-front { + position: absolute; + top: 70px; + left: 720px; + background-color: #F9F9F9; +} diff --git a/frontend/src/tslint.json b/frontend/src/tslint.json index 7e545ab7d80894dd3456be1fee601237e43b7bff..9a8fed218220e47daab1f3733704ab4ad76f4373 100644 --- a/frontend/src/tslint.json +++ b/frontend/src/tslint.json @@ -15,7 +15,7 @@ ], "template-cyclomatic-complexity": [ true, - 10 + 260 ] } }