mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-21 14:12:42 +02:00
Paginate Query History table (#3592)
* Add pagination for query history table * Fix method name * Fix mypy
This commit is contained in:
@@ -10,7 +10,6 @@ import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { getXDaysAgo } from "./dateUtils";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
export const THIRTY_DAYS = "30d";
|
||||
|
||||
@@ -84,8 +83,16 @@ export const DateRangeSelector = memo(function DateRangeSelector({
|
||||
defaultMonth={value?.from}
|
||||
selected={value}
|
||||
onSelect={(range) => {
|
||||
if (range?.from && range?.to) {
|
||||
onValueChange({ from: range.from, to: range.to });
|
||||
if (range?.from) {
|
||||
if (range.to) {
|
||||
// Normal range selection when initialized with a range
|
||||
onValueChange({ from: range.from, to: range.to });
|
||||
} else {
|
||||
// Single date selection when initilized without a range
|
||||
const to = new Date(range.from);
|
||||
const from = new Date(to.setDate(to.getDate() - 1));
|
||||
onValueChange({ from, to });
|
||||
}
|
||||
}
|
||||
}}
|
||||
numberOfMonths={2}
|
||||
|
@@ -65,27 +65,6 @@ export const useOnyxBotAnalytics = (timeRange: DateRangePickerValue) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useQueryHistory = ({
|
||||
selectedFeedbackType,
|
||||
timeRange,
|
||||
}: {
|
||||
selectedFeedbackType: Feedback | null;
|
||||
timeRange: DateRange;
|
||||
}) => {
|
||||
const url = buildApiPath("/api/admin/chat-session-history", {
|
||||
feedback_type: selectedFeedbackType,
|
||||
start: convertDateToStartOfDay(timeRange?.from)?.toISOString(),
|
||||
end: convertDateToEndOfDay(timeRange?.to)?.toISOString(),
|
||||
});
|
||||
|
||||
const swrResponse = useSWR<ChatSessionMinimal[]>(url, errorHandlingFetcher);
|
||||
|
||||
return {
|
||||
...swrResponse,
|
||||
refreshQueryHistory: () => mutate(url),
|
||||
};
|
||||
};
|
||||
|
||||
export function getDatesList(startDate: Date): string[] {
|
||||
const datesList: string[] = [];
|
||||
const endDate = new Date(); // current date
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { useQueryHistory, useTimeRange } from "../lib";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Table,
|
||||
@@ -20,8 +19,8 @@ import {
|
||||
import { ThreeDotsLoader } from "@/components/Loading";
|
||||
import { ChatSessionMinimal } from "../usage/types";
|
||||
import { timestampToReadableDate } from "@/lib/dateUtils";
|
||||
import { FiFrown, FiMinus, FiSmile } from "react-icons/fi";
|
||||
import { useCallback, useState } from "react";
|
||||
import { FiFrown, FiMinus, FiSmile, FiMeh } from "react-icons/fi";
|
||||
import { useCallback, useState, useMemo } from "react";
|
||||
import { Feedback } from "@/lib/types";
|
||||
import { DateRange, DateRangeSelector } from "../DateRangeSelector";
|
||||
import { PageSelector } from "@/components/PageSelector";
|
||||
@@ -29,8 +28,11 @@ import Link from "next/link";
|
||||
import { FeedbackBadge } from "./FeedbackBadge";
|
||||
import { DownloadAsCSV } from "./DownloadAsCSV";
|
||||
import CardSection from "@/components/admin/CardSection";
|
||||
import usePaginatedFetch from "@/hooks/usePaginatedFetch";
|
||||
import { ErrorCallout } from "@/components/ErrorCallout";
|
||||
|
||||
const NUM_IN_PAGE = 20;
|
||||
const ITEMS_PER_PAGE = 20;
|
||||
const PAGES_PER_BATCH = 2;
|
||||
|
||||
function QueryHistoryTableRow({
|
||||
chatSessionMinimal,
|
||||
@@ -108,6 +110,12 @@ function SelectFeedbackType({
|
||||
<span>Dislike</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="mixed">
|
||||
<div className="flex items-center gap-2">
|
||||
<FiMeh className="h-4 w-4" />
|
||||
<span>Mixed</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -116,31 +124,55 @@ function SelectFeedbackType({
|
||||
}
|
||||
|
||||
export function QueryHistoryTable() {
|
||||
const [selectedFeedbackType, setSelectedFeedbackType] = useState<
|
||||
Feedback | "all"
|
||||
>("all");
|
||||
const [timeRange, setTimeRange] = useTimeRange();
|
||||
const [dateRange, setDateRange] = useState<DateRange>(undefined);
|
||||
const [filters, setFilters] = useState<{
|
||||
feedback_type?: Feedback | "all";
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
}>({});
|
||||
|
||||
const { data: chatSessionData } = useQueryHistory({
|
||||
selectedFeedbackType:
|
||||
selectedFeedbackType === "all" ? null : selectedFeedbackType,
|
||||
timeRange,
|
||||
const {
|
||||
currentPageData: chatSessionData,
|
||||
isLoading,
|
||||
error,
|
||||
currentPage,
|
||||
totalPages,
|
||||
goToPage,
|
||||
refresh,
|
||||
} = usePaginatedFetch<ChatSessionMinimal>({
|
||||
itemsPerPage: ITEMS_PER_PAGE,
|
||||
pagesPerBatch: PAGES_PER_BATCH,
|
||||
endpoint: "/api/admin/chat-session-history",
|
||||
filter: filters,
|
||||
});
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const onTimeRangeChange = useCallback((value: DateRange) => {
|
||||
setDateRange(value);
|
||||
|
||||
const onTimeRangeChange = useCallback(
|
||||
(value: DateRange) => {
|
||||
if (value) {
|
||||
setTimeRange((prevTimeRange) => ({
|
||||
...prevTimeRange,
|
||||
from: new Date(value.from),
|
||||
to: new Date(value.to),
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setTimeRange]
|
||||
);
|
||||
if (value?.from && value?.to) {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
start_time: value.from.toISOString(),
|
||||
end_time: value.to.toISOString(),
|
||||
}));
|
||||
} else {
|
||||
setFilters((prev) => {
|
||||
const newFilters = { ...prev };
|
||||
delete newFilters.start_time;
|
||||
delete newFilters.end_time;
|
||||
return newFilters;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorCallout
|
||||
errorTitle="Error fetching query history"
|
||||
errorMsg={error?.message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CardSection className="mt-8">
|
||||
@@ -148,12 +180,22 @@ export function QueryHistoryTable() {
|
||||
<div className="flex">
|
||||
<div className="gap-y-3 flex flex-col">
|
||||
<SelectFeedbackType
|
||||
value={selectedFeedbackType || "all"}
|
||||
onValueChange={setSelectedFeedbackType}
|
||||
value={filters.feedback_type || "all"}
|
||||
onValueChange={(value) => {
|
||||
setFilters((prev) => {
|
||||
const newFilters = { ...prev };
|
||||
if (value === "all") {
|
||||
delete newFilters.feedback_type;
|
||||
} else {
|
||||
newFilters.feedback_type = value;
|
||||
}
|
||||
return newFilters;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<DateRangeSelector
|
||||
value={timeRange}
|
||||
value={dateRange}
|
||||
onValueChange={onTimeRangeChange}
|
||||
/>
|
||||
</div>
|
||||
@@ -172,33 +214,33 @@ export function QueryHistoryTable() {
|
||||
<TableHead>Date</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{chatSessionData &&
|
||||
chatSessionData
|
||||
.slice(NUM_IN_PAGE * (page - 1), NUM_IN_PAGE * page)
|
||||
.map((chatSessionMinimal) => (
|
||||
<QueryHistoryTableRow
|
||||
key={chatSessionMinimal.id}
|
||||
chatSessionMinimal={chatSessionMinimal}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
{isLoading ? (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center">
|
||||
<ThreeDotsLoader />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : (
|
||||
<TableBody>
|
||||
{chatSessionData?.map((chatSessionMinimal) => (
|
||||
<QueryHistoryTableRow
|
||||
key={chatSessionMinimal.id}
|
||||
chatSessionMinimal={chatSessionMinimal}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
|
||||
{chatSessionData && (
|
||||
<div className="mt-3 flex">
|
||||
<div className="mx-auto">
|
||||
<PageSelector
|
||||
totalPages={Math.ceil(chatSessionData.length / NUM_IN_PAGE)}
|
||||
currentPage={page}
|
||||
onPageChange={(newPage) => {
|
||||
setPage(newPage);
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
totalPages={totalPages}
|
||||
currentPage={currentPage}
|
||||
onPageChange={goToPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -106,9 +106,7 @@ export default function QueryPage(props: { params: Promise<{ id: string }> }) {
|
||||
|
||||
<div className="flex flex-col">
|
||||
{chatSessionSnapshot.messages.map((message) => {
|
||||
return (
|
||||
<MessageDisplay key={message.time_created} message={message} />
|
||||
);
|
||||
return <MessageDisplay key={message.id} message={message} />;
|
||||
})}
|
||||
</div>
|
||||
</CardSection>
|
||||
|
@@ -25,6 +25,7 @@ export interface AbridgedSearchDoc {
|
||||
}
|
||||
|
||||
export interface MessageSnapshot {
|
||||
id: number;
|
||||
message: string;
|
||||
message_type: "user" | "assistant";
|
||||
documents: AbridgedSearchDoc[];
|
||||
|
@@ -5,12 +5,14 @@ import {
|
||||
AcceptedUserSnapshot,
|
||||
InvitedUserSnapshot,
|
||||
} from "@/lib/types";
|
||||
import { ChatSessionMinimal } from "@/app/ee/admin/performance/usage/types";
|
||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||
|
||||
type PaginatedType =
|
||||
| IndexAttemptSnapshot
|
||||
| AcceptedUserSnapshot
|
||||
| InvitedUserSnapshot;
|
||||
| InvitedUserSnapshot
|
||||
| ChatSessionMinimal;
|
||||
|
||||
interface PaginatedApiResponse<T extends PaginatedType> {
|
||||
items: T[];
|
||||
@@ -22,7 +24,7 @@ interface PaginationConfig {
|
||||
pagesPerBatch: number;
|
||||
endpoint: string;
|
||||
query?: string;
|
||||
filter?: Record<string, string | boolean | number | string[]>;
|
||||
filter?: Record<string, string | boolean | number | string[] | Date>;
|
||||
refreshIntervalInMs?: number;
|
||||
}
|
||||
|
||||
|
@@ -98,7 +98,7 @@ export type ValidStatuses =
|
||||
| "in_progress"
|
||||
| "not_started";
|
||||
export type TaskStatus = "PENDING" | "STARTED" | "SUCCESS" | "FAILURE";
|
||||
export type Feedback = "like" | "dislike";
|
||||
export type Feedback = "like" | "dislike" | "mixed";
|
||||
export type AccessType = "public" | "private" | "sync";
|
||||
export type SessionType = "Chat" | "Search" | "Slack";
|
||||
|
||||
|
Reference in New Issue
Block a user