feat(edge-functions): keyboard shortcuts on overview, detail, and test sheet (#45947)

Closes
[FE-3245](https://linear.app/supabase/issue/FE-3245/add-keyboard-shortcuts-to-edge-functions-pages).

Adds keyboard shortcuts across the Edge Functions surface, mirroring the
patterns already in place for Database / Auth / Storage.

## Summary

Three layers of new shortcuts, plus one quality-of-life fix on the
existing search input:

### 1. Edge Functions list page (`/project/:ref/functions`)
| Key | Action |
|---|---|
| `Shift+F` | Focus the search input |
| `Shift+N` | Route to `/functions/new` (deploy a new function) |
| `F` then `C` | Clear search filter |
| `Shift+R` | Refresh the functions list (new toolbar button) |
| `S` then `C` | Reset sort to `name:asc` |
| `Esc` (in search) | Clears value, then blurs on a second press
(`onSearchInputEscape`) |

### 2. Edge Functions section nav (active anywhere under `/functions/*`)
| Key | Action |
|---|---|
| `F` then `O` | Functions overview |
| `F` then `K` | Secrets |

Wired through `EdgeFunctionsProductMenu` items via `shortcutId`,
registered by `<ProductMenuShortcuts />` mounted in
`EdgeFunctionsLayout`.

### 3. Per-function detail (active anywhere under `/functions/:slug/*`)
| Key | Action |
|---|---|
| `1` | Overview |
| `2` | Invocations |
| `3` | Logs |
| `4` | Code |
| `5` | Settings |
| `Shift+T` | Open the Test sheet |
| `Shift+D` | Toggle the Download popover |
| `Shift+C` | Copy the function URL (with toast) |

### 4. Test sheet (active when `EdgeFunctionTesterSheet` is open)
| Key | Action |
|---|---|
| `Mod+Enter` | Send Request — first binding for this; mirrors
`SQL_EDITOR_RUN` semantics |

### 5. New per-function Overview (`edgeFunctionsOverview` flag)
| Key | Action |
|---|---|
| `I` then `M` | 15 min |
| `I` then `H` | 1 hour |
| `I` then `T` | 3 hours |
| `I` then `D` | 1 day |
| `Shift+R` | Refresh combined stats query |
| `O` then `L` | Open Logs (or Invocations if unified-logs preview is
off) |

`ShortcutTooltip` added to the most prominent buttons (search, refresh,
copy URL, download, test, send request). Interval/refresh/open-logs on
the overview are registered without inline tooltips but remain
discoverable via `Cmd+K` and the shortcut reference sheet (`Mod+/`).

## Implementation notes

- New reference group `NAVIGATION_FUNCTION_DETAIL` ("Function Page
Navigation") added to keep the reference sheet grouped sensibly.
- Three new registry files: `functions-list.ts`, `functions-nav.ts`,
`functions-detail.ts`, `functions-detail-nav.ts`,
`functions-overview.ts`.
- Three new hooks: `useFunctionsListShortcuts`,
`useFunctionsDetailShortcuts`, `useEdgeFunctionOverviewShortcuts`.
- `EdgeFunctionsLayout` refactored to share a single
`useGenerateEdgeFunctionsMenu` hook between `<ProductMenu>` and
`<ProductMenuShortcuts>` (matches the AuthLayout / DatabaseLayout
pattern).
- Download popover hoisted to controlled state so `Shift+D` can toggle
it.

## Test plan

### Functions list page
- [x] On `/project/:ref/functions`, press `Shift+F` — search input gains
focus and value is selected
- [x] Type in the search → press `Esc` → value clears (focus retained).
Press `Esc` again → blurs
- [x] Press `Shift+N` → routes to `/functions/new`
- [x] With a non-default sort, press `S` then `C` → sort resets to
`name:asc`. Confirm shortcut is disabled when already at default
- [x] Press `Shift+R` → list refetches; loading indicator appears on the
new Refresh button
- [x] Press `F` then `C` → search clears

### Section nav (anywhere under `/functions/*`)
- [x] From any page under `/functions/*`, press `F` then `O` → navigates
to Functions list
- [x] Press `F` then `K` → navigates to Secrets
- [x] Verify the chord doesn't fire while typing in an input

### Per-function detail (any sub-page)
- [x] On any function detail tab, press `1`/`2`/`3`/`4`/`5` → navigates
to Overview / Invocations / Logs / Code / Settings respectively (digits
2 and 3 only on platform builds)
- [x] Press `Shift+T` → Test sheet opens. Press escape to close
- [x] Press `Shift+D` → Download popover opens; press escape to close
- [x] Press `Shift+C` → URL copied + toast appears
- [x] Hover the URL copy button, Download button, Test button —
`ShortcutTooltip` shows the chord

### Test sheet
- [x] Open the Test sheet (button or `Shift+T`)
- [x] Without focusing anything, press `Mod+Enter` → request fires
- [x] With focus inside the body editor / a header input, press
`Mod+Enter` → request still fires (`Mod+`-keys bypass input guard)
- [x] While `isPending`, `Mod+Enter` is a no-op (shortcut disabled)
- [x] Hover Send Request → tooltip shows `Mod+Enter`

### New overview (with `edgeFunctionsOverview` flag enabled)
- [x] Press `I` then `M` / `H` / `T` / `D` → interval segmented buttons
highlight accordingly and chart re-fetches
- [x] Press `Shift+R` → stats refetch
- [x] Press `O` then `L` → routes to logs (or invocations when
unified-logs preview is off)

### Regression checks
- [x] `Cmd+/` opens the reference sheet and the new "Edge Functions
Navigation" and "Function Page Navigation" groups render
- [x] `Cmd+K` command palette includes the new shortcut entries under
"Shortcuts"
- [x] On the list page, the existing X button on the search still clears
value
- [x] Esc handler does not interfere with closing modals/popovers
elsewhere on the page

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added comprehensive keyboard shortcuts for Edge Functions (navigation,
tab switching, chart intervals, create/refresh, test/send request,
download, copy URL) with visible shortcut hints on relevant buttons and
inputs.

* **Refactor**
* Layouts and product menu updated to surface and wire these shortcuts
across the UI.

* **Tests**
* Shortcut reference tests updated to include Edge Functions groups and
entries.

* **Documentation**
* Shortcut reference sheet labels updated to include Edge Functions
sections.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45947)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
This commit is contained in:
Ali WaseemandDanny White authored and GitHub committed 2026-05-15 07:59:18 -06:00
1 parent 8c6d5036ea
commit bffe49bb1d
17 files changed
+657 -90

No files matched your search

@@ -38,6 +38,7 @@ import * as z from 'zod'
import { HTTP_METHODS } from './EdgeFunctionDetails.constants'
import { ErrorWithStatus, ResponseData } from './EdgeFunctionDetails.types'
import { RoleImpersonationPopover } from '@/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
import { useSessionAccessTokenQuery } from '@/data/auth/session-access-token-query'
import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query'
@@ -53,6 +54,8 @@ import {
RoleImpersonationStateContextProvider,
useGetImpersonatedRoleState,
} from '@/state/role-impersonation-state'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface EdgeFunctionTesterSheetProps {
visible: boolean
@@ -172,6 +175,14 @@ const EdgeFunctionTesterSheetContent = ({ visible, onClose }: EdgeFunctionTester
}
}
useShortcut(
SHORTCUT_IDS.FUNCTION_DETAIL_SUBMIT_TEST,
() => {
form.handleSubmit(onSubmit)()
},
{ enabled: visible && !isPending }
)
const onSubmit = async (values: FormValues) => {
setError(null)
setResponse(null)
@@ -461,26 +472,28 @@ const EdgeFunctionTesterSheetContent = ({ visible, onClose }: EdgeFunctionTester
disallowAuthenticatedOption
header="Run edge function as a role"
/>
<Button
type="primary"
htmlType="submit"
loading={isPending}
disabled={isPending}
onClick={() =>
sendEvent({
action: 'edge_function_test_send_button_clicked',
properties: {
httpMethod: method,
},
groups: {
project: projectRef ?? 'Unknown',
organization: org?.slug ?? 'Unknown',
},
})
}
>
Send Request
</Button>
<ShortcutTooltip shortcutId={SHORTCUT_IDS.FUNCTION_DETAIL_SUBMIT_TEST} side="top">
<Button
type="primary"
htmlType="submit"
loading={isPending}
disabled={isPending}
onClick={() =>
sendEvent({
action: 'edge_function_test_send_button_clicked',
properties: {
httpMethod: method,
},
groups: {
project: projectRef ?? 'Unknown',
organization: org?.slug ?? 'Unknown',
},
})
}
>
Send Request
</Button>
</ShortcutTooltip>
</div>
</SheetFooter>
</form>
@@ -20,6 +20,7 @@ import type { EdgeFunctionChartRawDatum } from './EdgeFunctionOverview.utils'
import { EdgeFunctionPerformanceSection } from './EdgeFunctionPerformanceSection'
import { EdgeFunctionRecentErrors } from './EdgeFunctionRecentErrors'
import { EdgeFunctionUsageSection } from './EdgeFunctionUsageSection'
import { useEdgeFunctionOverviewShortcuts } from './useEdgeFunctionOverviewShortcuts'
import { useUnifiedLogsPreview } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import NoPermission from '@/components/ui/NoPermission'
import {
@@ -153,6 +154,20 @@ export const EdgeFunctionOverview = () => {
[functionSlug, isUnifiedLogsEnabled, projectRef]
)
useEdgeFunctionOverviewShortcuts({
onSetInterval: setInterval,
onRefresh: () => {
combinedStatsResults.refetch()
},
onOpenLogs: () => {
router.push(
`/project/${projectRef}/functions/${functionSlug}/${
isUnifiedLogsEnabled ? 'logs' : 'invocations'
}`
)
},
})
const { isLoading: permissionsLoading, can: canReadFunction } = useAsyncCheckPermissions(
PermissionAction.FUNCTIONS_READ,
functionSlug as string
@@ -0,0 +1,30 @@
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface UseEdgeFunctionOverviewShortcutsParams {
onSetInterval: (key: string) => void
onRefresh: () => void
onOpenLogs: () => void
}
/**
* Shortcuts scoped to the per-function Overview tab (the new flag-gated UI).
*
* - I+M / I+H / I+T / I+D: select chart interval
* - Shift+R: refresh the combined stats query
* - O+L: jump to the Logs (or Invocations) sub-page
*
* Should be mounted once inside `EdgeFunctionOverview`.
*/
export function useEdgeFunctionOverviewShortcuts({
onSetInterval,
onRefresh,
onOpenLogs,
}: UseEdgeFunctionOverviewShortcutsParams) {
useShortcut(SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_15MIN, () => onSetInterval('15min'))
useShortcut(SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_1HR, () => onSetInterval('1hr'))
useShortcut(SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_3HR, () => onSetInterval('3hr'))
useShortcut(SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_1DAY, () => onSetInterval('1day'))
useShortcut(SHORTCUT_IDS.FUNCTION_OVERVIEW_REFRESH, onRefresh)
useShortcut(SHORTCUT_IDS.FUNCTION_OVERVIEW_OPEN_LOGS, onOpenLogs)
}
@@ -0,0 +1,64 @@
import { useRouter } from 'next/router'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface UseFunctionsDetailShortcutsParams {
projectRef: string | undefined
functionSlug: string | undefined
isPlatform: boolean
onOpenTest: () => void
onOpenDownload: () => void
onCopyUrl: () => void
}
/**
* Registers shortcuts that apply across every tab of a single edge function:
* - 1..5: jump between Overview / Invocations / Logs / Code / Settings
* - Shift+T: open the test sheet
* - Shift+D: open the download popover
* - Shift+C: copy the function URL
*
* Should be mounted once at the EdgeFunctionDetailsLayout level.
*/
export function useFunctionsDetailShortcuts({
projectRef,
functionSlug,
isPlatform,
onOpenTest,
onOpenDownload,
onCopyUrl,
}: UseFunctionsDetailShortcutsParams) {
const router = useRouter()
const ready = Boolean(projectRef && functionSlug)
const base = `/project/${projectRef}/functions/${functionSlug}`
useShortcut(SHORTCUT_IDS.NAV_FUNCTION_DETAIL_OVERVIEW, () => router.push(base), {
enabled: ready && isPlatform,
})
useShortcut(
SHORTCUT_IDS.NAV_FUNCTION_DETAIL_INVOCATIONS,
() => router.push(`${base}/invocations`),
{ enabled: ready && isPlatform }
)
useShortcut(SHORTCUT_IDS.NAV_FUNCTION_DETAIL_LOGS, () => router.push(`${base}/logs`), {
enabled: ready && isPlatform,
})
useShortcut(SHORTCUT_IDS.NAV_FUNCTION_DETAIL_CODE, () => router.push(`${base}/code`), {
enabled: ready,
})
useShortcut(SHORTCUT_IDS.NAV_FUNCTION_DETAIL_SETTINGS, () => router.push(`${base}/details`), {
enabled: ready,
})
useShortcut(SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_TEST, onOpenTest, { enabled: ready })
useShortcut(SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_DOWNLOAD, onOpenDownload, { enabled: ready })
useShortcut(SHORTCUT_IDS.FUNCTION_DETAIL_COPY_URL, onCopyUrl, { enabled: ready })
}
@@ -0,0 +1,48 @@
import { Dispatch, RefObject, SetStateAction } from 'react'
import type { EdgeFunctionsSort } from '@/components/interfaces/EdgeFunctions/EdgeFunctionsSortDropdown'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
const DEFAULT_SORT: EdgeFunctionsSort = 'name:asc'
interface UseFunctionsListShortcutsParams {
searchInputRef: RefObject<HTMLInputElement>
setSearch: Dispatch<SetStateAction<string>> | ((value: string) => void)
sort: EdgeFunctionsSort
setSort: (value: EdgeFunctionsSort) => void
onCreateNew: () => void
onRefresh: () => void
}
export function useFunctionsListShortcuts({
searchInputRef,
setSearch,
sort,
setSort,
onCreateNew,
onRefresh,
}: UseFunctionsListShortcutsParams) {
useShortcut(
SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH,
() => {
searchInputRef.current?.focus()
searchInputRef.current?.select()
},
{ label: 'Search functions' }
)
useShortcut(SHORTCUT_IDS.LIST_PAGE_NEW_ITEM, onCreateNew, {
label: 'Deploy a new function',
})
useShortcut(SHORTCUT_IDS.LIST_PAGE_RESET_FILTERS, () => {
setSearch('')
})
useShortcut(SHORTCUT_IDS.FUNCTIONS_LIST_REFRESH, onRefresh)
useShortcut(SHORTCUT_IDS.FUNCTIONS_LIST_CLEAR_SORT, () => setSort(DEFAULT_SORT), {
enabled: sort !== DEFAULT_SORT,
})
}
@@ -14,6 +14,7 @@ import {
BreadcrumbList_Shadcn_ as BreadcrumbList,
BreadcrumbSeparator_Shadcn_ as BreadcrumbSeparator,
Button,
copyToClipboard,
HoverCard,
HoverCardContent,
HoverCardTrigger,
@@ -40,9 +41,11 @@ import {
import { ProjectLayout } from '../ProjectLayout'
import EdgeFunctionsLayout from './EdgeFunctionsLayout'
import { EdgeFunctionTesterSheet } from '@/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet'
import { useFunctionsDetailShortcuts } from '@/components/interfaces/Functions/useFunctionsDetailShortcuts'
import CopyButton from '@/components/ui/CopyButton'
import { DocsButton } from '@/components/ui/DocsButton'
import NoPermission from '@/components/ui/NoPermission'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useEdgeFunctionBodyQuery } from '@/data/edge-functions/edge-function-body-query'
import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query'
@@ -51,6 +54,7 @@ import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { withAuth } from '@/hooks/misc/withAuth'
import { DOCS_URL } from '@/lib/constants'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
dayjs.extend(relativeTime)
@@ -73,6 +77,7 @@ const EdgeFunctionDetailsLayout = ({
)
const [isOpen, setIsOpen] = useState(false)
const [isDownloadOpen, setIsDownloadOpen] = useState(false)
const [isTimestampHoverCardOpen, setIsTimestampHoverCardOpen] = useState(false)
const {
@@ -235,6 +240,35 @@ const EdgeFunctionDetailsLayout = ({
}
}, [isError])
const openTestSheet = () => {
if (!functionSlug) return
setIsOpen(true)
if (IS_PLATFORM) {
sendEvent({
action: 'edge_function_test_side_panel_opened',
groups: {
project: ref ?? 'Unknown',
organization: org?.slug ?? 'Unknown',
},
})
}
}
const copyFunctionUrl = () => {
if (!functionUrl) return
copyToClipboard(functionUrl)
toast.success('Function URL copied to clipboard')
}
useFunctionsDetailShortcuts({
projectRef: ref,
functionSlug,
isPlatform: IS_PLATFORM,
onOpenTest: openTestSheet,
onOpenDownload: () => setIsDownloadOpen((prev) => !prev),
onCopyUrl: copyFunctionUrl,
})
if (!isLoading && !canReadFunctions) {
return (
<ProjectLayout product="Edge Functions" browserTitle={browserTitle}>
@@ -274,7 +308,9 @@ const EdgeFunctionDetailsLayout = ({
<PageHeaderDescription className="flex flex-row flex-wrap items-center gap-x-4 gap-y-1 text-sm!">
<div className="flex items-center gap-x-2">
<span className="flex items-center gap-2">{functionUrl}</span>
<CopyButton iconOnly type="text" text={functionUrl} />
<ShortcutTooltip shortcutId={SHORTCUT_IDS.FUNCTION_DETAIL_COPY_URL} side="bottom">
<CopyButton iconOnly type="text" text={functionUrl} />
</ShortcutTooltip>
</div>
<HoverCard
@@ -330,12 +366,18 @@ const EdgeFunctionDetailsLayout = ({
<PageHeaderAside>
<div className="flex items-center space-x-2">
<DocsButton href={`${DOCS_URL}/guides/functions`} />
<Popover>
<PopoverTrigger asChild>
<Button type="default" icon={<Download />}>
Download
</Button>
</PopoverTrigger>
<Popover open={isDownloadOpen} onOpenChange={setIsDownloadOpen}>
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_DOWNLOAD}
side="bottom"
open={isDownloadOpen ? false : undefined}
>
<PopoverTrigger asChild>
<Button type="default" icon={<Download />}>
Download
</Button>
</PopoverTrigger>
</ShortcutTooltip>
<PopoverContent align="end" className="p-0">
{IS_PLATFORM && (
<>
@@ -366,24 +408,14 @@ const EdgeFunctionDetailsLayout = ({
</PopoverContent>
</Popover>
{!!functionSlug && (
<Button
type="default"
icon={<Send />}
onClick={() => {
setIsOpen(true)
if (IS_PLATFORM) {
sendEvent({
action: 'edge_function_test_side_panel_opened',
groups: {
project: ref ?? 'Unknown',
organization: org?.slug ?? 'Unknown',
},
})
}
}}
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_TEST}
side="bottom"
>
Test
</Button>
<Button type="default" icon={<Send />} onClick={openTestSheet}>
Test
</Button>
</ShortcutTooltip>
)}
</div>
</PageHeaderAside>
@@ -1,38 +1,50 @@
import { useParams } from 'common'
import { useRouter } from 'next/router'
import type { ComponentProps, PropsWithChildren } from 'react'
import { useMemo, type ComponentProps, type PropsWithChildren } from 'react'
import { ProjectLayout } from '../ProjectLayout'
import { ProductMenu } from '@/components/ui/ProductMenu'
import type { ProductMenuGroup } from '@/components/ui/ProductMenu/ProductMenu.types'
import { ProductMenuShortcuts } from '@/components/ui/ProductMenu/ProductMenuShortcuts'
import { withAuth } from '@/hooks/misc/withAuth'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
const useGenerateEdgeFunctionsMenu = (): ProductMenuGroup[] => {
const { ref: projectRef = 'default' } = useParams()
return useMemo(
() => [
{
title: 'Manage',
items: [
{
name: 'Functions',
key: 'main',
pages: ['', '[functionSlug]', 'new'],
url: `/project/${projectRef}/functions`,
items: [],
shortcutId: SHORTCUT_IDS.NAV_FUNCTIONS_OVERVIEW,
},
{
name: 'Secrets',
key: 'secrets',
url: `/project/${projectRef}/functions/secrets`,
items: [],
shortcutId: SHORTCUT_IDS.NAV_FUNCTIONS_SECRETS,
},
],
},
],
[projectRef]
)
}
export const EdgeFunctionsProductMenu = () => {
const { ref: projectRef = 'default' } = useParams()
const router = useRouter()
const page = router.pathname.split('/')[4]
const menu = useGenerateEdgeFunctionsMenu()
const menuItems = [
{
title: 'Manage',
items: [
{
name: 'Functions',
key: 'main',
pages: ['', '[functionSlug]', 'new'],
url: `/project/${projectRef}/functions`,
items: [],
},
{
name: 'Secrets',
key: 'secrets',
url: `/project/${projectRef}/functions/secrets`,
items: [],
},
],
},
]
return <ProductMenu page={page} menu={menuItems} />
return <ProductMenu page={page} menu={menu} />
}
interface EdgeFunctionsLayoutProps {
@@ -45,6 +57,8 @@ const EdgeFunctionsLayout = ({
title,
browserTitle,
}: PropsWithChildren<EdgeFunctionsLayoutProps>) => {
const menu = useGenerateEdgeFunctionsMenu()
return (
<ProjectLayout
product="Edge Functions"
@@ -52,6 +66,7 @@ const EdgeFunctionsLayout = ({
productMenu={<EdgeFunctionsProductMenu />}
isBlocking={false}
>
<ProductMenuShortcuts menu={menu} />
{children}
</ProjectLayout>
)
@@ -37,9 +37,17 @@ const ACTIVE_AUTH_SHORTCUT_IDS = [
SHORTCUT_IDS.NAV_AUTH_USERS,
] satisfies ShortcutId[]
const ACTIVE_FUNCTION_DETAIL_NAV_SHORTCUT_IDS = [
...ACTIVE_SHORTCUT_IDS,
SHORTCUT_IDS.NAV_FUNCTION_DETAIL_OVERVIEW,
] satisfies ShortcutId[]
const ACTIVE_SURFACE_SHORTCUT_IDS = [
...ACTIVE_SHORTCUT_IDS,
SHORTCUT_IDS.AUTH_USERS_REFRESH,
SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_TEST,
SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_15MIN,
SHORTCUT_IDS.FUNCTIONS_LIST_REFRESH,
SHORTCUT_IDS.SQL_EDITOR_FORMAT,
SHORTCUT_IDS.STORAGE_BUCKETS_REFRESH,
SHORTCUT_IDS.STORAGE_EXPLORER_REFRESH,
@@ -150,14 +158,29 @@ describe('ShortcutsReferenceSheet', () => {
expect(screen.getByText('Go to Users')).toBeInTheDocument()
})
it('shows the edge function tabs section when function tab shortcuts are active', async () => {
renderShortcutsReferenceSheet(ACTIVE_FUNCTION_DETAIL_NAV_SHORTCUT_IDS)
expect(await screen.findByText('Global Navigation')).toBeInTheDocument()
expect(screen.getByText('Edge Function Tabs')).toBeInTheDocument()
expect(screen.queryByText('Edge Function Page Navigation')).not.toBeInTheDocument()
expect(screen.getByText('Go to Overview')).toBeInTheDocument()
})
it('uses human labels for active surface shortcut groups', async () => {
renderShortcutsReferenceSheet(ACTIVE_SURFACE_SHORTCUT_IDS)
expect(await screen.findByText('Auth Users')).toBeInTheDocument()
expect(screen.getByText('Edge Function Actions')).toBeInTheDocument()
expect(screen.getByText('Edge Function Overview')).toBeInTheDocument()
expect(screen.getByText('Edge Functions')).toBeInTheDocument()
expect(screen.getByText('SQL Editor')).toBeInTheDocument()
expect(screen.getByText('Storage Buckets')).toBeInTheDocument()
expect(screen.getByText('Storage File Explorer')).toBeInTheDocument()
expect(screen.queryByText('auth-users')).not.toBeInTheDocument()
expect(screen.queryByText('functions-detail')).not.toBeInTheDocument()
expect(screen.queryByText('functions-list')).not.toBeInTheDocument()
expect(screen.queryByText('functions-overview')).not.toBeInTheDocument()
expect(screen.queryByText('sql-editor')).not.toBeInTheDocument()
expect(screen.queryByText('storage-buckets')).not.toBeInTheDocument()
expect(screen.queryByText('storage-explorer')).not.toBeInTheDocument()
@@ -46,6 +46,9 @@ const GROUP_LABELS: Record<string, string> = {
'auth-users': 'Auth Users',
'command-menu': 'Command Menu',
'data-table': 'Data Tables',
'functions-detail': 'Edge Function Actions',
'functions-list': 'Edge Functions',
'functions-overview': 'Edge Function Overview',
'inline-editor': 'Inline Editor',
'list-page': 'List pages',
nav: 'Navigation',
@@ -1,7 +1,8 @@
import { useFlag, useParams } from 'common'
import { ExternalLink, Search, X } from 'lucide-react'
import { ExternalLink, RefreshCw, Search, X } from 'lucide-react'
import { useRouter } from 'next/router'
import { parseAsString, parseAsStringLiteral, useQueryState } from 'nuqs'
import React, { useMemo } from 'react'
import React, { useMemo, useRef } from 'react'
import { Button, Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
import { Admonition } from 'ui-patterns'
import { Input } from 'ui-patterns/DataInputs/Input'
@@ -31,18 +32,25 @@ import {
FunctionsInstructionsLocal,
} from '@/components/interfaces/Functions/FunctionsEmptyState'
import { TerminalInstructionsDialog } from '@/components/interfaces/Functions/TerminalInstructionsDialog'
import { useFunctionsListShortcuts } from '@/components/interfaces/Functions/useFunctionsListShortcuts'
import DefaultLayout from '@/components/layouts/DefaultLayout'
import EdgeFunctionsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionsLayout'
import AlertError from '@/components/ui/AlertError'
import { DocsButton } from '@/components/ui/DocsButton'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
import { DOCS_URL, IS_PLATFORM } from '@/lib/constants'
import { onSearchInputEscape } from '@/lib/keyboard'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import type { NextPageWithLayout } from '@/types'
const EdgeFunctionsPage: NextPageWithLayout = () => {
const router = useRouter()
const { ref } = useParams()
const showLastHourStats = useFlag('edgeFunctionsRequestMetrics')
const searchInputRef = useRef<HTMLInputElement>(null)
const [search, setSearch] = useQueryState('search', parseAsString.withDefault(''))
const [sort, setSortQueryParam] = useQueryState(
'sort',
@@ -55,8 +63,21 @@ const EdgeFunctionsPage: NextPageWithLayout = () => {
isPending: isLoading,
isError,
isSuccess,
isFetching,
refetch,
} = useEdgeFunctionsQuery({ projectRef: ref })
useFunctionsListShortcuts({
searchInputRef,
setSearch,
sort,
setSort: setSortQueryParam,
onCreateNew: () => router.push(`/project/${ref}/functions/new`),
onRefresh: () => {
refetch()
},
})
const filteredFunctions = useMemo(() => {
const temp = (functions ?? []).filter((x) =>
x.name.toLowerCase().includes(search.toLowerCase())
@@ -106,31 +127,52 @@ const EdgeFunctionsPage: NextPageWithLayout = () => {
<div className="flex items-center gap-2">
<div className="flex items-center gap-2">
<div className="relative">
<Input
placeholder="Search function names"
icon={<Search />}
size="tiny"
className="w-32 md:w-64"
value={search}
onChange={(event) => setSearch(event.target.value)}
actions={[
search && (
<Button
key="clear"
size="tiny"
type="text"
icon={<X />}
onClick={() => setSearch('')}
className="p-0 h-5 w-5"
/>
),
]}
/>
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH}
label="Search functions"
side="bottom"
>
<Input
ref={searchInputRef}
placeholder="Search function names"
icon={<Search />}
size="tiny"
className="w-32 md:w-64"
value={search}
onChange={(event) => setSearch(event.target.value)}
onKeyDown={onSearchInputEscape(search, setSearch)}
actions={[
search && (
<Button
key="clear"
size="tiny"
type="text"
icon={<X />}
onClick={() => setSearch('')}
className="p-0 h-5 w-5"
/>
),
]}
/>
</ShortcutTooltip>
</div>
</div>
<div className="flex items-center gap-2">
<EdgeFunctionsSortDropdown value={sort} onChange={setSortQueryParam} />
</div>
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.FUNCTIONS_LIST_REFRESH}
side="bottom"
>
<Button
type="default"
icon={<RefreshCw />}
loading={isFetching}
onClick={() => refetch()}
>
Refresh
</Button>
</ShortcutTooltip>
<span className="border-l border-default pl-2 text-xs text-foreground-light">
{search && filteredFunctions.length !== functions.length
? `Viewing ${filteredFunctions.length} of ${functions.length} functions in total`
@@ -3,6 +3,8 @@ export const SHORTCUT_REFERENCE_GROUPS = {
NAVIGATION_DATABASE: 'navigation.database',
NAVIGATION_AUTH: 'navigation.auth',
NAVIGATION_STORAGE: 'navigation.storage',
NAVIGATION_FUNCTIONS: 'navigation.functions',
NAVIGATION_FUNCTION_DETAIL: 'navigation.function-detail',
} as const
export const SHORTCUT_REFERENCE_GROUP_LABELS: Record<string, string> = {
@@ -10,6 +12,8 @@ export const SHORTCUT_REFERENCE_GROUP_LABELS: Record<string, string> = {
[SHORTCUT_REFERENCE_GROUPS.NAVIGATION_DATABASE]: 'Database Navigation',
[SHORTCUT_REFERENCE_GROUPS.NAVIGATION_AUTH]: 'Auth Navigation',
[SHORTCUT_REFERENCE_GROUPS.NAVIGATION_STORAGE]: 'Storage Navigation',
[SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTIONS]: 'Edge Functions Navigation',
[SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTION_DETAIL]: 'Edge Function Tabs',
}
export const SHORTCUT_REFERENCE_GROUP_ORDER = [
@@ -22,6 +26,11 @@ export const SHORTCUT_REFERENCE_GROUP_ORDER = [
SHORTCUT_REFERENCE_GROUPS.NAVIGATION_STORAGE,
'storage-buckets',
'storage-explorer',
SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTIONS,
'functions-list',
SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTION_DETAIL,
'functions-detail',
'functions-overview',
'nav',
'ai-assistant',
'inline-editor',
+33
View File
@@ -2,6 +2,17 @@ import { SHORTCUT_REFERENCE_GROUPS } from './referenceGroups'
import { AUTH_NAV_SHORTCUT_IDS, authNavRegistry } from './registry/auth-nav'
import { AUTH_USERS_SHORTCUT_IDS, authUsersRegistry } from './registry/auth-users'
import { DATABASE_NAV_SHORTCUT_IDS, databaseNavRegistry } from './registry/database-nav'
import { FUNCTIONS_DETAIL_SHORTCUT_IDS, functionsDetailRegistry } from './registry/functions-detail'
import {
FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS,
functionsDetailNavRegistry,
} from './registry/functions-detail-nav'
import { FUNCTIONS_LIST_SHORTCUT_IDS, functionsListRegistry } from './registry/functions-list'
import { FUNCTIONS_NAV_SHORTCUT_IDS, functionsNavRegistry } from './registry/functions-nav'
import {
FUNCTIONS_OVERVIEW_SHORTCUT_IDS,
functionsOverviewRegistry,
} from './registry/functions-overview'
import { LIST_PAGE_SHORTCUT_IDS, listPageRegistry } from './registry/list-page'
import {
SCHEMA_VISUALIZER_SHORTCUT_IDS,
@@ -86,6 +97,17 @@ export const SHORTCUT_IDS = {
...STORAGE_BUCKETS_SHORTCUT_IDS,
// Storage Explorer (file browser) shortcuts
...STORAGE_EXPLORER_SHORTCUT_IDS,
// Edge Functions sub-page navigation chords
...FUNCTIONS_NAV_SHORTCUT_IDS,
// Edge Functions overview (list) page shortcuts
...FUNCTIONS_LIST_SHORTCUT_IDS,
// Per-function detail layout shortcuts (header actions + test submit)
...FUNCTIONS_DETAIL_SHORTCUT_IDS,
// Per-function detail tab navigation (digits)
...FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS,
// Per-function Overview tab shortcuts (intervals, refresh, open logs)
...FUNCTIONS_OVERVIEW_SHORTCUT_IDS,
} as const
/**
@@ -377,4 +399,15 @@ export const SHORTCUT_DEFINITIONS: Record<ShortcutId, ShortcutDefinition> = {
...storageBucketsRegistry,
// Storage Explorer (file browser) shortcut registration
...storageExplorerRegistry,
// Edge Functions sub-page navigation chord registration
...functionsNavRegistry,
// Edge Functions overview (list) page shortcut registration
...functionsListRegistry,
// Per-function detail layout shortcut registration
...functionsDetailRegistry,
// Per-function detail tab navigation registration
...functionsDetailNavRegistry,
// Per-function Overview tab shortcut registration
...functionsOverviewRegistry,
}
@@ -0,0 +1,59 @@
import { SHORTCUT_REFERENCE_GROUPS } from '../referenceGroups'
import { RegistryDefinations } from '../types'
/**
* Digit shortcuts (1-5) for jumping between the tabs on a function detail
* page — Overview, Invocations, Logs, Code, Settings.
*
* Active only while EdgeFunctionDetailsLayout is mounted (i.e. the user is
* on a `/project/<ref>/functions/<slug>/*` route). Digits are scoped to this
* layout so they don't burn global keys.
*/
export const FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS = {
NAV_FUNCTION_DETAIL_OVERVIEW: 'nav.function-detail-overview',
NAV_FUNCTION_DETAIL_INVOCATIONS: 'nav.function-detail-invocations',
NAV_FUNCTION_DETAIL_LOGS: 'nav.function-detail-logs',
NAV_FUNCTION_DETAIL_CODE: 'nav.function-detail-code',
NAV_FUNCTION_DETAIL_SETTINGS: 'nav.function-detail-settings',
}
export type FunctionsDetailNavShortcutId =
(typeof FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS)[keyof typeof FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS]
export const functionsDetailNavRegistry: RegistryDefinations<FunctionsDetailNavShortcutId> = {
[FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_OVERVIEW]: {
id: FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_OVERVIEW,
label: 'Go to Overview',
sequence: ['1'],
showInSettings: false,
referenceGroup: SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTION_DETAIL,
},
[FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_INVOCATIONS]: {
id: FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_INVOCATIONS,
label: 'Go to Invocations',
sequence: ['2'],
showInSettings: false,
referenceGroup: SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTION_DETAIL,
},
[FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_LOGS]: {
id: FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_LOGS,
label: 'Go to Logs',
sequence: ['3'],
showInSettings: false,
referenceGroup: SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTION_DETAIL,
},
[FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_CODE]: {
id: FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_CODE,
label: 'Go to Code',
sequence: ['4'],
showInSettings: false,
referenceGroup: SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTION_DETAIL,
},
[FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_SETTINGS]: {
id: FUNCTIONS_DETAIL_NAV_SHORTCUT_IDS.NAV_FUNCTION_DETAIL_SETTINGS,
label: 'Go to Settings',
sequence: ['5'],
showInSettings: false,
referenceGroup: SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTION_DETAIL,
},
}
@@ -0,0 +1,49 @@
import { RegistryDefinations } from '../types'
/**
* Shortcuts scoped to a single edge function's detail layout — active anywhere
* under `/project/<ref>/functions/<slug>/*`.
*
* The test sheet submit is registered here too, since it conceptually belongs
* with the test-related actions even though it's only listened to while the
* sheet is open.
*/
export const FUNCTIONS_DETAIL_SHORTCUT_IDS = {
FUNCTION_DETAIL_OPEN_TEST: 'functions-detail.open-test',
FUNCTION_DETAIL_OPEN_DOWNLOAD: 'functions-detail.open-download',
FUNCTION_DETAIL_COPY_URL: 'functions-detail.copy-url',
FUNCTION_DETAIL_SUBMIT_TEST: 'functions-detail.submit-test',
}
export type FunctionsDetailShortcutId =
(typeof FUNCTIONS_DETAIL_SHORTCUT_IDS)[keyof typeof FUNCTIONS_DETAIL_SHORTCUT_IDS]
export const functionsDetailRegistry: RegistryDefinations<FunctionsDetailShortcutId> = {
[FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_TEST]: {
id: FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_TEST,
label: 'Open test sheet',
sequence: ['Shift+T'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_DOWNLOAD]: {
id: FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_OPEN_DOWNLOAD,
label: 'Open download menu',
sequence: ['Shift+D'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_COPY_URL]: {
id: FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_COPY_URL,
label: 'Copy function URL',
sequence: ['Shift+C'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_SUBMIT_TEST]: {
id: FUNCTIONS_DETAIL_SHORTCUT_IDS.FUNCTION_DETAIL_SUBMIT_TEST,
label: 'Send test request',
sequence: ['Mod+Enter'],
showInSettings: false,
},
}
@@ -0,0 +1,33 @@
import { RegistryDefinations } from '../types'
/**
* Shortcuts scoped to the Edge Functions overview (list) page.
*
* The shared list-page shortcuts (focus search, create new item, reset
* filters) are reused directly from `list-page.ts`. Only functions-specific
* actions live here.
*/
export const FUNCTIONS_LIST_SHORTCUT_IDS = {
FUNCTIONS_LIST_REFRESH: 'functions-list.refresh',
FUNCTIONS_LIST_CLEAR_SORT: 'functions-list.clear-sort',
}
export type FunctionsListShortcutId =
(typeof FUNCTIONS_LIST_SHORTCUT_IDS)[keyof typeof FUNCTIONS_LIST_SHORTCUT_IDS]
export const functionsListRegistry: RegistryDefinations<FunctionsListShortcutId> = {
[FUNCTIONS_LIST_SHORTCUT_IDS.FUNCTIONS_LIST_REFRESH]: {
id: FUNCTIONS_LIST_SHORTCUT_IDS.FUNCTIONS_LIST_REFRESH,
label: 'Refresh functions',
sequence: ['Shift+R'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_LIST_SHORTCUT_IDS.FUNCTIONS_LIST_CLEAR_SORT]: {
id: FUNCTIONS_LIST_SHORTCUT_IDS.FUNCTIONS_LIST_CLEAR_SORT,
label: 'Clear sort',
sequence: ['S', 'C'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
}
@@ -0,0 +1,35 @@
import { SHORTCUT_REFERENCE_GROUPS } from '../referenceGroups'
import { RegistryDefinations } from '../types'
/**
* Contextual chords for jumping between Edge Functions sub-pages — `F + <letter>`.
*
* Active only while EdgeFunctionsLayout is mounted (i.e. the user is somewhere
* under `/project/<ref>/functions/*`). Mirrors the database-nav / auth-nav /
* storage-nav pattern: the leading `F` is layout-scoped so it doesn't burn a
* global key.
*/
export const FUNCTIONS_NAV_SHORTCUT_IDS = {
NAV_FUNCTIONS_OVERVIEW: 'nav.functions-overview',
NAV_FUNCTIONS_SECRETS: 'nav.functions-secrets',
}
export type FunctionsNavShortcutId =
(typeof FUNCTIONS_NAV_SHORTCUT_IDS)[keyof typeof FUNCTIONS_NAV_SHORTCUT_IDS]
export const functionsNavRegistry: RegistryDefinations<FunctionsNavShortcutId> = {
[FUNCTIONS_NAV_SHORTCUT_IDS.NAV_FUNCTIONS_OVERVIEW]: {
id: FUNCTIONS_NAV_SHORTCUT_IDS.NAV_FUNCTIONS_OVERVIEW,
label: 'Go to Functions',
sequence: ['F', 'O'],
showInSettings: false,
referenceGroup: SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTIONS,
},
[FUNCTIONS_NAV_SHORTCUT_IDS.NAV_FUNCTIONS_SECRETS]: {
id: FUNCTIONS_NAV_SHORTCUT_IDS.NAV_FUNCTIONS_SECRETS,
label: 'Go to Secrets',
sequence: ['F', 'K'],
showInSettings: false,
referenceGroup: SHORTCUT_REFERENCE_GROUPS.NAVIGATION_FUNCTIONS,
},
}
@@ -0,0 +1,64 @@
import { RegistryDefinations } from '../types'
/**
* Shortcuts scoped to the per-function Overview tab (the new flag-gated UI).
*
* The interval picker uses an `I + <letter>` chord matching the first letter
* of the interval's unit (M=minute, H=hour, T=three-hour, D=day).
*/
export const FUNCTIONS_OVERVIEW_SHORTCUT_IDS = {
FUNCTION_OVERVIEW_INTERVAL_15MIN: 'functions-overview.interval-15min',
FUNCTION_OVERVIEW_INTERVAL_1HR: 'functions-overview.interval-1hr',
FUNCTION_OVERVIEW_INTERVAL_3HR: 'functions-overview.interval-3hr',
FUNCTION_OVERVIEW_INTERVAL_1DAY: 'functions-overview.interval-1day',
FUNCTION_OVERVIEW_REFRESH: 'functions-overview.refresh',
FUNCTION_OVERVIEW_OPEN_LOGS: 'functions-overview.open-logs',
}
export type FunctionsOverviewShortcutId =
(typeof FUNCTIONS_OVERVIEW_SHORTCUT_IDS)[keyof typeof FUNCTIONS_OVERVIEW_SHORTCUT_IDS]
export const functionsOverviewRegistry: RegistryDefinations<FunctionsOverviewShortcutId> = {
[FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_15MIN]: {
id: FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_15MIN,
label: 'Show last 15 minutes',
sequence: ['I', 'M'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_1HR]: {
id: FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_1HR,
label: 'Show last hour',
sequence: ['I', 'H'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_3HR]: {
id: FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_3HR,
label: 'Show last 3 hours',
sequence: ['I', 'T'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_1DAY]: {
id: FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_INTERVAL_1DAY,
label: 'Show last day',
sequence: ['I', 'D'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_REFRESH]: {
id: FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_REFRESH,
label: 'Refresh function stats',
sequence: ['Shift+R'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
[FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_OPEN_LOGS]: {
id: FUNCTIONS_OVERVIEW_SHORTCUT_IDS.FUNCTION_OVERVIEW_OPEN_LOGS,
label: 'Open logs',
sequence: ['O', 'L'],
showInSettings: false,
options: { ignoreInputs: true, registerInCommandMenu: true },
},
}