import {Component, OnInit} from '@angular/core'; import {BehaviorSubject, Observable} from 'rxjs'; import {Pentest} from '@shared/models/pentest.model'; import * as FA from '@fortawesome/free-solid-svg-icons'; import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme'; import {NotificationService, PopupType} from '@shared/services/notification.service'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {filter, mergeMap, tap} from 'rxjs/operators'; import { Comment, CommentDialogBody, CommentEntry, transformCommentsToObjectiveEntries, transformCommentToRequestBody } from '@shared/models/comment.model'; import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined'; import {ProjectState} from '@shared/stores/project-state/project-state'; import {Store} from '@ngxs/store'; import {PentestStatus} from '@shared/models/pentest-status.model'; import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component'; import {DialogService} from '@shared/services/dialog-service/dialog.service'; import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service'; import {CommentService} from '@shared/services/comment.service'; import {UpdatePentestComments, UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions'; @UntilDestroy() @Component({ selector: 'app-pentest-comments', templateUrl: './pentest-comments.component.html', styleUrls: ['./pentest-comments.component.scss'] }) export class PentestCommentsComponent implements OnInit { // HTML only readonly fa = FA; notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED; pentestInfo$: BehaviorSubject = new BehaviorSubject(null); // comments$: BehaviorSubject = new BehaviorSubject(null); loading$: BehaviorSubject = new BehaviorSubject(true); columns: Array = [ CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS ]; dataSource: NbTreeGridDataSource; data: CommentEntry[] = []; getters: NbGetters = { dataGetter: (node: CommentEntry) => node, childrenGetter: (node: CommentEntry) => node.childEntries || undefined, expandedGetter: (node: CommentEntry) => !!node.expanded, }; constructor(private readonly commentService: CommentService, private dataSourceBuilder: NbTreeGridDataSourceBuilder, private notificationService: NotificationService, private dialogService: DialogService, private commentDialogService: CommentDialogService, private store: Store) { this.dataSource = dataSourceBuilder.create(this.data, this.getters); } ngOnInit(): void { this.store.select(ProjectState.pentest).pipe( untilDestroyed(this) ).subscribe({ next: (selectedPentest: Pentest) => { this.pentestInfo$.next(selectedPentest); this.loadCommentsData(); }, error: err => { console.error(err); } }); } loadCommentsData(): void { this.commentService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '') .pipe( untilDestroyed(this), /*filter(isNotNullOrUndefined),*/ tap(() => this.loading$.next(true)) ) .subscribe({ next: (comments: Comment[]) => { if (comments) { this.data = transformCommentsToObjectiveEntries(comments); } else { this.data = []; } this.dataSource.setData(this.data, this.getters); this.loading$.next(false); }, error: err => { console.error(err); // ToDo: Implement again after proper lazy loading and routing // this.notificationService.showPopup('comment.popup.not.found', PopupType.FAILURE); this.loading$.next(false); } }); } onClickAddComment(): void { this.commentDialogService.openCommentDialog( FindingDialogComponent, this.pentestInfo$.getValue().findingIds, null, { closeOnEsc: false, hasScroll: false, autoFocus: false, closeOnBackdropClick: false } ).pipe( filter(value => !!value), tap((value) => console.warn('CommentDialogBody: ', value)), mergeMap((value: CommentDialogBody) => this.commentService.saveComment( this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '', transformCommentToRequestBody(value) ) ), untilDestroyed(this) ).subscribe({ next: (newComment: Comment) => { this.store.dispatch(new UpdatePentestComments(newComment.id)); this.loadCommentsData(); // Todo: Fix trans keys this.notificationService.showPopup('comment.popup.save.success', PopupType.SUCCESS); }, error: err => { console.error(err); // Todo: Fix trans keys this.notificationService.showPopup('comment.popup.save.failed', PopupType.FAILURE); } }); } onClickEditComment(commentEntry): void { console.info('Coming soon..', commentEntry); } onClickDeleteComment(commentEntry): void { console.info('Coming soon..', commentEntry); } // HTML only isLoading(): Observable { return this.loading$.asObservable(); } } enum CommentColumns { COMMENT_ID = 'commentId', TITLE = 'title', DESCRIPTION = 'description', RELATED_FINDINGS = 'relatedFindings', ACTIONS = 'actions' }