mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 17:26:23 +02:00
chore: ran linting
This commit is contained in:
parent
772150eb66
commit
de8841fb86
110 changed files with 2673 additions and 1918 deletions
|
|
@ -283,9 +283,7 @@ class ComposioService:
|
|||
timeout=timeout,
|
||||
)
|
||||
status = getattr(account, "status", "UNKNOWN")
|
||||
logger.info(
|
||||
f"Composio account {connected_account_id} is now {status}"
|
||||
)
|
||||
logger.info(f"Composio account {connected_account_id} is now {status}")
|
||||
return status
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
|
|
|||
|
|
@ -67,7 +67,10 @@ class ConfluenceKBSyncService:
|
|||
content_hash = unique_hash
|
||||
|
||||
user_llm = await get_user_long_context_llm(
|
||||
self.db_session, user_id, search_space_id, disable_streaming=True,
|
||||
self.db_session,
|
||||
user_id,
|
||||
search_space_id,
|
||||
disable_streaming=True,
|
||||
)
|
||||
|
||||
doc_metadata_for_summary = {
|
||||
|
|
@ -116,17 +119,26 @@ class ConfluenceKBSyncService:
|
|||
|
||||
logger.info(
|
||||
"KB sync after create succeeded: doc_id=%s, page=%s",
|
||||
document.id, page_title,
|
||||
document.id,
|
||||
page_title,
|
||||
)
|
||||
return {"status": "success"}
|
||||
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
if "duplicate key value violates unique constraint" in error_str or "uniqueviolationerror" in error_str:
|
||||
if (
|
||||
"duplicate key value violates unique constraint" in error_str
|
||||
or "uniqueviolationerror" in error_str
|
||||
):
|
||||
await self.db_session.rollback()
|
||||
return {"status": "error", "message": "Duplicate document detected"}
|
||||
|
||||
logger.error("KB sync after create failed for page %s: %s", page_title, e, exc_info=True)
|
||||
logger.error(
|
||||
"KB sync after create failed for page %s: %s",
|
||||
page_title,
|
||||
e,
|
||||
exc_info=True,
|
||||
)
|
||||
await self.db_session.rollback()
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
|
@ -215,11 +227,14 @@ class ConfluenceKBSyncService:
|
|||
|
||||
logger.info(
|
||||
"KB sync successful for document %s (%s)",
|
||||
document_id, page_title,
|
||||
document_id,
|
||||
page_title,
|
||||
)
|
||||
return {"status": "success"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("KB sync failed for document %s: %s", document_id, e, exc_info=True)
|
||||
logger.error(
|
||||
"KB sync failed for document %s: %s", document_id, e, exc_info=True
|
||||
)
|
||||
await self.db_session.rollback()
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class ConfluenceToolMetadataService:
|
|||
|
||||
async def _check_account_health(self, connector: SearchSourceConnector) -> bool:
|
||||
"""Check if the Confluence connector auth is still valid.
|
||||
|
||||
|
||||
Returns True if auth is expired/invalid, False if healthy.
|
||||
"""
|
||||
try:
|
||||
|
|
@ -112,7 +112,7 @@ class ConfluenceToolMetadataService:
|
|||
|
||||
async def get_creation_context(self, search_space_id: int, user_id: str) -> dict:
|
||||
"""Return context needed to create a new Confluence page.
|
||||
|
||||
|
||||
Fetches all connected accounts, and for the first healthy one fetches spaces.
|
||||
"""
|
||||
connectors = await self._get_all_confluence_connectors(search_space_id, user_id)
|
||||
|
|
@ -126,10 +126,12 @@ class ConfluenceToolMetadataService:
|
|||
for connector in connectors:
|
||||
auth_expired = await self._check_account_health(connector)
|
||||
workspace = ConfluenceWorkspace.from_connector(connector)
|
||||
accounts.append({
|
||||
**workspace.to_dict(),
|
||||
"auth_expired": auth_expired,
|
||||
})
|
||||
accounts.append(
|
||||
{
|
||||
**workspace.to_dict(),
|
||||
"auth_expired": auth_expired,
|
||||
}
|
||||
)
|
||||
|
||||
if not auth_expired and not fetched_context:
|
||||
try:
|
||||
|
|
@ -146,7 +148,8 @@ class ConfluenceToolMetadataService:
|
|||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to fetch Confluence spaces for connector %s: %s",
|
||||
connector.id, e,
|
||||
connector.id,
|
||||
e,
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
@ -158,7 +161,7 @@ class ConfluenceToolMetadataService:
|
|||
self, search_space_id: int, user_id: str, page_ref: str
|
||||
) -> dict:
|
||||
"""Return context needed to update an indexed Confluence page.
|
||||
|
||||
|
||||
Resolves the page from KB, then fetches current content and version from API.
|
||||
"""
|
||||
document = await self._resolve_page(search_space_id, user_id, page_ref)
|
||||
|
|
@ -191,7 +194,11 @@ class ConfluenceToolMetadataService:
|
|||
await client.close()
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
if "401" in error_str or "403" in error_str or "authentication" in error_str:
|
||||
if (
|
||||
"401" in error_str
|
||||
or "403" in error_str
|
||||
or "authentication" in error_str
|
||||
):
|
||||
return {
|
||||
"error": f"Failed to fetch Confluence page: {e!s}",
|
||||
"auth_expired": True,
|
||||
|
|
@ -207,7 +214,9 @@ class ConfluenceToolMetadataService:
|
|||
body_storage = storage.get("value", "")
|
||||
|
||||
version_obj = page_data.get("version", {})
|
||||
version_number = version_obj.get("number", 1) if isinstance(version_obj, dict) else 1
|
||||
version_number = (
|
||||
version_obj.get("number", 1) if isinstance(version_obj, dict) else 1
|
||||
)
|
||||
|
||||
return {
|
||||
"account": {**workspace.to_dict(), "auth_expired": False},
|
||||
|
|
@ -263,9 +272,7 @@ class ConfluenceToolMetadataService:
|
|||
Document.document_type == DocumentType.CONFLUENCE_CONNECTOR,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
or_(
|
||||
func.lower(
|
||||
Document.document_metadata.op("->>")("page_title")
|
||||
)
|
||||
func.lower(Document.document_metadata.op("->>")("page_title"))
|
||||
== ref_lower,
|
||||
func.lower(Document.title) == ref_lower,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -183,10 +183,12 @@ class GmailToolMetadataService:
|
|||
and_(
|
||||
SearchSourceConnector.search_space_id == search_space_id,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
SearchSourceConnector.connector_type.in_([
|
||||
SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
|
||||
SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
|
||||
]),
|
||||
SearchSourceConnector.connector_type.in_(
|
||||
[
|
||||
SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
|
||||
SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
.order_by(SearchSourceConnector.last_indexed_at.desc())
|
||||
|
|
@ -223,9 +225,7 @@ class GmailToolMetadataService:
|
|||
service = build("gmail", "v1", credentials=creds)
|
||||
profile = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: service.users()
|
||||
.getProfile(userId="me")
|
||||
.execute(),
|
||||
lambda: service.users().getProfile(userId="me").execute(),
|
||||
)
|
||||
acc_dict["email"] = profile.get("emailAddress", "")
|
||||
except Exception:
|
||||
|
|
@ -306,10 +306,12 @@ class GmailToolMetadataService:
|
|||
|
||||
draft = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: service.users()
|
||||
.drafts()
|
||||
.get(userId="me", id=draft_id, format="full")
|
||||
.execute(),
|
||||
lambda: (
|
||||
service.users()
|
||||
.drafts()
|
||||
.get(userId="me", id=draft_id, format="full")
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
|
||||
payload = draft.get("message", {}).get("payload", {})
|
||||
|
|
@ -422,15 +424,15 @@ class GmailToolMetadataService:
|
|||
.filter(
|
||||
and_(
|
||||
Document.search_space_id == search_space_id,
|
||||
Document.document_type.in_([
|
||||
DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
DocumentType.COMPOSIO_GMAIL_CONNECTOR,
|
||||
]),
|
||||
Document.document_type.in_(
|
||||
[
|
||||
DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
DocumentType.COMPOSIO_GMAIL_CONNECTOR,
|
||||
]
|
||||
),
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
or_(
|
||||
func.lower(
|
||||
cast(Document.document_metadata["subject"], String)
|
||||
)
|
||||
func.lower(cast(Document.document_metadata["subject"], String))
|
||||
== func.lower(email_ref),
|
||||
func.lower(Document.title) == func.lower(email_ref),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,12 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.future import select
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from app.db import Document, DocumentType, SearchSourceConnector, SearchSourceConnectorType
|
||||
from app.db import (
|
||||
Document,
|
||||
DocumentType,
|
||||
SearchSourceConnector,
|
||||
SearchSourceConnectorType,
|
||||
)
|
||||
from app.services.llm_service import get_user_long_context_llm
|
||||
from app.utils.document_converters import (
|
||||
create_document_chunks,
|
||||
|
|
@ -107,7 +112,9 @@ class GoogleCalendarKBSyncService:
|
|||
)
|
||||
else:
|
||||
logger.warning("No LLM configured -- using fallback summary")
|
||||
summary_content = f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
|
||||
summary_content = (
|
||||
f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
|
||||
)
|
||||
summary_embedding = embed_text(summary_content)
|
||||
|
||||
chunks = await create_document_chunks(indexable_content)
|
||||
|
|
@ -201,12 +208,16 @@ class GoogleCalendarKBSyncService:
|
|||
None, lambda: build("calendar", "v3", credentials=creds)
|
||||
)
|
||||
|
||||
calendar_id = (document.document_metadata or {}).get("calendar_id", "primary")
|
||||
calendar_id = (document.document_metadata or {}).get(
|
||||
"calendar_id", "primary"
|
||||
)
|
||||
live_event = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: service.events()
|
||||
.get(calendarId=calendar_id, eventId=event_id)
|
||||
.execute(),
|
||||
lambda: (
|
||||
service.events()
|
||||
.get(calendarId=calendar_id, eventId=event_id)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
|
||||
event_summary = live_event.get("summary", "")
|
||||
|
|
@ -220,7 +231,10 @@ class GoogleCalendarKBSyncService:
|
|||
end_time = end_data.get("dateTime", end_data.get("date", ""))
|
||||
|
||||
attendees = [
|
||||
{"email": a.get("email", ""), "responseStatus": a.get("responseStatus", "")}
|
||||
{
|
||||
"email": a.get("email", ""),
|
||||
"responseStatus": a.get("responseStatus", ""),
|
||||
}
|
||||
for a in live_event.get("attendees", [])
|
||||
]
|
||||
|
||||
|
|
@ -252,7 +266,9 @@ class GoogleCalendarKBSyncService:
|
|||
indexable_content, user_llm, doc_metadata_for_summary
|
||||
)
|
||||
else:
|
||||
summary_content = f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
|
||||
summary_content = (
|
||||
f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
|
||||
)
|
||||
summary_embedding = embed_text(summary_content)
|
||||
|
||||
chunks = await create_document_chunks(indexable_content)
|
||||
|
|
@ -313,7 +329,10 @@ class GoogleCalendarKBSyncService:
|
|||
if not connector:
|
||||
raise ValueError(f"Connector {connector_id} not found")
|
||||
|
||||
if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
|
||||
if (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
|
||||
):
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
return build_composio_credentials(cca_id)
|
||||
|
|
@ -328,11 +347,17 @@ class GoogleCalendarKBSyncService:
|
|||
if token_encrypted and app_config.SECRET_KEY:
|
||||
token_encryption = TokenEncryption(app_config.SECRET_KEY)
|
||||
if config_data.get("token"):
|
||||
config_data["token"] = token_encryption.decrypt_token(config_data["token"])
|
||||
config_data["token"] = token_encryption.decrypt_token(
|
||||
config_data["token"]
|
||||
)
|
||||
if config_data.get("refresh_token"):
|
||||
config_data["refresh_token"] = token_encryption.decrypt_token(config_data["refresh_token"])
|
||||
config_data["refresh_token"] = token_encryption.decrypt_token(
|
||||
config_data["refresh_token"]
|
||||
)
|
||||
if config_data.get("client_secret"):
|
||||
config_data["client_secret"] = token_encryption.decrypt_token(config_data["client_secret"])
|
||||
config_data["client_secret"] = token_encryption.decrypt_token(
|
||||
config_data["client_secret"]
|
||||
)
|
||||
|
||||
exp = config_data.get("expiry", "")
|
||||
if exp:
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ class GoogleCalendarAccount:
|
|||
name: str
|
||||
|
||||
@classmethod
|
||||
def from_connector(cls, connector: SearchSourceConnector) -> "GoogleCalendarAccount":
|
||||
def from_connector(
|
||||
cls, connector: SearchSourceConnector
|
||||
) -> "GoogleCalendarAccount":
|
||||
return cls(id=connector.id, name=connector.name)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
|
|
@ -93,7 +95,10 @@ class GoogleCalendarToolMetadataService:
|
|||
self._db_session = db_session
|
||||
|
||||
async def _build_credentials(self, connector: SearchSourceConnector) -> Credentials:
|
||||
if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
|
||||
if (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
|
||||
):
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
return build_composio_credentials(cca_id)
|
||||
|
|
@ -108,11 +113,17 @@ class GoogleCalendarToolMetadataService:
|
|||
if token_encrypted and app_config.SECRET_KEY:
|
||||
token_encryption = TokenEncryption(app_config.SECRET_KEY)
|
||||
if config_data.get("token"):
|
||||
config_data["token"] = token_encryption.decrypt_token(config_data["token"])
|
||||
config_data["token"] = token_encryption.decrypt_token(
|
||||
config_data["token"]
|
||||
)
|
||||
if config_data.get("refresh_token"):
|
||||
config_data["refresh_token"] = token_encryption.decrypt_token(config_data["refresh_token"])
|
||||
config_data["refresh_token"] = token_encryption.decrypt_token(
|
||||
config_data["refresh_token"]
|
||||
)
|
||||
if config_data.get("client_secret"):
|
||||
config_data["client_secret"] = token_encryption.decrypt_token(config_data["client_secret"])
|
||||
config_data["client_secret"] = token_encryption.decrypt_token(
|
||||
config_data["client_secret"]
|
||||
)
|
||||
|
||||
exp = config_data.get("expiry", "")
|
||||
if exp:
|
||||
|
|
@ -149,10 +160,12 @@ class GoogleCalendarToolMetadataService:
|
|||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(
|
||||
None,
|
||||
lambda: build("calendar", "v3", credentials=creds)
|
||||
.calendarList()
|
||||
.list(maxResults=1)
|
||||
.execute(),
|
||||
lambda: (
|
||||
build("calendar", "v3", credentials=creds)
|
||||
.calendarList()
|
||||
.list(maxResults=1)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
|
|
@ -252,11 +265,13 @@ class GoogleCalendarToolMetadataService:
|
|||
None, lambda: service.calendarList().list().execute()
|
||||
)
|
||||
for cal in cal_list.get("items", []):
|
||||
calendars.append({
|
||||
"id": cal.get("id", ""),
|
||||
"summary": cal.get("summary", ""),
|
||||
"primary": cal.get("primary", False),
|
||||
})
|
||||
calendars.append(
|
||||
{
|
||||
"id": cal.get("id", ""),
|
||||
"summary": cal.get("summary", ""),
|
||||
"primary": cal.get("primary", False),
|
||||
}
|
||||
)
|
||||
|
||||
tz_setting = await loop.run_in_executor(
|
||||
None,
|
||||
|
|
@ -314,23 +329,34 @@ class GoogleCalendarToolMetadataService:
|
|||
calendar_id = event.calendar_id or "primary"
|
||||
live_event = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: service.events()
|
||||
.get(calendarId=calendar_id, eventId=event.event_id)
|
||||
.execute(),
|
||||
lambda: (
|
||||
service.events()
|
||||
.get(calendarId=calendar_id, eventId=event.event_id)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
|
||||
event_dict["summary"] = live_event.get("summary", event_dict["summary"])
|
||||
event_dict["description"] = live_event.get("description", event_dict["description"])
|
||||
event_dict["description"] = live_event.get(
|
||||
"description", event_dict["description"]
|
||||
)
|
||||
event_dict["location"] = live_event.get("location", event_dict["location"])
|
||||
|
||||
start_data = live_event.get("start", {})
|
||||
event_dict["start"] = start_data.get("dateTime", start_data.get("date", event_dict["start"]))
|
||||
event_dict["start"] = start_data.get(
|
||||
"dateTime", start_data.get("date", event_dict["start"])
|
||||
)
|
||||
|
||||
end_data = live_event.get("end", {})
|
||||
event_dict["end"] = end_data.get("dateTime", end_data.get("date", event_dict["end"]))
|
||||
event_dict["end"] = end_data.get(
|
||||
"dateTime", end_data.get("date", event_dict["end"])
|
||||
)
|
||||
|
||||
event_dict["attendees"] = [
|
||||
{"email": a.get("email", ""), "responseStatus": a.get("responseStatus", "")}
|
||||
{
|
||||
"email": a.get("email", ""),
|
||||
"responseStatus": a.get("responseStatus", ""),
|
||||
}
|
||||
for a in live_event.get("attendees", [])
|
||||
]
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ class GoogleDriveKBSyncService:
|
|||
|
||||
indexable_content = (content or "").strip()
|
||||
if not indexable_content:
|
||||
indexable_content = f"Google Drive file: {file_name} (type: {mime_type})"
|
||||
indexable_content = (
|
||||
f"Google Drive file: {file_name} (type: {mime_type})"
|
||||
)
|
||||
|
||||
content_hash = generate_content_hash(indexable_content, search_space_id)
|
||||
|
||||
|
|
@ -93,7 +95,9 @@ class GoogleDriveKBSyncService:
|
|||
)
|
||||
else:
|
||||
logger.warning("No LLM configured — using fallback summary")
|
||||
summary_content = f"Google Drive File: {file_name}\n\n{indexable_content}"
|
||||
summary_content = (
|
||||
f"Google Drive File: {file_name}\n\n{indexable_content}"
|
||||
)
|
||||
summary_embedding = embed_text(summary_content)
|
||||
|
||||
chunks = await create_document_chunks(indexable_content)
|
||||
|
|
|
|||
|
|
@ -133,10 +133,12 @@ class GoogleDriveToolMetadataService:
|
|||
and_(
|
||||
SearchSourceConnector.id == document.connector_id,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
SearchSourceConnector.connector_type.in_([
|
||||
SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
|
||||
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||
]),
|
||||
SearchSourceConnector.connector_type.in_(
|
||||
[
|
||||
SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
|
||||
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -168,10 +170,12 @@ class GoogleDriveToolMetadataService:
|
|||
and_(
|
||||
SearchSourceConnector.search_space_id == search_space_id,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
SearchSourceConnector.connector_type.in_([
|
||||
SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
|
||||
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||
]),
|
||||
SearchSourceConnector.connector_type.in_(
|
||||
[
|
||||
SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
|
||||
SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
.order_by(SearchSourceConnector.last_indexed_at.desc())
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ class JiraKBSyncService:
|
|||
if existing:
|
||||
logger.info(
|
||||
"Document for Jira issue %s already exists (doc_id=%s), skipping",
|
||||
issue_identifier, existing.id,
|
||||
issue_identifier,
|
||||
existing.id,
|
||||
)
|
||||
return {"status": "success"}
|
||||
|
||||
|
|
@ -61,7 +62,9 @@ class JiraKBSyncService:
|
|||
if not indexable_content:
|
||||
indexable_content = f"Jira Issue {issue_identifier}: {issue_title}"
|
||||
|
||||
issue_content = f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
|
||||
issue_content = (
|
||||
f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
|
||||
)
|
||||
|
||||
content_hash = generate_content_hash(issue_content, search_space_id)
|
||||
|
||||
|
|
@ -73,7 +76,10 @@ class JiraKBSyncService:
|
|||
content_hash = unique_hash
|
||||
|
||||
user_llm = await get_user_long_context_llm(
|
||||
self.db_session, user_id, search_space_id, disable_streaming=True,
|
||||
self.db_session,
|
||||
user_id,
|
||||
search_space_id,
|
||||
disable_streaming=True,
|
||||
)
|
||||
|
||||
doc_metadata_for_summary = {
|
||||
|
|
@ -88,7 +94,9 @@ class JiraKBSyncService:
|
|||
issue_content, user_llm, doc_metadata_for_summary
|
||||
)
|
||||
else:
|
||||
summary_content = f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
|
||||
summary_content = (
|
||||
f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
|
||||
)
|
||||
summary_embedding = embed_text(summary_content)
|
||||
|
||||
chunks = await create_document_chunks(issue_content)
|
||||
|
|
@ -122,17 +130,26 @@ class JiraKBSyncService:
|
|||
|
||||
logger.info(
|
||||
"KB sync after create succeeded: doc_id=%s, issue=%s",
|
||||
document.id, issue_identifier,
|
||||
document.id,
|
||||
issue_identifier,
|
||||
)
|
||||
return {"status": "success"}
|
||||
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
if "duplicate key value violates unique constraint" in error_str or "uniqueviolationerror" in error_str:
|
||||
if (
|
||||
"duplicate key value violates unique constraint" in error_str
|
||||
or "uniqueviolationerror" in error_str
|
||||
):
|
||||
await self.db_session.rollback()
|
||||
return {"status": "error", "message": "Duplicate document detected"}
|
||||
|
||||
logger.error("KB sync after create failed for issue %s: %s", issue_identifier, e, exc_info=True)
|
||||
logger.error(
|
||||
"KB sync after create failed for issue %s: %s",
|
||||
issue_identifier,
|
||||
e,
|
||||
exc_info=True,
|
||||
)
|
||||
await self.db_session.rollback()
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
|
@ -189,14 +206,18 @@ class JiraKBSyncService:
|
|||
issue_content, user_llm, doc_meta
|
||||
)
|
||||
else:
|
||||
summary_content = f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
|
||||
summary_content = (
|
||||
f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
|
||||
)
|
||||
summary_embedding = embed_text(summary_content)
|
||||
|
||||
chunks = await create_document_chunks(issue_content)
|
||||
|
||||
document.title = f"{issue_identifier}: {issue_title}"
|
||||
document.content = summary_content
|
||||
document.content_hash = generate_content_hash(issue_content, search_space_id)
|
||||
document.content_hash = generate_content_hash(
|
||||
issue_content, search_space_id
|
||||
)
|
||||
document.embedding = summary_embedding
|
||||
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
|
@ -219,11 +240,15 @@ class JiraKBSyncService:
|
|||
|
||||
logger.info(
|
||||
"KB sync successful for document %s (%s: %s)",
|
||||
document_id, issue_identifier, issue_title,
|
||||
document_id,
|
||||
issue_identifier,
|
||||
issue_title,
|
||||
)
|
||||
return {"status": "success"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("KB sync failed for document %s: %s", document_id, e, exc_info=True)
|
||||
logger.error(
|
||||
"KB sync failed for document %s: %s", document_id, e, exc_info=True
|
||||
)
|
||||
await self.db_session.rollback()
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class JiraToolMetadataService:
|
|||
|
||||
async def _check_account_health(self, connector: SearchSourceConnector) -> bool:
|
||||
"""Check if the Jira connector auth is still valid.
|
||||
|
||||
|
||||
Returns True if auth is expired/invalid, False if healthy.
|
||||
"""
|
||||
try:
|
||||
|
|
@ -98,9 +98,7 @@ class JiraToolMetadataService:
|
|||
await asyncio.to_thread(jira_client.get_myself)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Jira connector %s health check failed: %s", connector.id, e
|
||||
)
|
||||
logger.warning("Jira connector %s health check failed: %s", connector.id, e)
|
||||
try:
|
||||
connector.config = {**connector.config, "auth_expired": True}
|
||||
flag_modified(connector, "config")
|
||||
|
|
@ -116,7 +114,7 @@ class JiraToolMetadataService:
|
|||
|
||||
async def get_creation_context(self, search_space_id: int, user_id: str) -> dict:
|
||||
"""Return context needed to create a new Jira issue.
|
||||
|
||||
|
||||
Fetches all connected Jira accounts, and for the first healthy one
|
||||
fetches projects, issue types, and priorities.
|
||||
"""
|
||||
|
|
@ -165,7 +163,8 @@ class JiraToolMetadataService:
|
|||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to fetch Jira context for connector %s: %s",
|
||||
connector.id, e,
|
||||
connector.id,
|
||||
e,
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
@ -179,7 +178,7 @@ class JiraToolMetadataService:
|
|||
self, search_space_id: int, user_id: str, issue_ref: str
|
||||
) -> dict:
|
||||
"""Return context needed to update an indexed Jira issue.
|
||||
|
||||
|
||||
Resolves the issue from the KB, then fetches current details from the Jira API.
|
||||
"""
|
||||
document = await self._resolve_issue(search_space_id, user_id, issue_ref)
|
||||
|
|
@ -209,13 +208,15 @@ class JiraToolMetadataService:
|
|||
session=self._db_session, connector_id=connector.id
|
||||
)
|
||||
jira_client = await jira_history._get_jira_client()
|
||||
issue_data = await asyncio.to_thread(
|
||||
jira_client.get_issue, issue.issue_id
|
||||
)
|
||||
issue_data = await asyncio.to_thread(jira_client.get_issue, issue.issue_id)
|
||||
formatted = jira_client.format_issue(issue_data)
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
if "401" in error_str or "403" in error_str or "authentication" in error_str:
|
||||
if (
|
||||
"401" in error_str
|
||||
or "403" in error_str
|
||||
or "authentication" in error_str
|
||||
):
|
||||
return {
|
||||
"error": f"Failed to fetch Jira issue: {e!s}",
|
||||
"auth_expired": True,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ class LinearKBSyncService:
|
|||
if not indexable_content:
|
||||
indexable_content = f"Linear Issue {issue_identifier}: {issue_title}"
|
||||
|
||||
issue_content = f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
|
||||
issue_content = (
|
||||
f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
|
||||
)
|
||||
|
||||
content_hash = generate_content_hash(issue_content, search_space_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -190,7 +190,11 @@ class LinearToolMetadataService:
|
|||
issue_api = await self._fetch_issue_context(linear_client, issue.id)
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
if "401" in error_str or "authentication" in error_str or "re-authenticate" in error_str:
|
||||
if (
|
||||
"401" in error_str
|
||||
or "authentication" in error_str
|
||||
or "re-authenticate" in error_str
|
||||
):
|
||||
return {
|
||||
"error": f"Failed to fetch Linear issue context: {e!s}",
|
||||
"auth_expired": True,
|
||||
|
|
|
|||
|
|
@ -102,7 +102,10 @@ class NotionToolMetadataService:
|
|||
)
|
||||
db_connector = result.scalar_one_or_none()
|
||||
if db_connector and not db_connector.config.get("auth_expired"):
|
||||
db_connector.config = {**db_connector.config, "auth_expired": True}
|
||||
db_connector.config = {
|
||||
**db_connector.config,
|
||||
"auth_expired": True,
|
||||
}
|
||||
flag_modified(db_connector, "config")
|
||||
await self._db_session.commit()
|
||||
await self._db_session.refresh(db_connector)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue