Skip to content

Commit c024a4b

Browse files
committed
fix(search): render HTTPProxy + ExportPolicy hits in result list
kindToHref switch had no cases for HTTPProxy or ExportPolicy, so SearchResultItem's null-href guard returned null for every row — making the rows silently disappear while the group header still rendered (kindDisplayName is a separate map). Symptom: a group with no visible items. Three coordinated updates across the three kind-keyed switches: - kindToHref: add detail-page URL mappings for HTTPProxy (/project/[id]/edge/[proxyId]) and ExportPolicy (/project/[id]/export-policies/[exportPolicyId]) - KindIcon: GaugeIcon for HTTPProxy + ChartSplineIcon for ExportPolicy, matching the project sidebar nav icons - kindDisplayName: group header labels read 'AI Edge' and 'Metrics' so they mirror the sidebar nav titles exactly Tests updated to assert positive URL returns for both kinds and to document the API's all-caps HTTPProxy casing contract (Pascal 'HttpProxy' is still in the null list because the server never sends that form). The three kind-keyed switches (kindToHref, KindIcon, kindDisplayName) remain independent — adding a kind still requires updating all three. A future refactor could merge them into a single kind-registry record so kinds become a single source of truth.
1 parent 6e1ba3f commit c024a4b

3 files changed

Lines changed: 42 additions & 3 deletions

File tree

app/features/search/shared/kindIcon.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Icon } from '@datum-cloud/datum-ui/icons';
2-
import { Box, Building2, Layers, Signpost, Users } from 'lucide-react';
2+
import { Box, Building2, ChartSplineIcon, GaugeIcon, Layers, Signpost, Users } from 'lucide-react';
33

44
interface KindIconProps {
55
kind: string;
@@ -31,6 +31,12 @@ export function KindIcon({ kind, className }: KindIconProps) {
3131
return <Icon icon={Layers} size={16} className={className} aria-hidden />;
3232
case 'DNSZone':
3333
return <Icon icon={Signpost} size={16} className={className} aria-hidden />;
34+
case 'HTTPProxy':
35+
// Matches the GaugeIcon used by the "Edge" nav item in the project layout.
36+
return <Icon icon={GaugeIcon} size={16} className={className} aria-hidden />;
37+
case 'ExportPolicy':
38+
// Matches the ChartSplineIcon used by the "Metrics" nav item.
39+
return <Icon icon={ChartSplineIcon} size={16} className={className} aria-hidden />;
3440
default:
3541
return <Icon icon={Box} size={16} className={className} aria-hidden />;
3642
}
@@ -47,6 +53,10 @@ export function kindDisplayName(kind: string): string {
4753
Group: 'Groups',
4854
Domain: 'Domains',
4955
DNSZone: 'DNS',
56+
// Labels mirror the project sidebar nav titles so search group headers
57+
// feel consistent with the rest of the chrome.
58+
HTTPProxy: 'AI Edge',
59+
ExportPolicy: 'Metrics',
5060
};
5161
return map[kind] ?? kind;
5262
}

app/features/search/shared/kindToHref.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,29 @@ describe('kindToHref', () => {
4343
expect(href).toContain('admins');
4444
});
4545

46+
it('returns HTTPProxy URL for HTTPProxy kind (all-caps HTTP, matches API casing)', () => {
47+
const href = kindToHref(hit('HTTPProxy', 'jinja-resource', 'acme-prod'));
48+
expect(href).not.toBeNull();
49+
expect(href).toContain('jinja-resource');
50+
expect(href).toContain('edge');
51+
});
52+
53+
it('returns ExportPolicy URL for ExportPolicy kind', () => {
54+
const href = kindToHref(hit('ExportPolicy', 'metrics-policy', 'acme-prod'));
55+
expect(href).not.toBeNull();
56+
expect(href).toContain('metrics-policy');
57+
expect(href).toContain('export-policies');
58+
});
59+
4660
it('returns null for unsupported kind', () => {
4761
expect(kindToHref(hit('UnknownKind'))).toBeNull();
4862
});
4963

50-
it('returns null for kinds not yet indexed (HttpProxy, Secret, ExportPolicy, ServiceAccount, DnsRecordSet)', () => {
51-
for (const kind of ['HttpProxy', 'Secret', 'ExportPolicy', 'ServiceAccount', 'DnsRecordSet']) {
64+
it('returns null for kinds not yet wired (Pascal-case HttpProxy, Secret, ServiceAccount, DnsRecordSet)', () => {
65+
// HttpProxy (Pascal) intentionally returns null — the API uses all-caps
66+
// HTTPProxy, so the Pascal form is not a real kind that comes back from
67+
// the server. Keeping it in the null list documents the casing contract.
68+
for (const kind of ['HttpProxy', 'Secret', 'ServiceAccount', 'DnsRecordSet']) {
5269
expect(kindToHref(hit(kind, 'x', 'acme-prod'))).toBeNull();
5370
}
5471
});

app/features/search/shared/kindToHref.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ export function kindToHref(hit: SearchHit): string | null {
3939
dnsZoneId: hit.name,
4040
});
4141

42+
case 'HTTPProxy':
43+
return getPathWithParams(paths.project.detail.proxy.detail.root, {
44+
projectId: hit.tenant.name,
45+
proxyId: hit.name,
46+
});
47+
48+
case 'ExportPolicy':
49+
return getPathWithParams(paths.project.detail.metrics.detail.root, {
50+
projectId: hit.tenant.name,
51+
exportPolicyId: hit.name,
52+
});
53+
4254
default:
4355
return null;
4456
}

0 commit comments

Comments
 (0)