import {Fragment, useState} from 'react'; import {css, useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import orderBy from 'lodash/orderBy'; import {openModal} from 'sentry/actionCreators/modal'; import {Button, ButtonLabel} from 'sentry/components/button'; import {openConfirmModal} from 'sentry/components/confirm'; import type {MenuItemProps} from 'sentry/components/dropdownMenu'; import {DropdownMenu} from 'sentry/components/dropdownMenu'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {CreateSavedSearchModal} from 'sentry/components/modals/savedSearchModal/createSavedSearchModal'; import {EditSavedSearchModal} from 'sentry/components/modals/savedSearchModal/editSavedSearchModal'; import {IconClose, IconEllipsis} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {SavedSearch} from 'sentry/types/group'; import {SavedSearchVisibility} from 'sentry/types/group'; import type {Organization} from 'sentry/types/organization'; import {trackAnalytics} from 'sentry/utils/analytics'; import useMedia from 'sentry/utils/useMedia'; import {useSyncedLocalStorageState} from 'sentry/utils/useSyncedLocalStorageState'; import {useDeleteSavedSearchOptimistic} from 'sentry/views/issueList/mutations/useDeleteSavedSearch'; import {useFetchSavedSearchesForOrg} from 'sentry/views/issueList/queries/useFetchSavedSearchesForOrg'; import {SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY} from 'sentry/views/issueList/utils'; interface SavedIssueSearchesProps { onSavedSearchSelect: (savedSearch: SavedSearch) => void; organization: Organization; query: string; sort: string; } interface SavedSearchItemProps extends Pick { savedSearch: SavedSearch; } type CreateNewSavedSearchButtonProps = Pick< SavedIssueSearchesProps, 'query' | 'sort' | 'organization' >; const MAX_SHOWN_SEARCHES = 4; function SavedSearchItemDescription({ savedSearch, }: Pick) { if (savedSearch.isGlobal) { return {savedSearch.query}; } return ( {savedSearch.visibility === SavedSearchVisibility.ORGANIZATION ? t('Anyone in organization can see but not edit') : t('Only you can see and edit')} ); } function SavedSearchItem({ organization, onSavedSearchSelect, savedSearch, }: SavedSearchItemProps) { const {mutate: deleteSavedSearch} = useDeleteSavedSearchOptimistic(); const hasOrgWriteAccess = organization.access?.includes('org:write'); const canEdit = savedSearch.visibility === SavedSearchVisibility.OWNER || hasOrgWriteAccess; const actions: MenuItemProps[] = [ { key: 'edit', label: 'Edit', disabled: !canEdit, details: !canEdit ? t('You do not have permission to edit this search.') : undefined, onAction: () => { openModal(deps => ( )); }, }, { disabled: !canEdit, details: !canEdit ? t('You do not have permission to delete this search.') : undefined, key: 'delete', label: t('Delete'), onAction: () => { openConfirmModal({ message: t('Are you sure you want to delete this saved search?'), onConfirm: () => deleteSavedSearch({orgSlug: organization.slug, id: savedSearch.id}), }); }, priority: 'danger', }, ]; return ( onSavedSearchSelect(savedSearch)} borderless > {savedSearch.name} {!savedSearch.isGlobal && ( ( ); } function SavedIssueSearches({ organization, onSavedSearchSelect, query, sort, }: SavedIssueSearchesProps) { const theme = useTheme(); const [isOpen, setIsOpen] = useSyncedLocalStorageState( SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY, false ); const [showAll, setShowAll] = useState(false); const { data: savedSearches, isPending, isError, refetch, } = useFetchSavedSearchesForOrg({orgSlug: organization.slug}); const isMobile = useMedia(`(max-width: ${theme.breakpoints.small})`); if (!isOpen || isMobile) { return null; } if (isPending) { return ( ); } if (isError) { return ( ); } const orgSavedSearches = orderBy( savedSearches.filter(search => !search.isGlobal && !search.isPinned), 'dateCreated', 'desc' ); const recommendedSavedSearches = savedSearches.filter(search => search.isGlobal); const shownOrgSavedSearches = showAll ? orgSavedSearches : orgSavedSearches.slice(0, MAX_SHOWN_SEARCHES); return ( {t('Saved Searches')}