From 89ddca4602753af666ebabdbc87441ac4b897ab7 Mon Sep 17 00:00:00 2001 From: Shreya Pandit Date: Wed, 15 Mar 2023 21:49:41 +0200 Subject: [PATCH] added review state --- api/app/outcome/models.py | 3 ++ api/app/reviews/api.py | 23 ++++++++- api/app/reviews/tests.py | 49 ++++++++++++++++++- ...d0fd3894941a_add_outcome_status_journal.py | 28 +++++++++++ 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 api/migrations/versions/d0fd3894941a_add_outcome_status_journal.py diff --git a/api/app/outcome/models.py b/api/app/outcome/models.py index 55b45058c..95dfe4184 100644 --- a/api/app/outcome/models.py +++ b/api/app/outcome/models.py @@ -6,6 +6,9 @@ class Status(Enum): ACCEPTED = "accepted" REJECTED = "rejected" WAITLIST = "waitlist" + REVIEW = "in review" + ACCEPT_W_REVISION = "accept with minor revision" + REJECT_W_ENCOURAGEMENT = "reject with encouragement to resubmit" class Outcome(db.Model): id = db.Column(db.Integer(), primary_key = True, nullable = False) diff --git a/api/app/reviews/api.py b/api/app/reviews/api.py index 7258b622e..3cf85949e 100644 --- a/api/app/reviews/api.py +++ b/api/app/reviews/api.py @@ -10,7 +10,7 @@ from app import db, LOGGER from app.applicationModel.models import ApplicationForm -from app.events.models import Event, EventRole +from app.events.models import Event, EventRole, EventType from app.events.repository import EventRepository as event_repository from app.responses.repository import ResponseRepository as response_repository from app.responses.models import Response, ResponseReviewer @@ -27,6 +27,9 @@ from app.events.repository import EventRepository as event_repository from app.utils.auth import auth_required +from app.outcome.models import Outcome, Status +from app.outcome.repository import OutcomeRepository as outcome_repository + from app.utils.auth import auth_required, event_admin_required from app.utils.errors import EVENT_NOT_FOUND, REVIEW_RESPONSE_NOT_FOUND, FORBIDDEN, USER_NOT_FOUND, RESPONSE_NOT_FOUND, \ REVIEW_FORM_NOT_FOUND, REVIEW_ALREADY_COMPLETED, NO_ACTIVE_REVIEW_FORM, REVIEW_FORM_FOR_STAGE_NOT_FOUND @@ -188,6 +191,16 @@ def _add_reviewer_role(user_id, event_id): db.session.add(event_role) db.session.commit() +def _update_status(event, user_id, updated_user_id): + if event.event_type == EventType.JOURNAL or event.event_type == EventType.CONTINUOUS_JOURNAL: + new_outcome = Outcome( + event.id, + user_id, + Status.REVIEW, + updated_user_id + ) + outcome_repository.add(new_outcome) + db.session.commit() class ReviewResponseUser(): def __init__(self, review_form, response, reviews_remaining_count, language, reference_responses=None, @@ -473,11 +486,16 @@ def post(self): response_ids = self.get_eligible_response_ids(event_id, reviewer_user.id, num_reviews, config.num_reviews_required) + + response_user_ids = [response_repository.get_by_id(response_id).user_id for response_id in response_ids] + response_reviewers = [ResponseReviewer(response_id, reviewer_user.id) for response_id in response_ids] db.session.add_all(response_reviewers) db.session.commit() if len(response_ids) > 0: + for user_id in response_user_ids: + _update_status(event, user_id, g.current_user['id']) email_user( 'reviews-assigned', template_parameters=dict( @@ -633,6 +651,7 @@ def post(self, event_id): event = event_repository.get_by_id(event_id) + response_user_ids = [response_repository.get_by_id(response_id).user_id for response_id in response_ids] reviewer_user = user_repository.get_by_email(reviewer_email, g.organisation.id) if reviewer_user is None: return USER_NOT_FOUND @@ -645,6 +664,8 @@ def post(self, event_id): db.session.commit() if len(response_ids) > 0: + for user_id in response_user_ids: + _update_status(event, user_id, g.current_user['id']) email_user( 'reviews-assigned', template_parameters=dict( diff --git a/api/app/reviews/tests.py b/api/app/reviews/tests.py index cd3828848..7b688cf85 100644 --- a/api/app/reviews/tests.py +++ b/api/app/reviews/tests.py @@ -5,7 +5,7 @@ from app import db, LOGGER from app.utils.testing import ApiTestCase -from app.events.models import Event, EventRole +from app.events.models import Event, EventRole, EventType from app.users.models import AppUser, UserCategory, Country from app.applicationModel.models import ApplicationForm, Question, Section from app.responses.models import Response, Answer, ResponseReviewer @@ -13,6 +13,8 @@ from app.references.models import ReferenceRequest, Reference from app.references.repository import ReferenceRequestRepository as reference_request_repository from app.reviews.models import ReviewForm, ReviewQuestion, ReviewResponse, ReviewScore, ReviewConfiguration +from app.outcome.models import Outcome, Status +from app.outcome.repository import OutcomeRepository as outcome_repository from app.reviews.models import ReviewForm, ReviewQuestion, ReviewQuestionTranslation, ReviewResponse, ReviewScore, ReviewConfiguration from app.utils.errors import REVIEW_RESPONSE_NOT_FOUND, FORBIDDEN, USER_NOT_FOUND @@ -1418,6 +1420,8 @@ class ResponseReviewerAssignmentApiTest(ApiTestCase): def seed_static_data(self): self.event = self.add_event(key='event1') self.event2 = self.add_event(key='event2') + self.journal = self.add_event(key='journal1', event_type=EventType.JOURNAL) + self.event_admin = self.add_user('eventadmin@mail.com') self.reviewer = self.add_user('reviewer@mail.com') self.reviewer_user_id = self.reviewer.id @@ -1426,17 +1430,28 @@ def seed_static_data(self): self.user2 = self.add_user('user2@mail.com') self.user3 = self.add_user('user3@mail.com') + self.user1_id = self.user1.id + self.user2_id = self.user2.id + self.event.add_event_role('admin', self.event_admin.id) + self.journal.add_event_role('admin', self.event_admin.id) application_form = self.create_application_form(self.event.id) application_form2 = self.create_application_form(self.event2.id) + application_form3 = self.create_application_form(self.journal.id) + self.response1 = self.add_response(application_form.id, self.user1.id, is_submitted=True) self.response2 = self.add_response(application_form.id, self.user2.id, is_submitted=True) self.response3 = self.add_response(application_form.id, self.user3.id, is_submitted=True) + self.response1_journal = self.add_response(application_form3.id, self.user1.id, is_submitted=True) + self.response2_journal = self.add_response(application_form3.id, self.user2.id, is_submitted=True) + self.response3_journal = self.add_response(application_form3.id, self.user3.id, is_submitted=True) + self.add_review_form(application_form.id) self.add_review_form(application_form2.id) - + self.add_review_form(application_form3.id) + self.event2_response_id = self.add_response(application_form2.id, self.user1.id, is_submitted=True).id self.add_email_template('reviews-assigned') @@ -1461,6 +1476,36 @@ def test_responses_assigned(self): for rr in response_reviewers: self.assertEqual(rr.reviewer_user_id, self.reviewer_user_id) + + def test_journal_response_assigned(self): + """Test that the application status changes to REVIEW when a reviewer is assigned.""" + self.seed_static_data() + params = {'event_id' : self.journal.id, 'response_ids': [self.response1_journal.id, self.response2_journal.id], 'reviewer_email': 'reviewer@mail.com'} + + response = self.app.post( + '/api/v1/assignresponsereviewer', + headers=self.get_auth_header_for('eventadmin@mail.com'), + data=params) + self.assertEqual(response.status_code, 201) + + response_reviewers = (db.session.query(ResponseReviewer) + .join(Response, ResponseReviewer.response_id == Response.id) + .filter_by(application_form_id=3).all()) + + self.assertEqual(len(response_reviewers), 2) + + for rr in response_reviewers: + self.assertEqual(rr.reviewer_user_id, self.reviewer_user_id) + + outcomes = outcome_repository.get_latest_for_event(self.journal.id) + self.assertEqual(len(outcomes), 2) + ids = [self.user1_id, self.user2_id] + + for i, outcome in enumerate(outcomes): + self.assertEqual(outcome.event_id, self.journal.id) + self.assertEqual(outcome.user_id, ids[i]) + self.assertEqual(outcome.status, Status.REVIEW) + self.assertEqual(outcome.updated_by_user_id, self.event_admin.id) def test_response_for_different_event_forbidden(self): self.seed_static_data() diff --git a/api/migrations/versions/d0fd3894941a_add_outcome_status_journal.py b/api/migrations/versions/d0fd3894941a_add_outcome_status_journal.py new file mode 100644 index 000000000..b1fc5bfea --- /dev/null +++ b/api/migrations/versions/d0fd3894941a_add_outcome_status_journal.py @@ -0,0 +1,28 @@ +"""Add review, accept with revision, and reject with encouragement states. + +Revision ID: d0fd3894941a +Revises: 349da0b8780a +Create Date: 2023-03-14 14:43:16.898594 + +""" + +# revision identifiers, used by Alembic. +revision = 'd0fd3894941a' +down_revision = '349da0b8780a' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.execute("COMMIT") + op.execute("ALTER TYPE outcome_status ADD VALUE 'REVIEW'") + op.execute("ALTER TYPE outcome_status ADD VALUE 'ACCEPT_W_REVISION'") + op.execute("ALTER TYPE outcome_status ADD VALUE 'REJECT_W_ENCOURAGEMENT'") + +def downgrade(): + op.execute("""DELETE FROM pg_enum + WHERE enumlabel = 'REVIEW' OR enumlabel = 'ACCEPT_W_REVISION' OR enumlabel = 'REJECT_W_ENCOURAGEMENT' + AND enumtypid = ( + SELECT oid FROM pg_type WHERE typname = 'outcome_status' + )""")