Skip to content

Commit d8bb50d

Browse files
authored
Merge pull request #142 from topcoder-platform/develop
Further fixes for /v6/my-reviews for big accounts
2 parents 19d7797 + 14ea642 commit d8bb50d

File tree

3 files changed

+224
-51
lines changed

3 files changed

+224
-51
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
-- CreateTable
2+
CREATE TABLE "review_pending_summary" (
3+
"resourceId" TEXT NOT NULL,
4+
"pendingAppealCount" INTEGER NOT NULL,
5+
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
6+
7+
CONSTRAINT "review_pending_summary_pkey" PRIMARY KEY ("resourceId")
8+
);
9+
10+
-- CreateIndex
11+
CREATE INDEX "review_pending_summary_updated_at_idx" ON "review_pending_summary"("updatedAt");
12+
13+
BEGIN;
14+
15+
-- Prime the summary table
16+
INSERT INTO reviews.review_pending_summary ("resourceId", "pendingAppealCount", "updatedAt")
17+
SELECT
18+
rv."resourceId",
19+
COUNT(*) AS "pendingAppealCount",
20+
now()
21+
FROM reviews.review rv
22+
JOIN reviews."reviewItem" ri
23+
ON ri."reviewId" = rv.id
24+
JOIN reviews."reviewItemComment" ric
25+
ON ric."reviewItemId" = ri.id
26+
JOIN reviews.appeal ap
27+
ON ap."reviewItemCommentId" = ric.id
28+
LEFT JOIN reviews."appealResponse" apr
29+
ON apr."appealId" = ap.id
30+
AND apr."resourceId" = rv."resourceId"
31+
WHERE apr.id IS NULL
32+
GROUP BY rv."resourceId"
33+
ON CONFLICT ("resourceId")
34+
DO UPDATE SET
35+
"pendingAppealCount" = EXCLUDED."pendingAppealCount",
36+
"updatedAt" = now();
37+
38+
-- Helper to recompute a single resource
39+
CREATE OR REPLACE FUNCTION reviews.update_review_pending_summary_for_resource(p_resource_id text)
40+
RETURNS void
41+
LANGUAGE plpgsql
42+
AS $$
43+
DECLARE
44+
pending_count integer;
45+
BEGIN
46+
SELECT COUNT(*)
47+
INTO pending_count
48+
FROM reviews.review rv
49+
JOIN reviews."reviewItem" ri
50+
ON ri."reviewId" = rv.id
51+
JOIN reviews."reviewItemComment" ric
52+
ON ric."reviewItemId" = ri.id
53+
JOIN reviews.appeal ap
54+
ON ap."reviewItemCommentId" = ric.id
55+
LEFT JOIN reviews."appealResponse" apr
56+
ON apr."appealId" = ap.id
57+
AND apr."resourceId" = rv."resourceId"
58+
WHERE rv."resourceId" = p_resource_id
59+
AND apr.id IS NULL;
60+
61+
IF pending_count > 0 THEN
62+
INSERT INTO reviews.review_pending_summary ("resourceId", "pendingAppealCount", "updatedAt")
63+
VALUES (p_resource_id, pending_count, now())
64+
ON CONFLICT ("resourceId")
65+
DO UPDATE SET
66+
"pendingAppealCount" = EXCLUDED."pendingAppealCount",
67+
"updatedAt" = now();
68+
ELSE
69+
DELETE FROM reviews.review_pending_summary
70+
WHERE "resourceId" = p_resource_id;
71+
END IF;
72+
END;
73+
$$;
74+
75+
-- Triggers for the appeals table
76+
CREATE OR REPLACE FUNCTION reviews.handle_appeal_change()
77+
RETURNS trigger
78+
LANGUAGE plpgsql
79+
AS $$
80+
BEGIN
81+
PERFORM reviews.update_review_pending_summary_for_resource(NEW."resourceId");
82+
RETURN NEW;
83+
END;
84+
$$;
85+
86+
DROP TRIGGER IF EXISTS appeal_pending_maintainer
87+
ON reviews.appeal;
88+
89+
CREATE TRIGGER appeal_pending_maintainer
90+
AFTER INSERT OR UPDATE OR DELETE ON reviews.appeal
91+
FOR EACH ROW
92+
EXECUTE FUNCTION reviews.handle_appeal_change();
93+
94+
-- Triggers for appeal responses
95+
CREATE OR REPLACE FUNCTION reviews.handle_appeal_response_change()
96+
RETURNS trigger
97+
LANGUAGE plpgsql
98+
AS $$
99+
DECLARE
100+
target_resource text;
101+
BEGIN
102+
IF TG_OP IN ('INSERT','UPDATE') THEN
103+
target_resource := NEW."resourceId";
104+
ELSE
105+
target_resource := OLD."resourceId";
106+
END IF;
107+
108+
PERFORM reviews.update_review_pending_summary_for_resource(target_resource);
109+
RETURN COALESCE(NEW, OLD);
110+
END;
111+
$$;
112+
113+
DROP TRIGGER IF EXISTS appeal_response_pending_maintainer
114+
ON reviews."appealResponse";
115+
116+
CREATE TRIGGER appeal_response_pending_maintainer
117+
AFTER INSERT OR UPDATE OR DELETE ON reviews."appealResponse"
118+
FOR EACH ROW
119+
EXECUTE FUNCTION reviews.handle_appeal_response_change();
120+
121+
COMMIT;

