Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Type-safe API layer — replace double-casts with adapters and zod** (#206) - removed all `as unknown as T` and `as never` casts in 15 of `src/lib/api/*.ts` files. Each SDK call now goes through an adapter function or a Set-backed narrowing helper that warns on unknown enum values. `assertData` (new in `fetch.ts`) rejects empty body responses with a contextual error. `settings.ts` uses zod `.safeParse()` at the trust boundary for `getPasswordPolicy`/`getSmtpConfig`. Public `xxxApi` return types unchanged so consumer code is untouched.

### Fixed
- **Extend SSE EVENT_TYPE_MAP to webhook/artifact/scan/backup/plugin events** (#213) - the per-domain map only covered 7 domains (users, groups, repositories, service accounts, permissions, quality gates, dashboard). When backend events fired for the missing domains over SSE, the UI didn't refetch stale data — operators had to hard-refresh. Adds 5 QUERY_KEYS (`WEBHOOKS`, `WEBHOOK_DELIVERIES`, `BACKUPS`, `SECURITY`, `PLUGINS`), 4 INVALIDATION_GROUPS (`webhooks`, `backups`, `security`, `plugins`), and 19 new event-type entries (`webhook.{created,updated,deleted,delivery}`, `artifact.{uploaded,deleted}`, `scan.{started,completed,failed}`, `finding.{acknowledged,acknowledgment_revoked}`, `backup.{created,completed,failed,restored}`, `plugin.{installed,uninstalled,enabled,disabled}`). Map size grew 20 → 39.
- **Setup Guide: sanitize repo keys for Gradle/SBT property names + clearer SSR placeholder** (#362, partial) - the Gradle credentials snippet emitted property names like `my-jvm-repoUsername` for hyphenated repo keys; technically legal in `gradle.properties` but looks broken to readers expecting identifier rules. Added a `repoKeyToGradleId` helper that camelCases kebab/dot/underscore separators and strips remaining non-alphanumerics. URLs and `<id>` slots keep the raw key — only property names sanitize. Also replaced the SSR fallback `https://artifacts.example.com` with `__REPLACE_WITH_REGISTRY_URL__` so prerendered HTML doesn't ship with a real-looking domain a user could accidentally copy. Remaining `repo_type` (proxy/virtual hides publish steps) and `is_public` (anonymous mode) fixes deferred to follow-up.
- **Per-artifact Security tab now surfaces native scan_findings** (#368) - the Security tab on the repository view (`security-tab-content.tsx`) used to show only SBOM CVE history and Dependency-Track findings, never the native `scan_findings` table. A user who triggered a scan via `POST /api/v1/security/scan` for a specific artifact had no way to see the resulting findings on the artifact's own page — they had to navigate to `/security/scans` and find the right scan ID by name+timestamp. New `ArtifactScansSection` component lists recent scan_results rows for the artifact (status / type / counts / completed_at) with a "View findings" link to the per-scan page. Sourced from `securityApi.listArtifactScans(artifact.id)` which already existed but had no consumer.
- **`getInstallCommand` returns Gradle/SBT-native snippets instead of Maven XML** (#361) - the JVM case in `package-utils.ts` returned the same `<dependency>` XML for all three of `maven` / `gradle` / `sbt`. Users browsing a Gradle-named repo saw Maven XML in the package detail / copy-snippet UI — same bug class #333 fixed on the Setup Guide page. Now `gradle` returns `implementation 'GROUP:name:version'` and `sbt` returns `libraryDependencies += "GROUP" % "name" % "version"`. Maven output is unchanged.
Expand Down
46 changes: 40 additions & 6 deletions src/lib/__tests__/query-keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function createMockQueryClient() {
// ---------------------------------------------------------------------------

describe("QUERY_KEYS", () => {
const expectedKeys: Record<string, string[]> = {
const expectedKeys: Record<string, string[] | (string | undefined)[]> = {
ADMIN_STATS: ["admin-stats"],
RECENT_REPOS: ["recent-repositories"],
REPOSITORIES: ["repositories"],
Expand All @@ -36,10 +36,15 @@ describe("QUERY_KEYS", () => {
ADMIN_GROUPS: ["admin-groups"],
ADMIN_PERMISSIONS: ["admin-permissions"],
SERVICE_ACCOUNTS: ["service-accounts"],
WEBHOOKS: ["webhooks"],
WEBHOOK_DELIVERIES: ["webhook-deliveries"],
BACKUPS: ["backups"],
SECURITY: ["security"],
PLUGINS: ["plugins"],
};

it("has 12 key constants", () => {
expect(Object.keys(QUERY_KEYS)).toHaveLength(12);
it("has 17 key constants (#213)", () => {
expect(Object.keys(QUERY_KEYS)).toHaveLength(17);
});

it.each(Object.entries(expectedKeys))(
Expand All @@ -64,15 +69,19 @@ describe("QUERY_KEYS", () => {
// ---------------------------------------------------------------------------

describe("INVALIDATION_GROUPS", () => {
it("has all expected groups", () => {
it("has all expected groups (#213)", () => {
expect(Object.keys(INVALIDATION_GROUPS).sort((a, b) => a.localeCompare(b))).toEqual([
"backups",
"dashboard",
"groups",
"permissions",
"plugins",
"qualityGates",
"repositories",
"security",
"serviceAccounts",
"users",
"webhooks",
]);
});

Expand Down Expand Up @@ -118,8 +127,8 @@ describe("INVALIDATION_GROUPS", () => {
// ---------------------------------------------------------------------------

describe("EVENT_TYPE_MAP", () => {
it("maps 20 event types", () => {
expect(Object.keys(EVENT_TYPE_MAP)).toHaveLength(20);
it("maps 39 event types (#213)", () => {
expect(Object.keys(EVENT_TYPE_MAP)).toHaveLength(39);
});

it.each([
Expand All @@ -143,6 +152,31 @@ describe("EVENT_TYPE_MAP", () => {
["quality_gate.created", "qualityGates"],
["quality_gate.updated", "qualityGates"],
["quality_gate.deleted", "qualityGates"],
// Webhooks (#213)
["webhook.created", "webhooks"],
["webhook.updated", "webhooks"],
["webhook.deleted", "webhooks"],
["webhook.delivery", "webhooks"],
// Artifacts — invalidate repository lists since artifact CRUD changes
// counts shown on repo cards (#213)
["artifact.uploaded", "repositories"],
["artifact.deleted", "repositories"],
// Scans (#213)
["scan.started", "security"],
["scan.completed", "security"],
["scan.failed", "security"],
["finding.acknowledged", "security"],
["finding.acknowledgment_revoked", "security"],
// Backups (#213)
["backup.created", "backups"],
["backup.completed", "backups"],
["backup.failed", "backups"],
["backup.restored", "backups"],
// Plugins (#213)
["plugin.installed", "plugins"],
["plugin.uninstalled", "plugins"],
["plugin.enabled", "plugins"],
["plugin.disabled", "plugins"],
])("%s maps to %s group", (eventType, expectedGroup) => {
expect(EVENT_TYPE_MAP[eventType]).toBe(expectedGroup);
});
Expand Down
39 changes: 39 additions & 0 deletions src/lib/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export const QUERY_KEYS = {
ADMIN_GROUPS: ["admin-groups"],
ADMIN_PERMISSIONS: ["admin-permissions"],
SERVICE_ACCOUNTS: ["service-accounts"],
WEBHOOKS: ["webhooks"],
WEBHOOK_DELIVERIES: ["webhook-deliveries"],
BACKUPS: ["backups"],
// Single root key — TanStack invalidates by prefix, so any cache entry
// whose key starts with ["security", ...] (dashboard, scores, scans,
// findings, policies, scan-configs) gets invalidated. See #213.
SECURITY: ["security"],
PLUGINS: ["plugins"],
} as const;

// ---------------------------------------------------------------------------
Expand All @@ -44,6 +52,10 @@ export const INVALIDATION_GROUPS: Record<string, readonly (readonly string[])[]>
serviceAccounts: [QUERY_KEYS.SERVICE_ACCOUNTS],
permissions: [QUERY_KEYS.ADMIN_PERMISSIONS],
qualityGates: [QUERY_KEYS.QUALITY_GATES, QUERY_KEYS.QUALITY_HEALTH],
webhooks: [QUERY_KEYS.WEBHOOKS, QUERY_KEYS.WEBHOOK_DELIVERIES],
backups: [QUERY_KEYS.BACKUPS],
security: [QUERY_KEYS.SECURITY],
plugins: [QUERY_KEYS.PLUGINS],
};

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -71,6 +83,33 @@ export const EVENT_TYPE_MAP: Record<string, string> = {
"quality_gate.created": "qualityGates",
"quality_gate.updated": "qualityGates",
"quality_gate.deleted": "qualityGates",
// Webhooks — list view + per-delivery panel both stale on these (#213)
"webhook.created": "webhooks",
"webhook.updated": "webhooks",
"webhook.deleted": "webhooks",
"webhook.delivery": "webhooks",
// Artifact CRUD bumps the artifact_count shown on repo cards/lists, so
// map to repositories rather than introducing a separate artifacts group
// (no consumer queries `["artifacts"]` directly today). (#213)
"artifact.uploaded": "repositories",
"artifact.deleted": "repositories",
// Scans + finding triage — security dashboard, scan list, per-scan
// findings all live under the ["security", ...] prefix (#213)
"scan.started": "security",
"scan.completed": "security",
"scan.failed": "security",
"finding.acknowledged": "security",
"finding.acknowledgment_revoked": "security",
// Backups (#213)
"backup.created": "backups",
"backup.completed": "backups",
"backup.failed": "backups",
"backup.restored": "backups",
// Plugins (#213)
"plugin.installed": "plugins",
"plugin.uninstalled": "plugins",
"plugin.enabled": "plugins",
"plugin.disabled": "plugins",
};

// ---------------------------------------------------------------------------
Expand Down
Loading