Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion modules/ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ <h1 class="main-heading">Testrun</h1>
<mat-icon>tune</mat-icon>
</button>
<app-shutdown-app
[disable]="testrunInProgress((systemStatus$ | async)?.status)">
[disable]="isTestrunInProgress(vm.systemStatus?.status)">
</app-shutdown-app>
</mat-toolbar>
<div class="app-content-main" role="main" id="main">
Expand Down Expand Up @@ -207,6 +207,7 @@ <h1 class="main-heading">Testrun</h1>
class="settings-drawer">
<app-certificates
[certificates]="vm.certificates"
(deleteCertificateEvent)="deleteCertificate($event)"
(closeCertificatedEvent)="closeCertificates()"></app-certificates>
</mat-drawer>
</ng-container>
Expand Down
9 changes: 9 additions & 0 deletions modules/ui/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
} from './store/selectors';
import { MatIconTestingModule } from '@angular/material/icon/testing';
import { CertificatesComponent } from './pages/certificates/certificates.component';
import { of } from 'rxjs';

describe('AppComponent', () => {
let component: AppComponent;
Expand Down Expand Up @@ -96,8 +97,10 @@ describe('AppComponent', () => {
'getTestModules',
'testrunInProgress',
'fetchCertificates',
'deleteCertificate',
]);

mockService.deleteCertificate.and.returnValue(of(true));
mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [
'focusFirstElementInContainer',
]);
Expand Down Expand Up @@ -673,6 +676,12 @@ describe('AppComponent', () => {

expect(component.certDrawer.open).toHaveBeenCalledTimes(1);
});

it('should call delete certificate', () => {
component.deleteCertificate('name');

expect(mockService.deleteCertificate).toHaveBeenCalledWith('name');
});
});

@Component({
Expand Down
4 changes: 4 additions & 0 deletions modules/ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,8 @@ export class AppComponent {
isTestrunInProgress(status?: string) {
return this.testRunService.testrunInProgress(status);
}

deleteCertificate(name: string) {
this.appStore.deleteCertificate(name);
}
}
18 changes: 17 additions & 1 deletion modules/ui/src/app/app.store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import SpyObj = jasmine.SpyObj;
import { device } from './mocks/device.mock';
import { setDevices, setTestrunStatus } from './store/actions';
import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/progress.mock';
import { certificate } from './mocks/certificate.mock';
import { certificate, certificate2 } from './mocks/certificate.mock';

const mock = (() => {
let store: { [key: string]: string } = {};
Expand Down Expand Up @@ -62,6 +62,7 @@ describe('AppStore', () => {
'fetchDevices',
'fetchSystemStatus',
'fetchCertificates',
'deleteCertificate',
]);

TestBed.configureTestingModule({
Expand Down Expand Up @@ -216,5 +217,20 @@ describe('AppStore', () => {
appStore.getCertificates();
});
});

describe('deleteCertificate', () => {
it('should update store', done => {
mockService.deleteCertificate.and.returnValue(of(true));

appStore.updateCertificates([certificate, certificate2]);

appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => {
expect(store.certificates).toEqual([certificate2]);
done();
});

appStore.deleteCertificate('iot.bms.google.com');
});
});
});
});
22 changes: 21 additions & 1 deletion modules/ui/src/app/app.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { tap } from 'rxjs/operators';
import { tap, withLatestFrom } from 'rxjs/operators';
import {
selectError,
selectHasConnectionSettings,
Expand Down Expand Up @@ -148,6 +148,26 @@ export class AppStore extends ComponentStore<AppComponentState> {
})
);
});

deleteCertificate = this.effect<string>(trigger$ => {
return trigger$.pipe(
exhaustMap((name: string) => {
return this.testRunService.deleteCertificate(name).pipe(
withLatestFrom(this.certificates$),
tap(([, current]) => {
this.removeCertificate(name, current);
})
);
})
);
});

