feat: As a user I want to add a summary to the project when editing on the main page & when inside a project

This commit is contained in:
Marcel Haag 2023-02-13 11:32:34 +01:00 committed by Cel
parent 3c3f005537
commit 88b3647295
25 changed files with 238 additions and 26 deletions

View File

@ -9,18 +9,28 @@
</button> </button>
</div> </div>
<h4>{{selectedProjectTitle$.getValue()}}</h4> <h4>{{selectedProject$.getValue().title}}</h4>
<div class="export-button-container"> <div class="button-container">
<nb-actions size="medium"> <nb-actions size="medium">
<nb-action>
<button nbButton
status="button-outline-basic-text-color"
shape="round"
(click)="onClickEditPentestProject()">
<fa-icon [icon]="fa.faEdit"
class="element-icon fa-lg"></fa-icon>
<span class="element-text">{{ 'global.action.edit' | translate }}</span>
</button>
</nb-action>
<nb-action> <nb-action>
<button nbButton hero <button nbButton hero
status="info" status="info"
shape="round" shape="round"
(click)="onClickExportPentest()"> (click)="onClickExportPentest()">
<fa-icon [icon]="fa.faFileExport" <fa-icon [icon]="fa.faFileExport"
class="export-element-icon fa-lg"></fa-icon> class="element-icon fa-lg"></fa-icon>
<span class="export-element-text">{{ 'global.action.export' | translate }}</span> <span class="element-text">{{ 'global.action.export' | translate }}</span>
</button> </button>
</nb-action> </nb-action>
</nb-actions> </nb-actions>

View File

