Skip to content

Commit 420c308

Browse files
authored
Revert PR #116 — restore /analytics page (v1.15.3 broke prod) (#117)
Reverts PR #116. The v1.15.3 `getBlockedQueries` SQL throws against real Postgres, and the client-side `Promise.all` rejection blanks the entire /analytics page with 'Failed to load analytics: Failed to fetch blocked queries'. Restoring v1.15.2 immediately to get the page working again. The three v1.15.3 changes (blocked filter, Blocked Queries panel, Pacific timestamps) will re-ship as v1.15.4 after explicit local verification against the real Postgres schema.
2 parents f8f7ab7 + 4777275 commit 420c308

12 files changed

Lines changed: 59 additions & 798 deletions

CHANGELOG.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
# @copilotkit/pathfinder
22

3-
## 1.15.3
4-
5-
### Patch Changes
6-
7-
- **Analytics view — "Empty Result Queries" excludes blocked rows**: rows the v1.15.2 abuse blocklist short-circuited (`blocked = true`) are filtered out of the "Empty Result Queries (Last 7 days)" table. Those queries never reached retrieval, so `result_count = 0` is a known by-design outcome — not a content gap. Previously they showed up alongside genuine empty-result rows because the SQL filtered on `result_count = 0` alone. The blocked-column predicate is `blocked = false`, which is safe for historical rows that predate the column (the schema declares `BOOLEAN NOT NULL DEFAULT FALSE`).
8-
- **New "Blocked Queries (Last 7 days)" panel on /analytics**: surfaces the rows the blocklist caught, grouped by `block_reason` with hit count, last-seen timestamp, and up to 5 sample queries per reason. Operators can now see the blocklist actively catching abuse instead of inferring it from the absence of off-topic rows in the empty-results table. The panel deliberately ignores tool/source/request-source filters — those carry no meaning for rows the blocklist short-circuited before retrieval — but does honor the same `days` window as the rest of the dashboard.
9-
- **Analytics timestamps render in Pacific time (DST-aware)**: every absolute timestamp on the /analytics operator-facing page now renders in `America/Los_Angeles` via `Intl.DateTimeFormat`, emitting e.g. `2026-06-17 09:40:32 PDT` (or `PST` in winter). The dashboard is operated from Pacific; UTC timestamps forced an in-head conversion on every read. Storage and URL contracts are unchanged — Postgres still stores `created_at TIMESTAMPTZ` in UTC, the server still computes windows in UTC, and the URL `from`/`to` contract is still a UTC calendar date. Only display flips.
10-
113
## 1.15.2
124

135
### Patch Changes

docs/analytics.html

