Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7b4b692
Fix up challenge type selection and add that as a column
jmgasper Feb 26, 2026
83609e3
PM-4158 completed profiles report
vas3a Mar 3, 2026
67286d6
New challenge submitter report (PM-4151)
jmgasper Mar 3, 2026
46935fe
Merge branch 'develop' of github.com:topcoder-platform/reports-api-v6…
jmgasper Mar 3, 2026
867bf8a
Fix for PM-4046
jmgasper Mar 4, 2026
4df9eec
Merge branch 'develop' of github.com:topcoder-platform/reports-api-v6…
vas3a Mar 4, 2026
5dc144e
Merge pull request #56 from topcoder-platform/PM-4158_completed-profi…
kkartunov Mar 4, 2026
820da9e
Update Trivy action to use latest version
kkartunov Mar 4, 2026
3c03155
PM-4158 - Update profiles report
vas3a Mar 4, 2026
5a704df
depploy to dev
vas3a Mar 4, 2026
0072f8a
PM-4158 #time 30m Fix profiles query
vas3a Mar 4, 2026
857eb1e
Fix engagement availability
vas3a Mar 4, 2026
690f73e
PM-4158 #time 15m refactor query for completed profiles
vas3a Mar 4, 2026
45ecf70
Merge pull request #57 from topcoder-platform/PM-4158_completed-profi…
vas3a Mar 4, 2026
5dc70a5
Updates for new reports requirements
jmgasper Mar 4, 2026
2c66f3f
Merge branch 'develop' of github.com:topcoder-platform/reports-api-v6…
jmgasper Mar 4, 2026
7107090
Fix email retrieval
jmgasper Mar 4, 2026
c659c76
Fix for groups report and ID handling
jmgasper Mar 4, 2026
4934817
Fix valid submitters query
jmgasper Mar 4, 2026
c8f15a0
Allow Talent Manager / Project Manager access to users-by-handle
jmgasper Mar 5, 2026
1388a4a
PM-4158 #time 30m fix user role for completed profile report
vas3a Mar 5, 2026
f1821d3
PM-4198 add support for pagination for completed profiles report
vas3a Mar 5, 2026
e139072
Merge branch 'develop' of github.com:topcoder-platform/reports-api-v6…
vas3a Mar 5, 2026
f5a8966
Merge pull request #58 from topcoder-platform/PM-4158_completed-profi…
vas3a Mar 5, 2026
8e74b32
Merge branch 'develop' of github.com:topcoder-platform/reports-api-v6…
vas3a Mar 5, 2026
9a0c947
fix query
vas3a Mar 5, 2026
24f4dc7
deploy to dev
vas3a Mar 5, 2026
505c706
Bug fixes and better RBAC for PM / TM roles
jmgasper Mar 11, 2026
64396b5
Merge branch 'develop' of github.com:topcoder-platform/reports-api-v6…
jmgasper Mar 11, 2026
d120cfa
Fix for winner calculation in MMs
jmgasper Mar 11, 2026
be6f298
PM-4288 #time 3h understanding the codebase and implementing sending …
hentrymartin Mar 11, 2026
19b2e60
deploy to dev
hentrymartin Mar 11, 2026
c65e92c
PM-4288 #time 1h filter by open to work implemented
hentrymartin Mar 11, 2026
b3c58ae
added a transform for the boolean query param
hentrymartin Mar 11, 2026
f320ebc
Update categorization of reports
jmgasper Mar 11, 2026
e5771b2
Move some reports around for new requirements
jmgasper Mar 12, 2026
dd31672
Fix for MM scoring output
jmgasper Mar 12, 2026
7969839
Talent Manager permission updates
jmgasper Mar 12, 2026
251cec2
Merge pull request #59 from topcoder-platform/PM-4198_customer-portal…
vas3a Mar 12, 2026
30f17be
resolved conflicts
hentrymartin Mar 12, 2026
ed140cc
Merge pull request #60 from topcoder-platform/pm-4288
hentrymartin Mar 12, 2026
c0df2f7
Update Trivy action version to 0.35.0
kkartunov Mar 12, 2026
48187fd
PM-4288 #time 30m tweeked the count query to support open to work filter
hentrymartin Mar 12, 2026
7504b98
deploy to dev
hentrymartin Mar 12, 2026
649bc5b
fix: added back the open to work props
hentrymartin Mar 12, 2026
efe0f29
Merge pull request #61 from topcoder-platform/pm-4288_1
hentrymartin Mar 12, 2026
60c1ff0
PM-4203 #time 3h implemented skils filter in the completed profiles API
hentrymartin Mar 12, 2026
e64cb12
fix: query
hentrymartin Mar 12, 2026
28e16a8
fix: query
hentrymartin Mar 12, 2026
9118a9f
PM-4288 #time 20m send availableForGigs as part of completed profiles…
hentrymartin Mar 13, 2026
f3100b3
fix: param names
hentrymartin Mar 13, 2026
2193749
fix: query
hentrymartin Mar 13, 2026
5a91329
Merge pull request #62 from topcoder-platform/pm-4288_2
hentrymartin Mar 13, 2026
01abc54
updated from develop
hentrymartin Mar 14, 2026
a4c6025
Stats updates
jmgasper Mar 15, 2026
eeab26e
Merge branch 'develop' of github.com:topcoder-platform/reports-api-v6…
jmgasper Mar 15, 2026
47a02d9
Merge pull request #63 from topcoder-platform/pm-4203
kkartunov Mar 15, 2026
66dc60b
#time 45m modified the skills filter query
hentrymartin Mar 17, 2026
280c2fd
deploy to dev
hentrymartin Mar 17, 2026
6ad9e0c
Merge pull request #64 from topcoder-platform/pm-4203_1
kkartunov Mar 18, 2026
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
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ workflows:
only:
- develop
- pm-1127_1

