Skip to content

Commit e691386

Browse files
authored
ref(cmdk) reimplement dsn search (#112222)
Improve CMDK functionality by adding a dedicated DSN section that enables direct DSN queries as well as reverse lookup of the DSN action
1 parent 2fbbde1 commit e691386

File tree

5 files changed

+90
-177
lines changed

5 files changed

+90
-177
lines changed

static/app/components/commandPalette/ui/commandPalette.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ export function CommandPalette(props: CommandPaletteProps) {
175175
</Flex>
176176
</Text>
177177
),
178+
details: action.display.details ? (
179+
<Container style={{paddingLeft: '22px'}}>
180+
<Text size="sm" variant="muted">
181+
{action.display.details}
182+
</Text>
183+
</Container>
184+
) : undefined,
178185
hideCheck: true,
179186
children: [],
180187
}}
@@ -353,7 +360,9 @@ export function CommandPalette(props: CommandPaletteProps) {
353360
}}
354361
</Flex>
355362
</Flex>
356-
{treeState.collection.size === 0 ? (
363+
{treeState.collection.size === 0 &&
364+
// Don't show no results if we're still fetching data
365+
asyncQueries.every(query => !query.isFetching) ? (
357366
<CommandPaletteNoResults />
358367
) : (
359368
<ResultsList

static/app/components/commandPalette/ui/modal.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,12 @@ import type {ModalRenderProps} from 'sentry/actionCreators/modal';
55
import {closeModal} from 'sentry/actionCreators/modal';
66
import type {CommandPaletteActionWithKey} from 'sentry/components/commandPalette/types';
77
import {CommandPalette} from 'sentry/components/commandPalette/ui/commandPalette';
8-
import {useCommandPaletteState} from 'sentry/components/commandPalette/ui/commandPaletteStateContext';
9-
import {useDsnLookupActions} from 'sentry/components/commandPalette/useDsnLookupActions';
108
import type {Theme} from 'sentry/utils/theme';
119
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
1210
import {useNavigate} from 'sentry/utils/useNavigate';
1311

1412
export default function CommandPaletteModal({Body}: ModalRenderProps) {
1513
const navigate = useNavigate();
16-
const {query} = useCommandPaletteState();
17-
18-
useDsnLookupActions(query);
1914

2015
const handleSelect = useCallback(
2116
(action: CommandPaletteActionWithKey) => {

static/app/components/commandPalette/useDsnLookupActions.spec.tsx

Lines changed: 0 additions & 115 deletions
This file was deleted.

static/app/components/commandPalette/useDsnLookupActions.tsx

Lines changed: 0 additions & 56 deletions
This file was deleted.

static/app/components/commandPalette/useGlobalCommandPaletteActions.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,35 @@ import {addLoadingMessage, addSuccessMessage} from 'sentry/actionCreators/indica
88
import {openInviteMembersModal} from 'sentry/actionCreators/modal';
99
import {useCommandPaletteActionsRegister} from 'sentry/components/commandPalette/context';
1010
import type {
11+
CMDKQueryOptions,
1112
CommandPaletteAction,
1213
CommandPaletteAsyncResult,
1314
} from 'sentry/components/commandPalette/types';
15+
import {
16+
DSN_PATTERN,
17+
getDsnNavTargets,
18+
} from 'sentry/components/search/sources/dsnLookupUtils';
19+
import type {DsnLookupResponse} from 'sentry/components/search/sources/dsnLookupUtils';
1420
import {
1521
IconAdd,
1622
IconCompass,
1723
IconDashboard,
1824
IconDiscord,
1925
IconDocs,
26+
IconSearch,
2027
IconGithub,
2128
IconGraph,
2229
IconIssues,
30+
IconList,
2331
IconOpen,
2432
IconSettings,
2533
IconStar,
2634
IconUser,
2735
IconPanel,
36+
IconLock,
2837
} from 'sentry/icons';
2938
import {t} from 'sentry/locale';
39+
import {apiOptions} from 'sentry/utils/api/apiOptions';
3040
import {queryOptions} from 'sentry/utils/queryClient';
3141
import {useMutateUserOptions} from 'sentry/utils/useMutateUserOptions';
3242
import {useOrganization} from 'sentry/utils/useOrganization';
@@ -42,6 +52,12 @@ import {useStarredIssueViews} from 'sentry/views/navigation/secondary/sections/i
4252
import {useSecondaryNavigation} from 'sentry/views/navigation/secondaryNavigationContext';
4353
import {getUserOrgNavigationConfiguration} from 'sentry/views/settings/organization/userOrgNavigationConfiguration';
4454

55+
const DSN_ICONS: React.ReactElement[] = [
56+
<IconIssues key="issues" />,
57+
<IconSettings key="settings" />,
58+
<IconList key="list" />,
59+
];
60+
4561
// This hook generates actions for all pages in the primary and secondary navigation.
4662
// TODO: Consider refactoring the navigation so that this can read from the same source
4763
// of truth and avoid divergence.
@@ -304,6 +320,8 @@ function useNavigationToggleCollapsed(): CommandPaletteAction {
304320
*/
305321
export function useGlobalCommandPaletteActions() {
306322
const organization = useOrganization();
323+
const hasDsnLookup = organization.features.includes('cmd-k-dsn-lookup');
324+
const {projects} = useProjects();
307325
const navigateActions = useNavigationActions();
308326
const {mutateAsync: mutateUserOptions} = useMutateUserOptions();
309327
const navigationToggleAction = useNavigationToggleCollapsed();
@@ -325,32 +343,94 @@ export function useGlobalCommandPaletteActions() {
325343
label: t('Create Dashboard'),
326344
icon: <IconAdd />,
327345
},
346+
keywords: [t('add dashboard')],
328347
to: `${navPrefix}/dashboards/new/`,
329348
},
330349
{
331350
display: {
332351
label: t('Create Alert'),
333352
icon: <IconAdd />,
334353
},
354+
keywords: [t('add alert')],
335355
to: `${navPrefix}/issues/alerts/wizard/`,
336356
},
337357
{
338358
display: {
339359
label: t('Create Project'),
340360
icon: <IconAdd />,
341361
},
362+
keywords: [t('add project')],
342363
to: `${navPrefix}/projects/new/`,
343364
},
344365
{
345366
display: {
346367
label: t('Invite Members'),
347368
icon: <IconUser />,
348369
},
370+
keywords: [t('team invite')],
349371
onAction: openInviteMembersModal,
350372
},
351373
],
352374
},
353375
// END ADD
376+
// BEGIN DSN LOOKUP
377+
{
378+
display: {label: t('DSN')},
379+
keywords: [t('client keys')],
380+
actions: [
381+
{
382+
display: {
383+
label: t('Project DSN Keys'),
384+
icon: <IconLock locked />,
385+
},
386+
keywords: [t('client keys'), t('dsn keys')],
387+
actions: projects.map(project => ({
388+
display: {
389+
label: project.name,
390+
icon: <ProjectAvatar project={project} size={16} />,
391+
},
392+
keywords: [`dsn ${project.name}`, `dsn ${project.slug}`],
393+
to: `/settings/${organization.slug}/projects/${project.slug}/keys/`,
394+
})),
395+
},
396+
hasDsnLookup
397+
? {
398+
display: {
399+
label: t('Reverse DSN lookup'),
400+
details: t(
401+
'Paste a DSN into the search bar to find the project it belongs to.'
402+
),
403+
icon: <IconSearch />,
404+
},
405+
actions: [],
406+
resource: (query: string): CMDKQueryOptions => {
407+
return queryOptions({
408+
...apiOptions.as<DsnLookupResponse>()(
409+
'/organizations/$organizationIdOrSlug/dsn-lookup/',
410+
{
411+
path: {organizationIdOrSlug: organization.slug},
412+
query: {dsn: query},
413+
staleTime: 30_000,
414+
}
415+
),
416+
enabled: DSN_PATTERN.test(query),
417+
select: data =>
418+
getDsnNavTargets(data.json).map((target, i) => ({
419+
to: target.to,
420+
display: {
421+
label: target.label,
422+
details: target.description,
423+
icon: DSN_ICONS[i],
424+
},
425+
keywords: [query],
426+
})),
427+
});
428+
},
429+
}
430+
: undefined,
431+
].filter(action => action !== undefined),
432+
},
433+
// END DSN LOOKUP
354434
// BEGIN HELP ACTIONS
355435
{
356436
display: {

0 commit comments

Comments
 (0)