Skip to content
Permalink
Browse files

Merge branch 'develop' into feature/angularResultSelection

  • Loading branch information...
finnmartens committed May 16, 2019
2 parents e9bad0c + 13ae1cd commit 4781f62751a8b5c041262e8417e702dede3c1bc5
Showing with 504 additions and 35 deletions.
  1. +4 −0 frontend/src/app/app-routing.module.ts
  2. +5 −0 frontend/src/app/modules/metric-finder/components/line-chart/line-chart.component.html
  3. +25 −0 frontend/src/app/modules/metric-finder/components/line-chart/line-chart.component.scss
  4. +25 −0 frontend/src/app/modules/metric-finder/components/line-chart/line-chart.component.spec.ts
  5. +119 −0 frontend/src/app/modules/metric-finder/components/line-chart/line-chart.component.ts
  6. +2 −0 frontend/src/app/modules/metric-finder/metric-finder.component.html
  7. 0 frontend/src/app/modules/metric-finder/metric-finder.component.scss
  8. +37 −0 frontend/src/app/modules/metric-finder/metric-finder.component.spec.ts
  9. +19 −0 frontend/src/app/modules/metric-finder/metric-finder.component.ts
  10. +18 −0 frontend/src/app/modules/metric-finder/metric-finder.module.ts
  11. +43 −0 frontend/src/app/modules/metric-finder/models/test-result.ts
  12. +46 −0 frontend/src/app/modules/metric-finder/services/metric-finder.service.spec.ts
  13. +35 −0 frontend/src/app/modules/metric-finder/services/metric-finder.service.ts
  14. +1 −1 frontend/src/tslint.json
  15. +2 −0 grails-app/controllers/de/iteratec/osm/UrlMappings.groovy
  16. +75 −0 grails-app/controllers/de/iteratec/osm/result/MetricFinderController.groovy
  17. +10 −20 grails-app/controllers/de/iteratec/osm/result/ResultSelectionController.groovy
  18. +2 −3 grails-app/services/de/iteratec/osm/measurement/schedule/JobGroupService.groovy
  19. +20 −10 grails-app/services/de/iteratec/osm/result/ResultSelectionService.groovy
  20. +4 −0 src/main/groovy/de/iteratec/osm/result/SelectedMeasurand.groovy
  21. +11 −0 src/main/groovy/de/iteratec/osm/result/dao/EventResultQueryBuilder.groovy
  22. +1 −1 src/main/groovy/de/iteratec/osm/result/dao/query/EventResultQueryExecutor.groovy
