diff --git a/surfsense_backend/app/connectors/jira_connector.py b/surfsense_backend/app/connectors/jira_connector.py index 65cb657..2325a66 100644 --- a/surfsense_backend/app/connectors/jira_connector.py +++ b/surfsense_backend/app/connectors/jira_connector.py @@ -5,8 +5,10 @@ A module for retrieving data from Jira. Allows fetching issue lists and their comments, projects and more. """ -from typing import Any, Dict, List, Optional, Tuple +import base64 +import json from datetime import datetime +from typing import Any, Dict, List, Optional import requests @@ -17,55 +19,76 @@ class JiraConnector: def __init__( self, base_url: Optional[str] = None, - personal_access_token: Optional[str] = None, + email: Optional[str] = None, + api_token: Optional[str] = None, ): """ Initialize the JiraConnector class. Args: base_url: Jira instance base URL (e.g., 'https://yourcompany.atlassian.net') (optional) - personal_access_token: Jira personal access token (optional) + email: Jira account email address (optional) + api_token: Jira API token (optional) """ self.base_url = base_url.rstrip("/") if base_url else None - self.personal_access_token = personal_access_token + self.email = email + self.api_token = api_token self.api_version = "3" # Jira Cloud API version - def set_credentials(self, base_url: str, personal_access_token: str) -> None: + def set_credentials(self, base_url: str, email: str, api_token: str) -> None: """ Set the Jira credentials. Args: base_url: Jira instance base URL - personal_access_token: Jira personal access token + email: Jira account email address + api_token: Jira API token """ self.base_url = base_url.rstrip("/") - self.personal_access_token = personal_access_token + self.email = email + self.api_token = api_token - def set_personal_access_token(self, personal_access_token: str) -> None: + def set_email(self, email: str) -> None: """ - Set the Jira personal access token. + Set the Jira account email. Args: - personal_access_token: Jira personal access token + email: Jira account email address """ - self.personal_access_token = personal_access_token + self.email = email + + def set_api_token(self, api_token: str) -> None: + """ + Set the Jira API token. + + Args: + api_token: Jira API token + """ + self.api_token = api_token def get_headers(self) -> Dict[str, str]: """ - Get headers for Jira API requests. + Get headers for Jira API requests using Basic Authentication. Returns: Dictionary of headers Raises: - ValueError: If personal_access_token or base_url have not been set + ValueError: If email, api_token, or base_url have not been set """ - if not all([self.base_url, self.personal_access_token]): - raise ValueError("Jira personal access token or base URL not initialized.") + if not all([self.base_url, self.email, self.api_token]): + raise ValueError( + "Jira credentials not initialized. Call set_credentials() first." + ) + + # Create Basic Auth header using email:api_token + auth_str = f"{self.email}:{self.api_token}" + auth_bytes = auth_str.encode("utf-8") + auth_header = "Basic " + base64.b64encode(auth_bytes).decode("ascii") return { "Content-Type": "application/json", - "Authorization": f"Bearer {self.personal_access_token}", + "Authorization": auth_header, "Accept": "application/json", } @@ -83,17 +106,21 @@ class JiraConnector: Response data from the API Raises: - ValueError: If personal_access_token or base_url have not been set + ValueError: If email, api_token, or base_url have not been set Exception: If the API request fails """ - if not all([self.base_url, self.personal_access_token]): - raise ValueError("Jira personal access token or base URL not initialized.") + if not all([self.base_url, self.email, self.api_token]): + raise ValueError( + "Jira credentials not initialized. Call set_credentials() first." + ) url = f"{self.base_url}/rest/api/{self.api_version}/{endpoint}" headers = self.get_headers() response = requests.get(url, headers=headers, params=params, timeout=500) + print(json.dumps(response.json(), indent=2)) + if response.status_code == 200: return response.json() else: @@ -197,9 +224,11 @@ class JiraConnector: try: # Build JQL query for date range # Query issues that were either created OR updated within the date range - date_filter = f"(created >= '{start_date}' AND created <= '{end_date}') OR (updated >= '{start_date}' AND updated <= '{end_date}')" + date_filter = ( + f"(createdDate >= '{start_date}' AND createdDate <= '{end_date}')" + ) - jql = f"{date_filter} ORDER BY created DESC" + jql = f"{date_filter}" if project_key: jql = ( f'project = "{project_key}" AND {date_filter} ORDER BY created DESC' @@ -234,8 +263,11 @@ class JiraConnector: while True: params["startAt"] = start_at + print(json.dumps(params, indent=2)) result = self.make_api_request("search", params) + print(json.dumps(result, indent=2)) + if not isinstance(result, dict) or "issues" not in result: return [], "Invalid response from Jira API" diff --git a/surfsense_backend/app/connectors/test_jira_connector.py b/surfsense_backend/app/connectors/test_jira_connector.py index c9b7551..a4b33b0 100644 --- a/surfsense_backend/app/connectors/test_jira_connector.py +++ b/surfsense_backend/app/connectors/test_jira_connector.py @@ -1,104 +1,112 @@ import unittest -from unittest.mock import patch, Mock -from datetime import datetime +from unittest.mock import Mock, patch # Import the JiraConnector from .jira_connector import JiraConnector class TestJiraConnector(unittest.TestCase): - def setUp(self): """Set up test fixtures.""" self.base_url = "https://test.atlassian.net" - self.token = "test_token" - self.connector = JiraConnector(base_url=self.base_url, personal_access_token=self.token) + self.email = "test@example.com" + self.api_token = "test_api_token" + self.connector = JiraConnector( + base_url=self.base_url, email=self.email, api_token=self.api_token + ) def test_init(self): """Test JiraConnector initialization.""" self.assertEqual(self.connector.base_url, self.base_url) - self.assertEqual(self.connector.personal_access_token, self.token) + self.assertEqual(self.connector.email, self.email) + self.assertEqual(self.connector.api_token, self.api_token) self.assertEqual(self.connector.api_version, "3") def test_init_with_trailing_slash(self): """Test JiraConnector initialization with trailing slash in URL.""" - connector = JiraConnector(base_url="https://test.atlassian.net/", personal_access_token=self.token) + connector = JiraConnector( + base_url="https://test.atlassian.net/", + email=self.email, + api_token=self.api_token, + ) self.assertEqual(connector.base_url, "https://test.atlassian.net") def test_set_credentials(self): """Test setting credentials.""" new_url = "https://newtest.atlassian.net/" - new_token = "new_token" - - self.connector.set_credentials(new_url, new_token) - + new_email = "new@example.com" + new_token = "new_api_token" + + self.connector.set_credentials(new_url, new_email, new_token) + self.assertEqual(self.connector.base_url, "https://newtest.atlassian.net") - self.assertEqual(self.connector.personal_access_token, new_token) + self.assertEqual(self.connector.email, new_email) + self.assertEqual(self.connector.api_token, new_token) def test_get_headers(self): """Test header generation.""" headers = self.connector.get_headers() - - self.assertIn('Content-Type', headers) - self.assertIn('Authorization', headers) - self.assertIn('Accept', headers) - self.assertEqual(headers['Content-Type'], 'application/json') - self.assertEqual(headers['Accept'], 'application/json') - self.assertTrue(headers['Authorization'].startswith('Bearer ')) + + self.assertIn("Content-Type", headers) + self.assertIn("Authorization", headers) + self.assertIn("Accept", headers) + self.assertEqual(headers["Content-Type"], "application/json") + self.assertEqual(headers["Accept"], "application/json") + self.assertTrue(headers["Authorization"].startswith("Basic ")) def test_get_headers_no_credentials(self): """Test header generation without credentials.""" connector = JiraConnector() - + with self.assertRaises(ValueError) as context: connector.get_headers() - + self.assertIn("Jira credentials not initialized", str(context.exception)) - @patch('requests.get') + @patch("requests.get") def test_make_api_request_success(self, mock_get): """Test successful API request.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = {"test": "data"} mock_get.return_value = mock_response - + result = self.connector.make_api_request("test/endpoint") - + self.assertEqual(result, {"test": "data"}) mock_get.assert_called_once() - @patch('requests.get') + @patch("requests.get") def test_make_api_request_failure(self, mock_get): """Test failed API request.""" mock_response = Mock() mock_response.status_code = 401 mock_response.text = "Unauthorized" mock_get.return_value = mock_response - + with self.assertRaises(Exception) as context: self.connector.make_api_request("test/endpoint") - + self.assertIn("API request failed with status code 401", str(context.exception)) - @patch.object(JiraConnector, 'make_api_request') + @patch.object(JiraConnector, "make_api_request") def test_get_all_projects(self, mock_api_request): """Test getting all projects.""" mock_api_request.return_value = { "values": [ {"id": "1", "key": "TEST", "name": "Test Project"}, - {"id": "2", "key": "DEMO", "name": "Demo Project"} + {"id": "2", "key": "DEMO", "name": "Demo Project"}, ] } - + projects = self.connector.get_all_projects() - + self.assertEqual(len(projects), 2) self.assertEqual(projects[0]["key"], "TEST") self.assertEqual(projects[1]["key"], "DEMO") mock_api_request.assert_called_once_with("project") - @patch.object(JiraConnector, 'make_api_request') + @patch.object(JiraConnector, "make_api_request") def test_get_all_issues(self, mock_api_request): """Test getting all issues.""" mock_api_request.return_value = { @@ -114,15 +122,15 @@ class TestJiraConnector(unittest.TestCase): "issuetype": {"name": "Bug"}, "project": {"key": "TEST"}, "created": "2023-01-01T10:00:00.000+0000", - "updated": "2023-01-01T12:00:00.000+0000" - } + "updated": "2023-01-01T12:00:00.000+0000", + }, } ], - "total": 1 + "total": 1, } - + issues = self.connector.get_all_issues() - + self.assertEqual(len(issues), 1) self.assertEqual(issues[0]["key"], "TEST-1") self.assertEqual(issues[0]["fields"]["summary"], "Test Issue") @@ -144,18 +152,18 @@ class TestJiraConnector(unittest.TestCase): "reporter": { "accountId": "123", "displayName": "John Doe", - "emailAddress": "john@example.com" + "emailAddress": "john@example.com", }, "assignee": { "accountId": "456", "displayName": "Jane Smith", - "emailAddress": "jane@example.com" - } - } + "emailAddress": "jane@example.com", + }, + }, } - + formatted = self.connector.format_issue(raw_issue) - + self.assertEqual(formatted["id"], "1") self.assertEqual(formatted["key"], "TEST-1") self.assertEqual(formatted["title"], "Test Issue") @@ -170,17 +178,17 @@ class TestJiraConnector(unittest.TestCase): """Test date formatting.""" iso_date = "2023-01-01T10:30:00.000+0000" formatted_date = JiraConnector.format_date(iso_date) - + self.assertEqual(formatted_date, "2023-01-01 10:30:00") def test_format_date_invalid(self): """Test date formatting with invalid input.""" formatted_date = JiraConnector.format_date("invalid-date") self.assertEqual(formatted_date, "invalid-date") - + formatted_date = JiraConnector.format_date("") self.assertEqual(formatted_date, "Unknown date") - + formatted_date = JiraConnector.format_date(None) self.assertEqual(formatted_date, "Unknown date") @@ -198,11 +206,11 @@ class TestJiraConnector(unittest.TestCase): "created_at": "2023-01-01T10:00:00.000+0000", "updated_at": "2023-01-01T12:00:00.000+0000", "description": "Test Description", - "comments": [] + "comments": [], } - + markdown = self.connector.format_issue_to_markdown(formatted_issue) - + self.assertIn("# TEST-1: Test Issue", markdown) self.assertIn("**Status:** Open", markdown) self.assertIn("**Priority:** High", markdown) @@ -214,5 +222,5 @@ class TestJiraConnector(unittest.TestCase): self.assertIn("Test Description", markdown) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/surfsense_backend/app/schemas/search_source_connector.py b/surfsense_backend/app/schemas/search_source_connector.py index 17f1867..8c444a8 100644 --- a/surfsense_backend/app/schemas/search_source_connector.py +++ b/surfsense_backend/app/schemas/search_source_connector.py @@ -1,9 +1,12 @@ -from datetime import datetime import uuid -from typing import Dict, Any, Optional -from pydantic import BaseModel, field_validator, ConfigDict -from .base import IDModel, TimestampModel +from datetime import datetime +from typing import Any, Dict, Optional + from app.db import SearchSourceConnectorType +from pydantic import BaseModel, ConfigDict, field_validator + +from .base import IDModel, TimestampModel + class SearchSourceConnectorBase(BaseModel): name: str @@ -11,105 +14,129 @@ class SearchSourceConnectorBase(BaseModel): is_indexable: bool last_indexed_at: Optional[datetime] = None config: Dict[str, Any] - - @field_validator('config') + + @field_validator("config") @classmethod - def validate_config_for_connector_type(cls, config: Dict[str, Any], values: Dict[str, Any]) -> Dict[str, Any]: - connector_type = values.data.get('connector_type') - + def validate_config_for_connector_type( + cls, config: Dict[str, Any], values: Dict[str, Any] + ) -> Dict[str, Any]: + connector_type = values.data.get("connector_type") + if connector_type == SearchSourceConnectorType.SERPER_API: # For SERPER_API, only allow SERPER_API_KEY allowed_keys = ["SERPER_API_KEY"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For SERPER_API connector type, config must only contain these keys: {allowed_keys}") - + raise ValueError( + f"For SERPER_API connector type, config must only contain these keys: {allowed_keys}" + ) + # Ensure the API key is not empty if not config.get("SERPER_API_KEY"): raise ValueError("SERPER_API_KEY cannot be empty") - + elif connector_type == SearchSourceConnectorType.TAVILY_API: # For TAVILY_API, only allow TAVILY_API_KEY allowed_keys = ["TAVILY_API_KEY"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For TAVILY_API connector type, config must only contain these keys: {allowed_keys}") - + raise ValueError( + f"For TAVILY_API connector type, config must only contain these keys: {allowed_keys}" + ) + # Ensure the API key is not empty if not config.get("TAVILY_API_KEY"): raise ValueError("TAVILY_API_KEY cannot be empty") - + elif connector_type == SearchSourceConnectorType.LINKUP_API: # For LINKUP_API, only allow LINKUP_API_KEY allowed_keys = ["LINKUP_API_KEY"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For LINKUP_API connector type, config must only contain these keys: {allowed_keys}") - + raise ValueError( + f"For LINKUP_API connector type, config must only contain these keys: {allowed_keys}" + ) + # Ensure the API key is not empty if not config.get("LINKUP_API_KEY"): raise ValueError("LINKUP_API_KEY cannot be empty") - + elif connector_type == SearchSourceConnectorType.SLACK_CONNECTOR: # For SLACK_CONNECTOR, only allow SLACK_BOT_TOKEN allowed_keys = ["SLACK_BOT_TOKEN"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For SLACK_CONNECTOR connector type, config must only contain these keys: {allowed_keys}") + raise ValueError( + f"For SLACK_CONNECTOR connector type, config must only contain these keys: {allowed_keys}" + ) # Ensure the bot token is not empty if not config.get("SLACK_BOT_TOKEN"): raise ValueError("SLACK_BOT_TOKEN cannot be empty") - + elif connector_type == SearchSourceConnectorType.NOTION_CONNECTOR: # For NOTION_CONNECTOR, only allow NOTION_INTEGRATION_TOKEN allowed_keys = ["NOTION_INTEGRATION_TOKEN"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For NOTION_CONNECTOR connector type, config must only contain these keys: {allowed_keys}") - + raise ValueError( + f"For NOTION_CONNECTOR connector type, config must only contain these keys: {allowed_keys}" + ) + # Ensure the integration token is not empty if not config.get("NOTION_INTEGRATION_TOKEN"): raise ValueError("NOTION_INTEGRATION_TOKEN cannot be empty") - + elif connector_type == SearchSourceConnectorType.GITHUB_CONNECTOR: # For GITHUB_CONNECTOR, only allow GITHUB_PAT and repo_full_names allowed_keys = ["GITHUB_PAT", "repo_full_names"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For GITHUB_CONNECTOR connector type, config must only contain these keys: {allowed_keys}") - + raise ValueError( + f"For GITHUB_CONNECTOR connector type, config must only contain these keys: {allowed_keys}" + ) + # Ensure the token is not empty if not config.get("GITHUB_PAT"): raise ValueError("GITHUB_PAT cannot be empty") - + # Ensure the repo_full_names is present and is a non-empty list repo_full_names = config.get("repo_full_names") if not isinstance(repo_full_names, list) or not repo_full_names: raise ValueError("repo_full_names must be a non-empty list of strings") - + elif connector_type == SearchSourceConnectorType.LINEAR_CONNECTOR: # For LINEAR_CONNECTOR, only allow LINEAR_API_KEY allowed_keys = ["LINEAR_API_KEY"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For LINEAR_CONNECTOR connector type, config must only contain these keys: {allowed_keys}") - + raise ValueError( + f"For LINEAR_CONNECTOR connector type, config must only contain these keys: {allowed_keys}" + ) + # Ensure the token is not empty if not config.get("LINEAR_API_KEY"): raise ValueError("LINEAR_API_KEY cannot be empty") - + elif connector_type == SearchSourceConnectorType.DISCORD_CONNECTOR: # For DISCORD_CONNECTOR, only allow DISCORD_BOT_TOKEN allowed_keys = ["DISCORD_BOT_TOKEN"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For DISCORD_CONNECTOR connector type, config must only contain these keys: {allowed_keys}") + raise ValueError( + f"For DISCORD_CONNECTOR connector type, config must only contain these keys: {allowed_keys}" + ) # Ensure the bot token is not empty if not config.get("DISCORD_BOT_TOKEN"): raise ValueError("DISCORD_BOT_TOKEN cannot be empty") elif connector_type == SearchSourceConnectorType.JIRA_CONNECTOR: - # For JIRA_CONNECTOR, allow JIRA_PERSONAL_ACCESS_TOKEN and JIRA_BASE_URL - allowed_keys = ["JIRA_PERSONAL_ACCESS_TOKEN", "JIRA_BASE_URL"] + # For JIRA_CONNECTOR, require JIRA_EMAIL, JIRA_API_TOKEN and JIRA_BASE_URL + allowed_keys = ["JIRA_EMAIL", "JIRA_API_TOKEN", "JIRA_BASE_URL"] if set(config.keys()) != set(allowed_keys): - raise ValueError(f"For JIRA_CONNECTOR connector type, config must only contain these keys: {allowed_keys}") + raise ValueError( + f"For JIRA_CONNECTOR connector type, config must only contain these keys: {allowed_keys}" + ) - # Ensure the token is not empty - if not config.get("JIRA_PERSONAL_ACCESS_TOKEN"): - raise ValueError("JIRA_PERSONAL_ACCESS_TOKEN cannot be empty") + # Ensure the email is not empty + if not config.get("JIRA_EMAIL"): + raise ValueError("JIRA_EMAIL cannot be empty") + + # Ensure the API token is not empty + if not config.get("JIRA_API_TOKEN"): + raise ValueError("JIRA_API_TOKEN cannot be empty") # Ensure the base URL is not empty if not config.get("JIRA_BASE_URL"): @@ -117,9 +144,11 @@ class SearchSourceConnectorBase(BaseModel): return config + class SearchSourceConnectorCreate(SearchSourceConnectorBase): pass + class SearchSourceConnectorUpdate(BaseModel): name: Optional[str] = None connector_type: Optional[SearchSourceConnectorType] = None @@ -127,7 +156,8 @@ class SearchSourceConnectorUpdate(BaseModel): last_indexed_at: Optional[datetime] = None config: Optional[Dict[str, Any]] = None + class SearchSourceConnectorRead(SearchSourceConnectorBase, IDModel, TimestampModel): user_id: uuid.UUID - model_config = ConfigDict(from_attributes=True) + model_config = ConfigDict(from_attributes=True) diff --git a/surfsense_backend/app/tasks/connectors_indexing_tasks.py b/surfsense_backend/app/tasks/connectors_indexing_tasks.py index f4ae139..b01a2a1 100644 --- a/surfsense_backend/app/tasks/connectors_indexing_tasks.py +++ b/surfsense_backend/app/tasks/connectors_indexing_tasks.py @@ -1,4 +1,5 @@ import asyncio +import json import logging from datetime import datetime, timedelta, timezone from typing import Optional, Tuple @@ -2041,10 +2042,11 @@ async def index_jira_issues( return 0, f"Connector with ID {connector_id} not found" # Get the Jira credentials from the connector config - jira_token = connector.config.get("JIRA_PERSONAL_ACCESS_TOKEN") + jira_email = connector.config.get("JIRA_EMAIL") + jira_api_token = connector.config.get("JIRA_API_TOKEN") jira_base_url = connector.config.get("JIRA_BASE_URL") - if not jira_token or not jira_base_url: + if not jira_email or not jira_api_token or not jira_base_url: await task_logger.log_task_failure( log_entry, f"Jira credentials not found in connector config for connector {connector_id}", @@ -2061,7 +2063,7 @@ async def index_jira_issues( ) jira_client = JiraConnector( - base_url=jira_base_url, personal_access_token=jira_token + base_url=jira_base_url, email=jira_email, api_token=jira_api_token ) # Calculate date range @@ -2097,6 +2099,8 @@ async def index_jira_issues( start_date=start_date_str, end_date=end_date_str, include_comments=True ) + print(json.dumps(issues, indent=2)) + if error: logger.error(f"Failed to get Jira issues: {error}") @@ -2112,10 +2116,10 @@ async def index_jira_issues( f"Updated last_indexed_at to {connector.last_indexed_at} despite no issues found" ) - await task_logger.log_task_completion( + await task_logger.log_task_success( log_entry, f"No Jira issues found in date range {start_date_str} to {end_date_str}", - {"indexed_count": 0}, + {"issues_found": 0}, ) return 0, None else: @@ -2132,7 +2136,7 @@ async def index_jira_issues( await task_logger.log_task_progress( log_entry, f"Retrieved {len(issues)} issues from Jira API", - {"stage": "processing_issues", "issue_count": len(issues)}, + {"stage": "processing_issues", "issues_found": len(issues)}, ) except Exception as e: @@ -2254,10 +2258,10 @@ async def index_jira_issues( await session.commit() logger.info(f"Updated last_indexed_at to {connector.last_indexed_at}") - await task_logger.log_task_completion( + await task_logger.log_task_success( log_entry, f"Successfully indexed {indexed_count} Jira issues", - {"indexed_count": indexed_count}, + {"issues_indexed": indexed_count}, ) logger.info(f"Successfully indexed {indexed_count} Jira issues") diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx index 4292b7e..918a625 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx @@ -193,10 +193,17 @@ export default function EditConnectorPage() { /> + )} diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx index 625adfa..23e128f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx @@ -57,8 +57,11 @@ const jiraConnectorFormSchema = z.object({ message: "Please enter a valid Jira instance URL", }, ), - personal_access_token: z.string().min(10, { - message: "Jira Personal Access Token is required and must be valid.", + email: z.string().email({ + message: "Please enter a valid email address.", + }), + api_token: z.string().min(10, { + message: "Jira API Token is required and must be valid.", }), }); @@ -78,7 +81,8 @@ export default function JiraConnectorPage() { defaultValues: { name: "Jira Connector", base_url: "", - personal_access_token: "", + email: "", + api_token: "", }, }); @@ -91,7 +95,8 @@ export default function JiraConnectorPage() { connector_type: "JIRA_CONNECTOR", config: { JIRA_BASE_URL: values.base_url, - JIRA_PERSONAL_ACCESS_TOKEN: values.personal_access_token, + JIRA_EMAIL: values.email, + JIRA_API_TOKEN: values.api_token, }, is_indexable: true, last_indexed_at: null, @@ -210,20 +215,40 @@ export default function JiraConnectorPage() { ( - Personal Access Token + Email Address - Your Jira Personal Access Token will be encrypted - and stored securely. + Your Atlassian account email address. + + + + )} + /> + + ( + + API Token + + + + + Your Jira API Token will be encrypted and stored securely. @@ -296,8 +321,8 @@ export default function JiraConnectorPage() {

How it works

- The Jira connector uses the Jira REST API to fetch all - issues and comments that the Personal Access Token has + The Jira connector uses the Jira REST API with Basic Authentication + to fetch all issues and comments that your account has access to within your Jira instance.

    @@ -324,15 +349,14 @@ export default function JiraConnectorPage() { Read-Only Access is Sufficient You only need read access for this connector to work. - The Personal Access Token will only be used to read - your Jira data. + The API Token will only be used to read your Jira data.

    - Step 1: Create a Personal Access Token + Step 1: Create an API Token

    1. Log in to your Atlassian account
    2. @@ -369,10 +393,10 @@ export default function JiraConnectorPage() { Step 2: Grant necessary access

      - The Personal Access Token will have access to all - projects and issues that your user account can see. - Make sure your account has appropriate permissions - for the projects you want to index. + The API Token will have access to all projects and + issues that your user account can see. Make sure your + account has appropriate permissions for the projects + you want to index.