Slab connector UI (#130)

Also added in missing dateutil req
This commit is contained in:
Chris Weaver 2023-07-03 15:57:22 -07:00 committed by GitHub
parent 675a5aec9e
commit 10b36f4ce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 303 additions and 0 deletions

View File

@ -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

BIN
web/public/SlabLogoBlue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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<ConnectorIndexingStatus<any>[]>(
"/api/manage/admin/connector/indexing-status",
fetcher
);
const {
data: credentialsData,
isLoading: isCredentialsLoading,
isValidating: isCredentialsValidating,
error: isCredentialsError,
} = useSWR<Credential<any>[]>("/api/manage/credential", fetcher);
if (
isConnectorIndexingStatusesLoading ||
isCredentialsLoading ||
isCredentialsValidating
) {
return <LoadingAnimation text="Loading" />;
}
if (isConnectorIndexingStatusesError || !connectorIndexingStatuses) {
return <div>Failed to load connectors</div>;
}
if (isCredentialsError || !credentialsData) {
return <div>Failed to load credentials</div>;
}
const slabConnectorIndexingStatuses: ConnectorIndexingStatus<SlabConfig>[] =
connectorIndexingStatuses.filter(
(connectorIndexingStatus) =>
connectorIndexingStatus.connector.source === "slab"
);
const slabCredential = credentialsData.filter(
(credential) => credential.credential_json?.slab_bot_token
)[0];
return (
<>
{popup}
<h2 className="font-bold mb-2 mt-6 ml-auto mr-auto">
Step 1: Provide your Credentials
</h2>
{slabCredential ? (
<>
<div className="flex mb-1 text-sm">
<p className="my-auto">Existing Slab Bot Token: </p>
<p className="ml-1 italic my-auto max-w-md truncate">
{slabCredential.credential_json?.slab_bot_token}
</p>
<button
className="ml-1 hover:bg-gray-700 rounded-full p-1"
onClick={async () => {
if (slabConnectorIndexingStatuses.length > 0) {
setPopup({
type: "error",
message:
"Must delete all connectors before deleting credentials",
});
return;
}
await deleteCredential(slabCredential.id);
mutate("/api/manage/credential");
}}
>
<TrashIcon />
</button>
</div>
</>
) : (
<>
<p className="text-sm">
To use the Slab connector, first follow the guide{" "}
<a
className="text-blue-500"
href="https://docs.danswer.dev/connectors/slab#setting-up"
>
here
</a>{" "}
to generate a Slab Bot Token.
</p>
<div className="border-solid border-gray-600 border rounded-md p-6 mt-2">
<CredentialForm<SlabCredentialJson>
formBody={
<>
<TextFormField
name="slab_bot_token"
label="Slab Bot Token:"
/>
</>
}
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");
}
}}
/>
</div>
</>
)}
<h2 className="font-bold mb-2 mt-6 ml-auto mr-auto">
Step 2: What&apos;s the base URL for your Slab team?
</h2>
{slabCredential ? (
<>
{slabConnectorIndexingStatuses.length > 0 ? (
<>
<p className="text-sm mb-2">
We are pulling the latest documents from{" "}
<a
href={
slabConnectorIndexingStatuses[0].connector
.connector_specific_config.base_url
}
className="text-blue-600"
>
{
slabConnectorIndexingStatuses[0].connector
.connector_specific_config.base_url
}
</a>{" "}
every <b>10</b> minutes.
</p>
<ConnectorsTable<SlabConfig, SlabCredentialJson>
connectorIndexingStatuses={slabConnectorIndexingStatuses}
liveCredential={slabCredential}
getCredential={(credential) => {
return (
<div>
<p>{credential.credential_json.slab_bot_token}</p>
</div>
);
}}
onCredentialLink={async (connectorId) => {
if (slabCredential) {
await linkCredential(connectorId, slabCredential.id);
mutate("/api/manage/admin/connector/indexing-status");
}
}}
specialColumns={[
{
header: "Url",
key: "url",
getValue: (connector) => (
<a
className="text-blue-500"
href={connector.connector_specific_config.base_url}
>
{connector.connector_specific_config.base_url}
</a>
),
},
]}
onUpdate={() =>
mutate("/api/manage/admin/connector/indexing-status")
}
/>
</>
) : (
<>
<p className="text-sm mb-4">
Specify the base URL for your Slab team below. This will look
something like:{" "}
<b>
<i>https://danswer.slab.com/</i>
</b>
</p>
<div className="border-solid border-gray-600 border rounded-md p-6 mt-4">
<h2 className="font-bold mb-3">Add a New Space</h2>
<ConnectorForm<SlabConfig>
nameBuilder={(values) => `SlabConnector-${values.base_url}`}
source="slab"
inputType="poll"
formBody={
<>
<TextFormField name="base_url" label="Base URL:" />
</>
}
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");
}
}}
/>
</div>
</>
)}
</>
) : (
<>
<p className="text-sm">
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.
</p>
</>
)}
</>
);
};
export default function Page() {
return (
<div className="mx-auto container">
<div className="mb-4">
<HealthCheckBanner />
</div>
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
<SlabIcon size="32" />
<h1 className="text-3xl font-bold pl-2">Slab</h1>
</div>
<Main />
</div>
);
}

View File

@ -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: (
<div className="flex">
<SlabIcon size="16" />
<div className="ml-1">Slab</div>
</div>
),
link: "/admin/connectors/slab",
},
{
name: (
<div className="flex">

View File

@ -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 <SiJira size={size} className={className} />;
};
export const SlabIcon = ({
size = "16",
className = defaultTailwindCSS,
}: IconProps) => (
<div
style={{ width: `${size}px`, height: `${size}px` }}
className={`w-[${size}px] h-[${size}px] ` + className}
>
<Image src="/SlabLogoBlue.png" alt="Logo" width="96" height="96" />
</div>
);
export const InfoIcon = ({
size = "16",
className = defaultTailwindCSS,

View File

@ -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");
}

View File

@ -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;
}