From 10b36f4ce991734badbf20d7a6283cb2a4068d0b Mon Sep 17 00:00:00 2001 From: Chris Weaver <25087905+Weves@users.noreply.github.com> Date: Mon, 3 Jul 2023 15:57:22 -0700 Subject: [PATCH] Slab connector UI (#130) Also added in missing dateutil req --- backend/requirements/default.txt | 1 + web/public/SlabLogoBlue.png | Bin 0 -> 1570 bytes web/src/app/admin/connectors/slab/page.tsx | 263 +++++++++++++++++++++ web/src/app/admin/layout.tsx | 10 + web/src/components/icons/icons.tsx | 13 + web/src/components/source.tsx | 7 + web/src/lib/types.ts | 9 + 7 files changed, 303 insertions(+) create mode 100644 web/public/SlabLogoBlue.png create mode 100644 web/src/app/admin/connectors/slab/page.tsx diff --git a/backend/requirements/default.txt b/backend/requirements/default.txt index 23470f1c6..b32ebbedd 100644 --- a/backend/requirements/default.txt +++ b/backend/requirements/default.txt @@ -2,6 +2,7 @@ alembic==1.10.4 asyncpg==0.27.0 atlassian-python-api==3.37.0 beautifulsoup4==4.12.0 +python-dateutil==2.8.2 fastapi==0.95.0 fastapi-users==11.0.0 fastapi-users-db-sqlalchemy==5.0.0 diff --git a/web/public/SlabLogoBlue.png b/web/public/SlabLogoBlue.png new file mode 100644 index 0000000000000000000000000000000000000000..7857c1bf34cc064275b6a93db96ec711a68b9d03 GIT binary patch literal 1570 zcmZ`(dpJ~S7(XUv#zZ{K3>C}fRw+%`r=m7v?qS?Zd1&O8Bw>b4%4KUsV#>-^ZjVLv zNUqbM++qe%BUWf;Xg6h57F~A3%zXPz{jq=TdCqyi_j`Yr^FHUi=VW=Y8M?T2I0Qj- zSxl-ATsi6sy9&lpdZrrCj3j%I5u`qEwIW0d?#)A)J{|~?Y>gmk=MZEWmeT%25FQCZ z-f$4a=@$esI$6}}y&FML8@=59XaF8{1_}XD(M0%qdWmFSJu)wmRGo;VpdPPmBr2N- zU+cboav;Df1nR9%j~ZQRB6^Bso?Wt)d(Z^$C6*}#Fv=qa2LV7J3YqMl@s%$UkHOb! zE3rHcfR#ogPq7r-Gxioq6^&}4|LEvSBXpC(PP|1jPRGcXBn`Mwd3Y9Z zp(hy9feVFe$OJC5Bm)L;rEm!hK%vB$_^agj+Q|0s0Qn>+DFu=tFh36pu7Zc%U}_5F z=7Eqic(raw!?OW)zHuOqMdaEtHk3@UayPgJz zDTV99_C-vC@>`{{`&T&@>k@~bXINR6)a}&GxoPVBA|kFxOC$GzF256Zx&cK=C+sR8 zMejN$oW|N6YC{<~`0*}P+#8q}6#M$Wn(ljCRuP}L%~1n&F=xxhhH6LK8y5_*72sK^B^QzqPCxGWDEW(Kp0 zXuCZ?Yv_|2kBw*hX-LYGuERk;!lIJ>j!w=ME;-}lOnUbY*D!r5ObSlKpTF{G;3FiG!%bDSFdMGFb2r{<$?q#8ID`Pq{T~VkQJ?X8lfeT(dVDYQFqRqTRdlMu2 zv$mtfE93G2zFvgcoY18GYy#E2riZjpQNdAIlWm`W>+g%iU_VY&CG0a#386ZeP0_Lfu5i+yn;{2Z^IyE(L2q|< z>1^?sY2^Gpzk$NR79DuOH44M(q<;&4C!9S;QrDq;TU*!8Uei6?FoM0}Ti1!=b`5I( zAe`MzRJ;kF7Ov7P+OC<5TQ3ieTN-=go668-T<(arj#LlF_PPGd+1NO2IN$@mv0D(1}q literal 0 HcmV?d00001 diff --git a/web/src/app/admin/connectors/slab/page.tsx b/web/src/app/admin/connectors/slab/page.tsx new file mode 100644 index 000000000..824472008 --- /dev/null +++ b/web/src/app/admin/connectors/slab/page.tsx @@ -0,0 +1,263 @@ +"use client"; + +import * as Yup from "yup"; +import { SlabIcon, TrashIcon } from "@/components/icons/icons"; +import { TextFormField } from "@/components/admin/connectors/Field"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; +import { CredentialForm } from "@/components/admin/connectors/CredentialForm"; +import { + Credential, + ConnectorIndexingStatus, + SlabCredentialJson, + SlabConfig, +} from "@/lib/types"; +import useSWR, { useSWRConfig } from "swr"; +import { fetcher } from "@/lib/fetcher"; +import { LoadingAnimation } from "@/components/Loading"; +import { deleteCredential, linkCredential } from "@/lib/credential"; +import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm"; +import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable"; +import { usePopup } from "@/components/admin/connectors/Popup"; + +const Main = () => { + 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, + isValidating: isCredentialsValidating, + error: isCredentialsError, + } = useSWR[]>("/api/manage/credential", fetcher); + + if ( + isConnectorIndexingStatusesLoading || + isCredentialsLoading || + isCredentialsValidating + ) { + return ; + } + + if (isConnectorIndexingStatusesError || !connectorIndexingStatuses) { + return
Failed to load connectors
; + } + + if (isCredentialsError || !credentialsData) { + return
Failed to load credentials
; + } + + const slabConnectorIndexingStatuses: ConnectorIndexingStatus[] = + connectorIndexingStatuses.filter( + (connectorIndexingStatus) => + connectorIndexingStatus.connector.source === "slab" + ); + const slabCredential = credentialsData.filter( + (credential) => credential.credential_json?.slab_bot_token + )[0]; + + return ( + <> + {popup} +

+ Step 1: Provide your Credentials +

+ + {slabCredential ? ( + <> +
+

Existing Slab Bot Token:

+

+ {slabCredential.credential_json?.slab_bot_token} +

+ +
+ + ) : ( + <> +

+ To use the Slab connector, first follow the guide{" "} + + here + {" "} + to generate a Slab Bot Token. +

+
+ + formBody={ + <> + + + } + validationSchema={Yup.object().shape({ + slab_bot_token: Yup.string().required( + "Please enter your Slab Bot Token" + ), + })} + initialValues={{ + slab_bot_token: "", + }} + onSubmit={(isSuccess) => { + if (isSuccess) { + mutate("/api/manage/credential"); + } + }} + /> +
+ + )} + +

+ Step 2: What's the base URL for your Slab team? +

+ {slabCredential ? ( + <> + {slabConnectorIndexingStatuses.length > 0 ? ( + <> +

+ We are pulling the latest documents from{" "} + + { + slabConnectorIndexingStatuses[0].connector + .connector_specific_config.base_url + } + {" "} + every 10 minutes. +

+ + connectorIndexingStatuses={slabConnectorIndexingStatuses} + liveCredential={slabCredential} + getCredential={(credential) => { + return ( +
+

{credential.credential_json.slab_bot_token}

+
+ ); + }} + onCredentialLink={async (connectorId) => { + if (slabCredential) { + await linkCredential(connectorId, slabCredential.id); + mutate("/api/manage/admin/connector/indexing-status"); + } + }} + specialColumns={[ + { + header: "Url", + key: "url", + getValue: (connector) => ( + + {connector.connector_specific_config.base_url} + + ), + }, + ]} + onUpdate={() => + mutate("/api/manage/admin/connector/indexing-status") + } + /> + + ) : ( + <> +

+ Specify the base URL for your Slab team below. This will look + something like:{" "} + + https://danswer.slab.com/ + +

+
+

Add a New Space

+ + nameBuilder={(values) => `SlabConnector-${values.base_url}`} + source="slab" + inputType="poll" + formBody={ + <> + + + } + validationSchema={Yup.object().shape({ + base_url: Yup.string().required( + "Please enter the base URL for your team e.g. https://danswer.slab.com/" + ), + })} + initialValues={{ + base_url: "", + }} + refreshFreq={10 * 60} // 10 minutes + onSubmit={async (isSuccess, responseJson) => { + if (isSuccess && responseJson) { + await linkCredential(responseJson.id, slabCredential.id); + mutate("/api/manage/admin/connector/indexing-status"); + } + }} + /> +
+ + )} + + ) : ( + <> +

+ Please provide your access token in Step 1 first! Once done with + that, you can then specify the URL for your Slab team and get + started with indexing. +

+ + )} + + ); +}; + +export default function Page() { + return ( +
+
+ +
+
+ +

Slab

+
+
+
+ ); +} diff --git a/web/src/app/admin/layout.tsx b/web/src/app/admin/layout.tsx index 4c38eea3b..3a0f4c42b 100644 --- a/web/src/app/admin/layout.tsx +++ b/web/src/app/admin/layout.tsx @@ -10,6 +10,7 @@ import { ConfluenceIcon, FileIcon, JiraIcon, + SlabIcon, } from "@/components/icons/icons"; import { DISABLE_AUTH } from "@/lib/constants"; import { getCurrentUserSS } from "@/lib/userSS"; @@ -100,6 +101,15 @@ export default async function AdminLayout({ ), link: "/admin/connectors/jira", }, + { + name: ( +
+ +
Slab
+
+ ), + link: "/admin/connectors/slab", + }, { name: (
diff --git a/web/src/components/icons/icons.tsx b/web/src/components/icons/icons.tsx index 0f6b8e2c8..c6c6cdcc7 100644 --- a/web/src/components/icons/icons.tsx +++ b/web/src/components/icons/icons.tsx @@ -20,6 +20,7 @@ import { SiSlack, } from "react-icons/si"; import { FaFile, FaGlobe } from "react-icons/fa"; +import Image from "next/image"; interface IconProps { size?: string; @@ -126,6 +127,18 @@ export const JiraIcon = ({ return ; }; +export const SlabIcon = ({ + size = "16", + className = defaultTailwindCSS, +}: IconProps) => ( +
+ Logo +
+); + export const InfoIcon = ({ size = "16", className = defaultTailwindCSS, diff --git a/web/src/components/source.tsx b/web/src/components/source.tsx index d61dc92cf..b9ef9e094 100644 --- a/web/src/components/source.tsx +++ b/web/src/components/source.tsx @@ -6,6 +6,7 @@ import { GlobeIcon, GoogleDriveIcon, JiraIcon, + SlabIcon, SlackIcon, } from "./icons/icons"; @@ -59,6 +60,12 @@ export const getSourceMetadata = (sourceType: ValidSources): SourceMetadata => { displayName: "Jira", adminPageLink: "/admin/connectors/jira", }; + case "slab": + return { + icon: SlabIcon, + displayName: "Slab", + adminPageLink: "/admin/connectors/slab", + }; default: throw new Error("Invalid source type"); } diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 9ca395045..9c3453275 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -14,6 +14,7 @@ export type ValidSources = | "google_drive" | "confluence" | "jira" + | "slab" | "file"; export type ValidInputTypes = "load_state" | "poll" | "event"; @@ -55,6 +56,10 @@ export interface SlackConfig { workspace: string; } +export interface SlabConfig { + base_url: string; +} + export interface FileConfig { file_locations: string[]; } @@ -102,3 +107,7 @@ export interface SlackCredentialJson { export interface GoogleDriveCredentialJson { google_drive_tokens: string; } + +export interface SlabCredentialJson { + slab_bot_token: string; +}