From c68742e0b3f23c77168eb047f14d59c2594f226b Mon Sep 17 00:00:00 2001 From: VineetBala-AOT <90332175+VineetBala-AOT@users.noreply.github.com> Date: Tue, 26 Sep 2023 10:13:26 -0700 Subject: [PATCH] Changes to apply restriction on unpublished engagement (#2262) --- met-api/src/met_api/models/comment.py | 6 +- .../src/met_api/services/comment_service.py | 66 +++++++++++----- .../met_api/services/submission_service.py | 22 +++++- .../dashboard/comment/CommentViewContext.tsx | 19 +++-- .../survey/submit/ActionContext.tsx | 18 +++-- met-web/src/routes/NotAvailable.tsx | 76 +++++++++++++++++++ met-web/src/routes/UnauthenticatedRoutes.tsx | 2 + 7 files changed, 170 insertions(+), 39 deletions(-) create mode 100644 met-web/src/routes/NotAvailable.tsx diff --git a/met-api/src/met_api/models/comment.py b/met-api/src/met_api/models/comment.py index 9ff104c3b..71ed17de9 100644 --- a/met-api/src/met_api/models/comment.py +++ b/met-api/src/met_api/models/comment.py @@ -90,7 +90,7 @@ def get_comments_by_survey_id_paginated(cls, survey_id, pagination_options: Pagi @classmethod def get_accepted_comments_by_survey_id_paginated( - cls, survey_id, pagination_options: PaginationOptions, search_text=''): + cls, survey_id, pagination_options: PaginationOptions, search_text='', include_unpublished=False): """Get comments for closed engagements.""" query = db.session.query(Comment)\ .join(Submission, Submission.id == Comment.submission_id)\ @@ -103,10 +103,12 @@ def get_accepted_comments_by_survey_id_paginated( and_( Comment.survey_id == survey_id, CommentStatusModel.id == CommentStatus.Approved.value, - Engagement.status_id != EngagementStatus.Unpublished.value, ReportSetting.display == true() )) + if not include_unpublished: + query = query.filter(Engagement.status_id != EngagementStatus.Unpublished.value) + if search_text: query = query.filter(Comment.text.ilike('%' + search_text + '%')) diff --git a/met-api/src/met_api/services/comment_service.py b/met-api/src/met_api/services/comment_service.py index 47ee07808..59aa0f440 100644 --- a/met-api/src/met_api/services/comment_service.py +++ b/met-api/src/met_api/services/comment_service.py @@ -80,9 +80,11 @@ def can_view_unapproved_comments(survey_id: int) -> bool: @classmethod def get_comments_paginated(cls, survey_id, pagination_options: PaginationOptions, search_text=''): """Get comments paginated.""" + include_unpublished = CommentService.can_view_unapproved_comments(survey_id) + comment_schema = CommentSchema(many=True, only=('text', 'submission_date')) items, total = Comment.get_accepted_comments_by_survey_id_paginated( - survey_id, pagination_options, search_text) + survey_id, pagination_options, search_text, include_unpublished) return { 'items': comment_schema.dump(items), 'total': total @@ -149,12 +151,12 @@ def extract_comments_from_survey(cls, survey_submission: SubmissionSchema, surve @classmethod def export_comments_to_spread_sheet_staff(cls, survey_id): """Export comments to spread sheet.""" - engagement = SurveyModel.find_by_id(survey_id) + survey = SurveyModel.find_by_id(survey_id) comments = Comment.get_comments_by_survey_id(survey_id) - metadata_model = EngagementMetadataModel.find_by_id(engagement.engagement_id) + metadata_model = EngagementMetadataModel.find_by_id(survey.engagement_id) project_name = metadata_model.project_metadata.get('project_name') if metadata_model else None - titles = cls.get_titles(comments) + titles = cls.get_titles(survey) data_rows = cls.get_data_rows(titles, comments, project_name) formatted_comments = { @@ -170,15 +172,17 @@ def export_comments_to_spread_sheet_staff(cls, survey_id): return DocumentGenerationService().generate_document(data=formatted_comments, options=document_options) @classmethod - def get_titles(cls, comments): + def get_titles(cls, survey: SurveySchema): """Get the titles to be displayed on the sheet.""" # Title could be dynamic based on the number of comment type questions on the survey - unique_labels = set() - for submission in comments: - for comment in submission.get('comments', []): - unique_labels.add(comment.get('label')) + survey_form = survey.form_json + labels = [] + for component in survey_form.get('components', []): + if component.get('inputType', None) == 'text': + if 'label' in component: + labels.append(component['label']) - return [{'label': label} for label in unique_labels if label is not None] + return [{'label': label} for label in labels if label is not None] @classmethod def get_data_rows(cls, titles, comments, project_name): @@ -239,14 +243,14 @@ def get_rejection_note(cls, comment): @classmethod def export_comments_to_spread_sheet_proponent(cls, survey_id): """Export comments to spread sheet.""" - engagement = SurveyModel.find_by_id(survey_id) + survey = SurveyModel.find_by_id(survey_id) one_of_roles = ( MembershipType.TEAM_MEMBER.name, Role.EXPORT_ALL_TO_CSV.value ) - authorization.check_auth(one_of_roles=one_of_roles, engagement_id=engagement.engagement_id) + authorization.check_auth(one_of_roles=one_of_roles, engagement_id=survey.engagement_id) comments = Comment.get_public_viewable_comments_by_survey_id(survey_id) - formatted_comments = cls.format_comments(comments) + formatted_comments = cls.format_comments(survey, comments) document_options = { 'document_type': GeneratedDocumentTypes.COMMENT_SHEET_PROPONENT.value, 'template_name': 'proponent_comments_sheet.xlsx', @@ -256,10 +260,9 @@ def export_comments_to_spread_sheet_proponent(cls, survey_id): return DocumentGenerationService().generate_document(data=formatted_comments, options=document_options) @classmethod - def format_comments(cls, comments): + def group_comments_by_submission_id(cls, comments): """Group the comments together, arranging them in the same order as the titles.""" grouped_comments = [] - titles = [] for comment in comments: submission_id = comment['submission_id'] text = comment.get('text', '') # Get the text, or an empty string if it's missing @@ -276,20 +279,41 @@ def format_comments(cls, comments): new_group = {'submission_id': submission_id, 'commentText': [{'text': text, 'label': label}]} grouped_comments.append(new_group) - # Add unique labels to titles list in order of appearance - if label not in [title['label'] for title in titles]: - titles.append({'label': label}) + return grouped_comments - # Sort commentText within each group based on the order of titles + @classmethod + def get_visible_titles(cls, survey, comments): + """Add unique labels to titles list in order of appearance.""" + visible_titles = [] + for comment in comments: + label = comment['label'] + if label not in [title['label'] for title in visible_titles]: + visible_titles.append({'label': label}) + + return [title for title in cls.get_titles(survey) if title in visible_titles] + + @classmethod + def sort_comments_by_titles(cls, titles, grouped_comments): + """Sort commentText within each group based on the order of titles.""" for group in grouped_comments: sorted_comment_text = [] for title in titles: label = title['label'] matching_comments = [comment for comment in group['commentText'] if comment['label'] == label] if not matching_comments: - sorted_comment_text.append({'text': '', 'label': label}) # Add empty text for missing labels + sorted_comment_text.append({'text': '', 'label': label}) else: sorted_comment_text.extend(matching_comments) group['commentText'] = sorted_comment_text - return {'titles': titles, 'comments': grouped_comments} + return grouped_comments + + @classmethod + def format_comments(cls, survey, comments): + """Format comments.""" + grouped_comments = cls.group_comments_by_submission_id(comments) + visible_titles = cls.get_visible_titles(survey, comments) + titles = [title for title in cls.get_titles(survey) if title in visible_titles] + sorted_comments = cls.sort_comments_by_titles(titles, grouped_comments) + + return {'titles': titles, 'comments': sorted_comments} diff --git a/met-api/src/met_api/services/submission_service.py b/met-api/src/met_api/services/submission_service.py index 826b0cb08..f13fd17da 100644 --- a/met-api/src/met_api/services/submission_service.py +++ b/met-api/src/met_api/services/submission_service.py @@ -6,6 +6,7 @@ from met_api.constants.comment_status import Status from met_api.constants.email_verification import EmailVerificationType +from met_api.constants.engagement_status import Status as EngagementStatus from met_api.constants.engagement_status import SubmissionStatus from met_api.constants.membership_type import MembershipType from met_api.constants.staff_note_type import StaffNoteType @@ -79,6 +80,9 @@ def create(cls, token, submission: SubmissionSchema): survey_id = submission.get('survey_id') survey = SurveyService.get(survey_id) engagement_id = survey.get('engagement_id') + # Restrict submission on unpublished engagement + if SubmissionService.is_unpublished(engagement_id): + return {} # Creates a scoped session that will be committed when diposed or rolledback if a exception occurs with session_scope() as session: @@ -115,6 +119,10 @@ def update_comments(cls, token, data: PublicSubmissionSchema): submission_id = email_verification.get('submission_id') submission = SubmissionModel.find_by_id(submission_id) + # Restrict submission on unpublished engagement + if SubmissionService.is_unpublished(submission.engagement_id): + return {} + submission.comment_status_id = Status.Pending with session_scope() as session: @@ -154,7 +162,7 @@ def review_comment(cls, submission_id, staff_review_details: dict, external_user with session_scope() as session: should_send_email = SubmissionService._should_send_email( - submission_id, staff_review_details) + submission_id, staff_review_details, submission.engagement_id) submission = SubmissionModel.update_comment_status( submission_id, staff_review_details, session) if staff_notes := staff_review_details.get('staff_note', []): @@ -235,16 +243,18 @@ def _create_staff_notes(survey_id, submission_id, staff_note): return doc @staticmethod - def _should_send_email(submission_id: int, staff_comment_details: dict) -> bool: + def _should_send_email(submission_id: int, staff_comment_details: dict, engagement_id: int) -> bool: """Check if an email should be sent for a rejected submission.""" # Dont send the mail # if the comment has threat # if notify_email is false + # if engagement is unpublished # Send the mail # if the status of the comment is rejected # if review note has changed # if review reason has changed - + if SubmissionService.is_unpublished(engagement_id): + return False if staff_comment_details.get('has_threat') is True: return False if staff_comment_details.get('notify_email') is False: @@ -435,3 +445,9 @@ def _get_dashboard_path(engagement: EngagementModel): format(slug=engagement_slug.slug) return current_app.config.get('ENGAGEMENT_DASHBOARD_PATH'). \ format(engagement_id=engagement.id) + + @staticmethod + def is_unpublished(engagement_id): + """Check if the engagement is unpublished.""" + engagement: EngagementModel = EngagementModel.find_by_id(engagement_id) + return engagement.status_id == EngagementStatus.Unpublished.value diff --git a/met-web/src/components/engagement/dashboard/comment/CommentViewContext.tsx b/met-web/src/components/engagement/dashboard/comment/CommentViewContext.tsx index 5d9574a78..2d4a0517e 100644 --- a/met-web/src/components/engagement/dashboard/comment/CommentViewContext.tsx +++ b/met-web/src/components/engagement/dashboard/comment/CommentViewContext.tsx @@ -1,4 +1,5 @@ import React, { createContext, useState, useEffect } from 'react'; +import { AxiosError } from 'axios'; import { useNavigate, useParams } from 'react-router-dom'; import { useAppDispatch } from 'hooks'; import { openNotification } from 'services/notificationService/notificationSlice'; @@ -90,13 +91,17 @@ export const CommentViewProvider = ({ children }: { children: JSX.Element | JSX. setEngagementLoading(false); } catch (error) { console.log(error); - dispatch( - openNotification({ - severity: 'error', - text: getErrorMessage(error) || 'Error occurred while fetching Engagement information', - }), - ); - navigate('/'); + if ((error as AxiosError)?.response?.status === 500) { + navigate('/not-available'); + } else { + dispatch( + openNotification({ + severity: 'error', + text: getErrorMessage(error) || 'Error occurred while fetching Engagement information', + }), + ); + navigate('/'); + } } }; diff --git a/met-web/src/components/survey/submit/ActionContext.tsx b/met-web/src/components/survey/submit/ActionContext.tsx index 0c6b203ee..2cc0a5043 100644 --- a/met-web/src/components/survey/submit/ActionContext.tsx +++ b/met-web/src/components/survey/submit/ActionContext.tsx @@ -1,4 +1,5 @@ import React, { createContext, useState, useEffect } from 'react'; +import { AxiosError } from 'axios'; import { createDefaultSurvey, Survey } from 'models/survey'; import { useAppDispatch, useAppSelector } from 'hooks'; import { useNavigate, useParams } from 'react-router-dom'; @@ -100,12 +101,17 @@ export const ActionProvider = ({ children }: { children: JSX.Element }) => { setIsSurveyLoading(false); verifyToken(); } catch (error) { - dispatch( - openNotification({ - severity: 'error', - text: 'Error occurred while loading saved survey', - }), - ); + if ((error as AxiosError)?.response?.status === 500) { + navigate('/not-available'); + } else { + dispatch( + openNotification({ + severity: 'error', + text: 'Error occurred while loading saved survey', + }), + ); + navigate(`/not-available`); + } } }; diff --git a/met-web/src/routes/NotAvailable.tsx b/met-web/src/routes/NotAvailable.tsx new file mode 100644 index 000000000..58ae588f0 --- /dev/null +++ b/met-web/src/routes/NotAvailable.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { Grid } from '@mui/material'; +import { MetHeader1, MetParagraph, MetLabel } from 'components/common'; +import { Banner } from 'components/banner/Banner'; +import LandingPageBanner from 'assets/images/LandingPageBanner.png'; +import { useAppSelector } from 'hooks'; + +export const NotAvailable = () => { + const tenant = useAppSelector((state) => state.tenant); + return ( + + + + + + + {tenant?.name} + + + {'Description'} + + + + + + + + Sorry, this engagement is not available. + + + + ); +}; + +export default NotAvailable; diff --git a/met-web/src/routes/UnauthenticatedRoutes.tsx b/met-web/src/routes/UnauthenticatedRoutes.tsx index 934f0912f..b1ef03ef5 100644 --- a/met-web/src/routes/UnauthenticatedRoutes.tsx +++ b/met-web/src/routes/UnauthenticatedRoutes.tsx @@ -3,6 +3,7 @@ import EditSurvey from 'components/survey/edit'; import React from 'react'; import { Route, Routes } from 'react-router-dom'; import EngagementView from '../components/engagement/view'; +import NotAvailable from './NotAvailable'; import NotFound from './NotFound'; import EngagementComments from '../components/engagement/dashboard/comment'; import PublicDashboard from 'components/publicDashboard'; @@ -34,6 +35,7 @@ const UnauthenticatedRoutes = () => { } /> } /> } /> + } /> );