prisma/schema.prisma

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,15 @@ model appealResponse {
308308
@@index([appealId, resourceId], map: "appeal_response_appeal_resource_idx") // Supports lookups for pending appeal responses by appeal and resource
309309
}
310310

311+
model reviewPendingSummary {
312+
resourceId String @id
313+
pendingAppealCount Int
314+
updatedAt DateTime @default(now())
315+
316+
@@index([updatedAt], map: "review_pending_summary_updated_at_idx")
317+
@@map("review_pending_summary")
318+
}
319+
311320
model challengeResult {
312321
challengeId String
313322
userId String

src/api/my-review/myReview.service.ts

Lines changed: 94 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ interface ChallengeSummaryRow {
3131
status: string;
3232
hasIncompleteReviews: boolean | null;
3333
incompletePhaseName: string | null;
34-
hasPendingAppealResponses: boolean | null;
34+
hasPendingAppealResponses: boolean;
3535
isAppealsResponsePhaseOpen: boolean | null;
3636
appealsResponsePhaseName: string | null;
3737
}
@@ -156,6 +156,7 @@ export class MyReviewService {
156156
whereFragments.push(Prisma.sql`c.status = 'ACTIVE'`);
157157
}
158158

159+
const cteFragments: Prisma.Sql[] = [];
159160
const baseJoins: Prisma.Sql[] = [];
160161
const countJoins: Prisma.Sql[] = [];
161162
const countExtras: Prisma.Sql[] = [];
@@ -168,25 +169,64 @@ export class MyReviewService {
168169
);
169170
}
170171

172+
cteFragments.push(
173+
Prisma.sql`
174+
member_resources AS (
175+
SELECT
176+
r.id,
177+
r."challengeId",
178+
r."roleId"
179+
FROM resources."Resource" r
180+
WHERE r."memberId" = ${normalizedUserId}
181+
)
182+
`,
183+
);
184+
cteFragments.push(
185+
Prisma.sql`
186+
appeals_phase AS (
187+
SELECT DISTINCT ON (p."challengeId")
188+
p."challengeId",
189+
TRUE AS "isAppealsResponsePhaseOpen",
190+
p.name AS "appealsResponsePhaseName"
191+
FROM challenges."ChallengePhase" p
192+
WHERE p."challengeId" IN (
193+
SELECT DISTINCT mr."challengeId"
194+
FROM member_resources mr
195+
)
196+
AND LOWER(p.name) IN ('appeals response', 'iterative appeals response')
197+
AND p."isOpen" IS TRUE
198+
ORDER BY
199+
p."challengeId",
200+
p."scheduledEndDate" DESC NULLS LAST,
201+
p."actualEndDate" DESC NULLS LAST,
202+
p.name ASC
203+
)
204+
`,
205+
);
206+
171207
baseJoins.push(
172208
Prisma.sql`
173-
LEFT JOIN resources."Resource" r
209+
JOIN member_resources r
174210
ON r."challengeId" = c.id
175-
AND r."memberId" = ${normalizedUserId}
211+
`,
212+
);
213+
baseJoins.push(
214+
Prisma.sql`
176215
LEFT JOIN resources."ResourceRole" rr
177216
ON rr.id = r."roleId"
178217
`,
179218
);
180219

