Commit f3cf1611 authored by Alicja Reniewicz's avatar Alicja Reniewicz
Browse files

Merge branch 'rc3.1' into 'master'

Rc3.1

See merge request !8
parents 6df11092 e20ef2d0
Pipeline #12214 canceled with stage
......@@ -10,13 +10,17 @@ build-angular-ui:
- dist/melodic-frontend
before_script:
- npm install -g --save-dev @angular/cli@latest
- npm install --save-dev --unsafe-perm node-sass
- npm install --save-dev --unsafe-perm node-sass@4.14.1
- npm install
script:
- ng build --prod
build-ui-docker:
stage: build-ui-docker
only:
- master
- rc3.0
- rc3.1
image: docker:19.03.1
services:
- docker:19.03.1-dind
......
......@@ -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>
......
......@@ -40,7 +40,7 @@
<mat-header-cell *matHeaderCellDef> Value</mat-header-cell>
<mat-cell *matCellDef="let element;" [formGroup]="element">
<mat-form-field>
<input matInput placeholder="value" formControlName="value">
<input matInput placeholder="value" formControlName="value" type="password">
</mat-form-field>
</mat-cell>
</ng-container>
......
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;
condition: string;
expectedValue: 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>;
}
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} from '@angular/common/http';
import {tap} from 'rxjs/operators';
import {Observable} from 'rxjs';
import {AppConfigService} from '../../app-config/service/app-config.service';
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<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(
(response: TestResultTree) => { console.log('Response: ', response); },
error => { console.log('Error while running tests: ', error); }
));
}
}
samp {
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;
}
<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>
<p>Overall duration: {{ results.duration }} seconds</p>
<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><samp>{{ functionTestResult.functionName }}</samp> (<samp>{{ functionTestResult.overallResult }}</samp>)</span>
</mat-panel-title>
</mat-expansion-panel-header>
<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>
<h4>Test cases</h4>
<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>
<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>
</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 {
}
}
button {
padding: 0 5px;
}
mat-card {
margin: 10px 0;
}
.mat-card-content {
margin-left: 16px;
}
.mat-card-actions {
margin-left: 16px;
}
.mat-card-actions span {
padding-right: 8px;
}
<div id="test-runner-id">
<mat-card>
<mat-card-header>
<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 *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-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>
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();
}
}
td {
padding: 5px 30px 5px 0;
vertical-align: top;
}
button {
padding: 0 5px;
}
mat-card {
margin: 10px 0;
}
.mat-button {
margin-left: 16px;
}
.file {
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;
}
.mat-card-actions span {
padding-right: 8px;
}
<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>
</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>