Skip to content
Snippets Groups Projects
Commit f25404fd authored by Julian Matschinske's avatar Julian Matschinske
Browse files

Merge branch 'analysis-window-component' into 'master'

Analysis window component

See merge request covid-19/frontend!32
parents c474545c 9fd06189
No related branches found
No related tags found
No related merge requests found
Showing
with 409 additions and 214 deletions
......@@ -7,22 +7,30 @@ import {environment} from '../environments/environment';
@Injectable({
providedIn: 'root'
})
export class AnalysisService {
private selectedProteins = new Map<string, Protein>();
private selectSubject = new Subject<{protein: Protein, selected: boolean}>();
private selectSubject = new Subject<{ protein: Protein, selected: boolean }>();
private token: string | null = null;
public tokens: any[] = [];
private stats: any;
private task: any;
public tasks: any[] = [];
private intervalId: any;
constructor(private http: HttpClient) {
this.token = localStorage.getItem('token');
if (this.token) {
this.startWatching();
const tokens = localStorage.getItem('tokens');
if (tokens) {
this.tokens = JSON.parse(tokens);
}
this.startWatching();
}
async getTasks() {
return await this.http.get<any>(`${environment.backend}tasks/?tokens=${JSON.stringify(this.tokens)}`).toPromise().catch((e) => {
clearInterval(this.intervalId);
});
}
addProtein(protein: Protein) {
......@@ -56,13 +64,17 @@ export class AnalysisService {
});
}
getTask(): any {
return this.task;
getTask(token): any {
this.tasks.forEach((task) => {
if (task.token === token) {
return task;
}
});
}
reset() {
this.token = null;
this.task = null;
this.tokens = null;
this.tasks = null;
this.stats = null;
if (this.intervalId) {
clearInterval(this.intervalId);
......@@ -78,20 +90,15 @@ export class AnalysisService {
algorithm,
parameters,
}).toPromise();
this.token = resp.token;
localStorage.setItem('token', this.token);
this.tokens.push(resp.token);
localStorage.setItem('tokens', JSON.stringify(this.tokens));
this.startWatching();
}
async startWatching() {
startWatching() {
this.intervalId = setInterval(async () => {
const resp = await this.http.get<any>(`${environment.backend}task/?token=${this.token}`).toPromise().catch((e) => {
clearInterval(this.intervalId);
});
this.task = resp.task;
this.stats = resp.stats;
if (this.task.done) {
clearInterval(this.intervalId);
if (this.tokens.length > 0) {
this.tasks = await this.getTasks();
}
}, 1000);
}
......
......@@ -16,6 +16,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ProteinAnalysisComponent} from './components/protein-analysis/protein-analysis.component';
import {SelectDatasetComponent} from './components/select-dataset/select-dataset.component';
import {AnalysisWindowComponent} from './components/analysis-window/analysis-window.component';
import { TaskListComponent } from './components/task-list/task-list.component';
@NgModule({
......@@ -28,6 +29,7 @@ import {AnalysisWindowComponent} from './components/analysis-window/analysis-win
ProteinAnalysisComponent,
SelectDatasetComponent,
AnalysisWindowComponent,
TaskListComponent,
],
imports: [
BrowserModule,
......
<div class="card analysis">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-flask" aria-hidden="true"></i>
</span>
Analysis
</p>
<a (click)="closeAnalysisWindow()" class="card-header-icon" aria-label="more options">
<span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nec iaculis mauris.
<a href="#">@bulmaio</a>. <a href="#">#css</a> <a href="#">#responsive</a>
<br>
<time datetime="2016-1-1">11:09 PM - 1 Jan 2016</time>
<div *ngIf="token">
<div class="card analysis" *ngIf="info && info.done">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-flask" aria-hidden="true"></i>
</span>
Analysis Results
</p>
<a (click)="close()" class="card-header-icon" aria-label="close">
<span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div class="content">
<div class="network center" #network>
<button class="button is-loading center">Loading</button>
</div>
</div>
<button (click)="toggleDrugs()">Show/hide</button>
</div>
<footer class="card-footer">
</footer>
</div>
<div class="card analysis" *ngIf="info && !info.startedAt">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-cog" aria-hidden="true"></i>
</span>
Analysis Queued...
</p>
<a (click)="close()" class="card-header-icon" aria-label="close">
<span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div class="content">
The analysis is queued
<!--TODO: Display queue information-->
</div>
</div>
<footer class="card-footer">
</footer>
</div>
<div class="card analysis" *ngIf="info && info.startedAt && !info.done">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-cog fa-spin" aria-hidden="true"></i>
</span>
Analysis in Progress...
</p>
<a (click)="close()" class="card-header-icon" aria-label="close">
<span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div class="content">
The analysis is in progress
<!--TODO: Display analysis progress-->
</div>
</div>
<footer class="card-footer">
</footer>
</div>
<div class="card analysis" *ngIf="!info">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-question" aria-hidden="true"></i>
</span>
Analysis not found
</p>
<a (click)="close()" class="card-header-icon" aria-label="more options">
<span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div class="content">
<span class="notification is-danger">
The analysis you were looking for is either gone or never existed.
</span>
</div>
</div>
<footer class="card-footer">
</footer>
</div>
<footer class="card-footer">
<button [disabled]="true" (click)="export()" class="card-footer-item button is-primary"><span class="icon">
<i class="fas fa-cloud-download-alt" aria-hidden="true"></i>
</span><span>Export</span></button>
<button [disabled]="true" (click)="discard()" class="card-footer-item button is-danger"><span>Discard</span><span class="icon">
<i class="fas fa-trash" aria-hidden="true"></i>
</span></button>
<button (click)="closeAnalysisWindow()" class="card-footer-item button"><span>Close</span> <span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span></button>
</footer>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import { AnalysisWindowComponent } from './analysis-window.component';
import {AnalysisWindowComponent} from './analysis-window.component';
import {HttpClientModule} from '@angular/common/http';
describe('AnalysisWindowComponent', () => {
let component: AnalysisWindowComponent;
......@@ -8,9 +9,10 @@ describe('AnalysisWindowComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AnalysisWindowComponent ]
declarations: [AnalysisWindowComponent],
imports: [HttpClientModule],
})
.compileComponents();
.compileComponents();
}));
beforeEach(() => {
......
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from '../../../environments/environment';
import {Edge, Effect, getDatasetFilename, Protein, ProteinNetwork} from '../../pages/protein-network';
import {AnalysisService} from '../../analysis.service';
declare var vis: any;
@Component({
selector: 'app-analysis-window',
templateUrl: './analysis-window.component.html',
styleUrls: ['./analysis-window.component.scss']
})
export class AnalysisWindowComponent implements OnInit {
export class AnalysisWindowComponent implements OnInit, OnChanges {
@Input() token: string | null = null;
@Output() tokenChange = new EventEmitter<string | null>();
public info: any = null;
public stats: any = null;
@Input() analysisWindow: boolean;
@Output() closeWindow = new EventEmitter<boolean>();
@ViewChild('network', {static: false}) networkEl: ElementRef;
constructor() { }
private network: any;
private nodeData: { nodes: any, edges: any } = {nodes: null, edges: null};
private drugNodes = [];
private showDrugs = false;
ngOnInit(): void {
constructor(private http: HttpClient, private analysis: AnalysisService) {
}
closeAnalysisWindow() {
this.closeWindow.emit(this.analysisWindow);
async ngOnInit() {
}
async ngOnChanges(changes: SimpleChanges) {
if (this.token) {
const {info, stats} = await this.getTask(this.token);
this.info = info;
this.stats = stats;
if (this.info && this.info.done) {
const result = await this.http.get<any>(`${environment.backend}result/?token=${this.token}`).toPromise();
this.createNetwork(result);
}
}
}
private async getTask(token: string): Promise<any> {
return await this.http.get(`${environment.backend}task/?token=${token}`).toPromise();
}
close() {
this.token = null;
this.tokenChange.emit(this.token);
}
discard() {
......@@ -26,4 +61,100 @@ export class AnalysisWindowComponent implements OnInit {
export() {
}
public async createNetwork(result: any) {
const {nodes, edges} = this.mapDataToNodes(result);
this.nodeData.nodes = new vis.DataSet(nodes);
this.nodeData.edges = new vis.DataSet(edges);
const container = this.networkEl.nativeElement;
const options = {
layout: {
improvedLayout: false,
},
};
this.network = new vis.Network(container, this.nodeData, options);
}
private mapProteinToNode(protein: any): any {
let color = '#e2b600';
if (this.analysis.inSelection(protein)) {
color = '#c42eff';
}
return {
id: `p_${protein.proteinAc}`,
label: `${protein.proteinAc}`,
size: 10, color, shape: 'ellipse', shadow: false,
};
}
private mapDrugToNode(drug: any): any {
let color = '#ffffff';
if (drug.status === 'investigational') {
color = '#ffa066';
} else if (drug.status === 'approved') {
color = '#a0ff66';
}
return {
id: `d_${drug.drugId}`,
label: `${drug.name}`,
size: 10, color, shape: 'ellipse', shadow: true, font: {color: '#000000', size: 5},
};
}
private mapProteinProteinInteractionToEdge(edge: any): any {
return {
from: `p_${edge.from}`,
to: `p_${edge.to}`,
color: {color: '#afafaf', highlight: '#854141'},
};
}
private mapDrugProteinInteractionToEdge(edge: any): any {
return {
from: `p_${edge.proteinAc}`,
to: `d_${edge.drugId}`,
color: {color: '#afafaf', highlight: '#854141'},
};
}
private mapDataToNodes(result: any): { nodes: any[], edges: any[] } {
const nodes = [];
const edges = [];
for (const protein of result.proteins) {
nodes.push(this.mapProteinToNode(protein));
}
for (const drug of result.drugs) {
this.drugNodes.push(this.mapDrugToNode(drug));
}
for (const network of result.networks) {
for (const edge of network.ppEdges) {
edges.push(this.mapProteinProteinInteractionToEdge(edge));
}
}
for (const edge of result.dpEdges) {
edges.push(this.mapDrugProteinInteractionToEdge(edge));
}
return {
nodes,
edges,
};
}
public toggleDrugs() {
this.showDrugs = !this.showDrugs;
if (!this.showDrugs) {
this.nodeData.nodes.remove(this.drugNodes);
} else {
this.nodeData.nodes.add(this.drugNodes);
}
}
}
<div class="modal" [class.is-active]="show">
<div class="modal-background"></div>
<div class="modal-card" *ngIf="!analysis.getTask()">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Launch Protein Analysis</p>
<button class="delete" aria-label="close" (click)="close()"></button>
......@@ -35,62 +35,10 @@
<span class="icon"><i class="fa fa-play"></i></span>
<span>Key Pathway Miner</span>
</button>
<button class="button is-primary" (click)="startTask()">
<button class="button is-primary" (click)="startTask(); close()">
<span class="icon"><i class="fa fa-play"></i></span>
<span>Demo</span>
</button>
</footer>
</div>
<div class="modal-card" *ngIf="analysis.getTask()">
<div *ngIf="!analysis.getTask().startedAt">
<header class="modal-card-head">
<p class="modal-card-title">Queued...</p>
<button class="delete" aria-label="close" (click)="close()"></button>
</header>
<section class="modal-card-body">
<p>
Queue position: {{analysis.getStats().queuePosition}}
</p>
<p>
Queue length: {{analysis.getStats().queueLength}}
</p>
</section>
<footer class="modal-card-foot">
<button class="button is-danger" (click)="analysis.reset()">
<span class="icon"><i class="fa fa-stop"></i></span>
<span>Stop</span>
</button>
</footer>
</div>
<div *ngIf="analysis.getTask().startedAt && !analysis.getTask().done">
<header class="modal-card-head">
<p class="modal-card-title">Running...</p>
<button class="delete" aria-label="close" (click)="close()"></button>
</header>
<section class="modal-card-body">
<progress class="progress is-primary" [value]="analysis.getTask().progress * 100" max="100">15%</progress>
</section>
<footer class="modal-card-foot">
<button class="button is-danger" (click)="analysis.reset()">
<span class="icon"><i class="fa fa-stop"></i></span>
<span>Stop</span>
</button>
</footer>
</div>
<div *ngIf="analysis.getTask().done">
<header class="modal-card-head">
<p class="modal-card-title">View Results</p>
<button class="delete" aria-label="close" (click)="close()"></button>
</header>
<section class="modal-card-body">
<pre>{{analysis.getTask().result}}</pre>
</section>
<footer class="modal-card-foot">
<button class="button is-danger" (click)="analysis.reset()">
<span class="icon"><i class="fa fa-stop"></i></span>
<span>Discard</span>
</button>
</footer>
</div>
</div>
</div>
<div class="content">
<div class="list is-hoverable">
<a *ngFor="let task of analysis.tasks" class="list-item">
<div *ngIf="!task.info.startedAt">
<a (click)="open(task.token)"><b>Algorithm: {{task.info.algorithm}}</b></a><br>
Queue Length: {{task.stats.queueLength}}<br>
Queue Position:{{task.stats.queuePosition}}
</div>
<div *ngIf="task.info.startedAt && !task.info.done">
<a (click)="open(task.token)">Algorithm: {{task.info.algorithm}}</a><br>
<progress class="progress is-primary" [value]="task.info.progress * 100" max="100"></progress>
</div>
<div *ngIf="task.info.done">
<a (click)="open(task.token)"><span>Algorithm: {{task.info.algorithm}}</span>
<span class="icon is-success"><i class="fas fa-check is-success"
aria-hidden="true"></i>
</span></a>
</div>
</a>
</div>
</div>
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {TaskListComponent} from './task-list.component';
import {HttpClientModule} from '@angular/common/http';
describe('TaskListComponent', () => {
let component: TaskListComponent;
let fixture: ComponentFixture<TaskListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TaskListComponent],
imports: [HttpClientModule],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaskListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {AnalysisService} from '../../analysis.service';
@Component({
selector: 'app-task-list',
templateUrl: './task-list.component.html',
styleUrls: ['./task-list.component.scss']
})
export class TaskListComponent implements OnInit {
@Output() token: EventEmitter<any> = new EventEmitter();
constructor(public analysis: AnalysisService) {
}
ngOnInit(): void {
}
open(token) {
this.token.emit(token);
}
}
<app-protein-analysis [(show)]="showAnalysisDialog"></app-protein-analysis>
<div class="content explorer">
<div class="covex explorer">
<div class="content left-window">
<div class="covex left-window">
<div>
<div class="content bar-left">
<div class="covex bar-left">
<div class="card bar-small">
<header class="card-header">
......@@ -95,9 +95,7 @@
</div>
</div>
<div class="content network">
<div class="covex network">
<div class="card network">
<header class="card-header">
<p class="card-header-title">
......@@ -127,12 +125,12 @@
</div>
</div>
<div class="analysis-view" [hidden]="!analysisWindow">
<app-analysis-window (closeWindow)="closeAnalysisWindow()"></app-analysis-window>
<div class="analysis-view" *ngIf="selectedAnalysisToken">
<app-analysis-window [(token)]="selectedAnalysisToken"></app-analysis-window>
</div>
</div>
<div class="content bar-right">
<div class="covex bar-right">
<div class="card bar-medium">
<header class="card-header">
......@@ -158,7 +156,7 @@
<div *ngIf="!showDetails">
Please select a node for further information.
<a (click)="showAnalysisWindow()"> Open Analysis Window </a>
<!-- <a (click)="selectedAnalysisToken = 'oy4UsXfBDobTucdQBhN9IUzfnpqKwzqx'"> Open Analysis Window </a>-->
</div>
</div>
......@@ -181,13 +179,9 @@
</button>
<p></p>
<button (click)="showAnalysisDialog = true"
class="button"
[class.is-info]="!analysis.getTask()"
[class.is-warning]="analysis.getTask() && !analysis.getTask().startedAt"
[class.is-primary]="analysis.getTask() && analysis.getTask().startedAt && !analysis.getTask().done"
[class.is-success]="analysis.getTask() && analysis.getTask().done"
[disabled]="analysis.getCount() === 0 && !analysis.getTask()">
<span *ngIf="!analysis.getTask()">
class="button is-primary"
[disabled]="analysis.getCount() === 0">
<span>
<span class="icon">
<i class="fa fa-list"></i>
</span>
......@@ -196,67 +190,55 @@
</span>
</span>
<span *ngIf="analysis.getTask() && !analysis.getTask().startedAt">
<span class="icon">
<i class="fa fa-cog"></i>
</span>
<span>
Analysis queued...
</span>
</span>
</button>
</div>
<span *ngIf="analysis.getTask() && !analysis.getTask().done && analysis.getTask().startedAt">
<span class="icon">
<i class="fa fa-cog fa-spin"></i>
</span>
<span>
Analysis running ({{analysis.getTask().progress | percent}})
</span>
</span>
<span *ngIf="analysis.getTask() && analysis.getTask().done">
<span class="icon">
<i class="fa fa-check"></i>
</span>
<span>
Analysis done
</span>
</span>
</button>
<div class="card bar-large">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-filter" aria-hidden="true"></i>
</span> Tasks
</p>
</header>
<div class="card-content">
<app-task-list (token)="selectedAnalysisToken = $event"></app-task-list>
</div>
</div>
</div>
<div class="card bar-large">
<header class="card-header">
<p class="card-header-title">
<div class="card bar-large">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-filter" aria-hidden="true"></i>
</span> Selection
</p>
</header>
<div class="card-content overflow">
<table class="table">
<thead>
<tr>
<td>AC</td>
<td>Actions</td>
</tr>
</thead>
<tbody>
<tr *ngFor="let p of analysis.getSelection()">
<td>{{p.proteinAc}}</td>
<td>
<button (click)="analysis.removeProtein(p)" class="button is-small is-danger">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</p>
</header>
<div class="card-content overflow">
<table class="table">
<thead>
<tr>
<td>AC</td>
<td>Actions</td>
</tr>
</thead>
<tbody>
<tr *ngFor="let p of analysis.getSelection()">
<td>{{p.proteinAc}}</td>
<td>
<button (click)="analysis.removeProtein(p)" class="button is-small is-danger">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -4,8 +4,6 @@ import {
ElementRef,
OnInit,
ViewChild,
Output,
EventEmitter,
HostListener
} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
......@@ -51,7 +49,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit {
public queryItems = [];
public showAnalysisDialog = false;
public analysisWindow = false;
public selectedAnalysisToken: string | null = null;
public currentDataset = [];
private array = [0];
......@@ -88,15 +86,6 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit {
}
}
closeAnalysisWindow() {
this.analysisWindow = false;
}
showAnalysisWindow() {
this.analysisWindow = true;
}
constructor(private http: HttpClient,
private route: ActivatedRoute,
private router: Router,
......@@ -416,15 +405,6 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit {
};
}
// TODO: Remove this:
private random() {
const x = Math.sin(this.seed++) * 10000;
return x - Math.floor(x);
}
// Selection
// TODO: Improve usage of group ids, revise this after models have been changed to just protein
inSelection(proteinAc: string): boolean {
if (!this.proteinData || !proteinAc) {
return false;
......
......@@ -60,14 +60,14 @@ input.checkbox {
margin-left: 15px;
}
div.content.bar-left {
div.covex.bar-left {
float: left;
width: 350px;
height: calc(100vh - 102px);
overflow: auto;
}
div.content.bar-right {
div.covex.bar-right {
float: right;
width: 350px;
height: calc(100vh - 102px);
......@@ -96,14 +96,13 @@ div.card-content.overflow {
height: 500px;
}
div.content.left-window {
div.covex.left-window {
float: left;
width: calc(100vw - 350px - 2 * 20px);
height: 100%;
}
div.content.network {
div.covex.network {
width: calc(100% - 350px - 40px);
height: calc(100vh - 100px);
margin-left: 20px;
......@@ -127,13 +126,13 @@ div.center {
}
div.content.explorer {
div.covex.explorer {
height: calc(100vh - 90px);
margin-left: 20px;
margin-right: 20px;
}
div.analysis-view {
.analysis-view {
height: 100%;
width: calc(100% - 20px);
position: relative;
......@@ -143,5 +142,9 @@ div.analysis-view {
padding: 0;
}
i.fas.is-success {
}
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
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