Files
2024-02-15 17:01:50 +00:00

196 lines
5.9 KiB
TypeScript

import type {ReactNode} from 'react';
import type {InjectedRouter} from 'react-router';
import styled from '@emotion/styled';
import GuideAnchor from 'sentry/components/assistant/guideAnchor';
import Badge from 'sentry/components/badge';
import {Button} from 'sentry/components/button';
import ButtonBar from 'sentry/components/buttonBar';
import GlobalEventProcessingAlert from 'sentry/components/globalEventProcessingAlert';
import * as Layout from 'sentry/components/layouts/thirds';
import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
import QueryCount from 'sentry/components/queryCount';
import {TabList, Tabs} from 'sentry/components/tabs';
import {Tooltip} from 'sentry/components/tooltip';
import {SLOW_TOOLTIP_DELAY} from 'sentry/constants';
import {IconPause, IconPlay} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types';
import useProjects from 'sentry/utils/useProjects';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
import IssueListSetAsDefault from 'sentry/views/issueList/issueListSetAsDefault';
import type {QueryCounts} from './utils';
import {
CUSTOM_TAB_VALUE,
FOR_REVIEW_QUERIES,
getTabs,
IssueSortOptions,
Query,
TAB_MAX_COUNT,
} from './utils';
type IssueListHeaderProps = {
displayReprocessingTab: boolean;
onRealtimeChange: (realtime: boolean) => void;
organization: Organization;
query: string;
queryCounts: QueryCounts;
realtimeActive: boolean;
router: InjectedRouter;
selectedProjectIds: number[];
sort: string;
queryCount?: number;
};
type IssueListHeaderTabProps = {
name: string;
count?: number;
hasMore?: boolean;
tooltipHoverable?: boolean;
tooltipTitle?: ReactNode;
};
function IssueListHeaderTabContent({
count = 0,
hasMore = false,
name,
tooltipHoverable,
tooltipTitle,
}: IssueListHeaderTabProps) {
return (
<Tooltip
title={tooltipTitle}
position="bottom"
isHoverable={tooltipHoverable}
delay={SLOW_TOOLTIP_DELAY}
>
{name}{' '}
{count > 0 && (
<Badge>
<QueryCount hideParens count={count} max={hasMore ? TAB_MAX_COUNT : 1000} />
</Badge>
)}
</Tooltip>
);
}
function IssueListHeader({
organization,
query,
sort,
queryCounts,
realtimeActive,
onRealtimeChange,
router,
displayReprocessingTab,
selectedProjectIds,
}: IssueListHeaderProps) {
const {projects} = useProjects();
const tabs = getTabs(organization);
const visibleTabs = displayReprocessingTab
? tabs
: tabs.filter(([tab]) => tab !== Query.REPROCESSING);
const tabValues = new Set(visibleTabs.map(([val]) => val));
// Remove cursor and page when switching tabs
const {cursor: _cursor, page: _page, ...queryParms} = router?.location?.query ?? {};
const sortParam =
queryParms.sort === IssueSortOptions.INBOX ? undefined : queryParms.sort;
const selectedProjects = projects.filter(({id}) =>
selectedProjectIds.includes(Number(id))
);
const realtimeTitle = realtimeActive
? t('Pause real-time updates')
: t('Enable real-time updates');
return (
<Layout.Header noActionWrap>
<Layout.HeaderContent>
<Layout.Title>
{t('Issues')}
<PageHeadingQuestionTooltip
docsUrl="https://docs.sentry.io/product/issues/"
title={t(
'Detailed views of errors and performance problems in your application grouped by events with a similar set of characteristics.'
)}
/>
</Layout.Title>
</Layout.HeaderContent>
<Layout.HeaderActions>
<ButtonBar gap={1}>
<IssueListSetAsDefault {...{sort, query, organization}} />
<Button
size="sm"
data-test-id="real-time"
title={realtimeTitle}
aria-label={realtimeTitle}
icon={realtimeActive ? <IconPause /> : <IconPlay />}
onClick={() => onRealtimeChange(!realtimeActive)}
/>
</ButtonBar>
</Layout.HeaderActions>
<StyledGlobalEventProcessingAlert projects={selectedProjects} />
<StyledTabs value={tabValues.has(query) ? query : CUSTOM_TAB_VALUE}>
<TabList hideBorder>
{visibleTabs.map(
([tabQuery, {name: queryName, tooltipTitle, tooltipHoverable, hidden}]) => {
const to = normalizeUrl({
query: {
...queryParms,
query: tabQuery,
sort: FOR_REVIEW_QUERIES.includes(tabQuery || '')
? IssueSortOptions.INBOX
: sortParam,
},
pathname: `/organizations/${organization.slug}/issues/`,
});
return (
<TabList.Item
key={tabQuery}
to={to}
textValue={queryName}
hidden={hidden}
>
<GuideAnchor
disabled={tabQuery !== Query.ARCHIVED}
target="issue_stream_archive_tab"
position="bottom"
>
<IssueListHeaderTabContent
tooltipTitle={tooltipTitle}
tooltipHoverable={tooltipHoverable}
name={queryName}
count={queryCounts[tabQuery]?.count}
hasMore={queryCounts[tabQuery]?.hasMore}
/>
</GuideAnchor>
</TabList.Item>
);
}
)}
</TabList>
</StyledTabs>
</Layout.Header>
);
}
export default IssueListHeader;
const StyledGlobalEventProcessingAlert = styled(GlobalEventProcessingAlert)`
grid-column: 1/-1;
margin-top: ${space(1)};
margin-bottom: ${space(1)};
@media (min-width: ${p => p.theme.breakpoints.medium}) {
margin-top: ${space(2)};
margin-bottom: 0;
}
`;
const StyledTabs = styled(Tabs)`
grid-column: 1/-1;
`;