Skip to content

Commit ce63af9

Browse files
committed
fix(aurora-portal): messages merge conflicts
2 parents 0e6542c + fc1bc08 commit ce63af9

10 files changed

Lines changed: 169 additions & 12 deletions

File tree

.changeset/full-signs-cheat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cobaltcore-dev/aurora": minor
3+
---
4+
5+
show domain name on project cards by fetching auth/domains and using new juno heading system

packages/aurora/src/client/routes/_auth/projects/-components/ProjectCardView.test.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@ vi.mock("@cloudoperators/juno-ui-components", () => ({
1616
{children}
1717
</div>
1818
),
19-
ContentHeading: ({ children, className }: { children: React.ReactNode; className?: string }) => (
20-
<h1 className={className}>{children}</h1>
21-
),
2219
}))
2320

2421
const projects = [
2522
{
2623
domain_id: "1789d1",
24+
domain_name: "test-domain",
2725
enabled: true,
2826
id: "89ac3f",
2927
links: {
@@ -54,6 +52,46 @@ describe("ProjectCardView", () => {
5452
expect(screen.getByText("Manages security compliance and access control.")).toBeDefined()
5553
})
5654

55+
test("renders domain_name when available", async () => {
56+
render(
57+
<I18nProvider i18n={i18n}>
58+
<ProjectCardView projects={projects} />
59+
</I18nProvider>
60+
)
61+
62+
await waitFor(() => {
63+
expect(screen.getByText("test-domain")).toBeDefined()
64+
})
65+
})
66+
67+
test("falls back to domain_id when domain_name is missing", async () => {
68+
const projectWithoutDomainName = [{ ...projects[0], domain_name: undefined }]
69+
render(
70+
<I18nProvider i18n={i18n}>
71+
<ProjectCardView projects={projectWithoutDomainName} />
72+
</I18nProvider>
73+
)
74+
75+
await waitFor(() => {
76+
expect(screen.getByText("1789d1")).toBeDefined()
77+
})
78+
})
79+
80+
test("does not render description when absent", async () => {
81+
const projectWithoutDescription = [{ ...projects[0], description: undefined }]
82+
render(
83+
<I18nProvider i18n={i18n}>
84+
<ProjectCardView projects={projectWithoutDescription} />
85+
</I18nProvider>
86+
)
87+
88+
await waitFor(() => {
89+
expect(screen.getByText("Security Group")).toBeDefined()
90+
})
91+
92+
expect(screen.queryByText("Manages security compliance and access control.")).toBeNull()
93+
})
94+
5795
test("card navigates to the correct project route on click", async () => {
5896
render(
5997
<I18nProvider i18n={i18n}>

packages/aurora/src/client/routes/_auth/projects/-components/ProjectCardView.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useNavigate } from "@tanstack/react-router"
22
import { Project } from "@/server/Project/types/models"
3-
import { Card, ContentHeading } from "@cloudoperators/juno-ui-components"
3+
import { Card } from "@cloudoperators/juno-ui-components"
44
import { Trans } from "@lingui/react/macro"
55

66
type ProjectCardProps = {
@@ -16,12 +16,15 @@ export function ProjectCard({ project }: ProjectCardProps) {
1616
<Card
1717
padding
1818
onClick={() => navigate({ to: "/projects/$projectId", params: { projectId: project.id } })}
19-
className="min-h-50"
19+
className="flex flex-col gap-4"
2020
>
21-
<div className="w-full">
22-
<ContentHeading className="text-theme-accent">{project.name}</ContentHeading>
23-
<p className="mt-4 line-clamp-3 pr-4 leading-relaxed">{project.description}</p>
21+
<div className="flex min-w-0 flex-col">
22+
<span className="text-theme-light text-xs leading-6">
23+
{project.domain_name ?? project.domain_id ?? <Trans>Unknown domain</Trans>}
24+
</span>
25+
<h5>{project.name}</h5>
2426
</div>
27+
{project.description && <p className="text-theme-default line-clamp-2">{project.description}</p>}
2528
</Card>
2629
)
2730
}

packages/aurora/src/locales/de/messages.po

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3235,6 +3235,9 @@ msgstr "Image-Datei kann nicht hochgeladen werden"
32353235
msgid "Unknown"
32363236
msgstr ""
32373237

3238+
msgid "Unknown domain"
3239+
msgstr ""
3240+
32383241
msgid "Unnamed"
32393242
msgstr "Unbenannt"
32403243

packages/aurora/src/locales/de/messages.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/aurora/src/locales/en/messages.po

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3235,6 +3235,9 @@ msgstr "Unable to Upload Image File"
32353235
msgid "Unknown"
32363236
msgstr "Unknown"
32373237

3238+
msgid "Unknown domain"
3239+
msgstr "Unknown domain"
3240+
32383241
msgid "Unnamed"
32393242
msgstr "Unnamed"
32403243

packages/aurora/src/locales/en/messages.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import { createCallerFactory, auroraRouter } from "../../trpc"
3+
import { projectRouter } from "./projectRouter"
4+
import { AuroraPortalContext } from "../../context"
5+
import { TRPCError } from "@trpc/server"
6+
7+
const mockFetch = vi.fn()
8+
vi.stubGlobal("fetch", mockFetch)
9+
10+
const makeCtx = (overrides?: Partial<AuroraPortalContext>) =>
11+
({
12+
identityEndpoint: "https://identity.example.com/v3/",
13+
validateSession: vi.fn().mockReturnValue(true),
14+
imageMetadataExcludedProperties: [],
15+
signal: new AbortController().signal,
16+
openstack: {
17+
isValid: vi.fn().mockReturnValue(true),
18+
getToken: vi.fn().mockReturnValue({ authToken: "test-token", tokenData: {} }),
19+
service: vi.fn(),
20+
},
21+
getUserInfo: vi.fn().mockResolvedValue({ availableDomains: [] }),
22+
rescopeSession: vi.fn(),
23+
createSession: vi.fn(),
24+
terminateSession: vi.fn(),
25+
...overrides,
26+
}) as unknown as AuroraPortalContext
27+
28+
const createCaller = createCallerFactory(auroraRouter({ ...projectRouter }))
29+
30+
const projectsPayload = {
31+
projects: [
32+
{ id: "p1", name: "shadowvault", enabled: true, domain_id: "d1" },
33+
{ id: "p2", name: "ironkeep", enabled: true, domain_id: "d2" },
34+
{ id: "p3", name: "stormwatch", enabled: true, domain_id: "unknown-domain" },
35+
],
36+
}
37+
38+
const domainsPayload = {
39+
domains: [
40+
{ id: "d1", name: "domain-alpha" },
41+
{ id: "d2", name: "domain-beta" },
42+
],
43+
}
44+
45+
describe("projectRouter.getAuthProjects", () => {
46+
beforeEach(() => {
47+
vi.clearAllMocks()
48+
})
49+
50+
it("attaches domain_name by resolving domain_id", async () => {
51+
mockFetch
52+
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(projectsPayload) } as Response)
53+
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(domainsPayload) } as Response)
54+
55+
const caller = createCaller(makeCtx())
56+
const result = await caller.getAuthProjects()
57+
58+
expect(result?.find((p) => p.id === "p1")?.domain_name).toBe("domain-alpha")
59+
expect(result?.find((p) => p.id === "p2")?.domain_name).toBe("domain-beta")
60+
})
61+
62+
it("leaves domain_name undefined when domain_id has no match", async () => {
63+
mockFetch
64+
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(projectsPayload) } as Response)
65+
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(domainsPayload) } as Response)
66+
67+
const caller = createCaller(makeCtx())
68+
const result = await caller.getAuthProjects()
69+
70+
expect(result?.find((p) => p.id === "p3")?.domain_name).toBeUndefined()
71+
})
72+
73+
it("throws UNAUTHORIZED when no openstack session", async () => {
74+
const caller = createCaller(makeCtx({ openstack: undefined }))
75+
await expect(caller.getAuthProjects()).rejects.toBeInstanceOf(TRPCError)
76+
})
77+
78+
it("still returns projects with empty domain map when domains fetch fails", async () => {
79+
mockFetch
80+
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(projectsPayload) } as Response)
81+
.mockRejectedValueOnce(new Error("network error"))
82+
83+
const caller = createCaller(makeCtx())
84+
const result = await caller.getAuthProjects()
85+
86+
expect(result).toHaveLength(3)
87+
expect(result?.every((p) => p.domain_name === undefined)).toBe(true)
88+
})
89+
})