@@ -16,6 +16,10 @@ const appRoutes: Routes = [
path: 'landing',
loadChildren: './modules/landing/landing.module#LandingModule'
},
{
path: 'metricFinder',
loadChildren: './modules/metric-finder/metric-finder.module#MetricFinderModule'
},
{
path: '',
loadChildren: './modules/landing/landing.module#LandingModule',
@@ -0,0 +1,5 @@
<div class="metric-finder">
<svg #svg class="metric-finder" (window:resize)="redraw()" xmlns="http://www.w3.org/1999/html">

</svg>
</div>
@@ -0,0 +1,25 @@
@import "../../../../../styles/colors";

osm-line-chart {
svg.metric-finder {
width: 100%;
height: 100%;
min-width: 400px;
min-height: 200px;
background: $color-card-background;
}

path.line {
stroke: $color-primary;
fill: none;
}

.y-axis, .x-axis {
color: $color-contour-light;

text {
color: $color-text-light;
}
}
}

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { LineChartComponent } from './line-chart.component';

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

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

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

it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,119 @@
import {
AfterContentInit,
Component,
ElementRef,
Input,
OnChanges,
SimpleChanges,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import {TestResult} from '../../models/test-result';
import {select} from 'd3-selection';
import {line} from 'd3-shape';
import {ScaleLinear, scaleLinear, scaleTime, ScaleTime} from 'd3-scale';
import {extent, max} from 'd3-array';
import {axisBottom, axisLeft} from 'd3-axis';
import {format} from 'd3-format';

@Component({
selector: 'osm-line-chart',
templateUrl: './line-chart.component.html',
styleUrls: ['./line-chart.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class LineChartComponent implements AfterContentInit, OnChanges {

@Input()
results: TestResult[];

@ViewChild('svg')
svgElement: ElementRef;

@Input()
metric: string;

public width: number;
public height: number;

private margin = {
left: 80,
right: 40,
top: 30,
bottom: 30,
};
private xScale: ScaleTime<number, number>;
private yScale: ScaleLinear<number, number>;

constructor() {
}

redraw() {
if (!this.results) {
return;
}
this.width = this.svgElement.nativeElement.parentElement.offsetWidth - this.margin.left - this.margin.right;
this.height = this.svgElement.nativeElement.parentElement.offsetHeight - this.margin.top - this.margin.bottom;
this.xScale = scaleTime()
.domain(extent(this.results, result => result.date))
.range([0, this.width]);
this.yScale = scaleLinear()
.domain([0, max(this.results, result => result.timings[this.metric])])
.range([this.height, 0])
.nice();
this.render();
}

ngAfterContentInit(): void {
this.redraw();
}

public render() {
const selection = select(this.svgElement.nativeElement).selectAll('g.graph').data<TestResult[]>([this.results]);
this.enter(selection.enter());
this.update(selection.merge(selection.enter()));
}

ngOnChanges(changes: SimpleChanges): void {
this.redraw();
}


private enter(selection: any) {
const svg = selection
.append('g')
.attr('class', 'graph');
svg
.append('path')
.attr('class', 'line');
svg
.append('g')
.attr('class', 'y-axis');
svg
.append('g')
.attr('class', 'x-axis');
}

private update(selection: any) {
selection
.select('g.graph')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
selection.select('path.line')
.attr('d', line<TestResult>()
.x((result: TestResult) => this.xScale(result.date.getTime()))
.y((result: TestResult) => this.yScale(result.timings[this.metric]))
);

selection
.select('g.y-axis')
.call(
axisLeft(this.yScale)
.tickFormat(d => format(',')(d) + 'ms')
.ticks(5)
);
selection
.select('g.x-axis')
.attr('transform', `translate(0,${this.height})`)
.call(axisBottom(this.xScale));
}
}
@@ -0,0 +1,2 @@

<osm-line-chart [results]="testResults$ | async" [metric]="'speedIndex'"></osm-line-chart>
@@ -0,0 +1,37 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { MetricFinderComponent } from './metric-finder.component';
import {LineChartComponent} from './components/line-chart/line-chart.component';
import {MetricFinderService} from './services/metric-finder.service';
import {EMPTY} from 'rxjs';

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

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MetricFinderComponent, LineChartComponent ],
providers: [ {
provide: MetricFinderService,
useClass: class {
public testResults$ = EMPTY;
public loadTestData = jasmine.createSpy('loadTestData');
}
} ]
})
.compileComponents();
}));

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

it('should create', () => {
const metricService: MetricFinderService = TestBed.get(MetricFinderService);
expect(component).toBeTruthy();
expect(metricService.loadTestData).toHaveBeenCalled();
});
});
@@ -0,0 +1,19 @@
import { Component} from '@angular/core';
import {Observable} from 'rxjs';
import {TestResult} from './models/test-result';
import {MetricFinderService} from './services/metric-finder.service';

@Component({
selector: 'osm-metric-finder',
templateUrl: './metric-finder.component.html',
styleUrls: ['./metric-finder.component.scss']
})
export class MetricFinderComponent {
public testResults$: Observable<TestResult[]>;

constructor(private metricFinderService: MetricFinderService) {
this.testResults$ = metricFinderService.testResults$;
metricFinderService.loadTestData();
}

}
@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MetricFinderComponent } from './metric-finder.component';
import {RouterModule} from '@angular/router';
import {MetricFinderService} from './services/metric-finder.service';
import { LineChartComponent } from './components/line-chart/line-chart.component';