@ -6,16 +6,16 @@
} }
} }
.export-button-container { .button-container {
display: flex; display: flex;
align-content: flex-end; align-content: flex-end;
// ToDo: Fix so that longer / shorter name won't change needed margin // ToDo: Fix so that longer / shorter name won't change needed margin
margin-right: 2.25rem; // margin-right: 2.25rem;
.export-element-icon { .element-icon {
} }
.export-element-text { .element-text {
padding-left: 0.5rem; padding-left: 0.5rem;
font-size: 0.85rem; font-size: 0.85rem;
} }

View File

@ -8,14 +8,52 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../common-app.module'; import {HttpLoaderFactory} from '../../common-app.module';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {RouterTestingModule} from '@angular/router/testing'; import {RouterTestingModule} from '@angular/router/testing';
import {NgxsModule} from '@ngxs/store'; import {NgxsModule, Store} from '@ngxs/store';
import {ProjectState} from '@shared/stores/project-state/project-state'; import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {NbActionsModule, NbIconModule} from '@nebular/theme'; import {NbActionsModule, NbIconModule} from '@nebular/theme';
import {ProjectService} from '@shared/services/project.service';
import {ProjectServiceMock} from '@shared/services/project.service.mock';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {NotificationService} from '@shared/services/notification.service';
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
import {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},
// Manages Categories
disabledCategories: [],
selectedCategory: Category.INFORMATION_GATHERING,
// Manages Pentests of Category
disabledPentests: [],
selectedPentest: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33112',
category: Category.INFORMATION_GATHERING,
refNumber: 'OTF-001',
childEntries: [],
status: PentestStatus.NOT_STARTED,
findingIds: [],
commentIds: ['56c47c56-3bcd-45f1-a05b-c197dbd33112']
},
};
describe('ObjectiveHeaderComponent', () => { describe('ObjectiveHeaderComponent', () => {
let component: ObjectiveHeaderComponent; let component: ObjectiveHeaderComponent;
let fixture: ComponentFixture<ObjectiveHeaderComponent>; let fixture: ComponentFixture<ObjectiveHeaderComponent>;
let store: Store;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
@ -36,6 +74,12 @@ describe('ObjectiveHeaderComponent', () => {
}), }),
RouterTestingModule.withRoutes([]), RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([ProjectState]) NgxsModule.forRoot([ProjectState])
],
providers: [
{provide: ProjectService, useValue: new ProjectServiceMock()},
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
{provide: DialogService, useClass: DialogServiceMock},
{provide: NotificationService, useValue: new NotificationServiceMock()}
] ]
}) })
.compileComponents(); .compileComponents();
@ -43,6 +87,11 @@ describe('ObjectiveHeaderComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ObjectiveHeaderComponent); fixture = TestBed.createComponent(ObjectiveHeaderComponent);
store = TestBed.inject(Store);
store.reset({
...store.snapshot(),
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
});
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -6,7 +6,14 @@ import {Router} from '@angular/router';
import {PROJECT_STATE_NAME, ProjectState} from '@shared/stores/project-state/project-state'; import {PROJECT_STATE_NAME, ProjectState} from '@shared/stores/project-state/project-state';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject} from 'rxjs';
import {Project} from '@shared/models/project.model'; import {Project, ProjectDialogBody} from '@shared/models/project.model';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {filter, mergeMap} from 'rxjs/operators';
import {NotificationService, PopupType} from '@shared/services/notification.service';
import {ProjectService} from '@shared/services/project.service';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
import {InitProjectState} from '@shared/stores/project-state/project-state.actions';
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
@ -17,9 +24,13 @@ import {Project} from '@shared/models/project.model';
export class ObjectiveHeaderComponent implements OnInit { export class ObjectiveHeaderComponent implements OnInit {
readonly fa = FA; readonly fa = FA;
selectedProjectTitle$: BehaviorSubject<string> = new BehaviorSubject<string>(''); selectedProject$: BehaviorSubject<Project> = new BehaviorSubject<Project>(null);
constructor(private store: Store, constructor(private store: Store,
private readonly notificationService: NotificationService,
private projectService: ProjectService,
private dialogService: DialogService,
private projectDialogService: ProjectDialogService,
private readonly router: Router) { private readonly router: Router) {
} }
@ -28,7 +39,7 @@ export class ObjectiveHeaderComponent implements OnInit {
untilDestroyed(this) untilDestroyed(this)
).subscribe({ ).subscribe({
next: (selectedProject: Project) => { next: (selectedProject: Project) => {
this.selectedProjectTitle$.next(selectedProject?.title); this.selectedProject$.next(selectedProject);
}, },
error: err => { error: err => {
console.error(err); console.error(err);
@ -46,6 +57,36 @@ export class ObjectiveHeaderComponent implements OnInit {
).finally(); ).finally();
} }
onClickEditPentestProject(): void {
this.projectDialogService.openProjectDialog(
ProjectDialogComponent,
this.selectedProject$.getValue(),
{
closeOnEsc: false,
hasScroll: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(
filter(value => !!value),
mergeMap((value: ProjectDialogBody) => this.projectService.updateProject(this.selectedProject$.getValue().id, value)),
untilDestroyed(this)
).subscribe({
next: (project: Project) => {
this.store.dispatch(new InitProjectState(
project,
[],
[]
)).pipe(untilDestroyed(this)).subscribe();
this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
},
error: error => {
console.error(error);
this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
}
});
}
onClickExportPentest(): void { onClickExportPentest(): void {
// tslint:disable-next-line:no-console // tslint:disable-next-line:no-console
console.info('To be implemented..'); console.info('To be implemented..');

View File

@ -30,6 +30,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -21,6 +21,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -30,6 +30,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -21,6 +21,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -21,6 +21,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -67,7 +67,7 @@ describe('ProjectOverviewComponent', () => {
{provide: ProjectService, useValue: new ProjectServiceMock()}, {provide: ProjectService, useValue: new ProjectServiceMock()},
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock}, {provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
{provide: DialogService, useClass: DialogServiceMock}, {provide: DialogService, useClass: DialogServiceMock},
{provide: NotificationService, useValue: new NotificationServiceMock()} {provide: NotificationService, useClass: NotificationServiceMock}
] ]
}) })
.compileComponents(); .compileComponents();

View File

@ -7,16 +7,55 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../common-app.module'; import {HttpLoaderFactory} from '../../common-app.module';
import {HttpClient, HttpClientModule} from '@angular/common/http'; import {HttpClient, HttpClientModule} from '@angular/common/http';
import {RouterTestingModule} from '@angular/router/testing'; import {RouterTestingModule} from '@angular/router/testing';
import {NgxsModule} from '@ngxs/store'; import {NgxsModule, Store} from '@ngxs/store';
import {SessionState} from '@shared/stores/session-state/session-state'; import {SessionState} from '@shared/stores/session-state/session-state';
import {HttpClientTestingModule} from '@angular/common/http/testing'; import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NbCardModule, NbLayoutModule} from '@nebular/theme'; import {NbCardModule, NbLayoutModule} from '@nebular/theme';
import {KeycloakService} from 'keycloak-angular'; import {KeycloakService} from 'keycloak-angular';
import {ObjectiveOverviewModule} from '../../objective-overview'; import {ObjectiveOverviewModule} from '../../objective-overview';
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 {ProjectService} from '@shared/services/project.service';
import {ProjectServiceMock} from '@shared/services/project.service.mock';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
import {Category} from '@shared/models/category.model';
import {PentestStatus} from '@shared/models/pentest-status.model';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester',
summary: '',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},
// Manages Categories
disabledCategories: [],
selectedCategory: Category.INFORMATION_GATHERING,
// Manages Pentests of Category
disabledPentests: [],
selectedPentest: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33112',
category: Category.INFORMATION_GATHERING,
refNumber: 'OTF-001',
childEntries: [],
status: PentestStatus.NOT_STARTED,
findingIds: [],
commentIds: ['56c47c56-3bcd-45f1-a05b-c197dbd33112']
},
};
describe('ProjectComponent', () => { describe('ProjectComponent', () => {
let component: ProjectComponent; let component: ProjectComponent;
let fixture: ComponentFixture<ProjectComponent>; let fixture: ComponentFixture<ProjectComponent>;
let store: Store;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
@ -37,12 +76,16 @@ describe('ProjectComponent', () => {
} }
}), }),
RouterTestingModule.withRoutes([]), RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([SessionState]), NgxsModule.forRoot([ProjectState]),
HttpClientModule, HttpClientModule,
HttpClientTestingModule HttpClientTestingModule
], ],
providers: [ providers: [
KeycloakService KeycloakService,
{provide: ProjectService, useValue: new ProjectServiceMock()},
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
{provide: DialogService, useClass: DialogServiceMock},
{provide: NotificationService, useClass: NotificationServiceMock}
] ]
}) })
.compileComponents(); .compileComponents();
@ -50,6 +93,11 @@ describe('ProjectComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ProjectComponent); fixture = TestBed.createComponent(ProjectComponent);
store = TestBed.inject(Store);
store.reset({
...store.snapshot(),
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
});
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -9,6 +9,7 @@
"action.return": "Zurück", "action.return": "Zurück",
"action.exit": "Beenden", "action.exit": "Beenden",
"action.update": "Speichern", "action.update": "Speichern",
"action.edit": "Editieren",
"action.export": "Exportieren", "action.export": "Exportieren",
"action.reset": "Zurücksetzen", "action.reset": "Zurücksetzen",
"action.yes": "Ja", "action.yes": "Ja",
@ -46,9 +47,12 @@
"title.label": "Projekt Titel", "title.label": "Projekt Titel",
"client.label": "Name des Auftraggebers", "client.label": "Name des Auftraggebers",
"tester.label": "Name des Pentester", "tester.label": "Name des Pentester",
"summary.label": "Zusammenfassung",
"summary.placeholder": "Sollte eine Zusammenfassung, einen Ansatz, einen Umfang und eine Bewertungsübersicht sowie allgemeine Empfehlungen enthalten",
"title": "Titel", "title": "Titel",
"client": "Klient", "client": "Klient",
"tester": "Tester", "tester": "Tester",
"summary": "Zusammenfassung",
"createdAt": "Erstellt am", "createdAt": "Erstellt am",
"overview": { "overview": {
"add.project": "Projekt hinzufügen", "add.project": "Projekt hinzufügen",
@ -69,7 +73,8 @@
"validationMessage": { "validationMessage": {
"titleRequired": "Titel ist erforderlich.", "titleRequired": "Titel ist erforderlich.",
"clientRequired": "Klient ist erforderlich.", "clientRequired": "Klient ist erforderlich.",
"testerRequired": "Tester ist erforderlich." "testerRequired": "Tester ist erforderlich.",
"summaryRequired": "Zusammenfassung ist erforderlich."
}, },
"popup": { "popup": {
"not.found": "Keine Projekte gefunden", "not.found": "Keine Projekte gefunden",

View File

@ -9,6 +9,7 @@
"action.return": "Return", "action.return": "Return",
"action.exit": "Exit", "action.exit": "Exit",
"action.update": "Update", "action.update": "Update",
"action.edit": "Edit",
"action.export": "Export", "action.export": "Export",
"action.reset": "Reset", "action.reset": "Reset",
"action.yes": "Yes", "action.yes": "Yes",
@ -46,9 +47,12 @@
"title.label": "Project Title", "title.label": "Project Title",
"client.label": "Name of Client", "client.label": "Name of Client",
"tester.label": "Name of Pentester", "tester.label": "Name of Pentester",
"summary.label": "Summary",
"summary.placeholder": "Should include Executive Summary, Approach, Scope and Assessment Overview as well as General Recommendations",
"title": "Title", "title": "Title",
"client": "Client", "client": "Client",
"tester": "Tester", "tester": "Tester",
"summary": "Summary",
"createdAt": "Created at", "createdAt": "Created at",
"overview": { "overview": {
"add.project": "Add Project", "add.project": "Add Project",
@ -69,7 +73,8 @@
"validationMessage": { "validationMessage": {
"titleRequired": "Title is required.", "titleRequired": "Title is required.",
"clientRequired": "Client is required.", "clientRequired": "Client is required.",
"testerRequired": "Tester is required." "testerRequired": "Tester is required.",
"summaryRequired": "Summary is required."
}, },
"popup": { "popup": {
"not.found": "No projects found", "not.found": "No projects found",

View File

@ -40,6 +40,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -38,6 +38,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: new Date('2019-01-10T09:00:00'), createdAt: new Date('2019-01-10T09:00:00'),
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}, },

View File

@ -28,6 +28,27 @@
</span> </span>
</ng-template> </ng-template>
</nb-form-field> </nb-form-field>
<!-- Textarea styles -->
<nb-form-field *ngSwitchCase="'formText'" class="project-form-field">
<label for="{{fieldConfig.fieldName}}" class="label">
{{fieldConfig.labelKey | translate}}
</label>
<textarea formControlName="{{fieldConfig.fieldName}}"
type="formText" required fullWidth
id="{{fieldConfig.fieldName}}" nbInput
class="form-field form-textarea"
[status]="projectFormGroup.get(fieldConfig.fieldName).dirty ? (projectFormGroup.get(fieldConfig.fieldName).invalid ? 'danger' : 'basic') : 'basic'"
placeholder="{{fieldConfig.placeholder | translate}}">
</textarea>
<!-- FIXME: when the bug (https://github.com/angular/components/issues/7739) is fixed -->
<ng-template ngFor let-error [ngForOf]="fieldConfig.errors"
*ngIf="projectFormGroup.get(fieldConfig.fieldName).dirty">
<span class="error-text"
*ngIf="projectFormGroup.get(fieldConfig.fieldName)?.hasError(error.errorCode) && error.errorCode === 'required'">
{{error.translationKey | translate}}
</span>
</ng-template>
</nb-form-field>
</ng-container> </ng-container>
</ng-template> </ng-template>
</form> </form>

View File

@ -2,8 +2,8 @@
@import '../../../assets/@theme/styles/themes'; @import '../../../assets/@theme/styles/themes';
.project-dialog { .project-dialog {
width: 25.25rem; width: 40rem !important;
height: 35rem; height: 42.25rem;
.project-dialog-header { .project-dialog-header {
height: 8vh; height: 8vh;
@ -21,10 +21,17 @@
} }
.form-field { .form-field {
width: 18rem; width: 26.75rem;
// width: 30rem !important;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.form-textarea {
width: 26.75rem !important;
// width: 30rem !important;
height: 8rem;
}
.error-text { .error-text {
float: left; float: left;
color: nb-theme(color-danger-default); color: nb-theme(color-danger-default);

View File

@ -91,6 +91,7 @@ export const mockProject: Project = {
title: 'Test Project', title: 'Test Project',
client: 'Testclient', client: 'Testclient',
tester: 'Testpentester', tester: 'Testpentester',
summary: '',
createdAt: new Date(), createdAt: new Date(),
testingProgress: 0, testingProgress: 0,
createdBy: 'UID-11-12-13' createdBy: 'UID-11-12-13'

View File

@ -43,7 +43,8 @@ export class ProjectDialogComponent implements OnInit {
this.dialogRef.close({ this.dialogRef.close({
title: value.projectTitle, title: value.projectTitle,
client: value.projectClient, client: value.projectClient,
tester: value.projectTester tester: value.projectTester,
summary: value.projectSummary
}); });
} }

View File

@ -74,6 +74,19 @@ export class ProjectDialogService {
errors: [ errors: [
{errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'} {errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'}
] ]
},
projectSummary: {
fieldName: 'projectSummary',
type: 'formText',
labelKey: 'project.summary.label',
placeholder: 'project.summary.placeholder',
controlsConfig: [
{value: project ? project.summary : '', disabled: !project},
[project ? Validators.required : []]
],
errors: [
{errorCode: 'required', translationKey: 'project.validationMessage.summaryRequired'}
]
} }
}, },
options: [] options: []

View File

@ -41,6 +41,7 @@ describe('ProjectService', () => {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: dummyDate, createdAt: dummyDate,
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}; };
@ -90,6 +91,7 @@ describe('ProjectService', () => {
title: 'Some Mock API (v1.0) Scanning', title: 'Some Mock API (v1.0) Scanning',
createdAt: dummyDate, createdAt: dummyDate,
tester: 'Novatester', tester: 'Novatester',
summary: '',
testingProgress: 0, testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110' createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}; };

View File

@ -41,11 +41,13 @@ class ReportController(private val apiService: APIService, private val reportSer
jacksonObjectMapper().readValue<ProjectReport>(jsonProjectReportString) jacksonObjectMapper().readValue<ProjectReport>(jsonProjectReportString)
return this.reportService.createReport(jsonProjectReportCollection, "pdf").map { reportClassLoaderFilePatch -> return this.reportService.createReport(jsonProjectReportCollection, "pdf").map { reportClassLoaderFilePatch ->
val reportRessourceStream = ReportController::class.java.getResourceAsStream(reportClassLoaderFilePatch) val reportRessourceStream = ReportController::class.java.getResourceAsStream(reportClassLoaderFilePatch)
// Todo: Fix Error with IOUtils.toByteArray(reportRessourceStream) on first start of application
val response = IOUtils.toByteArray(reportRessourceStream) val response = IOUtils.toByteArray(reportRessourceStream)
this.reportService.cleanUpFiles()
ResponseEntity.ok().body(response) ResponseEntity.ok().body(response)
}.switchIfEmpty { }.switchIfEmpty {
Mono.just(notFound().build<ByteArray>()) Mono.just(notFound().build<ByteArray>())
}.doOnSuccess {
this.reportService.cleanUpFiles()
} }
} }

View File

@ -163,7 +163,7 @@
<textElement> <textElement>
<font size="12"/> <font size="12"/>
</textElement> </textElement>
<textFieldExpression><![CDATA[$F{summary}]]></textFieldExpression> <textFieldExpression><![CDATA[(($F{summary}.length() == 0) ? "" + $F{client} +" contracted " + $F{tester} + " to perform a Penetration Test to identify security weaknesses, determine the impact to " + $F{client} +", document all findings in a clear and repeatable manner, and provide remediation recommendations." : $F{summary})]]></textFieldExpression>
</textField> </textField>
<staticText> <staticText>
<reportElement x="0" y="10" width="380" height="20" forecolor="#232B44" uuid="b508eb27-8cf7-40f3-86e8-6b7c9328d919"/> <reportElement x="0" y="10" width="380" height="20" forecolor="#232B44" uuid="b508eb27-8cf7-40f3-86e8-6b7c9328d919"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 220 KiB

File diff suppressed because one or more lines are too long