diff --git a/.changeset/publish-owner-radio-default-org.md b/.changeset/publish-owner-radio-default-org.md new file mode 100644 index 000000000..9680d8349 --- /dev/null +++ b/.changeset/publish-owner-radio-default-org.md @@ -0,0 +1,7 @@ +--- +"@inkeep/open-knowledge": patch +--- + +Publish-to-GitHub: pick the owner with a radio list and default to an org + +The "Publish to GitHub" dialog now renders the eligible owners as a radio list (one card per owner) instead of a dropdown, so every choice is visible at a glance. When you belong to one or more organizations the wizard pre-selects the first org rather than your personal account, since a knowledge base is far more likely meant for the team, and accidentally publishing it under a personal login is annoying to undo. With no org available it falls back to your own account as before. diff --git a/packages/app/src/components/PublishToGitHubDialog.dom.test.tsx b/packages/app/src/components/PublishToGitHubDialog.dom.test.tsx index 3aea73d22..87abe7a4e 100644 --- a/packages/app/src/components/PublishToGitHubDialog.dom.test.tsx +++ b/packages/app/src/components/PublishToGitHubDialog.dom.test.tsx @@ -129,6 +129,8 @@ mock.module('@/lib/share/publish-wizard', () => ({ extractFolderBasename: (path: string) => path.split('/').filter(Boolean).at(-1) ?? '', fetchPublishNameCheck: fetchPublishNameCheckMock, fetchPublishOwners: fetchPublishOwnersMock, + pickDefaultOwner: (owners: SharePublishOwner[]) => + owners.find((o) => o.kind === 'org')?.login ?? owners[0]?.login ?? '', presentPublishError: () => presentError, resolveNameCheckStatus: (_result: unknown, owner: string, name: string) => ({ kind: 'available' as const, @@ -212,7 +214,13 @@ describe('PublishToGitHubDialog runtime behavior', () => { await renderDialog(); expect(screen.getByRole('dialog', { name: 'Publish to GitHub' })).toBeTruthy(); - expect(await screen.findByTestId('publish-owner-trigger')).toBeTruthy(); + expect(await screen.findByTestId('publish-owner-radio')).toBeTruthy(); + expect(screen.getByTestId('publish-owner-option-docs-team').getAttribute('data-state')).toBe( + 'checked', + ); + expect(screen.getByTestId('publish-owner-option-alice').getAttribute('data-state')).toBe( + 'unchecked', + ); expect((screen.getByTestId('publish-name') as HTMLInputElement).value).toBe('my-project'); expect(screen.getByText('Will be created as')).toBeTruthy(); expect(screen.getByText('my-project')).toBeTruthy(); @@ -232,6 +240,37 @@ describe('PublishToGitHubDialog runtime behavior', () => { expect((screen.getByTestId('publish-submit') as HTMLButtonElement).disabled).toBe(false); }); + test('pre-selects the user account when no org is available', async () => { + ownersQueue = [{ ok: true, owners: [{ kind: 'user', login: 'alice' }] }]; + await renderDialog(); + + expect(await screen.findByTestId('publish-owner-radio')).toBeTruthy(); + expect(screen.getByTestId('publish-owner-option-alice').getAttribute('data-state')).toBe( + 'checked', + ); + await waitForAvailableNameCheck(); + await userEvent.click(screen.getByTestId('publish-submit')); + + await waitFor(() => expect(submitCalls).toHaveLength(1)); + expect(submitCalls[0]?.owner).toBe('alice'); + }); + + test('selecting a different owner radio updates the publish payload', async () => { + const { onOpenChange } = await renderDialog(); + await waitForAvailableNameCheck(); + + await userEvent.click(screen.getByTestId('publish-owner-option-alice')); + expect(screen.getByTestId('publish-owner-option-alice').getAttribute('data-state')).toBe( + 'checked', + ); + await waitForAvailableNameCheck(); + await userEvent.click(screen.getByTestId('publish-submit')); + + await waitFor(() => expect(submitCalls).toHaveLength(1)); + expect(submitCalls[0]?.owner).toBe('alice'); + expect(onOpenChange).not.toHaveBeenCalledWith(false); + }); + test('submits through publish helpers, shows success URL, copies from a fresh button click, and Done closes', async () => { const { onOpenChange } = await renderDialog(); await waitForAvailableNameCheck(); @@ -246,7 +285,7 @@ describe('PublishToGitHubDialog runtime behavior', () => { { description: 'Internal docs', name: 'my-project', - owner: 'alice', + owner: 'docs-team', visibility: 'private', }, ]); diff --git a/packages/app/src/components/PublishToGitHubDialog.tsx b/packages/app/src/components/PublishToGitHubDialog.tsx index 400615419..6f71620ca 100644 --- a/packages/app/src/components/PublishToGitHubDialog.tsx +++ b/packages/app/src/components/PublishToGitHubDialog.tsx @@ -24,13 +24,6 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; import { useDocumentContext } from '@/editor/DocumentContext'; import { docNameToMarkdownPath } from '@/lib/doc-paths'; @@ -41,6 +34,7 @@ import { fetchPublishNameCheck, fetchPublishOwners, type NameCheckStatus, + pickDefaultOwner, presentPublishError, resolveNameCheckStatus, sanitizeRepoName, @@ -107,7 +101,7 @@ export function PublishToGitHubDialog({ open, onOpenChange }: PublishToGitHubDia } setOwners(res.owners); if (res.owners.length > 0 && selectedOwner === '') { - setSelectedOwner(res.owners[0]?.login ?? ''); + setSelectedOwner(pickDefaultOwner(res.owners)); } } catch { setOwnersError(t`Couldn't reach GitHub. Try again?`); @@ -133,7 +127,7 @@ export function PublishToGitHubDialog({ open, onOpenChange }: PublishToGitHubDia if (owners === null) { void loadOwners(); } else if (selectedOwner === '' && owners.length > 0) { - setSelectedOwner(owners[0]?.login ?? ''); + setSelectedOwner(pickDefaultOwner(owners)); } }, [open]); @@ -309,7 +303,7 @@ export function PublishToGitHubDialog({ open, onOpenChange }: PublishToGitHubDia
-
@@ -401,7 +396,7 @@ export function PublishToGitHubDialog({ open, onOpenChange }: PublishToGitHubDia
-