Commit aefa727c authored by Jérémy Destin's avatar Jérémy Destin
Browse files

feat: Display germplasm data and start the suggestion when focus on search box. GNP-4309.

parent 8f668eba
......@@ -10,8 +10,10 @@ import {
BrapiResult,
BrapiResults,
BrapiStudy,
BrapiTrial
BrapiTrial,
GermplasmCriteria
} from './models/brapi.model';
import { Germplasm } from './models/gnpis.model';
export const BASE_URL = 'brapi/v1';
......@@ -61,6 +63,11 @@ export class BrapiService {
.get<BrapiResult<BrapiTrial>>(`${BASE_URL}/trials/${trialsId}`);
}
germplasmSearch(criteria: GermplasmCriteria): Observable<BrapiResults<BrapiGermplasm>>{
return this.http.post<BrapiResults<Germplasm>>(`${BASE_URL}/germplasm-search`,criteria)
}
/**
* Get BrAPI single result response and replace the 'schema:includedInDataCatalog' URI value to the actual source object value.
*/
......
<p>This is a more complete example with a service that simulates server calling:</p>
<!--<p>This is a more complete example with a service that simulates server calling:</p>
<ul>
<li>an observable async service to fetch a list of countries</li>
<li>sorting, filtering and pagination</li>
<li>simulated delay and loading indicator</li>
<li>debouncing of search requests</li>
</ul>
</ul>-->
<form>
<div class="form-group form-inline">
Full text search: <input class="form-control ml-2" type="text" name="searchTerm" [(ngModel)]="service.searchTerm"/>
Full text search: <input class="form-control ml-2" type="text"
name="searchTerm"
[(ngModel)]="service.searchTerm"/>
<span class="ml-3" *ngIf="service.loading$ | async">Loading...</span>
</div>
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="{{ header }}" (sort)="onSort($event)" *ngFor="let header of tableHeaders ">{{ header }}</th>
<!--<th *ngFor="let header of tableHeaders " scope="col">#</th>
<th scope="col" sortable="name" (sort)="onSort($event)">Country</th>
<th scope="col" sortable="area" (sort)="onSort($event)">Area</th>
<th scope="col" sortable="population" (sort)="onSort($event)">Population</th>-->
</tr>
</thead>
<tbody>
<ng-container *ngFor="let row of rows"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{$implicit: row}">
</ng-container>
<!--<tr *ngFor="let country of countries$ | async">
<th scope="row">{{ country.id }}</th>
<td>
<img [src]="'https://upload.wikimedia.org/wikipedia/commons/' + country.flag" class="mr-2" style="width: 20px">
<ngb-highlight [result]="country.name" [term]="service.searchTerm"></ngb-highlight>
</td>
<td><ngb-highlight [result]="country.area | number" [term]="service.searchTerm"></ngb-highlight></td>
<td><ngb-highlight [result]="country.population | number" [term]="service.searchTerm"></ngb-highlight></td>
</tr>-->
</tbody>
</table>
<div class="card">
<table class="table table-striped mt-1">
<thead>
<tr>
<th scope="col" sortable="{{ header }}" (sort)="onSort($event)"
*ngFor="let header of tableHeaders ">{{ header }}</th>
<!--<th *ngFor="let header of tableHeaders " scope="col">#</th>
<th scope="col" sortable="name" (sort)="onSort($event)">Country</th>
<th scope="col" sortable="area" (sort)="onSort($event)">Area</th>
<th scope="col" sortable="population" (sort)="onSort($event)">Population</th>-->
</tr>
</thead>
<tbody>
<ng-container *ngFor="let row of rows"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{$implicit: row}">
</ng-container>
<!--<tr *ngFor="let country of countries$ | async">
<th scope="row">{{ country.id }}</th>
<td>
<img [src]="'https://upload.wikimedia.org/wikipedia/commons/' + country.flag" class="mr-2" style="width: 20px">
<ngb-highlight [result]="country.name" [term]="service.searchTerm"></ngb-highlight>
</td>
<td><ngb-highlight [result]="country.area | number" [term]="service.searchTerm"></ngb-highlight></td>
<td><ngb-highlight [result]="country.population | number" [term]="service.searchTerm"></ngb-highlight></td>
</tr>-->
</tbody>
</table>
</div>
<div class="d-flex justify-content-between p-2">
<ngb-pagination
[collectionSize]="total$ | async" [(page)]="service.page" [pageSize]="service.pageSize">
[collectionSize]="total$ | async" [(page)]="service.page"
[pageSize]="service.pageSize">
</ngb-pagination>
<select class="custom-select" style="width: auto" name="pageSize" [(ngModel)]="service.pageSize">
<select class="custom-select" style="width: auto" name="pageSize"
[(ngModel)]="service.pageSize">
<option [ngValue]="5">5 items per page</option>
<option [ngValue]="10">10 items per page</option>
<option [ngValue]="15">15 items per page</option>
......
......@@ -7,8 +7,7 @@ import {
ViewChildren
} from '@angular/core';
import { Observable } from 'rxjs';
import { Country } from './country';
import { CountryService } from './country.services';
import { GermplasmService } from './germplasm.services';
import { NgbdSortableHeader, SortEvent } from './sortable.directive';
@Component({
......@@ -18,7 +17,6 @@ import { NgbdSortableHeader, SortEvent } from './sortable.directive';
})
export class CardSortableTableComponent {
countries$: Observable<Country[]>;
total$: Observable<number>;
@Input() tableHeaders: String[];
......@@ -26,8 +24,7 @@ export class CardSortableTableComponent {
@ContentChild(TemplateRef) template: TemplateRef<any>;
constructor(public service: CountryService) {
this.countries$ = service.countries$;
constructor(public service: GermplasmService) {
this.total$ = service.total$;
}
......
import {Country} from './country';
export const COUNTRIES: Country[] = [
{
id: 1,
name: 'Russia',
flag: 'f/f3/Flag_of_Russia.svg',
area: 17075200,
population: 146989754
},
{
id: 2,
name: 'France',
flag: 'c/c3/Flag_of_France.svg',
area: 640679,
population: 64979548
},
{
id: 3,
name: 'Germany',
flag: 'b/ba/Flag_of_Germany.svg',
area: 357114,
population: 82114224
},
{
id: 4,
name: 'Portugal',
flag: '5/5c/Flag_of_Portugal.svg',
area: 92090,
population: 10329506
},
{
id: 5,
name: 'Canada',
flag: 'c/cf/Flag_of_Canada.svg',
area: 9976140,
population: 36624199
},
{
id: 6,
name: 'Vietnam',
flag: '2/21/Flag_of_Vietnam.svg',
area: 331212,
population: 95540800
},
{
id: 7,
name: 'Brazil',
flag: '0/05/Flag_of_Brazil.svg',
area: 8515767,
population: 209288278
},
{
id: 8,
name: 'Mexico',
flag: 'f/fc/Flag_of_Mexico.svg',
area: 1964375,
population: 129163276
},
{
id: 9,
name: 'United States',
flag: 'a/a4/Flag_of_the_United_States.svg',
area: 9629091,
population: 324459463
},
{
id: 10,
name: 'India',
flag: '4/41/Flag_of_India.svg',
area: 3287263,
population: 1324171354
},
{
id: 11,
name: 'Indonesia',
flag: '9/9f/Flag_of_Indonesia.svg',
area: 1910931,
population: 263991379
},
{
id: 12,
name: 'Tuvalu',
flag: '3/38/Flag_of_Tuvalu.svg',
area: 26,
population: 11097
},
{
id: 13,
name: 'China',
flag: 'f/fa/Flag_of_the_People%27s_Republic_of_China.svg',
area: 9596960,
population: 1409517397
}
];
export interface Country {
id: number;
name: string;
flag: string;
area: number;
population: number;
}
import { Injectable, PipeTransform } from '@angular/core';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { Country } from './country';
import { COUNTRIES } from './countries';
import { GERMPLASM } from './germplasm';
import { DecimalPipe } from '@angular/common';
import { debounceTime, delay, switchMap, tap } from 'rxjs/operators';
import { SortDirection } from './sortable.directive';
import { BrapiGermplasm } from '../models/brapi.model';
interface SearchResult {
countries: Country[];
germplasm: BrapiGermplasm[];
total: number;
}
......@@ -25,29 +24,30 @@ function compare(v1, v2) {
return v1 < v2 ? -1 : v1 > v2 ? 1 : 0;
}
function sort(countries: Country[], column: string, direction: string): Country[] {
function sort(germplasm: BrapiGermplasm[], column: string, direction: string): BrapiGermplasm[] {
if (direction === '') {
return countries;
return germplasm;
} else {
return [...countries].sort((a, b) => {
return [...germplasm].sort((a, b) => {
const res = compare(a[column], b[column]);
return direction === 'asc' ? res : -res;
});
}
}
function matches(country: Country, term: string, pipe: PipeTransform) {
return country.name.toLowerCase().includes(term.toLowerCase())
|| pipe.transform(country.area).includes(term)
|| pipe.transform(country.population).includes(term);
function matches(germplasm: BrapiGermplasm, term: string, pipe: PipeTransform) {
return germplasm.germplasmDbId.toLowerCase().includes(term.toLowerCase())
|| germplasm.accessionNumber.toLowerCase().includes(term.toLowerCase())
|| germplasm.instituteName.toLowerCase().includes(term.toLowerCase())
|| germplasm.commonCropName.toLowerCase().includes(term.toLowerCase());
}
@Injectable({providedIn: 'root'})
export class CountryService {
export class GermplasmService {
private _loading$ = new BehaviorSubject<boolean>(true);
private _search$ = new Subject<void>();
private _countries$ = new BehaviorSubject<Country[]>([]);
private _data$ = new BehaviorSubject<any[]>([]);
private _total$ = new BehaviorSubject<number>(0);
private _state: State = {
......@@ -66,14 +66,14 @@ export class CountryService {
delay(200),
tap(() => this._loading$.next(false))
).subscribe(result => {
this._countries$.next(result.countries);
this._data$.next(result['germplasm']);
this._total$.next(result.total);
});
this._search$.next();
}
get countries$() { return this._countries$.asObservable(); }
get data$() { return this._data$.asObservable(); }
get total$() { return this._total$.asObservable(); }
get loading$() { return this._loading$.asObservable(); }
get page() { return this._state.page; }
......@@ -91,19 +91,19 @@ export class CountryService {
this._search$.next();
}
private _search(): Observable<SearchResult> {
private _search(): Observable<any> {
const {sortColumn, sortDirection, pageSize, page, searchTerm} = this._state;
// 1. sort
let countries = sort(COUNTRIES, sortColumn, sortDirection);
let data = sort(GERMPLASM, sortColumn, sortDirection);
// 2. filter
countries = countries.filter(country => matches(country, searchTerm, this.pipe));
const total = countries.length;
data = data.filter(data => matches(data, searchTerm, this.pipe));
const total = data.length;
// 3. paginate
countries = countries.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);
return of({countries, total});
data = data.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);
return of({germplasm: data, total});
}
}
import { BrapiGermplasm } from '../models/brapi.model';
export const GERMPLASM: BrapiGermplasm[] = [
{
germplasmDbId: "aHR0cHM6Ly9kb2kub3JnLzEwLjE1NDU0L0hET0Y4Qw==",
defaultDisplayName: "FANNETTE",
accessionNumber: "10936",
germplasmName: "FANNETTE",
pedigree: "RIKA/VOLLA//EMIR",
seedSource: null,
synonyms: [
"FRA051:4278"
],
commonCropName: "Barley",
instituteCode: "FRA040",
instituteName: "UMR Génétique, Diversité et Ecophysiologie des Céréales, INRA-Clermont",
biologicalStatusOfAccessionCode: "Advanced or improved cultivar",
countryOfOriginCode: null,
typeOfGermplasmStorageCode: null,
taxonIds: null,
donors: [
{
donorGermplasmPUI: null,
donorAccessionNumber: "15955DA",
donorInstituteCode: "FRA018",
donationDate: null,
}
],
genus: "Hordeum",
species: "vulgare",
speciesAuthority: null,
subtaxa: "subsp. vulgare",
subtaxaAuthority: null,
acquisitionDate: null,
germplasmPUI: "https://doi.org/10.15454/HDOF8C",
documentationURL: null
},
{
germplasmDbId: "dXJuOlVSR0kvZ25waXNfcHVpJTNBdW5rbm93biUzQVdoZWF0JTNBUkUwMDExOQ==",
defaultDisplayName: "RE00119",
accessionNumber: "RE00119",
germplasmName: "RE00119",
pedigree: null,
seedSource: null,
synonyms: null,
commonCropName: "Wheat",
instituteCode: "FRA015",
instituteName: "INRA",
biologicalStatusOfAccessionCode: null,
countryOfOriginCode: null,
typeOfGermplasmStorageCode: null,
taxonIds: null,
donors: null,
genus: "Triticum",
species: "aestivum",
speciesAuthority: null,
subtaxa: "subsp. aestivum",
subtaxaAuthority: null,
acquisitionDate: null,
germplasmPUI: "urn:URGI/gnpis_pui%3Aunknown%3AWheat%3ARE00119",
documentationURL: null
},
{
germplasmDbId: "aHR0cHM6Ly9kb2kub3JnLzEwLjE1NDU0L0VVMk1LWA==",
defaultDisplayName: "AKER_11467",
accessionNumber: "PI 26545",
germplasmName: "AKER_11467",
pedigree: null,
seedSource: null,
synonyms: [
"PI 26545"
],
commonCropName: "Beta vulgaris vulgaris cv. Sugar beet",
instituteCode: "USA126",
instituteName: "Germplasm Resources Information Network",
biologicalStatusOfAccessionCode: null,
countryOfOriginCode: null,
typeOfGermplasmStorageCode: null,
taxonIds: null,
donors: null,
genus: "Beta",
species: "vulgaris",
speciesAuthority: null,
subtaxa: "subsp. vulgaris cv. Sugar beet",
subtaxaAuthority: null,
acquisitionDate: null,
germplasmPUI: "https://doi.org/10.15454/EU2MKX",
documentationURL: null
}
];
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { BehaviorSubject, merge, Observable, of, Subject } from 'rxjs';
import { BehaviorSubject, merge, Observable, Subject } from 'rxjs';
import { FormControl } from '@angular/forms';
import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
import {
NgbTypeahead,
NgbTypeaheadSelectItemEvent
} from '@ng-bootstrap/ng-bootstrap';
import { GnpisService } from '../../gnpis.service';
import { debounceTime, filter, map, switchMap } from 'rxjs/operators';
import { DataDiscoveryCriteria } from '../../models/data-discovery.model';
......@@ -25,7 +28,6 @@ export class SuggestionFieldComponent implements OnInit {
input = new FormControl();
@ViewChild('inputElement') inputElement: ElementRef;
@ViewChild('typeahead') typeahead: NgbTypeahead;
private localCriteria: DataDiscoveryCriteria = null;
......@@ -60,18 +62,18 @@ export class SuggestionFieldComponent implements OnInit {
const clicksWithClosedPopup$ = this.click$.pipe(
filter(() => !this.typeahead.isPopupOpen())
);
const text2$ = text$.pipe(
/*const text2$ = text$.pipe(
filter(term => term.length >= 2)
);
);*/
// When new text or focus or click with popup closed
return merge(text2$, clicksWithClosedPopup$).pipe(
return merge(text$, clicksWithClosedPopup$).pipe(
debounceTime(250),
switchMap((term: string) => {
if (!term) {
/*if (!term) {
// No term to search
return of([]);
}
}*/
// Otherwise, we fetch new suggestions
return this.fetchSuggestion(term);
......
<faidare-card-sortable-table
[tableHeaders]="['id','name', 'area', 'population']"
[rows]="countries">
<!--<faidare-germplasm-form
inputId="accessions"
criteriaField="accessions"
[criteria$]="criteria">
</faidare-germplasm-form>-->
<ng-container>
<h3 class="mb-4">
Germplasm result page
</h3>
<ng-template let-row>
<tr>
<th scope="row">{{ row.id }}</th>
<td>
<img [src]="'https://upload.wikimedia.org/wikipedia/commons/' + row.flag" class="mr-2" style="width: 20px">
<ngb-highlight [result]="row.name" [term]="service.searchTerm"></ngb-highlight>
</td>
<td><ngb-highlight [result]="row.area | number" [term]="service.searchTerm"></ngb-highlight></td>
<td><ngb-highlight [result]="row.population | number" [term]="service.searchTerm"></ngb-highlight></td>
</tr>
</ng-template>
<div class="col-sm-8 mb-3">
<faidare-suggestion-field
inputId="accessions"
criteriaField="accessions"
[criteria$]="Germplasmcriteria$"
placeholder="Search germplasm name">
</faidare-suggestion-field>
</div>
</faidare-card-sortable-table>
<faidare-card-sortable-table
[tableHeaders]="['germplasmName', 'accessionNumber', 'commonCropName']"
[rows]="germplasm">
<ng-template let-row>
<tr>
<td>
<ngb-highlight [result]="row.germplasmName"
[term]="service2.searchTerm"></ngb-highlight>
</td>
<td>
<ngb-highlight [result]="row.accessionNumber"
[term]="service2.searchTerm"></ngb-highlight>
</td>
<td>
<ngb-highlight [result]="row.commonCropName"
[term]="service2.searchTerm"></ngb-highlight>
</td>
</tr>
</ng-template>
</faidare-card-sortable-table>
</ng-container>
import { Component, OnInit } from '@angular/core';
import { CountryService } from '../card-sortable-table/country.services';
import { Country } from '../card-sortable-table/country';
import { GermplasmService } from '../card-sortable-table/germplasm.services';
import { BrapiService } from '../brapi.service';
import {
BrapiCriteriaUtils,
BrapiGermplasm,
GermplasmCriteria
} from '../models/brapi.model';
import { BehaviorSubject } from 'rxjs';
import { filter } from 'rxjs/operators';
@Component({
selector: 'faidare-germplasm-result-page',
......@@ -10,14 +18,38 @@ import { Country } from '../card-sortable-table/country';
export class GermplasmResultPageComponent implements OnInit {
countries: Country[];
germplasm: BrapiGermplasm[];
Germplasmcriteria$ = new BehaviorSubject<GermplasmCriteria> (BrapiCriteriaUtils.emptyCriteria());
private localCriteria: GermplasmCriteria = BrapiCriteriaUtils.emptyCriteria();
constructor(public service: CountryService) { }
constructor(public service2: GermplasmService, public service: BrapiService) { }
ngOnInit() {
this.service.countries$.subscribe(countries => {
this.countries = countries;
});
this.Germplasmcriteria$.pipe(filter(c => c !== this.localCriteria))
.subscribe(newCriteria => {
newCriteria.accessionNumbers = ["10936"];
for (const field in newCriteria){