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
30 changes: 21 additions & 9 deletions src/components/biz/IndexConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCurrentIndex } from "@/hooks/useCurrentIndex";
import { useMeiliClient } from "@/hooks/useMeiliClient";
import { cn } from "@/lib/cn";
import { hiddenRequestLoader, showRequestLoader } from "@/lib/loader";
import { showTaskSubmitNotification } from "@/lib/toast";
import { showTaskSubmitNotification, toast } from "@/lib/toast";
import { Button } from "@nextui-org/react";
import { useMutation, useQuery } from "@tanstack/react-query";
import type { Settings } from "meilisearch";
Expand Down Expand Up @@ -33,11 +33,9 @@ export const IndexConfigEditor: FC<{
(data: Settings = {}) => {
setIsSettingsEditing(false);
setIndexSettingInitialData(data);
if (!isSettingsEditing) {
setIndexSettingEditorData(JSON.stringify(data, null, 2));
}
setIndexSettingEditorData(JSON.stringify(data, null, 2));
},
[isSettingsEditing],
[],
);

const querySettings = useQuery({
Expand Down Expand Up @@ -75,19 +73,33 @@ export const IndexConfigEditor: FC<{
return await currentIndex.index.updateSettings(variables);
},

onSuccess: (t) => {
showTaskSubmitNotification(t);
onSuccess: (task) => {
setIsSettingsEditing(false);
showTaskSubmitNotification(task);
setTimeout(() => querySettings.refetch(), 450);
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(errorMessage);
},
onSettled: () => {
hiddenRequestLoader();
},
});

const onSaveSettings = useCallback(() => {
setIsSettingsEditing(false);
indexSettingEditorData &&
if (!indexSettingEditorData) {
return;
}

try {
settingsMutation.mutate(JSON.parse(indexSettingEditorData));
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(errorMessage);
}
}, [indexSettingEditorData, settingsMutation]);

const isLoading = useMemo(() => {
Expand Down
13 changes: 13 additions & 0 deletions src/components/biz/IndexList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import { useCurrentInstance } from "@/hooks/useCurrentInstance";
import { useIndexLastTaskAt } from "@/hooks/useIndexLastTaskAt";
import { useIndexTaskCounts } from "@/hooks/useIndexTaskCounts";
import { useInstanceStats } from "@/hooks/useInstanceStats";
import { cn } from "@/lib/cn";
Expand All @@ -16,6 +17,7 @@ import { useTranslation } from "react-i18next";
import { useImmer } from "use-immer";
import { EmptyArea } from "../common/empty";
import { LoaderPage } from "../common/Loader";
import { TimeAgo } from "../common/Timeago";
import { CreateIndexButton } from "./CreateIndex";

interface Props {
Expand Down Expand Up @@ -61,6 +63,7 @@ export const IndexList: FC<Props> = ({ className = "", client }) => {
[indexList],
);
const [taskCounts] = useIndexTaskCounts(client, indexUids);
const [lastTaskAt] = useIndexLastTaskAt(client, indexUids);

useEffect(() => {
// load index list into fuse collection
Expand Down Expand Up @@ -223,6 +226,15 @@ export const IndexList: FC<Props> = ({ className = "", client }) => {
</Tooltip>
</div>
</div>
<Tooltip content={t("last_task_tooltip")}>
<div className="flex items-center gap-2 text-sm text-neutral-500 pt-1">
<span className="shrink-0">{t("last_task")}</span>
<TimeAgo
className="text-neutral-600"
date={lastTaskAt[item.uid]}
/>
</div>
</Tooltip>
</CardBody>
</Card>
);
Expand Down Expand Up @@ -260,6 +272,7 @@ export const IndexList: FC<Props> = ({ className = "", client }) => {
statsQuery.refetch,
isLoading,
taskCounts,
lastTaskAt,
],
);
};
79 changes: 79 additions & 0 deletions src/hooks/useIndexLastTaskAt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useQuery, type UseQueryResult } from "@tanstack/react-query";
import type { MeiliSearch, Task } from "meilisearch";
import { useMemo } from "react";
import { useCurrentInstance } from "./useCurrentInstance";

const PAGE_LIMIT = 1000;
const MAX_PAGES = 100;

function pickTaskActivityTimestamp(
task: Task,
): string | Date | undefined {
return task.finishedAt ?? task.startedAt ?? task.enqueuedAt ?? undefined;
}

export const useIndexLastTaskAt = (
client: MeiliSearch,
indexUids: string[],
) => {
const currentInstance = useCurrentInstance();
const host = currentInstance?.host;
const enabled = indexUids.length > 0;

const query = useQuery({
queryKey: ["indexLastTaskAt", host, [...indexUids].sort().join(",")],
queryFn: async () => {
const lastByIndex = new Map<string, string | Date | undefined>();
const target = new Set(indexUids);
let from: number | undefined;
let pages = 0;

while (pages < MAX_PAGES) {
const page = await client.getTasks({
indexUids,
limit: PAGE_LIMIT,
...(from !== undefined ? { from } : {}),
});

for (const task of page.results) {
const uid = task.indexUid;
if (!uid || lastByIndex.has(uid)) {
continue;
}
lastByIndex.set(uid, pickTaskActivityTimestamp(task));
}

const allFound = [...target].every((id) => lastByIndex.has(id));
if (allFound) {
break;
}

if (page.next == null) {
break;
}
from = page.next;
pages++;
}

return lastByIndex;
},
enabled,
refetchInterval: 30_000,
});

const lastTaskAt = useMemo(() => {
if (!query.data) {
return {} as Record<string, string | Date | undefined>;
}
const result: Record<string, string | Date | undefined> = {};
for (const [indexUid, ts] of query.data.entries()) {
result[indexUid] = ts;
}
return result;
}, [query.data]);

return [lastTaskAt, query] as [
Record<string, string | Date | undefined>,
UseQueryResult<Map<string, string | Date | undefined>>,
];
};
4 changes: 3 additions & 1 deletion src/locales/en/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@
},
"tasks_count": "{{count}} pending/processing task(s)",
"tasks_count_tooltip": "{{count}} incomplete task(s)",
"count_tooltip": "View documents"
"count_tooltip": "View documents",
"last_task": "Last task",
"last_task_tooltip": "Time of the latest task for this index (finished, in progress, or enqueued)"
}
4 changes: 3 additions & 1 deletion src/locales/zh/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@
},
"tasks_count": "{{count}} 个待处理/处理中的任务",
"tasks_count_tooltip": "{{count}} 个未完成任务",
"count_tooltip": "查看文档"
"count_tooltip": "查看文档",
"last_task": "最近任务",
"last_task_tooltip": "该索引最新任务的时间(已完成、进行中或排队中)"
}