Conversation
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
There was a problem hiding this comment.
Pull request overview
Adds a new “Vulnerabilities” section to the project Security view, wiring multiple UI widgets (summary, charts, tables, filters, drawer) to new backend endpoints and gating access behind authentication.
Changes:
- Introduced vulnerability response types/config mappings and a TanStack query key for vulnerabilities.
- Added
VULNERABILITY_API_SERVICEwith summary, breakdown, recent, and paginated “all vulnerabilities” queries. - Implemented the vulnerabilities UI (summary cards, severity/ecosystem charts, recent table + “view more” drawer, and auth-wall preview) and mounted it on the Security page.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/types/vulnerabilities/responses.types.ts | Adds vulnerability enums/types plus UI config mappings for severity/status. |
| frontend/setup/tailwind.ts | Adds a Tailwind safelist for vulnerability-related color classes. |
| frontend/app/components/uikit/chart/configs/bar.chart.ts | Adds a reusable horizontal bar chart config helper. |
| frontend/app/components/shared/types/tanstack.ts | Adds a VULNERABILITIES query key. |
| frontend/app/components/modules/project/views/security.vue | Mounts vulnerabilities section + auth wall based on authentication. |
| frontend/app/components/modules/project/services/vulnerability.api.service.ts | Implements Vue Query calls for vulnerabilities summary/breakdowns/listing. |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerability-table.vue | Adds vulnerabilities table header + row rendering. |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerability-summary.vue | Adds top-level summary stats UI wired to the API. |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerability-severity.vue | Adds “by severity” stacked bar + legend wired to API. |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerability-row.vue | Adds a single vulnerability row renderer (badges, tooltips, status icon). |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerability-filters.vue | Adds filter/sort controls for the drawer list. |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerability-ecosystem.vue | Adds “by ecosystem” horizontal bar chart wired to API. |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerability-drawer.vue | Adds the “view more” drawer with infinite pagination and filters. |
| frontend/app/components/modules/project/components/vulnerabilities/vulnerabilities-section.vue | Composes summary + charts + recent list + drawer into a card section. |
| frontend/app/components/modules/project/components/vulnerabilities/recent-vulnerabilities.vue | Adds “recent vulnerabilities” list wired to API + view-more trigger. |
| frontend/app/components/modules/project/components/vulnerabilities/auth-wall-vulnerabilities.vue | Adds unauthenticated preview/auth-wall CTA for vulnerabilities. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| </div> | ||
| </lfx-card> | ||
|
|
||
| <lfx-project-vulnerabilities-section |
There was a problem hiding this comment.
LfxProjectVulnerabilitiesSection emits chooseRepository (used by the aggregated disclaimer CTA), but the parent doesn’t listen to it here. As a result, clicking “choose a specific repository” won’t open the repository switch modal. Wire the event to isSearchRepoModalOpen = true (kebab-case listener: @choose-repository).
| <lfx-project-vulnerabilities-section | |
| <lfx-project-vulnerabilities-section | |
| v-if="isAuthenticated" | |
| @choose-repository="isSearchRepoModalOpen = true" | |
| /> |
| <!-- Table Body --> | ||
| <lfx-project-vulnerability-row | ||
| v-for="vulnerability in props.vulnerabilities" | ||
| :key="vulnerability.cveId" |
There was a problem hiding this comment.
The v-for key uses vulnerability.cveId, which may not be unique (same CVE can appear across multiple packages/paths). Use a guaranteed-unique identifier like vulnerability.vulnerabilityId to avoid rendering/update issues.
| :key="vulnerability.cveId" | |
| :key="vulnerability.vulnerabilityId" |
| const fixedCount = Math.round((data.value.fixedPercentage / 100) * data.value.count); | ||
| return data.value.count - fixedCount; |
There was a problem hiding this comment.
openVulnerabilities is derived from fixedPercentage using Math.round, which can produce inconsistent values (e.g., rounding can make fixedCount exceed count, yielding negative opens) and will drift when percentages are not exact. Consider having the API return open/fixed counts directly, or at least clamp the computed value to [0, count] and avoid rounding errors (e.g., compute fixedCount from a count field, or use a safer rounding strategy).
| const fixedCount = Math.round((data.value.fixedPercentage / 100) * data.value.count); | |
| return data.value.count - fixedCount; | |
| const totalCount = typeof data.value.count === 'number' && data.value.count > 0 ? data.value.count : 0; | |
| const rawFixedPercentage = | |
| typeof data.value.fixedPercentage === 'number' ? data.value.fixedPercentage : 0; | |
| // Clamp percentage to [0, 100] to avoid invalid values from the API | |
| const clampedPercentage = Math.min(100, Math.max(0, rawFixedPercentage)); | |
| const rawFixedCount = (clampedPercentage / 100) * totalCount; | |
| // Use floor and clamp to [0, totalCount] to avoid rounding beyond bounds | |
| const fixedCount = Math.min(totalCount, Math.max(0, Math.floor(rawFixedCount))); | |
| const opens = totalCount - fixedCount; | |
| // Final guard to ensure non-negative open count | |
| return Math.max(0, opens); |
|
|
||
| export enum VulnerabilityStatus { | ||
| OPEN = 'OPEN', | ||
| FIXED = 'RESOLVED', |
There was a problem hiding this comment.
VulnerabilityStatus.FIXED is assigned the string value 'RESOLVED'. This mismatch between the enum member name and its actual value is confusing and easy to misuse in filters/logic. Consider renaming the enum member to RESOLVED (and keep label: 'Fixed' in the config), or make the value 'FIXED' if that’s what the API returns.
| FIXED = 'RESOLVED', | |
| FIXED = 'FIXED', |
| 'Median CVS of all vulnerabilities', | ||
| 'Fix status', | ||
| 'Vulnerability by severity', |
There was a problem hiding this comment.
User-facing copy has a typo/inaccuracy: “Median CVS of all vulnerabilities” should be “Median/average CVSS …” (CVS is a different term) and currently doesn’t match the “Average CVSS” metric shown in the summary. Also “Vulnerability by severity” should be plural for consistency.
| 'Median CVS of all vulnerabilities', | |
| 'Fix status', | |
| 'Vulnerability by severity', | |
| 'Average CVSS of all vulnerabilities', | |
| 'Fix status', | |
| 'Vulnerabilities by severity', |
| getNextPageParam: (lastPage: AllVulnerabilitiesResponse) => { | ||
| const nextPage = lastPage.page + 1; | ||
| const totalPages = Math.ceil(lastPage.total / lastPage.pageSize); | ||
| return nextPage < totalPages ? nextPage : null; |
There was a problem hiding this comment.
getNextPageParam returns null when there are no more pages. TanStack Query treats anything other than undefined as a valid next page param, so hasNextPage can stay truthy and fetchNextPage() may loop/refetch page 0 (since page=null becomes 0 server-side). Return undefined instead (and keep the return type aligned with the number pageParam generic).
| return nextPage < totalPages ? nextPage : null; | |
| return nextPage < totalPages ? nextPage : undefined; |
| params.value.severity, | ||
| params.value.ecosystem, | ||
| params.value.status, | ||
| params.value.sort, |
There was a problem hiding this comment.
fetchAllVulnerabilities supports a pageSize param (and uses it in the queryFn), but pageSize is not included in the queryKey. If pageSize changes, Vue Query can reuse cached data from a different page size. Include params.value.pageSize (or the resolved default) in the key.
| params.value.sort, | |
| params.value.sort, | |
| params.value.pageSize ?? DEFAULT_PAGE_SIZE, |
In this PR
Ticket
IN-1006