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: 25 additions & 5 deletions src/api/servers.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,12 +960,32 @@ export const countDocumentsByTimeRange = query(
database: z.string(),
collection: z.string(),
days: z.number(),
query: z.string().optional(),
}),
async ({ server, database, collection, days }): Promise<{ count: number | null; error: string | null }> => {
async ({
server,
database,
collection,
days,
query: queryStr,
}): Promise<{ count: number | null; error: string | null }> => {
const mongo = await getMongo();
const client = mongo.getClient(server);
const coll = client.db(database).collection(collection);

// Parse the base query if provided
let baseQuery: Document = {};
if (queryStr) {
try {
const parsed = parseJSON(queryStr);
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
baseQuery = JsonEncoder.encode(parsed) as Document;
}
} catch {
// If parsing fails, ignore the query
}
}

// Check if _id has a creation timestamp (is ObjectId with embedded date)
// Use createdAt field ONLY if _id doesn't have a date AND there's an index on createdAt
let useCreatedAt = false;
Expand All @@ -988,13 +1008,13 @@ export const countDocumentsByTimeRange = query(
const dateThreshold = new Date();
dateThreshold.setDate(dateThreshold.getDate() - days);

// Build filter based on _id type
let filter;
// Build filter based on _id type, merged with base query
let filter: Document;
if (useCreatedAt) {
filter = { createdAt: { $gte: dateThreshold } };
filter = { ...baseQuery, createdAt: { $gte: dateThreshold } };
} else {
const objectIdThreshold = ObjectId.createFromTime(Math.floor(dateThreshold.getTime() / 1000));
filter = { _id: { $gte: objectIdThreshold } };
filter = { ...baseQuery, _id: { $gte: objectIdThreshold } };
}

try {
Expand Down
33 changes: 30 additions & 3 deletions src/lib/components/SearchBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import IconEdit from "$lib/icons/IconEdit.svelte";
import type { SearchParams } from "$lib/types";
import { formatNumber } from "$lib/utils/filters";
import { parseJSON, serializeForEditing } from "$lib/utils/jsonParser";
import { tick } from "svelte";

// Threshold for URL length before switching to remote function call - see https://stackoverflow.com/questions/32763165/node-js-http-get-url-length-limitation
Expand Down Expand Up @@ -197,6 +198,7 @@
let newDocsButtonElement = $state<HTMLButtonElement>();
let newDocsDropdownPosition = $state({ left: "0px", top: "0px", minWidth: "200px" });
let isStatsLoading = $state(false);
let lastStatsQuery = $state<string | undefined>(undefined);
let stats = $state<
Array<{ label: string; days: number; count: number | null; error: string | null; loading?: boolean }>
>(TIME_RANGES.map((r) => ({ ...r, count: null, error: null, loading: false })));
Expand All @@ -223,6 +225,10 @@
// Reset all stats to loading state
stats = TIME_RANGES.map((r) => ({ ...r, count: null, error: null, loading: true }));

// Get current query to merge with time filter (only for query mode, not aggregation)
const currentQuery = params.mode === "query" ? params.query : undefined;
lastStatsQuery = currentQuery;

// Load each time range separately (sequentially, shortest first)
for (let i = 0; i < TIME_RANGES.length; i++) {
const range = TIME_RANGES[i];
Expand All @@ -232,6 +238,7 @@
database,
collection,
days: range.days,
query: currentQuery,
});

stats[i] = { ...range, count: result.count, error: result.error, loading: false };
Expand All @@ -245,7 +252,14 @@

function toggleNewDocsDropdown() {
showNewDocsDropdown = !showNewDocsDropdown;
if (showNewDocsDropdown && stats.every((s) => s.count === null && !s.error && !s.loading)) {
if (!showNewDocsDropdown) return;

// Reload stats if never loaded, or if the query has changed
const currentQuery = params.mode === "query" ? params.query : undefined;
const needsReload =
stats.every((s) => s.count === null && !s.error && !s.loading) || lastStatsQuery !== currentQuery;

if (needsReload) {
loadStats();
}
}
Expand All @@ -258,9 +272,22 @@

// Create ObjectId hex string from timestamp (first 4 bytes are timestamp)
const objectIdHex = timestamp.toString(16).padStart(8, "0") + "0000000000000000";
const idFilter = { $gte: { $type: "ObjectId", $value: objectIdHex } };

// Try to merge with existing query, overwriting _id
let newQuery = `{_id: {$gte: ObjectId("${objectIdHex}")}}`;

try {
const currentQuery = parseJSON(params.query);
if (currentQuery && typeof currentQuery === "object" && !Array.isArray(currentQuery)) {
const merged = { ...(currentQuery as Record<string, unknown>), _id: idFilter };
newQuery = serializeForEditing(merged);
}
} catch {
// If parsing fails, just use the simple _id filter
}

// Set the query with the ObjectId filter
params.query = `{_id: {$gte: ObjectId("${objectIdHex}")}}`;
params.query = newQuery;
params.mode = "query";
showNewDocsDropdown = false;

Expand Down