mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-06 18:00:29 +02:00
Improved google connector flow (#4155)
* fix handling * k * k * fix function * k * k
This commit is contained in:
parent
909403a648
commit
e80a0f2716
@ -360,18 +360,13 @@ def backend_update_credential_json(
|
||||
db_session.commit()
|
||||
|
||||
|
||||
def delete_credential(
|
||||
def _delete_credential_internal(
|
||||
credential: Credential,
|
||||
credential_id: int,
|
||||
user: User | None,
|
||||
db_session: Session,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
credential = fetch_credential_by_id_for_user(credential_id, user, db_session)
|
||||
if credential is None:
|
||||
raise ValueError(
|
||||
f"Credential by provided id {credential_id} does not exist or does not belong to user"
|
||||
)
|
||||
|
||||
"""Internal utility function to handle the actual deletion of a credential"""
|
||||
associated_connectors = (
|
||||
db_session.query(ConnectorCredentialPair)
|
||||
.filter(ConnectorCredentialPair.credential_id == credential_id)
|
||||
@ -416,6 +411,35 @@ def delete_credential(
|
||||
db_session.commit()
|
||||
|
||||
|
||||
def delete_credential_for_user(
|
||||
credential_id: int,
|
||||
user: User,
|
||||
db_session: Session,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Delete a credential that belongs to a specific user"""
|
||||
credential = fetch_credential_by_id_for_user(credential_id, user, db_session)
|
||||
if credential is None:
|
||||
raise ValueError(
|
||||
f"Credential by provided id {credential_id} does not exist or does not belong to user"
|
||||
)
|
||||
|
||||
_delete_credential_internal(credential, credential_id, db_session, force)
|
||||
|
||||
|
||||
def delete_credential(
|
||||
credential_id: int,
|
||||
db_session: Session,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Delete a credential regardless of ownership (admin function)"""
|
||||
credential = fetch_credential_by_id(credential_id, db_session)
|
||||
if credential is None:
|
||||
raise ValueError(f"Credential by provided id {credential_id} does not exist")
|
||||
|
||||
_delete_credential_internal(credential, credential_id, db_session, force)
|
||||
|
||||
|
||||
def create_initial_public_credential(db_session: Session) -> None:
|
||||
error_msg = (
|
||||
"DB is not in a valid initial state."
|
||||
|
@ -13,6 +13,7 @@ from onyx.db.credentials import cleanup_gmail_credentials
|
||||
from onyx.db.credentials import create_credential
|
||||
from onyx.db.credentials import CREDENTIAL_PERMISSIONS_TO_IGNORE
|
||||
from onyx.db.credentials import delete_credential
|
||||
from onyx.db.credentials import delete_credential_for_user
|
||||
from onyx.db.credentials import fetch_credential_by_id_for_user
|
||||
from onyx.db.credentials import fetch_credentials_by_source_for_user
|
||||
from onyx.db.credentials import fetch_credentials_for_user
|
||||
@ -88,7 +89,7 @@ def delete_credential_by_id_admin(
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> StatusResponse:
|
||||
"""Same as the user endpoint, but can delete any credential (not just the user's own)"""
|
||||
delete_credential(db_session=db_session, credential_id=credential_id, user=None)
|
||||
delete_credential(db_session=db_session, credential_id=credential_id)
|
||||
return StatusResponse(
|
||||
success=True, message="Credential deleted successfully", data=credential_id
|
||||
)
|
||||
@ -242,7 +243,7 @@ def delete_credential_by_id(
|
||||
user: User = Depends(current_user),
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> StatusResponse:
|
||||
delete_credential(
|
||||
delete_credential_for_user(
|
||||
credential_id,
|
||||
user,
|
||||
db_session,
|
||||
@ -259,7 +260,7 @@ def force_delete_credential_by_id(
|
||||
user: User = Depends(current_user),
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> StatusResponse:
|
||||
delete_credential(credential_id, user, db_session, True)
|
||||
delete_credential_for_user(credential_id, user, db_session, True)
|
||||
|
||||
return StatusResponse(
|
||||
success=True, message="Credential deleted successfully", data=credential_id
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button } from "@/components/Button";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSWRConfig } from "swr";
|
||||
import * as Yup from "yup";
|
||||
import { useRouter } from "next/navigation";
|
||||
@ -17,13 +17,18 @@ import {
|
||||
GoogleDriveCredentialJson,
|
||||
GoogleDriveServiceAccountCredentialJson,
|
||||
} from "@/lib/connectors/credentials";
|
||||
import { refreshAllGoogleData } from "@/lib/googleConnector";
|
||||
import { ValidSources } from "@/lib/types";
|
||||
import { buildSimilarCredentialInfoURL } from "@/app/admin/connector/[ccPairId]/lib";
|
||||
|
||||
type GoogleDriveCredentialJsonTypes = "authorized_user" | "service_account";
|
||||
|
||||
export const DriveJsonUpload = ({
|
||||
setPopup,
|
||||
onSuccess,
|
||||
}: {
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
onSuccess?: () => void;
|
||||
}) => {
|
||||
const { mutate } = useSWRConfig();
|
||||
const [credentialJsonStr, setCredentialJsonStr] = useState<
|
||||
@ -62,7 +67,6 @@ export const DriveJsonUpload = ({
|
||||
<Button
|
||||
disabled={!credentialJsonStr}
|
||||
onClick={async () => {
|
||||
// check if the JSON is a app credential or a service account credential
|
||||
let credentialFileType: GoogleDriveCredentialJsonTypes;
|
||||
try {
|
||||
const appCredentialJson = JSON.parse(credentialJsonStr!);
|
||||
@ -99,6 +103,10 @@ export const DriveJsonUpload = ({
|
||||
message: "Successfully uploaded app credentials",
|
||||
type: "success",
|
||||
});
|
||||
mutate("/api/manage/admin/connector/google-drive/app-credential");
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
@ -106,7 +114,6 @@ export const DriveJsonUpload = ({
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
mutate("/api/manage/admin/connector/google-drive/app-credential");
|
||||
}
|
||||
|
||||
if (credentialFileType === "service_account") {
|
||||
@ -122,19 +129,22 @@ export const DriveJsonUpload = ({
|
||||
);
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message: "Successfully uploaded app credentials",
|
||||
message: "Successfully uploaded service account key",
|
||||
type: "success",
|
||||
});
|
||||
mutate(
|
||||
"/api/manage/admin/connector/google-drive/service-account-key"
|
||||
);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
message: `Failed to upload app credentials - ${errorMsg}`,
|
||||
message: `Failed to upload service account key - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
mutate(
|
||||
"/api/manage/admin/connector/google-drive/service-account-key"
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -149,6 +159,7 @@ interface DriveJsonUploadSectionProps {
|
||||
appCredentialData?: { client_id: string };
|
||||
serviceAccountCredentialData?: { service_account_email: string };
|
||||
isAdmin: boolean;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export const DriveJsonUploadSection = ({
|
||||
@ -156,17 +167,36 @@ export const DriveJsonUploadSection = ({
|
||||
appCredentialData,
|
||||
serviceAccountCredentialData,
|
||||
isAdmin,
|
||||
onSuccess,
|
||||
}: DriveJsonUploadSectionProps) => {
|
||||
const { mutate } = useSWRConfig();
|
||||
const router = useRouter();
|
||||
const [localServiceAccountData, setLocalServiceAccountData] = useState(
|
||||
serviceAccountCredentialData
|
||||
);
|
||||
const [localAppCredentialData, setLocalAppCredentialData] =
|
||||
useState(appCredentialData);
|
||||
|
||||
if (serviceAccountCredentialData?.service_account_email) {
|
||||
useEffect(() => {
|
||||
setLocalServiceAccountData(serviceAccountCredentialData);
|
||||
setLocalAppCredentialData(appCredentialData);
|
||||
}, [serviceAccountCredentialData, appCredentialData]);
|
||||
|
||||
const handleSuccess = () => {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
} else {
|
||||
refreshAllGoogleData(ValidSources.GoogleDrive);
|
||||
}
|
||||
};
|
||||
|
||||
if (localServiceAccountData?.service_account_email) {
|
||||
return (
|
||||
<div className="mt-2 text-sm">
|
||||
<div>
|
||||
Found existing service account key with the following <b>Email:</b>
|
||||
<p className="italic mt-1">
|
||||
{serviceAccountCredentialData.service_account_email}
|
||||
{localServiceAccountData.service_account_email}
|
||||
</p>
|
||||
</div>
|
||||
{isAdmin ? (
|
||||
@ -188,11 +218,15 @@ export const DriveJsonUploadSection = ({
|
||||
mutate(
|
||||
"/api/manage/admin/connector/google-drive/service-account-key"
|
||||
);
|
||||
mutate(
|
||||
buildSimilarCredentialInfoURL(ValidSources.GoogleDrive)
|
||||
);
|
||||
setPopup({
|
||||
message: "Successfully deleted service account key",
|
||||
type: "success",
|
||||
});
|
||||
router.refresh();
|
||||
setLocalServiceAccountData(undefined);
|
||||
handleSuccess();
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
@ -216,12 +250,12 @@ export const DriveJsonUploadSection = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (appCredentialData?.client_id) {
|
||||
if (localAppCredentialData?.client_id) {
|
||||
return (
|
||||
<div className="mt-2 text-sm">
|
||||
<div>
|
||||
Found existing app credentials with the following <b>Client ID:</b>
|
||||
<p className="italic mt-1">{appCredentialData.client_id}</p>
|
||||
<p className="italic mt-1">{localAppCredentialData.client_id}</p>
|
||||
</div>
|
||||
{isAdmin ? (
|
||||
<>
|
||||
@ -242,10 +276,15 @@ export const DriveJsonUploadSection = ({
|
||||
mutate(
|
||||
"/api/manage/admin/connector/google-drive/app-credential"
|
||||
);
|
||||
mutate(
|
||||
buildSimilarCredentialInfoURL(ValidSources.GoogleDrive)
|
||||
);
|
||||
setPopup({
|
||||
message: "Successfully deleted app credentials",
|
||||
type: "success",
|
||||
});
|
||||
setLocalAppCredentialData(undefined);
|
||||
handleSuccess();
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
@ -297,7 +336,7 @@ export const DriveJsonUploadSection = ({
|
||||
Download the credentials JSON if choosing option (1) or the Service
|
||||
Account key JSON if chooosing option (2), and upload it here.
|
||||
</p>
|
||||
<DriveJsonUpload setPopup={setPopup} />
|
||||
<DriveJsonUpload setPopup={setPopup} onSuccess={handleSuccess} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -348,13 +387,41 @@ export const DriveAuthSection = ({
|
||||
appCredentialData,
|
||||
setPopup,
|
||||
refreshCredentials,
|
||||
connectorAssociated, // don't allow revoke if a connector / credential pair is active with the uploaded credential
|
||||
connectorAssociated,
|
||||
user,
|
||||
}: DriveCredentialSectionProps) => {
|
||||
const router = useRouter();
|
||||
const [localServiceAccountData, setLocalServiceAccountData] = useState(
|
||||
serviceAccountKeyData
|
||||
);
|
||||
const [localAppCredentialData, setLocalAppCredentialData] =
|
||||
useState(appCredentialData);
|
||||
const [
|
||||
localGoogleDrivePublicCredential,
|
||||
setLocalGoogleDrivePublicCredential,
|
||||
] = useState(googleDrivePublicUploadedCredential);
|
||||
const [
|
||||
localGoogleDriveServiceAccountCredential,
|
||||
setLocalGoogleDriveServiceAccountCredential,
|
||||
] = useState(googleDriveServiceAccountCredential);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalServiceAccountData(serviceAccountKeyData);
|
||||
setLocalAppCredentialData(appCredentialData);
|
||||
setLocalGoogleDrivePublicCredential(googleDrivePublicUploadedCredential);
|
||||
setLocalGoogleDriveServiceAccountCredential(
|
||||
googleDriveServiceAccountCredential
|
||||
);
|
||||
}, [
|
||||
serviceAccountKeyData,
|
||||
appCredentialData,
|
||||
googleDrivePublicUploadedCredential,
|
||||
googleDriveServiceAccountCredential,
|
||||
]);
|
||||
|
||||
const existingCredential =
|
||||
googleDrivePublicUploadedCredential || googleDriveServiceAccountCredential;
|
||||
localGoogleDrivePublicCredential ||
|
||||
localGoogleDriveServiceAccountCredential;
|
||||
if (existingCredential) {
|
||||
return (
|
||||
<>
|
||||
@ -377,7 +444,7 @@ export const DriveAuthSection = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (serviceAccountKeyData?.service_account_email) {
|
||||
if (localServiceAccountData?.service_account_email) {
|
||||
return (
|
||||
<div>
|
||||
<Formik
|
||||
@ -438,7 +505,7 @@ export const DriveAuthSection = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (appCredentialData?.client_id) {
|
||||
if (localAppCredentialData?.client_id) {
|
||||
return (
|
||||
<div className="text-sm mb-4">
|
||||
<p className="mb-2">
|
||||
|
@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { FetchError, errorHandlingFetcher } from "@/lib/fetcher";
|
||||
import React from "react";
|
||||
import { FetchError } from "@/lib/fetcher";
|
||||
import { ErrorCallout } from "@/components/ErrorCallout";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
@ -15,22 +14,17 @@ import {
|
||||
GoogleDriveCredentialJson,
|
||||
GoogleDriveServiceAccountCredentialJson,
|
||||
} from "@/lib/connectors/credentials";
|
||||
import { ConnectorSnapshot } from "@/lib/connectors/connectors";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import { buildSimilarCredentialInfoURL } from "@/app/admin/connector/[ccPairId]/lib";
|
||||
|
||||
const useConnectorsByCredentialId = (credential_id: number | null) => {
|
||||
let url: string | null = null;
|
||||
if (credential_id !== null) {
|
||||
url = `/api/manage/admin/connector?credential=${credential_id}`;
|
||||
}
|
||||
const swrResponse = useSWR<ConnectorSnapshot[]>(url, errorHandlingFetcher);
|
||||
|
||||
return {
|
||||
...swrResponse,
|
||||
refreshConnectorsByCredentialId: () => mutate(url),
|
||||
};
|
||||
};
|
||||
import {
|
||||
useGoogleAppCredential,
|
||||
useGoogleServiceAccountKey,
|
||||
useGoogleCredentials,
|
||||
useConnectorsByCredentialId,
|
||||
checkCredentialsFetched,
|
||||
filterUploadedCredentials,
|
||||
checkConnectorsExist,
|
||||
refreshAllGoogleData,
|
||||
} from "@/lib/googleConnector";
|
||||
|
||||
const GDriveMain = ({
|
||||
setPopup,
|
||||
@ -39,27 +33,20 @@ const GDriveMain = ({
|
||||
}) => {
|
||||
const { isAdmin, user } = useUser();
|
||||
|
||||
// tries getting the uploaded credential json
|
||||
// Get app credential and service account key
|
||||
const {
|
||||
data: appCredentialData,
|
||||
isLoading: isAppCredentialLoading,
|
||||
error: isAppCredentialError,
|
||||
} = useSWR<{ client_id: string }, FetchError>(
|
||||
"/api/manage/admin/connector/google-drive/app-credential",
|
||||
errorHandlingFetcher
|
||||
);
|
||||
} = useGoogleAppCredential("google_drive");
|
||||
|
||||
// tries getting the uploaded service account key
|
||||
const {
|
||||
data: serviceAccountKeyData,
|
||||
isLoading: isServiceAccountKeyLoading,
|
||||
error: isServiceAccountKeyError,
|
||||
} = useSWR<{ service_account_email: string }, FetchError>(
|
||||
"/api/manage/admin/connector/google-drive/service-account-key",
|
||||
errorHandlingFetcher
|
||||
);
|
||||
} = useGoogleServiceAccountKey("google_drive");
|
||||
|
||||
// gets all public credentials
|
||||
// Get all public credentials
|
||||
const {
|
||||
data: credentialsData,
|
||||
isLoading: isCredentialsLoading,
|
||||
@ -67,33 +54,19 @@ const GDriveMain = ({
|
||||
refreshCredentials,
|
||||
} = usePublicCredentials();
|
||||
|
||||
// gets all credentials for source type google drive
|
||||
// Get Google Drive-specific credentials
|
||||
const {
|
||||
data: googleDriveCredentials,
|
||||
isLoading: isGoogleDriveCredentialsLoading,
|
||||
error: googleDriveCredentialsError,
|
||||
} = useSWR<Credential<any>[]>(
|
||||
buildSimilarCredentialInfoURL(ValidSources.GoogleDrive),
|
||||
errorHandlingFetcher,
|
||||
{ refreshInterval: 5000 }
|
||||
} = useGoogleCredentials(ValidSources.GoogleDrive);
|
||||
|
||||
// Filter uploaded credentials and get credential ID
|
||||
const { credential_id, uploadedCredentials } = filterUploadedCredentials(
|
||||
googleDriveCredentials
|
||||
);
|
||||
|
||||
// filters down to just credentials that were created via upload (there should be only one)
|
||||
let credential_id = null;
|
||||
if (googleDriveCredentials) {
|
||||
const googleDriveUploadedCredentials: Credential<GoogleDriveCredentialJson>[] =
|
||||
googleDriveCredentials.filter(
|
||||
(googleDriveCredential) =>
|
||||
googleDriveCredential.credential_json.authentication_method !==
|
||||
"oauth_interactive"
|
||||
);
|
||||
|
||||
if (googleDriveUploadedCredentials.length > 0) {
|
||||
credential_id = googleDriveUploadedCredentials[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
// retrieves all connectors for that credential id
|
||||
// Get connectors for the credential ID
|
||||
const {
|
||||
data: googleDriveConnectors,
|
||||
isLoading: isGoogleDriveConnectorsLoading,
|
||||
@ -101,13 +74,25 @@ const GDriveMain = ({
|
||||
refreshConnectorsByCredentialId,
|
||||
} = useConnectorsByCredentialId(credential_id);
|
||||
|
||||
const appCredentialSuccessfullyFetched =
|
||||
appCredentialData ||
|
||||
(isAppCredentialError && isAppCredentialError.status === 404);
|
||||
const serviceAccountKeySuccessfullyFetched =
|
||||
serviceAccountKeyData ||
|
||||
(isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
|
||||
// Check if credentials were successfully fetched
|
||||
const {
|
||||
appCredentialSuccessfullyFetched,
|
||||
serviceAccountKeySuccessfullyFetched,
|
||||
} = checkCredentialsFetched(
|
||||
appCredentialData,
|
||||
isAppCredentialError,
|
||||
serviceAccountKeyData,
|
||||
isServiceAccountKeyError
|
||||
);
|
||||
|
||||
// Handle refresh of all data
|
||||
const handleRefresh = () => {
|
||||
refreshCredentials();
|
||||
refreshConnectorsByCredentialId();
|
||||
refreshAllGoogleData(ValidSources.GoogleDrive);
|
||||
};
|
||||
|
||||
// Loading state
|
||||
if (
|
||||
(!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
|
||||
(!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
|
||||
@ -122,6 +107,7 @@ const GDriveMain = ({
|
||||
);
|
||||
}
|
||||
|
||||
// Error states
|
||||
if (credentialsError || !credentialsData) {
|
||||
return <ErrorCallout errorTitle="Failed to load credentials." />;
|
||||
}
|
||||
@ -141,7 +127,16 @@ const GDriveMain = ({
|
||||
);
|
||||
}
|
||||
|
||||
// get the actual uploaded oauth or service account credentials
|
||||
if (googleDriveConnectorsError) {
|
||||
return (
|
||||
<ErrorCallout errorTitle="Failed to load Google Drive associated connectors." />
|
||||
);
|
||||
}
|
||||
|
||||
// Check if connectors exist
|
||||
const connectorAssociated = checkConnectorsExist(googleDriveConnectors);
|
||||
|
||||
// Get the uploaded OAuth credential
|
||||
const googleDrivePublicUploadedCredential:
|
||||
| Credential<GoogleDriveCredentialJson>
|
||||
| undefined = credentialsData.find(
|
||||
@ -152,6 +147,7 @@ const GDriveMain = ({
|
||||
credential.credential_json.authentication_method !== "oauth_interactive"
|
||||
);
|
||||
|
||||
// Get the service account credential
|
||||
const googleDriveServiceAccountCredential:
|
||||
| Credential<GoogleDriveServiceAccountCredentialJson>
|
||||
| undefined = credentialsData.find(
|
||||
@ -160,19 +156,6 @@ const GDriveMain = ({
|
||||
credential.source === "google_drive"
|
||||
);
|
||||
|
||||
if (googleDriveConnectorsError) {
|
||||
return (
|
||||
<ErrorCallout errorTitle="Failed to load Google Drive associated connectors." />
|
||||
);
|
||||
}
|
||||
|
||||
let connectorAssociated = false;
|
||||
if (googleDriveConnectors) {
|
||||
if (googleDriveConnectors.length > 0) {
|
||||
connectorAssociated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title className="mb-2 mt-6">Step 1: Provide your Credentials</Title>
|
||||
@ -181,27 +164,30 @@ const GDriveMain = ({
|
||||
appCredentialData={appCredentialData}
|
||||
serviceAccountCredentialData={serviceAccountKeyData}
|
||||
isAdmin={isAdmin}
|
||||
onSuccess={handleRefresh}
|
||||
/>
|
||||
|
||||
{isAdmin && (
|
||||
<>
|
||||
<Title className="mb-2 mt-6">Step 2: Authenticate with Onyx</Title>
|
||||
<DriveAuthSection
|
||||
setPopup={setPopup}
|
||||
refreshCredentials={refreshCredentials}
|
||||
googleDrivePublicUploadedCredential={
|
||||
googleDrivePublicUploadedCredential
|
||||
}
|
||||
googleDriveServiceAccountCredential={
|
||||
googleDriveServiceAccountCredential
|
||||
}
|
||||
appCredentialData={appCredentialData}
|
||||
serviceAccountKeyData={serviceAccountKeyData}
|
||||
connectorAssociated={connectorAssociated}
|
||||
user={user}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isAdmin &&
|
||||
(appCredentialData?.client_id ||
|
||||
serviceAccountKeyData?.service_account_email) && (
|
||||
<>
|
||||
<Title className="mb-2 mt-6">Step 2: Authenticate with Onyx</Title>
|
||||
<DriveAuthSection
|
||||
setPopup={setPopup}
|
||||
refreshCredentials={handleRefresh}
|
||||
googleDrivePublicUploadedCredential={
|
||||
googleDrivePublicUploadedCredential
|
||||
}
|
||||
googleDriveServiceAccountCredential={
|
||||
googleDriveServiceAccountCredential
|
||||
}
|
||||
appCredentialData={appCredentialData}
|
||||
serviceAccountKeyData={serviceAccountKeyData}
|
||||
connectorAssociated={connectorAssociated}
|
||||
user={user}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button } from "@/components/Button";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSWRConfig } from "swr";
|
||||
import * as Yup from "yup";
|
||||
import { useRouter } from "next/navigation";
|
||||
@ -17,13 +17,18 @@ import {
|
||||
GmailCredentialJson,
|
||||
GmailServiceAccountCredentialJson,
|
||||
} from "@/lib/connectors/credentials";
|
||||
import { refreshAllGoogleData } from "@/lib/googleConnector";
|
||||
import { ValidSources } from "@/lib/types";
|
||||
import { buildSimilarCredentialInfoURL } from "@/app/admin/connector/[ccPairId]/lib";
|
||||
|
||||
type GmailCredentialJsonTypes = "authorized_user" | "service_account";
|
||||
|
||||
const DriveJsonUpload = ({
|
||||
setPopup,
|
||||
onSuccess,
|
||||
}: {
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
onSuccess?: () => void;
|
||||
}) => {
|
||||
const { mutate } = useSWRConfig();
|
||||
const [credentialJsonStr, setCredentialJsonStr] = useState<
|
||||
@ -72,7 +77,7 @@ const DriveJsonUpload = ({
|
||||
credentialFileType = "service_account";
|
||||
} else {
|
||||
throw new Error(
|
||||
"Unknown credential type, expected 'OAuth Web application'"
|
||||
"Unknown credential type, expected one of 'OAuth Web application' or 'Service Account'"
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@ -99,6 +104,10 @@ const DriveJsonUpload = ({
|
||||
message: "Successfully uploaded app credentials",
|
||||
type: "success",
|
||||
});
|
||||
mutate("/api/manage/admin/connector/gmail/app-credential");
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
@ -106,7 +115,6 @@ const DriveJsonUpload = ({
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
mutate("/api/manage/admin/connector/gmail/app-credential");
|
||||
}
|
||||
|
||||
if (credentialFileType === "service_account") {
|
||||
@ -122,17 +130,20 @@ const DriveJsonUpload = ({
|
||||
);
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message: "Successfully uploaded app credentials",
|
||||
message: "Successfully uploaded service account key",
|
||||
type: "success",
|
||||
});
|
||||
mutate("/api/manage/admin/connector/gmail/service-account-key");
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
message: `Failed to upload app credentials - ${errorMsg}`,
|
||||
message: `Failed to upload service account key - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
mutate("/api/manage/admin/connector/gmail/service-account-key");
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -147,6 +158,7 @@ interface DriveJsonUploadSectionProps {
|
||||
appCredentialData?: { client_id: string };
|
||||
serviceAccountCredentialData?: { service_account_email: string };
|
||||
isAdmin: boolean;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export const GmailJsonUploadSection = ({
|
||||
@ -154,16 +166,37 @@ export const GmailJsonUploadSection = ({
|
||||
appCredentialData,
|
||||
serviceAccountCredentialData,
|
||||
isAdmin,
|
||||
onSuccess,
|
||||
}: DriveJsonUploadSectionProps) => {
|
||||
const { mutate } = useSWRConfig();
|
||||
const router = useRouter();
|
||||
const [localServiceAccountData, setLocalServiceAccountData] = useState(
|
||||
serviceAccountCredentialData
|
||||
);
|
||||
const [localAppCredentialData, setLocalAppCredentialData] =
|
||||
useState(appCredentialData);
|
||||
|
||||
if (serviceAccountCredentialData?.service_account_email) {
|
||||
// Update local state when props change
|
||||
useEffect(() => {
|
||||
setLocalServiceAccountData(serviceAccountCredentialData);
|
||||
setLocalAppCredentialData(appCredentialData);
|
||||
}, [serviceAccountCredentialData, appCredentialData]);
|
||||
|
||||
const handleSuccess = () => {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
} else {
|
||||
refreshAllGoogleData(ValidSources.Gmail);
|
||||
}
|
||||
};
|
||||
|
||||
if (localServiceAccountData?.service_account_email) {
|
||||
return (
|
||||
<div className="mt-2 text-sm">
|
||||
<div>
|
||||
Found existing service account key with the following <b>Email:</b>
|
||||
<p className="italic mt-1">
|
||||
{serviceAccountCredentialData.service_account_email}
|
||||
{localServiceAccountData.service_account_email}
|
||||
</p>
|
||||
</div>
|
||||
{isAdmin ? (
|
||||
@ -185,10 +218,15 @@ export const GmailJsonUploadSection = ({
|
||||
mutate(
|
||||
"/api/manage/admin/connector/gmail/service-account-key"
|
||||
);
|
||||
// Also mutate the credential endpoints to ensure Step 2 is reset
|
||||
mutate(buildSimilarCredentialInfoURL(ValidSources.Gmail));
|
||||
setPopup({
|
||||
message: "Successfully deleted service account key",
|
||||
type: "success",
|
||||
});
|
||||
// Immediately update local state
|
||||
setLocalServiceAccountData(undefined);
|
||||
handleSuccess();
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
@ -212,43 +250,56 @@ export const GmailJsonUploadSection = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (appCredentialData?.client_id) {
|
||||
if (localAppCredentialData?.client_id) {
|
||||
return (
|
||||
<div className="mt-2 text-sm">
|
||||
<div>
|
||||
Found existing app credentials with the following <b>Client ID:</b>
|
||||
<p className="italic mt-1">{appCredentialData.client_id}</p>
|
||||
<p className="italic mt-1">{localAppCredentialData.client_id}</p>
|
||||
</div>
|
||||
<div className="mt-4 mb-1">
|
||||
If you want to update these credentials, delete the existing
|
||||
credentials through the button below, and then upload a new
|
||||
credentials JSON.
|
||||
</div>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const response = await fetch(
|
||||
"/api/manage/admin/connector/gmail/app-credential",
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
if (response.ok) {
|
||||
mutate("/api/manage/admin/connector/gmail/app-credential");
|
||||
setPopup({
|
||||
message: "Successfully deleted service account key",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
message: `Failed to delete app credential - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
{isAdmin ? (
|
||||
<>
|
||||
<div className="mt-4 mb-1">
|
||||
If you want to update these credentials, delete the existing
|
||||
credentials through the button below, and then upload a new
|
||||
credentials JSON.
|
||||
</div>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const response = await fetch(
|
||||
"/api/manage/admin/connector/gmail/app-credential",
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
if (response.ok) {
|
||||
mutate("/api/manage/admin/connector/gmail/app-credential");
|
||||
// Also mutate the credential endpoints to ensure Step 2 is reset
|
||||
mutate(buildSimilarCredentialInfoURL(ValidSources.Gmail));
|
||||
setPopup({
|
||||
message: "Successfully deleted app credentials",
|
||||
type: "success",
|
||||
});
|
||||
// Immediately update local state
|
||||
setLocalAppCredentialData(undefined);
|
||||
handleSuccess();
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
message: `Failed to delete app credential - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className="mt-4 mb-1">
|
||||
To change these credentials, please contact an administrator.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -276,14 +327,14 @@ export const GmailJsonUploadSection = ({
|
||||
>
|
||||
here
|
||||
</a>{" "}
|
||||
to either (1) setup a google OAuth App in your company workspace or (2)
|
||||
to either (1) setup a Google OAuth App in your company workspace or (2)
|
||||
create a Service Account.
|
||||
<br />
|
||||
<br />
|
||||
Download the credentials JSON if choosing option (1) or the Service
|
||||
Account key JSON if chooosing option (2), and upload it here.
|
||||
Account key JSON if choosing option (2), and upload it here.
|
||||
</p>
|
||||
<DriveJsonUpload setPopup={setPopup} />
|
||||
<DriveJsonUpload setPopup={setPopup} onSuccess={handleSuccess} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -299,6 +350,34 @@ interface DriveCredentialSectionProps {
|
||||
user: User | null;
|
||||
}
|
||||
|
||||
async function handleRevokeAccess(
|
||||
connectorExists: boolean,
|
||||
setPopup: (popupSpec: PopupSpec | null) => void,
|
||||
existingCredential:
|
||||
| Credential<GmailCredentialJson>
|
||||
| Credential<GmailServiceAccountCredentialJson>,
|
||||
refreshCredentials: () => void
|
||||
) {
|
||||
if (connectorExists) {
|
||||
const message =
|
||||
"Cannot revoke the Gmail credential while any connector is still associated with the credential. " +
|
||||
"Please delete all associated connectors, then try again.";
|
||||
setPopup({
|
||||
message: message,
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await adminDeleteCredential(existingCredential.id);
|
||||
setPopup({
|
||||
message: "Successfully revoked the Gmail credential!",
|
||||
type: "success",
|
||||
});
|
||||
|
||||
refreshCredentials();
|
||||
}
|
||||
|
||||
export const GmailAuthSection = ({
|
||||
gmailPublicCredential,
|
||||
gmailServiceAccountCredential,
|
||||
@ -310,31 +389,49 @@ export const GmailAuthSection = ({
|
||||
user,
|
||||
}: DriveCredentialSectionProps) => {
|
||||
const router = useRouter();
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
const [localServiceAccountData, setLocalServiceAccountData] = useState(
|
||||
serviceAccountKeyData
|
||||
);
|
||||
const [localAppCredentialData, setLocalAppCredentialData] =
|
||||
useState(appCredentialData);
|
||||
const [localGmailPublicCredential, setLocalGmailPublicCredential] = useState(
|
||||
gmailPublicCredential
|
||||
);
|
||||
const [
|
||||
localGmailServiceAccountCredential,
|
||||
setLocalGmailServiceAccountCredential,
|
||||
] = useState(gmailServiceAccountCredential);
|
||||
|
||||
// Update local state when props change
|
||||
useEffect(() => {
|
||||
setLocalServiceAccountData(serviceAccountKeyData);
|
||||
setLocalAppCredentialData(appCredentialData);
|
||||
setLocalGmailPublicCredential(gmailPublicCredential);
|
||||
setLocalGmailServiceAccountCredential(gmailServiceAccountCredential);
|
||||
}, [
|
||||
serviceAccountKeyData,
|
||||
appCredentialData,
|
||||
gmailPublicCredential,
|
||||
gmailServiceAccountCredential,
|
||||
]);
|
||||
|
||||
const existingCredential =
|
||||
gmailPublicCredential || gmailServiceAccountCredential;
|
||||
localGmailPublicCredential || localGmailServiceAccountCredential;
|
||||
if (existingCredential) {
|
||||
return (
|
||||
<>
|
||||
<p className="mb-2 text-sm">
|
||||
<i>Existing credential already set up!</i>
|
||||
<i>Uploaded and authenticated credential already exists!</i>
|
||||
</p>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (connectorExists) {
|
||||
setPopup({
|
||||
message:
|
||||
"Cannot revoke access to Gmail while any connector is still set up. Please delete all connectors, then try again.",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await adminDeleteCredential(existingCredential.id);
|
||||
setPopup({
|
||||
message: "Successfully revoked access to Gmail!",
|
||||
type: "success",
|
||||
});
|
||||
refreshCredentials();
|
||||
handleRevokeAccess(
|
||||
connectorExists,
|
||||
setPopup,
|
||||
existingCredential,
|
||||
refreshCredentials
|
||||
);
|
||||
}}
|
||||
>
|
||||
Revoke Access
|
||||
@ -343,20 +440,21 @@ export const GmailAuthSection = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (serviceAccountKeyData?.service_account_email) {
|
||||
if (localServiceAccountData?.service_account_email) {
|
||||
return (
|
||||
<div>
|
||||
<CardSection>
|
||||
<Formik
|
||||
initialValues={{
|
||||
google_primary_admin: user?.email || "",
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
google_primary_admin: Yup.string().required(),
|
||||
})}
|
||||
onSubmit={async (values, formikHelpers) => {
|
||||
formikHelpers.setSubmitting(true);
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
google_primary_admin: user?.email || "",
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
google_primary_admin: Yup.string()
|
||||
.email("Must be a valid email")
|
||||
.required("Required"),
|
||||
})}
|
||||
onSubmit={async (values, formikHelpers) => {
|
||||
formikHelpers.setSubmitting(true);
|
||||
try {
|
||||
const response = await fetch(
|
||||
"/api/manage/admin/connector/gmail/service-account-credential",
|
||||
{
|
||||
@ -375,6 +473,7 @@ export const GmailAuthSection = ({
|
||||
message: "Successfully created service account credential",
|
||||
type: "success",
|
||||
});
|
||||
refreshCredentials();
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
@ -382,65 +481,73 @@ export const GmailAuthSection = ({
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
refreshCredentials();
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<TextFormField
|
||||
name="google_primary_admin"
|
||||
label="Primary Admin Email:"
|
||||
subtext="You must provide an admin/owner account to retrieve all org emails."
|
||||
/>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className={
|
||||
"bg-slate-500 hover:bg-slate-700 text-white " +
|
||||
"font-bold py-2 px-4 rounded focus:outline-none " +
|
||||
"focus:shadow-outline w-full max-w-sm mx-auto"
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</CardSection>
|
||||
} catch (error) {
|
||||
setPopup({
|
||||
message: `Failed to create service account credential - ${error}`,
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
formikHelpers.setSubmitting(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<TextFormField
|
||||
name="google_primary_admin"
|
||||
label="Primary Admin Email:"
|
||||
subtext="Enter the email of an admin/owner of the Google Organization that owns the Gmail account(s) you want to index."
|
||||
/>
|
||||
<div className="flex">
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
Create Credential
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (appCredentialData?.client_id) {
|
||||
if (localAppCredentialData?.client_id) {
|
||||
return (
|
||||
<div className="text-sm mb-4">
|
||||
<p className="mb-2">
|
||||
Next, you must provide credentials via OAuth. This gives us read
|
||||
access to the docs you have access to in your gmail account.
|
||||
access to the emails you have access to in your Gmail account.
|
||||
</p>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const [authUrl, errorMsg] = await setupGmailOAuth({
|
||||
isAdmin: true,
|
||||
});
|
||||
if (authUrl) {
|
||||
// cookie used by callback to determine where to finally redirect to
|
||||
setIsAuthenticating(true);
|
||||
try {
|
||||
Cookies.set(GMAIL_AUTH_IS_ADMIN_COOKIE_NAME, "true", {
|
||||
path: "/",
|
||||
});
|
||||
router.push(authUrl);
|
||||
return;
|
||||
}
|
||||
const [authUrl, errorMsg] = await setupGmailOAuth({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
setPopup({
|
||||
message: errorMsg,
|
||||
type: "error",
|
||||
});
|
||||
if (authUrl) {
|
||||
router.push(authUrl);
|
||||
} else {
|
||||
setPopup({
|
||||
message: errorMsg,
|
||||
type: "error",
|
||||
});
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setPopup({
|
||||
message: `Failed to authenticate with Gmail - ${error}`,
|
||||
type: "error",
|
||||
});
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
}}
|
||||
disabled={isAuthenticating}
|
||||
>
|
||||
Authenticate with Gmail
|
||||
{isAuthenticating ? "Authenticating..." : "Authenticate with Gmail"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@ -449,8 +556,8 @@ export const GmailAuthSection = ({
|
||||
// case where no keys have been uploaded in step 1
|
||||
return (
|
||||
<p className="text-sm">
|
||||
Please upload an OAuth or Service Account Credential JSON in Step 1 before
|
||||
moving onto Step 2.
|
||||
Please upload either a OAuth Client Credential JSON or a Gmail Service
|
||||
Account Key JSON in Step 1 before moving onto Step 2.
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import useSWR from "swr";
|
||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||
import React from "react";
|
||||
import { FetchError } from "@/lib/fetcher";
|
||||
import { ErrorCallout } from "@/components/ErrorCallout";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { CCPairBasicInfo } from "@/lib/types";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { CCPairBasicInfo, ValidSources } from "@/lib/types";
|
||||
import {
|
||||
Credential,
|
||||
GmailCredentialJson,
|
||||
@ -14,26 +15,33 @@ import { GmailAuthSection, GmailJsonUploadSection } from "./Credential";
|
||||
import { usePublicCredentials, useBasicConnectorStatus } from "@/lib/hooks";
|
||||
import Title from "@/components/ui/title";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import {
|
||||
useGoogleAppCredential,
|
||||
useGoogleServiceAccountKey,
|
||||
useGoogleCredentials,
|
||||
useConnectorsByCredentialId,
|
||||
checkCredentialsFetched,
|
||||
filterUploadedCredentials,
|
||||
checkConnectorsExist,
|
||||
refreshAllGoogleData,
|
||||
} from "@/lib/googleConnector";
|
||||
|
||||
export const GmailMain = () => {
|
||||
const { isAdmin, user } = useUser();
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
const {
|
||||
data: appCredentialData,
|
||||
isLoading: isAppCredentialLoading,
|
||||
error: isAppCredentialError,
|
||||
} = useSWR<{ client_id: string }>(
|
||||
"/api/manage/admin/connector/gmail/app-credential",
|
||||
errorHandlingFetcher
|
||||
);
|
||||
} = useGoogleAppCredential("gmail");
|
||||
|
||||
const {
|
||||
data: serviceAccountKeyData,
|
||||
isLoading: isServiceAccountKeyLoading,
|
||||
error: isServiceAccountKeyError,
|
||||
} = useSWR<{ service_account_email: string }>(
|
||||
"/api/manage/admin/connector/gmail/service-account-key",
|
||||
errorHandlingFetcher
|
||||
);
|
||||
} = useGoogleServiceAccountKey("gmail");
|
||||
|
||||
const {
|
||||
data: connectorIndexingStatuses,
|
||||
isLoading: isConnectorIndexingStatusesLoading,
|
||||
@ -47,20 +55,45 @@ export const GmailMain = () => {
|
||||
refreshCredentials,
|
||||
} = usePublicCredentials();
|
||||
|
||||
const { popup, setPopup } = usePopup();
|
||||
const {
|
||||
data: gmailCredentials,
|
||||
isLoading: isGmailCredentialsLoading,
|
||||
error: gmailCredentialsError,
|
||||
} = useGoogleCredentials(ValidSources.Gmail);
|
||||
|
||||
const appCredentialSuccessfullyFetched =
|
||||
appCredentialData ||
|
||||
(isAppCredentialError && isAppCredentialError.status === 404);
|
||||
const serviceAccountKeySuccessfullyFetched =
|
||||
serviceAccountKeyData ||
|
||||
(isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
|
||||
const { credential_id, uploadedCredentials } =
|
||||
filterUploadedCredentials(gmailCredentials);
|
||||
|
||||
const {
|
||||
data: gmailConnectors,
|
||||
isLoading: isGmailConnectorsLoading,
|
||||
error: gmailConnectorsError,
|
||||
refreshConnectorsByCredentialId,
|
||||
} = useConnectorsByCredentialId(credential_id);
|
||||
|
||||
const {
|
||||
appCredentialSuccessfullyFetched,
|
||||
serviceAccountKeySuccessfullyFetched,
|
||||
} = checkCredentialsFetched(
|
||||
appCredentialData,
|
||||
isAppCredentialError,
|
||||
serviceAccountKeyData,
|
||||
isServiceAccountKeyError
|
||||
);
|
||||
|
||||
const handleRefresh = () => {
|
||||
refreshCredentials();
|
||||
refreshConnectorsByCredentialId();
|
||||
refreshAllGoogleData(ValidSources.Gmail);
|
||||
};
|
||||
|
||||
if (
|
||||
(!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
|
||||
(!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
|
||||
(!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
|
||||
(!credentialsData && isCredentialsLoading)
|
||||
(!credentialsData && isCredentialsLoading) ||
|
||||
(!gmailCredentials && isGmailCredentialsLoading) ||
|
||||
(!gmailConnectors && isGmailConnectorsLoading)
|
||||
) {
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
@ -70,19 +103,15 @@ export const GmailMain = () => {
|
||||
}
|
||||
|
||||
if (credentialsError || !credentialsData) {
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
<div className="text-red-500">Failed to load credentials.</div>
|
||||
</div>
|
||||
);
|
||||
return <ErrorCallout errorTitle="Failed to load credentials." />;
|
||||
}
|
||||
|
||||
if (gmailCredentialsError || !gmailCredentials) {
|
||||
return <ErrorCallout errorTitle="Failed to load Gmail credentials." />;
|
||||
}
|
||||
|
||||
if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
<div className="text-red-500">Failed to load connectors.</div>
|
||||
</div>
|
||||
);
|
||||
return <ErrorCallout errorTitle="Failed to load connectors." />;
|
||||
}
|
||||
|
||||
if (
|
||||
@ -90,21 +119,28 @@ export const GmailMain = () => {
|
||||
!serviceAccountKeySuccessfullyFetched
|
||||
) {
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
<div className="text-red-500">
|
||||
Error loading Gmail app credentials. Contact an administrator.
|
||||
</div>
|
||||
</div>
|
||||
<ErrorCallout errorTitle="Error loading Gmail app credentials. Contact an administrator." />
|
||||
);
|
||||
}
|
||||
|
||||
const gmailPublicCredential: Credential<GmailCredentialJson> | undefined =
|
||||
credentialsData.find(
|
||||
(credential) =>
|
||||
(credential.credential_json?.google_service_account_key ||
|
||||
credential.credential_json?.google_tokens) &&
|
||||
credential.admin_public
|
||||
if (gmailConnectorsError) {
|
||||
return (
|
||||
<ErrorCallout errorTitle="Failed to load Gmail associated connectors." />
|
||||
);
|
||||
}
|
||||
|
||||
const connectorExistsFromCredential = checkConnectorsExist(gmailConnectors);
|
||||
|
||||
const gmailPublicUploadedCredential:
|
||||
| Credential<GmailCredentialJson>
|
||||
| undefined = credentialsData.find(
|
||||
(credential) =>
|
||||
credential.credential_json?.google_tokens &&
|
||||
credential.admin_public &&
|
||||
credential.source === "gmail" &&
|
||||
credential.credential_json.authentication_method !== "oauth_interactive"
|
||||
);
|
||||
|
||||
const gmailServiceAccountCredential:
|
||||
| Credential<GmailServiceAccountCredentialJson>
|
||||
| undefined = credentialsData.find(
|
||||
@ -118,6 +154,13 @@ export const GmailMain = () => {
|
||||
(connectorIndexingStatus) => connectorIndexingStatus.source === "gmail"
|
||||
);
|
||||
|
||||
const connectorExists =
|
||||
connectorExistsFromCredential || gmailConnectorIndexingStatuses.length > 0;
|
||||
|
||||
const hasUploadedCredentials =
|
||||
Boolean(appCredentialData?.client_id) ||
|
||||
Boolean(serviceAccountKeyData?.service_account_email);
|
||||
|
||||
return (
|
||||
<>
|
||||
{popup}
|
||||
@ -129,21 +172,22 @@ export const GmailMain = () => {
|
||||
appCredentialData={appCredentialData}
|
||||
serviceAccountCredentialData={serviceAccountKeyData}
|
||||
isAdmin={isAdmin}
|
||||
onSuccess={handleRefresh}
|
||||
/>
|
||||
|
||||
{isAdmin && (
|
||||
{isAdmin && hasUploadedCredentials && (
|
||||
<>
|
||||
<Title className="mb-2 mt-6 ml-auto mr-auto">
|
||||
Step 2: Authenticate with Onyx
|
||||
</Title>
|
||||
<GmailAuthSection
|
||||
setPopup={setPopup}
|
||||
refreshCredentials={refreshCredentials}
|
||||
gmailPublicCredential={gmailPublicCredential}
|
||||
refreshCredentials={handleRefresh}
|
||||
gmailPublicCredential={gmailPublicUploadedCredential}
|
||||
gmailServiceAccountCredential={gmailServiceAccountCredential}
|
||||
appCredentialData={appCredentialData}
|
||||
serviceAccountKeyData={serviceAccountKeyData}
|
||||
connectorExists={gmailConnectorIndexingStatuses.length > 0}
|
||||
connectorExists={connectorExists}
|
||||
user={user}
|
||||
/>
|
||||
</>
|
||||
|
@ -3,7 +3,6 @@ interface Props {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
type?: "button" | "submit" | "reset";
|
||||
disabled?: boolean;
|
||||
fullWidth?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@ -12,14 +11,12 @@ export const Button = ({
|
||||
onClick,
|
||||
type = "submit",
|
||||
disabled = false,
|
||||
fullWidth = false,
|
||||
className = "",
|
||||
}: Props) => {
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
"group relative " +
|
||||
(fullWidth ? "w-full " : "") +
|
||||
"py-1 px-2 border border-transparent text-sm " +
|
||||
"font-medium rounded-md text-white " +
|
||||
"focus:outline-none focus:ring-2 " +
|
||||
|
120
web/src/lib/googleConnector.ts
Normal file
120
web/src/lib/googleConnector.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { FetchError, errorHandlingFetcher } from "@/lib/fetcher";
|
||||
import { Credential } from "@/lib/connectors/credentials";
|
||||
import { ConnectorSnapshot } from "@/lib/connectors/connectors";
|
||||
import { ValidSources } from "@/lib/types";
|
||||
import { buildSimilarCredentialInfoURL } from "@/app/admin/connector/[ccPairId]/lib";
|
||||
|
||||
// Constants for service names to avoid typos
|
||||
export const GOOGLE_SERVICES = {
|
||||
GMAIL: "gmail",
|
||||
GOOGLE_DRIVE: "google-drive",
|
||||
} as const;
|
||||
|
||||
export const useGoogleAppCredential = (service: "gmail" | "google_drive") => {
|
||||
const endpoint = `/api/manage/admin/connector/${
|
||||
service === "gmail" ? GOOGLE_SERVICES.GMAIL : GOOGLE_SERVICES.GOOGLE_DRIVE
|
||||
}/app-credential`;
|
||||
|
||||
return useSWR<{ client_id: string }, FetchError>(
|
||||
endpoint,
|
||||
errorHandlingFetcher
|
||||
);
|
||||
};
|
||||
|
||||
export const useGoogleServiceAccountKey = (
|
||||
service: "gmail" | "google_drive"
|
||||
) => {
|
||||
const endpoint = `/api/manage/admin/connector/${
|
||||
service === "gmail" ? GOOGLE_SERVICES.GMAIL : GOOGLE_SERVICES.GOOGLE_DRIVE
|
||||
}/service-account-key`;
|
||||
|
||||
return useSWR<{ service_account_email: string }, FetchError>(
|
||||
endpoint,
|
||||
errorHandlingFetcher
|
||||
);
|
||||
};
|
||||
|
||||
export const useGoogleCredentials = (
|
||||
source: ValidSources.Gmail | ValidSources.GoogleDrive
|
||||
) => {
|
||||
return useSWR<Credential<any>[]>(
|
||||
buildSimilarCredentialInfoURL(source),
|
||||
errorHandlingFetcher,
|
||||
{ refreshInterval: 5000 }
|
||||
);
|
||||
};
|
||||
|
||||
export const useConnectorsByCredentialId = (credential_id: number | null) => {
|
||||
let url: string | null = null;
|
||||
if (credential_id !== null) {
|
||||
url = `/api/manage/admin/connector?credential=${credential_id}`;
|
||||
}
|
||||
const swrResponse = useSWR<ConnectorSnapshot[]>(url, errorHandlingFetcher);
|
||||
|
||||
return {
|
||||
...swrResponse,
|
||||
refreshConnectorsByCredentialId: () => mutate(url),
|
||||
};
|
||||
};
|
||||
|
||||
export const checkCredentialsFetched = (
|
||||
appCredentialData: any,
|
||||
appCredentialError: FetchError | undefined,
|
||||
serviceAccountKeyData: any,
|
||||
serviceAccountKeyError: FetchError | undefined
|
||||
) => {
|
||||
const appCredentialSuccessfullyFetched =
|
||||
appCredentialData ||
|
||||
(appCredentialError && appCredentialError.status === 404);
|
||||
|
||||
const serviceAccountKeySuccessfullyFetched =
|
||||
serviceAccountKeyData ||
|
||||
(serviceAccountKeyError && serviceAccountKeyError.status === 404);
|
||||
|
||||
return {
|
||||
appCredentialSuccessfullyFetched,
|
||||
serviceAccountKeySuccessfullyFetched,
|
||||
};
|
||||
};
|
||||
|
||||
export const filterUploadedCredentials = <
|
||||
T extends { authentication_method?: string },
|
||||
>(
|
||||
credentials: Credential<T>[] | undefined
|
||||
): { credential_id: number | null; uploadedCredentials: Credential<T>[] } => {
|
||||
let credential_id = null;
|
||||
let uploadedCredentials: Credential<T>[] = [];
|
||||
|
||||
if (credentials) {
|
||||
uploadedCredentials = credentials.filter(
|
||||
(credential) =>
|
||||
credential.credential_json.authentication_method !== "oauth_interactive"
|
||||
);
|
||||
|
||||
if (uploadedCredentials.length > 0) {
|
||||
credential_id = uploadedCredentials[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
return { credential_id, uploadedCredentials };
|
||||
};
|
||||
|
||||
export const checkConnectorsExist = (
|
||||
connectors: ConnectorSnapshot[] | undefined
|
||||
): boolean => {
|
||||
return !!connectors && connectors.length > 0;
|
||||
};
|
||||
|
||||
export const refreshAllGoogleData = (
|
||||
source: ValidSources.Gmail | ValidSources.GoogleDrive
|
||||
) => {
|
||||
mutate(buildSimilarCredentialInfoURL(source));
|
||||
|
||||
const service =
|
||||
source === ValidSources.Gmail
|
||||
? GOOGLE_SERVICES.GMAIL
|
||||
: GOOGLE_SERVICES.GOOGLE_DRIVE;
|
||||
mutate(`/api/manage/admin/connector/${service}/app-credential`);
|
||||
mutate(`/api/manage/admin/connector/${service}/service-account-key`);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user