Skip to content
Snippets Groups Projects
Commit 4cf48026 authored by Jérémy Destin's avatar Jérémy Destin Committed by Guillaume Cornut
Browse files

feat: Enhance document display in result list. Minor fixes. GNP-5430.

parent 0febfc68
No related branches found
No related tags found
1 merge request!5Implement result page
Showing
with 147 additions and 47 deletions
......@@ -5,7 +5,7 @@ import { ReactiveFormsModule } from '@angular/forms';
import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
import { GnpisService } from '../../gnpis.service';
import { of } from 'rxjs';
import { EMPTY_CRITERIA } from '../../model/dataDiscoveryCriteria';
import { newCriteria } from '../../model/dataDiscoveryCriteria';
describe('SuggestionFieldComponent', () => {
......@@ -30,7 +30,7 @@ describe('SuggestionFieldComponent', () => {
});
fixture = TestBed.createComponent(SuggestionFieldComponent);
component = fixture.componentInstance;
component.criteria$ = of(EMPTY_CRITERIA);
component.criteria$ = of(newCriteria());
});
it('should create', () => {
......@@ -51,7 +51,7 @@ describe('SuggestionFieldComponent', () => {
it('should display the selected criteria as pills', () => {
component.criteriaField = 'crops';
component.criteria$ = of({ ...EMPTY_CRITERIA, crops: ['Zea', 'Wheat'] });
component.criteria$ = of({ ...newCriteria(), crops: ['Zea', 'Wheat'] });
fixture.detectChanges();
......@@ -66,7 +66,7 @@ describe('SuggestionFieldComponent', () => {
it('should fetch suggestion', async(() => {
component.criteriaField = 'crops';
const selectedCrops = ['Zea', 'Wheat'];
component.criteria$ = of({ ...EMPTY_CRITERIA, crops: selectedCrops });
component.criteria$ = of({ ...newCriteria(), crops: selectedCrops });
const allSuggestions = ['Zea', 'Wheat', 'Vitis', 'Grapevine'];
service.suggest.and.returnValue(of(allSuggestions));
......
......@@ -43,15 +43,17 @@ export class SuggestionFieldComponent implements OnInit {
}
ngOnInit(): void {
this.criteria = null;
this.criteria$.subscribe(criteria => {
// When criteria changes
this.criteriaChanged = true;
if (!this.criteria) {
// Criteria first initialized
this.criteria = criteria;
this.selectedKeys = this.criteria[this.criteriaField];
this.selectedKeys = criteria[this.criteriaField];
}
this.criteria = criteria;
});
}
......
......@@ -16,11 +16,6 @@ describe('GnpisService', () => {
httpMock = TestBed.get(HttpTestingController);
});
it('should be created', () => {
expect(service).toBeTruthy();
httpMock.verify();
});
it('should suggest with criteria', () => {
const expectedSuggestions = ['a', 'b', 'c'];
const field = 'foo';
......
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Observable, ReplaySubject, zip } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { DataDiscoveryCriteria } from './model/dataDiscoveryCriteria';
import { BrapiResults } from './model/brapi';
import { DataDiscoveryDocument } from './model/dataDiscoveryDocument';
import { DataDiscoveryDocument, DataDiscoverySource } from './model/dataDiscoveryDocument';
import { map } from 'rxjs/operators';
@Injectable({
......@@ -12,7 +12,22 @@ import { map } from 'rxjs/operators';
export class GnpisService {
static BASE_URL = '/gnpis/v1/datadiscovery';
sourceByURI$ = new ReplaySubject<{ [key: string]: DataDiscoverySource }>(1);
constructor(private http: HttpClient) {
this.fetchSources();
}
private fetchSources() {
this.http.get(`${GnpisService.BASE_URL}/sources`).subscribe(
(brapiResults: BrapiResults<DataDiscoverySource>) => {
const sourceByURI = {};
for (const source of brapiResults.result.data) {
sourceByURI[source['@id']] = source;
}
this.sourceByURI$.next(sourceByURI);
}
);
}
/**
......@@ -43,10 +58,21 @@ export class GnpisService {
search(
criteria: DataDiscoveryCriteria
): Observable<DataDiscoveryDocument[]> {
return this.http.post<BrapiResults<DataDiscoveryDocument>>(
`${GnpisService.BASE_URL}/search`, criteria,
).pipe(map((brapiResult: BrapiResults<DataDiscoveryDocument>) => {
return brapiResult.result.data;
return zip(
// Get source by URI
this.sourceByURI$,
// Get documents by criteria
this.http.post<any>(`${GnpisService.BASE_URL}/search`, criteria)
).pipe(map(([sourceByURI, brapiResult]) => {
// Extract BrAPI documents from result
const documents = brapiResult.result.data;
// Transform document to have the source details in place of the source URI
return documents.map(document => {
const sourceURI = document['schema:includedInDataCatalog'] as string;
document['schema:includedInDataCatalog'] = sourceByURI[sourceURI];
return document;
});
}));
}
......
......@@ -4,8 +4,10 @@ export interface DataDiscoveryCriteria {
germplasmLists: string[];
}
export const EMPTY_CRITERIA: DataDiscoveryCriteria = {
accessions: [],
crops: [],
germplasmLists: []
};
export function newCriteria(): DataDiscoveryCriteria {
return {
accessions: [],
crops: [],
germplasmLists: []
};
}
export interface DataDiscoveryDocument {
export interface DataDiscoverySource {
['@id']: string;
['@type']: string[];
['schema:identifier']: string;
['schema:name']: string;
['schema:url']: string;
['schema:image']: string;
}
export interface DataDiscoveryDocument {
['@id']: string;
['@type']: DataDiscoveryDocumentType[];
['schema:identifier']: string;
['schema:name']: string;
['schema:url']: string;
['schema:description']: string;
['schema:includedInDataCatalog']: string;
['schema:includedInDataCatalog']: DataDiscoverySource;
}
export type DataDiscoveryDocumentType = 'Germplasm' | 'Phenotyping Study';
<p>
{{document["schema:name"]}}
</p>
<div>
<h5>
<span *ngFor="let type of document['@type']"
class="badge {{ getBadgeType(type) }} mr-2">
{{ type }}
</span>
<a class="badge badge-source mr-2" [href]="getSourceURL()" target="_blank">
{{ getSource() }}
</a>
<a *ngIf="getURL()" [href]="getURL()">
{{ document["schema:name"] }}
</a>
<a *ngIf="getRouterLink()" [routerLink]="getRouterLink()">
{{ document["schema:name"] }}
</a>
</h5>
<p>
{{ document["schema:description"] }}
</p>
</div>
.badge-source {
background-color: #b54646;
color: white;
}
.badge-germplasm {
background-color: #4aa434;
color: white;
}
.badge-study {
background-color: #437b9c;
color: white;
}
......@@ -2,6 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DocumentComponent } from './document.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { DataDiscoverySource } from '../../model/dataDiscoveryDocument';
describe('DocumentComponent', () => {
let component: DocumentComponent;
......@@ -19,14 +20,13 @@ describe('DocumentComponent', () => {
fixture = TestBed.createComponent(DocumentComponent);
component = fixture.componentInstance;
component.document = {
'@type': ['doc'],
'@type': ['Germplasm'],
'@id': 'urn',
'schema:identifier': 'schema',
'schema:name': 'doc_name',
'schema:url': 'http://dco/url',
'schema:description': 'description',
'schema:includedInDataCatalog': 'catalog'
'schema:includedInDataCatalog': {} as DataDiscoverySource
};
fixture.detectChanges();
});
......
import { Component, Input, OnInit } from '@angular/core';
import { DataDiscoveryDocument } from '../../model/dataDiscoveryDocument';
import { Component, Input } from '@angular/core';
import { DataDiscoveryDocument, DataDiscoveryDocumentType } from '../../model/dataDiscoveryDocument';
@Component({
selector: 'gpds-document',
templateUrl: './document.component.html',
styleUrls: ['./document.component.scss']
})
export class DocumentComponent implements OnInit {
export class DocumentComponent {
private static BADGE_TYPE = {
'Germplasm': 'badge-germplasm',
'Phenotyping Study': 'badge-study'
};
private static CARD_TYPE = {
'Germplasm': 'germplasm',
'Phenotyping Study': 'studies'
};
@Input() document: DataDiscoveryDocument;
constructor() {
getURL() {
return this.document['schema:url'] || '';
}
getRouterLink() {
if (!this.getURL()) {
for (const type of this.document['@type']) {
const cardUrl = DocumentComponent.CARD_TYPE[type];
if (cardUrl) {
return `/${cardUrl}/${this.document['schema:identifier']}`;
}
}
}
return '';
}
getSource() {
return this.document['schema:includedInDataCatalog']['schema:name'];
}
getSourceURL() {
return this.document['schema:includedInDataCatalog']['schema:url'];
}
ngOnInit() {
getBadgeType(type: DataDiscoveryDocumentType) {
return DocumentComponent.BADGE_TYPE[type];
}
}
......@@ -4,12 +4,12 @@ import { ResultPageComponent } from './result-page.component';
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { of } from 'rxjs';
import { fakeRoute } from 'ngx-speculoos';
import { DocumentComponent } from './document/document.component';
import { EMPTY_CRITERIA } from '../model/dataDiscoveryCriteria';
import { newCriteria } from '../model/dataDiscoveryCriteria';
import { GnpisService } from '../gnpis.service';
import { DataDiscoveryDocument } from '../model/dataDiscoveryDocument';
import { DataDiscoveryDocument, DataDiscoverySource } from '../model/dataDiscoveryDocument';
@Component({
......@@ -90,17 +90,18 @@ describe('ResultPageComponent', () => {
});
it('should fetch documents', () => {
const criteria = EMPTY_CRITERIA;
const documents: Observable<DataDiscoveryDocument[]> = of([{
'@type': ['doc'],
const criteria = newCriteria();
const document: DataDiscoveryDocument = {
'@type': ['Germplasm'],
'@id': 'urn',
'schema:identifier': 'schema',
'schema:name': 'doc_name',
'schema:url': 'http://dco/url',
'schema:description': 'description',
'schema:includedInDataCatalog': 'catalog'
}]);
service.search.and.returnValue(of(documents));
'schema:includedInDataCatalog': {} as DataDiscoverySource
};
const documents = of([document]);
service.search.and.returnValue(documents);
component.fetchDocuments(criteria);
expect(component.documents).not.toBe(null);
});
......
import { Component, OnInit } from '@angular/core';
import { NamedSelection } from '../form/suggestion-field/suggestion-field.component';
import { ActivatedRoute, Router } from '@angular/router';
import { DataDiscoveryCriteria, EMPTY_CRITERIA } from '../model/dataDiscoveryCriteria';
import { DataDiscoveryCriteria, newCriteria } from '../model/dataDiscoveryCriteria';
import { BehaviorSubject } from 'rxjs';
import { DataDiscoveryDocument } from '../model/dataDiscoveryDocument';
import { GnpisService } from '../gnpis.service';
......@@ -14,7 +14,7 @@ import { GnpisService } from '../gnpis.service';
})
export class ResultPageComponent implements OnInit {
criteria$ = new BehaviorSubject<DataDiscoveryCriteria>({ ...EMPTY_CRITERIA });
criteria$ = new BehaviorSubject<DataDiscoveryCriteria>(newCriteria());
documents: DataDiscoveryDocument[] = [];
constructor(private route: ActivatedRoute, private router: Router, private gnpisService: GnpisService) {
......@@ -35,7 +35,7 @@ export class ResultPageComponent implements OnInit {
ngOnInit(): void {
this.route.queryParams.subscribe(queryParams => {
const criteria = this.criteria$.value;
const criteria = newCriteria();
for (const key of Object.keys(queryParams)) {
const value = queryParams[key];
if (Array.isArray(value)) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment