Commit 57c0af12 authored by Maria C's avatar Maria C Committed by Alicja Reniewicz
Browse files

Add Serverless Testing view

parent 77c3c5df
......@@ -35,6 +35,11 @@ const routes: Routes = [
canActivate: [CommonUserAdminRoleGuard]
},
{
path: 'serverless-testing', loadChildren: './serverless-testing/serverless-testing.module#ServerlessTestingModule',
canActivate: [CommonUserAdminRoleGuard]
},
{path: '**', loadChildren: './user/user.module#UserModule'},
];
......
......@@ -29,6 +29,7 @@
<a mat-list-item routerLink="/process/details/offer"><i class="material-icons">local_offer</i>&nbsp;Offers</a>
<a mat-list-item routerLink="/simulation"><i class="material-icons">toys</i>&nbsp;Simulation
</a>
<a mat-list-item routerLink="/serverless-testing"><i class="material-icons">cloud_done</i>&nbsp;Serverless Testing</a>
<a *ngIf="isAdmin()" mat-list-item routerLink="/user"><i class="material-icons">supervisor_account</i>&nbsp;Manage Users</a>
<a mat-list-item routerLink="/user/password"><i class="material-icons">visibility</i>&nbsp;Change Password</a>
<a mat-list-item (click)="onLogOutClick(); snav.close()"><i class="material-icons">eject</i>&nbsp;Log Out</a>
......
class TestCaseResult {
event: string;
expectedOutput: string;
actualOutput: string;
message: string;
result: string;
duration: number;
}
class FunctionTestResult {
functionName: string;
duration: number;
passed: number;
failed: number;
ignored: number;
testCaseResults: Array<TestCaseResult>;
overallResult: string;
failedAtStage: string;
message: string;
}
export class TestResultTree {
testsRunResult: string;
failedAtStage: string;
message: string;
duration: number;
functionTestResults: Array<FunctionTestResult>;
}
export class UploadYmlResponse {
testConfigfilePath: string;
}
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ServerlessTestingMainComponent} from '../serverless-testing-main/serverless-testing-main.component';
const routes: Routes = [
{path: '', component: ServerlessTestingMainComponent},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ServerlessTestingRoutingModule {
}
<mat-card>
<mat-card-header>
<mat-card-title>
Test Serverless Functions
</mat-card-title>
<mat-card-subtitle>
Here you can test your serverless functions which have been deployed to the cloud.
Upload a test configuration file and then run the tests.
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<app-uploader-yml></app-uploader-yml>
<app-test-runner
[testsRunning]="testsRunning"
(runTestsEvent)="runTests()"
></app-test-runner>
<app-test-results
[results]="testResultTree"
></app-test-results>
</mat-card-content>
</mat-card>
import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {UploaderYmlComponent} from '../uploader-yml/uploader-yml.component';
import {MatSnackBar} from '@angular/material';
import {ServerlessTestingService} from '../service/serverless-testing.service';
import {TestRunnerComponent} from '../test-runner/test-runner.component';
import {TestResultsComponent} from '../test-results/test-results.component';
import {TestResultTree} from '../model/TestResultTree';
@Component({
selector: 'app-serverless-testing',
templateUrl: './serverless-testing-main.component.html',
styleUrls: ['../../app.component.css']
})
export class ServerlessTestingMainComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(UploaderYmlComponent) uploaderYmlComponent: UploaderYmlComponent;
@ViewChild(TestRunnerComponent) testRunnerComponent: TestRunnerComponent;
@ViewChild(TestResultsComponent) testResultsComponent: TestResultsComponent;
testsRunning = false;
testResultTree: TestResultTree = null;
constructor(
private snackBar: MatSnackBar,
private testingServerlessService: ServerlessTestingService,
) {
}
ngOnInit() {
localStorage.setItem('viewTitle', 'Serverless Testing');
console.log('Component Serverless-testing-main initiated');
}
ngAfterViewInit(): void {
}
ngOnDestroy(): void {
console.log('Component Serverless-testing-main destroyed');
}
runTests() {
this.testsRunning = true;
this.testingServerlessService.runTests().subscribe(
response => {
this.snackBar.open('Tests run ended.', 'Close', {duration: 10000});
this.testResultTree = response;
this.testsRunning = false;
},
error => {
this.testsRunning = false;
console.log(error);
this.snackBar.open(`${error.error.message}`, 'Close', {duration: 10000});
},
);
}
}
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ServerlessTestingMainComponent} from './serverless-testing-main/serverless-testing-main.component';
import {AngularMaterialModule} from '../angular-material/angular-material.module';
import {ServerlessTestingRoutingModule} from './route/serverless-testing-routing.module';
import {UploaderYmlComponent} from './uploader-yml/uploader-yml.component';
import {TestRunnerComponent} from './test-runner/test-runner.component';
import {TestResultsComponent} from './test-results/test-results.component';
@NgModule({
declarations: [
ServerlessTestingMainComponent,
UploaderYmlComponent,
TestRunnerComponent,
TestResultsComponent,
],
imports: [
CommonModule,
ServerlessTestingRoutingModule,
AngularMaterialModule
],
exports: [
],
entryComponents: [
]
})
export class ServerlessTestingModule {
}
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {tap} from 'rxjs/operators';
import {Observable} from 'rxjs';
import {AppConfigService} from '../../app-config/service/app-config.service';
import {UploadYmlResponse} from '../model/UploadYmlResponse';
import {TestResultTree} from '../model/TestResultTree';
@Injectable({
providedIn: 'root'
})
export class ServerlessTestingService {
apiUrl = `${AppConfigService.settings.apiUrl}/auth/test`;
constructor(private http: HttpClient) {
}
uploadTestConfiguration(formData: FormData): Observable<UploadYmlResponse> {
const uploadUrl = this.apiUrl + '/upload';
return this.http.post(uploadUrl, formData, { responseType: 'json' }).pipe(tap(
(response: UploadYmlResponse) => { console.log('Response: ', response); },
error => { console.log('Error while uploading test configuration file: ', error); }
));
}
runTests(): Observable<TestResultTree> {
const requestUrl = this.apiUrl + '/run';
return this.http.post(requestUrl, null, {responseType: 'json'}).pipe(tap(
(response: TestResultTree) => { console.log('Response: ', response); },
error => { console.log('Error while running tests: ', error); }
));
}
}
div {
padding-bottom: 10px;
}
pre {
white-space: pre-wrap;
}
<div id="test-results-id">
<mat-card>
<mat-card-header>
<mat-card-title>
Test Results
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div *ngIf="results == null; else resultsBlock">
There are currently no results.
</div>
<ng-template #resultsBlock>
<div *ngIf="results.testsRunResult === 'FAILURE'; else resultTree">
<p>An error occurred while running the tests at stage {{ results.failedAtStage }}.</p>
<span>Message <pre>{{ results.message }}</pre></span>
</div>
<ng-template #resultTree>
<div>Overall duration: {{ results.duration }} seconds</div>
<mat-accordion>
<mat-expansion-panel
*ngFor="let functionTestResult of results.functionTestResults"
(opened)="panelOpenState = true"
(closed)="panelOpenState = false">
<mat-expansion-panel-header>
<mat-panel-title>
<span>{{ functionTestResult.functionName }} ({{ functionTestResult.overallResult }})</span>
</mat-panel-title>
</mat-expansion-panel-header>
<span>Function name <pre>{{ functionTestResult.functionName }}</pre></span>
<span>Tests duration (seconds) <pre>{{ functionTestResult.duration }}</pre></span>
<span>Summary <pre>{{ functionTestResult.overallResult }}</pre></span>
<span>Number of passed tests <pre>{{ functionTestResult.passed }}</pre></span>
<span>Number of failed tests <pre>{{ functionTestResult.failed }}</pre></span>
<span>Number of ignored tests <pre>{{ functionTestResult.ignored }}</pre></span>
<span *ngIf="functionTestResult.failedAtStage">
Failed to run tests due to failure at stage
<pre>{{ functionTestResult.failedAtStage }}</pre>
</span>
<span *ngIf="functionTestResult.message">Message <pre>{{ functionTestResult.message }}</pre></span>
<mat-accordion>
<div>Test cases</div>
<mat-expansion-panel
*ngFor="let testCaseResult of functionTestResult.testCaseResults; let i = index"
(opened)="panelOpenState = true"
(closed)="panelOpenState = false"
>
<mat-expansion-panel-header>
<mat-panel-title>
<span>#{{ i+1 }} ({{ testCaseResult.result }})</span>
</mat-panel-title>
</mat-expansion-panel-header>
<span>Result <pre>{{ testCaseResult.result }}</pre></span>
<span>Duration <pre>{{ testCaseResult.duration }}</pre></span>
<span>Input <pre>{{ testCaseResult.event }}</pre></span>
<span>Expected output <pre>{{ testCaseResult.expectedOutput }}</pre></span>
<span *ngIf="testCaseResult.actualOutput">Actual output <pre>{{ testCaseResult.actualOutput }}</pre></span>
<span *ngIf="testCaseResult.message">Message <pre>{{ testCaseResult.message }}</pre></span>
</mat-expansion-panel>
</mat-accordion>
</mat-expansion-panel>
</mat-accordion>
</ng-template>
</ng-template>
</mat-card-content>
</mat-card>
</div>
import {Component, Input, OnInit} from '@angular/core';
import {TestResultTree} from '../model/TestResultTree';
@Component({
selector: 'app-test-results',
templateUrl: './test-results.component.html',
styleUrls: ['./test-results.component.css'],
})
export class TestResultsComponent implements OnInit {
@Input() results: TestResultTree;
panelOpenState = false;
ngOnInit(): void {
}
}
div {
margin-bottom: 10px;
margin-top: 10px;
}
button {
margin-left: var(--double-margin);
}
<div id="test-runner-id">
<mat-card>
<mat-card-header>
<mat-card-title>
Run tests
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>Click here to run the tests.</div>
<button mat-fab (click)="runTests()">
<mat-icon>play_arrow</mat-icon>
</button>
<div *ngIf="testsRunning" >
<div>
Please wait. It may take a while...
</div>
<mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar>
</div>
</mat-card-content>
</mat-card>
</div>
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
@Component({
selector: 'app-test-runner',
templateUrl: './test-runner.component.html',
styleUrls: ['./test-runner.component.css'],
})
export class TestRunnerComponent implements OnInit {
@Input() testsRunning: boolean;
@Output() runTestsEvent = new EventEmitter();
ngOnInit(): void {
}
runTests() {
this.runTestsEvent.emit();
}
}
.file {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
}
<div id="yml-uploader-div">
<mat-card>
<mat-card-header>
<mat-card-title>
YAML file Upload
</mat-card-title>
<mat-card-subtitle>
Upload the test configuration file here. Only .yml and .yaml files are accepted.
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div>
<input
type="file"
#ymlFile
id="fileUploadDialog"
(change)="onFileSelectionEvent($event)"
accept=".yml,.yaml"
>
</div>
<div class="file" *ngIf="fileToUpload != null">
{{fileToUpload.name}} ({{convertBytesToSize(fileToUpload.size)}})
<div class="action-buttons">
<button
mat-button
color="primary"
[disabled]="fileUploadDisabled"
(click)="onFileUploadEvent()"
title="Upload file"
>
<mat-icon>file_upload</mat-icon>
</button>
<button
mat-button color="warn"
[disabled]="fileUploadDisabled"
(click)="removeSelectedFile()"
title="Remove selected file"
>
<mat-icon>cancel</mat-icon>
</button>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
import {ServerlessTestingService} from '../service/serverless-testing.service';
import {MatSnackBar} from '@angular/material';
@Component({
selector: 'app-uploader-yml',
templateUrl: './uploader-yml.component.html',
styleUrls: ['./uploader-yml.component.css'],
})
export class UploaderYmlComponent implements OnInit {
uploadInProgress = false;
fileToUpload: File = null;
total: number = null;
@ViewChild('ymlFile') ymlFile: ElementRef;
constructor(
private snackBar: MatSnackBar,
private testingServerlessService: ServerlessTestingService,
) {
}
ngOnInit(): void {
}
@Input()
set file(file: any) {
this.fileToUpload = file;
this.total = this.fileToUpload.size;
}
get file(): any {
return this.fileToUpload;
}
convertBytesToSize(bytes): string {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) {
return '0 Byte';
}
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
}
get fileUploadDisabled() {
return this.uploadInProgress || this.fileToUpload == null;
}
onFileSelectionEvent(event: Event) {
this.fileToUpload = (<HTMLInputElement> event.target).files[0];
console.log('Received event: ', event);
}
onFileUploadEvent() {
this.uploadInProgress = true;
const formData: FormData = new FormData();
formData.set('file', this.fileToUpload, this.fileToUpload.name);
this.testingServerlessService.uploadTestConfiguration(formData).subscribe(
response => {
this.uploadInProgress = false;
this.snackBar.open(
`Successfully uploaded test config file: ${this.fileToUpload.name}`,
'Close',
{duration: 10000},
);
this.removeSelectedFile();
},
error => {
this.uploadInProgress = false;
console.log(error);
this.snackBar.open(`Error while uploading: ${error.error.message}`, 'Close', {duration: 10000});
}
);
}
removeSelectedFile() {
this.fileToUpload = null;
this.ymlFile.nativeElement.value = null;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment