-
Notifications
You must be signed in to change notification settings - Fork 66
OSAC-3382: add identity provider list page #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import React, { type ReactNode, createElement } from 'react'; | ||
| import { createRouterTransport } from '@connectrpc/connect'; | ||
| import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
| import { renderHook, waitFor } from '@testing-library/react'; | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| import { IdentityProviderPhase, IdentityProviders } from '@osac/types'; | ||
|
|
||
| import { useIdentityProviders } from './identity-provider'; | ||
| import { ApiProvider } from '../api-context'; | ||
|
|
||
| const makeIdentityProvider = (id: string, title: string, phase?: IdentityProviderPhase) => ({ | ||
| id, | ||
| spec: { title, enabled: true, config: { case: 'oidc' as const, value: {} } }, | ||
| status: phase !== undefined ? { phase, message: '', conditions: [] } : undefined, | ||
| }); | ||
|
|
||
| const makeWrapper = (transport: ReturnType<typeof createRouterTransport>) => { | ||
| const queryClient = new QueryClient({ | ||
| defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, | ||
| }); | ||
| const wrapper = ({ children }: { children: ReactNode }) => | ||
| createElement( | ||
| ApiProvider, | ||
| { transport } as React.ComponentProps<typeof ApiProvider>, | ||
| createElement(QueryClientProvider, { client: queryClient }, children), | ||
| ); | ||
| return { wrapper, queryClient }; | ||
| }; | ||
|
|
||
| describe('useIdentityProviders', () => { | ||
| it('returns identity provider items from the list response', async () => { | ||
| const transport = createRouterTransport((router) => { | ||
| router.service(IdentityProviders, { | ||
| list: () => ({ | ||
| items: [ | ||
| makeIdentityProvider('idp-1', 'Corporate OIDC', IdentityProviderPhase.READY), | ||
| makeIdentityProvider('idp-2', 'GitHub SSO', IdentityProviderPhase.ERROR), | ||
| ], | ||
| size: 2, | ||
| total: 2, | ||
| }), | ||
| }); | ||
| }); | ||
|
|
||
| const { wrapper } = makeWrapper(transport); | ||
| const { result } = renderHook(() => useIdentityProviders(), { wrapper }); | ||
|
|
||
| await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
| expect(result.current.data).toHaveLength(2); | ||
| expect(result.current.data?.[0].id).toBe('idp-1'); | ||
| expect(result.current.data?.[1].id).toBe('idp-2'); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { IdentityProviders } from '@osac/types'; | ||
|
|
||
| import { useApiFetch } from '../api-context'; | ||
| import { type ListParams, apiQueryKey } from '../types'; | ||
| import { useApiQuery } from '../use-api-query'; | ||
|
|
||
| export const useIdentityProviders = (params: ListParams = {}) => { | ||
| const client = useApiFetch(IdentityProviders); | ||
| return useApiQuery({ | ||
| queryKey: apiQueryKey('v1/identity_providers', undefined, params), | ||
| queryFn: () => client.list(params), | ||
| select: (data) => data.items, | ||
|
Comment on lines
+7
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 4 'export type ListParams|useIdentityProviders\s*\(' libs apps
rg -n -C 4 'IdentityProviders.*list|metadata\.tenant|IdentityProvidersList' libsRepository: osac-project/osac-ui Length of output: 17403 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "## Candidate files"
git ls-files | rg 'libs/ui-components/src/(api/v1/identity-provider|api/types|components/IdentityProvider/IdentityProviderListPage|test-utils/createMockConnectTransport).*\.(ts|tsx)$' || true
echo "## identity-provider.ts"
cat -n libs/ui-components/src/api/v1/identity-provider.ts
echo "## api/types relevant"
cat -n libs/ui-components/src/api/types.ts | sed -n '70,115p'
echo "## IdentityProviderListPage"
cat -n libs/ui-components/src/components/IdentityProvider/IdentityProviderListPage.tsx
echo "## createMockConnectTransport relevant"
cat -n libs/ui-components/src/test-utils/createMockConnectTransport.ts | sed -n '190,230p'
echo "## identity-provider tests relevant"
cat -n libs/ui-components/src/api/v1/identity-provider.test.ts | sed -n '1,90p'Repository: osac-project/osac-ui Length of output: 10281 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "## IdentityProviders usage across ui-components"
rg -n -C 3 'IdentityProviders|identityProviders|metadata\.tenant|useActive|activeTenant|useTenant|tenant' libs/ui-components/src --glob '*.ts' --glob '*.tsx' || true
echo "## createMockConnectTransport setup"
cat -n libs/ui-components/src/test-utils/createMockConnectTransport.ts | sed -n '1,140p'
cat -n libs/ui-components/src/test-utils/createMockConnectTransport.ts | sed -n '230,340p'Repository: osac-project/osac-ui Length of output: 50376 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "## IdentityProviderListPage.test.tsx"
cat -n libs/ui-components/src/components/IdentityProvider/IdentityProviderListPage.test.tsx | sed -n '1,120p'
echo "## createMockConnectTransport header/defaults"
cat -n libs/ui-components/src/test-utils/createMockConnectTransport.ts | sed -n '1,90p'Repository: osac-project/osac-ui Length of output: 7376 Preserve tenant scope for identity-provider lists.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import { screen, waitFor } from '@testing-library/react'; | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| import type { IdentityProvider } from '@osac/types'; | ||
| import { IdentityProviderPhase } from '@osac/types'; | ||
|
|
||
| import IdentityProviderListPage from './IdentityProviderListPage'; | ||
| import { renderWithProviders } from '../../test-utils/TestProviders'; | ||
|
|
||
| const makeIdentityProvider = (id: string, title: string, phase?: IdentityProviderPhase) => | ||
| ({ | ||
| id, | ||
| metadata: { | ||
| creationTimestamp: { seconds: BigInt(1717000000), nanos: 0 }, | ||
| }, | ||
| spec: { | ||
| title, | ||
| enabled: true, | ||
| config: { case: 'oidc' as const, value: {} }, | ||
| }, | ||
| status: phase !== undefined ? { phase, message: '', conditions: [] } : undefined, | ||
| }) as IdentityProvider; | ||
|
|
||
| const defaultIdentityProviders = [ | ||
| makeIdentityProvider('idp-1', 'Corporate OIDC', IdentityProviderPhase.READY), | ||
| makeIdentityProvider('idp-2', 'GitHub SSO', IdentityProviderPhase.ERROR), | ||
| ]; | ||
|
|
||
| const renderPage = (identityProviders: IdentityProvider[] = defaultIdentityProviders) => | ||
| renderWithProviders(<IdentityProviderListPage />, { | ||
| apiFixtures: { identityProviders }, | ||
| }); | ||
|
|
||
| describe('IdentityProviderListPage', () => { | ||
| it('renders the page title', async () => { | ||
| renderPage(); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByRole('heading', { name: 'Identity providers' })).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
|
|
||
| it('renders identity provider rows with titles', async () => { | ||
| renderPage(); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByText('Corporate OIDC')).toBeInTheDocument(); | ||
| }); | ||
| expect(screen.getByText('GitHub SSO')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('renders status labels for each identity provider', async () => { | ||
| renderPage(); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByText('Ready')).toBeInTheDocument(); | ||
| }); | ||
| expect(screen.getByText('Error')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('renders type column showing OIDC', async () => { | ||
| renderPage(); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getAllByText('OIDC')).toHaveLength(2); | ||
| }); | ||
| }); | ||
|
|
||
| it('shows empty state when there are no identity providers', async () => { | ||
| renderPage([]); | ||
|
|
||
| await waitFor(() => { | ||
| expect( | ||
| screen.getByText('No identity providers yet. Create one to get started.'), | ||
| ).toBeInTheDocument(); | ||
| }); | ||
| expect(screen.queryByRole('table')).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('shows filtered empty state when search matches nothing', async () => { | ||
| const { user } = renderPage(); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByText('Corporate OIDC')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| const searchInput = screen.getByRole('textbox', { | ||
| name: 'Search identity providers', | ||
| }); | ||
| await user.type(searchInput, 'nonexistent'); | ||
|
|
||
| expect(screen.getByText('No identity providers match your search.')).toBeInTheDocument(); | ||
| expect(screen.queryByText('Corporate OIDC')).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('filters identity providers by title', async () => { | ||
| const { user } = renderPage(); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByText('Corporate OIDC')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| const searchInput = screen.getByRole('textbox', { | ||
| name: 'Search identity providers', | ||
| }); | ||
| await user.type(searchInput, 'Corporate'); | ||
|
|
||
| expect(screen.getByText('Corporate OIDC')).toBeInTheDocument(); | ||
| expect(screen.queryByText('GitHub SSO')).not.toBeInTheDocument(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, new persona should not be added at this stage (avoid complexity at init point)
please use tenant admin