Repository: TobikoData/sqlmesh
Pull request #2643 (2024-05-21 16:39:57)

feat: Github CI/CD Bot Allow Overriding PR Environment Name
Prior to this the PR name was always the repo name normalized + pr number.

This allows users to set their own PR name. The provided name will be normalized (which you can verify in the tests). 


Patch:
diff --git a/docs/integrations/github.md b/docs/integrations/github.md
index 8e24cc59..7cf0c8a1 100644
--- a/docs/integrations/github.md
+++ b/docs/integrations/github.md
@@ -284,17 +284,18 @@ Below is an example of how to define the default config for the bot in either YA
 
 ### Configuration Properties
 
-| Option                                | Description                                                                                                                                                                                                                                                                                          | Type   | Required |
-|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------:|:--------:|
-| `invalidate_environment_after_deploy` | Indicates if the PR environment created should be automatically invalidated after changes are deployed. Invalidated environments are cleaned up automatically by the Janitor. Default: `True`                                                                                                        | bool   | N        |
-| `merge_method`                        | The merge method to use when automatically merging a PR after deploying to prod. Defaults to `None` meaning automatic merge is not done. Options: `merge`, `squash`, `rebase`                                                                                                                        | string | N        |
-| `enable_deploy_command`               | Indicates if the `/deploy` command should be enabled in order to allowed synchronized deploys to production. Default: `False`                                                                                                                                                                        | bool   | N        |
-| `command_namespace`                   | The namespace to use for SQLMesh commands. For example if you provide `#SQLMesh` as a value then commands will be expected in the format of `#SQLMesh/<command>`. Default: `None` meaning no namespace is used.                                                                                      | string | N        |
-| `auto_categorize_changes`             | Auto categorization behavior to use for the bot. If not provided then the project-wide categorization behavior is used. See [Auto-categorize model changes](https://sqlmesh.readthedocs.io/en/stable/guides/configuration/#auto-categorize-model-changes) for details.                               | dict   | N        |
-| `default_pr_start`                    | Default start when creating PR environment plans. If running in a mode where the bot automatically backfills models (based on `auto_categorize_changes` behavior) then this can be used to limit the amount of data backfilled. Defaults to `None` meaning the start date is set to the earliest model's start or to 1 day ago if [data previews](../concepts/plans.md#data-preview) need to be computed. | str    | N        |
-| `skip_pr_backfill`                    | Indicates if the bot should skip backfilling models in the PR environment. Default: `True`                                                                                                                                                                                                           | bool   | N        |
-| `pr_include_unmodified`               | Indicates whether to include unmodified models in the PR environment. Default to the project's config value (which defaults to `False`)                                                                                                                                                              | bool   | N        |
-| `run_on_deploy_to_prod`               | Indicates whether to run latest intervals when deploying to prod. If set to false, the deployment will backfill only the changed models up to the existing latest interval in production, ignoring any missing intervals beyond this point. Default: `True`                                          | bool   | N        |
+| Option                                | Description                                                                                                                                                                                                                                                                                                                                                                                               |  Type  | Required |
+|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------:|:--------:|
+| `invalidate_environment_after_deploy` | Indicates if the PR environment created should be automatically invalidated after changes are deployed. Invalidated environments are cleaned up automatically by the Janitor. Default: `True`                                                                                                                                                                                                             |  bool  |    N     |
+| `merge_method`                        | The merge method to use when automatically merging a PR after deploying to prod. Defaults to `None` meaning automatic merge is not done. Options: `merge`, `squash`, `rebase`                                                                                                                                                                                                                             | string |    N     |
+| `enable_deploy_command`               | Indicates if the `/deploy` command should be enabled in order to allowed synchronized deploys to production. Default: `False`                                                                                                                                                                                                                                                                             |  bool  |    N     |
+| `command_namespace`                   | The namespace to use for SQLMesh commands. For example if you provide `#SQLMesh` as a value then commands will be expected in the format of `#SQLMesh/<command>`. Default: `None` meaning no namespace is used.                                                                                                                                                                                           | string |    N     |
+| `auto_categorize_changes`             | Auto categorization behavior to use for the bot. If not provided then the project-wide categorization behavior is used. See [Auto-categorize model changes](https://sqlmesh.readthedocs.io/en/stable/guides/configuration/#auto-categorize-model-changes) for details.                                                                                                                                    |  dict  |    N     |
+| `default_pr_start`                    | Default start when creating PR environment plans. If running in a mode where the bot automatically backfills models (based on `auto_categorize_changes` behavior) then this can be used to limit the amount of data backfilled. Defaults to `None` meaning the start date is set to the earliest model's start or to 1 day ago if [data previews](../concepts/plans.md#data-preview) need to be computed. |  str   |    N     |
+| `skip_pr_backfill`                    | Indicates if the bot should skip backfilling models in the PR environment. Default: `True`                                                                                                                                                                                                                                                                                                                |  bool  |    N     |
+| `pr_include_unmodified`               | Indicates whether to include unmodified models in the PR environment. Default to the project's config value (which defaults to `False`)                                                                                                                                                                                                                                                                   |  bool  |    N     |
+| `run_on_deploy_to_prod`               | Indicates whether to run latest intervals when deploying to prod. If set to false, the deployment will backfill only the changed models up to the existing latest interval in production, ignoring any missing intervals beyond this point. Default: `True`                                                                                                                                               |  bool  |    N     |
+| `pr_environment_name`                 | The name of the PR environment to create for which a PR number will be appended to. Defaults to the repo name if not provided. Note: The name will be normalized to alphanumeric + underscore and lowercase.                                                                                                                                                                                              |  str   |    N     |	
 
 Example with all properties defined:
 
diff --git a/sqlmesh/integrations/github/cicd/config.py b/sqlmesh/integrations/github/cicd/config.py
index ea5acd87..95b090af 100644
--- a/sqlmesh/integrations/github/cicd/config.py
+++ b/sqlmesh/integrations/github/cicd/config.py
@@ -33,6 +33,7 @@ class GithubCICDBotConfig(BaseConfig):
     skip_pr_backfill: bool = True
     pr_include_unmodified: t.Optional[bool] = None
     run_on_deploy_to_prod: bool = True
+    pr_environment_name: t.Optional[str] = None
 
     @model_validator(mode="before")
     @model_validator_v1_args
diff --git a/sqlmesh/integrations/github/cicd/controller.py b/sqlmesh/integrations/github/cicd/controller.py
index d4c6e6f5..3ae15051 100644
--- a/sqlmesh/integrations/github/cicd/controller.py
+++ b/sqlmesh/integrations/github/cicd/controller.py
@@ -360,7 +360,7 @@ class GithubController:
         return Environment.normalize_name(
             "_".join(
                 [
-                    self._event.pull_request_info.repo,
+                    self.bot_config.pr_environment_name or self._event.pull_request_info.repo,
                     str(self._event.pull_request_info.pr_number),
                 ]
             )


Test patch:
diff --git a/tests/integrations/github/cicd/test_config.py b/tests/integrations/github/cicd/test_config.py
index fffd85eb..394015c7 100644
--- a/tests/integrations/github/cicd/test_config.py
+++ b/tests/integrations/github/cicd/test_config.py
@@ -38,6 +38,7 @@ model_defaults:
     assert not config.cicd_bot.enable_deploy_command
     assert config.cicd_bot.skip_pr_backfill
     assert config.cicd_bot.pr_include_unmodified is None
+    assert config.cicd_bot.pr_environment_name is None
 
 
 def test_load_yaml_config(tmp_path):
@@ -59,6 +60,7 @@ cicd_bot:
     enable_deploy_command: true
     skip_pr_backfill: false
     pr_include_unmodified: true
+    pr_environment_name: "MyOverride"
 model_defaults:
     dialect: duckdb
 """,
@@ -81,6 +83,7 @@ model_defaults:
     assert config.cicd_bot.enable_deploy_command
     assert not config.cicd_bot.skip_pr_backfill
     assert config.cicd_bot.pr_include_unmodified
+    assert config.cicd_bot.pr_environment_name == "MyOverride"
 
 
 def test_load_python_config_defaults(tmp_path):
@@ -110,6 +113,7 @@ config = Config(
     assert not config.cicd_bot.enable_deploy_command
     assert config.cicd_bot.skip_pr_backfill
     assert config.cicd_bot.pr_include_unmodified is None
+    assert config.cicd_bot.pr_environment_name is None
 
 
 def test_load_python_config(tmp_path):
@@ -135,6 +139,7 @@ config = Config(
         enable_deploy_command=True,
         skip_pr_backfill=False,
         pr_include_unmodified=True,
+        pr_environment_name="MyOverride",
     ),
     model_defaults=ModelDefaultsConfig(dialect="duckdb"),
 )
@@ -159,6 +164,7 @@ config = Config(
     assert config.cicd_bot.enable_deploy_command
     assert not config.cicd_bot.skip_pr_backfill
     assert config.cicd_bot.pr_include_unmodified
+    assert config.cicd_bot.pr_environment_name == "MyOverride"
 
 
 def test_validation(tmp_path):
diff --git a/tests/integrations/github/cicd/test_github_commands.py b/tests/integrations/github/cicd/test_github_commands.py
index 1d480adb..940fe32e 100644
--- a/tests/integrations/github/cicd/test_github_commands.py
+++ b/tests/integrations/github/cicd/test_github_commands.py
@@ -66,7 +66,9 @@ def test_run_all_success_with_approvers_approved(
     controller = make_controller(
         "tests/fixtures/github/pull_request_synchronized.json",
         github_client,
-        bot_config=GithubCICDBotConfig(invalidate_environment_after_deploy=False),
+        bot_config=GithubCICDBotConfig(
+            invalidate_environment_after_deploy=False, pr_environment_name="MyOverride"
+        ),
     )
     controller._context._run_tests = mocker.MagicMock(
         side_effect=lambda **kwargs: (TestResult(), "")
@@ -127,7 +129,7 @@ def test_run_all_success_with_approvers_approved(
 
     assert len(controller._context.apply.call_args_list) == 2
     pr_plan = controller._context.apply.call_args_list[0][0]
-    assert pr_plan[0].environment.name == "hello_world_2"
+    assert pr_plan[0].environment.name == "myoverride_2"
     prod_plan = controller._context.apply.call_args_list[1][0]
     assert prod_plan[0].environment.name == c.PROD
 
@@ -137,7 +139,7 @@ def test_run_all_success_with_approvers_approved(
     assert len(created_comments) == 1
     assert created_comments[0].body.startswith(
         """**SQLMesh Bot Info**
-- PR Virtual Data Environment: hello_world_2
+- PR Virtual Data Environment: myoverride_2
 <details>
   <summary>Prod Plan Being Applied</summary>
 
@@ -147,7 +149,7 @@ def test_run_all_success_with_approvers_approved(
         output = f.read()
         assert (
             output
-            == "run_unit_tests=success\nhas_required_approval=success\ncreated_pr_environment=true\npr_environment_name=hello_world_2\npr_environment_synced=success\nprod_plan_preview=success\nprod_environment_synced=success\n"
+            == "run_unit_tests=success\nhas_required_approval=success\ncreated_pr_environment=true\npr_environment_name=myoverride_2\npr_environment_synced=success\nprod_plan_preview=success\nprod_environment_synced=success\n"
         )