mirror of
https://github.com/supabase/supabase.git
synced 2026-06-15 18:17:09 +08:00
## What kind of change does this PR introduce? Feature. Resolves FE-3470. ## What is the current behavior? Organization surfaces have a `G then ,` shortcut to enter org settings, but once inside there is no keyboard navigation, sidebar tooltips, or action shortcuts for the various org pages. | Area | Current behaviour | | --- | --- | | Org Settings sidebar | Routes are click-only once users are inside Settings. | | OAuth Apps | Publish / confirm actions have no keyboard shortcuts. | | Private Apps | Create app has no keyboard shortcut. | | Team | Invite / send actions have no keyboard shortcuts. | | Integrations | Add project connection has no keyboard shortcut. | | Org Projects | New project and search have no keyboard shortcuts. | | Audit Logs | Refresh has no keyboard shortcut. | ## What is the new behavior? Mirrors the Project Settings shortcut pattern (#46352) across all Organization surfaces. | Area | New shortcut coverage | | --- | --- | | Org Settings sidebar | `S then G/C/S/A/P/W/L/D` for General, Security, SSO, OAuth apps, Private apps, Webhooks, Audit logs, Legal documents. Shortcut badge appears on hover in the sidebar. | | Org Settings entry | `G then ,` (remapped from `G then O`) to match the Project Settings chord. | | OAuth Apps | `Shift+N` opens Publish app panel; `Mod+Enter` confirms the open panel. | | Private Apps | `Shift+N` opens Create app sheet (works in both empty-state and list-state). | | Team | `Shift+N` opens Invite members dialog; `Mod+Enter` sends the invitation(s). | | Integrations | `Shift+N` triggers Add project connection when permitted. | | Org Projects | `Shift+N` navigates to new project; `Shift+F` focuses the search input. | | Audit Logs | `Shift+R` refreshes the log list. | ### Implementation notes - Threads `shortcutId` through the `WithSidebar` pipeline (`SidebarLink` → `SubMenuSection` → `ProductMenuGroup`) so tooltip display is automatic — no new rendering logic. - Layout-scoped chords mount only while `OrganizationSettingsLayout` is active, so `S then G` in org settings does not conflict with `S then G` in project settings. - Cheatsheet reference groups promoted to typed constants with readable labels (was: bare strings like `'org-oauth-apps'`). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * System-wide keyboard shortcuts for org areas: project search & new project, private app creation, OAuth app publish/confirm, add GitHub integration, invite members (open/submit), and refresh audit logs. * Sidebar and product menu now show assigned shortcuts for faster navigation; org settings navigation shortcut remapped. * **Tests** * Added coverage for org shortcut registry behavior, sequences, and ordering. * **Chores** * New shortcut reference groups and ordering for improved discoverability. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46356?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ali Waseem <waseema393@gmail.com>
146 lines
4.7 KiB
TypeScript
146 lines
4.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
generateOrganizationSettingsMenuItems,
|
|
generateOrganizationSettingsSections,
|
|
normalizeOrganizationSettingsPath,
|
|
} from './OrganizationSettingsLayout'
|
|
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
|
|
describe('generateOrganizationSettingsMenuItems', () => {
|
|
it('includes webhooks entry for organization settings nav', () => {
|
|
const items = generateOrganizationSettingsMenuItems({
|
|
slug: 'my-org',
|
|
showSecuritySettings: true,
|
|
showSsoSettings: true,
|
|
showLegalDocuments: true,
|
|
})
|
|
|
|
expect(items.some((item) => item.label === 'Webhooks')).toBe(true)
|
|
expect(items.some((item) => item.href === '/org/my-org/webhooks')).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('OrganizationSettingsLayout helpers', () => {
|
|
it('returns expected organization settings sections and links', () => {
|
|
const sections = generateOrganizationSettingsSections({
|
|
slug: 'my-org',
|
|
currentPath: '/org/my-org/general',
|
|
showSecuritySettings: true,
|
|
showSsoSettings: true,
|
|
showLegalDocuments: true,
|
|
})
|
|
|
|
expect(sections.map((section) => section.heading)).toEqual([
|
|
'Configuration',
|
|
'Connections',
|
|
'Compliance',
|
|
])
|
|
expect(sections.flatMap((section) => section.links.map((item) => item.label))).toEqual([
|
|
'General',
|
|
'Security',
|
|
'SSO',
|
|
'OAuth Apps',
|
|
'Webhooks',
|
|
'Audit Logs',
|
|
'Legal Documents',
|
|
])
|
|
expect(
|
|
sections.flatMap((section) => section.links).find((item) => item.label === 'General')
|
|
?.isActive
|
|
).toBe(true)
|
|
})
|
|
|
|
it('hides feature-flagged items when flags are disabled', () => {
|
|
const sections = generateOrganizationSettingsSections({
|
|
slug: 'my-org',
|
|
currentPath: '/org/my-org/general',
|
|
showSecuritySettings: false,
|
|
showSsoSettings: false,
|
|
showLegalDocuments: false,
|
|
})
|
|
|
|
expect(sections.map((section) => section.heading)).toEqual([
|
|
'Configuration',
|
|
'Connections',
|
|
'Compliance',
|
|
])
|
|
expect(sections.flatMap((section) => section.links.map((item) => item.label))).toEqual([
|
|
'General',
|
|
'OAuth Apps',
|
|
'Webhooks',
|
|
'Audit Logs',
|
|
])
|
|
})
|
|
|
|
it('normalizes hash paths for active state checks', () => {
|
|
const currentPath = normalizeOrganizationSettingsPath('/org/my-org/security#sso')
|
|
const sections = generateOrganizationSettingsSections({
|
|
slug: 'my-org',
|
|
currentPath,
|
|
showSecuritySettings: true,
|
|
showSsoSettings: true,
|
|
showLegalDocuments: true,
|
|
})
|
|
|
|
expect(
|
|
sections.flatMap((section) => section.links).find((item) => item.label === 'Security')
|
|
?.isActive
|
|
).toBe(true)
|
|
})
|
|
|
|
it('attaches shortcutId to each settings link', () => {
|
|
const sections = generateOrganizationSettingsSections({
|
|
slug: 'my-org',
|
|
currentPath: '/org/my-org/general',
|
|
showSecuritySettings: true,
|
|
showSsoSettings: true,
|
|
showLegalDocuments: true,
|
|
showPlatformWebhooks: true,
|
|
})
|
|
|
|
const allLinks = sections.flatMap((s) => s.links)
|
|
const linkByKey = (key: string) => allLinks.find((l) => l.key === key)
|
|
|
|
expect(linkByKey('general')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_GENERAL)
|
|
expect(linkByKey('security')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_SECURITY)
|
|
expect(linkByKey('sso')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_SSO)
|
|
expect(linkByKey('apps')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_APPS)
|
|
expect(linkByKey('webhooks')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_WEBHOOKS)
|
|
expect(linkByKey('audit')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_AUDIT)
|
|
expect(linkByKey('documents')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_DOCUMENTS)
|
|
})
|
|
|
|
it('omits feature-flagged links (and their shortcutIds) when flags are off', () => {
|
|
const sections = generateOrganizationSettingsSections({
|
|
slug: 'my-org',
|
|
currentPath: '/org/my-org/general',
|
|
showSecuritySettings: false,
|
|
showSsoSettings: false,
|
|
showLegalDocuments: false,
|
|
})
|
|
|
|
const allLinks = sections.flatMap((s) => s.links)
|
|
const keys = allLinks.map((l) => l.key)
|
|
|
|
expect(keys).not.toContain('security')
|
|
expect(keys).not.toContain('sso')
|
|
expect(keys).not.toContain('documents')
|
|
})
|
|
|
|
it('keeps webhooks nav item active for nested endpoint routes', () => {
|
|
const sections = generateOrganizationSettingsSections({
|
|
slug: 'my-org',
|
|
currentPath: '/org/my-org/webhooks/org-endpoint-1',
|
|
showSecuritySettings: true,
|
|
showSsoSettings: true,
|
|
showLegalDocuments: true,
|
|
})
|
|
|
|
expect(
|
|
sections.flatMap((section) => section.links).find((item) => item.label === 'Webhooks')
|
|
?.isActive
|
|
).toBe(true)
|
|
})
|
|
})
|