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 = () => {
} />
} />
} />
+ } />
>
);