From 56b93f51a70d258df0097515ea5788e4fc09363a Mon Sep 17 00:00:00 2001 From: Shreya Pandit Date: Mon, 3 Apr 2023 12:26:46 +0200 Subject: [PATCH 1/6] added action editor view --- api/app/applicationModel/api.py | 4 +- api/app/outcome/api.py | 6 +- api/app/responses/api.py | 37 +++++++++--- api/app/responses/models.py | 2 +- api/app/responses/repository.py | 14 ++++- api/app/reviews/api.py | 30 ++++++---- api/app/reviews/mixins.py | 1 + api/app/tags/api.py | 4 +- api/app/users/models.py | 12 +++- api/app/utils/auth.py | 17 ++++++ webapp/src/App.js | 41 ++++++++++++- .../components/ResponseListForm.js | 6 +- webapp/src/pages/ResponsePage/ResponsePage.js | 58 +++++++++++++++---- .../components/ReviewAssignmentComponent.js | 4 +- .../components/ReviewDashboard.js | 4 +- webapp/src/services/reviews/review.service.js | 4 +- webapp/src/utils/user.js | 13 +++++ 17 files changed, 209 insertions(+), 48 deletions(-) diff --git a/api/app/applicationModel/api.py b/api/app/applicationModel/api.py index 6226102be..0755cecda 100644 --- a/api/app/applicationModel/api.py +++ b/api/app/applicationModel/api.py @@ -11,7 +11,7 @@ from app.events.repository import EventRepository as event_repository from app.applicationModel.repository import ApplicationFormRepository as application_form_repository from app.users.repository import UserRepository as user_repository -from app.utils.auth import auth_required, event_admin_required +from app.utils.auth import auth_required, event_admin_required, event_admin_or_action_editor_required from app.utils.errors import APPLICATION_FORM_EXISTS, EVENT_NOT_FOUND, QUESTION_NOT_FOUND, SECTION_NOT_FOUND, DB_NOT_AVAILABLE, FORM_NOT_FOUND, APPLICATIONS_CLOSED from app import db, bcrypt @@ -406,7 +406,7 @@ def _serialize_question(question, language): class QuestionListApi(restful.Resource): - @event_admin_required + @event_admin_or_action_editor_required def get(self, event_id): req_parser = reqparse.RequestParser() req_parser.add_argument('language', type=str, required=True) diff --git a/api/app/outcome/api.py b/api/app/outcome/api.py index a782a49fc..2fb40cab6 100644 --- a/api/app/outcome/api.py +++ b/api/app/outcome/api.py @@ -10,7 +10,7 @@ from app.users.repository import UserRepository as user_repository from app.utils.emailer import email_user -from app.utils.auth import auth_required, event_admin_required +from app.utils.auth import auth_required, event_admin_required, event_admin_or_action_editor_required from app import LOGGER from app import db from app.utils import errors @@ -46,7 +46,7 @@ def _extract_status(outcome): class OutcomeAPI(restful.Resource): - @event_admin_required + @event_admin_or_action_editor_required @marshal_with(outcome_fields) def get(self, event_id): req_parser = reqparse.RequestParser() @@ -69,7 +69,7 @@ def get(self, event_id): LOGGER.error("Encountered unknown error: {}".format(traceback.format_exc())) return errors.DB_NOT_AVAILABLE - @event_admin_required + @event_admin_or_action_editor_required @marshal_with(outcome_fields) def post(self, event_id): req_parser = reqparse.RequestParser() diff --git a/api/app/responses/api.py b/api/app/responses/api.py index fad3c160e..33cc392bc 100644 --- a/api/app/responses/api.py +++ b/api/app/responses/api.py @@ -22,7 +22,7 @@ from app.users.repository import UserRepository as user_repository from app.utils import emailer, errors, strings, pdfconvertor, zipping, storage from app.utils.zipping import zip_in_memory -from app.utils.auth import auth_required, event_admin_required +from app.utils.auth import auth_required, event_admin_or_action_editor_required from flask import g, request, send_file from flask_restful import fields, inputs, marshal_with, reqparse from sqlalchemy.exc import SQLAlchemyError @@ -276,7 +276,7 @@ def _serialize_tag(tag, language): class ResponseListAPI(restful.Resource): - @event_admin_required + @event_admin_or_action_editor_required def get(self, event_id): req_parser = reqparse.RequestParser() req_parser.add_argument('include_unsubmitted', type=inputs.boolean, required=True) @@ -288,10 +288,15 @@ def get(self, event_id): include_unsubmitted = args['include_unsubmitted'] question_ids = args['question_ids[]'] language = args['language'] - + current_user = user_repository.get_by_id(g.current_user['id']) + print(('Include unsubmitted:', include_unsubmitted)) - responses = response_repository.get_all_for_event(event_id, not include_unsubmitted) + if current_user.is_event_admin(event_id): + responses = response_repository.get_all_for_event(event_id, not include_unsubmitted) + + elif current_user.is_action_editor(event_id): + responses = response_repository.get_all_for_action_editor(event_id, g.current_user['id'], not include_unsubmitted) review_config = review_configuration_repository.get_configuration_for_event(event_id) required_reviewers = 1 if review_config is None else review_config.num_reviews_required + review_config.num_optional_reviews @@ -306,10 +311,18 @@ def get(self, event_id): r.reviewer_user_id: r for r in review_responses } + event = event_repository.get_by_id(event_id) + serialized_responses = [] for response in responses: - reviewers = [_serialize_reviewer(r, reviewer_to_review_response.get(r.reviewer_user_id, None)) - for r in response_to_reviewers.get(response.id, [])] + reviewers = [] + action_editor = None + for r in response_to_reviewers.get(response.id, []): + if not r.is_action_editor: + reviewers.append(_serialize_reviewer(r, reviewer_to_review_response.get(r.reviewer_user_id, None))) + else: + action_editor = _serialize_reviewer(r, reviewer_to_review_response.get(r.reviewer_user_id, None)) + reviewers = _pad_list(reviewers, required_reviewers) if question_ids: answers = [_serialize_answer(answer, language) for answer in response.answers if answer.question_id in question_ids] @@ -332,8 +345,10 @@ def get(self, event_id): 'tags': [_serialize_tag(rt.tag, language) for rt in response.response_tags] } + if (event.event_type == EventType.CONTINUOUS_JOURNAL or event.event_type == EventType.JOURNAL): + serialized['action_editor'] = action_editor + serialized_responses.append(serialized) - return serialized_responses @@ -425,7 +440,8 @@ def _serialize_reviewer(response_reviewer, review_form_id): 'user_title': response_reviewer.user.user_title, 'firstname': response_reviewer.user.firstname, 'lastname': response_reviewer.user.lastname, - 'status': _review_response_status(review_response) + 'status': _review_response_status(review_response), + 'is_action_editor': response_reviewer.is_action_editor } @@ -449,7 +465,7 @@ def _serialize_response(response, language, review_form_id, num_reviewers): 'reviewers': [ResponseDetailAPI._serialize_reviewer(r, review_form_id) for r in response.reviewers] } - @event_admin_required + @event_admin_or_action_editor_required def get(self, event_id): req_parser = reqparse.RequestParser() req_parser.add_argument('response_id', type=int, required=True) @@ -458,6 +474,9 @@ def get(self, event_id): response_id = args['response_id'] language = args['language'] + + if not _validate_user_admin_or_reviewer(g.current_user['id'], event_id, response_id): + return errors.FORBIDDEN response = response_repository.get_by_id(response_id) review_form = review_repository.get_review_form(event_id) diff --git a/api/app/responses/models.py b/api/app/responses/models.py index dac0fd2c7..43df0740c 100644 --- a/api/app/responses/models.py +++ b/api/app/responses/models.py @@ -103,7 +103,7 @@ def __init__(self, response_id, reviewer_user_id, is_action_editor=False): self.response_id = response_id self.reviewer_user_id = reviewer_user_id self.active = True - is_action_editor = is_action_editor + self.is_action_editor = is_action_editor def deactivate(self): self.active = False diff --git a/api/app/responses/repository.py b/api/app/responses/repository.py index 48ea1caf0..b81f79525 100644 --- a/api/app/responses/repository.py +++ b/api/app/responses/repository.py @@ -1,7 +1,7 @@ from typing import List from app import db -from app.responses.models import Response, Answer, ResponseTag +from app.responses.models import Response, Answer, ResponseTag, ResponseReviewer from app.applicationModel.models import ApplicationForm, Question, Section from app.users.models import AppUser from sqlalchemy import func, cast, Date @@ -137,6 +137,18 @@ def get_all_for_event(event_id, submitted_only=True) -> List[Response]: .join(ApplicationForm, Response.application_form_id == ApplicationForm.id) .filter_by(event_id=event_id) .all()) + + def get_all_for_action_editor(event_id, action_editor_id, submitted_only=True) -> List[Response]: + query = db.session.query(Response) + if submitted_only: + query = query.filter_by(is_submitted=True) + + return (query + .join(ApplicationForm, Response.application_form_id == ApplicationForm.id) + .filter_by(event_id=event_id) + .join(ResponseReviewer, Response.id == ResponseReviewer.response_id) + .filter_by(reviewer_user_id=action_editor_id, is_action_editor=True) + .all()) @staticmethod def tag_response(response_id, tag_id): diff --git a/api/app/reviews/api.py b/api/app/reviews/api.py index b0b202638..143547198 100644 --- a/api/app/reviews/api.py +++ b/api/app/reviews/api.py @@ -25,9 +25,8 @@ from app.users.repository import UserRepository as user_repository from app.events.repository import EventRepository as event_repository -from app.utils.auth import auth_required -from app.utils.auth import auth_required, event_admin_required +from app.utils.auth import auth_required, event_admin_required, event_admin_or_action_editor_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 @@ -187,6 +186,18 @@ def _add_reviewer_role(user_id, event_id): event_role = EventRole('reviewer', user_id, event_id) db.session.add(event_role) db.session.commit() + +def _validate_user_admin_or_reviewer(user_id, event_id, response_id): + user = user_repository.get_by_id(user_id) + # Check if the user is an event admin + permitted = user.is_event_admin(event_id) + # If they're not an event admin, check if they're a reviewer for the relevant response + if not permitted and user.is_reviewer(event_id): + response_reviewer = review_repository.get_response_reviewer(response_id, user.id) + if response_reviewer is not None: + permitted = True + + return permitted class ReviewResponseUser(): @@ -431,15 +442,14 @@ class ReviewAssignmentAPI(GetReviewAssignmentMixin, PostReviewAssignmentMixin, r 'reviews_completed': fields.Integer } - @auth_required + @event_admin_or_action_editor_required @marshal_with(reviews_count_fields) - def get(self): + def get(self, event_id): args = self.get_req_parser.parse_args() - event_id = args['event_id'] + response_id = args['response_id'] user_id = g.current_user['id'] - current_user = user_repository.get_by_id(user_id) - if not current_user.is_event_admin(event_id): + if not _validate_user_admin_or_reviewer(user_id, event_id, response_id): return FORBIDDEN counts = review_repository.count_reviews_allocated_and_completed_per_reviewer(event_id) @@ -621,7 +631,7 @@ def get(self): for response, review_response in responses_to_review] class ResponseReviewAssignmentAPI(restful.Resource): - @event_admin_required + @event_admin_or_action_editor_required def post(self, event_id): parser = reqparse.RequestParser() parser.add_argument('response_ids', type=int, required=True, action='append') @@ -662,7 +672,7 @@ def post(self, event_id): user=reviewer_user) return {}, 201 - @event_admin_required + @event_admin_or_action_editor_required def delete(self, event_id): parser = reqparse.RequestParser() parser.add_argument('response_id', type=int, required=True) @@ -749,7 +759,7 @@ def _serialise_review_response(review_response, language): } @auth_required - @event_admin_required + @event_admin_or_action_editor_required def get(self, event_id): parser = reqparse.RequestParser() parser.add_argument('language', type=str, required=True) diff --git a/api/app/reviews/mixins.py b/api/app/reviews/mixins.py index a4ae8f152..d70a2e1d2 100644 --- a/api/app/reviews/mixins.py +++ b/api/app/reviews/mixins.py @@ -22,6 +22,7 @@ class PostReviewResponseMixin(object): class GetReviewAssignmentMixin(object): get_req_parser = reqparse.RequestParser() get_req_parser.add_argument('event_id', type=int, required=True) + get_req_parser.add_argument('response_id', type=int, required=True) class PostReviewAssignmentMixin(object): post_req_parser = reqparse.RequestParser() diff --git a/api/app/tags/api.py b/api/app/tags/api.py index be578c96a..981bfdc80 100644 --- a/api/app/tags/api.py +++ b/api/app/tags/api.py @@ -1,7 +1,7 @@ from flask import g import flask_restful as restful from flask_restful import reqparse, fields, marshal_with -from app.utils.auth import auth_required, event_admin_required +from app.utils.auth import event_admin_required, event_admin_or_action_editor_required from app.tags.repository import TagRepository as tag_repository from app.utils import errors from app.tags.models import Tag, TagTranslation @@ -90,7 +90,7 @@ def put(self, event_id): class TagListAPI(restful.Resource): - @event_admin_required + @event_admin_or_action_editor_required def get(self, event_id): req_parser = reqparse.RequestParser() req_parser.add_argument('language', type=str, required=True) diff --git a/api/app/users/models.py b/api/app/users/models.py index b19977735..a94e0908a 100644 --- a/api/app/users/models.py +++ b/api/app/users/models.py @@ -114,12 +114,22 @@ def is_registration_admin(self, event_id): # An event admin is also a registration admin return self._has_admin_role(event_id, 'registration-admin') or self._has_admin_role(event_id, 'admin') + def is_action_editor(self, event_id): + if self.event_roles is None: + return False + + for event_role in self.event_roles: + if event_role.event_id == event_id and event_role.role == 'action-editor': + return True + + return False + def is_reviewer(self, event_id): if self.event_roles is None: return False for event_role in self.event_roles: - if event_role.event_id == event_id and event_role.role == 'reviewer': + if event_role.event_id == event_id and (event_role.role == 'reviewer' or event_role.role == 'action-editor'): return True return False diff --git a/api/app/utils/auth.py b/api/app/utils/auth.py index fc449a970..529423b04 100644 --- a/api/app/utils/auth.py +++ b/api/app/utils/auth.py @@ -104,3 +104,20 @@ def wrapper(*args, **kwargs): return FORBIDDEN return wrapper + +def event_admin_or_action_editor_required(func): + @wraps(func) + def wrapper(*args, **kwargs): + req_parser = reqparse.RequestParser() + req_parser.add_argument('event_id', type=int, required=True) + req_args = req_parser.parse_args() + + user = get_user_from_request() + if user: + user_info = user_repository.get_by_id(user['id']) + if user_info.is_action_editor(req_args['event_id']) or user_info.is_event_admin(req_args['event_id']): + g.current_user = user + return func(*args, event_id=req_args['event_id'], **kwargs) + + return FORBIDDEN + return wrapper diff --git a/webapp/src/App.js b/webapp/src/App.js index 90b1d72d5..9a87da267 100755 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -21,7 +21,7 @@ import ReactGA from "react-ga"; import "./App.css"; import history from "./History"; -import { isEventAdmin, isRegistrationAdmin, isRegistrationVolunteer, isEventReviewer } from "./utils/user"; +import { isEventAdmin, isRegistrationAdmin, isRegistrationVolunteer, isEventReviewer, isActionEditor } from "./utils/user"; import { withTranslation } from 'react-i18next'; import { userService } from "./services/user"; @@ -217,6 +217,45 @@ class EventNav extends Component { )} + {isActionEditor(this.props.user, this.props.event) && + this.props.event && + this.props.event.is_review_open && ( +
  • + +
    + + {t('Response List')} + + + {t('Review')} + + + {t('Review History')} + +
    +
  • + )} {(isRegistrationAdmin(this.props.user, this.props.event) || isRegistrationVolunteer(this.props.user, this.props.event)) && this.props.event && this.props.event.is_registration_open && ( diff --git a/webapp/src/pages/ResponseList/components/ResponseListForm.js b/webapp/src/pages/ResponseList/components/ResponseListForm.js index 84315727c..408fb4d4f 100644 --- a/webapp/src/pages/ResponseList/components/ResponseListForm.js +++ b/webapp/src/pages/ResponseList/components/ResponseListForm.js @@ -52,7 +52,7 @@ class ResponseListForm extends Component { this.toggleList(false); const { selectedTags, selectedQuestions, includeUnsubmitted } = this.state; - responsesService.getResponseList(this.props.event.id, includeUnsubmitted, selectedQuestions).then(resp => { + responsesService.getResponseList(this.props.event.id, includeUnsubmitted, selectedQuestions).then(resp => { this.setState({ responses: resp.responses.filter(r => selectedTags.length == 0 || r.tags.map(t => t.name).some(t => selectedTags.includes(t))), error: resp.error @@ -150,6 +150,8 @@ class ResponseListForm extends Component { val.is_submitted = val.is_submitted ? "True" : "False"; val.is_withdrawn = val.is_withdrawn ? "True" : "False"; + val.action_editor = val.action_editor ? val.action_editor.reviewer_name : ""; + // delete original review and answer rows as they don't need to be displayed with all their data delete val.answers; delete val.reviewers; @@ -232,7 +234,7 @@ class ResponseListForm extends Component { return 200 }; - if (colItem.includes('user') || colItem.includes('Review') || colItem.includes('date') || colItem.includes('email')) { + if (colItem.includes('user') || colItem.includes('Review') || colItem.includes('date') || colItem.includes('email') || colItem.includes('action_editor')) { return 180 } else { diff --git a/webapp/src/pages/ResponsePage/ResponsePage.js b/webapp/src/pages/ResponsePage/ResponsePage.js index a5f4e94f4..fd1403962 100644 --- a/webapp/src/pages/ResponsePage/ResponsePage.js +++ b/webapp/src/pages/ResponsePage/ResponsePage.js @@ -41,19 +41,19 @@ class ResponsePage extends Component { applicationFormService.getForEvent(this.props.event.id), responsesService.getResponseDetail(this.props.match.params.id, this.props.event.id), tagsService.getTagList(this.props.event.id), - reviewService.getReviewAssignments(this.props.event.id) + reviewService.getReviewAssignments(this.props.event.id, this.props.match.params.id) ]).then(responses => { - console.log(responses); + console.log('r',responses); this.setState({ eventLanguages: responses[0].event ? Object.keys(responses[0].event.name) : null, event_type: responses[0].event.event_type, applicationForm: responses[1].formSpec, applicationData: responses[2].detail, tagList: responses[3].tags, - reviewers: responses[4].reviewers, + reviewers: responses[2].detail.reviewers, error: responses[0].error || responses[1].error || responses[2].error || responses[3].error || responses[4].error, }, () => { - this.handleData(); + // this.handleData(); this.getOutcome(); }); }); @@ -482,17 +482,43 @@ class ResponsePage extends Component { }; }; + renderActionEditor() { + const action_editor = this.state.reviewers.find(reviewer => reviewer.is_action_editor === true) + console.log(action_editor) + if (action_editor) { + return
    + +
    +

    {action_editor.user_title} {action_editor.firstname} {action_editor.lastname}

    + +
    +
    + } else { + return
    + +
    + } + + } // Reviews // Render Reviews renderReviews() { - if (this.state.applicationData) { - if (this.state.applicationData.reviewers) { - const reviews = this.state.applicationData.reviewers.map((val, index) => { - let num = index + 1; - // {"reviewer_user_id": 4, "user_title": "Mr", "firstname": "Joe", "lastname": "Soap", "status": "completed"}, - return
    + if (this.state.reviewers) { + if (this.state.reviewers) { + let num = 0 + const reviews = this.state.reviewers.map((val) => { + // {"reviewer_user_id": 4, "user_title": "Mr", "firstname": "Joe", "lastname": "Soap", "status": "completed", "is_action_editor": False}, + if (!val.is_action_editor) { + num = num + 1; + return

    {val.user_title} {val.firstname} {val.lastname}

    @@ -512,7 +538,9 @@ class ResponsePage extends Component { }
    - }); + } + } + ); return reviews }; @@ -696,6 +724,14 @@ class ResponsePage extends Component { {applicationData &&
    {/* Reviews */} +
    +

    {t('Action Editor')}

    +
    + {this.renderActionEditor()} + +
    +
    +

    {t('Reviewers')}

    diff --git a/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js b/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js index 7be749e39..5f73937e9 100644 --- a/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js +++ b/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js @@ -29,7 +29,7 @@ class ReviewAssignmentComponent extends Component { Promise.all([ tagsService.getTagList(event_id), - reviewService.getReviewAssignments(event_id), + reviewService.getReviewAssignments(event_id, this.props.match.params.id), reviewService.getReviewSummary(event_id, tags) ]).then(responses => { this.setState({ @@ -60,7 +60,7 @@ class ReviewAssignmentComponent extends Component { this.setState({ error: result.error }) - return reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0) + return reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0, this.props.match.params.id) }, ).then( result => { diff --git a/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js b/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js index 17ee687ff..08938a5c8 100644 --- a/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js +++ b/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js @@ -25,7 +25,9 @@ class ReviewListComponent extends Component { } componentDidMount() { + console.log('got to load', this.props) this.loadReviewList(this.state.mode); + console.log('loaded') } responseIDCell = props => { @@ -247,7 +249,7 @@ class ReviewAssignmentComponent extends Component { } componentDidMount() { - reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0).then(result => { + reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0, this.props.match.params.id).then(result => { this.setState({ loading: false, reviewers: result.reviewers, diff --git a/webapp/src/services/reviews/review.service.js b/webapp/src/services/reviews/review.service.js index 5b60e1ac7..760fd2c16 100644 --- a/webapp/src/services/reviews/review.service.js +++ b/webapp/src/services/reviews/review.service.js @@ -119,9 +119,9 @@ function submit(responseId, reviewFormId, scores, shouldUpdate, isSubmitted) { }); } -function getReviewAssignments(eventId) { +function getReviewAssignments(eventId,responseId) { return axios - .get(baseUrl + "/api/v1/reviewassignment?event_id=" + eventId, { + .get(baseUrl + "/api/v1/reviewassignment?event_id=" + eventId + "&response_id=" + responseId, { headers: authHeader() }) diff --git a/webapp/src/utils/user.js b/webapp/src/utils/user.js index 1939905b2..fc1b3bd47 100644 --- a/webapp/src/utils/user.js +++ b/webapp/src/utils/user.js @@ -11,6 +11,19 @@ export const isEventAdmin = (user, event) => { ); }; + export const isActionEditor = (user, event) => { + if (!user) { + return false; + } + return ( + user.is_admin || + (user.roles && + user.roles.some( + r => r.role === "action-editor" && event && r.event_id === event.id + )) + ); + }; + export const isRegistrationAdmin = (user, event) => { if (!user) { return false; From d16c8a99fc9f20c6676c4d240b572cf84875af13 Mon Sep 17 00:00:00 2001 From: Shreya Pandit Date: Mon, 3 Apr 2023 12:43:20 +0200 Subject: [PATCH 2/6] removed print --- webapp/src/pages/ResponsePage/ResponsePage.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/webapp/src/pages/ResponsePage/ResponsePage.js b/webapp/src/pages/ResponsePage/ResponsePage.js index fd1403962..70d5b0e72 100644 --- a/webapp/src/pages/ResponsePage/ResponsePage.js +++ b/webapp/src/pages/ResponsePage/ResponsePage.js @@ -484,7 +484,6 @@ class ResponsePage extends Component { renderActionEditor() { const action_editor = this.state.reviewers.find(reviewer => reviewer.is_action_editor === true) - console.log(action_editor) if (action_editor) { return
    @@ -493,18 +492,7 @@ class ResponsePage extends Component {
    - } else { - return
    - -
    } - } // Reviews From 0f4aa1d25e66bf670aaf99d1975d5a99184e0d00 Mon Sep 17 00:00:00 2001 From: Shreya Pandit Date: Mon, 3 Apr 2023 14:05:06 +0200 Subject: [PATCH 3/6] removed reviewAssignment --- api/app/reviews/api.py | 9 +++++---- api/app/reviews/mixins.py | 1 - webapp/src/pages/ResponsePage/ResponsePage.js | 6 ++---- .../components/ReviewAssignmentComponent.js | 4 ++-- .../pages/reviewDashboard/components/ReviewDashboard.js | 2 +- webapp/src/services/reviews/review.service.js | 4 ++-- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/api/app/reviews/api.py b/api/app/reviews/api.py index 143547198..93d38deb4 100644 --- a/api/app/reviews/api.py +++ b/api/app/reviews/api.py @@ -442,14 +442,15 @@ class ReviewAssignmentAPI(GetReviewAssignmentMixin, PostReviewAssignmentMixin, r 'reviews_completed': fields.Integer } - @event_admin_or_action_editor_required + @auth_required @marshal_with(reviews_count_fields) - def get(self, event_id): + def get(self): args = self.get_req_parser.parse_args() - response_id = args['response_id'] + event_id = args['event_id'] user_id = g.current_user['id'] - if not _validate_user_admin_or_reviewer(user_id, event_id, response_id): + current_user = user_repository.get_by_id(user_id) + if not current_user.is_event_admin(event_id): return FORBIDDEN counts = review_repository.count_reviews_allocated_and_completed_per_reviewer(event_id) diff --git a/api/app/reviews/mixins.py b/api/app/reviews/mixins.py index d70a2e1d2..a4ae8f152 100644 --- a/api/app/reviews/mixins.py +++ b/api/app/reviews/mixins.py @@ -22,7 +22,6 @@ class PostReviewResponseMixin(object): class GetReviewAssignmentMixin(object): get_req_parser = reqparse.RequestParser() get_req_parser.add_argument('event_id', type=int, required=True) - get_req_parser.add_argument('response_id', type=int, required=True) class PostReviewAssignmentMixin(object): post_req_parser = reqparse.RequestParser() diff --git a/webapp/src/pages/ResponsePage/ResponsePage.js b/webapp/src/pages/ResponsePage/ResponsePage.js index 70d5b0e72..96133b5f1 100644 --- a/webapp/src/pages/ResponsePage/ResponsePage.js +++ b/webapp/src/pages/ResponsePage/ResponsePage.js @@ -41,9 +41,8 @@ class ResponsePage extends Component { applicationFormService.getForEvent(this.props.event.id), responsesService.getResponseDetail(this.props.match.params.id, this.props.event.id), tagsService.getTagList(this.props.event.id), - reviewService.getReviewAssignments(this.props.event.id, this.props.match.params.id) ]).then(responses => { - console.log('r',responses); + console.log(responses); this.setState({ eventLanguages: responses[0].event ? Object.keys(responses[0].event.name) : null, event_type: responses[0].event.event_type, @@ -51,9 +50,8 @@ class ResponsePage extends Component { applicationData: responses[2].detail, tagList: responses[3].tags, reviewers: responses[2].detail.reviewers, - error: responses[0].error || responses[1].error || responses[2].error || responses[3].error || responses[4].error, + error: responses[0].error || responses[1].error || responses[2].error || responses[3].error, }, () => { - // this.handleData(); this.getOutcome(); }); }); diff --git a/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js b/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js index 5f73937e9..7be749e39 100644 --- a/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js +++ b/webapp/src/pages/reviewAssignment/components/ReviewAssignmentComponent.js @@ -29,7 +29,7 @@ class ReviewAssignmentComponent extends Component { Promise.all([ tagsService.getTagList(event_id), - reviewService.getReviewAssignments(event_id, this.props.match.params.id), + reviewService.getReviewAssignments(event_id), reviewService.getReviewSummary(event_id, tags) ]).then(responses => { this.setState({ @@ -60,7 +60,7 @@ class ReviewAssignmentComponent extends Component { this.setState({ error: result.error }) - return reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0, this.props.match.params.id) + return reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0) }, ).then( result => { diff --git a/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js b/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js index 08938a5c8..604418c7d 100644 --- a/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js +++ b/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js @@ -249,7 +249,7 @@ class ReviewAssignmentComponent extends Component { } componentDidMount() { - reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0, this.props.match.params.id).then(result => { + reviewService.getReviewAssignments(this.props.event ? this.props.event.id : 0).then(result => { this.setState({ loading: false, reviewers: result.reviewers, diff --git a/webapp/src/services/reviews/review.service.js b/webapp/src/services/reviews/review.service.js index 760fd2c16..5b60e1ac7 100644 --- a/webapp/src/services/reviews/review.service.js +++ b/webapp/src/services/reviews/review.service.js @@ -119,9 +119,9 @@ function submit(responseId, reviewFormId, scores, shouldUpdate, isSubmitted) { }); } -function getReviewAssignments(eventId,responseId) { +function getReviewAssignments(eventId) { return axios - .get(baseUrl + "/api/v1/reviewassignment?event_id=" + eventId + "&response_id=" + responseId, { + .get(baseUrl + "/api/v1/reviewassignment?event_id=" + eventId, { headers: authHeader() }) From 7426b21a87a1bfbf973de72b31576f3c5625ddab Mon Sep 17 00:00:00 2001 From: Shreya Pandit Date: Mon, 3 Apr 2023 14:49:56 +0200 Subject: [PATCH 4/6] fixed reviewassignment --- api/app/reviews/api.py | 5 +- api/app/reviews/repository.py | 3 +- webapp/src/pages/ResponsePage/ResponsePage.js | 69 +++++++++---------- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/api/app/reviews/api.py b/api/app/reviews/api.py index 93d38deb4..5cc231c58 100644 --- a/api/app/reviews/api.py +++ b/api/app/reviews/api.py @@ -442,11 +442,10 @@ class ReviewAssignmentAPI(GetReviewAssignmentMixin, PostReviewAssignmentMixin, r 'reviews_completed': fields.Integer } - @auth_required + @event_admin_or_action_editor_required @marshal_with(reviews_count_fields) - def get(self): + def get(self, event_id): args = self.get_req_parser.parse_args() - event_id = args['event_id'] user_id = g.current_user['id'] current_user = user_repository.get_by_id(user_id) diff --git a/api/app/reviews/repository.py b/api/app/reviews/repository.py index d687a19f4..183da0645 100644 --- a/api/app/reviews/repository.py +++ b/api/app/reviews/repository.py @@ -27,7 +27,8 @@ def count_reviews_allocated_and_completed_per_reviewer(event_id): 0 as reviews_completed from app_user join event_role on event_role.user_id = app_user.id - where event_role.role = 'reviewer' + where event_role.role = 'reviewer' + or event_role.role = 'action-editor' and event_role.event_id = {event_id} and not exists ( select 1 diff --git a/webapp/src/pages/ResponsePage/ResponsePage.js b/webapp/src/pages/ResponsePage/ResponsePage.js index 96133b5f1..a5d9b7c62 100644 --- a/webapp/src/pages/ResponsePage/ResponsePage.js +++ b/webapp/src/pages/ResponsePage/ResponsePage.js @@ -41,6 +41,7 @@ class ResponsePage extends Component { applicationFormService.getForEvent(this.props.event.id), responsesService.getResponseDetail(this.props.match.params.id, this.props.event.id), tagsService.getTagList(this.props.event.id), + reviewService.getReviewAssignments(this.props.event.id) ]).then(responses => { console.log(responses); this.setState({ @@ -49,8 +50,8 @@ class ResponsePage extends Component { applicationForm: responses[1].formSpec, applicationData: responses[2].detail, tagList: responses[3].tags, - reviewers: responses[2].detail.reviewers, - error: responses[0].error || responses[1].error || responses[2].error || responses[3].error, + reviewers: responses[4].reviewers, + error: responses[0].error || responses[1].error || responses[2].error || responses[3].error || responses[4].error, }, () => { this.getOutcome(); }); @@ -481,7 +482,7 @@ class ResponsePage extends Component { }; renderActionEditor() { - const action_editor = this.state.reviewers.find(reviewer => reviewer.is_action_editor === true) + const action_editor = this.state.applicationData.reviewers.find(reviewer => reviewer.is_action_editor === true) if (action_editor) { return
    @@ -497,40 +498,38 @@ class ResponsePage extends Component { // Render Reviews renderReviews() { - if (this.state.reviewers) { - if (this.state.reviewers) { - let num = 0 - const reviews = this.state.reviewers.map((val) => { - // {"reviewer_user_id": 4, "user_title": "Mr", "firstname": "Joe", "lastname": "Soap", "status": "completed", "is_action_editor": False}, - if (!val.is_action_editor) { - num = num + 1; - return
    - -
    -

    {val.user_title} {val.firstname} {val.lastname}

    - - {val.status === "completed" &&

    {this.props.t("Completed")}

    } - {val.status === "started" &&

    {this.props.t("In Progress")}

    } - {val.status === "not_started" && -

    - {this.props.t("Not Started")} - -

    - } -
    + if (this.state.applicationData.reviewers) { + let num = 0 + const reviews = this.state.applicationData.reviewers.map((val) => { + // {"reviewer_user_id": 4, "user_title": "Mr", "firstname": "Joe", "lastname": "Soap", "status": "completed", "is_action_editor": False}, + if (!val.is_action_editor) { + num = num + 1; + return
    + +
    +

    {val.user_title} {val.firstname} {val.lastname}

    + + {val.status === "completed" &&

    {this.props.t("Completed")}

    } + {val.status === "started" &&

    {this.props.t("In Progress")}

    } + {val.status === "not_started" && +

    + {this.props.t("Not Started")} + +

    + }
    - } - } - ); - - return reviews - }; +
    } + } + ); + + return reviews + }; }; From 17a022c89c223dcd350d849ee56d14654a66715b Mon Sep 17 00:00:00 2001 From: Shreya Pandit Date: Tue, 4 Apr 2023 08:35:15 +0200 Subject: [PATCH 5/6] fixed comments --- api/app/responses/api.py | 11 +++-- api/app/reviews/api.py | 7 --- webapp/src/pages/ResponsePage/ResponsePage.js | 44 +++++++++---------- .../components/ReviewDashboard.js | 2 - 4 files changed, 26 insertions(+), 38 deletions(-) diff --git a/api/app/responses/api.py b/api/app/responses/api.py index 33cc392bc..5db274a3e 100644 --- a/api/app/responses/api.py +++ b/api/app/responses/api.py @@ -318,10 +318,11 @@ def get(self, event_id): reviewers = [] action_editor = None for r in response_to_reviewers.get(response.id, []): - if not r.is_action_editor: - reviewers.append(_serialize_reviewer(r, reviewer_to_review_response.get(r.reviewer_user_id, None))) + reviewer = _serialize_reviewer(r, reviewer_to_review_response.get(r.reviewer_user_id, None)) + if reviewer.is_action_editor: + action_editor = reviewer else: - action_editor = _serialize_reviewer(r, reviewer_to_review_response.get(r.reviewer_user_id, None)) + reviewers.append(reviewer) reviewers = _pad_list(reviewers, required_reviewers) if question_ids: @@ -342,11 +343,9 @@ def get(self, event_id): 'language': response.language, 'answers': answers, 'reviewers': reviewers, + 'action_editor': action_editor, 'tags': [_serialize_tag(rt.tag, language) for rt in response.response_tags] } - - if (event.event_type == EventType.CONTINUOUS_JOURNAL or event.event_type == EventType.JOURNAL): - serialized['action_editor'] = action_editor serialized_responses.append(serialized) return serialized_responses diff --git a/api/app/reviews/api.py b/api/app/reviews/api.py index 5cc231c58..d9af04322 100644 --- a/api/app/reviews/api.py +++ b/api/app/reviews/api.py @@ -445,13 +445,6 @@ class ReviewAssignmentAPI(GetReviewAssignmentMixin, PostReviewAssignmentMixin, r @event_admin_or_action_editor_required @marshal_with(reviews_count_fields) def get(self, event_id): - args = self.get_req_parser.parse_args() - user_id = g.current_user['id'] - - current_user = user_repository.get_by_id(user_id) - if not current_user.is_event_admin(event_id): - return FORBIDDEN - counts = review_repository.count_reviews_allocated_and_completed_per_reviewer(event_id) views = [ReviewCountView(count) for count in counts] return views diff --git a/webapp/src/pages/ResponsePage/ResponsePage.js b/webapp/src/pages/ResponsePage/ResponsePage.js index a5d9b7c62..355a3e9c3 100644 --- a/webapp/src/pages/ResponsePage/ResponsePage.js +++ b/webapp/src/pages/ResponsePage/ResponsePage.js @@ -498,39 +498,37 @@ class ResponsePage extends Component { // Render Reviews renderReviews() { - if (this.state.applicationData.reviewers) { - let num = 0 - const reviews = this.state.applicationData.reviewers.map((val) => { - // {"reviewer_user_id": 4, "user_title": "Mr", "firstname": "Joe", "lastname": "Soap", "status": "completed", "is_action_editor": False}, - if (!val.is_action_editor) { - num = num + 1; - return
    + if (! this.state.applicationData || ! this.state.applicationData.reviewers){ + return
    + } + let num = 0 + const reviews = this.state.applicationData.reviewers.map((val) => { + // {"reviewer_user_id": 4, "user_title": "Mr", "firstname": "Joe", "lastname": "Soap", "status": "completed", "is_action_editor": False}, + if (!val.is_action_editor) { + num = num + 1; + return

    {val.user_title} {val.firstname} {val.lastname}

    - + {val.status === "completed" &&

    {this.props.t("Completed")}

    } {val.status === "started" &&

    {this.props.t("In Progress")}

    } {val.status === "not_started" && -

    - {this.props.t("Not Started")} - -

    +

    + {this.props.t("Not Started")} + +

    }
    } - } - ); - - return reviews - }; - + }); + return reviews; }; // Remove Reviewer diff --git a/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js b/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js index 604418c7d..17ee687ff 100644 --- a/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js +++ b/webapp/src/pages/reviewDashboard/components/ReviewDashboard.js @@ -25,9 +25,7 @@ class ReviewListComponent extends Component { } componentDidMount() { - console.log('got to load', this.props) this.loadReviewList(this.state.mode); - console.log('loaded') } responseIDCell = props => { From b6b4f85dc99ef0ed9afa4b915325e1cdf7826c5f Mon Sep 17 00:00:00 2001 From: Shreya Pandit Date: Tue, 4 Apr 2023 22:09:59 +0200 Subject: [PATCH 6/6] fixed reviewer check --- api/app/responses/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/responses/api.py b/api/app/responses/api.py index 5db274a3e..15d7a794e 100644 --- a/api/app/responses/api.py +++ b/api/app/responses/api.py @@ -319,7 +319,7 @@ def get(self, event_id): action_editor = None for r in response_to_reviewers.get(response.id, []): reviewer = _serialize_reviewer(r, reviewer_to_review_response.get(r.reviewer_user_id, None)) - if reviewer.is_action_editor: + if r.is_action_editor: action_editor = reviewer else: reviewers.append(reviewer)