diff --git a/frontend/src/admin/Connectors.test.tsx b/frontend/src/admin/Connectors.test.tsx index b8c9a0e9..cf190634 100644 --- a/frontend/src/admin/Connectors.test.tsx +++ b/frontend/src/admin/Connectors.test.tsx @@ -144,6 +144,41 @@ describe('Admin Connectors', () => { ); }); + it('shows GitHub as ready, with its optional GitHub App settings in the guide', async () => { + const GITHUB = connector({ + key: 'github', + name: 'GitHub', + icon: 'github', + auth_kind: 'api_key', + capabilities: ['sync', 'read'], + configured: true, + required_settings: [], + oauth_settings: [ + { name: 'GITHUB_CLIENT_ID', set: true }, + { name: 'GITHUB_CLIENT_SECRET', set: false }, + { name: 'GITHUB_APP_SLUG', set: false }, + ], + oauth_configured: false, + connection_count: 0, + }); + getAdmin.mockResolvedValue(payload({ connectors: [GITHUB] })); + await render(); + const row = container.querySelector('tbody tr')!; + expect(row.textContent).toContain('Ready'); + expect(row.textContent).toContain('Tokens only'); + const guide = Array.from(row.querySelectorAll('button')).find( + (b) => b.textContent === 'Setup guide', + )!; + await act(async () => guide.click()); + const text = document.body.textContent ?? ''; + expect(text).toContain('Sign in with GitHub'); + expect(text).toContain('GITHUB_APP_SLUG'); + expect(text).toContain( + 'Request user authorization (OAuth) during installation', + ); + expect(text).toContain('https://docs.example/api/connectors/callback'); + }); + it('warns when credentials use the default encryption key', async () => { getAdmin.mockResolvedValue(payload({ default_encryption_key: true })); await render(); diff --git a/frontend/src/admin/Connectors.tsx b/frontend/src/admin/Connectors.tsx index 3549fca9..36c27a52 100644 --- a/frontend/src/admin/Connectors.tsx +++ b/frontend/src/admin/Connectors.tsx @@ -65,6 +65,9 @@ type AdminConnector = { credential_mode: Policy; configured: boolean; required_settings: { name: string; set: boolean }[]; + /** Optional settings that add a second sign-in (GitHub's App). */ + oauth_settings?: { name: string; set: boolean }[]; + oauth_configured?: boolean; connection_count: number; docs_url: string | null; mcp_url: string | null; @@ -88,6 +91,35 @@ const POLICY_LABELS: Record = { const hasTools = (connector: AdminConnector) => connector.capabilities.some((capability) => capability !== 'sync'); +const hasSetupGuide = (connector: AdminConnector) => + connector.required_settings.length > 0 || + (connector.oauth_settings?.length ?? 0) > 0; + +/** Works with pasted tokens, but its optional OAuth sign-in is not set up yet. */ +const tokensOnly = (connector: AdminConnector) => + (connector.oauth_settings?.length ?? 0) > 0 && !connector.oauth_configured; + +function SettingsList({ + settings, +}: { + settings: { name: string; set: boolean }[]; +}) { + return ( + + {settings.map((setting) => ( + {setting.name}} + > + + {setting.set ? 'Set' : 'Missing'} + + + ))} + + ); +} + function CodeRow({ value }: { value: string }) { return ( @@ -108,12 +140,19 @@ function SetupGuide({ redirectUri: string; onClose: () => void; }) { + const oauthSettings = connector.oauth_settings ?? []; + const optionalOAuth = + connector.required_settings.length === 0 && oauthSettings.length > 0; return ( !open && onClose()} title={`Set up ${connector.name}`} - description="Register DocsGPT as an OAuth app with the provider, then set these server settings and restart the API and the worker." + description={ + optionalOAuth + ? `Members can already connect ${connector.name} with their own access tokens. To also offer Sign in with ${connector.name}, register a GitHub App, then set these server settings and restart the API and the worker.` + : 'Register DocsGPT as an OAuth app with the provider, then set these server settings and restart the API and the worker.' + } footer={