Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions frontend/app/api/mutations/useS3ConfigureMutation.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";

export interface S3ConfigurePayload {
access_key?: string;
secret_key?: string;
endpoint_url?: string;
region?: string;
bucket_names?: string[];
connection_id?: string;
access_key?: string;
secret_key?: string;
session_token?: string;
endpoint_url?: string;
region?: string;
bucket_names?: string[];
connection_id?: string;
}

async function configureS3(payload: S3ConfigurePayload) {
const res = await fetch("/api/connectors/aws_s3/configure", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to configure S3");
return data as { connection_id: string; status: string };
const res = await fetch("/api/connectors/aws_s3/configure", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to configure S3");
return data as { connection_id: string; status: string };
}

export function useS3ConfigureMutation() {
const queryClient = useQueryClient();
const queryClient = useQueryClient();

return useMutation({
mutationFn: configureS3,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["connectors"] });
queryClient.invalidateQueries({ queryKey: ["s3-defaults"] });
},
});
return useMutation({
mutationFn: configureS3,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["connectors"] });
queryClient.invalidateQueries({ queryKey: ["s3-defaults"] });
},
});
}
43 changes: 43 additions & 0 deletions frontend/app/api/mutations/useShareDocumentMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

import { useMutation, useQueryClient } from "@tanstack/react-query";

interface ShareDocumentRequest {
filename: string;
user_ids: string[];
}

interface ShareDocumentResponse {
success: boolean;
allowed_users: string[];
}

async function shareDocument(
data: ShareDocumentRequest,
): Promise<ShareDocumentResponse> {
const response = await fetch("/api/documents/acl/share", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Failed to share document");
}

return response.json();
}

export const useShareDocumentMutation = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: shareDocument,
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: ["document-acl", variables.filename],
});
},
});
};
43 changes: 43 additions & 0 deletions frontend/app/api/mutations/useUnshareDocumentMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

import { useMutation, useQueryClient } from "@tanstack/react-query";

interface UnshareDocumentRequest {
filename: string;
user_ids: string[];
}

interface UnshareDocumentResponse {
success: boolean;
allowed_users: string[];
}

async function unshareDocument(
data: UnshareDocumentRequest,
): Promise<UnshareDocumentResponse> {
const response = await fetch("/api/documents/acl/unshare", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Failed to unshare document");
}

return response.json();
}

export const useUnshareDocumentMutation = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: unshareDocument,
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: ["document-acl", variables.filename],
});
},
});
};
10 changes: 8 additions & 2 deletions frontend/app/api/queries/useGetConnectorsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
import { isDirectConnectorDevEnabled } from "@/lib/dev-flags";

