Skip to content

Commit f4c55ce

Browse files
Merge branch 'main' into guoda-improve-toolbar
2 parents b6d150a + 13ef69a commit f4c55ce

27 files changed

Lines changed: 919 additions & 252 deletions

.changeset/happy-pigs-think.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudoperators/juno-app-greenhouse": patch
3+
---
4+
5+
Correctly extract organisation name from URL with basePath and add TypeScript types and Zod validation for auth system.

.changeset/little-kiwis-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
3+
---
4+
5+
1368 franz uxdocs signin update

.changeset/many-meteors-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudoperators/juno-app-greenhouse": patch
3+
---
4+
5+
Migrate YamlViewer from @uiw/react-codemirror to native CodeMirror packages

.changeset/stale-onions-know.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
3+
---
4+
5+
feat(docs): add first version of sign-in ux docs page

.changeset/three-ravens-knock.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudoperators/juno-ui-components": patch
3+
---
4+
5+
feat(ui): switch Modal default button sequence

apps/greenhouse/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ These are the customizable application properties (appProps) that you can define
5252
- **demoOrg** (optional): `"demo"`. If the organization name matches this value, the app will use the demo user token for authentication.
5353
- **demoUserToken** (optional): `"token for demo user"`. Used for authentication if `demoOrg` and `demoUserToken` are set, and the organization name matches `demoOrg`.
5454
**Note:** This is ignored if `mockAuth` is set.
55+
- **basePath** (optional, default: `/`):
56+
Specifies the root path under which the application is served. Useful for deploying the app to a subdirectory. If not provided, defaults to the root path `/`.
5557

5658
## Contributing
5759

apps/greenhouse/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"clean:cache": "rm -rf .turbo"
4848
},
4949
"dependencies": {
50+
"@cloudoperators/greenhouse-auth-provider": "workspace:*",
5051
"@cloudoperators/juno-app-doop": "workspace:*",
5152
"@cloudoperators/juno-app-heureka": "workspace:*",
5253
"@cloudoperators/juno-app-supernova": "workspace:*",
@@ -55,12 +56,15 @@
5556
"@cloudoperators/juno-oauth": "workspace:*",
5657
"@cloudoperators/juno-ui-components": "workspace:*",
5758
"@cloudoperators/juno-url-state-provider": "workspace:*",
58-
"@cloudoperators/greenhouse-auth-provider": "workspace:*",
59-
"@codemirror/lang-yaml": "6.1.2",
59+
"@codemirror/lang-yaml": "^6.1.2",
60+
"@codemirror/language": "^6.12.2",
61+
"@codemirror/state": "^6.5.4",
62+
"@codemirror/theme-one-dark": "^6.1.2",
63+
"@codemirror/view": "^6.39.15",
6064
"@tanstack/react-query": "5.90.21",
6165
"@tanstack/react-router": "1.161.3",
62-
"@uiw/react-codemirror": "4.25.4",
6366
"js-yaml": "4.1.1",
64-
"lodash": "4.17.23"
67+
"lodash": "4.17.23",
68+
"zod": "4.3.6"
6569
}
6670
}

apps/greenhouse/src/Shell.tsx

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import styles from "./styles.css?inline"
1515
import StoreProvider, { useGlobalsApiEndpoint } from "./components/StoreProvider"
1616
import { AuthProvider, useAuth } from "./components/AuthProvider"
1717
import { routeTree } from "./routeTree.gen"
18+
import { getRouterBasePath } from "./utils/organizationResolver"
19+
import type { AuthContextValue, MockAuthValue } from "./types/auth"
1820

1921
// Create a new query client instance
2022
const queryClient = new QueryClient()
@@ -43,35 +45,21 @@ export type AppProps = {
4345
authClientId?: string
4446
authIssuerUrl?: string
4547
demoOrg?: string
46-
mockAuth?: boolean
48+
mockAuth?: MockAuthValue
4749
demoUserToken?: string
4850
currentHost?: string
4951
enableHashedRouting?: boolean
52+
basePath?: string
5053
}
5154