Lines changed: 57 additions & 256 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@copilotkit/pathfinder",
3-
"version": "1.15.3",
3+
"version": "1.15.2",
44
"description": "The knowledge server for AI agents — index docs, code, Notion, Slack, and Discord into searchable, agent-accessible knowledge via MCP. Supports OpenAI, Ollama, and local transformers.js embeddings.",
55
"type": "module",
66
"repository": {

src/__tests__/analytics-auth-length-check.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ vi.mock("../db/analytics.js", () => ({
1919
getAnalyticsSummary: vi.fn(),
2020
getTopQueries: vi.fn(),
2121
getEmptyQueries: vi.fn(),
22-
getBlockedQueries: vi.fn(),
2322
getToolCounts: vi.fn(),
2423
}));
2524

@@ -80,7 +79,6 @@ describe("analyticsAuth length-check early return (R4-18)", () => {
8079
getAnalyticsSummary: async () => ({ total: 0 }) as never,
8180
getTopQueries: async () => [],
8281
getEmptyQueries: async () => [],
83-
getBlockedQueries: async () => [],
8482
getToolCounts: async () => [],
8583
});
8684
server = http.createServer(app);

src/__tests__/analytics-error-log-ip.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ vi.mock("../db/analytics.js", () => ({
1313
getAnalyticsSummary: vi.fn(),
1414
getTopQueries: vi.fn(),
1515
getEmptyQueries: vi.fn(),
16-
getBlockedQueries: vi.fn(),
1716
getToolCounts: vi.fn(),
1817
}));
1918

@@ -48,9 +47,6 @@ function buildApp() {
4847
getEmptyQueries: async () => {
4948
throw new Error("db boom");
5049
},
51-
getBlockedQueries: async () => {
52-
throw new Error("db boom");
53-
},
5450
getToolCounts: async () => {
5551
throw new Error("db boom");
5652
},

src/__tests__/analytics-sendfile-correlation.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ vi.mock("../db/analytics.js", () => ({
1717
getAnalyticsSummary: vi.fn(),
1818
getTopQueries: vi.fn(),
1919
getEmptyQueries: vi.fn(),
20-
getBlockedQueries: vi.fn(),
2120
getToolCounts: vi.fn(),
2221
}));
2322

src/__tests__/analytics-server.test.ts

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ import path from "node:path";
66
const mockGetAnalyticsSummary = vi.fn();
77
const mockGetTopQueries = vi.fn();
88
const mockGetEmptyQueries = vi.fn();
9-
const mockGetBlockedQueries = vi.fn();
109
const mockGetToolCounts = vi.fn();
1110

1211
vi.mock("../db/analytics.js", () => ({
1312
getAnalyticsSummary: (...args: unknown[]) => mockGetAnalyticsSummary(...args),
1413
getTopQueries: (...args: unknown[]) => mockGetTopQueries(...args),
1514
getEmptyQueries: (...args: unknown[]) => mockGetEmptyQueries(...args),
16-
getBlockedQueries: (...args: unknown[]) => mockGetBlockedQueries(...args),
1715
getToolCounts: (...args: unknown[]) => mockGetToolCounts(...args),
1816
}));
1917

@@ -84,8 +82,6 @@ function buildTestApp() {
8482
mockGetTopQueries(...args),
8583
getEmptyQueries: (...args: Parameters<typeof mockGetEmptyQueries>) =>
8684
mockGetEmptyQueries(...args),
87-
getBlockedQueries: (...args: Parameters<typeof mockGetBlockedQueries>) =>
88-
mockGetBlockedQueries(...args),
8985
getToolCounts: (...args: Parameters<typeof mockGetToolCounts>) =>
9086
mockGetToolCounts(...args),
9187
analyticsHtmlPath,
@@ -370,86 +366,6 @@ describe("Analytics server routes (HTTP-level)", () => {
370366
});
371367
});
372368

373-
// ---- /api/analytics/blocked-queries (v1.15.3) ----------------------------
374-
375-
describe("GET /api/analytics/blocked-queries (v1.15.3)", () => {
376-
it("returns blocked-row groups with a valid token", async () => {
377-
mockGetAnalyticsConfigFn.mockReturnValue({
378-
enabled: true,
379-
log_queries: true,
380-
retention_days: 90,
381-
token: "secret",
382-
});
383-
mockGetBlockedQueries.mockResolvedValue([
384-
{
385-
block_reason: "box-office",
386-
hits: 142,
387-
last_seen: "2026-06-17T16:40:32.000Z",
388-
sample_queries: ["box office weekend"],
389-
},
390-
]);
391-
392-
await startApp();
393-
const res = await request(
394-
server,
395-
"GET",
396-
"/api/analytics/blocked-queries?days=7",
397-
{ Authorization: "Bearer secret" },
398-
);
399-
400-
expect(res.status).toBe(200);
401-
const body = JSON.parse(res.body);
402-
expect(Array.isArray(body)).toBe(true);
403-
expect(body[0].block_reason).toBe("box-office");
404-
expect(body[0].hits).toBe(142);
405-
// The route forwards the parsed days value to the DB call.
406-
expect(mockGetBlockedQueries).toHaveBeenCalledWith(7);
407-
});
408-
409-
it("returns 401 without a token", async () => {
410-
mockGetAnalyticsConfigFn.mockReturnValue({
411-
enabled: true,
412-
log_queries: true,
413-
retention_days: 90,
414-
token: "secret",
415-
});
416-
417-
await startApp();
418-
const res = await request(
419-
server,
420-
"GET",
421-
"/api/analytics/blocked-queries",
422-
);
423-
expect(res.status).toBe(401);
424-
});
425-
426-
it("returns 500 with a clean error envelope when the DB call rejects", async () => {
427-
mockGetAnalyticsConfigFn.mockReturnValue({
428-
enabled: true,
429-
log_queries: true,
430-
retention_days: 90,
431-
token: "secret",
432-
});
433-
mockGetBlockedQueries.mockRejectedValue(new Error("boom"));
434-
const consoleErrSpy = vi
435-
.spyOn(console, "error")
436-
.mockImplementation(() => {});
437-
438-
await startApp();
439-
const res = await request(
440-
server,
441-
"GET",
442-
"/api/analytics/blocked-queries",
443-
{ Authorization: "Bearer secret" },
444-
);
445-
446-
expect(res.status).toBe(500);
447-
const body = JSON.parse(res.body);
448-
expect(body.error).toBe("Failed to fetch blocked queries");
449-
consoleErrSpy.mockRestore();
450-
});
451-
});
452-
453369
// ---- Auto-generated token via HTTP ---------------------------------------
454370

455371
describe("auto-generated token (HTTP-level)", () => {

0 commit comments

Comments
 (0)