pierre-review 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/routes/timeline.js +19 -0
- package/dist/db/queries.js +49 -17
- package/package.json +1 -1
- package/public/assets/index-DmAenjsb.js +1371 -0
- package/public/assets/index-Lropvevu.css +10 -0
- package/public/index.html +2 -2
- package/public/assets/index-BZsTx_6Z.css +0 -10
- package/public/assets/index-WMbXhXtx.js +0 -1371
|
@@ -17,6 +17,12 @@ const EVENT_TYPES = [
|
|
|
17
17
|
'commit_pushed',
|
|
18
18
|
];
|
|
19
19
|
const PR_STATUSES = ['draft', 'open', 'merged', 'closed'];
|
|
20
|
+
const REVIEW_FILTER_STATES = [
|
|
21
|
+
'approved',
|
|
22
|
+
'changes_requested',
|
|
23
|
+
'commented',
|
|
24
|
+
'dismissed',
|
|
25
|
+
];
|
|
20
26
|
function parseIntList(raw) {
|
|
21
27
|
if (!raw)
|
|
22
28
|
return null;
|
|
@@ -48,6 +54,18 @@ function parseStatuses(raw) {
|
|
|
48
54
|
.map((s) => s.trim())
|
|
49
55
|
.filter((s) => allowed.has(s));
|
|
50
56
|
}
|
|
57
|
+
// Absent (undefined) → null = no review-verdict filter (show all). Present, even
|
|
58
|
+
// empty ("") → an explicit (possibly empty) set, so deselecting every verdict hides
|
|
59
|
+
// all review markers rather than falling back to "all". Mirrors parseStatuses.
|
|
60
|
+
function parseReviewStates(raw) {
|
|
61
|
+
if (raw === undefined)
|
|
62
|
+
return null;
|
|
63
|
+
const allowed = new Set(REVIEW_FILTER_STATES);
|
|
64
|
+
return raw
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((s) => s.trim())
|
|
67
|
+
.filter((s) => allowed.has(s));
|
|
68
|
+
}
|
|
51
69
|
function parseDate(raw, fallback) {
|
|
52
70
|
if (!raw)
|
|
53
71
|
return fallback;
|
|
@@ -66,6 +84,7 @@ export async function timelineRoutes(app) {
|
|
|
66
84
|
userIds: parseIntList(q.userIds),
|
|
67
85
|
types: parseTypes(q.types),
|
|
68
86
|
statuses: parseStatuses(q.statuses),
|
|
87
|
+
reviewStates: parseReviewStates(q.reviewStates),
|
|
69
88
|
excludeBots: q.excludeBots === 'true',
|
|
70
89
|
excludeStale: q.excludeStale === 'true',
|
|
71
90
|
};
|
package/dist/db/queries.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { and, asc, count, desc, eq, exists, gt, gte, inArray, isNotNull, isNull, lte, notInArray, or, sql, } from 'drizzle-orm';
|
|
2
|
+
import { and, asc, count, desc, eq, exists, gt, gte, inArray, isNotNull, isNull, lte, ne, notInArray, or, sql, } from 'drizzle-orm';
|
|
3
3
|
// Local copy of the shared `REASON_PRIORITY` value constant. `@pierre-review/shared`
|
|
4
4
|
// is a types-only workspace package that is NOT shipped in the published tarball,
|
|
5
5
|
// so the backend must only `import type` from it. Keep in sync with packages/shared.
|
|
@@ -222,7 +222,7 @@ function mapTimelinePr(p, counts, tr) {
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
export async function getTimeline(filters) {
|
|
225
|
-
const { accountId, from, to, repoIds, userIds, types, statuses, excludeBots, excludeStale, } = filters;
|
|
225
|
+
const { accountId, from, to, repoIds, userIds, types, statuses, reviewStates, excludeBots, excludeStale, } = filters;
|
|
226
226
|
// ---- PRs that overlap the window ----
|
|
227
227
|
const prConds = [
|
|
228
228
|
eq(pullRequests.accountId, accountId),
|
|
@@ -280,6 +280,19 @@ export async function getTimeline(filters) {
|
|
|
280
280
|
.from(pullRequests)
|
|
281
281
|
.where(and(eq(pullRequests.id, events.prId), prStatusWhere(statuses)))));
|
|
282
282
|
}
|
|
283
|
+
// Review-verdict filter: keep every NON-review event; for review_submitted events,
|
|
284
|
+
// keep only those whose referenced review's state is selected. An empty selection
|
|
285
|
+
// drops all review markers (the review row exists but no verdict matches). null =
|
|
286
|
+
// no filter. Pure-reviewer rows vanish when their verdict is deselected because the
|
|
287
|
+
// event is removed here (not just hidden client-side), so no empty row lingers.
|
|
288
|
+
if (reviewStates) {
|
|
289
|
+
evConds.push(reviewStates.length === 0
|
|
290
|
+
? ne(events.type, 'review_submitted')
|
|
291
|
+
: or(ne(events.type, 'review_submitted'), exists(db
|
|
292
|
+
.select({ x: sql `1` })
|
|
293
|
+
.from(reviews)
|
|
294
|
+
.where(and(eq(reviews.id, events.refId), eq(events.refTable, 'reviews'), inArray(reviews.state, reviewStates))))));
|
|
295
|
+
}
|
|
283
296
|
// Likewise drop a stale open PR's own events (only ever lifecycle markers, since
|
|
284
297
|
// by definition it has no activity events in-window) so its contributor row can
|
|
285
298
|
// disappear instead of lingering empty. Keep events with no PR (defensive).
|
|
@@ -310,21 +323,40 @@ export async function getTimeline(filters) {
|
|
|
310
323
|
for (const r of rows)
|
|
311
324
|
reviewStateById.set(r.id, r.state);
|
|
312
325
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
type
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
// Batch-load the derived state of each review_comment event's thread, so the
|
|
327
|
+
// timeline's "Threads" filter can narrow markers to a specific thread state
|
|
328
|
+
// (e.g. only resolved) instead of every comment on a PR that has a matching
|
|
329
|
+
// thread. A single keyed lookup; the thread's id is the event's refId.
|
|
330
|
+
const threadRefIds = evRows
|
|
331
|
+
.filter((e) => e.type === 'review_comment' && e.refTable === 'review_threads' && e.refId != null)
|
|
332
|
+
.map((e) => e.refId);
|
|
333
|
+
const threadStateById = new Map();
|
|
334
|
+
if (threadRefIds.length > 0) {
|
|
335
|
+
const rows = await db
|
|
336
|
+
.select({ id: reviewThreads.id, state: reviewThreads.derivedState })
|
|
337
|
+
.from(reviewThreads)
|
|
338
|
+
.where(inArray(reviewThreads.id, threadRefIds))
|
|
339
|
+
.execute();
|
|
340
|
+
for (const r of rows)
|
|
341
|
+
threadStateById.set(r.id, r.state);
|
|
342
|
+
}
|
|
343
|
+
const timelineEvents = evRows.map((e) => {
|
|
344
|
+
const threadId = e.type === 'review_comment' && e.refTable === 'review_threads' ? e.refId : null;
|
|
345
|
+
return {
|
|
346
|
+
id: e.id,
|
|
347
|
+
repoId: e.repoId,
|
|
348
|
+
actorId: e.actorId,
|
|
349
|
+
prId: e.prId,
|
|
350
|
+
type: e.type,
|
|
351
|
+
occurredAt: e.occurredAt.toISOString(),
|
|
352
|
+
threadId,
|
|
353
|
+
derivedState: threadId != null ? (threadStateById.get(threadId) ?? null) : null,
|
|
354
|
+
refId: e.refId,
|
|
355
|
+
reviewState: e.type === 'review_submitted' && e.refTable === 'reviews' && e.refId != null
|
|
356
|
+
? (reviewStateById.get(e.refId) ?? null)
|
|
357
|
+
: null,
|
|
358
|
+
};
|
|
359
|
+
});
|
|
328
360
|
return { prs, events: timelineEvents };
|
|
329
361
|
}
|
|
330
362
|
export async function getOpenPrs(filters) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pierre-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Dashboard for tracking your team's GitHub PR activity across repos — local (SQLite + gh) or self-hosted multi-tenant cloud (Postgres + GitHub App).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Alex Wakeman",
|