Skip to content

Commit

Permalink
delete step with comments by path
Browse files Browse the repository at this point in the history
  • Loading branch information
JieunSon96 committed Dec 11, 2023
1 parent a7cb880 commit fd25150
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 9 deletions.
32 changes: 32 additions & 0 deletions src/backend/src/modules/comments/comments.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,36 @@ export class CommentsController {
resolve(@Param('id') id: string) {
return this.commentsService.resolve(+id);
}

@Delete('/deleteAll/:path') // HTTP DELETE endpoint to handle comment deletion based on the provided 'path'
@ApiOperation({
description: 'Delete comments based on step',
})
@ApiOkResponse({
description: 'Successfully deleted comments',
})
@ApiBadRequestResponse({
description: 'Failed to delete comments: Invalid request',
})
@ApiForbiddenResponse({
description:
'Failed to delete comments: User lacks permission to delete comment of this PIA',
})
@ApiNotFoundResponse({
description: 'Failed to delete comments: Comments not found',
})
@ApiGoneResponse({
description: 'Failed to delete comments: The PIA is not active',
})
removeCommentsByPath(
@Param('path') path: string,
@Req() req: IRequest,
): Promise<Array<CommentRO>> {
// Delegate the comment removal logic to the commentsService, passing required parameters
return this.commentsService.removeCommentsByPath(
path,
req.user,
req.userRoles,
);
}
}
81 changes: 81 additions & 0 deletions src/backend/src/modules/comments/comments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,85 @@ export class CommentsService {
async resolve(id: number) {
return `This is a resolve method yet to be developed for comment ${id}`;
}

/**
* Asynchronously finds all comments based on the specified path.
*
* @param where - The options to filter the comments.
* @returns A promise that resolves to an array of CommentEntity objects.
*/
async findAllByPath(
where: FindOptionsWhere<CommentEntity>,
): Promise<CommentEntity[]> {
// Retrieve comments from the repository based on the provided filter options
const comments: CommentEntity[] =
await this.commentRepository.findBy(where);

// If no comments are found, throw a NotFoundException
if (!comments) {
throw new NotFoundException();
}

// Return the found comments
return comments;
}

/**
* Asynchronously removes comments associated with a specified path.
*
* @param path - The path identifier for which comments are to be removed.
* @param user - The Keycloak user initiating the removal.
* @param userRoles - An array of roles assigned to the user.
* @returns A promise resolving to an array of CommentRO (formatted comment objects).
*/
async removeCommentsByPath(
path: string,
user: KeycloakUser,
userRoles: Array<RolesEnum>,
): Promise<Array<CommentRO>> {
// Fetch comments for the specified path
const comments: CommentEntity[] = await this.commentRepository.find({
where: {
path,
},
order: { createdAt: 1 },
});

const updatedComments: CommentEntity[] = [];
// Iterate through each comment and perform removal logic
comments.forEach(async (comment) => {
// Validate access to the associated PIA. Throw an error if not allowed.
const pia = await this.piaService.validatePiaAccess(
comment.piaId,
user,
userRoles,
);

// Check if deleting comments for this PIA is allowed
const isActionAllowed = checkUpdatePermissions({
status: pia?.status,
entityType: 'comment',
entityAction: 'remove',
});

if (!isActionAllowed) {
throw new ForbiddenException({
piaId: pia.id,
message: 'Forbidden: Failed to remove comments of the PIA',
});
}

// Soft delete the comment
updatedComments.push(
await this.commentRepository.save({
...comment,
isActive: false,
text: null,
}),
);
});

// Return formatted comment objects
return getFormattedComments(comments);
}
}
18 changes: 18 additions & 0 deletions src/frontend/src/components/common/Table/views/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default {
Modal: {
Delete: {
ConfirmLabel: {
en: `Yes, delete`,
},
CancelLabel: {
en: `Cancel`,
},
TitleText: {
en: `Delete step? Comments on this step will also be deleted.`,
},
ParagraphText: {
en: `All information and comments for this step will be deleted.`,
},
},
},
};
70 changes: 70 additions & 0 deletions src/frontend/src/components/common/Table/views/useTableRowView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,63 @@ import { TableViewProps } from './table-view-props.interface';
import { TextInputEnum } from '../../../../constant/constant';
import ViewComments from '../../../../components/common/ViewComment';
import { PiaSections } from '../../../../types/enums/pia-sections.enum';
import Modal from '../../../../components/common/Modal';
import { useState } from 'react';
import Messages from './messages';
import { API_ROUTES } from '../../../../constant/apiRoutes';
import { HttpRequest } from '../../../../utils/http-request.util';

type UseTableRowViewProps = TableViewProps;

