feat: added dialog-service and new project dialog
This commit is contained in:
parent
a54f064692
commit
8dc287fc8a
|
@ -6,7 +6,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
|||
import {
|
||||
NbLayoutModule,
|
||||
NbToastrModule,
|
||||
NbIconModule, NbCardModule, NbButtonModule,
|
||||
NbIconModule, NbCardModule, NbButtonModule, NbDialogService, NbDialogModule,
|
||||
} from '@nebular/theme';
|
||||
import {NbEvaIconsModule} from '@nebular/eva-icons';
|
||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||
|
@ -26,6 +26,7 @@ import {HomeModule} from './home/home.module';
|
|||
import {KeycloakService} from 'keycloak-angular';
|
||||
import {httpInterceptorProviders} from '@shared/interceptors';
|
||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -53,6 +54,7 @@ import {FlexLayoutModule} from '@angular/flex-layout';
|
|||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
NbDialogModule.forRoot(),
|
||||
HeaderModule,
|
||||
HomeModule,
|
||||
FlexLayoutModule
|
||||
|
@ -67,7 +69,9 @@ import {FlexLayoutModule} from '@angular/flex-layout';
|
|||
},
|
||||
KeycloakService,
|
||||
httpInterceptorProviders,
|
||||
NotificationService
|
||||
NotificationService,
|
||||
NbDialogService,
|
||||
DialogService
|
||||
],
|
||||
bootstrap: [
|
||||
AppComponent
|
||||
|
|
|
@ -48,7 +48,6 @@ describe('LoginComponent', () => {
|
|||
BrowserAnimationsModule,
|
||||
ReactiveFormsModule,
|
||||
NbInputModule,
|
||||
NbCardModule,
|
||||
NbButtonModule,
|
||||
NbLayoutModule,
|
||||
ThemeModule.forRoot(),
|
||||
|
|
|
@ -22,6 +22,8 @@ import {ProjectServiceMock} from '@shared/services/project.service.mock';
|
|||
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||
import {KeycloakService} from 'keycloak-angular';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||
|
||||
describe('ProjectOverviewComponent', () => {
|
||||
let component: ProjectOverviewComponent;
|
||||
|
@ -61,6 +63,7 @@ describe('ProjectOverviewComponent', () => {
|
|||
providers: [
|
||||
KeycloakService,
|
||||
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
||||
{provide: DialogService, useClass: DialogServiceMock},
|
||||
{provide: NotificationService, useValue: new NotificationServiceMock()}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||
import {Project} from '@shared/models/project.model';
|
||||
import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {untilDestroyed} from 'ngx-take-until-destroy';
|
||||
import {ProjectService} from '@shared/services/project.service';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
import {tap} from 'rxjs/operators';
|
||||
import {filter, mergeMap, tap} from 'rxjs/operators';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||
import {NB_DIALOG_CONFIG} from '@nebular/theme/components/dialog/dialog-config';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-overview',
|
||||
|
@ -21,20 +24,21 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(
|
||||
private readonly projectService: ProjectService,
|
||||
private readonly dialogService: DialogService,
|
||||
private readonly notificationService: NotificationService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getProjects();
|
||||
this.loadProjects();
|
||||
}
|
||||
|
||||
getProjects(): void {
|
||||
loadProjects(): void {
|
||||
this.projectService.getProjects()
|
||||
.pipe(
|
||||
untilDestroyed(this),
|
||||
tap(() => this.loading$.next(true))
|
||||
)
|
||||
.subscribe( {
|
||||
.subscribe({
|
||||
next: (projects) => {
|
||||
this.projects.next(projects);
|
||||
this.loading$.next(false);
|
||||
|
@ -48,7 +52,28 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
onClickAddProject(): void {
|
||||
console.log('to be implemented...');
|
||||
this.dialogService.openCustomDialog(
|
||||
ProjectDialogComponent,
|
||||
{
|
||||
closeOnEsc: false,
|
||||
hasScroll: false,
|
||||
autoFocus: false,
|
||||
closeOnBackdropClick: false
|
||||
}
|
||||
).onClose.pipe(
|
||||
filter(value => !!value),
|
||||
mergeMap((value: SaveProjectDialogBody) => this.projectService.saveProject(value)),
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.loadProjects();
|
||||
this.notificationService.showPopup('project.popup.save.success', PopupType.SUCCESS);
|
||||
},
|
||||
error: error => {
|
||||
console.error(error);
|
||||
this.notificationService.showPopup('project.popup.save.failed', PopupType.FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClickEditProject(): void {
|
||||
|
|
|
@ -2,12 +2,15 @@ import {NgModule} from '@angular/core';
|
|||
import {CommonModule} from '@angular/common';
|
||||
import {ProjectOverviewComponent} from './project-overview.component';
|
||||
import {ProjectOverviewRoutingModule} from './project-overview-routing.module';
|
||||
import {NbButtonModule, NbCardModule, NbProgressBarModule} from '@nebular/theme';
|
||||
import {NbButtonModule, NbCardModule, NbDialogService, NbProgressBarModule} from '@nebular/theme';
|
||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe';
|
||||
import {ProjectModule} from './project';
|
||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -23,7 +26,12 @@ import {ProjectModule} from './project';
|
|||
FontAwesomeModule,
|
||||
TranslateModule,
|
||||
NbProgressBarModule,
|
||||
ProjectModule
|
||||
ProjectModule,
|
||||
ProjectDialogModule
|
||||
],
|
||||
providers: [
|
||||
DialogService,
|
||||
NbDialogService
|
||||
]
|
||||
})
|
||||
export class ProjectOverviewModule {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
"global": {
|
||||
"action.login": "Einloggen",
|
||||
"action.retry": "Erneut Versuchen",
|
||||
"action.save": "Speichern",
|
||||
"action.cancel": "Abbrechen",
|
||||
"username": "Nutzername",
|
||||
"password": "Passwort"
|
||||
},
|
||||
|
@ -28,9 +30,18 @@
|
|||
"add.project": "Projekt hinzufügen",
|
||||
"no.projects": "Keine Projekte verfügbar"
|
||||
},
|
||||
"popup": {
|
||||
"not.found": "Keine Projekte gefunden"
|
||||
"create": {
|
||||
"header": "Neues Projekt erstellen"
|
||||
},
|
||||
"popup": {
|
||||
"not.found": "Keine Projekte gefunden",
|
||||
"save.success": "Projekt erfolgreich gespeichert",
|
||||
"save.failed": "Projekt konnte nicht gespeichert werden"
|
||||
},
|
||||
"title.label": "Projekt Titel",
|
||||
"client.label": "Name des Auftraggebers",
|
||||
"tester.label": "Name des Pentester",
|
||||
"title": "Titel",
|
||||
"client": "Klient",
|
||||
"tester": "Tester",
|
||||
"createdAt": "Erstellt am"
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
"global": {
|
||||
"action.login": "Login",
|
||||
"action.retry": "Try again",
|
||||
"action.save": "Save",
|
||||
"action.cancel": "Cancel",
|
||||
"username": "Username",
|
||||
"password": "Password"
|
||||
},
|
||||
|
@ -28,9 +30,18 @@
|
|||
"add.project": "Add project",
|
||||
"no.projects": "No projects available"
|
||||
},
|
||||
"popup": {
|
||||
"not.found": "No projects found"
|
||||
"create": {
|
||||
"header": "Create New Project"
|
||||
},
|
||||
"popup": {
|
||||
"not.found": "No projects found",
|
||||
"save.success": "Project saved successfully",
|
||||
"save.failed": "Project could not be saved"
|
||||
},
|
||||
"title.label": "Project Title",
|
||||
"client.label": "Name of Client",
|
||||
"tester.label": "Name of Pentester",
|
||||
"title": "Title",
|
||||
"client": "Client",
|
||||
"tester": "Tester",
|
||||
"createdAt": "Created at"
|
||||
|
|
|
@ -20,3 +20,9 @@ export class Project {
|
|||
this.createdBy = createdBy;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SaveProjectDialogBody {
|
||||
title: string;
|
||||
client: string;
|
||||
tester: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<nb-card #dialog accent="primary" class="project-dialog">
|
||||
<nb-card-header fxLayoutAlign="start center" class="project-dialog-header">
|
||||
{{ 'project.create.header' | translate }}
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<form [formGroup]="projectFormGroup" fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
|
||||
<nb-form-field class="project-form-field">
|
||||
<label for="projectTitleInput" class="label">
|
||||
{{'project.title.label' | translate}}:
|
||||
</label>
|
||||
<input formControlName="projectTitle" required
|
||||
id="projectTitleInput" nbInput
|
||||
class="input" type="text" fullWidth
|
||||
status="{{formCtrlStatus}}"
|
||||
placeholder="{{'project.title' | translate}} *">
|
||||
</nb-form-field>
|
||||
|
||||
<nb-form-field class="project-form-field">
|
||||
<label for="projectClientInput" class="label">
|
||||
{{'project.client.label' | translate}}:
|
||||
</label>
|
||||
<input formControlName="projectClient" required
|
||||
id="projectClientInput" nbInput
|
||||
class="input" type="text" fullWidth
|
||||
status="{{formCtrlStatus}}"
|
||||
placeholder="{{'project.client' | translate}} *">
|
||||
</nb-form-field>
|
||||
|
||||
<nb-form-field class="project-form-field">
|
||||
<label for="projectTesterInput" class="label">
|
||||
{{'project.tester.label' | translate}}:
|
||||
</label>
|
||||
<input formControlName="projectTester" required
|
||||
id="projectTesterInput" nbInput
|
||||
class="input" type="text" fullWidth
|
||||
status="{{formCtrlStatus}}"
|
||||
placeholder="{{'project.tester' | translate}} *">
|
||||
</nb-form-field>
|
||||
</form>
|
||||
</nb-card-body>
|
||||
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
|
||||
<button nbButton status="success" [disabled]="formIsEmptyOrInvalid()" (click)="onClickSave(projectFormGroup.value)">
|
||||
{{ 'global.action.save' | translate}}
|
||||
</button>
|
||||
<button nbButton status="danger" (click)="onClickClose()">
|
||||
{{ 'global.action.cancel' | translate }}
|
||||
</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
|
@ -0,0 +1,23 @@
|
|||
.project-dialog {
|
||||
width: 24rem;
|
||||
height: 31rem;
|
||||
|
||||
.project-dialog-header {
|
||||
height: 8vh;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
nb-form-field {
|
||||
padding: 0.5rem 0 0.75rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 15rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ProjectDialogComponent} from './project-dialog.component';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NbButtonModule, NbCardModule, NbDialogRef, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme';
|
||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {NotificationService} from '@shared/services/notification.service';
|
||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
describe('ProjectDialogComponent', () => {
|
||||
let component: ProjectDialogComponent;
|
||||
let fixture: ComponentFixture<ProjectDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
ProjectDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
NbLayoutModule,
|
||||
NbCardModule,
|
||||
NbButtonModule,
|
||||
FlexLayoutModule,
|
||||
NbInputModule,
|
||||
NbFormFieldModule,
|
||||
ReactiveFormsModule,
|
||||
BrowserAnimationsModule,
|
||||
ThemeModule.forRoot(),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
HttpClientModule,
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [
|
||||
{provide: NotificationService, useValue: new NotificationServiceMock()},
|
||||
{provide: DialogService, useClass: DialogServiceMock},
|
||||
{provide: NbDialogRef, useValue: {}}
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProjectDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {NbDialogRef} from '@nebular/theme';
|
||||
import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||
import {FieldStatus} from '@shared/models/form-field-status.model';
|
||||
import {untilDestroyed} from 'ngx-take-until-destroy';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-dialog',
|
||||
templateUrl: './project-dialog.component.html',
|
||||
styleUrls: ['./project-dialog.component.scss']
|
||||
})
|
||||
export class ProjectDialogComponent implements OnInit, OnDestroy {
|
||||
// form control elements
|
||||
projectFormGroup: FormGroup;
|
||||
projectTitleCtrl: AbstractControl;
|
||||
projectClientCtrl: AbstractControl;
|
||||
projectTesterCtrl: AbstractControl;
|
||||
|
||||
formCtrlStatus = FieldStatus.BASIC;
|
||||
|
||||
invalidProjectTitle: string;
|
||||
invalidProjectClient: string;
|
||||
invalidProjectTester: string;
|
||||
|
||||
readonly MIN_LENGTH: number = 2;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
protected dialogRef: NbDialogRef<ProjectDialogComponent>
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectFormGroup = this.fb.group({
|
||||
projectTitle: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
|
||||
projectClient: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
|
||||
projectTester: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]]
|
||||
});
|
||||
|
||||
this.projectTitleCtrl = this.projectFormGroup.get('projectTitle');
|
||||
this.projectClientCtrl = this.projectFormGroup.get('projectClient');
|
||||
this.projectTesterCtrl = this.projectFormGroup.get('projectTester');
|
||||
|
||||
this.projectFormGroup.valueChanges
|
||||
.pipe(untilDestroyed(this))
|
||||
.subscribe(() => {
|
||||
this.formCtrlStatus = FieldStatus.BASIC;
|
||||
});
|
||||
}
|
||||
|
||||
onClickSave(value): void {
|
||||
this.dialogRef.close({
|
||||
title: value.projectTitle,
|
||||
client: value.projectClient,
|
||||
tester: value.projectTester
|
||||
});
|
||||
}
|
||||
|
||||
onClickClose(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
formIsEmptyOrInvalid(): boolean {
|
||||
return this.isEmpty(this.projectTitleCtrl.value)
|
||||
|| this.isEmpty(this.projectClientCtrl.value)
|
||||
|| this.isEmpty(this.projectTesterCtrl.value)
|
||||
|| this.projectTitleCtrl.invalid
|
||||
|| this.projectClientCtrl.invalid
|
||||
|| this.projectTesterCtrl.invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ctrlValue of type string
|
||||
* @return if ctrlValue is empty or not
|
||||
*/
|
||||
isEmpty(ctrlValue: string): boolean {
|
||||
return ctrlValue === '';
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||
import {NbButtonModule, NbCardModule, NbDialogService, NbFormFieldModule, NbInputModule} from '@nebular/theme';
|
||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ProjectDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
NbCardModule,
|
||||
NbButtonModule,
|
||||
FlexLayoutModule,
|
||||
FontAwesomeModule,
|
||||
TranslateModule,
|
||||
ReactiveFormsModule,
|
||||
NbFormFieldModule,
|
||||
NbInputModule,
|
||||
],
|
||||
providers: [
|
||||
DialogService,
|
||||
NbDialogService
|
||||
],
|
||||
entryComponents: [
|
||||
ProjectDialogComponent
|
||||
]
|
||||
})
|
||||
export class ProjectDialogModule { }
|
|
@ -0,0 +1,16 @@
|
|||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||
import {ComponentType} from '@angular/cdk/overlay';
|
||||
import {TemplateRef} from '@angular/core';
|
||||
import {NbDialogConfig, NbDialogRef} from '@nebular/theme';
|
||||
|
||||
export class DialogServiceMock implements Required<DialogService> {
|
||||
|
||||
dialog: any;
|
||||
|
||||
openCustomDialog<T, D = any, R = any>(
|
||||
componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
|
||||
config?: Partial<NbDialogConfig<Partial<T> | string>>
|
||||
): NbDialogRef<T> {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {DialogService} from './dialog.service';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {NbDialogModule, NbDialogRef} from '@nebular/theme';
|
||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||
|
||||
describe('DialogService', () => {
|
||||
let service: DialogService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
BrowserAnimationsModule,
|
||||
NbDialogModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
{provide: DialogService, useClass: DialogServiceMock},
|
||||
{provide: NbDialogRef, useValue: {}},
|
||||
]
|
||||
});
|
||||
service = TestBed.inject(DialogService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
import {Injectable, TemplateRef} from '@angular/core';
|
||||
import {NbDialogConfig, NbDialogRef, NbDialogService} from '@nebular/theme';
|
||||
import {ComponentType} from '@angular/cdk/overlay';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DialogService {
|
||||
|
||||
constructor(private dialog: NbDialogService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a custom MatDialog
|
||||
*/
|
||||
openCustomDialog<T>(
|
||||
componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
|
||||
config?: Partial<NbDialogConfig<Partial<T> | string>>
|
||||
): NbDialogRef<T> {
|
||||
return this.dialog.open<T>(componentOrTemplateRef, {
|
||||
context: config?.context || undefined,
|
||||
closeOnEsc: config?.closeOnEsc || false,
|
||||
hasScroll: config?.hasScroll || false,
|
||||
autoFocus: config?.autoFocus || false,
|
||||
closeOnBackdropClick: config?.closeOnBackdropClick || false,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ export class NotificationService {
|
|||
.subscribe((translationContainer) => {
|
||||
this.toastrService.show(
|
||||
'',
|
||||
translationContainer[popupType] + ' ' + translationContainer[translationKey], {
|
||||
translationContainer[translationKey] + ' ' + translationContainer[popupType], {
|
||||
position: NbGlobalPhysicalPosition.BOTTOM_RIGHT,
|
||||
duration: 5000,
|
||||
toastClass: createCssClassName(popupType)
|
||||
|
|
|
@ -11,4 +11,8 @@ export class ProjectServiceMock implements Required<ProjectService> {
|
|||
getProjects(): Observable<Project[]> {
|
||||
return of([]);
|
||||
}
|
||||
|
||||
saveProject(): Observable<Project> {
|
||||
return of();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import { ProjectService } from './project.service';
|
||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||
import {ProjectService} from './project.service';
|
||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {KeycloakService} from 'keycloak-angular';
|
||||
import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
|
||||
import {environment} from '../../environments/environment';
|
||||
|
||||
describe('ProjectService', () => {
|
||||
let service: ProjectService;
|
||||
let httpMock: HttpTestingController;
|
||||
|
||||
const apiBaseURL = `${environment.apiEndpoint}/projects`;
|
||||
const dummyDate = new Date('2019-01-10T09:00:00');
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
|
@ -19,9 +25,89 @@ describe('ProjectService', () => {
|
|||
]
|
||||
});
|
||||
service = TestBed.inject(ProjectService);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('getProjects', () => {
|
||||
const mockProject: Project = {
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||
client: 'E Corp',
|
||||
title: 'Some Mock API (v1.0) Scanning',
|
||||
createdAt: dummyDate,
|
||||
tester: 'Novatester',
|
||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||
};
|
||||
|
||||
const httpResponse = [{
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||
client: 'E Corp',
|
||||
title: 'Some Mock API (v1.0) Scanning',
|
||||
createdAt: dummyDate,
|
||||
tester: 'Novatester',
|
||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||
}];
|
||||
|
||||
it('should get Projects', (done) => {
|
||||
service.getProjects().subscribe((projects) => {
|
||||
expect(projects[0].id).toEqual(mockProject.id);
|
||||
expect(projects[0].client).toEqual(mockProject.client);
|
||||
expect(projects[0].title).toEqual(mockProject.title);
|
||||
expect(projects[0].createdAt).toBe(mockProject.createdAt);
|
||||
expect(projects[0].tester).toEqual(mockProject.tester);
|
||||
expect(projects[0].createdBy).toEqual(mockProject.createdBy);
|
||||
done();
|
||||
});
|
||||
|
||||
const mockReq = httpMock.expectOne(`${apiBaseURL}`);
|
||||
expect(mockReq.cancelled).toBe(false);
|
||||
expect(mockReq.request.responseType).toEqual('json');
|
||||
mockReq.flush(httpResponse);
|
||||
|
||||
httpMock.verify();
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveProject', () => {
|
||||
const mockSaveProjectDialogBody: SaveProjectDialogBody = {
|
||||
client: 'E Corp',
|
||||
title: 'Some Mock API (v1.0) Scanning',
|
||||
tester: 'Novatester',
|
||||
};
|
||||
|
||||
const mockProject: Project = {
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||
client: 'E Corp',
|
||||
title: 'Some Mock API (v1.0) Scanning',
|
||||
createdAt: dummyDate,
|
||||
tester: 'Novatester',
|
||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||
};
|
||||
|
||||
const httpResponse = {
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||
client: 'E Corp',
|
||||
title: 'Some Mock API (v1.0) Scanning',
|
||||
createdAt: dummyDate,
|
||||
tester: 'Novatester',
|
||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||
};
|
||||
|
||||
it('should save project', (done) => {
|
||||
|
||||
service.saveProject(mockSaveProjectDialogBody).subscribe(
|
||||
value => {
|
||||
expect(value).toEqual(mockProject);
|
||||
done();
|
||||
},
|
||||
fail);
|
||||
|
||||
const req = httpMock.expectOne(`${apiBaseURL}`);
|
||||
expect(req.request.method).toBe('POST');
|
||||
req.flush(mockProject);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {environment} from '../../environments/environment';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Project} from '../models/project.model';
|
||||
import {Project, SaveProjectDialogBody} from '../models/project.model';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
|
@ -17,4 +17,12 @@ export class ProjectService {
|
|||
public getProjects(): Observable<Project[]> {
|
||||
return this.http.get<Project[]>(`${this.apiBaseURL}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Project
|
||||
* @param project the information of the project
|
||||
*/
|
||||
public saveProject(project: SaveProjectDialogBody): Observable<Project> {
|
||||
return this.http.post<Project>(`${this.apiBaseURL}`, project);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"info": {
|
||||
"_postman_id": "58021f5f-0ae9-4f64-990b-f09dcc2d3bc2",
|
||||
"_postman_id": "a20516f0-5f7f-4d15-9f26-ada358993ff8",
|
||||
"name": "security-c4po-api",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
|
@ -71,7 +71,7 @@
|
|||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j Pentest\",\n \"tester\": \"Stipe\",\n \"createyBy\": \"10e06d7a-8dd0-4ecd-8963-056b45079c4f\"\n}",
|
||||
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j Pentest\",\n \"tester\": \"Stipe\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
|
|
|
@ -41,8 +41,7 @@ fun ProjectOverview.toProjectOverviewResponseBody(): ResponseBody {
|
|||
data class ProjectRequestBody(
|
||||
val client: String,
|
||||
val title: String,
|
||||
val tester: String? = null,
|
||||
val createdBy: String
|
||||
val tester: String? = null
|
||||
)
|
||||
|
||||
fun ProjectRequestBody.toProject(): Project {
|
||||
|
@ -52,6 +51,7 @@ fun ProjectRequestBody.toProject(): Project {
|
|||
title = this.title,
|
||||
createdAt = Instant.now().toString(),
|
||||
tester = this.tester,
|
||||
createdBy = this.createdBy
|
||||
// ToDo: Should be changed to SUB from Token after adding AUTH Header
|
||||
createdBy = UUID.randomUUID().toString()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.securityc4po.api.ResponseBody
|
|||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import reactor.core.publisher.Mono
|
||||
import java.util.*
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/projects")
|
||||
|
|
|
@ -50,22 +50,33 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
|||
.expectStatus().isOk
|
||||
.expectHeader().doesNotExist("")
|
||||
.expectBody().json(Json.write(getProjectsResponse()))
|
||||
.consumeWith(WebTestClientRestDocumentation.document("{methodName}",
|
||||
Preprocessors.preprocessRequest(Preprocessors.prettyPrint(),
|
||||
.consumeWith(
|
||||
WebTestClientRestDocumentation.document(
|
||||
"{methodName}",
|
||||
Preprocessors.preprocessRequest(
|
||||
Preprocessors.prettyPrint(),
|
||||
Preprocessors.modifyUris().removePort(),
|
||||
Preprocessors.removeHeaders("Host", "Content-Length")),
|
||||
Preprocessors.removeHeaders("Host", "Content-Length")
|
||||
),
|
||||
Preprocessors.preprocessResponse(
|
||||
Preprocessors.prettyPrint()
|
||||
),
|
||||
PayloadDocumentation.relaxedResponseFields(
|
||||
PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING).description("The id of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("[].client").type(JsonFieldType.STRING).description("The name of the client of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING).description("The title of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("[].createdAt").type(JsonFieldType.STRING).description("The date where the project was created at"),
|
||||
PayloadDocumentation.fieldWithPath("[].tester").type(JsonFieldType.STRING).description("The user that is assigned as a tester in the project"),
|
||||
PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING).description("The id of the user that created the project")
|
||||
PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING)
|
||||
.description("The id of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("[].client").type(JsonFieldType.STRING)
|
||||
.description("The name of the client of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING)
|
||||
.description("The title of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("[].createdAt").type(JsonFieldType.STRING)
|
||||
.description("The date where the project was created at"),
|
||||
PayloadDocumentation.fieldWithPath("[].tester").type(JsonFieldType.STRING)
|
||||
.description("The user that is assigned as a tester in the project"),
|
||||
PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING)
|
||||
.description("The id of the user that created the project")
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
val projectOne = Project(
|
||||
|
@ -102,29 +113,39 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
|||
.expectStatus().isAccepted
|
||||
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||
.expectBody().json(Json.write(project))
|
||||
.consumeWith(WebTestClientRestDocumentation.document("{methodName}",
|
||||
Preprocessors.preprocessRequest(Preprocessors.prettyPrint(),
|
||||
.consumeWith(
|
||||
WebTestClientRestDocumentation.document(
|
||||
"{methodName}",
|
||||
Preprocessors.preprocessRequest(
|
||||
Preprocessors.prettyPrint(),
|
||||
Preprocessors.modifyUris().removePort(),
|
||||
Preprocessors.removeHeaders("Host", "Content-Length")),
|
||||
Preprocessors.removeHeaders("Host", "Content-Length")
|
||||
),
|
||||
Preprocessors.preprocessResponse(
|
||||
Preprocessors.prettyPrint()
|
||||
),
|
||||
PayloadDocumentation.relaxedResponseFields(
|
||||
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING).description("The id of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("client").type(JsonFieldType.STRING).description("The name of the client of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING).description("The title of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("createdAt").type(JsonFieldType.STRING).description("The date where the project was created at"),
|
||||
PayloadDocumentation.fieldWithPath("tester").type(JsonFieldType.STRING).description("The user that is assigned as a tester in the project"),
|
||||
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING).description("The id of the user that created the project")
|
||||
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
|
||||
.description("The id of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("client").type(JsonFieldType.STRING)
|
||||
.description("The name of the client of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
||||
.description("The title of the requested project"),
|
||||
PayloadDocumentation.fieldWithPath("createdAt").type(JsonFieldType.STRING)
|
||||
.description("The date where the project was created at"),
|
||||
PayloadDocumentation.fieldWithPath("tester").type(JsonFieldType.STRING)
|
||||
.description("The user that is assigned as a tester in the project"),
|
||||
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
|
||||
.description("The id of the user that created the project")
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
val project = ProjectRequestBody(
|
||||
client = "Novatec",
|
||||
title = "log4j Pentest",
|
||||
tester = "Stipe",
|
||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||
tester = "Stipe"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -98,11 +98,12 @@ class ProjectControllerIntTest : BaseIntTest() {
|
|||
.exchange()
|
||||
.expectStatus().isAccepted
|
||||
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||
.expectBody().json(Json.write(project))
|
||||
.expectBody()
|
||||
.jsonPath("$.client").isEqualTo("Novatec")
|
||||
.jsonPath("$.title").isEqualTo("log4j Pentest")
|
||||
.jsonPath("$.tester").isEqualTo("Stipe")
|
||||
.jsonPath("$.createdBy").isEqualTo("f8aab31f-4925-4242-a6fa-f98135b4b032")
|
||||
// ToDo: Should be changed to SUB from Token after adding AUTH Header
|
||||
/*.jsonPath("$.createdBy").isEqualTo("f8aab31f-4925-4242-a6fa-f98135b4b032")*/
|
||||
}
|
||||
|
||||
val project = Project(
|
||||
|
@ -111,7 +112,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
|||
title = "log4j Pentest",
|
||||
createdAt = "2021-04-10T18:05:00Z",
|
||||
tester = "Stipe",
|
||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue