mirror of
https://github.com/MODSetter/SurfSense.git
synced 2025-09-01 10:09:08 +00:00
update auth type
This commit is contained in:
parent
7af65a5a66
commit
4984aab3f1
6 changed files with 245 additions and 140 deletions
|
@ -5,8 +5,10 @@ A module for retrieving data from Jira.
|
||||||
Allows fetching issue lists and their comments, projects and more.
|
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 datetime import datetime
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -17,55 +19,76 @@ class JiraConnector:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_url: Optional[str] = None,
|
base_url: Optional[str] = None,
|
||||||
personal_access_token: Optional[str] = None,
|
email: Optional[str] = None,
|
||||||
|
api_token: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize the JiraConnector class.
|
Initialize the JiraConnector class.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
base_url: Jira instance base URL (e.g., 'https://yourcompany.atlassian.net') (optional)
|
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.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
|
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.
|
Set the Jira credentials.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
base_url: Jira instance base URL
|
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.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:
|
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]:
|
def get_headers(self) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Get headers for Jira API requests.
|
Get headers for Jira API requests using Basic Authentication.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary of headers
|
Dictionary of headers
|
||||||
|
|
||||||
Raises:
|
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]):
|
if not all([self.base_url, self.email, self.api_token]):
|
||||||
raise ValueError("Jira personal access token or base URL not initialized.")
|
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 {
|
return {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": f"Bearer {self.personal_access_token}",
|
"Authorization": auth_header,
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,17 +106,21 @@ class JiraConnector:
|
||||||
Response data from the API
|
Response data from the API
|
||||||
|
|
||||||
Raises:
|
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
|
Exception: If the API request fails
|
||||||
"""
|
"""
|
||||||
if not all([self.base_url, self.personal_access_token]):
|
if not all([self.base_url, self.email, self.api_token]):
|
||||||
raise ValueError("Jira personal access token or base URL not initialized.")
|
raise ValueError(
|
||||||
|
"Jira credentials not initialized. Call set_credentials() first."
|
||||||
|
)
|
||||||
|
|
||||||
url = f"{self.base_url}/rest/api/{self.api_version}/{endpoint}"
|
url = f"{self.base_url}/rest/api/{self.api_version}/{endpoint}"
|
||||||
headers = self.get_headers()
|
headers = self.get_headers()
|
||||||
|
|
||||||
response = requests.get(url, headers=headers, params=params, timeout=500)
|
response = requests.get(url, headers=headers, params=params, timeout=500)
|
||||||
|
|
||||||
|
print(json.dumps(response.json(), indent=2))
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
|
@ -197,9 +224,11 @@ class JiraConnector:
|
||||||
try:
|
try:
|
||||||
# Build JQL query for date range
|
# Build JQL query for date range
|
||||||
# Query issues that were either created OR updated within the 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:
|
if project_key:
|
||||||
jql = (
|
jql = (
|
||||||
f'project = "{project_key}" AND {date_filter} ORDER BY created DESC'
|
f'project = "{project_key}" AND {date_filter} ORDER BY created DESC'
|
||||||
|
@ -234,8 +263,11 @@ class JiraConnector:
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
params["startAt"] = start_at
|
params["startAt"] = start_at
|
||||||
|
print(json.dumps(params, indent=2))
|
||||||
result = self.make_api_request("search", params)
|
result = self.make_api_request("search", params)
|
||||||
|
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
|
||||||
if not isinstance(result, dict) or "issues" not in result:
|
if not isinstance(result, dict) or "issues" not in result:
|
||||||
return [], "Invalid response from Jira API"
|
return [], "Invalid response from Jira API"
|
||||||
|
|
||||||
|
|
|
@ -1,104 +1,112 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import Mock, patch
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# Import the JiraConnector
|
# Import the JiraConnector
|
||||||
from .jira_connector import JiraConnector
|
from .jira_connector import JiraConnector
|
||||||
|
|
||||||
|
|
||||||
class TestJiraConnector(unittest.TestCase):
|
class TestJiraConnector(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Set up test fixtures."""
|
"""Set up test fixtures."""
|
||||||
self.base_url = "https://test.atlassian.net"
|
self.base_url = "https://test.atlassian.net"
|
||||||
self.token = "test_token"
|
self.email = "test@example.com"
|
||||||
self.connector = JiraConnector(base_url=self.base_url, personal_access_token=self.token)
|
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):
|
def test_init(self):
|
||||||
"""Test JiraConnector initialization."""
|
"""Test JiraConnector initialization."""
|
||||||
self.assertEqual(self.connector.base_url, self.base_url)
|
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")
|
self.assertEqual(self.connector.api_version, "3")
|
||||||
|
|
||||||
def test_init_with_trailing_slash(self):
|
def test_init_with_trailing_slash(self):
|
||||||
"""Test JiraConnector initialization with trailing slash in URL."""
|
"""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")
|
self.assertEqual(connector.base_url, "https://test.atlassian.net")
|
||||||
|
|
||||||
def test_set_credentials(self):
|
def test_set_credentials(self):
|
||||||
"""Test setting credentials."""
|
"""Test setting credentials."""
|
||||||
new_url = "https://newtest.atlassian.net/"
|
new_url = "https://newtest.atlassian.net/"
|
||||||
new_token = "new_token"
|
new_email = "new@example.com"
|
||||||
|
new_token = "new_api_token"
|
||||||
self.connector.set_credentials(new_url, new_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.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):
|
def test_get_headers(self):
|
||||||
"""Test header generation."""
|
"""Test header generation."""
|
||||||
headers = self.connector.get_headers()
|
headers = self.connector.get_headers()
|
||||||
|
|
||||||
self.assertIn('Content-Type', headers)
|
self.assertIn("Content-Type", headers)
|
||||||
self.assertIn('Authorization', headers)
|
self.assertIn("Authorization", headers)
|
||||||
self.assertIn('Accept', headers)
|
self.assertIn("Accept", headers)
|
||||||
self.assertEqual(headers['Content-Type'], 'application/json')
|
self.assertEqual(headers["Content-Type"], "application/json")
|
||||||
self.assertEqual(headers['Accept'], 'application/json')
|
self.assertEqual(headers["Accept"], "application/json")
|
||||||
self.assertTrue(headers['Authorization'].startswith('Bearer '))
|
self.assertTrue(headers["Authorization"].startswith("Basic "))
|
||||||
|
|
||||||
def test_get_headers_no_credentials(self):
|
def test_get_headers_no_credentials(self):
|
||||||
"""Test header generation without credentials."""
|
"""Test header generation without credentials."""
|
||||||
connector = JiraConnector()
|
connector = JiraConnector()
|
||||||
|
|
||||||
with self.assertRaises(ValueError) as context:
|
with self.assertRaises(ValueError) as context:
|
||||||
connector.get_headers()
|
connector.get_headers()
|
||||||
|
|
||||||
self.assertIn("Jira credentials not initialized", str(context.exception))
|
self.assertIn("Jira credentials not initialized", str(context.exception))
|
||||||
|
|
||||||
@patch('requests.get')
|
@patch("requests.get")
|
||||||
def test_make_api_request_success(self, mock_get):
|
def test_make_api_request_success(self, mock_get):
|
||||||
"""Test successful API request."""
|
"""Test successful API request."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
mock_response.status_code = 200
|
mock_response.status_code = 200
|
||||||
mock_response.json.return_value = {"test": "data"}
|
mock_response.json.return_value = {"test": "data"}
|
||||||
mock_get.return_value = mock_response
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
result = self.connector.make_api_request("test/endpoint")
|
result = self.connector.make_api_request("test/endpoint")
|
||||||
|
|
||||||
self.assertEqual(result, {"test": "data"})
|
self.assertEqual(result, {"test": "data"})
|
||||||
mock_get.assert_called_once()
|
mock_get.assert_called_once()
|
||||||
|
|
||||||
@patch('requests.get')
|
@patch("requests.get")
|
||||||
def test_make_api_request_failure(self, mock_get):
|
def test_make_api_request_failure(self, mock_get):
|
||||||
"""Test failed API request."""
|
"""Test failed API request."""
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
mock_response.status_code = 401
|
mock_response.status_code = 401
|
||||||
mock_response.text = "Unauthorized"
|
mock_response.text = "Unauthorized"
|
||||||
mock_get.return_value = mock_response
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
with self.assertRaises(Exception) as context:
|
with self.assertRaises(Exception) as context:
|
||||||
self.connector.make_api_request("test/endpoint")
|
self.connector.make_api_request("test/endpoint")
|
||||||
|
|
||||||
self.assertIn("API request failed with status code 401", str(context.exception))
|
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):
|
def test_get_all_projects(self, mock_api_request):
|
||||||
"""Test getting all projects."""
|
"""Test getting all projects."""
|
||||||
mock_api_request.return_value = {
|
mock_api_request.return_value = {
|
||||||
"values": [
|
"values": [
|
||||||
{"id": "1", "key": "TEST", "name": "Test Project"},
|
{"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()
|
projects = self.connector.get_all_projects()
|
||||||
|
|
||||||
self.assertEqual(len(projects), 2)
|
self.assertEqual(len(projects), 2)
|
||||||
self.assertEqual(projects[0]["key"], "TEST")
|
self.assertEqual(projects[0]["key"], "TEST")
|
||||||
self.assertEqual(projects[1]["key"], "DEMO")
|
self.assertEqual(projects[1]["key"], "DEMO")
|
||||||
mock_api_request.assert_called_once_with("project")
|
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):
|
def test_get_all_issues(self, mock_api_request):
|
||||||
"""Test getting all issues."""
|
"""Test getting all issues."""
|
||||||
mock_api_request.return_value = {
|
mock_api_request.return_value = {
|
||||||
|
@ -114,15 +122,15 @@ class TestJiraConnector(unittest.TestCase):
|
||||||
"issuetype": {"name": "Bug"},
|
"issuetype": {"name": "Bug"},
|
||||||
"project": {"key": "TEST"},
|
"project": {"key": "TEST"},
|
||||||
"created": "2023-01-01T10:00:00.000+0000",
|
"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()
|
issues = self.connector.get_all_issues()
|
||||||
|
|
||||||
self.assertEqual(len(issues), 1)
|
self.assertEqual(len(issues), 1)
|
||||||
self.assertEqual(issues[0]["key"], "TEST-1")
|
self.assertEqual(issues[0]["key"], "TEST-1")
|
||||||
self.assertEqual(issues[0]["fields"]["summary"], "Test Issue")
|
self.assertEqual(issues[0]["fields"]["summary"], "Test Issue")
|
||||||
|
@ -144,18 +152,18 @@ class TestJiraConnector(unittest.TestCase):
|
||||||
"reporter": {
|
"reporter": {
|
||||||
"accountId": "123",
|
"accountId": "123",
|
||||||
"displayName": "John Doe",
|
"displayName": "John Doe",
|
||||||
"emailAddress": "john@example.com"
|
"emailAddress": "john@example.com",
|
||||||
},
|
},
|
||||||
"assignee": {
|
"assignee": {
|
||||||
"accountId": "456",
|
"accountId": "456",
|
||||||
"displayName": "Jane Smith",
|
"displayName": "Jane Smith",
|
||||||
"emailAddress": "jane@example.com"
|
"emailAddress": "jane@example.com",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
formatted = self.connector.format_issue(raw_issue)
|
formatted = self.connector.format_issue(raw_issue)
|
||||||
|
|
||||||
self.assertEqual(formatted["id"], "1")
|
self.assertEqual(formatted["id"], "1")
|
||||||
self.assertEqual(formatted["key"], "TEST-1")
|
self.assertEqual(formatted["key"], "TEST-1")
|
||||||
self.assertEqual(formatted["title"], "Test Issue")
|
self.assertEqual(formatted["title"], "Test Issue")
|
||||||
|
@ -170,17 +178,17 @@ class TestJiraConnector(unittest.TestCase):
|
||||||
"""Test date formatting."""
|
"""Test date formatting."""
|
||||||
iso_date = "2023-01-01T10:30:00.000+0000"
|
iso_date = "2023-01-01T10:30:00.000+0000"
|
||||||
formatted_date = JiraConnector.format_date(iso_date)
|
formatted_date = JiraConnector.format_date(iso_date)
|
||||||
|
|
||||||
self.assertEqual(formatted_date, "2023-01-01 10:30:00")
|
self.assertEqual(formatted_date, "2023-01-01 10:30:00")
|
||||||
|
|
||||||
def test_format_date_invalid(self):
|
def test_format_date_invalid(self):
|
||||||
"""Test date formatting with invalid input."""
|
"""Test date formatting with invalid input."""
|
||||||
formatted_date = JiraConnector.format_date("invalid-date")
|
formatted_date = JiraConnector.format_date("invalid-date")
|
||||||
self.assertEqual(formatted_date, "invalid-date")
|
self.assertEqual(formatted_date, "invalid-date")
|
||||||
|
|
||||||
formatted_date = JiraConnector.format_date("")
|
formatted_date = JiraConnector.format_date("")
|
||||||
self.assertEqual(formatted_date, "Unknown date")
|
self.assertEqual(formatted_date, "Unknown date")
|
||||||
|
|
||||||
formatted_date = JiraConnector.format_date(None)
|
formatted_date = JiraConnector.format_date(None)
|
||||||
self.assertEqual(formatted_date, "Unknown date")
|
self.assertEqual(formatted_date, "Unknown date")
|
||||||
|
|
||||||
|
@ -198,11 +206,11 @@ class TestJiraConnector(unittest.TestCase):
|
||||||
"created_at": "2023-01-01T10:00:00.000+0000",
|
"created_at": "2023-01-01T10:00:00.000+0000",
|
||||||
"updated_at": "2023-01-01T12:00:00.000+0000",
|
"updated_at": "2023-01-01T12:00:00.000+0000",
|
||||||
"description": "Test Description",
|
"description": "Test Description",
|
||||||
"comments": []
|
"comments": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
markdown = self.connector.format_issue_to_markdown(formatted_issue)
|
markdown = self.connector.format_issue_to_markdown(formatted_issue)
|
||||||
|
|
||||||
self.assertIn("# TEST-1: Test Issue", markdown)
|
self.assertIn("# TEST-1: Test Issue", markdown)
|
||||||
self.assertIn("**Status:** Open", markdown)
|
self.assertIn("**Status:** Open", markdown)
|
||||||
self.assertIn("**Priority:** High", markdown)
|
self.assertIn("**Priority:** High", markdown)
|
||||||
|
@ -214,5 +222,5 @@ class TestJiraConnector(unittest.TestCase):
|
||||||
self.assertIn("Test Description", markdown)
|
self.assertIn("Test Description", markdown)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
from datetime import datetime
|
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, Any, Optional
|
from datetime import datetime
|
||||||
from pydantic import BaseModel, field_validator, ConfigDict
|
from typing import Any, Dict, Optional
|
||||||
from .base import IDModel, TimestampModel
|
|
||||||
from app.db import SearchSourceConnectorType
|
from app.db import SearchSourceConnectorType
|
||||||
|
from pydantic import BaseModel, ConfigDict, field_validator
|
||||||
|
|
||||||
|
from .base import IDModel, TimestampModel
|
||||||
|
|
||||||
|
|
||||||
class SearchSourceConnectorBase(BaseModel):
|
class SearchSourceConnectorBase(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
|
@ -11,105 +14,129 @@ class SearchSourceConnectorBase(BaseModel):
|
||||||
is_indexable: bool
|
is_indexable: bool
|
||||||
last_indexed_at: Optional[datetime] = None
|
last_indexed_at: Optional[datetime] = None
|
||||||
config: Dict[str, Any]
|
config: Dict[str, Any]
|
||||||
|
|
||||||
@field_validator('config')
|
@field_validator("config")
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_config_for_connector_type(cls, config: Dict[str, Any], values: Dict[str, Any]) -> Dict[str, Any]:
|
def validate_config_for_connector_type(
|
||||||
connector_type = values.data.get('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:
|
if connector_type == SearchSourceConnectorType.SERPER_API:
|
||||||
# For SERPER_API, only allow SERPER_API_KEY
|
# For SERPER_API, only allow SERPER_API_KEY
|
||||||
allowed_keys = ["SERPER_API_KEY"]
|
allowed_keys = ["SERPER_API_KEY"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the API key is not empty
|
||||||
if not config.get("SERPER_API_KEY"):
|
if not config.get("SERPER_API_KEY"):
|
||||||
raise ValueError("SERPER_API_KEY cannot be empty")
|
raise ValueError("SERPER_API_KEY cannot be empty")
|
||||||
|
|
||||||
elif connector_type == SearchSourceConnectorType.TAVILY_API:
|
elif connector_type == SearchSourceConnectorType.TAVILY_API:
|
||||||
# For TAVILY_API, only allow TAVILY_API_KEY
|
# For TAVILY_API, only allow TAVILY_API_KEY
|
||||||
allowed_keys = ["TAVILY_API_KEY"]
|
allowed_keys = ["TAVILY_API_KEY"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the API key is not empty
|
||||||
if not config.get("TAVILY_API_KEY"):
|
if not config.get("TAVILY_API_KEY"):
|
||||||
raise ValueError("TAVILY_API_KEY cannot be empty")
|
raise ValueError("TAVILY_API_KEY cannot be empty")
|
||||||
|
|
||||||
elif connector_type == SearchSourceConnectorType.LINKUP_API:
|
elif connector_type == SearchSourceConnectorType.LINKUP_API:
|
||||||
# For LINKUP_API, only allow LINKUP_API_KEY
|
# For LINKUP_API, only allow LINKUP_API_KEY
|
||||||
allowed_keys = ["LINKUP_API_KEY"]
|
allowed_keys = ["LINKUP_API_KEY"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the API key is not empty
|
||||||
if not config.get("LINKUP_API_KEY"):
|
if not config.get("LINKUP_API_KEY"):
|
||||||
raise ValueError("LINKUP_API_KEY cannot be empty")
|
raise ValueError("LINKUP_API_KEY cannot be empty")
|
||||||
|
|
||||||
elif connector_type == SearchSourceConnectorType.SLACK_CONNECTOR:
|
elif connector_type == SearchSourceConnectorType.SLACK_CONNECTOR:
|
||||||
# For SLACK_CONNECTOR, only allow SLACK_BOT_TOKEN
|
# For SLACK_CONNECTOR, only allow SLACK_BOT_TOKEN
|
||||||
allowed_keys = ["SLACK_BOT_TOKEN"]
|
allowed_keys = ["SLACK_BOT_TOKEN"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the bot token is not empty
|
||||||
if not config.get("SLACK_BOT_TOKEN"):
|
if not config.get("SLACK_BOT_TOKEN"):
|
||||||
raise ValueError("SLACK_BOT_TOKEN cannot be empty")
|
raise ValueError("SLACK_BOT_TOKEN cannot be empty")
|
||||||
|
|
||||||
elif connector_type == SearchSourceConnectorType.NOTION_CONNECTOR:
|
elif connector_type == SearchSourceConnectorType.NOTION_CONNECTOR:
|
||||||
# For NOTION_CONNECTOR, only allow NOTION_INTEGRATION_TOKEN
|
# For NOTION_CONNECTOR, only allow NOTION_INTEGRATION_TOKEN
|
||||||
allowed_keys = ["NOTION_INTEGRATION_TOKEN"]
|
allowed_keys = ["NOTION_INTEGRATION_TOKEN"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the integration token is not empty
|
||||||
if not config.get("NOTION_INTEGRATION_TOKEN"):
|
if not config.get("NOTION_INTEGRATION_TOKEN"):
|
||||||
raise ValueError("NOTION_INTEGRATION_TOKEN cannot be empty")
|
raise ValueError("NOTION_INTEGRATION_TOKEN cannot be empty")
|
||||||
|
|
||||||
elif connector_type == SearchSourceConnectorType.GITHUB_CONNECTOR:
|
elif connector_type == SearchSourceConnectorType.GITHUB_CONNECTOR:
|
||||||
# For GITHUB_CONNECTOR, only allow GITHUB_PAT and repo_full_names
|
# For GITHUB_CONNECTOR, only allow GITHUB_PAT and repo_full_names
|
||||||
allowed_keys = ["GITHUB_PAT", "repo_full_names"]
|
allowed_keys = ["GITHUB_PAT", "repo_full_names"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the token is not empty
|
||||||
if not config.get("GITHUB_PAT"):
|
if not config.get("GITHUB_PAT"):
|
||||||
raise ValueError("GITHUB_PAT cannot be empty")
|
raise ValueError("GITHUB_PAT cannot be empty")
|
||||||
|
|
||||||
# Ensure the repo_full_names is present and is a non-empty list
|
# Ensure the repo_full_names is present and is a non-empty list
|
||||||
repo_full_names = config.get("repo_full_names")
|
repo_full_names = config.get("repo_full_names")
|
||||||
if not isinstance(repo_full_names, list) or not 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")
|
raise ValueError("repo_full_names must be a non-empty list of strings")
|
||||||
|
|
||||||
elif connector_type == SearchSourceConnectorType.LINEAR_CONNECTOR:
|
elif connector_type == SearchSourceConnectorType.LINEAR_CONNECTOR:
|
||||||
# For LINEAR_CONNECTOR, only allow LINEAR_API_KEY
|
# For LINEAR_CONNECTOR, only allow LINEAR_API_KEY
|
||||||
allowed_keys = ["LINEAR_API_KEY"]
|
allowed_keys = ["LINEAR_API_KEY"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the token is not empty
|
||||||
if not config.get("LINEAR_API_KEY"):
|
if not config.get("LINEAR_API_KEY"):
|
||||||
raise ValueError("LINEAR_API_KEY cannot be empty")
|
raise ValueError("LINEAR_API_KEY cannot be empty")
|
||||||
|
|
||||||
elif connector_type == SearchSourceConnectorType.DISCORD_CONNECTOR:
|
elif connector_type == SearchSourceConnectorType.DISCORD_CONNECTOR:
|
||||||
# For DISCORD_CONNECTOR, only allow DISCORD_BOT_TOKEN
|
# For DISCORD_CONNECTOR, only allow DISCORD_BOT_TOKEN
|
||||||
allowed_keys = ["DISCORD_BOT_TOKEN"]
|
allowed_keys = ["DISCORD_BOT_TOKEN"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the bot token is not empty
|
||||||
if not config.get("DISCORD_BOT_TOKEN"):
|
if not config.get("DISCORD_BOT_TOKEN"):
|
||||||
raise ValueError("DISCORD_BOT_TOKEN cannot be empty")
|
raise ValueError("DISCORD_BOT_TOKEN cannot be empty")
|
||||||
elif connector_type == SearchSourceConnectorType.JIRA_CONNECTOR:
|
elif connector_type == SearchSourceConnectorType.JIRA_CONNECTOR:
|
||||||
# For JIRA_CONNECTOR, allow JIRA_PERSONAL_ACCESS_TOKEN and JIRA_BASE_URL
|
# For JIRA_CONNECTOR, require JIRA_EMAIL, JIRA_API_TOKEN and JIRA_BASE_URL
|
||||||
allowed_keys = ["JIRA_PERSONAL_ACCESS_TOKEN", "JIRA_BASE_URL"]
|
allowed_keys = ["JIRA_EMAIL", "JIRA_API_TOKEN", "JIRA_BASE_URL"]
|
||||||
if set(config.keys()) != set(allowed_keys):
|
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
|
# Ensure the email is not empty
|
||||||
if not config.get("JIRA_PERSONAL_ACCESS_TOKEN"):
|
if not config.get("JIRA_EMAIL"):
|
||||||
raise ValueError("JIRA_PERSONAL_ACCESS_TOKEN cannot be empty")
|
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
|
# Ensure the base URL is not empty
|
||||||
if not config.get("JIRA_BASE_URL"):
|
if not config.get("JIRA_BASE_URL"):
|
||||||
|
@ -117,9 +144,11 @@ class SearchSourceConnectorBase(BaseModel):
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
class SearchSourceConnectorCreate(SearchSourceConnectorBase):
|
class SearchSourceConnectorCreate(SearchSourceConnectorBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SearchSourceConnectorUpdate(BaseModel):
|
class SearchSourceConnectorUpdate(BaseModel):
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
connector_type: Optional[SearchSourceConnectorType] = None
|
connector_type: Optional[SearchSourceConnectorType] = None
|
||||||
|
@ -127,7 +156,8 @@ class SearchSourceConnectorUpdate(BaseModel):
|
||||||
last_indexed_at: Optional[datetime] = None
|
last_indexed_at: Optional[datetime] = None
|
||||||
config: Optional[Dict[str, Any]] = None
|
config: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
|
||||||
class SearchSourceConnectorRead(SearchSourceConnectorBase, IDModel, TimestampModel):
|
class SearchSourceConnectorRead(SearchSourceConnectorBase, IDModel, TimestampModel):
|
||||||
user_id: uuid.UUID
|
user_id: uuid.UUID
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
@ -2041,10 +2042,11 @@ async def index_jira_issues(
|
||||||
return 0, f"Connector with ID {connector_id} not found"
|
return 0, f"Connector with ID {connector_id} not found"
|
||||||
|
|
||||||
# Get the Jira credentials from the connector config
|
# 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")
|
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(
|
await task_logger.log_task_failure(
|
||||||
log_entry,
|
log_entry,
|
||||||
f"Jira credentials not found in connector config for connector {connector_id}",
|
f"Jira credentials not found in connector config for connector {connector_id}",
|
||||||
|
@ -2061,7 +2063,7 @@ async def index_jira_issues(
|
||||||
)
|
)
|
||||||
|
|
||||||
jira_client = JiraConnector(
|
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
|
# 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
|
start_date=start_date_str, end_date=end_date_str, include_comments=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print(json.dumps(issues, indent=2))
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
logger.error(f"Failed to get Jira issues: {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"
|
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,
|
log_entry,
|
||||||
f"No Jira issues found in date range {start_date_str} to {end_date_str}",
|
f"No Jira issues found in date range {start_date_str} to {end_date_str}",
|
||||||
{"indexed_count": 0},
|
{"issues_found": 0},
|
||||||
)
|
)
|
||||||
return 0, None
|
return 0, None
|
||||||
else:
|
else:
|
||||||
|
@ -2132,7 +2136,7 @@ async def index_jira_issues(
|
||||||
await task_logger.log_task_progress(
|
await task_logger.log_task_progress(
|
||||||
log_entry,
|
log_entry,
|
||||||
f"Retrieved {len(issues)} issues from Jira API",
|
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:
|
except Exception as e:
|
||||||
|
@ -2254,10 +2258,10 @@ async def index_jira_issues(
|
||||||
await session.commit()
|
await session.commit()
|
||||||
logger.info(f"Updated last_indexed_at to {connector.last_indexed_at}")
|
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,
|
log_entry,
|
||||||
f"Successfully indexed {indexed_count} Jira issues",
|
f"Successfully indexed {indexed_count} Jira issues",
|
||||||
{"indexed_count": indexed_count},
|
{"issues_indexed": indexed_count},
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Successfully indexed {indexed_count} Jira issues")
|
logger.info(f"Successfully indexed {indexed_count} Jira issues")
|
||||||
|
|
|
@ -193,10 +193,17 @@ export default function EditConnectorPage() {
|
||||||
/>
|
/>
|
||||||
<EditSimpleTokenForm
|
<EditSimpleTokenForm
|
||||||
control={editForm.control}
|
control={editForm.control}
|
||||||
fieldName="JIRA_PERSONAL_ACCESS_TOKEN"
|
fieldName="JIRA_EMAIL"
|
||||||
fieldLabel="Jira Personal Access Token"
|
fieldLabel="Jira Email"
|
||||||
fieldDescription="Update your Jira Personal Access Token if needed."
|
fieldDescription="Update your Atlassian account email if needed."
|
||||||
placeholder="Your Jira Personal Access Token"
|
placeholder="your.email@company.com"
|
||||||
|
/>
|
||||||
|
<EditSimpleTokenForm
|
||||||
|
control={editForm.control}
|
||||||
|
fieldName="JIRA_API_TOKEN"
|
||||||
|
fieldLabel="Jira API Token"
|
||||||
|
fieldDescription="Update your Jira API Token if needed."
|
||||||
|
placeholder="Your Jira API Token"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -57,8 +57,11 @@ const jiraConnectorFormSchema = z.object({
|
||||||
message: "Please enter a valid Jira instance URL",
|
message: "Please enter a valid Jira instance URL",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
personal_access_token: z.string().min(10, {
|
email: z.string().email({
|
||||||
message: "Jira Personal Access Token is required and must be valid.",
|
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: {
|
defaultValues: {
|
||||||
name: "Jira Connector",
|
name: "Jira Connector",
|
||||||
base_url: "",
|
base_url: "",
|
||||||
personal_access_token: "",
|
email: "",
|
||||||
|
api_token: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,7 +95,8 @@ export default function JiraConnectorPage() {
|
||||||
connector_type: "JIRA_CONNECTOR",
|
connector_type: "JIRA_CONNECTOR",
|
||||||
config: {
|
config: {
|
||||||
JIRA_BASE_URL: values.base_url,
|
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,
|
is_indexable: true,
|
||||||
last_indexed_at: null,
|
last_indexed_at: null,
|
||||||
|
@ -210,20 +215,40 @@ export default function JiraConnectorPage() {
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="personal_access_token"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Personal Access Token</FormLabel>
|
<FormLabel>Email Address</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="email"
|
||||||
placeholder="Your Jira Personal Access Token"
|
placeholder="your.email@company.com"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Your Jira Personal Access Token will be encrypted
|
Your Atlassian account email address.
|
||||||
and stored securely.
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="api_token"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>API Token</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Your Jira API Token"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Your Jira API Token will be encrypted and stored securely.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -296,8 +321,8 @@ export default function JiraConnectorPage() {
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold mb-2">How it works</h3>
|
<h3 className="text-xl font-semibold mb-2">How it works</h3>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
The Jira connector uses the Jira REST API to fetch all
|
The Jira connector uses the Jira REST API with Basic Authentication
|
||||||
issues and comments that the Personal Access Token has
|
to fetch all issues and comments that your account has
|
||||||
access to within your Jira instance.
|
access to within your Jira instance.
|
||||||
</p>
|
</p>
|
||||||
<ul className="mt-2 list-disc pl-5 text-muted-foreground">
|
<ul className="mt-2 list-disc pl-5 text-muted-foreground">
|
||||||
|
@ -324,15 +349,14 @@ export default function JiraConnectorPage() {
|
||||||
<AlertTitle>Read-Only Access is Sufficient</AlertTitle>
|
<AlertTitle>Read-Only Access is Sufficient</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
You only need read access for this connector to work.
|
You only need read access for this connector to work.
|
||||||
The Personal Access Token will only be used to read
|
The API Token will only be used to read your Jira data.
|
||||||
your Jira data.
|
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">
|
<h4 className="font-medium mb-2">
|
||||||
Step 1: Create a Personal Access Token
|
Step 1: Create an API Token
|
||||||
</h4>
|
</h4>
|
||||||
<ol className="list-decimal pl-5 space-y-3">
|
<ol className="list-decimal pl-5 space-y-3">
|
||||||
<li>Log in to your Atlassian account</li>
|
<li>Log in to your Atlassian account</li>
|
||||||
|
@ -369,10 +393,10 @@ export default function JiraConnectorPage() {
|
||||||
Step 2: Grant necessary access
|
Step 2: Grant necessary access
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-muted-foreground mb-3">
|
<p className="text-muted-foreground mb-3">
|
||||||
The Personal Access Token will have access to all
|
The API Token will have access to all projects and
|
||||||
projects and issues that your user account can see.
|
issues that your user account can see. Make sure your
|
||||||
Make sure your account has appropriate permissions
|
account has appropriate permissions for the projects
|
||||||
for the projects you want to index.
|
you want to index.
|
||||||
</p>
|
</p>
|
||||||
<Alert className="bg-muted">
|
<Alert className="bg-muted">
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue