From de8c27e666e4bdd762ea15112b65609d67b51de7 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Thu, 25 Jun 2026 13:10:55 +0200 Subject: [PATCH] feat: update sorting Signed-off-by: Umberto Sgueglia --- .../src/api/public/v1/ossprey/packageList.ts | 59 ++++++++++--------- .../data-access-layer/src/osspckgs/api.ts | 5 +- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/backend/src/api/public/v1/ossprey/packageList.ts b/backend/src/api/public/v1/ossprey/packageList.ts index ee8a1b1fd3..b8e1819366 100644 --- a/backend/src/api/public/v1/ossprey/packageList.ts +++ b/backend/src/api/public/v1/ossprey/packageList.ts @@ -20,33 +20,38 @@ const LIFECYCLE_SET = new Set(LIFECYCLE_VALUES) const boolParam = z.preprocess((v) => v === 'true', z.boolean()).default(false) -const querySchema = z.object({ - page: z.coerce.number().int().min(1).default(1), - pageSize: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(25), - ecosystem: z.string().trim().optional(), - lifecycle: z.enum(LIFECYCLE_VALUES).optional(), - name: z.string().trim().optional(), - purl: purlFilterSchema, - status: z - .enum([ - 'unassigned', - 'open', - 'assessing', - 'active', - 'needs_attention', - 'escalated', - 'blocked', - 'inactive', - ]) - .optional(), - healthBand: z.enum(HEALTH_BAND_VALUES).optional(), - vulnSeverity: z.enum(['any', 'high', 'critical', 'none']).optional(), - staleOnly: boolParam, - unstewardedOnly: boolParam, - busFactor1Only: boolParam, - sortBy: z.enum(['name', 'risk', 'impact', 'openVulns', 'health']).default('risk'), - sortDir: z.enum(['asc', 'desc']).default('desc'), -}) +const querySchema = z + .object({ + page: z.coerce.number().int().min(1).default(1), + pageSize: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(25), + ecosystem: z.string().trim().optional(), + lifecycle: z.enum(LIFECYCLE_VALUES).optional(), + name: z.string().trim().optional(), + purl: purlFilterSchema, + status: z + .enum([ + 'unassigned', + 'open', + 'assessing', + 'active', + 'needs_attention', + 'escalated', + 'blocked', + 'inactive', + ]) + .optional(), + healthBand: z.enum(HEALTH_BAND_VALUES).optional(), + vulnSeverity: z.enum(['any', 'high', 'critical', 'none']).optional(), + staleOnly: boolParam, + unstewardedOnly: boolParam, + busFactor1Only: boolParam, + sortBy: z.enum(['name', 'risk', 'impact', 'openVulns', 'health']).default('risk'), + sortDir: z.enum(['asc', 'desc']).optional(), + }) + .transform((data) => ({ + ...data, + sortDir: data.sortDir ?? (data.sortBy === 'name' || data.sortBy === 'health' ? 'asc' : 'desc'), + })) export async function packageListHandler(req: Request, res: Response): Promise { const params = validateOrThrow(querySchema, req.query) diff --git a/services/libs/data-access-layer/src/osspckgs/api.ts b/services/libs/data-access-layer/src/osspckgs/api.ts index 24850b4b68..778413ff3b 100644 --- a/services/libs/data-access-layer/src/osspckgs/api.ts +++ b/services/libs/data-access-layer/src/osspckgs/api.ts @@ -436,7 +436,7 @@ export async function listPackagesForApi( } else if (opts.sortBy === 'openVulns') { sortExpr = '"openVulns"' } else if (opts.sortBy === 'health') { - sortExpr = 'r_sc.scorecard_score' + sortExpr = 'COALESCE(p.health_score, r_sc.scorecard_score * 10)' } else if (opts.sortBy === 'risk') { // Composite risk score: impact + health deficit + vuln exposure + bus factor + staleness sortExpr = `( @@ -451,6 +451,7 @@ export async function listPackagesForApi( sortExpr = 'LOWER(p.name)' } const sortDir = opts.sortDir === 'desc' ? 'DESC' : 'ASC' + const nullsDir = sortDir === 'ASC' ? 'NULLS FIRST' : 'NULLS LAST' // Separate paginated params from filter-only params used by the fallback COUNT query const queryParams: Record = { @@ -568,7 +569,7 @@ export async function listPackagesForApi( FROM packages p ${laterals} ${where} - ORDER BY ${[exactSort, `${sortExpr} ${sortDir} NULLS LAST`, `p.purl ${sortDir}`].filter(Boolean).join(', ')} + ORDER BY ${[exactSort, `${sortExpr} ${sortDir} ${nullsDir}`, `p.purl ${sortDir}`].filter(Boolean).join(', ')} LIMIT $(limit) OFFSET $(offset) `, queryParams,