- pm-4203_1

# Production builds are exectuted only on tagged commits to the
# master branch.
- "build-prod":
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/trivy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v4

- name: Run Trivy scanner in repo mode
uses: aquasecurity/trivy-action@0.33.1
uses: aquasecurity/trivy-action@0.35.0
with:
scan-type: "fs"
ignore-unfixed: true
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
This repository houses the reports API for all Topcoder and Topgear reports on the Topcoder platform. The reports are pulled directly from live data, not a data warehouse, so they should be up-to-date when they are generated and the response is returned.

Reports return JSON data by default. Endpoints that support CSV can also return
CSV when the request sets `Accept: text/csv` (including the Challenges and
Topcoder report groups).
CSV when the request sets `Accept: text/csv` (including the Challenges,
Topcoder, Member, and Admin report groups).

## Security

Expand Down
60 changes: 60 additions & 0 deletions sql/reports/challenges/registered-users.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
WITH challenge_context AS (
SELECT c.id
FROM challenges."Challenge" AS c
WHERE c.id = $1::text
),
registered_members AS MATERIALIZED (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
Consider removing the MATERIALIZED keyword unless you have a specific reason to use it. Materializing a CTE can have performance implications, as it forces the query planner to execute and store the results of the CTE immediately, which might not be necessary here.

SELECT
res."memberId",
MAX(res."memberHandle") AS "memberHandle"
FROM challenge_context AS cc
JOIN resources."Resource" AS res
ON res."challengeId" = cc.id
JOIN resources."ResourceRole" AS rr
ON rr.id = res."roleId"
AND rr.name = 'Submitter'
GROUP BY res."memberId"
)
SELECT
COALESCE(
u.user_id::bigint,
CASE
WHEN rm."memberId" ~ '^[0-9]+$' THEN rm."memberId"::bigint
ELSE NULL
END
) AS "userId",
COALESCE(
NULLIF(TRIM(u.handle), ''),
NULLIF(TRIM(mem.handle), ''),
rm."memberHandle"
) AS "handle",
COALESCE(e.address, NULLIF(TRIM(mem.email), '')) AS "email",
COALESCE(
comp_code.name,
comp_id.name,
home_code.name,
home_id.name,
NULLIF(TRIM(mem."competitionCountryCode"), ''),
NULLIF(TRIM(mem."homeCountryCode"), '')
) AS "country"
FROM registered_members AS rm
LEFT JOIN identity."user" AS u
ON rm."memberId" ~ '^[0-9]+$'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
The regex pattern rm."memberId" ~ '^[0-9]+$' is used multiple times. Consider extracting this pattern into a separate CTE or using a common table expression to avoid repetition and improve maintainability.

