From 1670d923aa4c0d8cc0c8299b9e01a25122cbfeac Mon Sep 17 00:00:00 2001 From: Rutik Thakre <58112334+Rutik7066@users.noreply.github.com> Date: Fri, 19 Jan 2024 05:13:17 +0530 Subject: [PATCH] Gitlab Connector (#931) --- backend/danswer/configs/constants.py | 1 + backend/danswer/connectors/factory.py | 2 + backend/danswer/connectors/gitlab/__init__.py | 0 .../danswer/connectors/gitlab/connector.py | 182 +++++++++++++ backend/requirements/default.txt | 1 + web/public/Gitlab.png | Bin 0 -> 19675 bytes web/src/app/admin/connectors/gitlab/page.tsx | 253 ++++++++++++++++++ .../admin/connectors/ConnectorTitle.tsx | 9 +- web/src/components/icons/icons.tsx | 15 +- web/src/lib/sources.ts | 6 + web/src/lib/types.ts | 14 + 11 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 backend/danswer/connectors/gitlab/__init__.py create mode 100644 backend/danswer/connectors/gitlab/connector.py create mode 100644 web/public/Gitlab.png create mode 100644 web/src/app/admin/connectors/gitlab/page.tsx diff --git a/backend/danswer/configs/constants.py b/backend/danswer/configs/constants.py index febf816ce..791bf030f 100644 --- a/backend/danswer/configs/constants.py +++ b/backend/danswer/configs/constants.py @@ -63,6 +63,7 @@ class DocumentSource(str, Enum): GOOGLE_DRIVE = "google_drive" REQUESTTRACKER = "requesttracker" GITHUB = "github" + GITLAB = "gitlab" GURU = "guru" BOOKSTACK = "bookstack" CONFLUENCE = "confluence" diff --git a/backend/danswer/connectors/factory.py b/backend/danswer/connectors/factory.py index 5a3f9b090..b8f490b2c 100644 --- a/backend/danswer/connectors/factory.py +++ b/backend/danswer/connectors/factory.py @@ -8,6 +8,7 @@ from danswer.connectors.danswer_jira.connector import JiraConnector from danswer.connectors.document360.connector import Document360Connector from danswer.connectors.file.connector import LocalFileConnector from danswer.connectors.github.connector import GithubConnector +from danswer.connectors.gitlab.connector import GitlabConnector from danswer.connectors.gong.connector import GongConnector from danswer.connectors.google_drive.connector import GoogleDriveConnector from danswer.connectors.google_site.connector import GoogleSitesConnector @@ -47,6 +48,7 @@ def identify_connector_class( InputType.POLL: SlackPollConnector, }, DocumentSource.GITHUB: GithubConnector, + DocumentSource.GITLAB: GitlabConnector, DocumentSource.GOOGLE_DRIVE: GoogleDriveConnector, DocumentSource.BOOKSTACK: BookstackConnector, DocumentSource.CONFLUENCE: ConfluenceConnector, diff --git a/backend/danswer/connectors/gitlab/__init__.py b/backend/danswer/connectors/gitlab/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/danswer/connectors/gitlab/connector.py b/backend/danswer/connectors/gitlab/connector.py new file mode 100644 index 000000000..ef2181d0f --- /dev/null +++ b/backend/danswer/connectors/gitlab/connector.py @@ -0,0 +1,182 @@ + +import itertools +from collections.abc import Iterator +from datetime import datetime +from datetime import timezone +from typing import Any + +import gitlab +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 BasicExpertInfo, ConnectorMissingCredentialError +from danswer.connectors.models import Document +from danswer.connectors.models import Section +from danswer.utils.logger import setup_logger + + +logger = setup_logger() + + +def _batch_gitlab_objects( + git_objs: list[Any], + batch_size: int +) -> Iterator[list[Any]]: + it = iter(git_objs) + while True: + batch = list(itertools.islice(it, batch_size[0])) + if not batch: + break + yield batch + +def get_author(author:Any)-> BasicExpertInfo: + return BasicExpertInfo( + display_name=author.get("name"), + first_name=author.get("name").split(" ")[0], + last_name=author.get("name").split(" ")[1] + + ) + + +def _convert_merge_request_to_document(mr: Any) -> Document: + return Document( + id=mr.web_url, + sections=[Section(link=mr.web_url, text=mr.description or "")], + source=DocumentSource.GITLAB, + semantic_identifier=mr.title, + # updated_at is UTC time but is timezone unaware, explicitly add UTC + # as there is logic in indexing to prevent wrong timestamped docs + # due to local time discrepancies with UTC + doc_updated_at=mr.updated_at.replace(tzinfo=timezone.utc), + primary_owners=[get_author(mr.author)], + metadata={ + "state": mr.state, + "type": "MergeRequest" + }, + ) + + +def _convert_issue_to_document(issue: Any) -> Document: + return Document( + id=issue.web_url, + sections=[Section(link=issue.web_url, text=issue.description or "")], + source=DocumentSource.GITLAB, + semantic_identifier=issue.title, + # updated_at is UTC time but is timezone unaware, explicitly add UTC + # as there is logic in indexing to prevent wrong timestamped docs + # due to local time discrepancies with UTC + doc_updated_at=issue.updated_at.replace(tzinfo=timezone.utc), + primary_owners=[get_author(issue.author)], + metadata={ + "state": issue.state, + "type": issue.type | "Issue" + }, + ) + +class GitlabConnector(LoadConnector, PollConnector): + def __init__(self, + project_owner: str, + project_name: str, + batch_size: int = INDEX_BATCH_SIZE, + state_filter: str = "all", + include_mrs: bool = True, + include_issues: bool = True, + ) -> None: + self.project_owner=project_owner, + self.project_name=project_name, + self.batch_size=batch_size, + self.state_filter=state_filter, + self.include_mrs=include_mrs, + self.include_issues=include_issues, + self.gitlab_client :gitlab.Gitlab | None = None + + + + def load_credentials(self, credentials: dict[str, Any]) -> dict[str, Any] | None: + self.gitlab_client = gitlab.Gitlab(credentials["gitlab_url"], private_token=credentials['gitlab_access_token']) + return None + + + + def _fetch_from_gitlab(self, start: datetime | None = None, end: datetime | None = None) -> GenerateDocumentsOutput: + if self.gitlab_client is None: + raise ConnectorMissingCredentialError("Gitlab") + project = self.gitlab_client.projects.get(f"{self.project_owner[0]}/{self.project_name[0]}") + + if self.include_mrs: + merge_requests = project.mergerequests.list( + state=self.state_filter, order_by="updated_at", sort="desc" + ) + + for mr_batch in _batch_gitlab_objects(merge_requests, self.batch_size): + doc_batch =[] + for mr in mr_batch: + mr.updated_at = datetime.strptime(mr.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ") + if start is not None and mr.updated_at < start: + yield doc_batch + return + if end is not None and mr.updated_at > end: + continue + doc_batch.append(_convert_merge_request_to_document(mr)) + yield doc_batch + + if self.include_issues: + issues = project.issues.list( + state=self.state_filter + ) + + for issue_batch in _batch_gitlab_objects(issues, self.batch_size): + doc_batch =[] + for issue in issue_batch: + issue.updated_at = datetime.strptime(issue.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ") + if start is not None and issue.updated_at < start: + yield doc_batch + return + if end is not None and issue.updated_at > end: + continue + if issue.updated_at is not None : + # MRs are handled separately + continue + doc_batch.append(_convert_issue_to_document(issue)) + yield doc_batch + + def load_from_state(self) -> GenerateDocumentsOutput: + return self._fetch_from_gitlab() + + def poll_source(self, start: SecondsSinceUnixEpoch, end: SecondsSinceUnixEpoch) -> GenerateDocumentsOutput: + start_datetime = datetime.utcfromtimestamp(start) + end_datetime = datetime.utcfromtimestamp(end) + return self._fetch_from_gitlab(start_datetime, end_datetime) + + + + + + + +if __name__ == "__main__": + import os + connector = GitlabConnector( + # gitlab_url="https://gitlab.com/api/v4", + project_owner=os.environ["PROJECT_OWNER"], + project_name=os.environ["PROJECT_NAME"], + batch_size=10, + state_filter="all", + include_mrs=True, + include_issues=True, + ) + + connector.load_credentials( + { + "github_access_token": os.environ["GITLAB_ACCESS_TOKEN"], + "gitlab_url":os.environ["GITLAB_URL"] + } + ) + document_batches = connector.load_from_state() + print(next(document_batches)) + + + diff --git a/backend/requirements/default.txt b/backend/requirements/default.txt index f4e7f8c7c..f8dcbe5db 100644 --- a/backend/requirements/default.txt +++ b/backend/requirements/default.txt @@ -36,6 +36,7 @@ psycopg2-binary==2.9.9 pycryptodome==3.19.1 pydantic==1.10.7 PyGithub==1.58.2 +python-gitlab==3.9.0 pypdf==3.17.0 pytest-playwright==0.3.2 python-dotenv==1.0.0 diff --git a/web/public/Gitlab.png b/web/public/Gitlab.png new file mode 100644 index 0000000000000000000000000000000000000000..b464c02221f10ceaa6752fe459a7ba890883031b GIT binary patch literal 19675 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 literal 0 HcmV?d00001 diff --git a/web/src/app/admin/connectors/gitlab/page.tsx b/web/src/app/admin/connectors/gitlab/page.tsx new file mode 100644 index 000000000..46dd9fc5a --- /dev/null +++ b/web/src/app/admin/connectors/gitlab/page.tsx @@ -0,0 +1,253 @@ +"use client"; + +import * as Yup from "yup"; +import { GitlabIcon, TrashIcon } from "@/components/icons/icons"; +import { TextFormField } from "@/components/admin/connectors/Field"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; +import useSWR, { useSWRConfig } from "swr"; +import { fetcher } from "@/lib/fetcher"; +import { + GitlabConfig, + GitlabCredentialJson, + Credential, + ConnectorIndexingStatus, +} from "@/lib/types"; +import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm"; +import { LoadingAnimation } from "@/components/Loading"; +import { CredentialForm } from "@/components/admin/connectors/CredentialForm"; +import { adminDeleteCredential, linkCredential } from "@/lib/credential"; +import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable"; +import { usePublicCredentials } from "@/lib/hooks"; +import { Card, Divider, Text, Title } from "@tremor/react"; +import { AdminPageTitle } from "@/components/admin/Title"; + +const Main = () => { + 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 gitlabConnectorIndexingStatuses: ConnectorIndexingStatus< + GitlabConfig, + GitlabCredentialJson + >[] = connectorIndexingStatuses.filter( + (connectorIndexingStatus) => + connectorIndexingStatus.connector.source === "gitlab" + ); + const gitlabCredential: Credential | undefined = + credentialsData.find( + (credential) => credential.credential_json?.gitlab_access_token + ); + + return ( + <> + + Step 1: Provide your access token + + {gitlabCredential ? ( + <> + {" "} +
+

Existing Access Token:

+

+ {gitlabCredential.credential_json.gitlab_access_token} +

{" "} + +
+ + ) : ( + <> + + If you don't have an access token, read the guide{" "} +
+ here + {" "} + on how to get one from Gitlab. + + + + formBody={ + <> + + If you are using GitLab Cloud, keep the default value below + + + + + } + validationSchema={Yup.object().shape({ + gitlab_url: Yup.string().default("https://gitlab.com"), + gitlab_access_token: Yup.string().required( + "Please enter the access token for Gitlab" + ), + })} + initialValues={{ + gitlab_access_token: "", + gitlab_url: "https://gitlab.com" + }} + onSubmit={(isSuccess) => { + if (isSuccess) { + refreshCredentials(); + } + }} + /> + + + )} + + + Step 2: Which repositories do you want to make searchable? + + + {gitlabConnectorIndexingStatuses.length > 0 && ( + <> + + We pull the latest Pull Requests from each project listed below + every 10 minutes. + +
+ + connectorIndexingStatuses={gitlabConnectorIndexingStatuses} + liveCredential={gitlabCredential} + getCredential={(credential) => + credential.credential_json.gitlab_access_token + } + onCredentialLink={async (connectorId) => { + if (gitlabCredential) { + await linkCredential(connectorId, gitlabCredential.id); + mutate("/api/manage/admin/connector/indexing-status"); + } + }} + specialColumns={[ + { + header: "Project", + key: "project", + getValue: (ccPairStatus) => { + const connectorConfig = + ccPairStatus.connector.connector_specific_config; + return `${connectorConfig.project_owner}/${connectorConfig.project_name}`; + }, + }, + ]} + onUpdate={() => + mutate("/api/manage/admin/connector/indexing-status") + } + /> +
+ + + )} + + {gitlabCredential ? ( + +

Connect to a New Project

+ + nameBuilder={(values) => + `GitlabConnector-${values.project_owner}/${values.project_name}` + } + ccPairNameBuilder={(values) => + `${values.project_owner}/${values.project_name}` + } + source="gitlab" + inputType="poll" + formBody={ + <> + + + + } + validationSchema={Yup.object().shape({ + project_owner: Yup.string().required( + "Please enter the owner of the project to index e.g. danswer-ai" + ), + project_name: Yup.string().required( + "Please enter the name of the project to index e.g. danswer " + ), + include_mrs: Yup.boolean().required(), + include_issues: Yup.boolean().required(), + })} + initialValues={{ + project_owner: "", + project_name: "", + include_mrs: true, + include_issues: true, + }} + refreshFreq={10 * 60} // 10 minutes + credentialId={gitlabCredential.id} + /> +
+ ) : ( + + Please provide your access token in Step 1 first! Once done with that, + you can then specify which Gitlab repositories you want to make + searchable. + + )} + + ); +}; + +export default function Page() { + return ( +
+
+ +
+ + } + title="Gitlab MRs + Issues" + /> + +
+
+ ); +} diff --git a/web/src/components/admin/connectors/ConnectorTitle.tsx b/web/src/components/admin/connectors/ConnectorTitle.tsx index d4262f424..c6baf4666 100644 --- a/web/src/components/admin/connectors/ConnectorTitle.tsx +++ b/web/src/components/admin/connectors/ConnectorTitle.tsx @@ -3,6 +3,7 @@ import { ConfluenceConfig, Connector, GithubConfig, + GitlabConfig, GoogleDriveConfig, JiraConfig, SlackConfig, @@ -38,7 +39,13 @@ export const ConnectorTitle = ({ "Repo", `${typedConnector.connector_specific_config.repo_owner}/${typedConnector.connector_specific_config.repo_name}` ); - } else if (connector.source === "confluence") { + } else if (connector.source === "gitlab") { + const typedConnector = connector as Connector; + additionalMetadata.set( + "Repo", + `${typedConnector.connector_specific_config.project_owner}/${typedConnector.connector_specific_config.project_name}` + ); + } else if (connector.source === "confluence") { const typedConnector = connector as Connector; additionalMetadata.set( "Wiki URL", diff --git a/web/src/components/icons/icons.tsx b/web/src/components/icons/icons.tsx index cb231c75b..9c49c1040 100644 --- a/web/src/components/icons/icons.tsx +++ b/web/src/components/icons/icons.tsx @@ -329,7 +329,19 @@ export const SlackIcon = ({ ); }; - +export const GitlabIcon = ({ + size = 16, + className = defaultTailwindCSS, +}: IconProps) => { + return ( +
+ Logo +
+ ); +}; export const GithubIcon = ({ size = 16, className = defaultTailwindCSS, @@ -344,6 +356,7 @@ export const GithubIcon = ({ ); }; + export const GoogleDriveIcon = ({ size = 16, className = defaultTailwindCSS, diff --git a/web/src/lib/sources.ts b/web/src/lib/sources.ts index 8be03567c..b82f79d58 100644 --- a/web/src/lib/sources.ts +++ b/web/src/lib/sources.ts @@ -4,6 +4,7 @@ import { Document360Icon, FileIcon, GithubIcon, + GitlabIcon, GlobeIcon, GongIcon, GoogleDriveIcon, @@ -60,6 +61,11 @@ const SOURCE_METADATA_MAP: SourceMap = { displayName: "Github", category: SourceCategory.AppConnection, }, + gitlab :{ + icon:GitlabIcon, + displayName:"Gitlab", + category:SourceCategory.AppConnection, + }, confluence: { icon: ConfluenceIcon, displayName: "Confluence", diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index aee8cb6d2..0827ba512 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -12,6 +12,7 @@ export interface User { export type ValidSources = | "web" | "github" + | "gitlab" | "slack" | "google_drive" | "bookstack" @@ -77,6 +78,14 @@ export interface GithubConfig { include_issues: boolean; } +export interface GitlabConfig { + project_owner: string; + project_name: string; + include_mrs: boolean; + include_issues: boolean; +} + + export interface GoogleDriveConfig { folder_paths?: string[]; include_shared?: boolean; @@ -189,6 +198,11 @@ export interface GithubCredentialJson { github_access_token: string; } +export interface GitlabCredentialJson { + gitlab_url:string, + gitlab_access_token: string; +} + export interface BookstackCredentialJson { bookstack_base_url: string; bookstack_api_token_id: string;