From 5c521a79165808dfd08732a5c87bd3d38da421c1 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Sat, 5 Oct 2024 16:32:48 -0700 Subject: [PATCH] validated --- backend/danswer/configs/app_configs.py | 2 +- backend/danswer/configs/constants.py | 1 + backend/danswer/connectors/factory.py | 2 + .../danswer/connectors/freshdesk/connector.py | 141 ++++++++++++++++++ .../docker_compose/docker-compose.dev.yml | 2 +- .../docker_compose/docker-compose.gpu-dev.yml | 2 +- .../docker-compose.search-testing.yml | 2 +- web/public/Freshdesk.png | Bin 0 -> 18386 bytes web/src/components/icons/icons.tsx | 15 ++ web/src/lib/connectors/connectors.ts | 4 + web/src/lib/connectors/credentials.ts | 16 ++ web/src/lib/sources.ts | 7 + web/src/lib/types.ts | 1 + 13 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 backend/danswer/connectors/freshdesk/connector.py create mode 100644 web/public/Freshdesk.png diff --git a/backend/danswer/configs/app_configs.py b/backend/danswer/configs/app_configs.py index 4857e2aa9..5b9acf249 100644 --- a/backend/danswer/configs/app_configs.py +++ b/backend/danswer/configs/app_configs.py @@ -135,7 +135,7 @@ POSTGRES_PASSWORD = urllib.parse.quote_plus( os.environ.get("POSTGRES_PASSWORD") or "password" ) POSTGRES_HOST = os.environ.get("POSTGRES_HOST") or "localhost" -POSTGRES_PORT = os.environ.get("POSTGRES_PORT") or "5432" +POSTGRES_PORT = os.environ.get("POSTGRES_PORT") or "5433" POSTGRES_DB = os.environ.get("POSTGRES_DB") or "postgres" # defaults to False diff --git a/backend/danswer/configs/constants.py b/backend/danswer/configs/constants.py index c26c2fbd6..5090bd527 100644 --- a/backend/danswer/configs/constants.py +++ b/backend/danswer/configs/constants.py @@ -74,6 +74,7 @@ CELERY_PRIMARY_WORKER_LOCK_TIMEOUT = 120 class DocumentSource(str, Enum): # Special case, document passed in via Danswer APIs without specifying a source type INGESTION_API = "ingestion_api" + FRESHDESK = "freshdesk" SLACK = "slack" WEB = "web" GOOGLE_DRIVE = "google_drive" diff --git a/backend/danswer/connectors/factory.py b/backend/danswer/connectors/factory.py index 75e0d9bb2..7f97e4684 100644 --- a/backend/danswer/connectors/factory.py +++ b/backend/danswer/connectors/factory.py @@ -15,6 +15,7 @@ from danswer.connectors.discourse.connector import DiscourseConnector from danswer.connectors.document360.connector import Document360Connector from danswer.connectors.dropbox.connector import DropboxConnector from danswer.connectors.file.connector import LocalFileConnector +from danswer.connectors.freshdesk.connector import FreshdeskConnector from danswer.connectors.github.connector import GithubConnector from danswer.connectors.gitlab.connector import GitlabConnector from danswer.connectors.gmail.connector import GmailConnector @@ -58,6 +59,7 @@ def identify_connector_class( input_type: InputType | None = None, ) -> Type[BaseConnector]: connector_map = { + DocumentSource.FRESHDESK: FreshdeskConnector, DocumentSource.WEB: WebConnector, DocumentSource.FILE: LocalFileConnector, DocumentSource.SLACK: { diff --git a/backend/danswer/connectors/freshdesk/connector.py b/backend/danswer/connectors/freshdesk/connector.py new file mode 100644 index 000000000..9580d1b6f --- /dev/null +++ b/backend/danswer/connectors/freshdesk/connector.py @@ -0,0 +1,141 @@ +import json +from datetime import datetime +from typing import Any +from typing import List +from typing import Optional + +import requests +from bs4 import BeautifulSoup # Add this import for HTML parsing + +from danswer.configs.app_configs import INDEX_BATCH_SIZE +from danswer.configs.constants import DocumentSource +from danswer.connectors.interfaces import GenerateDocumentsOutput +from danswer.connectors.interfaces import PollConnector +from danswer.connectors.models import ConnectorMissingCredentialError +from danswer.connectors.models import Document +from danswer.connectors.models import Section +from danswer.utils.logger import setup_logger + +logger = setup_logger() + + +class FreshdeskConnector(PollConnector): + def __init__( + self, + api_key: str, + domain: str, + password: str, + batch_size: int = INDEX_BATCH_SIZE, + ) -> None: + self.api_key = api_key + self.domain = domain + self.password = password + self.batch_size = batch_size + + def ticket_link(self, tid: int) -> str: + return f"https://{self.domain}.freshdesk.com/helpdesk/tickets/{tid}" + + def build_doc_sections_from_ticket(self, ticket: dict) -> List[Section]: + # Use list comprehension for building sections + return [ + Section( + link=self.ticket_link(int(ticket["id"])), + text=json.dumps( + { + key: value + for key, value in ticket.items() + if isinstance(value, str) + }, + default=str, + ), + ) + ] + + def strip_html_tags(self, html: str) -> str: + soup = BeautifulSoup(html, "html.parser") + return soup.get_text() + + def load_credentials(self, credentials: dict[str, Any]) -> Optional[dict[str, Any]]: + logger.info("Loading credentials") + self.api_key = credentials.get("freshdesk_api_key") + self.domain = credentials.get("freshdesk_domain") + self.password = credentials.get("freshdesk_password") + return None + + def _process_tickets( + self, start: datetime, end: datetime + ) -> GenerateDocumentsOutput: + logger.info("Processing tickets") + if any([self.api_key, self.domain, self.password]) is None: + raise ConnectorMissingCredentialError("freshdesk") + + freshdesk_url = ( + f"https://{self.domain}.freshdesk.com/api/v2/tickets?include=description" + ) + response = requests.get(freshdesk_url, auth=(self.api_key, self.password)) + response.raise_for_status() # raises exception when not a 2xx response + + if response.status_code != 204: + tickets = json.loads(response.content) + logger.info(f"Fetched {len(tickets)} tickets from Freshdesk API") + doc_batch: List[Document] = [] + + for ticket in tickets: + # Convert the "created_at", "updated_at", and "due_by" values to ISO 8601 strings + for date_field in ["created_at", "updated_at", "due_by"]: + ticket[date_field] = datetime.fromisoformat( + ticket[date_field] + ).strftime("%Y-%m-%d %H:%M:%S") + + # Convert all other values to strings + ticket = { + key: str(value) if not isinstance(value, str) else value + for key, value in ticket.items() + } + + # Checking for overdue tickets + today = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ticket["overdue"] = "true" if today > ticket["due_by"] else "false" + + # Mapping the status field values + status_mapping = {2: "open", 3: "pending", 4: "resolved", 5: "closed"} + ticket["status"] = status_mapping.get( + ticket["status"], str(ticket["status"]) + ) + + # Stripping HTML tags from the description field + ticket["description"] = self.strip_html_tags(ticket["description"]) + + # Remove extra white spaces from the description field + ticket["description"] = " ".join(ticket["description"].split()) + + # Use list comprehension for building sections + sections = self.build_doc_sections_from_ticket(ticket) + + created_at = datetime.fromisoformat(ticket["created_at"]) + today = datetime.now() + if (today - created_at).days / 30.4375 <= 2: + doc = Document( + id=ticket["id"], + sections=sections, + source=DocumentSource.FRESHDESK, + semantic_identifier=ticket["subject"], + metadata={ + key: value + for key, value in ticket.items() + if isinstance(value, str) + and key not in ["description", "description_text"] + }, + ) + + doc_batch.append(doc) + + if len(doc_batch) >= self.batch_size: + yield doc_batch + doc_batch = [] + + if doc_batch: + yield doc_batch + + def poll_source(self, start: datetime, end: datetime) -> GenerateDocumentsOutput: + yield from self._process_tickets(start, end) diff --git a/deployment/docker_compose/docker-compose.dev.yml b/deployment/docker_compose/docker-compose.dev.yml index 86d988e7d..52dda002b 100644 --- a/deployment/docker_compose/docker-compose.dev.yml +++ b/deployment/docker_compose/docker-compose.dev.yml @@ -298,7 +298,7 @@ services: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} ports: - - "5432:5432" + - "5433:5432" volumes: - db_volume:/var/lib/postgresql/data diff --git a/deployment/docker_compose/docker-compose.gpu-dev.yml b/deployment/docker_compose/docker-compose.gpu-dev.yml index ebce01ead..55038dd9e 100644 --- a/deployment/docker_compose/docker-compose.gpu-dev.yml +++ b/deployment/docker_compose/docker-compose.gpu-dev.yml @@ -308,7 +308,7 @@ services: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} ports: - - "5432:5432" + - "5433:5432" volumes: - db_volume:/var/lib/postgresql/data diff --git a/deployment/docker_compose/docker-compose.search-testing.yml b/deployment/docker_compose/docker-compose.search-testing.yml index f9be3360d..7757420fd 100644 --- a/deployment/docker_compose/docker-compose.search-testing.yml +++ b/deployment/docker_compose/docker-compose.search-testing.yml @@ -154,7 +154,7 @@ services: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} ports: - - "5432" + - "5433" volumes: - db_volume:/var/lib/postgresql/data diff --git a/web/public/Freshdesk.png b/web/public/Freshdesk.png new file mode 100644 index 0000000000000000000000000000000000000000..a3343ceb01d473601cf3522ca809dfea79d34c97 GIT binary patch literal 18386 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 X)1C$M`J{G100000NkvXXu0mjf8T-ry literal 0 HcmV?d00001 diff --git a/web/src/components/icons/icons.tsx b/web/src/components/icons/icons.tsx index 186e4473c..686f2b15b 100644 --- a/web/src/components/icons/icons.tsx +++ b/web/src/components/icons/icons.tsx @@ -52,6 +52,7 @@ import litellmIcon from "../../../public/LiteLLM.jpg"; import awsWEBP from "../../../public/Amazon.webp"; import azureIcon from "../../../public/Azure.png"; +import freshdeskIcon from "../../../public/Freshdesk.png"; import asanaIcon from "../../../public/Asana.png"; import anthropicSVG from "../../../public/Anthropic.svg"; import nomicSVG from "../../../public/nomic.svg"; @@ -1260,6 +1261,20 @@ export const AWSIcon = ({ ); }; +export const FreshdeskIcon = ({ + size = 16, + className = defaultTailwindCSS, +}: IconProps) => { + return ( +
+ Logo +
+ ); +}; + export const AzureIcon = ({ size = 16, className = defaultTailwindCSS, diff --git a/web/src/lib/connectors/connectors.ts b/web/src/lib/connectors/connectors.ts index 61ae2b076..c93568f43 100644 --- a/web/src/lib/connectors/connectors.ts +++ b/web/src/lib/connectors/connectors.ts @@ -542,6 +542,10 @@ For example, specifying .*-support.* as a "channel" will cause the connector to }, ], }, + freshdesk: { + description: "Configure Freshdesk connector", + values: [], + }, clickup: { description: "Configure ClickUp connector", values: [ diff --git a/web/src/lib/connectors/credentials.ts b/web/src/lib/connectors/credentials.ts index d7bcef0ad..3b5884e78 100644 --- a/web/src/lib/connectors/credentials.ts +++ b/web/src/lib/connectors/credentials.ts @@ -19,6 +19,12 @@ export interface GithubCredentialJson { github_access_token: string; } +export interface FreshdeskCredentialJson { + freshdesk_api_key: string; + freshdesk_password: string; + freshdesk_domain: string; +} + export interface GitlabCredentialJson { gitlab_url: string; gitlab_access_token: string; @@ -248,6 +254,11 @@ export const credentialTemplates: Record = { asana: { asana_api_token_secret: "", } as AsanaCredentialJson, + freshdesk: { + freshdesk_api_key: "", + freshdesk_password: "", + freshdesk_domain: "", + } as FreshdeskCredentialJson, teams: { teams_client_id: "", teams_client_secret: "", @@ -353,6 +364,11 @@ export const credentialDisplayNames: Record = { // Zulip zuliprc_content: "Zuliprc Content", + // Freshdesk + freshdesk_api_key: "Freshdesk API Key", + freshdesk_password: "Freshdesk Password", + freshdesk_domain: "Freshdesk Domain", + // Guru guru_user: "Guru User", guru_user_token: "Guru User Token", diff --git a/web/src/lib/sources.ts b/web/src/lib/sources.ts index 7347964a7..f61585db3 100644 --- a/web/src/lib/sources.ts +++ b/web/src/lib/sources.ts @@ -38,6 +38,7 @@ import { GoogleStorageIcon, ColorSlackIcon, XenforoIcon, + FreshdeskIcon, } from "@/components/icons/icons"; import { ValidSources } from "./types"; import { @@ -59,6 +60,12 @@ type SourceMap = { }; const SOURCE_METADATA_MAP: SourceMap = { + freshdesk: { + icon: FreshdeskIcon, + displayName: "Freshdesk", + category: SourceCategory.CustomerSupport, + docs: "https://docs.danswer.dev/connectors/freshdesk", + }, web: { icon: GlobeIcon, displayName: "Web", diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index f47517808..ccdcc457e 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -262,6 +262,7 @@ const validSources = [ "oci_storage", "not_applicable", "ingestion_api", + "freshdesk", ] as const; export type ValidSources = (typeof validSources)[number];