interface GoogleDriveFile {
id: string;
Expand Down Expand Up @@ -52,6 +53,7 @@ export const useGetConnectorsQuery = (
options?: Omit<UseQueryOptions<Connector[]>, "queryKey" | "queryFn">,
) => {
async function getConnectors(): Promise<Connector[]> {
const directConnectorDevEnabled = isDirectConnectorDevEnabled();
const connectorsResponse = await fetch("/api/connectors");
if (!connectorsResponse.ok) {
throw new Error("Failed to fetch available connectors");
Expand All @@ -63,6 +65,10 @@ export const useGetConnectorsQuery = (
const connectorsWithStatus = await Promise.all(
connectorTypes.map(async (type) => {
const connectorData = connectorsMap[type];
const available =
(type === "ibm_cos" || type === "aws_s3") && directConnectorDevEnabled
? true
: connectorData.available;
const statusResponse = await fetch(`/api/connectors/${type}/status`);

let status: Connector["status"] = "not_connected";
Expand All @@ -88,7 +94,7 @@ export const useGetConnectorsQuery = (
connectionId,
clientId: activeConnection.client_id,
baseUrl: activeConnection.base_url,
available: connectorData.available,
available,
} as Connector;
}
}
Expand All @@ -101,7 +107,7 @@ export const useGetConnectorsQuery = (
status,
type,
connectionId,
available: connectorData.available,
available,
} as Connector;
}),
);
Expand Down
35 changes: 35 additions & 0 deletions frontend/app/api/queries/useGetCurrentUserQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
type UseQueryOptions,
useQuery,
useQueryClient,
} from "@tanstack/react-query";

export interface CurrentUser {
user_id: string;
email: string;
name: string;
}

async function fetchCurrentUser(): Promise<CurrentUser> {
const response = await fetch("/api/auth/me");
if (!response.ok) {
throw new Error("Failed to fetch current user");
}
return response.json();
}

export const useGetCurrentUserQuery = (
options?: Omit<UseQueryOptions<CurrentUser>, "queryKey" | "queryFn">,
) => {
const queryClient = useQueryClient();

return useQuery(
{
queryKey: ["current-user"],
queryFn: fetchCurrentUser,
staleTime: 1000 * 60 * 30, // 30 minutes — user identity doesn't change mid-session
...options,
},
queryClient,
);
};
39 changes: 39 additions & 0 deletions frontend/app/api/queries/useGetDocumentAclQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
type UseQueryOptions,
useQuery,
useQueryClient,
} from "@tanstack/react-query";

export interface DocumentAcl {
owner: string | null;
allowed_users: string[];
allowed_groups: string[];
}

async function fetchDocumentAcl(filename: string): Promise<DocumentAcl> {
const response = await fetch(
`/api/documents/acl?filename=${encodeURIComponent(filename)}`,
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Failed to fetch document ACL");
}
return response.json();
}

export const useGetDocumentAclQuery = (
filename: string | null | undefined,
options?: Omit<UseQueryOptions<DocumentAcl>, "queryKey" | "queryFn">,
) => {
const queryClient = useQueryClient();

return useQuery(
{
queryKey: ["document-acl", filename],
queryFn: () => fetchDocumentAcl(filename!),
enabled: !!filename,
...options,
},
queryClient,
);
};
31 changes: 16 additions & 15 deletions frontend/app/api/queries/useS3DefaultsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { useQuery } from "@tanstack/react-query";

export interface S3Defaults {
access_key_set: boolean;
secret_key_set: boolean;
endpoint: string;
region: string;
bucket_names: string[];
connection_id: string | null;
access_key_set: boolean;
secret_key_set: boolean;
session_token_set: boolean;
endpoint: string;
region: string;
bucket_names: string[];
connection_id: string | null;
}

async function fetchS3Defaults(): Promise<S3Defaults> {
const res = await fetch("/api/connectors/aws_s3/defaults");
if (!res.ok) throw new Error("Failed to fetch S3 defaults");
return res.json();
const res = await fetch("/api/connectors/aws_s3/defaults");
if (!res.ok) throw new Error("Failed to fetch S3 defaults");
return res.json();
}

export function useS3DefaultsQuery(options?: { enabled?: boolean }) {
return useQuery<S3Defaults>({
queryKey: ["s3-defaults"],
queryFn: fetchS3Defaults,
enabled: options?.enabled ?? true,
staleTime: 0,
});
return useQuery<S3Defaults>({
queryKey: ["s3-defaults"],
queryFn: fetchS3Defaults,
enabled: options?.enabled ?? true,
staleTime: 0,
});
}
4 changes: 4 additions & 0 deletions frontend/app/knowledge/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import SharePointIcon from "../../components/icons/share-point-logo";
import { useDeleteDocument } from "../api/mutations/useDeleteDocument";
import { useRefreshOpenragDocs } from "../api/mutations/useRefreshOpenragDocs";
import { useSyncAllConnectors } from "../api/mutations/useSyncConnector";
import { useGetCurrentUserQuery } from "../api/queries/useGetCurrentUserQuery";

// Function to get the appropriate icon for a connector type
function getSourceIcon(connectorType?: string) {
Expand Down Expand Up @@ -103,6 +104,7 @@ function SearchPage() {
const deleteDocumentMutation = useDeleteDocument();
const syncAllConnectorsMutation = useSyncAllConnectors();
const refreshOpenragDocsMutation = useRefreshOpenragDocs();
const { data: currentUser } = useGetCurrentUserQuery();

useEffect(() => {
refreshTasks();
Expand Down Expand Up @@ -558,6 +560,8 @@ function SearchPage() {
<KnowledgeActionsDropdown
filename={data?.filename || ""}
connectorType={data?.connector_type}
owner={data?.owner}
currentUserId={currentUser?.user_id}
/>
);
},
Expand Down
6 changes: 5 additions & 1 deletion frontend/app/settings/_components/connector-cards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import IBMCOSIcon from "@/components/icons/ibm-cos-icon";
import OneDriveIcon from "@/components/icons/one-drive-logo";
import SharePointIcon from "@/components/icons/share-point-logo";
import { useAuth } from "@/contexts/auth-context";
import { isDirectConnectorDevEnabled } from "@/lib/dev-flags";
import ConnectorCard, { type Connector } from "./connector-card";
import ConnectorsSkeleton from "./connectors-skeleton";
import IBMCOSSettingsDialog from "./ibm-cos-settings-dialog";
import S3SettingsDialog from "./s3-settings-dialog";

export default function ConnectorCards() {
const { isAuthenticated, isNoAuthMode, isIbmAuthMode } = useAuth();
const directConnectorDevEnabled = isDirectConnectorDevEnabled();
const router = useRouter();
const [ibmCOSDialogOpen, setIBMCOSDialogOpen] = useState(false);
const [s3DialogOpen, setS3DialogOpen] = useState(false);
Expand Down Expand Up @@ -52,7 +54,9 @@ export default function ConnectorCards() {

const connectors = queryConnectors
.filter((c) => {
if (c.type === "ibm_cos" || c.type === "aws_s3") return isIbmAuthMode;
if (c.type === "ibm_cos" || c.type === "aws_s3") {
return isIbmAuthMode || directConnectorDevEnabled;
}
return true;
})
.map((c) => ({
Expand Down
Loading
Loading