52-
const getBasePath = (auth: any) => {
53-
// Determine if org is part of the domain
54-
const currentUrl = new URL(window.location.href)
55-
const organizationIsPartOfDomain = currentUrl.host.match(/^(.+)\.dashboard\..+/)
56-
if (organizationIsPartOfDomain) {
57-
return "/"
58-
}
59-
// If the organization is not part of the domain, extract it from the auth token
60-
const orgString = auth?.data?.raw.groups?.find((g: any) => g.indexOf("organization:") === 0)
61-
return orgString ? orgString.split(":")[1] : undefined
62-
}
63-
64-
const getUser = (auth: unknown) => ({
65-
// @ts-expect-error - auth?.data type needs to be properly defined
66-
organization: auth?.data?.raw?.groups?.find((g: any) => g.startsWith("organization:"))?.split(":")[1] ?? "",
67-
// @ts-expect-error - auth?.data type needs to be properly defined
55+
const getUser = (auth: AuthContextValue) => ({
56+
organization: auth?.data?.raw?.groups?.find((g) => g.startsWith("organization:"))?.split(":")[1] ?? "",
6857
supportGroups: auth?.data?.parsed?.supportGroups ?? [],
6958
})
7059

7160
function App(props: AppProps) {
7261
const auth = useAuth()
7362
const apiEndpoint = useGlobalsApiEndpoint()
74-
// @ts-expect-error - useAuth return type is not properly typed
7563
const token = auth?.data?.JWT
7664
// Create k8s client if apiEndpoint and token are available
7765
// @ts-expect-error - apiEndpoint type needs to be properly typed as string
@@ -85,7 +73,7 @@ function App(props: AppProps) {
8573
* want the app to use browser history.
8674
*/
8775
router.update({
88-
basepath: getBasePath(auth),
76+
basepath: getRouterBasePath(auth?.data?.raw?.groups, props.basePath),
8977
context: { appProps: props, apiClient, user },
9078
stringifySearch: encodeV2,
9179
parseSearch: decodeV2,

apps/greenhouse/src/components/Auth.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { useAuth } from "./AuthProvider"
1919
*
2020
*/
2121
const Auth = ({ children }: any) => {
22-
// @ts-expect-error TS(2339): Property 'isProcessing' does not exist on type 'un... Remove this comment to see the full error message
2322
const { isProcessing: authIsProcessing, loggedIn: authLoggedIn, error: authError, login } = useAuth()
2423

2524
if (authLoggedIn) {

apps/greenhouse/src/components/AuthProvider.test.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,8 @@ describe("AuthProvider", () => {
2323
it("initializes default the oidc session but return error because no issuerUrl or clientId provided", () => {
2424
const wrapper = ({ children }: any) => <AuthProvider options={{}}>{children}</AuthProvider>
2525
const { result } = renderHook(() => useAuth(), { wrapper })
26-
// @ts-expect-error TS(2531): Object is possibly 'null'.
2726
expect(result.current.loggedIn).toBe(false)
28-
// @ts-expect-error TS(2531): Object is possibly 'null'.
2927
expect(result.current.isDemoMode).toBe(false)
30-
// @ts-expect-error TS(2531): Object is possibly 'null'.
3128
expect(result.current.error).not.toBe(null)
3229
})
3330
})
@@ -107,6 +104,52 @@ describe("AuthProvider", () => {
107104
// @ts-expect-error TS(2531): Object is possibly 'null'.
108105
expect(result.current.data.parsed.groups).toEqual(mockAuthData.groups)
109106
})
107+
108+
it("preserves case in JSON string mockAuth data", () => {
109+
const replaceStateSpy = vi.spyOn(window.history, "replaceState").mockImplementation(() => {})
110+
111+
// Test with mixed-case data that should NOT be lowercased
112+
const mockAuthData = {
113+
email: "Jane.Doe@Example.com",
114+
name: "Jane Doe",
115+
groups: ["organization:TestOrg", "team:DevTeam"],
116+
}
117+
const mockAuthDataString = JSON.stringify(mockAuthData)
118+
119+
const wrapper = ({ children }: any) => (
120+
<AuthProvider options={{ mockAuth: mockAuthDataString }}>{children}</AuthProvider>
121+
)
122+
123+
const { result } = renderHook(() => useAuth(), { wrapper })
124+
125+
expect(replaceStateSpy).toHaveBeenCalledTimes(1)
126+
// @ts-expect-error TS(2531): Object is possibly 'null'.
127+
expect(result.current.data.raw.email).toEqual("Jane.Doe@Example.com")
128+
// @ts-expect-error TS(2531): Object is possibly 'null'.
129+
expect(result.current.data.raw.name).toEqual("Jane Doe")
130+
// @ts-expect-error TS(2531): Object is possibly 'null'.
131+
expect(result.current.data.raw.groups).toContain("organization:TestOrg")
132+
})
133+
134+
it("rejects invalid mockAuth values (number, array, etc)", () => {
135+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
136+
137+
// Test with number (invalid)
138+
const wrapper1 = ({ children }: any) => <AuthProvider options={{ mockAuth: 123 }}>{children}</AuthProvider>
139+
const { result: result1 } = renderHook(() => useAuth(), { wrapper: wrapper1 })
140+
expect(result1.current.loggedIn).toBe(false)
141+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid mockAuth value"), 123)
142+
143+
consoleWarnSpy.mockClear()
144+
145+
// Test with array (invalid)
146+
const wrapper2 = ({ children }: any) => <AuthProvider options={{ mockAuth: [1, 2, 3] }}>{children}</AuthProvider>
147+
const { result: result2 } = renderHook(() => useAuth(), { wrapper: wrapper2 })
148+
expect(result2.current.loggedIn).toBe(false)
149+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid mockAuth value"), [1, 2, 3])
150+
151+
consoleWarnSpy.mockRestore()
152+
})
110153
})
111154

112155
describe("initializes demo auth session", () => {

0 commit comments

Comments
 (0)