-
Notifications
You must be signed in to change notification settings - Fork 729
feat: stewardship api unmock (CM-1218) #4195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/backfill-stewardship-script
Are you sure you want to change the base?
Changes from all commits
81f23a4
b7f1724
f1421c2
a739c9f
1bcf9ad
f0cebf0
3066140
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,90 @@ | ||
| import type { Request, Response } from 'express' | ||
| import { z } from 'zod' | ||
|
|
||
| import { BadRequestError, NotFoundError } from '@crowd/common' | ||
| import { NotFoundError } from '@crowd/common' | ||
| import { getAdvisoriesByPackageId, getPackageDetailByPurl } from '@crowd/data-access-layer' | ||
|
|
||
| import { getPackagesQx } from '@/db/packagesDb' | ||
| import { ok } from '@/utils/api' | ||
| import { validateOrThrow } from '@/utils/validation' | ||
|
|
||
| import { MOCK_DETAILS } from './mockData' | ||
| import type { StewardshipStatus } from './types' | ||
|
|
||
| const querySchema = z.object({ | ||
| purl: z.string().trim().min(1), | ||
| purl: z | ||
| .string() | ||
| .trim() | ||
| .min(1) | ||
| .refine((v) => v.startsWith('pkg:'), { message: 'purl must start with pkg:' }), | ||
| }) | ||
|
|
||
| // TODO: replace with real DB queries once packages DB is wired into the backend | ||
| export async function getPackage(req: Request, res: Response): Promise<void> { | ||
| const { purl } = validateOrThrow(querySchema, req.query) | ||
|
|
||
| if (!purl.startsWith('pkg:')) { | ||
| throw new BadRequestError('Invalid purl format: must start with pkg:') | ||
| } | ||
| const qx = await getPackagesQx() | ||
| const pkg = await getPackageDetailByPurl(qx, purl) | ||
|
|
||
| const detail = MOCK_DETAILS[purl] | ||
| if (!detail) { | ||
| if (!pkg) { | ||
| throw new NotFoundError() | ||
| } | ||
|
|
||
| ok(res, detail) | ||
| const advisories = await getAdvisoriesByPackageId(qx, pkg.id) | ||
|
|
||
| ok(res, { | ||
| purl: pkg.purl, | ||
| name: pkg.name, | ||
| ecosystem: pkg.ecosystem, | ||
| general: { | ||
| healthScore: null, | ||
| impact: { | ||
| impactScore: | ||
| pkg.criticalityScore != null ? Math.round(Number(pkg.criticalityScore) * 100) : null, | ||
| downloadsLastMonth: | ||
| pkg.downloadsLast30d != null ? parseInt(pkg.downloadsLast30d, 10) : null, | ||
| dependentPackages: pkg.dependentPackagesCount ?? null, | ||
| dependentRepos: pkg.dependentReposCount ?? null, | ||
| transitiveReach: pkg.transitiveReach, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. transitiveReach wrong API formatMedium Severity Package detail returns Additional Locations (1)Reviewed by Cursor Bugbot for commit 3066140. Configure here. |
||
| }, | ||
| riskSignals: { | ||
| lifecycle: null, | ||
| maintainerBusFactor: pkg.maintainerCount, | ||
| lastRelease: pkg.latestReleaseAt ? pkg.latestReleaseAt.toISOString() : null, | ||
| hasSecurityFile: pkg.hasSecurityFile, | ||
| openSSFScorecard: pkg.scorecardScore != null ? Number(pkg.scorecardScore) : null, | ||
| }, | ||
| }, | ||
| assessment: {}, | ||
| security: { | ||
| securityContacts: null, | ||
| advisories: advisories.map((a) => ({ | ||
| osvId: a.osvId, | ||
| severity: a.severity, | ||
| resolution: a.resolution, | ||
| })), | ||
| cvd: { | ||
| isPvrEnabled: null, | ||
| hasSecurityPolicyEnabled: pkg.hasSecurityPolicy, | ||
| tier0Steward: null, | ||
| criticalVulnerabilityFlag: pkg.hasCriticalVulnerability, | ||
| }, | ||
| }, | ||
| provenance: { | ||
| repositoryMapping: { | ||
| declaredRepo: pkg.repoUrl ?? pkg.repositoryUrl ?? pkg.declaredRepositoryUrl ?? null, | ||
| mappingConfidence: | ||
| pkg.repoMappingConfidence != null ? Number(pkg.repoMappingConfidence) : null, | ||
| lastCommitAt: pkg.repoLastCommitAt ? pkg.repoLastCommitAt.toISOString() : null, | ||
| }, | ||
| supplyChainIntegrity: { | ||
| buildProvenance: null, | ||
| signedReleases: null, | ||
| }, | ||
| }, | ||
| stewardship: { | ||
| status: (pkg.stewardshipStatus ?? 'unassigned') as StewardshipStatus, | ||
| stewards: null, | ||
| lastActivityAt: null, | ||
| }, | ||
| history: {}, | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,12 @@ | ||
| import type { Request, Response } from 'express' | ||
|
|
||
| import { ok } from '@/utils/api' | ||
| import { getPackageMetrics } from '@crowd/data-access-layer' | ||
|
|
||
| import { MOCK_METRICS } from './mockData' | ||
| import { getPackagesQx } from '@/db/packagesDb' | ||
| import { ok } from '@/utils/api' | ||
|
|
||
| // TODO: replace with real DB queries once packages DB is wired into the backend | ||
| export async function getPackagesMetrics(req: Request, res: Response): Promise<void> { | ||
| ok(res, MOCK_METRICS) | ||
| const qx = await getPackagesQx() | ||
| const metrics = await getPackageMetrics(qx) | ||
| ok(res, metrics) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,16 @@ | ||
| import type { Request, Response } from 'express' | ||
| import { z } from 'zod' | ||
|
|
||
| import { listPackagesForApi } from '@crowd/data-access-layer' | ||
|
|
||
| import { getPackagesQx } from '@/db/packagesDb' | ||
| import { ok } from '@/utils/api' | ||
| import { validateOrThrow } from '@/utils/validation' | ||
|
|
||
| import { MOCK_DETAILS, MOCK_PACKAGES } from './mockData' | ||
| import type { StewardshipStatus } from './types' | ||
|
|
||
| const DEFAULT_PAGE_SIZE = 20 | ||
| const MAX_PAGE_SIZE = 100 | ||
| const STALE_MONTHS = 18 | ||
|
|
||
| const booleanQueryParam = z.preprocess((v) => v === 'true', z.boolean()).default(false) | ||
|
|
||
|
|
@@ -26,7 +28,6 @@ const querySchema = z.object({ | |
| sortDir: z.enum(['asc', 'desc']).default('asc'), | ||
| }) | ||
|
|
||
| // TODO: replace with real DB queries once packages DB is wired into the backend | ||
| export async function listPackages(req: Request, res: Response): Promise<void> { | ||
| const { | ||
| page, | ||
|
|
@@ -40,40 +41,32 @@ export async function listPackages(req: Request, res: Response): Promise<void> { | |
| sortDir, | ||
| } = validateOrThrow(querySchema, req.query) | ||
|
|
||
| const staleThreshold = new Date() | ||
| staleThreshold.setMonth(staleThreshold.getMonth() - STALE_MONTHS) | ||
|
|
||
| let filtered = MOCK_PACKAGES.filter((p) => { | ||
| if (ecosystem && p.ecosystem !== ecosystem) return false | ||
| if (lifecycle && p.lifecycle !== lifecycle) return false | ||
| if (busFactor1Only && p.maintainerBusFactor !== 1) return false | ||
| if (unstewardedOnly && p.stewardship !== 'unassigned') return false | ||
| if (staleOnly) { | ||
| const lastRelease = MOCK_DETAILS[p.purl]?.general.riskSignals.lastRelease | ||
| if (!lastRelease || new Date(lastRelease) >= staleThreshold) return false | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignored list query filtersMedium Severity
Reviewed by Cursor Bugbot for commit 2d2aa85. Configure here. |
||
| } | ||
| return true | ||
| }) | ||
| // health is a v2 field with no backing column yet — fall back to name sort | ||
| const effectiveSortBy = sortBy === 'health' ? 'name' : sortBy | ||
|
|
||
| filtered = filtered.sort((a, b) => { | ||
| let cmp = 0 | ||
| if (sortBy === 'name') { | ||
| cmp = a.name.localeCompare(b.name) | ||
| } else if (sortBy === 'health') { | ||
| cmp = (a.health ?? 0) - (b.health ?? 0) | ||
| } else if (sortBy === 'impact') { | ||
| cmp = (a.impact ?? 0) - (b.impact ?? 0) | ||
| } else if (sortBy === 'openVulns') { | ||
| const sumA = a.openVulns.low + a.openVulns.medium + a.openVulns.high + a.openVulns.critical | ||
| const sumB = b.openVulns.low + b.openVulns.medium + b.openVulns.high + b.openVulns.critical | ||
| cmp = sumA - sumB | ||
| } | ||
| return sortDir === 'desc' ? -cmp : cmp | ||
| const qx = await getPackagesQx() | ||
| const { rows, total } = await listPackagesForApi(qx, { | ||
| page, | ||
| pageSize, | ||
| ecosystem, | ||
| staleOnly, | ||
| unstewardedOnly, | ||
| sortBy: effectiveSortBy, | ||
| sortDir, | ||
| }) | ||
|
Comment on lines
+47
to
56
|
||
|
|
||
| const total = filtered.length | ||
| const start = (page - 1) * pageSize | ||
| const packages = filtered.slice(start, start + pageSize) | ||
| const packages = rows.map((r) => ({ | ||
| purl: r.purl, | ||
| name: r.name, | ||
| ecosystem: r.ecosystem, | ||
| health: null, | ||
| impact: r.criticalityScore != null ? Math.round(Number(r.criticalityScore) * 100) : null, | ||
| lifecycle: null, | ||
| maintainerBusFactor: null, | ||
| openVulns: r.openVulns, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. List openVulns wrong response shapeHigh Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit 3066140. Configure here. |
||
| stewardship: (r.stewardshipStatus ?? 'unassigned') as StewardshipStatus, | ||
| stewards: null, | ||
| })) | ||
|
|
||
|
|
||
| ok(res, { | ||
| page, | ||
|
|
@@ -86,7 +79,7 @@ export async function listPackages(req: Request, res: Response): Promise<void> { | |
| staleOnly, | ||
| unstewardedOnly, | ||
| }, | ||
| sort: { by: sortBy, dir: sortDir }, | ||
| sort: { by: effectiveSortBy, dir: sortDir }, | ||
| packages, | ||
| }) | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why transitiveReach, maintainerBusFactor, resolution, securityContacts (this one we should check with Mouad how he is storing these) are null?