181-
rowExtras.push(Prisma.sql`r."challengeId" IS NOT NULL`);
182-
countExtras.push(
220+
countJoins.push(
183221
Prisma.sql`
184-
EXISTS (
185-
SELECT 1
186-
FROM resources."Resource" r
187-
WHERE r."challengeId" = c.id
188-
AND r."memberId" = ${normalizedUserId}
189-
)
222+
JOIN member_resources r
223+
ON r."challengeId" = c.id
224+
`,
225+
);
226+
countJoins.push(
227+
Prisma.sql`
228+
LEFT JOIN resources."ResourceRole" rr
229+
ON rr.id = r."roleId"
190230
`,
191231
);
192232
} else {
@@ -237,10 +277,8 @@ export class MyReviewService {
237277
0
238278
)::bigint AS "completedReviews"
239279
FROM reviews.review rv
240-
INNER JOIN resources."Resource" rr
241-
ON rr.id = rv."resourceId"
242-
WHERE rr."challengeId" = c.id
243-
) rp ON TRUE
280+
WHERE rv."resourceId" = r.id
281+
) rp ON r.id IS NOT NULL
244282
`,
245283
Prisma.sql`
246284
LEFT JOIN LATERAL (
@@ -284,41 +322,40 @@ export class MyReviewService {
284322
) deliverable_reviews ON r.id IS NOT NULL
285323
`,
286324
Prisma.sql`
287-
LEFT JOIN LATERAL (
288-
SELECT
289-
EXISTS (
290-
SELECT 1
291-
FROM reviews.review rv_pending
292-
JOIN reviews."reviewItem" ri
293-
ON ri."reviewId" = rv_pending.id
294-
JOIN reviews."reviewItemComment" ric
295-
ON ric."reviewItemId" = ri.id
296-
JOIN reviews.appeal ap
297-
ON ap."reviewItemCommentId" = ric.id
298-
LEFT JOIN reviews."appealResponse" apr
299-
ON apr."appealId" = ap.id
300-
AND apr."resourceId" = r.id
301-
WHERE rv_pending."resourceId" = r.id
302-
AND apr.id IS NULL
303-
) AS "hasPendingAppealResponses"
304-
) pending_appeals ON r.id IS NOT NULL
305-
`,
306-
Prisma.sql`
307-
LEFT JOIN LATERAL (
308-
SELECT
309-
TRUE AS "isAppealsResponsePhaseOpen",
310-
p.name AS "appealsResponsePhaseName"
311-
FROM challenges."ChallengePhase" p
312-
WHERE p."challengeId" = c.id
313-
AND LOWER(p.name) IN ('appeals response', 'iterative appeals response')
314-
AND p."isOpen" IS TRUE
315-
ORDER BY
316-
p."scheduledEndDate" DESC NULLS LAST,
317-
p."actualEndDate" DESC NULLS LAST,
318-
p.name ASC
319-
LIMIT 1
320-
) appeals_response_phase ON TRUE
325+
LEFT JOIN reviews.review_pending_summary pending_appeals
326+
ON pending_appeals."resourceId" = r.id
321327
`,
328+
];
329+
330+
if (!adminUser) {
331+
metricJoins.push(
332+
Prisma.sql`
333+
LEFT JOIN appeals_phase appeals_response_phase
334+
ON appeals_response_phase."challengeId" = c.id
335+
`,
336+
);
337+
} else {
338+
metricJoins.push(
339+
Prisma.sql`
340+
LEFT JOIN LATERAL (
341+
SELECT
342+
TRUE AS "isAppealsResponsePhaseOpen",
343+
p.name AS "appealsResponsePhaseName"
344+
FROM challenges."ChallengePhase" p
345+
WHERE p."challengeId" = c.id
346+
AND LOWER(p.name) IN ('appeals response', 'iterative appeals response')
347+
AND p."isOpen" IS TRUE
348+
ORDER BY
349+
p."scheduledEndDate" DESC NULLS LAST,
350+
p."actualEndDate" DESC NULLS LAST,
351+
p.name ASC
352+
LIMIT 1
353+
) appeals_response_phase ON TRUE
354+
`,
355+
);
356+
}
357+
358+
metricJoins.push(
322359
Prisma.sql`
323360
LEFT JOIN LATERAL (
324361
SELECT
@@ -329,7 +366,7 @@ export class MyReviewService {
329366
LIMIT 1
330367
) cr ON TRUE
331368
`,
332-
];
369+
);
333370

334371
const joinClause = joinSqlFragments(
335372
[...baseJoins, ...metricJoins],
@@ -442,7 +479,12 @@ export class MyReviewService {
442479
: fallbackOrderFragments;
443480
const orderClause = joinSqlFragments(orderFragments, Prisma.sql`, `);
444481

482+
const withClause = cteFragments.length
483+
? Prisma.sql`WITH ${joinSqlFragments(cteFragments, Prisma.sql`, `)} `
484+
: Prisma.sql``;
485+
445486
const countQuery = Prisma.sql`
487+
${withClause}
446488
SELECT COUNT(*) AS "total"
447489
FROM challenges."Challenge" c
448490
${countJoinClause}
@@ -475,6 +517,7 @@ export class MyReviewService {
475517
}
476518

477519
const rowQuery = Prisma.sql`
520+
${withClause}
478521
SELECT
479522
c.id AS "challengeId",
480523
c.name AS "challengeName",
@@ -491,7 +534,7 @@ export class MyReviewService {
491534
cw.winners AS "winners",
492535
deliverable_reviews."hasIncompleteReviews" AS "hasIncompleteReviews",
493536
deliverable_reviews."incompletePhaseName" AS "incompletePhaseName",
494-
pending_appeals."hasPendingAppealResponses" AS "hasPendingAppealResponses",
537+
COALESCE(pending_appeals."pendingAppealCount" > 0, FALSE) AS "hasPendingAppealResponses",
495538
appeals_response_phase."isAppealsResponsePhaseOpen" AS "isAppealsResponsePhaseOpen",
496539
appeals_response_phase."appealsResponsePhaseName" AS "appealsResponsePhaseName",
497540
c.status AS "status"

0 commit comments

Comments
 (0)