Browse Source

fix(jira): Fall back to other projects if project was deleted (#34669)

* fix(jira): Fall back to other projects if project was deleted
Colleen O'Rourke 2 years ago
parent
commit
51a4a736ba

+ 10 - 7
src/sentry/integrations/jira/integration.py

@@ -506,14 +506,17 @@ class JiraIntegration(IntegrationInstallation, IssueSyncMixin):
         return issue_type_meta
 
     def get_issue_create_meta(self, client, project_id, jira_projects):
+        meta = None
         if project_id:
-            return self.fetch_issue_create_meta(client, project_id)
-
-        # If we don't have a jira projectid, iterate all projects and find
-        # the first project that has metadata. We only want one project as getting
-        # all project metadata is expensive and wasteful. In the first run experience,
-        # the user won't have a 'last used' project id so we need to iterate available
-        # projects until we find one that we can get metadata for.
+            meta = self.fetch_issue_create_meta(client, project_id)
+        if meta is not None:
+            return meta
+
+        # If we don't have a jira projectid (or we couldn't fetch the metadata from the given project_id),
+        # iterate all projects and find the first project that has metadata.
+        # We only want one project as getting all project metadata is expensive and wasteful.
+        # In the first run experience, the user won't have a 'last used' project id
+        # so we need to iterate available projects until we find one that we can get metadata for.
         attempts = 0
         if len(jira_projects):
             for fallback in jira_projects:

+ 651 - 0
tests/fixtures/integrations/jira/stubs/fetch_issue_create_meta.json

@@ -0,0 +1,651 @@
+{
+   "expand":"issuetypes",
+   "self":"https://beepboopimatest.atlassian.net/rest/api/2/project/10001",
+   "id":"10001",
+   "key":"CO2",
+   "name":"Colleen-2",
+   "avatarUrls":{
+      "48x48":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410",
+      "24x24":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=small",
+      "16x16":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=xsmall",
+      "32x32":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=medium"
+   },
+   "issuetypes":[
+      {
+         "self":"https://beepboopimatest.atlassian.net/rest/api/2/issuetype/10000",
+         "id":"10000",
+         "description":"A big user story that needs to be broken down. Created by Jira Software - do not edit or delete.",
+         "iconUrl":"https://beepboopimatest.atlassian.net/images/icons/issuetypes/epic.svg",
+         "name":"Epic",
+         "untranslatedName":"Epic",
+         "subtask":false,
+         "expand":"fields",
+         "fields":{
+            "summary":{
+               "required":true,
+               "schema":{
+                  "type":"string",
+                  "system":"summary"
+               },
+               "name":"Summary",
+               "key":"summary",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "issuetype":{
+               "required":true,
+               "schema":{
+                  "type":"issuetype",
+                  "system":"issuetype"
+               },
+               "name":"Issue Type",
+               "key":"issuetype",
+               "hasDefaultValue":false,
+               "operations":[
+                  
+               ],
+               "allowedValues":[
+                  {
+                     "self":"https://beepboopimatest.atlassian.net/rest/api/2/issuetype/10000",
+                     "id":"10000",
+                     "description":"A big user story that needs to be broken down. Created by Jira Software - do not edit or delete.",
+                     "iconUrl":"https://beepboopimatest.atlassian.net/images/icons/issuetypes/epic.svg",
+                     "name":"Epic",
+                     "subtask":false,
+                     "hierarchyLevel":1
+                  }
+               ]
+            },
+            "components":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"component",
+                  "system":"components"
+               },
+               "name":"Components",
+               "key":"components",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add",
+                  "set",
+                  "remove"
+               ],
+               "allowedValues":[
+                  
+               ]
+            },
+            "description":{
+               "required":false,
+               "schema":{
+                  "type":"string",
+                  "system":"description"
+               },
+               "name":"Description",
+               "key":"description",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "project":{
+               "required":true,
+               "schema":{
+                  "type":"project",
+                  "system":"project"
+               },
+               "name":"Project",
+               "key":"project",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ],
+               "allowedValues":[
+                  {
+                     "self":"https://beepboopimatest.atlassian.net/rest/api/2/project/10001",
+                     "id":"10001",
+                     "key":"CO2",
+                     "name":"Colleen-2",
+                     "projectTypeKey":"software",
+                     "simplified":false,
+                     "avatarUrls":{
+                        "48x48":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410",
+                        "24x24":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=small",
+                        "16x16":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=xsmall",
+                        "32x32":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=medium"
+                     }
+                  }
+               ]
+            },
+            "reporter":{
+               "required":true,
+               "schema":{
+                  "type":"user",
+                  "system":"reporter"
+               },
+               "name":"Reporter",
+               "key":"reporter",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/user/search?query=",
+               "hasDefaultValue":true,
+               "operations":[
+                  "set"
+               ]
+            },
+            "customfield_10011":{
+               "required":true,
+               "schema":{
+                  "type":"string",
+                  "custom":"com.pyxis.greenhopper.jira:gh-epic-label",
+                  "customId":10011
+               },
+               "name":"Epic Name",
+               "key":"customfield_10011",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "customfield_10014":{
+               "required":false,
+               "schema":{
+                  "type":"any",
+                  "custom":"com.pyxis.greenhopper.jira:gh-epic-link",
+                  "customId":10014
+               },
+               "name":"Epic Link",
+               "key":"customfield_10014",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "labels":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"string",
+                  "system":"labels"
+               },
+               "name":"Labels",
+               "key":"labels",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/1.0/labels/suggest?query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add",
+                  "set",
+                  "remove"
+               ]
+            },
+            "attachment":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"attachment",
+                  "system":"attachment"
+               },
+               "name":"Attachment",
+               "key":"attachment",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "issuelinks":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"issuelinks",
+                  "system":"issuelinks"
+               },
+               "name":"Linked Issues",
+               "key":"issuelinks",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/issue/picker?currentProjectId=&showSubTaskParent=true&showSubTasks=true&currentIssueKey=null&query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add"
+               ]
+            },
+            "assignee":{
+               "required":false,
+               "schema":{
+                  "type":"user",
+                  "system":"assignee"
+               },
+               "name":"Assignee",
+               "key":"assignee",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/user/assignable/search?project=CO2&query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            }
+         }
+      },
+      {
+         "self":"https://beepboopimatest.atlassian.net/rest/api/2/issuetype/10001",
+         "id":"10001",
+         "description":"Functionality or a feature expressed as a user goal.",
+         "iconUrl":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium",
+         "name":"Story",
+         "untranslatedName":"Story",
+         "subtask":false,
+         "expand":"fields",
+         "fields":{
+            "summary":{
+               "required":true,
+               "schema":{
+                  "type":"string",
+                  "system":"summary"
+               },
+               "name":"Summary",
+               "key":"summary",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "issuetype":{
+               "required":true,
+               "schema":{
+                  "type":"issuetype",
+                  "system":"issuetype"
+               },
+               "name":"Issue Type",
+               "key":"issuetype",
+               "hasDefaultValue":false,
+               "operations":[
+                  
+               ],
+               "allowedValues":[
+                  {
+                     "self":"https://beepboopimatest.atlassian.net/rest/api/2/issuetype/10001",
+                     "id":"10001",
+                     "description":"Functionality or a feature expressed as a user goal.",
+                     "iconUrl":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium",
+                     "name":"Story",
+                     "subtask":false,
+                     "avatarId":10315,
+                     "hierarchyLevel":0
+                  }
+               ]
+            },
+            "parent":{
+               "required":false,
+               "schema":{
+                  "type":"issuelink",
+                  "system":"parent"
+               },
+               "name":"Parent",
+               "key":"parent",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "components":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"component",
+                  "system":"components"
+               },
+               "name":"Components",
+               "key":"components",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add",
+                  "set",
+                  "remove"
+               ],
+               "allowedValues":[
+                  
+               ]
+            },
+            "description":{
+               "required":false,
+               "schema":{
+                  "type":"string",
+                  "system":"description"
+               },
+               "name":"Description",
+               "key":"description",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "project":{
+               "required":true,
+               "schema":{
+                  "type":"project",
+                  "system":"project"
+               },
+               "name":"Project",
+               "key":"project",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ],
+               "allowedValues":[
+                  {
+                     "self":"https://beepboopimatest.atlassian.net/rest/api/2/project/10001",
+                     "id":"10001",
+                     "key":"CO2",
+                     "name":"Colleen-2",
+                     "projectTypeKey":"software",
+                     "simplified":false,
+                     "avatarUrls":{
+                        "48x48":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410",
+                        "24x24":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=small",
+                        "16x16":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=xsmall",
+                        "32x32":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=medium"
+                     }
+                  }
+               ]
+            },
+            "reporter":{
+               "required":true,
+               "schema":{
+                  "type":"user",
+                  "system":"reporter"
+               },
+               "name":"Reporter",
+               "key":"reporter",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/user/search?query=",
+               "hasDefaultValue":true,
+               "operations":[
+                  "set"
+               ]
+            },
+            "customfield_10014":{
+               "required":false,
+               "schema":{
+                  "type":"any",
+                  "custom":"com.pyxis.greenhopper.jira:gh-epic-link",
+                  "customId":10014
+               },
+               "name":"Epic Link",
+               "key":"customfield_10014",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "labels":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"string",
+                  "system":"labels"
+               },
+               "name":"Labels",
+               "key":"labels",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/1.0/labels/suggest?query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add",
+                  "set",
+                  "remove"
+               ]
+            },
+            "attachment":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"attachment",
+                  "system":"attachment"
+               },
+               "name":"Attachment",
+               "key":"attachment",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "issuelinks":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"issuelinks",
+                  "system":"issuelinks"
+               },
+               "name":"Linked Issues",
+               "key":"issuelinks",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/issue/picker?currentProjectId=&showSubTaskParent=true&showSubTasks=true&currentIssueKey=null&query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add"
+               ]
+            },
+            "assignee":{
+               "required":false,
+               "schema":{
+                  "type":"user",
+                  "system":"assignee"
+               },
+               "name":"Assignee",
+               "key":"assignee",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/user/assignable/search?project=CO2&query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            }
+         }
+      },
+      {
+         "self":"https://beepboopimatest.atlassian.net/rest/api/2/issuetype/10002",
+         "id":"10002",
+         "description":"A small, distinct piece of work.",
+         "iconUrl":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10318?size=medium",
+         "name":"Task",
+         "untranslatedName":"Task",
+         "subtask":false,
+         "expand":"fields",
+         "fields":{
+            "summary":{
+               "required":true,
+               "schema":{
+                  "type":"string",
+                  "system":"summary"
+               },
+               "name":"Summary",
+               "key":"summary",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "issuetype":{
+               "required":true,
+               "schema":{
+                  "type":"issuetype",
+                  "system":"issuetype"
+               },
+               "name":"Issue Type",
+               "key":"issuetype",
+               "hasDefaultValue":false,
+               "operations":[
+                  
+               ],
+               "allowedValues":[
+                  {
+                     "self":"https://beepboopimatest.atlassian.net/rest/api/2/issuetype/10002",
+                     "id":"10002",
+                     "description":"A small, distinct piece of work.",
+                     "iconUrl":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10318?size=medium",
+                     "name":"Task",
+                     "subtask":false,
+                     "avatarId":10318,
+                     "hierarchyLevel":0
+                  }
+               ]
+            },
+            "parent":{
+               "required":false,
+               "schema":{
+                  "type":"issuelink",
+                  "system":"parent"
+               },
+               "name":"Parent",
+               "key":"parent",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "components":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"component",
+                  "system":"components"
+               },
+               "name":"Components",
+               "key":"components",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add",
+                  "set",
+                  "remove"
+               ],
+               "allowedValues":[
+                  
+               ]
+            },
+            "description":{
+               "required":false,
+               "schema":{
+                  "type":"string",
+                  "system":"description"
+               },
+               "name":"Description",
+               "key":"description",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "project":{
+               "required":true,
+               "schema":{
+                  "type":"project",
+                  "system":"project"
+               },
+               "name":"Project",
+               "key":"project",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ],
+               "allowedValues":[
+                  {
+                     "self":"https://beepboopimatest.atlassian.net/rest/api/2/project/10001",
+                     "id":"10001",
+                     "key":"CO2",
+                     "name":"Colleen-2",
+                     "projectTypeKey":"software",
+                     "simplified":false,
+                     "avatarUrls":{
+                        "48x48":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410",
+                        "24x24":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=small",
+                        "16x16":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=xsmall",
+                        "32x32":"https://beepboopimatest.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10410?size=medium"
+                     }
+                  }
+               ]
+            },
+            "reporter":{
+               "required":true,
+               "schema":{
+                  "type":"user",
+                  "system":"reporter"
+               },
+               "name":"Reporter",
+               "key":"reporter",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/user/search?query=",
+               "hasDefaultValue":true,
+               "operations":[
+                  "set"
+               ]
+            },
+            "customfield_10014":{
+               "required":false,
+               "schema":{
+                  "type":"any",
+                  "custom":"com.pyxis.greenhopper.jira:gh-epic-link",
+                  "customId":10014
+               },
+               "name":"Epic Link",
+               "key":"customfield_10014",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "labels":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"string",
+                  "system":"labels"
+               },
+               "name":"Labels",
+               "key":"labels",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/1.0/labels/suggest?query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add",
+                  "set",
+                  "remove"
+               ]
+            },
+            "attachment":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"attachment",
+                  "system":"attachment"
+               },
+               "name":"Attachment",
+               "key":"attachment",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            },
+            "issuelinks":{
+               "required":false,
+               "schema":{
+                  "type":"array",
+                  "items":"issuelinks",
+                  "system":"issuelinks"
+               },
+               "name":"Linked Issues",
+               "key":"issuelinks",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/issue/picker?currentProjectId=&showSubTaskParent=true&showSubTasks=true&currentIssueKey=null&query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "add"
+               ]
+            },
+            "assignee":{
+               "required":false,
+               "schema":{
+                  "type":"user",
+                  "system":"assignee"
+               },
+               "name":"Assignee",
+               "key":"assignee",
+               "autoCompleteUrl":"https://beepboopimatest.atlassian.net/rest/api/2/user/assignable/search?project=CO2&query=",
+               "hasDefaultValue":false,
+               "operations":[
+                  "set"
+               ]
+            }
+         }
+      }
+   ]
+}

