mirror of
https://github.com/StanGirard/quivr.git
synced 2024-12-24 20:03:41 +03:00
fix(Analytics): no tags tracking for upload & crawl (#1024)
* 🚚 create useCrawlApi to use in useCrawler hook * 🚑 fix tracking in Crawl * 🧑💻 add hot reloading within docker containers * 🚑 fix tracking for upload * 🚚 create useUploadApi for fileUpload request * 📈 add june tag for Language change * 🩹 revert dependencies
This commit is contained in:
parent
0ca25e2af5
commit
2b74ebc1f0
@ -32,7 +32,7 @@ services:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: backend-core
|
||||
command: uvicorn main:app --host 0.0.0.0 --port 5050
|
||||
command: uvicorn main:app --reload --host 0.0.0.0 --port 5050
|
||||
restart: always
|
||||
volumes:
|
||||
- ./backend/:/code/
|
||||
@ -49,7 +49,7 @@ services:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: backend-chat
|
||||
command: uvicorn chat_service:app --host 0.0.0.0 --port 5050
|
||||
command: uvicorn --reload chat_service:app --host 0.0.0.0 --port 5050
|
||||
restart: always
|
||||
volumes:
|
||||
- ./backend/:/code/
|
||||
@ -66,7 +66,7 @@ services:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: backend-crawl
|
||||
command: uvicorn crawl_service:app --host 0.0.0.0 --port 5050
|
||||
command: uvicorn --reload crawl_service:app --host 0.0.0.0 --port 5050
|
||||
restart: always
|
||||
volumes:
|
||||
- ./backend/:/code/
|
||||
@ -83,7 +83,7 @@ services:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: backend-upload
|
||||
command: uvicorn upload_service:app --host 0.0.0.0 --port 5050
|
||||
command: uvicorn --reload upload_service:app --host 0.0.0.0 --port 5050
|
||||
restart: always
|
||||
volumes:
|
||||
- ./backend/:/code/
|
||||
|
@ -1,41 +1,41 @@
|
||||
/* eslint-disable */
|
||||
import { UUID } from "crypto";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useCrawlApi } from "@/lib/api/crawl/useCrawlApi";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useAxios, useToast } from "@/lib/hooks";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { UUID } from "crypto";
|
||||
import { isValidUrl } from "../helpers/isValidUrl";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useCrawler = () => {
|
||||
const [isCrawling, setCrawling] = useState(false);
|
||||
const urlInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const { session } = useSupabase();
|
||||
const { publish } = useToast();
|
||||
const { axiosInstance } = useAxios();
|
||||
const { track } = useEventTracking();
|
||||
|
||||
const { crawlWebsiteUrl } = useCrawlApi();
|
||||
if (session === null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {t, i18n} = useTranslation(["translation","upload"]);
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
const crawlWebsite = useCallback(
|
||||
async (brainId: UUID | undefined) => {
|
||||
// Validate URL
|
||||
const url = urlInputRef.current ? urlInputRef.current.value : null;
|
||||
|
||||
if (!url || !isValidUrl(url)) {
|
||||
if (url === null || !isValidUrl(url)) {
|
||||
void track("URL_INVALID");
|
||||
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("invalidUrl",{ns:"upload"})
|
||||
text: t("invalidUrl", { ns: "upload" }),
|
||||
});
|
||||
|
||||
return;
|
||||
@ -51,15 +51,16 @@ export const useCrawler = () => {
|
||||
};
|
||||
|
||||
setCrawling(true);
|
||||
|
||||
void track("URL_CRAWLED");
|
||||
|
||||
try {
|
||||
console.log("Crawling website...", brainId);
|
||||
if (brainId !== undefined) {
|
||||
const response = await axiosInstance.post(
|
||||
`/crawl?brain_id=${brainId}`,
|
||||
config
|
||||
);
|
||||
const response = await crawlWebsiteUrl({
|
||||
brainId,
|
||||
config,
|
||||
});
|
||||
|
||||
publish({
|
||||
variant: response.data.type,
|
||||
@ -69,19 +70,21 @@ export const useCrawler = () => {
|
||||
} catch (error: unknown) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("crawlFailed",{ message: JSON.stringify(error),ns:"upload"})
|
||||
text: t("crawlFailed", {
|
||||
message: JSON.stringify(error),
|
||||
ns: "upload",
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setCrawling(false);
|
||||
}
|
||||
},
|
||||
[session.access_token, publish]
|
||||
[session.access_token]
|
||||
);
|
||||
|
||||
return {
|
||||
isCrawling,
|
||||
urlInputRef,
|
||||
|
||||
crawlWebsite,
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,4 @@
|
||||
/* eslint-disable */
|
||||
"use client";
|
||||
import { Suspense } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
@ -14,44 +12,35 @@ export const Crawler = (): JSX.Element => {
|
||||
const { urlInputRef, isCrawling, crawlWebsite } = useCrawler();
|
||||
const { currentBrain } = useBrainContext();
|
||||
|
||||
const {t, i18n} = useTranslation(["translation","upload"]);
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
function Crawler() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex justify-center gap-5 px-6">
|
||||
<div className="max-w-xl w-full">
|
||||
<div className="flex-col justify-center gap-5">
|
||||
<Card className="h-32 flex gap-5 justify-center items-center px-5">
|
||||
<div className="text-center max-w-sm w-full flex flex-col gap-5 items-center">
|
||||
<Field
|
||||
name="crawlurl"
|
||||
ref={urlInputRef}
|
||||
type="text"
|
||||
placeholder={t("webSite",{ ns: "upload"})}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center gap-5">
|
||||
<Button
|
||||
isLoading={isCrawling}
|
||||
onClick={() => void crawlWebsite(currentBrain?.id)}
|
||||
>
|
||||
{t("crawlButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex justify-center gap-5 px-6">
|
||||
<div className="max-w-xl w-full">
|
||||
<div className="flex-col justify-center gap-5">
|
||||
<Card className="h-32 flex gap-5 justify-center items-center px-5">
|
||||
<div className="text-center max-w-sm w-full flex flex-col gap-5 items-center">
|
||||
<Field
|
||||
name="crawlurl"
|
||||
ref={urlInputRef}
|
||||
type="text"
|
||||
placeholder={t("webSite", { ns: "upload" })}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center gap-5">
|
||||
<Button
|
||||
isLoading={isCrawling}
|
||||
onClick={() => void crawlWebsite(currentBrain?.id)}
|
||||
>
|
||||
{t("crawlButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback = {"Loading..."}>
|
||||
<Crawler />
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,32 +1,32 @@
|
||||
/* eslint-disable */
|
||||
import { useCallback, useState } from "react";
|
||||
import { FileRejection, useDropzone } from "react-dropzone";
|
||||
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useAxios, useToast } from "@/lib/hooks";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
/* eslint-disable max-lines */
|
||||
import axios from "axios";
|
||||
import { UUID } from "crypto";
|
||||
import { useCallback, useState } from "react";
|
||||
import { FileRejection, useDropzone } from "react-dropzone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useUploadApi } from "@/lib/api/upload/useUploadApi";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useFileUploader = () => {
|
||||
const { track } = useEventTracking();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const { publish } = useToast();
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const { session } = useSupabase();
|
||||
|
||||
const { uploadFile } = useUploadApi();
|
||||
const { currentBrain } = useBrainContext();
|
||||
const { axiosInstance } = useAxios();
|
||||
|
||||
if (session === null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {t, i18n} = useTranslation(["upload"]);
|
||||
const { t } = useTranslation(["upload"]);
|
||||
|
||||
const upload = useCallback(
|
||||
async (file: File, brainId: UUID) => {
|
||||
@ -34,18 +34,13 @@ export const useFileUploader = () => {
|
||||
formData.append("uploadFile", file);
|
||||
try {
|
||||
void track("FILE_UPLOADED");
|
||||
const response = await axiosInstance.post(
|
||||
`/upload?brain_id=${brainId}`,
|
||||
formData
|
||||
);
|
||||
track("FILE_UPLOADED");
|
||||
const response = await uploadFile({ brainId, formData });
|
||||
publish({
|
||||
variant: response.data.type,
|
||||
text:
|
||||
(response.data.type === "success"
|
||||
? t("success",{ ns: "upload" })
|
||||
: t("error",{ message: response.data.message, ns: "upload" })
|
||||
)
|
||||
response.data.type === "success"
|
||||
? t("success", { ns: "upload" })
|
||||
: t("error", { message: response.data.message, ns: "upload" }),
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e) && e.response?.status === 403) {
|
||||
@ -62,17 +57,17 @@ export const useFileUploader = () => {
|
||||
} else {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("error",{ message: e, ns: "upload" })
|
||||
text: t("error", { message: e, ns: "upload" }),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[session.access_token, publish]
|
||||
[session.access_token]
|
||||
);
|
||||
|
||||
const onDrop = (acceptedFiles: File[], fileRejections: FileRejection[]) => {
|
||||
if (fileRejections.length > 0) {
|
||||
publish({ variant: "danger", text: t("maxSizeError",{ ns: "upload" }) });
|
||||
publish({ variant: "danger", text: t("maxSizeError", { ns: "upload" }) });
|
||||
|
||||
return;
|
||||
}
|
||||
@ -85,11 +80,12 @@ export const useFileUploader = () => {
|
||||
if (isAlreadyInFiles) {
|
||||
publish({
|
||||
variant: "warning",
|
||||
text: t("alreadyAdded",{ fileName: file.name, ns: "upload" }),
|
||||
text: t("alreadyAdded", { fileName: file.name, ns: "upload" }),
|
||||
});
|
||||
acceptedFiles.splice(i, 1);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
setFiles((files) => [...files, ...acceptedFiles]);
|
||||
};
|
||||
|
||||
@ -104,7 +100,7 @@ export const useFileUploader = () => {
|
||||
}
|
||||
setIsPending(true);
|
||||
if (currentBrain?.id !== undefined) {
|
||||
await Promise.all(files.map((file) => upload(file, currentBrain?.id)));
|
||||
await Promise.all(files.map((file) => upload(file, currentBrain.id)));
|
||||
setFiles([]);
|
||||
} else {
|
||||
publish({
|
||||
@ -128,7 +124,6 @@ export const useFileUploader = () => {
|
||||
isDragActive,
|
||||
open,
|
||||
uploadAllFiles,
|
||||
|
||||
files,
|
||||
setFiles,
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
"use client";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -21,8 +20,7 @@ export const FileUploader = (): JSX.Element => {
|
||||
setFiles,
|
||||
} = useFileUploader();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {t, i18n} = useTranslation(["translation","upload"]);
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
return (
|
||||
<section
|
||||
@ -35,13 +33,13 @@ export const FileUploader = (): JSX.Element => {
|
||||
<input {...getInputProps()} />
|
||||
<div className="text-center p-6 max-w-sm w-full flex flex-col gap-5 items-center">
|
||||
{isDragActive ? (
|
||||
<p className="text-blue-600">{t("drop",{"ns":"upload"})}</p>
|
||||
<p className="text-blue-600">{t("drop", { ns: "upload" })}</p>
|
||||
) : (
|
||||
<button
|
||||
onClick={open}
|
||||
className="opacity-50 h-full cursor-pointer hover:opacity-100 hover:underline transition-opacity"
|
||||
>
|
||||
{t("dragAndDrop",{"ns":"upload"})}
|
||||
{t("dragAndDrop", { ns: "upload" })}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@ -73,6 +71,4 @@ export const FileUploader = (): JSX.Element => {
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
|
||||
};
|
||||
|
@ -57,8 +57,8 @@ const UploadPage = (): JSX.Element => {
|
||||
);
|
||||
}
|
||||
|
||||
const Upload = () => {
|
||||
return (
|
||||
return (
|
||||
<Suspense fallback="Loading...">
|
||||
<main className="pt-10">
|
||||
<PageHeading
|
||||
title={t("title", { ns: "upload" })}
|
||||
@ -75,12 +75,6 @@ const UploadPage = (): JSX.Element => {
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Suspense fallback="Loading...">
|
||||
<Upload />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
43
frontend/lib/api/crawl/__tests__/useCrawlApi.test.ts
Normal file
43
frontend/lib/api/crawl/__tests__/useCrawlApi.test.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { CrawlInputProps } from "../crawl";
|
||||
import { useCrawlApi } from "../useCrawlApi";
|
||||
|
||||
const axiosPostMock = vi.fn(() => ({}));
|
||||
|
||||
vi.mock("@/lib/hooks", () => ({
|
||||
useAxios: () => ({
|
||||
axiosInstance: {
|
||||
post: axiosPostMock,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("useCrawlApi", () => {
|
||||
// TODO: Create a test user within preview and prod databases to interact with
|
||||
it("should call updateUserIdentity with the correct parameters", async () => {
|
||||
const {
|
||||
result: {
|
||||
current: { crawlWebsiteUrl },
|
||||
},
|
||||
} = renderHook(() => useCrawlApi());
|
||||
const crawlInputProps: CrawlInputProps = {
|
||||
brainId: "e7001ccd-6d90-4eab-8c50-2f23d39441e4",
|
||||
config: {
|
||||
url: "https://en.wikipedia.org/wiki/Mali",
|
||||
js: false,
|
||||
depth: 1,
|
||||
max_pages: 100,
|
||||
max_time: 60,
|
||||
},
|
||||
};
|
||||
await crawlWebsiteUrl(crawlInputProps);
|
||||
|
||||
expect(axiosPostMock).toHaveBeenCalledTimes(1);
|
||||
expect(axiosPostMock).toHaveBeenCalledWith(
|
||||
`/crawl?brain_id=${crawlInputProps.brainId}`,
|
||||
crawlInputProps.config
|
||||
);
|
||||
});
|
||||
});
|
25
frontend/lib/api/crawl/crawl.ts
Normal file
25
frontend/lib/api/crawl/crawl.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { AxiosInstance } from "axios";
|
||||
import { UUID } from "crypto";
|
||||
|
||||
import { ToastData } from "@/lib/components/ui/Toast/domain/types";
|
||||
|
||||
export type CrawlInputProps = {
|
||||
brainId: UUID;
|
||||
config: {
|
||||
url: string;
|
||||
js: boolean;
|
||||
depth: number;
|
||||
max_pages: number;
|
||||
max_time: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type CrawlResponse = {
|
||||
data: { type: ToastData["variant"]; message: ToastData["text"] };
|
||||
};
|
||||
|
||||
export const crawlWebsiteUrl = async (
|
||||
props: CrawlInputProps,
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<CrawlResponse> =>
|
||||
axiosInstance.post(`/crawl?brain_id=${props.brainId}`, props.config);
|
13
frontend/lib/api/crawl/useCrawlApi.ts
Normal file
13
frontend/lib/api/crawl/useCrawlApi.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { useAxios } from "@/lib/hooks";
|
||||
|
||||
import { CrawlInputProps, crawlWebsiteUrl } from "./crawl";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useCrawlApi = () => {
|
||||
const { axiosInstance } = useAxios();
|
||||
|
||||
return {
|
||||
crawlWebsiteUrl: async (props: CrawlInputProps) =>
|
||||
crawlWebsiteUrl(props, axiosInstance),
|
||||
};
|
||||
};
|
19
frontend/lib/api/upload/upload.ts
Normal file
19
frontend/lib/api/upload/upload.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { AxiosInstance } from "axios";
|
||||
import { UUID } from "crypto";
|
||||
|
||||
import { ToastData } from "@/lib/components/ui/Toast/domain/types";
|
||||
|
||||
export type UploadResponse = {
|
||||
data: { type: ToastData["variant"]; message: ToastData["text"] };
|
||||
};
|
||||
|
||||
export type UploadInputProps = {
|
||||
brainId: UUID;
|
||||
formData: FormData;
|
||||
};
|
||||
|
||||
export const uploadFile = async (
|
||||
props: UploadInputProps,
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<UploadResponse> =>
|
||||
axiosInstance.post(`/upload?brain_id=${props.brainId}`, props.formData);
|
13
frontend/lib/api/upload/useUploadApi.ts
Normal file
13
frontend/lib/api/upload/useUploadApi.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { useAxios } from "@/lib/hooks";
|
||||
|
||||
import { uploadFile, UploadInputProps } from "./upload";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useUploadApi = () => {
|
||||
const { axiosInstance } = useAxios();
|
||||
|
||||
return {
|
||||
uploadFile: async (props: UploadInputProps) =>
|
||||
uploadFile(props, axiosInstance),
|
||||
};
|
||||
};
|
@ -1,6 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
export type Language = {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -14,6 +16,8 @@ export const useLanguageHook = (): {
|
||||
const { i18n } = useTranslation();
|
||||
const [allLanguages, setAllLanguages] = useState<Language[]>([]);
|
||||
const [currentLanguage, setCurrentLanguage] = useState<Language | null>(null);
|
||||
const { track } = useEventTracking();
|
||||
|
||||
useEffect(() => {
|
||||
const languages = [
|
||||
{
|
||||
@ -63,6 +67,7 @@ export const useLanguageHook = (): {
|
||||
}, [i18n]);
|
||||
|
||||
const change = (newLanguage: Language) => {
|
||||
void track("CHANGE_LANGUAGE");
|
||||
setCurrentLanguage(newLanguage);
|
||||
localStorage.setItem("selectedLanguage", newLanguage.id);
|
||||
void i18n.changeLanguage(newLanguage.id);
|
||||
|
Loading…
Reference in New Issue
Block a user