@@ -494,7 +494,7 @@ def get_plan_summary(self, plan: Plan) -> str:
494494
495495 try :
496496 # Clear out any output that might exist from prior steps
497- self ._console .clear_captured_outputs ()
497+ self ._console .consume_captured_output ()
498498 if plan .restatements :
499499 self ._console ._print ("\n **Restating models**\n " )
500500 else :
@@ -522,13 +522,118 @@ def get_plan_summary(self, plan: Plan) -> str:
522522 if not difference_summary and not missing_dates :
523523 return f"No changes to apply.{ plan_flags_section } "
524524
525- return f"{ difference_summary } \n { missing_dates } { plan_flags_section } "
525+ warnings_block = self ._console .captured_warnings
526+ errors_block = self ._console .captured_errors
527+
528+ return f"{ warnings_block } { errors_block } { difference_summary } \n { missing_dates } { plan_flags_section } "
526529 except PlanError as e :
527530 logger .exception ("Plan failed to generate" )
528531 return f"Plan failed to generate. Check for pending or unresolved changes. Error: { e } "
529532 finally :
530533 self ._console .verbosity = orig_verbosity
531534
535+ def get_pr_environment_summary (
536+ self , conclusion : GithubCheckConclusion , exception : t .Optional [Exception ] = None
537+ ) -> str :
538+ heading = ""
539+ summary = ""
540+
541+ if conclusion .is_success :
542+ summary = self ._get_pr_environment_summary_success ()
543+ elif conclusion .is_action_required :
544+ heading = f":warning: Action Required to create or update PR Environment `{ self .pr_environment_name } ` :warning:"
545+ summary = self ._get_pr_environment_summary_action_required (exception )
546+ elif conclusion .is_failure :
547+ heading = (
548+ f":x: Failed to create or update PR Environment `{ self .pr_environment_name } ` :x:"
549+ )
550+ summary = self ._get_pr_environment_summary_failure (exception )
551+ elif conclusion .is_skipped :
552+ heading = f":next_track_button: Skipped creating or updating PR Environment `{ self .pr_environment_name } ` :next_track_button:"
553+ summary = self ._get_pr_environment_summary_skipped (exception )
554+ else :
555+ heading = f":interrobang: Got an unexpected conclusion: { conclusion .value } "
556+
557+ # note: we just add warnings here, errors will be covered by the "failure" conclusion
558+ if warnings := self ._console .consume_captured_warnings ():
559+ summary = f"{ warnings } \n { summary } "
560+
561+ return f"{ heading } \n \n { summary } " .strip ()
562+
563+ def _get_pr_environment_summary_success (self ) -> str :
564+ prod_plan = self .prod_plan_with_gaps
565+
566+ if not prod_plan .has_changes :
567+ summary = "No models were modified in this PR.\n "
568+ else :
569+ intro = self ._generate_pr_environment_summary_intro ()
570+ summary = intro + self ._generate_pr_environment_summary_list (prod_plan )
571+
572+ if prod_plan .user_provided_flags :
573+ summary += self ._generate_plan_flags_section (prod_plan .user_provided_flags )
574+
575+ return summary
576+
577+ def _get_pr_environment_summary_skipped (self , exception : t .Optional [Exception ] = None ) -> str :
578+ if isinstance (exception , NoChangesPlanError ):
579+ skip_reason = "No changes were detected compared to the prod environment."
580+ elif isinstance (exception , TestFailure ):
581+ skip_reason = "Unit Test(s) Failed so skipping PR creation"
582+ else :
583+ skip_reason = "A prior stage failed resulting in skipping PR creation."
584+
585+ return skip_reason
586+
587+ def _get_pr_environment_summary_action_required (
588+ self , exception : t .Optional [Exception ] = None
589+ ) -> str :
590+ plan = self .pr_plan_or_none
591+ if isinstance (exception , UncategorizedPlanError ) and plan :
592+ failure_msg = f"The following models could not be categorized automatically:\n "
593+ for snapshot in plan .uncategorized :
594+ failure_msg += f"- { snapshot .name } \n "
595+ failure_msg += (
596+ f"\n Run `sqlmesh plan { self .pr_environment_name } ` locally to apply these changes.\n \n "
597+ "If you would like the bot to automatically categorize changes, check the [documentation](https://sqlmesh.readthedocs.io/en/stable/integrations/github/) for more information."
598+ )
599+ else :
600+ failure_msg = "Please check the Actions Workflow logs for more information."
601+
602+ return failure_msg
603+
604+ def _get_pr_environment_summary_failure (self , exception : t .Optional [Exception ] = None ) -> str :
605+ if exception :
606+ logger .debug (f"Got { type (exception ).__name__ } . Stack trace: " + traceback .format_exc ())
607+
608+ console_output = self ._console .consume_captured_output ()
609+
610+ if isinstance (exception , PlanError ):
611+ failure_msg = f"Plan application failed.\n "
612+ if exception .args and (msg := exception .args [0 ]) and isinstance (msg , str ):
613+ failure_msg += f"\n { msg } \n "
614+ if console_output :
615+ failure_msg += f"\n { console_output } "
616+ elif isinstance (exception , (SQLMeshError , SqlglotError , ValueError )):
617+ # this logic is taken from the global error handler attached to the CLI, which uses `click.echo()` to output the message
618+ # so cant be re-used here because it bypasses the Console
619+ failure_msg = f"**Error:** { str (exception )} "
620+ else :
621+ logger .debug (
622+ "Got unexpected error. Error Type: "
623+ + str (type (exception ))
624+ + " Stack trace: "
625+ + traceback .format_exc ()
626+ )
627+ failure_msg = f"This is an unexpected error.\n \n **Exception:**\n ```\n { traceback .format_exc ()} \n ```"
628+
629+ if captured_errors := self ._console .consume_captured_errors ():
630+ failure_msg = f"{ captured_errors } \n { failure_msg } "
631+
632+ if plan_flags := self .pr_plan_flags :
633+ failure_msg += f"\n \n { self ._generate_plan_flags_section (plan_flags )} "
634+
635+ return failure_msg
636+
532637 def run_tests (self ) -> t .Tuple [ModelTextTestResult , str ]:
533638 """
534639 Run tests for the PR
@@ -539,7 +644,7 @@ def run_linter(self) -> None:
539644 """
540645 Run linter for the PR
541646 """
542- self ._console .clear_captured_outputs ()
647+ self ._console .consume_captured_output ()
543648 self ._context .lint_models ()
544649
545650 def _get_or_create_comment (self , header : str = BOT_HEADER_MSG ) -> IssueComment :
@@ -611,7 +716,22 @@ def update_pr_environment(self) -> None:
611716 Creates a PR environment from the logic present in the PR. If the PR contains changes that are
612717 uncategorized, then an error will be raised.
613718 """
614- self ._context .apply (self .pr_plan )
719+ self ._context .apply (self .pr_plan ) # will raise if PR environment creation fails
720+
721+ # update PR info comment
722+ vde_title = "- :eyes: To **review** this PR's changes, use virtual data environment:"
723+ comment_value = f"{ vde_title } \n - `{ self .pr_environment_name } `"
724+ if self .bot_config .enable_deploy_command :
725+ comment_value += (
726+ "\n - :arrow_forward: To **apply** this PR's plan to prod, comment:\n - `/deploy`"
727+ )
728+ dedup_regex = vde_title .replace ("*" , r"\*" ) + r".*"
729+ updated_comment , _ = self .update_sqlmesh_comment_info (
730+ value = comment_value ,
731+ dedup_regex = dedup_regex ,
732+ )
733+ if updated_comment :
734+ self ._append_output ("created_pr_environment" , "true" )
615735
616736 def deploy_to_prod (self ) -> None :
617737 """
@@ -855,13 +975,7 @@ def conclusion_handler(
855975 )
856976
857977 def update_pr_environment_check (
858- self ,
859- status : GithubCheckStatus ,
860- exception : t .Optional [Exception ] = None ,
861- plan : t .Optional [Plan ] = None ,
862- plan_flags : t .Optional [
863- t .Dict [str , UserProvidedFlags ]
864- ] = None , # note: the plan flags are passed separately in case the plan fails to build
978+ self , status : GithubCheckStatus , exception : t .Optional [Exception ] = None
865979 ) -> t .Optional [GithubCheckConclusion ]:
866980 """
867981 Updates the status of the merge commit for the PR environment.
@@ -882,87 +996,7 @@ def update_pr_environment_check(
882996 def conclusion_handler (
883997 conclusion : GithubCheckConclusion , exception : t .Optional [Exception ]
884998 ) -> t .Tuple [GithubCheckConclusion , str , t .Optional [str ]]:
885- if conclusion .is_success :
886- prod_plan = self .prod_plan_with_gaps
887-
888- if not prod_plan .has_changes :
889- summary = "No models were modified in this PR.\n "
890- else :
891- intro = self ._generate_pr_environment_summary_intro ()
892- summary = intro + self ._generate_pr_environment_summary_list (prod_plan )
893- if prod_plan .user_provided_flags :
894- summary += self ._generate_plan_flags_section (prod_plan .user_provided_flags )
895-
896- vde_title = (
897- "- :eyes: To **review** this PR's changes, use virtual data environment:"
898- )
899- comment_value = f"{ vde_title } \n - `{ self .pr_environment_name } `"
900- if self .bot_config .enable_deploy_command :
901- comment_value += "\n - :arrow_forward: To **apply** this PR's plan to prod, comment:\n - `/deploy`"
902- dedup_regex = vde_title .replace ("*" , r"\*" ) + r".*"
903- updated_comment , _ = self .update_sqlmesh_comment_info (
904- value = comment_value ,
905- dedup_regex = dedup_regex ,
906- )
907- if updated_comment :
908- self ._append_output ("created_pr_environment" , "true" )
909- else :
910- if isinstance (exception , NoChangesPlanError ):
911- skip_reason = "No changes were detected compared to the prod environment."
912- elif isinstance (exception , TestFailure ):
913- skip_reason = "Unit Test(s) Failed so skipping PR creation"
914- else :
915- skip_reason = "A prior stage failed resulting in skipping PR creation."
916-
917- if not skip_reason and exception :
918- logger .debug (
919- f"Got { type (exception ).__name__ } . Stack trace: " + traceback .format_exc ()
920- )
921-
922- captured_errors = self ._console .consume_captured_errors ()
923- if captured_errors :
924- logger .debug (f"Captured errors: { captured_errors } " )
925- failure_msg = f"**Errors:**\n { captured_errors } \n "
926- elif isinstance (exception , UncategorizedPlanError ) and plan :
927- failure_msg = f"The following models could not be categorized automatically:\n "
928- for snapshot in plan .uncategorized :
929- failure_msg += f"- { snapshot .name } \n "
930- failure_msg += (
931- f"\n Run `sqlmesh plan { self .pr_environment_name } ` locally to apply these changes.\n \n "
932- "If you would like the bot to automatically categorize changes, check the [documentation](https://sqlmesh.readthedocs.io/en/stable/integrations/github/) for more information."
933- )
934- elif isinstance (exception , PlanError ):
935- failure_msg = f"Plan application failed.\n "
936- if exception .args and (msg := exception .args [0 ]) and isinstance (msg , str ):
937- failure_msg += f"\n { msg } \n "
938- if self ._console .captured_output :
939- failure_msg += f"\n { self ._console .captured_output } "
940- elif isinstance (exception , (SQLMeshError , SqlglotError , ValueError )):
941- # this logic is taken from the global error handler attached to the CLI, which uses `click.echo()` to output the message
942- # so cant be re-used here because it bypasses the Console
943- failure_msg = f"**Error:** { str (exception )} "
944- else :
945- logger .debug (
946- "Got unexpected error. Error Type: "
947- + str (type (exception ))
948- + " Stack trace: "
949- + traceback .format_exc ()
950- )
951- failure_msg = f"This is an unexpected error.\n \n **Exception:**\n ```\n { traceback .format_exc ()} \n ```"
952-
953- conclusion_to_summary = {
954- GithubCheckConclusion .SKIPPED : f":next_track_button: Skipped creating or updating PR Environment `{ self .pr_environment_name } `. { skip_reason } " ,
955- GithubCheckConclusion .FAILURE : f":x: Failed to create or update PR Environment `{ self .pr_environment_name } ` :x:\n \n { failure_msg } " ,
956- GithubCheckConclusion .CANCELLED : f":stop_sign: Cancelled creating or updating PR Environment `{ self .pr_environment_name } `" ,
957- GithubCheckConclusion .ACTION_REQUIRED : f":warning: Action Required to create or update PR Environment `{ self .pr_environment_name } ` :warning:\n \n { failure_msg } " ,
958- }
959- summary = conclusion_to_summary .get (
960- conclusion , f":interrobang: Got an unexpected conclusion: { conclusion .value } "
961- )
962- if plan_flags :
963- plan_flags_section = self ._generate_plan_flags_section (plan_flags )
964- summary += f"\n \n { plan_flags_section } "
965-
999+ summary = self .get_pr_environment_summary (conclusion , exception )
9661000 self ._append_output ("pr_environment_name" , self .pr_environment_name )
9671001 return conclusion , check_title , summary
9681002
0 commit comments