123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- // eslint-disable-next-line no-restricted-imports
- import {browserHistory, withRouter, WithRouterProps} from 'react-router';
- import styled from '@emotion/styled';
- import {openModal} from 'sentry/actionCreators/modal';
- import {pinSearch, unpinSearch} from 'sentry/actionCreators/savedSearches';
- import Access from 'sentry/components/acl/access';
- import Button from 'sentry/components/button';
- import MenuItem from 'sentry/components/menuItem';
- import CreateSavedSearchModal from 'sentry/components/modals/createSavedSearchModal';
- import {IconAdd, IconPin, IconSliders} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {SavedSearch, SavedSearchType} from 'sentry/types';
- import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
- import SmartSearchBar from './index';
- import {removeSpace} from './utils';
- type SmartSearchBarProps = React.ComponentProps<typeof SmartSearchBar>;
- type ActionItem = NonNullable<SmartSearchBarProps['actionBarItems']>[number];
- type ActionProps = React.ComponentProps<ActionItem['Action']>;
- type PinSearchActionOpts = {
- /**
- * The current issue sort
- */
- sort: string;
- /**
- * The currently pinned search
- */
- pinnedSearch?: SavedSearch;
- };
- /**
- * The Pin Search action toggles the current as a pinned search
- */
- export function makePinSearchAction({pinnedSearch, sort}: PinSearchActionOpts) {
- const PinSearchAction = ({
- menuItemVariant,
- savedSearchType,
- organization,
- api,
- query,
- location,
- }: ActionProps & WithRouterProps) => {
- const onTogglePinnedSearch = async (evt: React.MouseEvent) => {
- evt.preventDefault();
- evt.stopPropagation();
- if (savedSearchType === undefined) {
- return;
- }
- const {cursor: _cursor, page: _page, ...currentQuery} = location.query;
- trackAdvancedAnalyticsEvent('search.pin', {
- organization,
- action: !!pinnedSearch ? 'unpin' : 'pin',
- search_type: savedSearchType === SavedSearchType.ISSUE ? 'issues' : 'events',
- query: pinnedSearch?.query ?? query,
- });
- if (!!pinnedSearch) {
- unpinSearch(api, organization.slug, savedSearchType, pinnedSearch).then(() => {
- browserHistory.push({
- ...location,
- pathname: `/organizations/${organization.slug}/issues/`,
- query: {
- ...currentQuery,
- query: pinnedSearch.query,
- sort: pinnedSearch.sort,
- },
- });
- });
- return;
- }
- const resp = await pinSearch(
- api,
- organization.slug,
- savedSearchType,
- removeSpace(query),
- sort
- );
- if (!resp || !resp.id) {
- return;
- }
- browserHistory.push({
- ...location,
- pathname: `/organizations/${organization.slug}/issues/searches/${resp.id}/`,
- query: currentQuery,
- });
- };
- const pinTooltip = !!pinnedSearch ? t('Unpin this search') : t('Pin this search');
- return menuItemVariant ? (
- <MenuItem
- withBorder
- data-test-id="pin-icon"
- icon={<IconPin isSolid={!!pinnedSearch} size="xs" />}
- onClick={onTogglePinnedSearch}
- >
- {!!pinnedSearch ? t('Unpin Search') : t('Pin Search')}
- </MenuItem>
- ) : (
- <ActionButton
- title={pinTooltip}
- disabled={!query}
- aria-label={pinTooltip}
- onClick={onTogglePinnedSearch}
- isActive={!!pinnedSearch}
- data-test-id="pin-icon"
- icon={<IconPin isSolid={!!pinnedSearch} size="xs" />}
- />
- );
- };
- return {key: 'pinSearch', Action: withRouter(PinSearchAction)};
- }
- type SaveSearchActionOpts = {
- /**
- * The current issue sort
- */
- sort: string;
- };
- /**
- * The Save Search action triggers the create saved search modal from the
- * current query.
- */
- export function makeSaveSearchAction({sort}: SaveSearchActionOpts) {
- const SavedSearchAction = ({menuItemVariant, query, organization}: ActionProps) => {
- const onClick = () =>
- openModal(deps => (
- <CreateSavedSearchModal {...deps} {...{organization, query, sort}} />
- ));
- return (
- <Access organization={organization} access={['org:write']}>
- {({hasAccess}) => {
- const title = hasAccess
- ? t('Add to organization saved searches')
- : t('You do not have permission to create a saved search');
- return menuItemVariant ? (
- <MenuItem
- onClick={onClick}
- disabled={!hasAccess}
- icon={<IconAdd size="xs" />}
- title={!hasAccess ? title : undefined}
- withBorder
- >
- {t('Create Saved Search')}
- </MenuItem>
- ) : (
- <ActionButton
- onClick={onClick}
- disabled={!hasAccess}
- icon={<IconAdd size="xs" />}
- title={title}
- aria-label={title}
- data-test-id="save-current-search"
- />
- );
- }}
- </Access>
- );
- };
- return {key: 'saveSearch', Action: SavedSearchAction};
- }
- type SearchBuilderActionOpts = {
- onSidebarToggle: React.MouseEventHandler;
- };
- /**
- * The Search Builder action toggles the Issue Stream search builder
- */
- export function makeSearchBuilderAction({onSidebarToggle}: SearchBuilderActionOpts) {
- const SearchBuilderAction = ({menuItemVariant}: ActionProps) =>
- menuItemVariant ? (
- <MenuItem withBorder icon={<IconSliders size="xs" />} onClick={onSidebarToggle}>
- {t('Toggle sidebar')}
- </MenuItem>
- ) : (
- <ActionButton
- title={t('Toggle search builder')}
- tooltipProps={{containerDisplayMode: 'inline-flex'}}
- aria-label={t('Toggle search builder')}
- onClick={onSidebarToggle}
- icon={<IconSliders size="xs" />}
- />
- );
- return {key: 'searchBuilder', Action: SearchBuilderAction};
- }
- export const ActionButton = styled(Button)<{isActive?: boolean}>`
- color: ${p => (p.isActive ? p.theme.blue300 : p.theme.gray300)};
- width: 18px;
- &,
- &:hover,
- &:focus {
- background: transparent;
- }
- &:hover {
- color: ${p => p.theme.gray400};
- }
- `;
- ActionButton.defaultProps = {
- type: 'button',
- borderless: true,
- size: 'zero',
- };
|