+ 48 - 1
tests/sentry/integrations/jira/test_integration.py

@@ -1,5 +1,6 @@
 import copy
 from unittest import mock
+from unittest.mock import patch
 
 import pytest
 import responses
@@ -7,7 +8,7 @@ from django.test.utils import override_settings
 from django.urls import reverse
 from exam import fixture
 
-from sentry.integrations.jira import JiraIntegrationProvider
+from sentry.integrations.jira.integration import JiraIntegrationProvider
 from sentry.models import (
     ExternalIssue,
     Integration,
@@ -330,6 +331,52 @@ class JiraIntegrationTest(APITestCase):
                 "updatesForm": True,
             }
 
+    @patch("sentry.integrations.jira.integration.JiraIntegration.fetch_issue_create_meta")
+    def test_get_create_issue_config_with_default_project_deleted(
+        self, mock_fetch_issue_create_meta
+    ):
+        event = self.store_event(
+            data={
+                "event_id": "a" * 32,
+                "message": "message",
+                "timestamp": self.min_ago,
+                "stacktrace": copy.deepcopy(DEFAULT_EVENT_DATA["stacktrace"]),
+            },
+            project_id=self.project.id,
+        )
+        group = event.group
+        installation = self.integration.get_installation(self.organization.id)
+        installation.org_integration.config = {
+            "project_issue_defaults": {str(group.project_id): {"project": "10004"}}
+        }
+        installation.org_integration.save()
+
+        with mock.patch.object(installation, "get_client", get_client):
+            mock_fetch_issue_create_meta_return_value = json.loads(
+                StubService.get_stub_json("jira", "fetch_issue_create_meta.json")
+            )
+            project_list_response = json.loads(
+                StubService.get_stub_json("jira", "project_list_response.json")
+            )
+            side_effect_values = [
+                mock_fetch_issue_create_meta_return_value for project in project_list_response
+            ]
+            # return None the first time fetch_issue_create_meta is called to mimic a deleted default project id (10004)
+            # so that we drop into the code block where it iterates over available projects
+            mock_fetch_issue_create_meta.side_effect = [None, *side_effect_values]
+
+            fields = installation.get_create_issue_config(group, self.user)
+            project_field = [field for field in fields if field["name"] == "project"][0]
+
+            assert project_field == {
+                "default": "10001",
+                "choices": [("10000", "EX"), ("10001", "ABC")],
+                "type": "select",
+                "name": "project",
+                "label": "Jira Project",
+                "updatesForm": True,
+            }
+
     def test_get_create_issue_config_with_label_default(self):
         event = self.store_event(
             data={