AND u.user_id = rm."memberId"::numeric
LEFT JOIN identity.email AS e
ON e.user_id = u.user_id
AND e.primary_ind = 1
LEFT JOIN members."member" AS mem
ON rm."memberId" ~ '^[0-9]+$'
AND mem."userId" = rm."memberId"::bigint
LEFT JOIN lookups."Country" AS home_code
ON UPPER(home_code."countryCode") = UPPER(mem."homeCountryCode")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
Using UPPER on both sides of the join condition can lead to performance issues, especially if the countryCode column is indexed. Consider ensuring that the countryCode values are stored in a consistent case in the database to leverage indexing.

LEFT JOIN lookups."Country" AS home_id
ON UPPER(home_id.id) = UPPER(mem."homeCountryCode")
LEFT JOIN lookups."Country" AS comp_code
ON UPPER(comp_code."countryCode") = UPPER(mem."competitionCountryCode")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
Using UPPER on both sides of the join condition can lead to performance issues, especially if the countryCode column is indexed. Consider ensuring that the countryCode values are stored in a consistent case in the database to leverage indexing.

LEFT JOIN lookups."Country" AS comp_id
ON UPPER(comp_id.id) = UPPER(mem."competitionCountryCode")
ORDER BY
"handle" ASC NULLS LAST,
"userId" ASC NULLS LAST;
37 changes: 29 additions & 8 deletions sql/reports/challenges/registrants-history.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ filtered_challenges AS MATERIALIZED (
SELECT
c.id,
c.status,
ct.name AS "challengeType",
lp."actualEndDate" AS "challengeCompletedDate"
FROM challenges."Challenge" c
JOIN challenges."ChallengeType" ct
ON ct.id = c."typeId"
LEFT JOIN LATERAL (
SELECT cp."actualEndDate"
FROM challenges."ChallengePhase" cp
Expand Down Expand Up @@ -40,8 +43,8 @@ filtered_challenges AS MATERIALIZED (
)
-- filter by challenge status
AND ($3::text[] IS NULL OR c.status::text = ANY($3::text[]))
-- exclude task challenge types from this report
AND COALESCE(c."taskIsTask", false) = false
-- include only challenge types supported by this report
AND ct.name IN ('Challenge', 'Marathon Match', 'First2Finish')
-- filter by completion date bounds on the latest challenge phase end date
AND (
($4::timestamptz IS NULL AND $5::timestamptz IS NULL)
Expand All @@ -57,6 +60,7 @@ registrants AS MATERIALIZED (
SELECT
fc.id AS "challengeId",
fc.status AS "challengeStatus",
fc."challengeType",
fc."challengeCompletedDate",
registrant."memberId",
registrant."registrantHandle"
Expand All @@ -71,23 +75,29 @@ registrants AS MATERIALIZED (
AND res."roleId" = sr.id
GROUP BY res."memberId"
) registrant ON true
LIMIT 1000
)
SELECT
r."challengeId",
r."challengeStatus",
r."challengeType",
win."winnerHandle",
COALESCE(sub."isWinner", false) AS "isWinner",
(
COALESCE(win."isWinner", false)
OR COALESCE(sub."isWinner", false)
) AS "isWinner",
CASE
WHEN r."challengeStatus" = 'COMPLETED'
THEN r."challengeCompletedDate"
ELSE null
END AS "challengeCompletedDate",
r."registrantHandle",
sub."registrantFinalScore"
COALESCE(sub."registrantFinalScore", sum."registrantFinalScore")
AS "registrantFinalScore"
FROM registrants r
LEFT JOIN LATERAL (
SELECT MAX(cw.handle) AS "winnerHandle"
SELECT
MAX(cw.handle) AS "winnerHandle",
COUNT(*) > 0 AS "isWinner"
FROM challenges."ChallengeWinner" cw
WHERE cw."challengeId" = r."challengeId"
AND cw."userId"::text = r."memberId"
Expand All @@ -96,8 +106,19 @@ LEFT JOIN LATERAL (
LEFT JOIN LATERAL (
SELECT
BOOL_OR(s.placement = 1) AS "isWinner",
ROUND(MAX(s."finalScore"), 2) AS "registrantFinalScore"
COALESCE(ROUND(MAX(s."finalScore")::numeric, 2), ROUND(MAX(s."initialScore")::numeric, 2)) AS "registrantFinalScore"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
The use of COALESCE to choose between finalScore and initialScore could lead to unexpected results if both scores are present but finalScore is NULL. Consider verifying the logic to ensure it aligns with business requirements.

FROM reviews.submission s
WHERE s."challengeId" = r."challengeId"
AND s."memberId" = r."memberId"
) sub ON true;
) sub ON true
LEFT JOIN LATERAL (
SELECT
ROUND(MAX(rs."aggregateScore")::numeric, 2) AS "registrantFinalScore"
FROM reviews."reviewSummation" rs
WHERE rs."submissionId" IN (
SELECT id from reviews.submission
WHERE "challengeId" = r."challengeId"
AND "memberId" = r."memberId"
)
) sum ON true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
The subquery in the LEFT JOIN LATERAL for reviewSummation could potentially return multiple rows, which might lead to incorrect aggregation if not intended. Ensure that the subquery logic aligns with the expected single-row result.


166 changes: 166 additions & 0 deletions sql/reports/challenges/submitters.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
WITH challenge_context AS (
SELECT
c.id,
(ct.name = 'Marathon Match') AS is_marathon_match
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 readability]
Using a boolean expression (ct.name = 'Marathon Match') directly in the SELECT clause can be unclear. Consider using a CASE statement for better readability and maintainability.

FROM challenges."Challenge" AS c
JOIN challenges."ChallengeType" AS ct
ON ct.id = c."typeId"
WHERE c.id = $1::text
),
submission_metrics AS (
SELECT
s.id AS submission_id,
s."memberId",
COALESCE(s."submittedDate", s."createdAt") AS submission_timestamp,
COALESCE(
final_review."aggregateScore",
s."finalScore"::double precision,
s."initialScore"::double precision
) AS standard_score,
provisional_review.provisional_score,
final_review."aggregateScore" AS final_score_raw
FROM challenge_context AS cc
JOIN reviews."submission" AS s
ON s."challengeId" = cc.id
AND s."memberId" IS NOT NULL
LEFT JOIN LATERAL (
SELECT rs."aggregateScore"
FROM reviews."reviewSummation" AS rs
WHERE rs."submissionId" = s.id
AND COALESCE(rs."isFinal", TRUE) = TRUE
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
The use of COALESCE(rs."isFinal", TRUE) = TRUE assumes that isFinal can be NULL and defaults it to TRUE. Ensure this logic is intentional and aligns with business rules.

AND rs."isProvisional" IS DISTINCT FROM TRUE
ORDER BY COALESCE(rs."reviewedDate", rs."createdAt") DESC NULLS LAST, rs.id DESC
LIMIT 1
) AS final_review ON TRUE
LEFT JOIN LATERAL (
SELECT rs."aggregateScore" AS provisional_score
FROM reviews."reviewSummation" AS rs
WHERE rs."submissionId" = s.id
AND rs."isProvisional" IS TRUE
ORDER BY COALESCE(rs."reviewedDate", rs."createdAt") DESC NULLS LAST, rs.id DESC
LIMIT 1
) AS provisional_review ON TRUE
),
submitter_members AS MATERIALIZED (
SELECT
cc.is_marathon_match,
res."memberId",
MAX(res."memberHandle") AS "memberHandle"
FROM challenge_context AS cc
JOIN resources."Resource" AS res
ON res."challengeId" = cc.id
JOIN resources."ResourceRole" AS rr
ON rr.id = res."roleId"
AND rr.name = 'Submitter'
JOIN submission_metrics AS smx
ON smx."memberId" = res."memberId"
GROUP BY
cc.is_marathon_match,
res."memberId"
),
standard_member_scores AS (
SELECT
sm."memberId",
ROUND(MAX(sm.standard_score)::numeric, 2) AS "submissionScore"
FROM submission_metrics AS sm
GROUP BY sm."memberId"
),
mm_latest_submission_scores AS (
SELECT DISTINCT ON (sm."memberId")
sm."memberId",
sm.provisional_score AS provisional_score_raw,
sm.final_score_raw,
COALESCE(sm.final_score_raw, sm.provisional_score) AS effective_score_raw,
sm.submission_timestamp
FROM submission_metrics AS sm
ORDER BY
sm."memberId",
sm.submission_timestamp DESC NULLS LAST,
sm.submission_id DESC
),
mm_ranked_scores AS (
SELECT
mlss."memberId",
CASE
WHEN mlss.provisional_score_raw IS NULL THEN NULL
ELSE ROUND(mlss.provisional_score_raw::numeric, 2)
END AS "provisionalScore",
CASE
WHEN mlss.effective_score_raw IS NULL THEN NULL
ELSE ROW_NUMBER() OVER (
ORDER BY
mlss.effective_score_raw DESC NULLS LAST,
mlss.submission_timestamp ASC NULLS LAST,
mlss."memberId" ASC
)
END AS "finalRank"
FROM mm_latest_submission_scores AS mlss
)
SELECT
COALESCE(
u.user_id::bigint,
CASE
WHEN sm."memberId" ~ '^[0-9]+$' THEN sm."memberId"::bigint
ELSE NULL
END
) AS "userId",
COALESCE(
NULLIF(TRIM(u.handle), ''),
NULLIF(TRIM(mem.handle), ''),
sm."memberHandle"
) AS "handle",
COALESCE(e.address, NULLIF(TRIM(mem.email), '')) AS "email",
COALESCE(
comp_code.name,
comp_id.name,
home_code.name,
home_id.name,
NULLIF(TRIM(mem."competitionCountryCode"), ''),
NULLIF(TRIM(mem."homeCountryCode"), '')
) AS "country",
sm.is_marathon_match AS "isMarathonMatch",
CASE
WHEN sm.is_marathon_match THEN NULL
ELSE sms."submissionScore"
END AS "submissionScore",
CASE
WHEN sm.is_marathon_match THEN mrs."provisionalScore"
ELSE NULL
END AS "provisionalScore",
CASE
WHEN sm.is_marathon_match THEN mrs."finalRank"
ELSE NULL
END AS "finalRank"
FROM submitter_members AS sm
LEFT JOIN identity."user" AS u
ON sm."memberId" ~ '^[0-9]+$'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
The regular expression sm."memberId" ~ '^[0-9]+$' is used multiple times. Consider extracting this logic into a common CTE or function to improve maintainability.

AND u.user_id = sm."memberId"::numeric
LEFT JOIN identity.email AS e
ON e.user_id = u.user_id
AND e.primary_ind = 1
LEFT JOIN members."member" AS mem
ON sm."memberId" ~ '^[0-9]+$'
AND mem."userId" = sm."memberId"::bigint
LEFT JOIN lookups."Country" AS home_code
ON UPPER(home_code."countryCode") = UPPER(mem."homeCountryCode")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
Using UPPER() for case-insensitive comparison can lead to performance issues if the column is not indexed accordingly. Consider ensuring the column is indexed with a case-insensitive collation or using a more efficient method.

LEFT JOIN lookups."Country" AS home_id
ON UPPER(home_id.id) = UPPER(mem."homeCountryCode")
LEFT JOIN lookups."Country" AS comp_code
ON UPPER(comp_code."countryCode") = UPPER(mem."competitionCountryCode")
LEFT JOIN lookups."Country" AS comp_id
ON UPPER(comp_id.id) = UPPER(mem."competitionCountryCode")
LEFT JOIN standard_member_scores AS sms
ON sms."memberId" = sm."memberId"
LEFT JOIN mm_ranked_scores AS mrs
ON mrs."memberId" = sm."memberId"
ORDER BY
CASE
WHEN sm.is_marathon_match THEN mrs."finalRank"
ELSE NULL
END ASC NULLS LAST,
CASE
WHEN sm.is_marathon_match THEN NULL
ELSE sms."submissionScore"
END DESC NULLS LAST,
"handle" ASC NULLS LAST,
"userId" ASC NULLS LAST;
Loading
Loading