export const UseTableRowView = (props: UseTableRowViewProps) => {
// State variables to manage the delete modal and its content
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
const [modalConfirmLabel, setModalConfirmLabel] = useState<string>('');
const [modalCancelLabel, setModalCancelLabel] = useState<string>('');
const [modalTitleText, setModalTitleText] = useState<string>('');
const [modalParagraph, setModalParagraph] = useState<string>('');
const [modalButtonValue, setModalButtonValue] = useState<string>('');
const [deleteCommentPath, setDeleteCommentPath] = useState<string>('');

// Function to delete comments based on the provided path
const deleteCommentsByPath = async (path: PiaSections | string) => {
// Make a DELETE request to the backend API to delete comments based on the path

await HttpRequest.delete(
API_ROUTES.DELETE_COMMENTS_BY_PATH.replace(':path', `${path}`),
{},
{},
true,
);
};

// Function to handle closing the modal
const handleModalClose = async (event: any) => {
event.preventDefault();
setShowDeleteModal(false);
// Initiate the deletion of comments associated with the specified path
await deleteCommentsByPath(deleteCommentPath);
};

// Function to handle canceling the modal
const handleModalCancel = async (event: any) => {
event?.preventDefault();
setShowDeleteModal(false);
};

// Function to initiate the deletion of a step along with its comments
const handleDeleteStepwithComments = async (path: PiaSections | string) => {
setModalConfirmLabel(Messages.Modal.Delete.ConfirmLabel.en);
setModalCancelLabel(Messages.Modal.Delete.CancelLabel.en);
setModalTitleText(Messages.Modal.Delete.TitleText.en);
setModalParagraph(Messages.Modal.Delete.ParagraphText.en);
setShowDeleteModal(true);
setModalButtonValue('deleteStepwithComments');
setDeleteCommentPath(path);
// Show the delete modal
setShowDeleteModal(true);
};

return (
<>
{props.data.map((rowData, index) => (
Expand Down Expand Up @@ -76,6 +129,11 @@ export const UseTableRowView = (props: UseTableRowViewProps) => {
className="bcgovbtn bcgovbtn__tertiary bold min-gap delete__btn"
onClick={(e) => {
e.preventDefault();
handleDeleteStepwithComments(
PiaSections.COLLECTION_USE_AND_DISCLOSURE_STEPS +
'-' +
rowData.uid,
);
props.removeRow(index);
}}
aria-label="Delete row button"
Expand Down Expand Up @@ -104,6 +162,18 @@ export const UseTableRowView = (props: UseTableRowViewProps) => {
</button>
</div>
)}
{/* Rendering a Delete Modal component*/}
<Modal
confirmLabel={modalConfirmLabel}
cancelLabel={modalCancelLabel}
titleText={modalTitleText}
show={showDeleteModal}
value={modalButtonValue}
handleClose={(e) => handleModalClose(e)}
handleCancel={handleModalCancel}
>
<p className="modal-text">{modalParagraph}</p>
</Modal>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export default interface CommentSidebarProps {
pia: IPiaForm;
comments?: Comment[];
piaId?: number;
path: PiaSections | undefined;
path: PiaSections | string | undefined;
handleStatusChange: () => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,8 @@ const PIACollectionUseAndDisclosure = ({
const columns: Array<ColumnMetaData> = [
{
key: 'uid',
label: 'uid',
type: TextInputEnum.INPUT_TEXT,
//type: TextInputEnum.INPUT_HIDDEN
label: '',
type: TextInputEnum.INPUT_HIDDEN,
},
{
key: 'drafterInput',
Expand Down Expand Up @@ -139,7 +138,7 @@ const PIACollectionUseAndDisclosure = ({
);
}
}, [piaStateChangeHandler, collectionUseAndDisclosureForm, initialFormState]);
console.log('collection page: ' + showComments);

return (
<>
<h2 className="results-header">
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/constant/apiRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export const API_ROUTES = {
PIA_COMMENTS: '/api/comments',
GET_COMMENTS_COUNT: '/api/comments/count',
DELETE_COMMENT: '/api/comments/:id',
DELETE_COMMENTS_BY_PATH: '/api/comments/deleteAll/:path',
GET_INVITE_CODE: '/api/invite',
};
2 changes: 1 addition & 1 deletion src/frontend/src/contexts/PiaFormContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { PiaSections } from '../types/enums/pia-sections.enum';
export interface IPiaFormContext {
pia: IPiaForm;
commentCount?: CommentCount;
selectedSection?: PiaSections;
selectedSection?: PiaSections | string | undefined;
piaStateChangeHandler: PiaStateChangeHandlerType;
piaCollapsibleChangeHandler?: (isOpen: boolean) => void;
isReadOnly: boolean;
Expand Down
5 changes: 2 additions & 3 deletions src/frontend/src/pages/PIAForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import Collapsible from '../../components/common/Collapsible';
import { PiaFormContext } from '../../contexts/PiaFormContext';
import { faBars, faCommentDots } from '@fortawesome/free-solid-svg-icons';
import CommentSidebar from '../../components/public/CommentsSidebar';
import { PiaSections } from '../../types/enums/pia-sections.enum';
import { CommentCount } from '../../components/common/ViewComment/interfaces';
import { isCPORole } from '../../utils/user';
import { statusList } from '../../utils/statusList/statusList';
Expand All @@ -45,7 +44,7 @@ const PIAFormPage = () => {
const [isRightOpen, setIsRightOpen] = useState(false);
const [isLeftOpen, setIsLeftOpen] = useState(true);
const [commentCount, setCommentCount] = useState<CommentCount>({});
const [selectedSection, setSelectedSection] = useState<PiaSections>();
const [selectedSection, setSelectedSection] = useState<string>();
const [bringCommentsSidebarToFocus, setBringCommentsSidebarToFocus] =
useState(0);

Expand Down Expand Up @@ -192,7 +191,7 @@ const PIAFormPage = () => {
setBringCommentsSidebarToFocus((prev) => prev + 1);
}
};
const piaCommentPathHandler = (path: PiaSections | undefined) => {
const piaCommentPathHandler = (path: string | undefined) => {
setSelectedSection(path);
};
const commentChangeHandler = () => {
Expand Down

0 comments on commit fd25150

Please sign in to comment.