"""Logic for collecting items from Review Queue.""" from owsresponse import error as owsresponse_error from owsresponse import response from owsresponse import status from owsresponse import status as owsresponse_status from product_review.api import db from product_review.models import graphql_router as graphql_router_model from product_review.constants.notification import ACCEPTED_VENDOR_OWNERS_APPROVAL from product_review.constants.notification import ACCEPTED_COMPANY_BRANDS_APPROVAL from product_review.constants import brand from product_review.models import move_target as move_target_model from product_review.models import ows_account as ows_account_model from product_review.models import ows_notifications from product_review.models import ows_product as ows_product_model from product_review.models import ows_product_workflow from product_review.models import ows_users as ows_users_model from product_review.models import queue_move_history as queue_move_history_model from product_review.models import review_queue as review_queue_model from product_review.models import splitio as splitio_model from product_review.models import target_group as target_group_model from product_review.util import exception as exception_util from product_review.util.db import db_transaction from product_review.util.event import publish_queue_move_event from product_review.util.review_queue_item import get_review_queue_item_for_update def get_review_queue_items( status=None, page_offset=None, page_limit=None, review_queue_id=None, product_id=None, ): """Return list of review queue items.""" item_status = status or review_queue_model.DEFAULT_STATUS results, total_records = review_queue_model.get_items( item_status, page_offset=page_offset, page_limit=page_limit, review_queue_id=review_queue_id, product_id=product_id, ) response_msg = _format_review_items(results, total_records, page_offset, page_limit) return response.Response(response_msg) def get_review_queue_items_by_id(review_queue_ids): """Return review queue items by review queue IDs.""" response_msg = review_queue_model.get_review_queue_items_by_id(review_queue_ids) return response.Response(response_msg) def get_target_group_by_item_id(review_queue_ids): """Return review queue items by review queue IDs.""" response_msg = review_queue_model.get_target_group_by_item_id(review_queue_ids) return response.Response(response_msg) def get_product_review_history(product_id, page_offset=None, page_limit=None): """Get review history for a give product id.""" results, total_records = review_queue_model.review_history( page_offset=page_offset, page_limit=page_limit, product_id=product_id, current_item=True ) response_msg = _format_review_items( results, total_records, page_offset, page_limit, include_context=True, include_history=True ) return response.Response(response_msg) @db_transaction def lock(review_queue_id): """Lock a review queue item.""" return review_queue_model.lock(review_queue_id) @db_transaction def unlock(review_queue_id): """Unlock a review queue item.""" return review_queue_model.unlock(review_queue_id) def get_oa_user(user_id): """Get OA user.""" return ows_users_model.get_orchadmin_user(f"oa:{user_id}") def get_move_target(moved_to_target_id, product_id): """Get move target from id.""" if moved_to_target_id is None: return {} move_target_item = move_target_model.get_move_target(moved_to_target_id) if move_target_item is None: exception_util.raise_exception_for_ows_response( status=owsresponse_status.NOT_FOUND, code=owsresponse_error.ERROR_CODE_NOT_FOUND, message=f"No records for moved_to_target_id {moved_to_target_id}", ) # handle Label Manager and Product Manager if move_target_item.get("target_group_id") == 1: product = ows_product_model.get_product(product_id) vendor_id = product["vendor_id"] escalate_to_pm_ff = splitio_model.is_enabled_escalate_to_product_manager() orchadmin_user = None # handle Product Manager if orchard brand if escalate_to_pm_ff: vendor_product_manager = None vendor_company_brand = graphql_router_model.get_vendor_company_brand( vendor_id ) brand_name = ( vendor_company_brand.get("name") if vendor_company_brand else None ) if brand_name == brand.ORCHARD_BRAND_NAME: vendor_product_manager = ( graphql_router_model.get_vendor_product_manager( vendor_id ) ) if vendor_product_manager: orchadmin_user = get_oa_user(vendor_product_manager) # handle Label Manager if orchadmin_user is None: vendor_assigned_to = ows_account_model.get_vendor_assigned_to(vendor_id) orchadmin_user = get_oa_user(vendor_assigned_to.get("id")) f_name = orchadmin_user.get("first_name").strip() l_name = orchadmin_user.get("last_name").strip() orchadmin_user_id = int(orchadmin_user.get("user_id").replace("oa:", "")) move_target_item["display_name"] = f_name + " " + l_name move_target_item["orchadmin_user_id"] = orchadmin_user_id move_target_item["email"] = orchadmin_user.get("email") # handle QC Support elif move_target_item.get("target_group_id") == 2: orchadmin_user = get_oa_user(move_target_item.get("orchadmin_user_id")) f_name = orchadmin_user.get("first_name").strip() l_name = orchadmin_user.get("last_name").strip() move_target_item["display_name"] = f_name + " " + l_name move_target_item["email"] = orchadmin_user.get("email") # handle Assigned Reviewer elif move_target_item.get("target_group_id") == 3: product = ows_product_model.get_product(product_id) vendor_id = product["vendor_id"] assigned_reviewer = ows_account_model.get_vendor_assigned_reviewer(vendor_id) f_name = assigned_reviewer.get("f_name").strip() l_name = assigned_reviewer.get("l_name").strip() move_target_item["display_name"] = f_name + " " + l_name move_target_item["orchadmin_user_id"] = assigned_reviewer.get("id") move_target_item["email"] = assigned_reviewer.get("email") else: exception_util.raise_exception_for_ows_response( status=owsresponse_status.NOT_FOUND, code=owsresponse_error.ERROR_CODE_NOT_FOUND, message=f"Target group not found for moved_to_target_id {moved_to_target_id}", # noqa: E501 ) return move_target_item def move_review_queue_item_to_queue( *, review_queue_id, queue_name, move_note=None, moved_to_target_id=None ): """Move a review queue item to a different queue.""" with db_transaction: review_queue_item = get_review_queue_item_for_update(review_queue_id) previous_queue_name = review_queue_item.queue_name if queue_name != "escalation" and review_queue_item.queue_name == queue_name: return { "message": f"Review queue item already in {queue_name} queue", } move_target_item = get_move_target( moved_to_target_id, review_queue_item.product_id ) if moved_to_target_id is not None: # check to see if the product is being moved to the same escalation group if ( review_queue_item.moved_to_target_id == move_target_item["moved_to_target_id"] ): return { "message": ( f"Review queue item already assigned to " f"{move_target_item['display_name']}" ), } updated_review_queue_item = review_queue_model.move_review_queue_item_to_queue( review_queue_id=review_queue_id, queue_name=queue_name, move_note=move_note, moved_to_target_id=moved_to_target_id, moved_to_orchadmin_user_id=move_target_item.get("orchadmin_user_id"), moved_to_target_email=move_target_item.get("email"), moved_to_target_name=move_target_item.get("display_name"), ) queue_move_history_model.add_queue_move( review_queue_id=review_queue_id, queue_name=queue_name, previous_queue_name=previous_queue_name, move_note=move_note, moved_to_target_id=moved_to_target_id, moved_to_orchadmin_user_id=move_target_item.get("orchadmin_user_id"), moved_to_target_email=move_target_item.get("email"), moved_to_target_name=move_target_item.get("display_name"), ) publish_queue_move_event( product_id=review_queue_item.product_id, review_queue_id=review_queue_id, queue_name=queue_name, ) return updated_review_queue_item def get_queue_move_history(review_queue_id): """Get queue move history for a given review queue item.""" queue_move_history = queue_move_history_model.get_queue_move_history( review_queue_id=review_queue_id ) return { "items": queue_move_history, } def get_error_correction_id(product_id): """Get the submitted error correction id for a product if it exists.""" product_response = ows_product_model.get_product(product_id) if product_response.get("status") != "in_content": return None revisions = ows_product_workflow.get_submitted_revisions(product_id) if not revisions: return None return revisions.get("release_correction_id") def get_submission_type(history_count, review_history, error_correction_id): """Get the submission_type for a product.""" if error_correction_id: return "revision" if history_count and review_history[0].rejection: return "re_submission" return "new" def get_submission_count(submission_type, review_history, error_correction_id): """Get submission count.""" count = 1 if submission_type == "re_submission": for review_queue_item in review_history: if review_queue_item.rejection: count += 1 elif submission_type == "revision": count = 1 for review_queue_item in review_history: if review_queue_item.error_correction_id != error_correction_id: break if review_queue_item.rejection: count += 1 return count def insert_review_queue_item( product_id, queue_name, identity_id=None, submission_datetime=None): """Insert a product into the review queue.""" _, item_count = review_queue_model.get_items( product_id=product_id, status=( review_queue_model.DEFAULT_STATUS, review_queue_model.STATUS.APPLYING, ), ) if item_count: exception_util.raise_exception_for_ows_response( status=owsresponse_status.CONFLICT, code="exists", message=f"Product {product_id} already exists in queue", ) error_correction_id = get_error_correction_id(product_id) review_history, count = review_queue_model.review_history(product_id, 250, 0) submission_type = get_submission_type(count, review_history, error_correction_id) submission_count = get_submission_count( submission_type, review_history, error_correction_id ) with db_transaction: new_item = review_queue_model.ReviewQueue( product_id=product_id, status=review_queue_model.DEFAULT_STATUS, error_correction_id=error_correction_id, submission_type=submission_type, submission_count=submission_count, queue_name=queue_name, submitted_by_user_id=identity_id, submission_datetime=submission_datetime, ) db.session.add(new_item) return response.Response(new_item.to_dict(), status=owsresponse_status.CREATED) def _format_review_items( results, total_records, offset, limit, include_context=False, include_history=False ): if not results: output_results = [] else: output_results = [ r.to_dict( include_context=include_context, include_history=include_history ) for r in results ] return { "items": output_results, "pagination": { "type": "standard", "offset": int(offset or 0), "limit": int(limit or review_queue_model.DEFAULT_PAGE_LIMIT), "total_records": total_records, }, } def get_status(product_id): """Get rejections for a given product from the database.""" item = review_queue_model.ReviewQueue.latest_review_by_product(product_id) if not item: return response.create_not_found_response( f"No records for product {product_id}" ) return response.Response(format_product_status_response(item)) def format_product_status_response(item): """Format product status response. Mimics ows-project-manager's product status endpoint's structure. Use by legacy review and error correction. See persister.py:_get_release_status_for_product for current impl """ result = { "id": item.product_id, "ec_and_ar": False, "release_status": None, "release_approval_status": None, "last_updated": None, } if item.status == review_queue_model.STATUS.NEW: result.update( { "release_status": "transfer_to_content", "release_approval_status": "checked_in", "last_updated": str(item.created_datetime), } ) elif item.status == review_queue_model.STATUS.APPLYING: result.update( { "release_status": "transfer_to_content", "release_approval_status": "checked_out", "last_updated": str(item.created_datetime), } ) elif item.status == review_queue_model.STATUS.COMPLETE: if item.approval and item.approval.approval_id: result.update( { "release_status": "in_content", "release_approval_status": "approved", "last_updated": str(item.approval.created_datetime), } ) elif item.rejection and item.rejection.rejection_id: if item.rejection.will_not_deliver: result.update( { "release_status": "transfer_to_content", "release_approval_status": "checked_out", "last_updated": str(item.rejection.created_datetime), } ) else: result.update( { "release_status": "action_required", "release_approval_status": "rejected", "last_updated": str(item.rejection.created_datetime), "note": item.rejection.note, } ) else: result.update({"release_status": "label_processing"}) return result def get_all_target_groups(): """Get all target groups and associated move target items.""" result = [] target_groups = target_group_model.get_items() for target_group in target_groups: move_target_items = move_target_model.get_items( target_group_id=target_group["target_group_id"] ) result.append({**target_group, "targets": move_target_items}) return result def send_email_notification(review_id): """Send email for completed review id.""" results, total_records = review_queue_model.get_items( review_queue_id=review_id, status="complete" ) if total_records == 0: return response.create_error_response( status=owsresponse_status.NOT_FOUND, code=owsresponse_error.ERROR_CODE_NOT_FOUND, message="Review {} not found.".format(review_id), ) review = results[0].to_dict(include_context=True) if review["approval"] is not None: product = ows_product_model.get_product(review["product_id"]) vendor = ows_account_model.get_vendor(product["vendor_id"]) if not ( vendor["owner"] in ACCEPTED_VENDOR_OWNERS_APPROVAL or vendor["company_brand"] in ACCEPTED_COMPANY_BRANDS_APPROVAL ): return response.Response( status=status.OK, message={ "notification_type": "approval", "profiles": [], }, ) # send approval email here return ows_notifications.send_approval_email( review["product_id"], ) if review["rejection"] is not None: if review["rejection"]["will_not_deliver"] is True: return response.Response( status=status.OK, message={"will_not_deliver": True} ) else: return ows_notifications.send_rejection_email( review["product_id"], review["rejection"]["note"] ) return response.create_error_response( status=owsresponse_status.BAD_REQUEST, code="bad_request", message="Review {} is missing data.".format(review_id), )