fix(security-ui): expose Community-tier scan surfaces per PR #930 (#1070)

PR #930 opened the backend security routes to Community admin but left
several frontend isPaid gates in place, so the matching UI surfaces
stayed hidden from Community even though the API would accept the
request. This brings the UI in line with the backend matrix.

Flipped:
- ResourcesView Scan history button (was trivy.available && isPaid)
- ResourcesView Full scan (vulnerabilities + secrets) menu item
- ResourcesView VulnerabilityScanSheet canCompare
- ResourcesView VulnerabilityScanSheet canManageSuppressions (now isAdmin)
- EditorView stack-level Scan config button (canScan)
- SecurityHistoryView primaryAction (Compare)
- SecurityHistoryView VulnerabilityScanSheet canManageSuppressions

Unchanged on purpose (still paid, matching backend gates):
- canGenerateSbom (SBOM endpoint requirePaid)
- SARIF download in the drawer overflow
- Scan Policies block in Settings
- Trivy auto-update toggle (requireAdmiral)

Test: inverted the SecurityHistoryView.test.tsx assertion that locked
in the now-incorrect "hides Compare on Community" behavior.
This commit is contained in:
Anso
2026-05-16 20:01:38 -04:00
committed by GitHub
parent e206fa005e
commit 6354cb3387
4 changed files with 14 additions and 16 deletions
@@ -399,7 +399,7 @@ export function EditorView({
</Button>
{(() => {
const canRollback = isPaid && backupInfo.exists;
const canScan = trivy.available && isAdmin && isPaid;
const canScan = trivy.available && isAdmin;
const hasOverflowExtras = canRollback || canScan;
return (
<DropdownMenu>
+8 -10
View File
@@ -732,7 +732,7 @@ export default function ResourcesView() {
</TabsHighlightItem>
</TabsHighlight>
</TabsList>
{trivy.available && isPaid && (
{trivy.available && (
<Button
variant="outline"
size="sm"
@@ -836,13 +836,11 @@ export default function ResourcesView() {
>
Scan (vulnerabilities)
</DropdownMenuItem>
{isPaid && (
<DropdownMenuItem
onClick={() => handleScanImage(img.RepoTags![0], { scanners: ['vuln', 'secret'] })}
>
Full scan (vulnerabilities + secrets)
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => handleScanImage(img.RepoTags![0], { scanners: ['vuln', 'secret'] })}
>
Full scan (vulnerabilities + secrets)
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
@@ -1304,8 +1302,8 @@ export default function ResourcesView() {
onClose={() => setInspectScanId(null)}
onRescan={(imageRef) => { setInspectScanId(null); handleScanImage(imageRef, { force: true }); }}
canGenerateSbom={isPaid}
canCompare={isPaid}
canManageSuppressions={isPaid && isAdmin}
canCompare
canManageSuppressions={isAdmin}
/>
</div>
);
@@ -157,12 +157,12 @@ export function SecurityHistoryView({ open, onClose }: SecurityHistoryViewProps)
crumb={['Security', 'Scan history']}
name="Scan history"
meta={meta}
primaryAction={isPaid ? {
primaryAction={{
label: `Compare (${selected.length}/2)`,
icon: GitCompare,
onClick: compareSelected,
disabled: compareDisabled,
} : undefined}
}}
secondaryActions={[{
label: 'Refresh',
icon: RefreshCw,
@@ -316,7 +316,7 @@ export function SecurityHistoryView({ open, onClose }: SecurityHistoryViewProps)
onClose={() => setInspectScanId(null)}
canGenerateSbom={isPaid}
canCompare={false}
canManageSuppressions={isPaid && isAdmin}
canManageSuppressions={isAdmin}
/>
</SystemSheet>
);
@@ -211,7 +211,7 @@ describe('SecurityHistoryView', () => {
expect(mockedFetch).toHaveBeenCalledTimes(1);
});
it('hides Compare button for community tier', async () => {
it('shows Compare button for community tier (scan compare is Community per PR #930)', async () => {
licenseState.isPaid = false;
mockedFetch.mockResolvedValue(
listResponse([
@@ -226,6 +226,6 @@ describe('SecurityHistoryView', () => {
await user.click(checkboxes[0]);
await user.click(checkboxes[1]);
expect(screen.queryByRole('button', { name: /Compare/ })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: /Compare \(2\/2\)/ })).toBeEnabled();
});
});