Commit 823f3178 authored by Maria C's avatar Maria C
Browse files

serverless testing: improved UI

parent 850cff34
export class TestConfigurationResponse {
path: string;
configuration: TestConfiguration;
}
class TestConfiguration {
tests: Array<FunctionTestConfiguration>;
}
class FunctionTestConfiguration {
functionName: string;
triggerPath: string;
testCases: Array<TestCaseConfiguration>;
}
class TestCaseConfiguration {
event: string;
condition: string;
expectedValue: string;
}
class TestCaseResult {
event: string;
expectedOutput: string;
condition: string;
expectedValue: string;
actualOutput: string;
message: string;
result: string;
......
export class UploadYmlResponse {
testConfigfilePath: string;
}
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {HttpClient} 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';
import {TestConfigurationResponse} from '../model/TestConfigurationResponse';
@Injectable({
providedIn: 'root'
})
export class ServerlessTestingService {
apiUrl = `${AppConfigService.settings.apiUrl}/auth/test`;
testConfigUrl = this.apiUrl + '/config';
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); },
uploadTestConfiguration(formData: FormData): Observable<TestConfigurationResponse> {
return this.http.post(this.testConfigUrl, formData, { responseType: 'json' }).pipe(tap(
(response: TestConfigurationResponse) => { console.log('Response: ', response); },
error => { console.log('Error while uploading test configuration file: ', error); }
));
}
getTestConfiguration(): Observable<TestConfigurationResponse> {
return this.http.get(this.testConfigUrl, { responseType: 'json' }).pipe(tap(
(response: TestConfigurationResponse) => { console.log('Response: ', response); },
error => { console.log('Error while getting test configuration file: ', error); }
));
}
deleteTestConfiguration(): Observable<any> {
return this.http.delete(this.testConfigUrl, { responseType: 'json' }).pipe(tap(
() => { console.log('Deleted test config file successfully.'); },
error => { console.log('Error while deleting test configuration file: ', error); }
));
}
runTests(): Observable<TestResultTree> {
const requestUrl = this.apiUrl + '/run';
return this.http.post(requestUrl, null, {responseType: 'json'}).pipe(tap(
......
div {
padding-bottom: 10px;
samp {
white-space: pre-wrap;
}
pre {
white-space: pre-wrap;
td {
padding: 5px 30px 5px 0;
vertical-align: top;
}
mat-card {
margin: 10px 0;
}
.mat-card-content {
margin-left: 16px;
}
.test-case-key {
font-weight: bold;
}
.mat-expansion-panel-header-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
......@@ -15,7 +15,7 @@
<span>Message <pre>{{ results.message }}</pre></span>
</div>
<ng-template #resultTree>
<div>Overall duration: {{ results.duration }} seconds</div>
<p>Overall duration: {{ results.duration }} seconds</p>
<mat-accordion>
<mat-expansion-panel
*ngFor="let functionTestResult of results.functionTestResults"
......@@ -23,22 +23,26 @@
(closed)="panelOpenState = false">
<mat-expansion-panel-header>
<mat-panel-title>
<span>{{ functionTestResult.functionName }} ({{ functionTestResult.overallResult }})</span>
<span><samp>{{ functionTestResult.functionName }}</samp> (<samp>{{ functionTestResult.overallResult }}</samp>)</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>
<table>
<tr><td class="test-case-key">Function name</td><td><samp>{{ functionTestResult.functionName }}</samp></td></tr>
<tr><td class="test-case-key">Tests duration</td><td>{{ functionTestResult.duration }} seconds</td></tr>
<tr><td class="test-case-key">Number of passed tests</td><td>{{ functionTestResult.passed }}</td></tr>
<tr><td class="test-case-key">Number of failed tests</td><td>{{ functionTestResult.failed }}</td></tr>
<tr><td class="test-case-key">Number of passed ignored</td><td>{{ functionTestResult.ignored }}</td></tr>
<tr *ngIf="functionTestResult.failedAtStage">
<td class="test-case-key">Failed to run tests due to failure at stage</td>
<td><samp>{{ functionTestResult.failedAtStage }}</samp></td>
</tr>
<tr *ngIf="functionTestResult.message">
<td class="test-case-key">Message</td>
<td><samp>{{ functionTestResult.message }}</samp></td>
</tr>
</table>
<mat-accordion>
<div>Test cases</div>
<h4>Test cases</h4>
<mat-expansion-panel
*ngFor="let testCaseResult of functionTestResult.testCaseResults; let i = index"
(opened)="panelOpenState = true"
......@@ -49,12 +53,20 @@
<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>
<table>
<tr><td class="test-case-key">Result</td><td><samp>{{ testCaseResult.result }}</samp></td></tr>
<tr><td class="test-case-key">Duration</td><td>{{ testCaseResult.duration }} seconds</td></tr>
<tr><td class="test-case-key">Input event</td><td><samp>{{ testCaseResult.event }}</samp></td></tr>
<tr><td class="test-case-key">Condition for the output</td><td><samp>{{ testCaseResult.condition }}</samp></td></tr>
<tr><td class="test-case-key">Expected value</td><td><samp>{{ testCaseResult.expectedValue }}</samp></td></tr>
<tr *ngIf="testCaseResult.actualOutput">
<td class="test-case-key">Actual output</td><td><samp>{{ testCaseResult.actualOutput }}</samp></td>
</tr>
<tr *ngIf="testCaseResult.message">
<td class="test-case-key">Message</td><td><samp>{{ testCaseResult.message }}</samp></td>
</tr>
</table>
</mat-expansion-panel>
</mat-accordion>
......
div {
margin-bottom: 10px;
margin-top: 10px;
button {
padding: 0 5px;
}
button {
margin-left: var(--double-margin);
mat-card {
margin: 10px 0;
}
.mat-card-content {
margin-left: 16px;
}
.mat-card-actions {
margin-left: 16px;
}
.mat-card-actions span {
padding-right: 8px;
}
......@@ -4,14 +4,11 @@
<mat-card-title>
Run tests
</mat-card-title>
<mat-card-subtitle>
Click on the button to execute the tests.
</mat-card-subtitle>
</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...
......@@ -19,5 +16,17 @@
<mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar>
</div>
</mat-card-content>
<mat-card-actions>
<button
mat-raised-button
color="primary"
[disabled]="testsRunning"
(click)="runTests()"
title="Run tests"
>
<mat-icon>play_arrow</mat-icon>
<span>Run tests</span>
</button>
</mat-card-actions>
</mat-card>
</div>
td {
padding: 5px 30px 5px 0;
vertical-align: top;
}
button {
padding: 0 5px;
}
mat-card {
margin: 10px 0;
}
.mat-button {
margin-left: 16px;
}
.file {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-top: 20px;
}
.mat-accordion .mat-expansion-panel:first-of-type {
margin-top: 10px;
}
.mat-expansion-panel-header-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.test-case-key {
font-weight: bold;
}
.delete-button {
color: var(--color-warn);
}
.mat-card-content {
margin-left: 16px;
}
.mat-card-actions {
margin-left: 16px;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
.mat-card-actions span {
padding-right: 8px;
}
......@@ -21,28 +21,105 @@
</div>
<div class="file" *ngIf="fileToUpload != null">
{{fileToUpload.name}} ({{convertBytesToSize(fileToUpload.size)}})
</div>
</mat-card-content>
<mat-card-actions>
<button
mat-button
color="primary"
[disabled]="fileUploadDisabled"
(click)="onFileUploadEvent()"
title="Upload file"
>
<mat-icon>file_upload</mat-icon>
<span>Upload</span>
</button>
<button
mat-button
color="warn"
[disabled]="fileUploadDisabled"
(click)="removeSelectedFile()"
title="Cancel"
>
<mat-icon>cancel</mat-icon>
<span>Cancel</span>
</button>
</mat-card-actions>
</mat-card>
<mat-card>
<mat-card-header>
<mat-card-title>
Test configuration
</mat-card-title>
<mat-card-subtitle>
You can check the current test configuration.
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div>
<div *ngIf="!testConfig; else testConfigBlock">
<p>There is no test configuration file on the server.</p>
</div>
<ng-template #testConfigBlock>
<h4>Configuration file</h4>
<pre>{{ testConfig.path }}</pre>
<h4>Tests</h4>
<mat-accordion>
<mat-expansion-panel
*ngFor="let functionConfig of testConfig.configuration.tests"
(opened)="panelOpenState = true"
(closed)="panelOpenState = false">
<mat-expansion-panel-header>
<mat-panel-title>
<samp>{{ functionConfig.functionName }}</samp>
</mat-panel-title>
</mat-expansion-panel-header>
<table *ngIf="functionConfig.triggerPath">
<tr><td class="test-case-key">Trigger path</td><td><samp>{{ functionConfig.triggerPath }}</samp></td></tr>
</table>
<mat-accordion>
<h4>Test cases</h4>
<mat-expansion-panel
*ngFor="let testCase of functionConfig.testCases; let i = index"
(opened)="panelOpenState = true"
(closed)="panelOpenState = false"
>
<mat-expansion-panel-header>
<mat-panel-title>
<span>
#{{ i+1 }} Output invoked with <samp>{{ testCase.event }}</samp>
{{ formatCondition(testCase.condition) }} <samp>{{ testCase.expectedValue }}</samp>
</span>
</mat-panel-title>
</mat-expansion-panel-header>
<table>
<tr><td class="test-case-key">Input event</td><td><samp>{{ testCase.event }}</samp></td></tr>
<tr><td class="test-case-key">Condition for the output</td><td><samp>{{ testCase.condition }}</samp></td></tr>
<tr><td class="test-case-key">Expected value</td><td><samp>{{ testCase.expectedValue }}</samp></td></tr>
</table>
</mat-expansion-panel>
</mat-accordion>
<div class="action-buttons">
<button
mat-button
color="primary"
[disabled]="fileUploadDisabled"
(click)="onFileUploadEvent()"
title="Upload file"
>
<mat-icon>file_upload</mat-icon>
</button>
</mat-expansion-panel>
</mat-accordion>
</ng-template>
</div>
<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-actions>
<button
*ngIf="testConfig"
mat-button
class="delete-button"
[disabled]="deletingTestConfig"
(click)="onFileDeletionEvent()"
title="Delete test configuration"
>
<mat-icon>delete</mat-icon>
Delete
</button>
</mat-card-actions>
</mat-card>
</div>
import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
import {ServerlessTestingService} from '../service/serverless-testing.service';
import {MatSnackBar} from '@angular/material';
import {TestConfigurationResponse} from '../model/TestConfigurationResponse';
@Component({
selector: 'app-uploader-yml',
......@@ -9,8 +10,11 @@ import {MatSnackBar} from '@angular/material';
})
export class UploaderYmlComponent implements OnInit {
uploadInProgress = false;
panelOpenState = false;
deletingTestConfig = false;
fileToUpload: File = null;
total: number = null;
testConfig: TestConfigurationResponse = null;
@ViewChild('ymlFile') ymlFile: ElementRef;
......@@ -21,6 +25,22 @@ export class UploaderYmlComponent implements OnInit {
}
ngOnInit(): void {
this.testingServerlessService.getTestConfiguration().subscribe(
response => {
this.testConfig = response;
},
error => {
if (error.error.status === 404) {
this.testConfig = null;
} else {
this.snackBar.open(
`Error while getting test config: ${error.error.message}`,
'Close',
{duration: 10000}
);
}
}
);
}
@Input()
......@@ -42,6 +62,23 @@ export class UploaderYmlComponent implements OnInit {
return Math.round(bytes / Math.pow(1024, i)) + ' ' + sizes[i];
}
formatCondition(condition): string {
switch (condition) {
case 'EQUALS':
return 'should equal';
case 'STARTS_WITH':
return 'should start with';
case 'ENDS_WITH':
return 'should end with';
case 'CONTAINS_SUBSTRING':
return 'should contain substring';
case 'EQUALS_IGNORE_CASE':
return 'should equal (ignoring the case)';
case 'MATCHES_REGEX':
return 'should match regex pattern';
}
}
get fileUploadDisabled() {
return this.uploadInProgress || this.fileToUpload == null;
}
......@@ -63,12 +100,17 @@ export class UploaderYmlComponent implements OnInit {
'Close',
{duration: 10000},
);
this.testConfig = response;
this.removeSelectedFile();
},
error => {
this.uploadInProgress = false;
console.log(error);
this.snackBar.open(`Error while uploading: ${error.error.message}`, 'Close', {duration: 10000});
this.snackBar.open(
`Error while uploading: ${error.error.message}`,
'Close',
{duration: 10000}
);
}
);
}
......@@ -77,4 +119,28 @@ export class UploaderYmlComponent implements OnInit {
this.fileToUpload = null;
this.ymlFile.nativeElement.value = null;
}
onFileDeletionEvent() {
this.deletingTestConfig = true;
this.testingServerlessService.deleteTestConfiguration().subscribe(
response => {
this.deletingTestConfig = false;
this.testConfig = null;
this.snackBar.open(
'Successfully deleted test config file.',
'Close',
{duration: 10000},
);
},
error => {
this.deletingTestConfig = false;
console.log(error);
this.snackBar.open(
`Error while deleting test config file: ${error.error.message}`,
'Close',
{duration: 10000}
);
}
);
}
}
Markdown is supported
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