Skip to content

Commit

Permalink
Merge pull request #105 from EBISPOT/develop
Browse files Browse the repository at this point in the history
publication ui
  • Loading branch information
ala-ebi authored Jun 2, 2023
2 parents 3cdfb77 + 1ca866b commit 5db1cf3
Show file tree
Hide file tree
Showing 15 changed files with 447 additions and 17 deletions.
4 changes: 4 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const routes: Routes = [
path: 'studies',
loadChildren: () => import('./feature/study/study.module').then(m => m.StudyModule)
},
{
path: 'publications',
loadChildren: () => import('./feature/publication/publication.module').then(m => m.PublicationModule)
},
{
path: 'login',
loadChildren: () => import('./feature/authentication/authentication.module').then(m => m.AuthenticationModule)
Expand Down
3 changes: 3 additions & 0 deletions src/app/core/components/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<a routerLink="reported-traits" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white px-2 py-2 rounded-md text-sm font-medium">Reported traits</a>
<a routerLink="efo-traits" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white px-2 py-2 rounded-md text-sm font-medium">EFO traits</a>
<a routerLink="studies" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white px-2 py-2 rounded-md text-sm font-medium">Studies</a>
<a routerLink="publications" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white px-2 py-2 rounded-md text-sm font-medium">Publications</a>
</div>
</div>
</div>
Expand All @@ -45,6 +46,8 @@
<a routerLink="submissions" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Submissions</a>
<a routerLink="reported-traits" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Reported traits</a>
<a routerLink="efo-traits" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">EFO traits</a>
<a routerLink="studies" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Studies</a>
<a routerLink="publications" mat-button class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Publications</a>
</div>
</div>
</nav>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ApiResponse } from './apiResponse';
import { Publication } from '../../publication';

export interface PublicationListApiResponse extends ApiResponse {
_embedded: {
solrPublicationDToes: Publication[]
};
}
16 changes: 16 additions & 0 deletions src/app/core/services/publication.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { PublicationService } from './publication.service';

