Skip to content

Commit 6e5e899

Browse files
committed
ref(issues) cmdk actions
1 parent e737c61 commit 6e5e899

File tree

4 files changed

+73
-27
lines changed

4 files changed

+73
-27
lines changed

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
import {useCommandPaletteAnalytics} from 'sentry/components/commandPalette/useCommandPaletteAnalytics';
3131
import {FeedbackButton} from 'sentry/components/feedbackButton/feedbackButton';
3232
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
33-
import {IconArrow, IconClose, IconLink, IconSearch} from 'sentry/icons';
33+
import {IconArrow, IconClose, IconLink, IconOpen, IconSearch} from 'sentry/icons';
3434
import {IconDefaultsProvider} from 'sentry/icons/useIconDefaults';
3535
import {t} from 'sentry/locale';
3636
import {fzf} from 'sentry/utils/search/fzf';
@@ -249,6 +249,12 @@ export function CommandPalette(props: CommandPaletteProps) {
249249

250250
const resultsListRef = useRef<HTMLDivElement>(null);
251251

252+
useEffect(() => {
253+
if (resultsListRef.current) {
254+
resultsListRef.current.scrollTop = 0;
255+
}
256+
}, [state.action, state.query]);
257+
252258
// Track whether Shift is held so that actions with `to` can be opened in a
253259
// new tab. Using a ref (instead of state) avoids re-renders on every keypress.
254260
const shiftHeldRef = useRef(false);
@@ -331,9 +337,6 @@ export function CommandPalette(props: CommandPaletteProps) {
331337
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
332338
dispatch({type: 'set query', query: e.target.value});
333339
treeState.selectionManager.setFocusedKey(null);
334-
if (resultsListRef.current) {
335-
resultsListRef.current.scrollTop = 0;
336-
}
337340
},
338341
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => {
339342
if (e.key === 'Backspace' && state.query.length === 0) {
@@ -622,7 +625,15 @@ function makeMenuItemFromAction(action: CMDKFlatItem): CommandPaletteActionMenuI
622625
<IconDefaultsProvider size="sm">{action.display.icon}</IconDefaultsProvider>
623626
</Flex>
624627
),
625-
trailingItems: 'to' in action ? <IconLink size="xs" variant="muted" /> : undefined,
628+
trailingItems:
629+
'to' in action ? (
630+
typeof action.to === 'string' &&
631+
(action.to.startsWith('http://') || action.to.startsWith('https://')) ? (
632+
<IconOpen size="xs" variant="muted" />
633+
) : (
634+
<IconLink size="xs" variant="muted" />
635+
)
636+
) : undefined,
626637
children: [],
627638
hideCheck: true,
628639
};

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

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import DOMPurify from 'dompurify';
44
import {ProjectAvatar} from '@sentry/scraps/avatar';
55

66
import {addLoadingMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
7-
import {openInviteMembersModal} from 'sentry/actionCreators/modal';
87
import {openSudo} from 'sentry/actionCreators/sudoModal';
98
import type {
109
CMDKQueryOptions,
@@ -30,7 +29,6 @@ import {
3029
IconSearch,
3130
IconSettings,
3231
IconStar,
33-
IconUser,
3432
} from 'sentry/icons';
3533
import {t} from 'sentry/locale';
3634
import {apiOptions} from 'sentry/utils/api/apiOptions';
@@ -276,11 +274,6 @@ export function GlobalCommandPaletteActions() {
276274
keywords={[t('add project')]}
277275
to={`${prefix}/projects/new/`}
278276
/>
279-
<CMDKAction
280-
display={{label: t('Invite Members'), icon: <IconUser />}}
281-
keywords={[t('team invite')]}
282-
onAction={openInviteMembersModal}
283-
/>
284277
</CMDKAction>
285278

286279
<CMDKAction display={{label: t('DSN')}} keywords={[t('client keys')]}>
@@ -343,25 +336,19 @@ export function GlobalCommandPaletteActions() {
343336
<CMDKAction display={{label: t('Help')}}>
344337
<CMDKAction
345338
display={{label: t('Open Documentation'), icon: <IconDocs />}}
346-
onAction={() => window.open('https://docs.sentry.io', '_blank', 'noreferrer')}
339+
to="https://docs.sentry.io"
347340
/>
348341
<CMDKAction
349342
display={{label: t('Join Discord'), icon: <IconDiscord />}}
350-
onAction={() =>
351-
window.open('https://discord.gg/sentry', '_blank', 'noreferrer')
352-
}
343+
to="https://discord.gg/sentry"
353344
/>
354345
<CMDKAction
355346
display={{label: t('Open GitHub Repository'), icon: <IconGithub />}}
356-
onAction={() =>
357-
window.open('https://github.com/getsentry/sentry', '_blank', 'noreferrer')
358-
}
347+
to="https://github.com/getsentry/sentry"
359348
/>
360349
<CMDKAction
361-
display={{label: t('View Changelog'), icon: <IconOpen />}}
362-
onAction={() =>
363-
window.open('https://sentry.io/changelog/', '_blank', 'noreferrer')
364-
}
350+
display={{label: t('View Changelog')}}
351+
to="https://sentry.io/changelog/"
365352
/>
366353
<CMDKAction
367354
display={{label: t('Search Results')}}
@@ -406,7 +393,7 @@ export function GlobalCommandPaletteActions() {
406393
</CMDKAction>
407394

408395
<CMDKAction display={{label: t('Interface')}}>
409-
<CMDKAction display={{label: t('Change Color Theme'), icon: <IconSettings />}}>
396+
<CMDKAction display={{label: t('Change Color Theme')}}>
410397
<CMDKAction
411398
display={{label: t('System')}}
412399
onAction={async () => {

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@ export default function CommandPaletteModal({Body, closeModal}: ModalRenderProps
2525
const handleSelect = useCallback(
2626
(action: CollectionTreeNode<CMDKActionData>, modifierKeys: CMDKModifierKeys) => {
2727
if ('to' in action) {
28-
if (modifierKeys.shift) {
28+
const href = locationToString(action.to);
29+
const isExternal = href.startsWith('http://') || href.startsWith('https://');
30+
if (isExternal) {
31+
window.open(href, '_blank', 'noreferrer');
32+
} else if (modifierKeys.shift) {
2933
// Open in a new tab and leave the palette open so the user can
3034
// continue selecting more items.
3135
window.open(locationToString(normalizeUrl(action.to)), '_blank');
3236
return;
37+
} else {
38+
navigate(normalizeUrl(action.to));
3339
}
34-
navigate(normalizeUrl(action.to));
3540
} else if ('onAction' in action) {
3641
action.onAction();
3742
// When the action has children, the palette will push into them so the

static/app/views/issueList/actions/index.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
IconMerge,
2828
IconMute,
2929
IconSliders,
30+
IconSort,
3031
IconStack,
3132
} from 'sentry/icons';
3233
import {t, tct, tn} from 'sentry/locale';
@@ -39,16 +40,25 @@ import {defined} from 'sentry/utils';
3940
import {trackAnalytics} from 'sentry/utils/analytics';
4041
import {uniq} from 'sentry/utils/array/uniq';
4142
import {useQueryClient} from 'sentry/utils/queryClient';
43+
import {decodeScalar} from 'sentry/utils/queryString';
4244
import {useApi} from 'sentry/utils/useApi';
45+
import {useLocation} from 'sentry/utils/useLocation';
4346
import {useMedia} from 'sentry/utils/useMedia';
47+
import {useNavigate} from 'sentry/utils/useNavigate';
4448
import {useOrganization} from 'sentry/utils/useOrganization';
4549
import {useSyncedLocalStorageState} from 'sentry/utils/useSyncedLocalStorageState';
4650
import {
4751
useIssueSelectionActions,
4852
useIssueSelectionSummary,
4953
} from 'sentry/views/issueList/issueSelectionContext';
5054
import type {IssueUpdateData} from 'sentry/views/issueList/types';
51-
import {SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY} from 'sentry/views/issueList/utils';
55+
import {
56+
DEFAULT_ISSUE_STREAM_SORT,
57+
FOR_REVIEW_QUERIES,
58+
getSortLabel,
59+
IssueSortOptions,
60+
SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY,
61+
} from 'sentry/views/issueList/utils';
5262

5363
import {ActionSet} from './actionSet';
5464
import {Headers} from './headers';
@@ -182,6 +192,12 @@ export function IssueListActions({
182192
const api = useApi();
183193
const queryClient = useQueryClient();
184194
const organization = useOrganization();
195+
const location = useLocation();
196+
const navigate = useNavigate();
197+
const sort = decodeScalar(
198+
location.query.sort,
199+
DEFAULT_ISSUE_STREAM_SORT
200+
) as IssueSortOptions;
185201
const {setAllInQuerySelected, deselectAll, toggleSelectAllVisible} =
186202
useIssueSelectionActions();
187203
const {pageSelected, multiSelected, anySelected, allInQuerySelected, selectedIdsSet} =
@@ -473,6 +489,33 @@ export function IssueListActions({
473489
/>
474490
)}
475491
</CMDKAction>
492+
<CMDKAction display={{label: t('Sort by'), icon: <IconSort />}}>
493+
{[
494+
...(FOR_REVIEW_QUERIES.includes(query || '')
495+
? [IssueSortOptions.INBOX]
496+
: []),
497+
IssueSortOptions.DATE,
498+
IssueSortOptions.NEW,
499+
IssueSortOptions.TRENDS,
500+
IssueSortOptions.FREQ,
501+
IssueSortOptions.USER,
502+
].map(sortOption => (
503+
<CMDKAction
504+
key={sortOption}
505+
display={{
506+
label: getSortLabel(sortOption),
507+
icon: sortOption === sort ? <IconCheckmark /> : undefined,
508+
}}
509+
onAction={() => {
510+
trackAnalytics('issues_stream.sort_changed', {
511+
organization,
512+
sort: sortOption,
513+
});
514+
navigate({...location, query: {...location.query, sort: sortOption}});
515+
}}
516+
/>
517+
))}
518+
</CMDKAction>
476519
{groupIds.map(id => {
477520
const group = GroupStore.get(id);
478521
if (!group) return null;

0 commit comments

Comments
 (0)