header.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {ReactNode} from 'react';
  2. import {InjectedRouter} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import Badge from 'sentry/components/badge';
  5. import {Button} from 'sentry/components/button';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import GlobalEventProcessingAlert from 'sentry/components/globalEventProcessingAlert';
  8. import * as Layout from 'sentry/components/layouts/thirds';
  9. import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
  10. import QueryCount from 'sentry/components/queryCount';
  11. import {TabList, Tabs} from 'sentry/components/tabs';
  12. import {Tooltip} from 'sentry/components/tooltip';
  13. import {SLOW_TOOLTIP_DELAY} from 'sentry/constants';
  14. import {IconPause, IconPlay} from 'sentry/icons';
  15. import {t} from 'sentry/locale';
  16. import {space} from 'sentry/styles/space';
  17. import {Organization} from 'sentry/types';
  18. import useProjects from 'sentry/utils/useProjects';
  19. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  20. import IssueListSetAsDefault from 'sentry/views/issueList/issueListSetAsDefault';
  21. import {getTabs, IssueSortOptions, Query, QueryCounts, TAB_MAX_COUNT} from './utils';
  22. type IssueListHeaderProps = {
  23. displayReprocessingTab: boolean;
  24. onRealtimeChange: (realtime: boolean) => void;
  25. organization: Organization;
  26. query: string;
  27. queryCounts: QueryCounts;
  28. realtimeActive: boolean;
  29. router: InjectedRouter;
  30. selectedProjectIds: number[];
  31. sort: string;
  32. queryCount?: number;
  33. };
  34. type IssueListHeaderTabProps = {
  35. name: string;
  36. query: string;
  37. count?: number;
  38. hasMore?: boolean;
  39. tooltipHoverable?: boolean;
  40. tooltipTitle?: ReactNode;
  41. };
  42. function IssueListHeaderTabContent({
  43. count = 0,
  44. hasMore = false,
  45. name,
  46. query,
  47. tooltipHoverable,
  48. tooltipTitle,
  49. }: IssueListHeaderTabProps) {
  50. return (
  51. <Tooltip
  52. title={tooltipTitle}
  53. position="bottom"
  54. isHoverable={tooltipHoverable}
  55. delay={SLOW_TOOLTIP_DELAY}
  56. >
  57. {name}{' '}
  58. {count > 0 && (
  59. <Badge type={query === Query.FOR_REVIEW && count > 0 ? 'review' : 'default'}>
  60. <QueryCount hideParens count={count} max={hasMore ? TAB_MAX_COUNT : 1000} />
  61. </Badge>
  62. )}
  63. </Tooltip>
  64. );
  65. }
  66. function IssueListHeader({
  67. organization,
  68. query,
  69. sort,
  70. queryCounts,
  71. realtimeActive,
  72. onRealtimeChange,
  73. router,
  74. displayReprocessingTab,
  75. selectedProjectIds,
  76. }: IssueListHeaderProps) {
  77. const {projects} = useProjects();
  78. const tabs = getTabs(organization);
  79. const visibleTabs = displayReprocessingTab
  80. ? tabs
  81. : tabs.filter(([tab]) => tab !== Query.REPROCESSING);
  82. // Remove cursor and page when switching tabs
  83. const {cursor: _, page: __, ...queryParms} = router?.location?.query ?? {};
  84. const sortParam =
  85. queryParms.sort === IssueSortOptions.INBOX ? undefined : queryParms.sort;
  86. const selectedProjects = projects.filter(({id}) =>
  87. selectedProjectIds.includes(Number(id))
  88. );
  89. const realtimeTitle = realtimeActive
  90. ? t('Pause real-time updates')
  91. : t('Enable real-time updates');
  92. return (
  93. <Layout.Header noActionWrap>
  94. <Layout.HeaderContent>
  95. <Layout.Title>
  96. {t('Issues')}
  97. <PageHeadingQuestionTooltip
  98. docsUrl="https://docs.sentry.io/product/issues/"
  99. title={t(
  100. 'Detailed views of errors and performance problems in your application grouped by events with a similar set of characteristics.'
  101. )}
  102. />
  103. </Layout.Title>
  104. </Layout.HeaderContent>
  105. <Layout.HeaderActions>
  106. <ButtonBar gap={1}>
  107. <IssueListSetAsDefault {...{sort, query, organization}} />
  108. <Button
  109. size="sm"
  110. data-test-id="real-time"
  111. title={realtimeTitle}
  112. aria-label={realtimeTitle}
  113. icon={realtimeActive ? <IconPause size="xs" /> : <IconPlay size="xs" />}
  114. onClick={() => onRealtimeChange(!realtimeActive)}
  115. />
  116. </ButtonBar>
  117. </Layout.HeaderActions>
  118. <StyledGlobalEventProcessingAlert projects={selectedProjects} />
  119. <StyledTabs selectedKey={query} onSelectionChange={() => {}}>
  120. <TabList hideBorder>
  121. {[
  122. ...visibleTabs.map(
  123. ([tabQuery, {name: queryName, tooltipTitle, tooltipHoverable}]) => {
  124. const to = normalizeUrl({
  125. query: {
  126. ...queryParms,
  127. query: tabQuery,
  128. sort:
  129. tabQuery === Query.FOR_REVIEW ? IssueSortOptions.INBOX : sortParam,
  130. },
  131. pathname: `/organizations/${organization.slug}/issues/`,
  132. });
  133. return (
  134. <TabList.Item key={tabQuery} to={to} textValue={queryName}>
  135. <IssueListHeaderTabContent
  136. tooltipTitle={tooltipTitle}
  137. tooltipHoverable={tooltipHoverable}
  138. name={queryName}
  139. count={queryCounts[tabQuery]?.count}
  140. hasMore={queryCounts[tabQuery]?.hasMore}
  141. query={tabQuery}
  142. />
  143. </TabList.Item>
  144. );
  145. }
  146. ),
  147. ]}
  148. </TabList>
  149. </StyledTabs>
  150. </Layout.Header>
  151. );
  152. }
  153. export default IssueListHeader;
  154. const StyledGlobalEventProcessingAlert = styled(GlobalEventProcessingAlert)`
  155. grid-column: 1/-1;
  156. margin-top: ${space(1)};
  157. margin-bottom: ${space(1)};
  158. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  159. margin-top: ${space(2)};
  160. margin-bottom: 0;
  161. }
  162. `;
  163. const StyledTabs = styled(Tabs)`
  164. grid-column: 1/-1;
  165. `;