Repository: getsentry/sentry

Issue:
Linking issue to JIRA server failing
### Environment

SaaS (https://sentry.io/)

### Steps to Reproduce

> This only happens with certain projects. The Jira integration is definitely working since many work fine and can create issues.
I noticed that some of the Jira projects in the configuration section were blank. This led me to believe that some of the projects may have been removed or Sentry no longer had access to them, so I ensured that the required projects had access by re-requesting access for the service account used to access those projects, and I deleted the blank entries. However this did not fix the problem.
I then tried linking to an existing Jira ticket and this worked fine. This leads me to believe that the cached last used project might be the problem. The Sentry Issue pulls the last project used and fails because it no longer exists



### Expected Result

Linking issue to Jira ticket.




### Actual Result

![image](https://github.com/getsentry/sentry/assets/86369415/c370e46e-a683-41bb-90e8-24c12692165c)


This bug was reported a while ago in this issue https://sentry.sentry.io/issues/4130565830/?project=1 and a more recent event can be seen in this issue: https://sentry.sentry.io/issues/4267026714/events/e6c5ccc91a694b45824d96d56ee260c0/?project=1

### Product Area

Issues

### Link

_No response_

### DSN

_No response_

### Version

_No response_


Discussion:
Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route) ⏲️
Routing to @getsentry/product-owners-settings-integrations for [triage](https://develop.sentry.dev/processing-tickets/#3-triage) ⏲️
@dachakra FYI If you need to share any private data, there's a Private Data field for our board.

This is the code I believe is related:
https://github.com/getsentry/sentry/blob/805821d9c333638be7f997c3ce675e09ee0709c5/src/sentry/integrations/jira_server/integration.py#L705-L712
@dachakra could you please paste a link to a customer's issue where this happens?
(Under "Private Data" and let us know when you do)

The event you point me to shows a 400 error, could you please confirm that the `jira_project_id` is correct?
https://<jira_deployment>/rest/api/2/issue/createmeta/<jira_project_id>/issuetypes

If you can't get me a URL to an issue where this happens, could you please get me the API payload and response? (You can use private data if PII is contained)
In my case this is what I hit the Sentry API with:
```
{components: ""
description: "Sentry Issue: [SENTRY-10QN|https://sentry.sentry REDACTED"
issuetype: "10004"
priority: "4"
project: "10374"
reporter: "REDACTED"
title: "Test ticket feel free to delete"
```

Can you please confirm that they're using this prompt to create the Jira links?
<img width="469" alt="image" src="https://github.com/getsentry/sentry/assets/44410/7fa730a3-79bf-4225-9d78-04852cb9e20a">

I do not know how the UI would not send the project ID if they selected it.
The code would fall back to the first project if no value is passed:
```
        jira_projects = self.get_projects()

        if not project_id:
            project_id = jira_projects[0]["id"]
```
I created a shadow JIRA ticket here that contains the details: https://getsentry.atlassian.net/browse/ISSUE-1673
Hey, I was just wondering if there was any update on this issue?

Resolving patch:
diff --git a/src/sentry/integrations/jira_server/integration.py b/src/sentry/integrations/jira_server/integration.py
--- a/src/sentry/integrations/jira_server/integration.py
+++ b/src/sentry/integrations/jira_server/integration.py
@@ -705,11 +705,12 @@ def get_create_issue_config(self, group, user, **kwargs):
         project_id = params.get("project", defaults.get("project"))
         jira_projects = self.get_projects()
 
-        if not project_id:
+        if not project_id or project_id not in {proj["id"] for proj in jira_projects}:
             project_id = jira_projects[0]["id"]
 
         client = self.get_client()
         issue_type_choices = client.get_issue_types(project_id)
+
         issue_type_choices_formatted = [
             (choice["id"], choice["name"]) for choice in issue_type_choices["values"]
         ]


Test patch:
diff --git a/tests/sentry/integrations/jira_server/test_integration.py b/tests/sentry/integrations/jira_server/test_integration.py
--- a/tests/sentry/integrations/jira_server/test_integration.py
+++ b/tests/sentry/integrations/jira_server/test_integration.py
@@ -30,7 +30,7 @@
 
 from . import get_integration
 
-DFAULT_PROJECT_ID = 10000
+DEFAULT_PROJECT_ID = 10000
 DEFAULT_ISSUE_TYPE_ID = 10000
 
 
@@ -408,6 +408,56 @@ def test_get_create_issue_config_with_default(self):
                 "updatesForm": True,
             }
 
+    @responses.activate
+    def test_get_create_issue_config_with_default_project_not_available(self):
+        """Test that if you have a default project set that's not in the list of available jira projects
+        we try again with another project"""
+        event = self.store_event(
+            data={"message": "oh no", "timestamp": self.min_ago}, project_id=self.project.id
+        )
+        group = event.group
+        assert group is not None
+        assert self.installation.org_integration is not None
+        self.installation.org_integration = integration_service.update_organization_integration(
+            org_integration_id=self.installation.org_integration.id,
+            config={"project_issue_defaults": {str(group.project_id): {"project": "9999"}}},
+        )
+        responses.add(
+            responses.GET,
+            "https://jira.example.org/rest/api/2/project",
+            content_type="json",
+            body="""[
+                {"id": "10001", "key": "SAMP"},
+                {"id": "10002", "key": "SAHM"}
+            ]""",
+        )
+        responses.add(
+            responses.GET,
+            "https://jira.example.org/rest/api/2/issue/createmeta/10001/issuetypes",
+            body=StubService.get_stub_json("jira", "issue_types_response.json"),
+            content_type="json",
+        )
+        responses.add(
+            responses.GET,
+            f"https://jira.example.org/rest/api/2/issue/createmeta/10001/issuetypes/{DEFAULT_ISSUE_TYPE_ID}",
+            body=StubService.get_stub_json("jira", "issue_fields_response.json"),
+            content_type="json",
+        )
+        responses.add(
+            responses.GET,
+            "https://jira.example.org/rest/api/2/priority",
+            body=StubService.get_stub_json("jira", "priorities_response.json"),
+            content_type="json",
+        )
+        responses.add(
+            responses.GET,
+            "https://jira.example.org/rest/api/2/project/10001/versions",
+            body=StubService.get_stub_json("jira", "versions_response.json"),
+            content_type="json",
+        )
+
+        self.installation.get_create_issue_config(event.group, self.user)
+
     @patch("sentry.integrations.jira_server.client.JiraServerClient.get_issue_fields")
     def test_get_create_issue_config_with_default_project_deleted(self, mock_get_issue_fields):
         event = self.store_event(
@@ -443,9 +493,8 @@ def test_get_create_issue_config_with_default_project_deleted(self, mock_get_iss
 
             fields = self.installation.get_create_issue_config(group, self.user)
             project_field = [field for field in fields if field["name"] == "project"][0]
-
             assert project_field == {
-                "default": "10004",
+                "default": "10000",  # a new default is set from actually available projects
                 "choices": [("10000", "EX"), ("10001", "ABC")],
                 "type": "select",
                 "name": "project",
@@ -517,14 +566,14 @@ def test_get_create_issue_config__no_issue_config(self):
         )
         responses.add(
             responses.GET,
-            f"https://jira.example.org/rest/api/2/issue/createmeta/{DFAULT_PROJECT_ID}/issuetypes",
+            f"https://jira.example.org/rest/api/2/issue/createmeta/{DEFAULT_PROJECT_ID}/issuetypes",
             body=StubService.get_stub_json("jira", "issue_types_response.json"),
             content_type="json",
         )
         # Fail to return metadata
         responses.add(
             responses.GET,
-            f"https://jira.example.org/rest/api/2/issue/createmeta/{DFAULT_PROJECT_ID}/issuetypes/{DEFAULT_ISSUE_TYPE_ID}",
+            f"https://jira.example.org/rest/api/2/issue/createmeta/{DEFAULT_PROJECT_ID}/issuetypes/{DEFAULT_ISSUE_TYPE_ID}",
             content_type="json",
             status=401,
             body="",
@@ -573,7 +622,7 @@ def test_create_issue(self):
     def test_create_issue_labels_and_option(self):
         responses.add(
             responses.GET,
-            f"https://jira.example.org/rest/api/2/issue/createmeta/{DFAULT_PROJECT_ID}/issuetypes/{DEFAULT_ISSUE_TYPE_ID}",
+            f"https://jira.example.org/rest/api/2/issue/createmeta/{DEFAULT_PROJECT_ID}/issuetypes/{DEFAULT_ISSUE_TYPE_ID}",
             body=StubService.get_stub_json("jira", "issue_fields_response.json"),
             content_type="json",
         )