From 914dc27a8f7f5255cae1624fc930d23621d52221 Mon Sep 17 00:00:00 2001 From: Bill Yang <45103519+goldflag@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:58:01 -0700 Subject: [PATCH] Add Dropbox connector (#956) * start dropbox connector * add wip ui * polish ui * Fix some ci * ignore types * addressed, fixed, and tested all comments * ran prettier * ran mypy fixes --------- Co-authored-by: Bill Yang Co-authored-by: hagen-danswer --- backend/danswer/configs/constants.py | 1 + .../danswer/connectors/dropbox/__init__.py | 0 .../danswer/connectors/dropbox/connector.py | 151 +++++++++++++ backend/danswer/connectors/factory.py | 2 + backend/requirements/default.txt | 1 + web/public/Dropbox.png | Bin 0 -> 43377 bytes web/src/app/admin/connectors/dropbox/page.tsx | 209 ++++++++++++++++++ web/src/components/icons/icons.tsx | 13 ++ web/src/lib/sources.ts | 6 + web/src/lib/types.ts | 8 + 10 files changed, 391 insertions(+) create mode 100644 backend/danswer/connectors/dropbox/__init__.py create mode 100644 backend/danswer/connectors/dropbox/connector.py create mode 100644 web/public/Dropbox.png create mode 100644 web/src/app/admin/connectors/dropbox/page.tsx diff --git a/backend/danswer/configs/constants.py b/backend/danswer/configs/constants.py index 641738a4c..58a782541 100644 --- a/backend/danswer/configs/constants.py +++ b/backend/danswer/configs/constants.py @@ -93,6 +93,7 @@ class DocumentSource(str, Enum): GOOGLE_SITES = "google_sites" ZENDESK = "zendesk" LOOPIO = "loopio" + DROPBOX = "dropbox" SHAREPOINT = "sharepoint" TEAMS = "teams" DISCOURSE = "discourse" diff --git a/backend/danswer/connectors/dropbox/__init__.py b/backend/danswer/connectors/dropbox/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/danswer/connectors/dropbox/connector.py b/backend/danswer/connectors/dropbox/connector.py new file mode 100644 index 000000000..2fd39948a --- /dev/null +++ b/backend/danswer/connectors/dropbox/connector.py @@ -0,0 +1,151 @@ +from datetime import timezone +from io import BytesIO +from typing import Any + +from dropbox import Dropbox # type: ignore +from dropbox.exceptions import ApiError # type:ignore +from dropbox.files import FileMetadata # type:ignore +from dropbox.files import FolderMetadata # type:ignore + +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 LoadConnector +from danswer.connectors.interfaces import PollConnector +from danswer.connectors.interfaces import SecondsSinceUnixEpoch +from danswer.connectors.models import ConnectorMissingCredentialError +from danswer.connectors.models import Document +from danswer.connectors.models import Section +from danswer.file_processing.extract_file_text import extract_file_text +from danswer.utils.logger import setup_logger + + +logger = setup_logger() + + +class DropboxConnector(LoadConnector, PollConnector): + def __init__(self, batch_size: int = INDEX_BATCH_SIZE) -> None: + self.batch_size = batch_size + self.dropbox_client: Dropbox | None = None + + def load_credentials(self, credentials: dict[str, Any]) -> dict[str, Any] | None: + self.dropbox_client = Dropbox(credentials["dropbox_access_token"]) + return None + + def _download_file(self, path: str) -> bytes: + """Download a single file from Dropbox.""" + if self.dropbox_client is None: + raise ConnectorMissingCredentialError("Dropbox") + _, resp = self.dropbox_client.files_download(path) + return resp.content + + def _get_shared_link(self, path: str) -> str: + """Create a shared link for a file in Dropbox.""" + if self.dropbox_client is None: + raise ConnectorMissingCredentialError("Dropbox") + + try: + # Check if a shared link already exists + shared_links = self.dropbox_client.sharing_list_shared_links(path=path) + if shared_links.links: + return shared_links.links[0].url + + link_metadata = ( + self.dropbox_client.sharing_create_shared_link_with_settings(path) + ) + return link_metadata.url + except ApiError as err: + logger.exception(f"Failed to create a shared link for {path}: {err}") + return "" + + def _yield_files_recursive( + self, + path: str, + start: SecondsSinceUnixEpoch | None, + end: SecondsSinceUnixEpoch | None, + ) -> GenerateDocumentsOutput: + """Yield files in batches from a specified Dropbox folder, including subfolders.""" + if self.dropbox_client is None: + raise ConnectorMissingCredentialError("Dropbox") + + result = self.dropbox_client.files_list_folder( + path, + limit=self.batch_size, + recursive=False, + include_non_downloadable_files=False, + ) + + while True: + batch: list[Document] = [] + for entry in result.entries: + if isinstance(entry, FileMetadata): + modified_time = entry.client_modified + if modified_time.tzinfo is None: + # If no timezone info, assume it is UTC + modified_time = modified_time.replace(tzinfo=timezone.utc) + else: + # If not in UTC, translate it + modified_time = modified_time.astimezone(timezone.utc) + + time_as_seconds = int(modified_time.timestamp()) + if start and time_as_seconds < start: + continue + if end and time_as_seconds > end: + continue + + downloaded_file = self._download_file(entry.path_display) + link = self._get_shared_link(entry.path_display) + try: + text = extract_file_text(entry.name, BytesIO(downloaded_file)) + batch.append( + Document( + id=f"doc:{entry.id}", + sections=[Section(link=link, text=text)], + source=DocumentSource.DROPBOX, + semantic_identifier=entry.name, + doc_updated_at=modified_time, + metadata={"type": "article"}, + ) + ) + except Exception as e: + logger.exception( + f"Error decoding file {entry.path_display} as utf-8 error occurred: {e}" + ) + + elif isinstance(entry, FolderMetadata): + yield from self._yield_files_recursive(entry.path_lower, start, end) + + if batch: + yield batch + + if not result.has_more: + break + + result = self.dropbox_client.files_list_folder_continue(result.cursor) + + def load_from_state(self) -> GenerateDocumentsOutput: + return self.poll_source(None, None) + + def poll_source( + self, start: SecondsSinceUnixEpoch | None, end: SecondsSinceUnixEpoch | None + ) -> GenerateDocumentsOutput: + if self.dropbox_client is None: + raise ConnectorMissingCredentialError("Dropbox") + + for batch in self._yield_files_recursive("", start, end): + yield batch + + return None + + +if __name__ == "__main__": + import os + + connector = DropboxConnector() + connector.load_credentials( + { + "dropbox_access_token": os.environ["DROPBOX_ACCESS_TOKEN"], + } + ) + document_batches = connector.load_from_state() + print(next(document_batches)) diff --git a/backend/danswer/connectors/factory.py b/backend/danswer/connectors/factory.py index cb0d41b8a..37ecd8f59 100644 --- a/backend/danswer/connectors/factory.py +++ b/backend/danswer/connectors/factory.py @@ -8,6 +8,7 @@ from danswer.connectors.confluence.connector import ConfluenceConnector from danswer.connectors.danswer_jira.connector import JiraConnector 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.github.connector import GithubConnector from danswer.connectors.gitlab.connector import GitlabConnector @@ -74,6 +75,7 @@ def identify_connector_class( DocumentSource.GOOGLE_SITES: GoogleSitesConnector, DocumentSource.ZENDESK: ZendeskConnector, DocumentSource.LOOPIO: LoopioConnector, + DocumentSource.DROPBOX: DropboxConnector, DocumentSource.SHAREPOINT: SharepointConnector, DocumentSource.TEAMS: TeamsConnector, DocumentSource.DISCOURSE: DiscourseConnector, diff --git a/backend/requirements/default.txt b/backend/requirements/default.txt index 6052624ad..e25f02b7a 100644 --- a/backend/requirements/default.txt +++ b/backend/requirements/default.txt @@ -69,3 +69,4 @@ uvicorn==0.21.1 zulip==0.8.2 hubspot-api-client==8.1.0 zenpy==2.0.41 +dropbox==11.36.2 diff --git a/web/public/Dropbox.png b/web/public/Dropbox.png new file mode 100644 index 0000000000000000000000000000000000000000..cd83e09eb6622919b416e635d611c43893a07a4f GIT binary patch literal 43377 zcmYhi1yq#Z^EkW$(%s!D-6dU1mvl*YmvkdYEg>BuEh*h7(%oIsjlk0NKKl86|K~ji z*Sqt~ow_q~XJ&V!)l}rrkcp8&AP|~7SXu)FLd*pIfe>Kfr7XGZw&xJFaNg&9tCZKJof)R-30~gfr1bJJ=z8Z zZ-WAXzyG!ar2eggPe8$&pnx4v@IUzfd-~tP^&bm>zW?T5?f?*=ruB~(uss-9{7)VL z$Lr`X9pKOJf3gK1{2#mE^?!T+A^(R1kODLcp8coPKY~7ce{uW)G5#$8%KrZ&{^9pO zIsnK2jsL@|PO0_)qx%nEtm0UjOO{Z2!*={|xw7cmVl7ss7(C zz%qLvpKZ|pqyK*k0PR1H0E=Ca<1#4d-&_Cxor2&04CoIS|9@ft3*dBtjliJ*0{kb8 zFJPAcjQ@`UV0l31|BeHI|39?9>wk3o{-FWuzh?xH`bXz4{(ox#{!g#JhTZ#1=kKn6 z`u@ibcnd55N&dd>|HJvW{_p93uYYL*1ph}9cneVY5BPWEKWYDB{%`&F=|53{*Z+_K z$o~)F<>kd3MKcy~-CYk26-^LE;x}ZSVSa_p3G!FL&rcaB*t@)_RjZO(hd279Bk07{ zLi;qyuxsb|g*@7^9X3D4)iTf+3<{bG>+y~1?u zjdh_&JNM?%N#^vPsPgrc@kuLY(nI@Fhqwg&0_&iy+`8cGo(3gO*T#{^tx>)5pK^r! zpLECLWZhR(JnX%UECU6cV|@feTcV39+Dh49TO4Z|E#6Zs8%nE}tZ{tF(&UqJ5EE$7 zZJRXbu2n0>XO_(zO=-5zn>H~hE3!+j0RJe=jgK@nO9+ez@$d>tbw#ofPXjc5Wu_=6 zz1L`Omkhi?@Q_!QLD)mWLq_3?&5G*=q8%hJE%ENd((zMt*t^*rfj@j`t|v~&m{>@m zp)#0hY;6)SUxS-5dtNhPS{63E%J7nUYQa>a4JDK5c^&s@nJ_yY%p;&K7uQ3`6OWTT z5}WDcynAvsyMDe&3@IvaJ6!WS?Z_J!n=sn*@7l|4UDsTno&ZH3%r685s|Sbs?|lhF zgZXiaE?BQ2cDQ*boLd?2xdID#OE>d9I{)S#K`!SSK z1`j__K8^A1c$jC$DXN#q`jq>12>}`!M3FnwTKl@PXmMX0I+odxhg*?dIfx07+RHXr z?7Flfp^RF>CBpySJNhFt(R zNyJ-JLYi&j#H%E3%)}z#B4?R5j4;MS|$O9jL*S(q5eZ+`F{_5JB^ew5T zV0(rD0;^-JRi>Rk?IHp9iENhsw}!V9jNW{`gHMsKPBn*_nzQ4}LJaM(9qId?U>4!6 zOoeL0BRLo~>#*u0BmA+pwe(kuPqmHJolu0vNBCdxSM?WtYgVR;rlqE^wFkvn%pLPr zL8zoN`zOn(3qcA$zNFvuzG9T)n2oiBQ~@i%q?BG$WIc& z5PknTp;0#3N!kh>m zhVW=JOxd)au}<`ln?4f#{Wv1AXlk4`h1E!Wa9V#}sr1%hB|O15{`;~hjq{N(G&TtJ zmi?h((0f%eC;$9IcBGH8sh?m`!eZGW_~UO4q9^M2VmaRJK1oevc0VMK36CI|P-b!;P}K}pAX(`uWF7*I+%wq)7>bl}4w-Ogoq` zCs2*sJZ*Xc=5dzplk^nVk9B+q0M&xP%^kA#(lrTpXy=)1t)iozHD%uyh* z?Z&HV*1Fw*r(va?#BJZx8FmpnM-F6VdBJ5Tk$h6NLX4UN@<8bIwzj+Un46o_f}qbC zwL6@&jYK|nYl#W3^0xfLj|q1-cfENkO0EznU}D=NW!#p@Cw8WR!66!h5nRk@=IU>S z-J7*7)C}}1S@DIWlPk8h!L9cCjqOSJU5%(oC?)F|nT2nUd8S{_EE3JX)7XZo%pC}s zr!Qt?0vy>2wX~q|nx`@(sLseStHS;bhLVnU?p>=q?BOnu25jrL(WYgKM{ng1Yu(

19rC;Uy7sCh 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≀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 { + const { popup, setPopup } = usePopup(); + + const { mutate } = useSWRConfig(); + const { + data: connectorIndexingStatuses, + isLoading: isConnectorIndexingStatusesLoading, + error: isConnectorIndexingStatusesError, + } = useSWR[]>( + "/api/manage/admin/connector/indexing-status", + fetcher + ); + const { + data: credentialsData, + isLoading: isCredentialsLoading, + error: isCredentialsError, + refreshCredentials, + } = usePublicCredentials(); + + if ( + (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) || + (!credentialsData && isCredentialsLoading) + ) { + return ; + } + + if (isConnectorIndexingStatusesError || !connectorIndexingStatuses) { + return

Failed to load connectors
; + } + + if (isCredentialsError || !credentialsData) { + return
Failed to load credentials
; + } + + const dropboxConnectorIndexingStatuses: ConnectorIndexingStatus< + DropboxConfig, + DropboxCredentialJson + >[] = connectorIndexingStatuses.filter( + (connectorIndexingStatus) => + connectorIndexingStatus.connector.source === "dropbox" + ); + const dropboxCredential: Credential | undefined = + credentialsData.find( + (credential) => credential.credential_json?.dropbox_access_token + ); + + return ( + <> + {popup} + + Provide your API details + + + {dropboxCredential ? ( + <> +
+

Existing API Token:

+

+ {dropboxCredential.credential_json?.dropbox_access_token} +

+ +
+ + ) : ( + <> + + See the Dropbox connector{" "} + + setup guide + {" "} + on the Danswer docs to obtain a Dropbox token. + + + + formBody={ + <> + + + } + validationSchema={Yup.object().shape({ + dropbox_access_token: Yup.string().required( + "Please enter your Dropbox API token" + ), + })} + initialValues={{ + dropbox_access_token: "", + }} + onSubmit={(isSuccess) => { + if (isSuccess) { + refreshCredentials(); + mutate("/api/manage/admin/connector/indexing-status"); + } + }} + /> + + + )} + + {dropboxConnectorIndexingStatuses.length > 0 && ( + <> + + Dropbox indexing status + + + The latest article changes are fetched every 10 minutes. + +
+ + connectorIndexingStatuses={dropboxConnectorIndexingStatuses} + liveCredential={dropboxCredential} + onCredentialLink={async (connectorId) => { + if (dropboxCredential) { + await linkCredential(connectorId, dropboxCredential.id); + mutate("/api/manage/admin/connector/indexing-status"); + } + }} + onUpdate={() => + mutate("/api/manage/admin/connector/indexing-status") + } + /> +
+ + )} + + {dropboxCredential && dropboxConnectorIndexingStatuses.length === 0 && ( + <> + +

Create Connection

+

+ Press connect below to start the connection to your Dropbox + instance. +

+ + nameBuilder={(values) => `Dropbox`} + ccPairNameBuilder={(values) => `Dropbox`} + source="dropbox" + inputType="poll" + formBody={<>} + validationSchema={Yup.object().shape({})} + initialValues={{}} + refreshFreq={10 * 60} // 10 minutes + credentialId={dropboxCredential.id} + /> +
+ + )} + + ); +}; + +export default function Page() { + return ( +
+
+ +
+ } title="Dropbox" /> +
+
+ ); +} diff --git a/web/src/components/icons/icons.tsx b/web/src/components/icons/icons.tsx index 8cbec72c4..04d003a59 100644 --- a/web/src/components/icons/icons.tsx +++ b/web/src/components/icons/icons.tsx @@ -51,6 +51,7 @@ import hubSpotIcon from "../../../public/HubSpot.png"; import document360Icon from "../../../public/Document360.png"; import googleSitesIcon from "../../../public/GoogleSites.png"; import zendeskIcon from "../../../public/Zendesk.svg"; +import dropboxIcon from "../../../public/Dropbox.png"; import sharepointIcon from "../../../public/Sharepoint.png"; import teamsIcon from "../../../public/Teams.png"; import mediawikiIcon from "../../../public/MediaWiki.svg"; @@ -617,6 +618,18 @@ export const ZendeskIcon = ({ ); +export const DropboxIcon = ({ + size = 16, + className = defaultTailwindCSS, +}: IconProps) => ( +
+ Logo +
+); + export const DiscourseIcon = ({ size = 16, className = defaultTailwindCSS, diff --git a/web/src/lib/sources.ts b/web/src/lib/sources.ts index 303108394..597f45f4d 100644 --- a/web/src/lib/sources.ts +++ b/web/src/lib/sources.ts @@ -4,6 +4,7 @@ import { ConfluenceIcon, DiscourseIcon, Document360Icon, + DropboxIcon, FileIcon, GithubIcon, GitlabIcon, @@ -154,6 +155,11 @@ const SOURCE_METADATA_MAP: SourceMap = { displayName: "Loopio", category: SourceCategory.AppConnection, }, + dropbox: { + icon: DropboxIcon, + displayName: "Dropbox", + category: SourceCategory.AppConnection, + }, sharepoint: { icon: SharepointIcon, displayName: "Sharepoint", diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 55cfe700c..f20173d1d 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -42,6 +42,7 @@ export type ValidSources = | "file" | "google_sites" | "loopio" + | "dropbox" | "sharepoint" | "teams" | "zendesk" @@ -191,6 +192,8 @@ export interface GoogleSitesConfig { export interface ZendeskConfig {} +export interface DropboxConfig {} + export interface MediaWikiBaseConfig { connector_name: string; language_code: string; @@ -198,6 +201,7 @@ export interface MediaWikiBaseConfig { pages?: string[]; recurse_depth?: number; } + export interface MediaWikiConfig extends MediaWikiBaseConfig { hostname: string; } @@ -362,6 +366,10 @@ export interface ZendeskCredentialJson { zendesk_token: string; } +export interface DropboxCredentialJson { + dropbox_access_token: string; +} + export interface SharepointCredentialJson { sp_client_id: string; sp_client_secret: string;