describe('PublicationService', () => {
let service: PublicationService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PublicationService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
33 changes: 33 additions & 0 deletions src/app/core/services/publication.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { CurationHttpService } from './curation-http.service';
import { Observable } from 'rxjs';
import { HttpParams } from '@angular/common/http';
import { PublicationListApiResponse } from '../models/rest/api-responses/publicationListApiResponse';

@Injectable({
providedIn: 'root'
})
export class PublicationService {

constructor(private http: CurationHttpService) { }

getPublications(size: number, page: number, sort: string, order: string, pmid: string, title: string, curator: string): Observable<PublicationListApiResponse> {
let params: HttpParams = new HttpParams();
params = params
.set('size', String(size))
.set('page', String(page));
if (sort) {
params = params.set('sort', sort + ',' + order);
}
if (pmid) {
params = params.set('pmid', pmid);
}
if (title) {
params = params.set('title', title);
}
if (curator) {
params = params.set('curator', curator);
}
return this.http.get('/publications', params);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.loading-shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.15);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}

.ellipsis-col {
overflow: hidden;
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
}

.mat-cell {
padding: 0 4px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<div class="mt-0 grid gap-0 grid-cols-12 card-3b"
style="min-height: 6.5rem; margin-bottom: 15px; !important; border:1px solid #999; background-color: white; padding: 20px 30px 15px ">
<div class="col-span-12 sm:col-span-6">
<div class="col-span-12">
<div style="font-size: 1.5rem; line-height: 2.0rem;" class="font-normal">Publications</div>
</div>
<div class="col-span-6">
<div style="margin-top: 5px;"><a href="#" style="margin-right: 5px;">Dashboard</a> / <a>
Publications</a></div>
</div>
</div>
<div class="col-span-12 sm:col-span-6">
<div class="mt-5 sm:mt-1 sm:float-right">
<button mat-raised-button color="primary" style="font-size:12px; margin-right: -10px;" class="gwas-elevation">
<mat-icon>share</mat-icon>
Export Data
</button>
</div>
</div>
</div>
<div class="mt-4 grid gap-0 grid-cols-12">
<aside *ngIf="showFilter"
class="col-span-12 fixed z-40 overflow-y-scroll bottom-0 top-60
sm:overflow-y-hidden md:relative md:inset-y-auto md:col-span-3 h-96
lg:col-span-3 xl:col-span-2 ml-2 card-3b bg-white">
<div class="p-3" style="background-color: #EDF8F9; border-bottom: 1px solid #ccc;">
<span class="text-sm font-normal"> Filter By: </span>
<a style="float: right; cursor: pointer;" (click)="toggleDisplay('filter')">
<mat-icon> chevron_left</mat-icon>
</a>
</div>

<div class="p-2">
<form [formGroup]="filterForm" (ngSubmit)="submitFilter()" class="flex flex-col items-center mt-7">
<mat-form-field appearance="fill" class="w-4/5">
<mat-label>PMID</mat-label>
<input matInput placeholder="Enter PMID" formControlName="pmid" type="number">
</mat-form-field>
<mat-form-field appearance="fill" class="w-4/5">
<mat-label>Title</mat-label>
<input matInput placeholder="Enter title" formControlName="title">
</mat-form-field>
<mat-form-field appearance="fill" class="w-4/5">
<mat-label>Curator</mat-label>
<input type="text"
placeholder="Select curator"
matInput
formControlName="curator"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<div class=" justify-center mt-2">
<div>
<button mat-raised-button type="submit" color="primary" (click)="search()">
<mat-icon>filter_alt</mat-icon>
Filter
</button>
</div>
</div>
</form>
</div>
</aside>
<main [ngClass]="{'sm:col-span-12 md:col-span-9 lg:col-span-9 xl:col-span-10 card-3b': showFilter}" class="col-span-12 ml-2">
<div class="card-3 mr-3">
<div class="p-3 bg-white" style="border-bottom: 1px solid #ccc;">
<span class="text-sm ml-1"> Manage Publications </span>
</div>
<div class="mat-elevation-z1 relative">
<div class="loading-shade" *ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<table mat-table [dataSource]="dataSource" matSort (matSortChange)="resetPaging()" matSortDisableClear class="w-full">
<ng-container matColumnDef="pmid">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>PMID</th>
<td mat-cell *matCellDef="let row">
{{row.pmid}}
</td>
</ng-container>
<ng-container matColumnDef="firstAuthor">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>First Author</th>
<td mat-cell *matCellDef="let row">
{{row.firstAuthor}}
</td>
</ng-container>
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>Title</th>
<td mat-cell *matCellDef="let row" class="ellipsis-col">
<span matTooltip="{{row?.title}}" matTooltipClass="mat-tooltip">{{row?.title || 'NA'}}</span>
</td>
</ng-container>
<ng-container matColumnDef="submitter">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>Submitter</th>
<td mat-cell *matCellDef="let row">
{{row.submitter}}
</td>
</ng-container>
<ng-container matColumnDef="curator">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>Curator</th>
<td mat-cell *matCellDef="let row">
<mat-form-field class="mt-1">
<input type="text"
placeholder="Curator"
matInput
[matAutocomplete]="auto"
[(ngModel)]="row.curator">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="curationStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>Curation status</th>
<td mat-cell *matCellDef="let row">
<mat-form-field class="mt-1">
<mat-label>Status</mat-label>
<mat-select [(ngModel)]="row.curationStatus">
<mat-option *ngFor="let status of curationStatuses" [value]="status">
{{status}}
</mat-option>
</mat-select>
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="publicationDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>Publication Date</th>
<td mat-cell *matCellDef="let row">
{{row.publicationDate}}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="hover:bg-gray-200 cursor-pointer" mat-row *matRowDef="let row;
columns: displayedColumns; let index = index;" [ngClass]="{gray: index % 2 == 0}"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="9999">
No data matching the search criteria.
</td>
</tr>
</table>
<mat-paginator [length]="resultsLength" [pageSize]="10" [pageSizeOptions]="[10, 20, 50, 100]" showFirstLastButtons="true"></mat-paginator>
</div>
</div>
</main>
<div class="fab-button-container" *ngIf="!showFilter">
<button (click)="toggleDisplay('filter')" mat-fab color="primary">
<mat-icon>filter_list</mat-icon>
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PublicationsListComponent } from './publications-list.component';

describe('PublicationsListComponent', () => {
let component: PublicationsListComponent;
let fixture: ComponentFixture<PublicationsListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PublicationsListComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(PublicationsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { merge, Observable, of } from 'rxjs';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { MatTableDataSource } from '@angular/material/table';
import { Publication } from '../../../../core/models/publication';
import { PublicationService } from '../../../../core/services/publication.service';
import { MatSort } from '@angular/material/sort';
import { MatPaginator } from '@angular/material/paginator';

@Component({
selector: 'app-publications-list',
templateUrl: './publications-list.component.html',
styleUrls: ['./publications-list.component.css']
})
export class PublicationsListComponent implements OnInit, AfterViewInit {
isLoadingResults = true;
pmid: string;
options: string[] = ['Elliot Sollis', 'Laura Harris', 'Santhi Ramachandran', 'Elizabeth Lewis'];
filteredOptions: Observable<string[]>;
filterForm = new FormGroup({
pmid: new FormControl(''),
title: new FormControl(''),
curator: new FormControl('')
});
showFilter = true;
menuShow = false;
visualization = false;
@ViewChild(MatSort) sort: MatSort;
@ViewChild(MatPaginator) paginator: MatPaginator;
dataSource: MatTableDataSource<Publication>;
displayedColumns: string[] = ['pmid', 'firstAuthor', 'title', 'submitter', 'curator', 'curationStatus', 'publicationDate'];
curationStatuses: string[] = ['Level 1 ancestry done','Level 2 ancestry done','Level 1 curation done','Level 2 curation done','Publish study','Awaiting Curation','Outstanding Query','CNV Paper','Curation Abandoned','Conversion Problem','Unpublished from catalog','Pending author query','Awaiting EFO assignment','Requires re-curation','Preliminary review done','Level 3 curation done','Awaiting mapping','Awaiting literature','Permanently unpublished from catalog','Scientific pilot','Requires Review'];
resultsLength = 0;

constructor(private publicationService: PublicationService) { }

ngOnInit(): void {
this.filteredOptions = this.filterForm.controls.curator.valueChanges.pipe(
startWith(''),
map(value => this._filterCurator(value || '')),
);
}

ngAfterViewInit() {
// add this.sort.sortChange to merge() params and delete datasource.sort = sort in subscribe() to enable server-side sorting
merge(this.paginator.page, this.sort.sortChange)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.publicationService
.getPublications(this.paginator.pageSize, this.paginator.pageIndex, this.sort.active, this.sort.direction, null, null, null);
}),
map(data => {
this.isLoadingResults = false;
this.resultsLength = data.page.totalElements;
return data?._embedded?.solrPublicationDToes;
}),
catchError(() => {
this.isLoadingResults = false;
return of([]);
})
)
.subscribe(value => {
this.dataSource = new MatTableDataSource<Publication>(value);
});
}

resetFilters() {

}

search() {
this.isLoadingResults = true;
this.publicationService.getPublications(this.paginator.pageSize, this.paginator.pageIndex, this.sort.active, this.sort.direction,
this.filterForm.value.pmid, this.filterForm.value.title, this.filterForm.value.curator).subscribe(value => {
this.dataSource = new MatTableDataSource<Publication>(value?._embedded?.solrPublicationDToes);
this.isLoadingResults = false;
});
}

toggleDisplay(compType: string) {
if (compType === 'filter') {
this.showFilter = (this.showFilter !== true);
} else if (compType === 'menuShow') {
this.menuShow = (this.menuShow !== true);
} else if (compType === 'visualization') {
this.visualization = (this.visualization !== true);
}
}

submitFilter() {

}

private _filterCurator(value: string): string[] {
const filterValue = value.toLowerCase();
return this.options.filter(option => option.toLowerCase().includes(filterValue));
}

resetPaging() {
this.paginator.pageIndex = 0;
}
}
Loading

0 comments on commit 5db1cf3

Please sign in to comment.