@NgModule({
declarations: [MetricFinderComponent, LineChartComponent],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: MetricFinderComponent}])
],
providers: [
MetricFinderService
]
})
export class MetricFinderModule { }
@@ -0,0 +1,43 @@
import {parseDate} from '../../../utils/date.util';

export interface TestInfoDTO {
testId: string;
run: number;
cached: boolean;
step: number;
wptUrl: string;
}

export class TestInfo implements TestInfoDTO {
public testId: string;
public run: number;
public cached: boolean;
public step: number;
public wptUrl: string;

constructor(dto: TestInfoDTO) {
this.testId = dto.testId;
this.run = dto.run;
this.cached = dto.cached;
this.step = dto.step;
this.wptUrl = dto.wptUrl;
}
}

export interface TestResultDTO {
date: string | Date;
testInfo: TestInfoDTO;
timings: {[metric: string]: number};
}

export class TestResult implements TestResultDTO {
public date: Date;
public testInfo: TestInfo;
public timings: {[metric: string]: number} = {};

constructor(dto: TestResultDTO) {
this.date = parseDate(dto.date);
this.timings = dto.timings;
this.testInfo = new TestInfo(dto.testInfo);
}
}
@@ -0,0 +1,46 @@
import { TestBed } from '@angular/core/testing';

import { MetricFinderService } from './metric-finder.service';
import {HttpClientTestingModule, HttpTestingController, TestRequest} from '@angular/common/http/testing';
import {skip} from 'rxjs/operators';
import {TestInfo, TestResult, TestResultDTO} from '../models/test-result';

describe('MetricFinderService', () => {
let httpMock: HttpTestingController;
let metricService: MetricFinderService;
const now = Date.now();
const testResultDto: TestResultDTO = {
testInfo: {testId: '1', run: 1, step: 2, cached: false, wptUrl: 'http://wpt'},
timings: {speedIndex: 5},
date: new Date(now).toISOString()
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [ MetricFinderService ]
});
httpMock = TestBed.get(HttpTestingController);
metricService = TestBed.get(MetricFinderService);
});

it('should put request data into observable', (done) => {
metricService.testResults$.pipe(skip(1)).subscribe(result => {
expect(result).toEqual([new TestResult(testResultDto)]);
done();
});
metricService.loadData(new Date(now - 1000), new Date(now), 5, 6, 7);

const mockRequest: TestRequest = httpMock.expectOne(req =>
req.method === 'GET' && req.url === '/metricFinder/rest/getEventResults'
);
mockRequest.flush([testResultDto]);
expect(mockRequest.request.params.get('from')).toEqual(new Date(now - 1000).toISOString());
expect(mockRequest.request.params.get('to')).toEqual(new Date(now).toISOString());
expect(mockRequest.request.params.get('applicationId')).toEqual('5');
expect(mockRequest.request.params.get('pageId')).toEqual('6');
expect(mockRequest.request.params.get('browserId')).toEqual('7');

httpMock.verify();
});
});
@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import {TestResult, TestResultDTO} from '../models/test-result';
import {BehaviorSubject} from 'rxjs';
import {HttpClient} from '@angular/common/http';
import {map} from 'rxjs/operators';

@Injectable()
export class MetricFinderService {
public testResults$ = new BehaviorSubject<TestResult[]>([]);

constructor(
private http: HttpClient
) {
}

public loadTestData() {
const now = Date.now();
const dayInMillisecs = 1000 * 60 * 60 * 24;
this.loadData(new Date(now - 28 * dayInMillisecs), new Date(now), 94, 76, 4);
}

public loadData(from: Date, to: Date, application: number, page: number, browser: number) {
const params = {
from: from.toISOString(),
to: to.toISOString(),
applicationId: application.toString(),
pageId: page.toString(),
browserId: browser.toString()
};
this.http.get<TestResultDTO[]>('/metricFinder/rest/getEventResults', {params}).pipe(
map(dtos => dtos.map(dto => new TestResult(dto)))
).subscribe(next => this.testResults$.next(next));
}

}
Oops, something went wrong.

0 comments on commit 4781f62

Please sign in to comment.
You can’t perform that action at this time.