+ Device options
{{testModules[i].displayName}}
diff --git a/modules/ui/src/app/components/device-tests/device-tests.component.scss b/modules/ui/src/app/components/device-tests/device-tests.component.scss
index 40d1fcae3..08c50f4ec 100644
--- a/modules/ui/src/app/components/device-tests/device-tests.component.scss
+++ b/modules/ui/src/app/components/device-tests/device-tests.component.scss
@@ -1,3 +1,5 @@
+@import "../../../theming/colors";
+
:host {
overflow: auto;
}
@@ -6,3 +8,10 @@
pointer-events: none;
opacity: 0.6;
}
+
+.device-tests-title {
+ margin-top: 20px;
+ font-size: 18px;
+ line-height: 24px;
+ color: $grey-800;
+}
diff --git a/modules/ui/src/app/components/download-report/download-report.component.scss b/modules/ui/src/app/components/download-report/download-report.component.scss
index 23500eff1..9a1d5e610 100644
--- a/modules/ui/src/app/components/download-report/download-report.component.scss
+++ b/modules/ui/src/app/components/download-report/download-report.component.scss
@@ -1,3 +1,7 @@
+:host {
+ display: inline-block;
+}
+
.download-report-link {
display: inline-block;
}
diff --git a/modules/ui/src/app/device-repository/device-form/device-form.component.html b/modules/ui/src/app/device-repository/device-form/device-form.component.html
index dfa6d9924..e4ace47e8 100644
--- a/modules/ui/src/app/device-repository/device-form/device-form.component.html
+++ b/modules/ui/src/app/device-repository/device-form/device-form.component.html
@@ -27,6 +27,9 @@
Please, check. A MAC address consists of 12 hexadecimal digits (0 to 9, a to f, or A to F).
+
+ This MAC address is already used for another device in the repository.
+
{
flush();
}));
- it('should not save data when device with mac address is already exist', fakeAsync(() => {
- const closeSpy = spyOn(component.dialogRef, 'close');
- component.model.setValue('model');
- component.manufacturer.setValue('manufacturer');
- component.mac_addr.setValue('07:07:07:07:07:07');
- testRunServiceMock.hasDevice.and.returnValue(true);
-
- component.saveDevice();
- fixture.detectChanges();
-
- fixture.whenStable().then(() => {
- const error = compiled.querySelector('mat-error')!;
- expect(error.innerHTML).toContain('This MAC address is already used for another device in the repository.');
- });
-
- expect(closeSpy).not.toHaveBeenCalled();
-
- closeSpy.calls.reset();
- flush();
- }));
-
it('should not save data when server response with error', fakeAsync(() => {
const closeSpy = spyOn(component.dialogRef, 'close');
component.model.setValue('model');
@@ -303,6 +282,25 @@ describe('DeviceFormComponent', () => {
flush();
})
}));
+
+ it('should have "has_same_mac_address" error when MAC address is already used', fakeAsync(() => {
+ testRunServiceMock.hasDevice.and.returnValue(true);
+ const macAddress: HTMLInputElement = compiled.querySelector('.device-form-mac-address')!;
+ macAddress.value = '07:07:07:07:07:07';
+ macAddress.dispatchEvent(new Event('input'));
+ component.mac_addr.markAsTouched();
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const macAddressError = compiled.querySelector('mat-error')!.innerHTML;
+ const error = component.mac_addr.errors!['has_same_mac_address'];
+
+ expect(error).toBeTruthy();
+ expect(macAddressError).toContain('This MAC address is already used for another device in the repository.');
+ });
+
+ flush();
+ }));
});
describe('when device is present', () => {
diff --git a/modules/ui/src/app/device-repository/device-form/device-form.component.ts b/modules/ui/src/app/device-repository/device-form/device-form.component.ts
index 342f83264..9676e3416 100644
--- a/modules/ui/src/app/device-repository/device-form/device-form.component.ts
+++ b/modules/ui/src/app/device-repository/device-form/device-form.component.ts
@@ -3,7 +3,7 @@ import {AbstractControl, FormArray, FormBuilder, FormGroup, Validators} from '@a
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {Device, TestModule} from '../../model/device';
import {TestRunService} from '../../test-run.service';
-import {DeviceStringFormatValidator} from './device-string-format.validator';
+import {DeviceValidators} from './device.validators';
import {catchError, of, retry, Subject, takeUntil} from 'rxjs';
import {BehaviorSubject} from 'rxjs/internal/BehaviorSubject';
@@ -30,7 +30,7 @@ export class DeviceFormComponent implements OnInit, OnDestroy {
@Inject(MAT_DIALOG_DATA) public data: DialogData,
private fb: FormBuilder,
private testRunService: TestRunService,
- private deviceStringFormatValidator: DeviceStringFormatValidator,
+ private deviceValidators: DeviceValidators,
) {
}
@@ -81,11 +81,6 @@ export class DeviceFormComponent implements OnInit, OnDestroy {
return;
}
- if (!this.data.device && this.testRunService.hasDevice(this.mac_addr.value)) {
- this.error$.next('This MAC address is already used for another device in the repository.');
- return;
- }
-
const device = this.createDeviceFromForm();
this.testRunService.saveDevice(device)
@@ -142,9 +137,12 @@ export class DeviceFormComponent implements OnInit, OnDestroy {
private createDeviceForm() {
this.deviceForm = this.fb.group({
- model: ['', [this.deviceStringFormatValidator.deviceStringFormat()]],
- manufacturer: ['', [this.deviceStringFormatValidator.deviceStringFormat()]],
- mac_addr: ['', [Validators.pattern(MAC_ADDRESS_PATTERN)]],
+ model: ['', [this.deviceValidators.deviceStringFormat()]],
+ manufacturer: ['', [this.deviceValidators.deviceStringFormat()]],
+ mac_addr: ['', [
+ Validators.pattern(MAC_ADDRESS_PATTERN),
+ this.deviceValidators.differentMACAddress(this.data.device)
+ ]],
test_modules: new FormArray([])
});
}
diff --git a/modules/ui/src/app/device-repository/device-form/device-string-format.validator.ts b/modules/ui/src/app/device-repository/device-form/device.validators.ts
similarity index 54%
rename from modules/ui/src/app/device-repository/device-form/device-string-format.validator.ts
rename to modules/ui/src/app/device-repository/device-form/device.validators.ts
index b98b1d4f0..4e21c7cc8 100644
--- a/modules/ui/src/app/device-repository/device-form/device-string-format.validator.ts
+++ b/modules/ui/src/app/device-repository/device-form/device.validators.ts
@@ -1,12 +1,17 @@
import {Injectable} from '@angular/core';
import {AbstractControl, ValidationErrors, ValidatorFn} from '@angular/forms';
+import {TestRunService} from '../../test-run.service';
+import {Device} from '../../model/device';
@Injectable({providedIn: 'root'})
/**
* Validator uses for Device Name and Device Manufacturer inputs
*/
-export class DeviceStringFormatValidator {
+export class DeviceValidators {
+
+ constructor(private testRunService: TestRunService) {
+ }
readonly STRING_FORMAT_REGEXP = new RegExp('^([a-z0-9\\p{L}\\p{M}.\',-_ ]{1,64})$', 'u');
@@ -20,4 +25,15 @@ export class DeviceStringFormatValidator {
return null;
}
}
+
+ public differentMACAddress(device?: Device): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const value = control.value?.trim();
+ if (value && !device) {
+ let result = this.testRunService.hasDevice(value)
+ return result ? {'has_same_mac_address': true} : null;
+ }
+ return null;
+ }
+ }
}
diff --git a/modules/ui/src/app/device-repository/device-repository.component.html b/modules/ui/src/app/device-repository/device-repository.component.html
index f7e27265f..1d5d700b3 100644
--- a/modules/ui/src/app/device-repository/device-repository.component.html
+++ b/modules/ui/src/app/device-repository/device-repository.component.html
@@ -1,6 +1,6 @@
- Device Repository
+ Device Repository
diff --git a/modules/ui/src/app/device-repository/device-repository.component.scss b/modules/ui/src/app/device-repository/device-repository.component.scss
index 3c969f181..5504d8df4 100644
--- a/modules/ui/src/app/device-repository/device-repository.component.scss
+++ b/modules/ui/src/app/device-repository/device-repository.component.scss
@@ -15,10 +15,10 @@
}
.device-repository-toolbar {
- padding-left: 41px;
- gap: 10px;
+ padding-left: 40px;
+ gap: 16px;
background: $white;
- height: 76px;
+ height: 72px;
}
.device-repository-content {
@@ -28,5 +28,4 @@
grid-template-columns: repeat(auto-fit, $device-item-width);
gap: 16px;
overflow-y: auto;
- height: 100%;
}
diff --git a/modules/ui/src/app/history/history.component.html b/modules/ui/src/app/history/history.component.html
index 3c98e6986..8d90e5ea4 100644
--- a/modules/ui/src/app/history/history.component.html
+++ b/modules/ui/src/app/history/history.component.html
@@ -1,43 +1,36 @@
- Results
+ Results
-
-
- | Test Run # |
-
- {{getTestRunId(data)}} |
-
-
Started |
{{getFormattedDateString(data.started)}} |
-
-
- Finished |
- {{getFormattedDateString(data.finished)}} |
+
+
+ Duration |
+ {{getDuration(data.started, data.finished)}} |
-
-
- Manufacturer |
- {{data.device.manufacturer}} |
+
+
+ Device |
+ {{data.device.manufacturer}} {{data.device.model}} |
-
-
- Model |
- {{data.device.model}} |
+
+
+ Firmware |
+ {{data.device.firmware}} |
-
+
Result |
@@ -48,8 +41,8 @@
- |
-
+ | Download |
+
file_download
@@ -68,7 +61,7 @@
- Reports will automatically generate following a testrun completion.
+ Reports will automatically generate following a test run completion.
diff --git a/modules/ui/src/app/history/history.component.scss b/modules/ui/src/app/history/history.component.scss
index e049d19ff..cbf913f06 100644
--- a/modules/ui/src/app/history/history.component.scss
+++ b/modules/ui/src/app/history/history.component.scss
@@ -11,13 +11,7 @@
padding-left: 32px;
gap: 10px;
background: $white;
- height: 93px;
-
- span {
- font-size: 36px;
- font-weight: 400;
- line-height: 44px;
- }
+ height: 89px;
}
.history-content {
@@ -48,6 +42,10 @@
line-height: 20px;
letter-spacing: 0.2px;
}
+
+ .table-cell-report {
+ padding-left: 36px;
+ }
}
.results-content-empty {
diff --git a/modules/ui/src/app/history/history.component.spec.ts b/modules/ui/src/app/history/history.component.spec.ts
index 53189b6e7..cf226faf2 100644
--- a/modules/ui/src/app/history/history.component.spec.ts
+++ b/modules/ui/src/app/history/history.component.spec.ts
@@ -18,7 +18,7 @@ const history = [{
},
"report": "https://api.testrun.io/report.pdf",
"started": "2023-06-23T10:11:00.123Z",
- "finished": "2023-06-23T10:17:00.123Z"
+ "finished": "2023-06-23T10:17:10.123Z"
}] as TestrunStatus[];
describe('HistoryComponent', () => {
@@ -56,6 +56,38 @@ describe('HistoryComponent', () => {
expect(res).toEqual(history)
})
});
+
+ it('#getFormattedDateString should return string in the format "d MMM y H:mm"', () => {
+ const expectedResult = '23 Jun 2023 10:11';
+
+ const result = component.getFormattedDateString(history[0].started);
+
+ expect(result).toEqual(expectedResult);
+ });
+
+ it('#getFormattedDateString should return empty string if no date', () => {
+ const expectedResult = '';
+
+ const result = component.getFormattedDateString(null);
+
+ expect(result).toEqual(expectedResult);
+ });
+
+ it('#getDuration should return dates duration in minutes and seconds', () => {
+ const expectedResult = '06m 10s';
+
+ const result = component.getDuration(history[0].started, history[0].finished);
+
+ expect(result).toEqual(expectedResult);
+ });
+
+ it('#getDuration should return empty string if any of dates are not provided', () => {
+ const expectedResult = '';
+
+ const result = component.getDuration(history[0].started, null);
+
+ expect(result).toEqual(expectedResult);
+ });
});
describe('DOM tests', () => {
diff --git a/modules/ui/src/app/history/history.component.ts b/modules/ui/src/app/history/history.component.ts
index b9460e8fa..50d6084b2 100644
--- a/modules/ui/src/app/history/history.component.ts
+++ b/modules/ui/src/app/history/history.component.ts
@@ -11,7 +11,7 @@ import {DatePipe} from '@angular/common';
})
export class HistoryComponent implements OnInit {
history$!: Observable;
- displayedColumns: string[] = ['#', 'started', 'finished', 'manufacturer', 'model', 'result', 'report']
+ displayedColumns: string[] = ['started', 'duration', 'device', 'firmware', 'result', 'report'];
constructor(private testRunService: TestRunService, private datePipe: DatePipe) {
this.testRunService.fetchHistory();
@@ -21,14 +21,28 @@ export class HistoryComponent implements OnInit {
this.history$ = this.testRunService.getHistory();
}
- getTestRunId(data: TestrunStatus) {
- return `${data.device.manufacturer} ${data.device.model} ${data.device.firmware} ${this.getFormattedDateString(data.started)}`;
- }
-
getFormattedDateString(date: string | null) {
return date ? this.datePipe.transform(date, 'd MMM y H:mm') : '';
}
+ private transformDate(date: number, format: string) {
+ return this.datePipe.transform(date, format);
+ }
+
+ public getDuration(started: string | null, finished: string | null): string {
+ if (!started || !finished) {
+ return '';
+ }
+ const startedDate = new Date(started);
+ const finishedDate = new Date(finished);
+
+ const durationMillisecond = finishedDate.getTime() - startedDate.getTime();
+ const durationMinuts = this.transformDate(durationMillisecond, 'mm');
+ const durationSeconds = this.transformDate(durationMillisecond, 'ss');
+
+ return `${durationMinuts}m ${durationSeconds}s`
+ }
+
public getResultClass(status: string): StatusResultClassName {
return this.testRunService.getResultClass(status);
}
diff --git a/modules/ui/src/app/mocks/progress.mock.ts b/modules/ui/src/app/mocks/progress.mock.ts
index 360399b65..996b73841 100644
--- a/modules/ui/src/app/mocks/progress.mock.ts
+++ b/modules/ui/src/app/mocks/progress.mock.ts
@@ -36,9 +36,19 @@ const PROGRESS_DATA_RESPONSE = ((status: string, finished: string | null, tests:
export const MOCK_PROGRESS_DATA_IN_PROGRESS: TestrunStatus = PROGRESS_DATA_RESPONSE(StatusOfTestrun.InProgress, null, TEST_DATA);
export const MOCK_PROGRESS_DATA_COMPLIANT: TestrunStatus = PROGRESS_DATA_RESPONSE(
- StatusOfTestrun.Compliant,'2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, 'https://api.testrun.io/report.pdf'
+ StatusOfTestrun.Compliant, '2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, 'https://api.testrun.io/report.pdf'
);
export const MOCK_PROGRESS_DATA_CANCELLED: TestrunStatus = PROGRESS_DATA_RESPONSE(StatusOfTestrun.Cancelled, null, TEST_DATA);
-export const MOCK_PROGRESS_DATA_NOT_STARTED: TestrunStatus = {...MOCK_PROGRESS_DATA_IN_PROGRESS, status: StatusOfTestrun.Idle, started: null};
+export const MOCK_PROGRESS_DATA_NOT_STARTED: TestrunStatus = {
+ ...MOCK_PROGRESS_DATA_IN_PROGRESS,
+ status: StatusOfTestrun.Idle,
+ started: null
+};
+
+export const MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE: TestrunStatus = {
+ ...MOCK_PROGRESS_DATA_IN_PROGRESS,
+ status: StatusOfTestrun.WaitingForDevice,
+ started: null
+};
diff --git a/modules/ui/src/app/model/device.ts b/modules/ui/src/app/model/device.ts
index 03f4e8619..f002b048e 100644
--- a/modules/ui/src/app/model/device.ts
+++ b/modules/ui/src/app/model/device.ts
@@ -2,7 +2,8 @@ export interface Device {
manufacturer: string;
model: string;
mac_addr: string;
- test_modules?: TestModules
+ test_modules?: TestModules;
+ firmware?: string;
}
/**
diff --git a/modules/ui/src/app/notification.service.spec.ts b/modules/ui/src/app/notification.service.spec.ts
new file mode 100644
index 000000000..2307cf413
--- /dev/null
+++ b/modules/ui/src/app/notification.service.spec.ts
@@ -0,0 +1,46 @@
+import {TestBed} from '@angular/core/testing';
+
+import {NotificationService} from './notification.service';
+import {MatSnackBar} from '@angular/material/snack-bar';
+
+describe('NotificationService', () => {
+ let service: NotificationService;
+
+ const mockMatSnackBar = {
+ open: () => {
+ }
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ {provide: MatSnackBar, useValue: mockMatSnackBar},
+ ]
+ });
+ service = TestBed.inject(NotificationService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ describe('notify', () => {
+ it('should open snackbar with message', () => {
+ const matSnackBarSpy = spyOn(mockMatSnackBar, 'open').and.stub();
+
+ service.notify('something good happened');
+
+ expect(matSnackBarSpy).toHaveBeenCalled();
+
+ const args = matSnackBarSpy.calls.argsFor(0);
+ expect(args.length).toBe(3);
+ expect(args[0]).toBe('something good happened');
+ expect(args[1]).toBe('x');
+ expect(args[2]).toEqual({
+ horizontalPosition: 'right',
+ panelClass: 'test-run-notification',
+ });
+ });
+ });
+
+});
diff --git a/modules/ui/src/app/notification.service.ts b/modules/ui/src/app/notification.service.ts
new file mode 100644
index 000000000..509c2f0dd
--- /dev/null
+++ b/modules/ui/src/app/notification.service.ts
@@ -0,0 +1,17 @@
+import {Injectable} from '@angular/core';
+import {MatSnackBar} from '@angular/material/snack-bar';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NotificationService {
+ constructor(private snackBar: MatSnackBar) {
+ }
+
+ notify(message: string) {
+ this.snackBar.open(message, 'x', {
+ horizontalPosition: 'right',
+ panelClass: 'test-run-notification'
+ })
+ }
+}
diff --git a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html b/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html
index 14122bb83..9ca7cff7c 100644
--- a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html
+++ b/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.html
@@ -6,9 +6,6 @@
keyboard_arrow_right
-
- local_taxi
-
{{breadcrumb}}
diff --git a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss b/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss
index 62ad7cfb1..f2f1441ed 100644
--- a/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss
+++ b/modules/ui/src/app/progress/progress-breadcrumbs/progress-breadcrumbs.component.scss
@@ -1,15 +1,21 @@
@use '@angular/material' as mat;
@import "../../../theming/colors";
+:host {
+ width: 100%;
+ overflow: hidden;
+}
+
ul {
display: flex;
- flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 0 8px;
.breadcrumb-item {
display: flex;
+ flex: 0 1 auto;
+ max-width: 25%;
align-items: center;
justify-content: center;
gap: 8px;
@@ -17,15 +23,18 @@ ul {
&.first {
margin-right: 6px;
+ flex-shrink: 0;
}
}
.icon-home {
color: $dark-grey;
+ flex-shrink: 0;
}
.icon {
color: $secondary;
+ flex-shrink: 0;
}
.breadcrumb-text {
@@ -36,5 +45,8 @@ ul {
font-weight: 700;
line-height: 16px;
letter-spacing: 0.3px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
}
diff --git a/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.html b/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.html
index 82dc94be4..1f2862c09 100644
--- a/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.html
+++ b/modules/ui/src/app/progress/progress-initiate-form/progress-initiate-form.component.html
@@ -1,5 +1,5 @@
| |