mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-27 04:18:35 +02:00
create a hubspot connector (#482)
This commit is contained in:
BIN
web/public/HubSpot.png
Normal file
BIN
web/public/HubSpot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
223
web/src/app/admin/connectors/hubspot/page.tsx
Normal file
223
web/src/app/admin/connectors/hubspot/page.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
"use client";
|
||||
|
||||
import * as Yup from "yup";
|
||||
import { HubSpotIcon, 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,
|
||||
HubSpotConfig,
|
||||
HubSpotCredentialJson,
|
||||
} from "@/lib/types";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
import { fetcher } from "@/lib/fetcher";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { adminDeleteCredential, 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";
|
||||
import { usePublicCredentials } from "@/lib/hooks";
|
||||
|
||||
const Main = () => {
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
const { mutate } = useSWRConfig();
|
||||
const {
|
||||
data: connectorIndexingStatuses,
|
||||
isLoading: isConnectorIndexingStatusesLoading,
|
||||
error: isConnectorIndexingStatusesError,
|
||||
} = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
||||
"/api/manage/admin/connector/indexing-status",
|
||||
fetcher
|
||||
);
|
||||
|
||||
const {
|
||||
data: credentialsData,
|
||||
isLoading: isCredentialsLoading,
|
||||
isValidating: isCredentialsValidating,
|
||||
error: isCredentialsError,
|
||||
refreshCredentials,
|
||||
} = usePublicCredentials();
|
||||
|
||||
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 hubSpotConnectorIndexingStatuses: ConnectorIndexingStatus<
|
||||
HubSpotConfig,
|
||||
HubSpotCredentialJson
|
||||
>[] = connectorIndexingStatuses.filter(
|
||||
(connectorIndexingStatus) =>
|
||||
connectorIndexingStatus.connector.source === "hubspot"
|
||||
);
|
||||
const hubSpotCredential: Credential<HubSpotCredentialJson> = credentialsData.filter(
|
||||
(credential) => credential.credential_json?.hubspot_access_token
|
||||
)[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
{popup}
|
||||
<p className="text-sm">
|
||||
This connector allows you to sync all your HubSpot Tickets into Danswer.
|
||||
</p>
|
||||
|
||||
<h2 className="font-bold mb-2 mt-6 ml-auto mr-auto">
|
||||
Step 1: Provide your Credentials
|
||||
</h2>
|
||||
|
||||
{hubSpotCredential ? (
|
||||
<>
|
||||
<div className="flex mb-1 text-sm">
|
||||
<p className="my-auto">Existing Access Token: </p>
|
||||
<p className="ml-1 italic my-auto max-w-md truncate">
|
||||
{hubSpotCredential.credential_json?.hubspot_access_token}
|
||||
</p>
|
||||
<button
|
||||
className="ml-1 hover:bg-gray-700 rounded-full p-1"
|
||||
onClick={async () => {
|
||||
if (hubSpotConnectorIndexingStatuses.length > 0) {
|
||||
setPopup({
|
||||
type: "error",
|
||||
message:
|
||||
"Must delete all connectors before deleting credentials",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await adminDeleteCredential(hubSpotCredential.id);
|
||||
refreshCredentials();
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm">
|
||||
To use the HubSpot connector, provide the HubSpot Access Token.
|
||||
</p>
|
||||
<div className="border-solid border-gray-600 border rounded-md p-6 mt-2">
|
||||
<CredentialForm<HubSpotCredentialJson>
|
||||
formBody={
|
||||
<>
|
||||
<TextFormField
|
||||
name="hubspot_access_token"
|
||||
label="HubSpot Access Token:"
|
||||
type="password"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
validationSchema={Yup.object().shape({
|
||||
hubspot_access_token: Yup.string().required(
|
||||
"Please enter your HubSpot Access Token"
|
||||
)
|
||||
})}
|
||||
initialValues={{
|
||||
hubspot_access_token: "",
|
||||
}}
|
||||
onSubmit={(isSuccess) => {
|
||||
if (isSuccess) {
|
||||
refreshCredentials();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<h2 className="font-bold mb-2 mt-6 ml-auto mr-auto">
|
||||
Step 2: Start indexing!
|
||||
</h2>
|
||||
{hubSpotCredential ? (
|
||||
!hubSpotConnectorIndexingStatuses.length ? (
|
||||
<>
|
||||
<p className="text-sm mb-2">
|
||||
Click the button below to start indexing! We will pull the latest
|
||||
tickets from HubSpot every <b>10</b> minutes.
|
||||
</p>
|
||||
<div className="flex">
|
||||
<ConnectorForm<HubSpotConfig>
|
||||
nameBuilder={() => "HubSpotConnector"}
|
||||
source="hubspot"
|
||||
inputType="poll"
|
||||
formBody={null}
|
||||
validationSchema={Yup.object().shape({})}
|
||||
initialValues={{}}
|
||||
refreshFreq={10 * 60} // 10 minutes
|
||||
onSubmit={async (isSuccess, responseJson) => {
|
||||
if (isSuccess && responseJson) {
|
||||
await linkCredential(responseJson.id, hubSpotCredential.id);
|
||||
mutate("/api/manage/admin/connector/indexing-status");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm mb-2">
|
||||
HubSpot connector is setup! We are pulling the latest tickets from HubSpot
|
||||
every <b>10</b> minutes.
|
||||
</p>
|
||||
<ConnectorsTable<HubSpotConfig, HubSpotCredentialJson>
|
||||
connectorIndexingStatuses={hubSpotConnectorIndexingStatuses}
|
||||
liveCredential={hubSpotCredential}
|
||||
getCredential={(credential) => {
|
||||
return (
|
||||
<div>
|
||||
<p>{credential.credential_json.hubspot_access_token}</p>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
onCredentialLink={async (connectorId) => {
|
||||
if (hubSpotCredential) {
|
||||
await linkCredential(connectorId, hubSpotCredential.id);
|
||||
mutate("/api/manage/admin/connector/indexing-status");
|
||||
}
|
||||
}}
|
||||
onUpdate={() =>
|
||||
mutate("/api/manage/admin/connector/indexing-status")
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm">
|
||||
Please provide your access token in Step 1 first! Once done with
|
||||
that, you can then start indexing all your HubSpot tickets.
|
||||
</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">
|
||||
<HubSpotIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">HubSpot</h1>
|
||||
</div>
|
||||
<Main />
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -19,6 +19,7 @@ import {
|
||||
LinearIcon,
|
||||
UsersIcon,
|
||||
ThumbsUpIcon,
|
||||
HubSpotIcon,
|
||||
BookmarkIcon,
|
||||
CPUIcon,
|
||||
} from "@/components/icons/icons";
|
||||
@@ -192,6 +193,15 @@ export default async function AdminLayout({
|
||||
),
|
||||
link: "/admin/connectors/file",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<HubSpotIcon size={16} />
|
||||
<div className="ml-1">HubSpot</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/hubspot",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -41,6 +41,7 @@ import confluenceSVG from "../../../public/Confluence.svg";
|
||||
import guruIcon from "../../../public/Guru.svg";
|
||||
import zulipIcon from "../../../public/Zulip.png";
|
||||
import linearIcon from "../../../public/Linear.png";
|
||||
import hubSpotIcon from "../../../public/HubSpot.png";
|
||||
|
||||
interface IconProps {
|
||||
size?: number;
|
||||
@@ -421,3 +422,18 @@ export const GuruIcon = ({
|
||||
<Image src={guruIcon} alt="Logo" width="96" height="96" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const HubSpotIcon = ({
|
||||
size = 16,
|
||||
className = defaultTailwindCSS,
|
||||
}: IconProps) => {
|
||||
return (
|
||||
<div
|
||||
// HubSpot Icon has a bit more surrounding whitespace than other icons, which is why we need to adjust it here
|
||||
style={{ width: `${size + 4}px`, height: `${size + 4}px` }}
|
||||
className={`w-[${size + 4}px] h-[${size + 4}px] -m-0.5 ` + className}
|
||||
>
|
||||
<Image src={hubSpotIcon} alt="Logo" width="96" height="96" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -27,6 +27,7 @@ const sources: Source[] = [
|
||||
{ displayName: "Notion", internalName: "notion" },
|
||||
{ displayName: "Zulip", internalName: "zulip" },
|
||||
{ displayName: "Linear", internalName: "linear" },
|
||||
{ displayName: "HubSpot", internalName: "hubspot" },
|
||||
];
|
||||
|
||||
interface SourceSelectorProps {
|
||||
|
@@ -14,6 +14,7 @@ import {
|
||||
SlabIcon,
|
||||
SlackIcon,
|
||||
ZulipIcon,
|
||||
HubSpotIcon,
|
||||
} from "./icons/icons";
|
||||
|
||||
interface SourceMetadata {
|
||||
@@ -108,6 +109,12 @@ export const getSourceMetadata = (sourceType: ValidSources): SourceMetadata => {
|
||||
displayName: "Linear",
|
||||
adminPageLink: "/admin/connectors/linear",
|
||||
};
|
||||
case "hubspot":
|
||||
return {
|
||||
icon: HubSpotIcon,
|
||||
displayName: "HubSpot",
|
||||
adminPageLink: "/admin/connectors/hubspot",
|
||||
};
|
||||
default:
|
||||
throw new Error("Invalid source type");
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ export type ValidSources =
|
||||
| "guru"
|
||||
| "zulip"
|
||||
| "linear"
|
||||
| "hubspot"
|
||||
| "file";
|
||||
export type ValidInputTypes = "load_state" | "poll" | "event";
|
||||
export type ValidStatuses =
|
||||
@@ -106,6 +107,8 @@ export interface ZulipConfig {
|
||||
|
||||
export interface NotionConfig {}
|
||||
|
||||
export interface HubSpotConfig {}
|
||||
|
||||
export interface IndexAttemptSnapshot {
|
||||
status: ValidStatuses | null;
|
||||
num_docs_indexed: number;
|
||||
@@ -204,6 +207,10 @@ export interface LinearCredentialJson {
|
||||
linear_api_key: string;
|
||||
}
|
||||
|
||||
export interface HubSpotCredentialJson {
|
||||
hubspot_access_token: string;
|
||||
}
|
||||
|
||||
// DELETION
|
||||
|
||||
export interface DeletionAttemptSnapshot {
|
||||
|
Reference in New Issue
Block a user