private removeCertificate(name: string, current: Certificate[]) {
const certificates = current.filter(
certificate => certificate.name !== name
);
this.updateCertificates(certificates);
}
constructor(
private store: Store<AppState>,
private testRunService: TestRunService
Expand Down
6 changes: 6 additions & 0 deletions modules/ui/src/app/mocks/certificate.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ export const certificate = {
organisation: 'Google, Inc.',
expires: '2024-09-01T09:00:12Z',
} as Certificate;

export const certificate2 = {
name: 'sensor.bms.google.com',
organisation: 'Google, Inc.',
expires: '2024-09-01T09:00:12Z',
} as Certificate;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
<button
class="certificate-item-delete"
mat-icon-button
aria-label="Delete certificate">
aria-label="Delete certificate"
(click)="deleteButtonClicked.emit(certificate.name)">
<mat-icon fontSet="material-symbols-outlined"> delete </mat-icon>
</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,24 @@ describe('CertificateItemComponent', () => {
expect(date?.textContent?.trim()).toEqual('01 Sep 2024');
});

it('should have delete button', () => {
const deleteButton = fixture.nativeElement.querySelector(
'#main .test-button'
) as HTMLButtonElement;
describe('delete button', () => {
let deleteButton: HTMLButtonElement;
beforeEach(() => {
deleteButton = fixture.nativeElement.querySelector(
'.certificate-item-delete'
) as HTMLButtonElement;
});

expect(deleteButton).toBeDefined();
it('should be present', () => {
expect(deleteButton).toBeDefined();
});

it('should emit delete event on delete button clicked', () => {
const deleteSpy = spyOn(component.deleteButtonClicked, 'emit');
deleteButton.click();

expect(deleteSpy).toHaveBeenCalledWith('iot.bms.google.com');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Certificate } from '../../../model/certificate';
import { MatIcon } from '@angular/material/icon';
import { DatePipe } from '@angular/common';
Expand All @@ -13,4 +13,5 @@ import { MatButtonModule } from '@angular/material/button';
})
export class CertificateItemComponent {
@Input() certificate!: Certificate;
@Output() deleteButtonClicked = new EventEmitter<string>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ <h2 class="certificates-drawer-header-title">Certificates</h2>
<section class="content-certificates">
<app-certificate-item
*ngFor="let certificate of certificates"
[certificate]="certificate"></app-certificate-item>
[certificate]="certificate"
(deleteButtonClicked)="deleteCertificate($event)"></app-certificate-item>
</section>
<div class="certificates-drawer-footer">
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';

import { CertificatesComponent } from './certificates.component';
import { MatIconTestingModule } from '@angular/material/icon/testing';
import { MatIcon } from '@angular/material/icon';
import { certificate } from '../../mocks/certificate.mock';
import { LiveAnnouncer } from '@angular/cdk/a11y';
import SpyObj = jasmine.SpyObj;
import { of } from 'rxjs';
import { MatDialogRef } from '@angular/material/dialog';
import { DeleteFormComponent } from '../../components/delete-form/delete-form.component';

describe('CertificatesComponent', () => {
let component: CertificatesComponent;
Expand Down Expand Up @@ -95,4 +98,30 @@ describe('CertificatesComponent', () => {
});
});
});

describe('Class tests', () => {
describe('#deleteCertificate', () => {
it('should open delete certificate modal', fakeAsync(() => {
const openSpy = spyOn(component.dialog, 'open').and.returnValue({
afterClosed: () => of(true),
} as MatDialogRef<typeof DeleteFormComponent>);

component.deleteCertificate(certificate.name);

expect(openSpy).toHaveBeenCalledWith(DeleteFormComponent, {
ariaLabel: 'Delete certificate',
data: {
title: 'Delete certificate',
content: `You are about to delete a certificate iot.bms.google.com. Are you sure?`,
},
autoFocus: true,
hasBackdrop: true,
disableClose: true,
panelClass: 'delete-form-dialog',
});

openSpy.calls.reset();
}));
});
});
});
49 changes: 46 additions & 3 deletions modules/ui/src/app/pages/certificates/certificates.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import {
Component,
EventEmitter,
Input,
OnDestroy,
Output,
} from '@angular/core';
import { MatIcon } from '@angular/material/icon';
import { CertificateItemComponent } from './certificate-item/certificate-item.component';
import { NgForOf } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { Certificate } from '../../model/certificate';
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { DeleteFormComponent } from '../../components/delete-form/delete-form.component';
import { Subject, takeUntil } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';

@Component({
selector: 'app-certificates',
Expand All @@ -28,14 +37,48 @@ import { LiveAnnouncer } from '@angular/cdk/a11y';
templateUrl: './certificates.component.html',
styleUrl: './certificates.component.scss',
})
export class CertificatesComponent {
export class CertificatesComponent implements OnDestroy {
@Input() certificates: Certificate[] = [];
@Output() closeCertificatedEvent = new EventEmitter<void>();
@Output() deleteCertificateEvent = new EventEmitter<string>();

constructor(private liveAnnouncer: LiveAnnouncer) {}
private destroy$: Subject<boolean> = new Subject<boolean>();

constructor(
private liveAnnouncer: LiveAnnouncer,
public dialog: MatDialog
) {}

ngOnDestroy() {
this.destroy$.next(true);
this.destroy$.unsubscribe();
}

closeCertificates() {
this.liveAnnouncer.announce('The certificates panel is closed.');
this.closeCertificatedEvent.emit();
}

deleteCertificate(name: string) {
const dialogRef = this.dialog.open(DeleteFormComponent, {
ariaLabel: 'Delete certificate',
data: {
title: 'Delete certificate',
content: `You are about to delete a certificate ${name}. Are you sure?`,
},
autoFocus: true,
hasBackdrop: true,
disableClose: true,
panelClass: 'delete-form-dialog',
});

dialogRef
?.afterClosed()
.pipe(takeUntil(this.destroy$))
.subscribe(deleteCertificate => {
if (deleteCertificate) {
this.deleteCertificateEvent.emit(name);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ describe('ProgressStatusCardComponent', () => {

describe('with available systemStatus$ data, as "In Progress" and finish date', () => {
beforeEach(() => {
component.systemStatus$ = of({
component.systemStatus = {
...MOCK_PROGRESS_DATA_IN_PROGRESS,
finished: '2023-06-22T09:26:00.123Z',
});
};
fixture.detectChanges();
});

Expand Down
14 changes: 14 additions & 0 deletions modules/ui/src/app/services/test-run.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,4 +475,18 @@ describe('TestRunService', () => {

req.flush(certificates);
});

it('deleteCertificate should delete certificate', () => {
service.deleteCertificate('test').subscribe(res => {
expect(res).toEqual(true);
});

const req = httpTestingController.expectOne(
'http://localhost:8000/system/config/certs/delete'
);

expect(req.request.method).toBe('DELETE');

req.flush(true);
});
});
8 changes: 8 additions & 0 deletions modules/ui/src/app/services/test-run.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,12 @@ export class TestRunService {
fetchCertificates(): Observable<Certificate[]> {
return this.http.get<Certificate[]>(`${API_URL}/system/config/certs/list`);
}

deleteCertificate(name: string): Observable<boolean> {
return this.http
.delete<boolean>(`${API_URL}/system/config/certs/delete`, {
body: JSON.stringify({ name }),
})
.pipe(map(() => true));
}
}