"""Video auto-approval logic.""" import json from http import HTTPStatus from typing import Any import sentry_sdk from flask import g from jsonschema import validate from spec.jsonschema import jobs as jobs_validation from video.config import AUTO_APPROVE_USER, AUTO_APPROVE_USER_ID from video.constants import ( approval as approval_constants, notifications as notifications_constants, stores as stores_constants, ) from video.exceptions import InvalidRequest, ProductVideoNotFound from video.logic import ( approval as approval_logic, carveouts_python as carveouts_python_logic, job as job_logic, ) from video.models.ows import ( artist as ows_artist, carveouts_python as ows_carveouts_python, notifications as notifications_model, ) from video.models.sql.classes import ( auto_carveout_dms, product_video, release, video_type as video, ) def should_auto_apply_label_mgr_approval(product: dict[str, Any]) -> bool: """Check if special instructions blank and YT/Vevo channel selected.""" special_instructions_blank = ( product["special_instructions"] is None or product["special_instructions"] == "" ) yt_vevo_selected = product["channel_selection"] != "I don't see the channel I want" return bool(special_instructions_blank and yt_vevo_selected) def auto_apply_label_mgr_approval(product_id: int) -> None: """Auto apply label manager release approval.""" # Call logic.approval.change for release column only. approval_logic.change( product_id, AUTO_APPROVE_USER, data={"approval_type": approval_constants.RELEASE_TYPE, "value": True}, ) g.log.info( "Auto-applied release approval (label manager) for product id: {}".format( product_id ) ) def _get_auto_carveout_dms_ids_by_video_type(video_type: str) -> list[int] | None: """Get list of dms_ids by video_type. Returns: retuns list of dms_ids. """ video_product_type = video.get_video_type(video_type) if not video_product_type: return None auto_carveout_dms_results = auto_carveout_dms.get_auto_carveouts_by_video_type_id( video_product_type["video_type_id"] ) if not auto_carveout_dms_results: return None return [ auto_carveout_dms["dms_id"] for auto_carveout_dms in auto_carveout_dms_results ] def auto_apply_video_type_carveouts(product_id: int) -> None: """Auto apply carveouts based on video type.""" # We reload product_video here to capture upc updates from # _update_orchard_created_fields. Without this, product.message['upc'] # would still be None in the case where a user had not input # their own upc product = product_video.get(product_id) if not product: raise ProductVideoNotFound() video_type = product["type_of_video"] customer_master_master_ids = _get_auto_carveout_dms_ids_by_video_type(video_type) if customer_master_master_ids is not None: distribution_type_id = stores_constants.VIDEO_DISTRIBUTION_TYPE_ID updated_by = AUTO_APPROVE_USER_ID dms_restriction_data = [] for customer_master_master_id in customer_master_master_ids: dms_restriction_data.append( { "service_id": customer_master_master_id, "distribution_types": [distribution_type_id], } ) post_data = {"service": dms_restriction_data, "updated_by": updated_by} ows_carveouts_python.save_carveouts(product_id, post_data) g.log.info("Auto-applied video carveouts for product id: {}".format(product_id)) def should_auto_apply_vidops_approval(product_id: int) -> bool: """Check if carved out of iTunes/Apple for vendor or release.""" return carveouts_python_logic.is_itunes_carved_out(product_id) def auto_apply_vidops_approval(product_id: int) -> None: """Auto apply vidops content approval.""" approval_logic.change( product_id, AUTO_APPROVE_USER, data={"approval_type": approval_constants.CONTENT_TYPE, "value": True}, ) g.log.info( "Auto-applied content approval (vid ops) for product id: {}".format(product_id) ) def should_final_approval_be_applied(product_id: int) -> bool: """Check whether a video product can be final auto approved.""" approval_data = approval_logic.get(product_id) return ( approval_data is not None and approval_data.get("release_approved_by") == AUTO_APPROVE_USER and approval_data.get("content_approved_by") == AUTO_APPROVE_USER ) def apply_final_approval( product_id: int, video_product_message: dict[str, Any], release_message: dict[str, Any], ) -> dict[str, Any]: """Apply final auto approval to a video product.""" upc = video_product_message["upc"] isrc = video_product_message["isrc"] approval_logic_data = { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True, "upc": upc, "isrc": isrc, "approval_in_progress": True, } try: approval_logic.change(product_id, AUTO_APPROVE_USER, data=approval_logic_data) except InvalidRequest: g.log.info("failed validation when applying final approval") return release.get(product_id) _create_approval_workflow_job( product_id, upc, video_product_message.get("latest_pipeline_run_id") ) send_final_approval_notification(video_product_message, release_message) g.log.info("Auto-applied final approval for product id: {}".format(product_id)) # Get updated release data after final approval # changes the release_status to in_content return release.get(product_id) def send_final_approval_notification( video_product_message: dict[str, Any], release_message: dict[str, Any], ) -> None: """Send approval notification on final approval via ows-notifications.""" primary_artist_id = video_product_message["primary_artist_id"] artist = ows_artist.get_artist(int(primary_artist_id)) data = { "feed_name": notifications_constants.APPROVAL_FEED_NAME, "feed_id": notification_feed_id(release_message), "payload": { "actor": notifications_constants.APPROVAL_ACTOR, "verb": notifications_constants.APPROVAL_VERB, "object": notifications_constants.APPROVAL_OBJECT, "project_id": release_message.get("project_id"), "product_id": video_product_message.get("product_id"), "video_title": video_product_message.get("video_title"), "upc": video_product_message.get("upc"), "isrc": video_product_message.get("isrc"), "artist_name": artist.get("name"), }, } notification_response = notifications_model.create_notification(data) if notification_response.status_code != HTTPStatus.CREATED: error_log = { "message": f"{notifications_constants.NOTIFICATION_RESPONSE_ERROR_MSG} - {notification_response.content}", "status": notification_response.status_code, } sentry_sdk.capture_message(json.dumps(error_log)) def notification_feed_id(release: dict[str, Any]) -> str: """Create feed id for final approval notification.""" subaccount_id = release.get("subaccount_id") vendor_id = release.get("vendor_id") account_type = "subaccount" if subaccount_id else "vendor" account_id = subaccount_id if subaccount_id else vendor_id return f"{account_type}_{account_id}" def _create_approval_workflow_job( product_id: int, upc: str, latest_pipeline_run_id: int | None ) -> bool: data = { "type": "workflow_approval", "context": {"product_id": product_id, "upc": upc}, "workflow_ingest_job_id": latest_pipeline_run_id, } # kick off approval workflow state machine approval_workflow_response = job_logic.setup_approval_workflow(data) for job in approval_workflow_response: response_schema = jobs_validation.build_response_schema_jobs(job["type"]) validate(job, response_schema) return True