packages/aurora/src/server/Project/routers/projectRouter.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,30 @@ export const projectRouter = {
100100
})
101101
}
102102

103-
const response = await callIdentityAPI(ctx.identityEndpoint, token.authToken, "auth/projects")
103+
const [response, domainsResponse] = await Promise.all([
104+
callIdentityAPI(ctx.identityEndpoint, token.authToken, "auth/projects"),
105+
callIdentityAPI(ctx.identityEndpoint, token.authToken, "auth/domains").catch(() => null),
106+
])
107+
104108
const data = await response.json()
105109
const parsedData = projectsResponseSchema.safeParse(data)
106110

107111
if (!parsedData.success) {
108112
console.error("Zod Parsing Error:", parsedData.error.message)
109113
return undefined
110114
}
111-
return parsedData.data.projects
115+
116+
const domainsData = domainsResponse ? await domainsResponse.json().catch(() => null) : null
117+
const domainsResponseSchema = z.object({ domains: z.array(z.object({ id: z.string(), name: z.string() })) })
118+
const parsedDomains = domainsResponseSchema.safeParse(domainsData)
119+
const domainMap = new Map<string, string>(
120+
(parsedDomains.success ? parsedDomains.data.domains : []).map((d) => [d.id, d.name])
121+
)
122+
123+
return parsedData.data.projects.map((project) => ({
124+
...project,
125+
domain_name: project.domain_id ? (domainMap.get(project.domain_id) ?? undefined) : undefined,
126+
}))
112127
}),
113128

114129
/**

packages/aurora/src/server/Project/types/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const baseProjectSchema = z.object({
1818
})
1919

2020
export const projectSchema = baseProjectSchema.extend({
21+
domain_name: z.string().optional(),
2122
parents: z
2223
.array(
2324
z.object({

0 commit comments

Comments
 (0)