Skip to content

Commit cbdd3d4

Browse files
committed
fix: adding maintainer filter
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent 90f5681 commit cbdd3d4

2 files changed

Lines changed: 34 additions & 6 deletions

File tree

backend/src/api/public/v1/packages/batchGetStewardship.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ const bodySchema = z.object({
2626

2727
export async function batchGetStewardship(req: Request, res: Response): Promise<void> {
2828
const { purls: rawPurls } = validateOrThrow(bodySchema, req.body)
29-
const normalizedPurls = rawPurls.map((p) => p.replace(/@/g, '%40'))
29+
// Normalize after parsing (not in the schema) so rawPurls keeps the client's
30+
// original form — used as the response key so clients can look up their input.
31+
const normalizedPurls = rawPurls.map(normalizePurl)
3032

3133
const qx = await getPackagesQx()
3234
const rows = await getPackagesByStewardshipPurls(qx, normalizedPurls)

services/libs/data-access-layer/src/osspckgs/api.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ export async function listPackagesForApi(
9494
conditions.push(`(s.status = 'unassigned' OR s.id IS NULL)`)
9595
}
9696

97+
if (opts.busFactor1Only) {
98+
// References pm_counts LATERAL join below — computed once, used in both WHERE and SELECT
99+
conditions.push(`pm_counts.cnt = 1`)
100+
}
101+
97102
const where = `WHERE ${conditions.join(' AND ')}`
98103

99104
// health is a v2 field — fall back to name sort
@@ -103,8 +108,8 @@ export async function listPackagesForApi(
103108
else sortExpr = 'LOWER(p.name)'
104109
const sortDir = opts.sortDir === 'desc' ? 'DESC' : 'ASC'
105110

106-
params.limit = opts.pageSize
107-
params.offset = (opts.page - 1) * opts.pageSize
111+
// Separate paginated params from filter-only params used by the fallback COUNT query
112+
const queryParams = { ...params, limit: opts.pageSize, offset: (opts.page - 1) * opts.pageSize }
108113

109114
const rows: PackageListRow[] = await qx.select(
110115
`
@@ -115,21 +120,42 @@ export async function listPackagesForApi(
115120
p.impact AS "criticalityScore",
116121
s.status AS "stewardshipStatus",
117122
COALESCE(ap_counts.cnt, 0) AS "openVulns",
118-
(SELECT COUNT(*)::int FROM package_maintainers pm WHERE pm.package_id = p.id) AS "maintainerCount",
123+
pm_counts.cnt AS "maintainerCount",
119124
COUNT(*) OVER() AS total
120125
FROM packages p
121126
LEFT JOIN stewardships s ON s.package_id = p.id
122127
LEFT JOIN LATERAL (
123128
SELECT COUNT(*)::int AS cnt FROM advisory_packages WHERE package_id = p.id
124129
) ap_counts ON true
130+
LEFT JOIN LATERAL (
131+
SELECT COUNT(*)::int AS cnt FROM package_maintainers pm WHERE pm.package_id = p.id
132+
) pm_counts ON true
125133
${where}
126134
ORDER BY ${sortExpr} ${sortDir} NULLS LAST, p.purl ${sortDir}
127135
LIMIT $(limit) OFFSET $(offset)
128136
`,
129-
params,
137+
queryParams,
130138
)
131139

132-
const total = rows.length > 0 ? parseInt(rows[0].total, 10) : 0
140+
let total: number
141+
if (rows.length > 0) {
142+
total = parseInt(rows[0].total, 10)
143+
} else {
144+
// Window function returns no rows when the page is beyond the result set.
145+
// Fall back to a separate COUNT so the caller always gets the real total.
146+
const countRow: { count: string } = await qx.selectOne(
147+
`SELECT COUNT(*)::text AS count
148+
FROM packages p
149+
LEFT JOIN stewardships s ON s.package_id = p.id
150+
LEFT JOIN LATERAL (
151+
SELECT COUNT(*)::int AS cnt FROM package_maintainers pm WHERE pm.package_id = p.id
152+
) pm_counts ON true
153+
${where}`,
154+
params,
155+
)
156+
total = parseInt(countRow.count, 10)
157+
}
158+
133159
return { rows, total }
134160
}
135161

0 commit comments

Comments
 (0)