eventDetailsHeader.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import styled from '@emotion/styled';
  2. import {Button, LinkButton} from 'sentry/components/button';
  3. import {Flex} from 'sentry/components/container/flex';
  4. import ErrorBoundary from 'sentry/components/errorBoundary';
  5. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  6. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  7. import {IconChevron} from 'sentry/icons/iconChevron';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {Event} from 'sentry/types/event';
  11. import type {Group} from 'sentry/types/group';
  12. import {useLocation} from 'sentry/utils/useLocation';
  13. import {useNavigate} from 'sentry/utils/useNavigate';
  14. import {useSyncedLocalStorageState} from 'sentry/utils/useSyncedLocalStorageState';
  15. import {EventGraph} from 'sentry/views/issueDetails/streamline/eventGraph';
  16. import {
  17. EventSearch,
  18. useEventQuery,
  19. } from 'sentry/views/issueDetails/streamline/eventSearch';
  20. import {Tab, TabPaths} from 'sentry/views/issueDetails/types';
  21. import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute';
  22. import {useEnvironmentsFromUrl} from 'sentry/views/issueDetails/utils';
  23. export function EventDetailsHeader({
  24. group,
  25. event,
  26. }: {
  27. event: Event | undefined;
  28. group: Group;
  29. }) {
  30. const navigate = useNavigate();
  31. const location = useLocation();
  32. const environments = useEnvironmentsFromUrl();
  33. const searchQuery = useEventQuery({group});
  34. const {baseUrl} = useGroupDetailsRoute();
  35. const [sidebarOpen, setSidebarOpen] = useSyncedLocalStorageState(
  36. 'issue-details-sidebar-open',
  37. true
  38. );
  39. const direction = sidebarOpen ? 'right' : 'left';
  40. return (
  41. <PageErrorBoundary mini message={t('There was an error loading the event filters')}>
  42. <FilterContainer>
  43. <EnvironmentFilter
  44. triggerProps={{
  45. borderless: true,
  46. style: {
  47. borderRadius: 0,
  48. },
  49. }}
  50. />
  51. <DateFilter
  52. triggerProps={{
  53. borderless: true,
  54. style: {
  55. borderRadius: 0,
  56. },
  57. }}
  58. />
  59. <Flex style={{gridArea: 'search'}}>
  60. <SearchFilter
  61. group={group}
  62. handleSearch={query => {
  63. navigate({...location, query: {...location.query, query}}, {replace: true});
  64. }}
  65. environments={environments}
  66. query={searchQuery}
  67. queryBuilderProps={{
  68. disallowFreeText: true,
  69. }}
  70. />
  71. <ToggleContainer sidebarOpen={sidebarOpen}>
  72. <ToggleButton
  73. onClick={() => setSidebarOpen(!sidebarOpen)}
  74. aria-label={sidebarOpen ? t('Close Sidebar') : t('Open Sidebar')}
  75. >
  76. <LeftChevron direction={direction} />
  77. <RightChevron direction={direction} />
  78. </ToggleButton>
  79. </ToggleContainer>
  80. </Flex>
  81. <GraphSection>
  82. <EventGraph event={event} group={group} style={{flex: 1}} />
  83. <SectionDivider />
  84. <IssueTagsButton
  85. aria-label={t('View Issue Tags')}
  86. to={{
  87. pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
  88. query: location.query,
  89. replace: true,
  90. }}
  91. >
  92. {t('Issue Tags')}
  93. </IssueTagsButton>
  94. </GraphSection>
  95. </FilterContainer>
  96. </PageErrorBoundary>
  97. );
  98. }
  99. const FilterContainer = styled('div')`
  100. padding-left: 24px;
  101. display: grid;
  102. grid-template-columns: auto auto minmax(100px, 1fr);
  103. grid-template-rows: minmax(38px, auto) auto;
  104. grid-template-areas:
  105. 'env date search toggle'
  106. 'graph graph graph graph';
  107. border: 0px solid ${p => p.theme.translucentBorder};
  108. border-width: 0 1px 1px 0;
  109. `;
  110. const EnvironmentFilter = styled(EnvironmentPageFilter)`
  111. grid-area: env;
  112. &:before {
  113. right: 0;
  114. top: ${space(1)};
  115. bottom: ${space(1)};
  116. width: 1px;
  117. content: '';
  118. position: absolute;
  119. background: ${p => p.theme.translucentInnerBorder};
  120. }
  121. `;
  122. const SearchFilter = styled(EventSearch)`
  123. border: 0;
  124. border-radius: 0;
  125. box-shadow: none;
  126. `;
  127. const DateFilter = styled(DatePageFilter)`
  128. grid-area: date;
  129. &:before {
  130. right: 0;
  131. top: ${space(1)};
  132. bottom: ${space(1)};
  133. width: 1px;
  134. content: '';
  135. position: absolute;
  136. background: ${p => p.theme.translucentInnerBorder};
  137. }
  138. `;
  139. const ToggleContainer = styled('div')<{sidebarOpen: boolean}>`
  140. width: ${p => (p.sidebarOpen ? '30px' : '50px')};
  141. position: relative;
  142. padding: ${space(0.5)} 0;
  143. @media (max-width: ${p => p.theme.breakpoints.large}) {
  144. display: none;
  145. }
  146. `;
  147. // The extra 1px on width is to display above the sidebar border
  148. const ToggleButton = styled(Button)`
  149. border-radius: ${p => p.theme.borderRadiusLeft};
  150. border-right-color: ${p => p.theme.background} !important;
  151. box-shadow: none;
  152. position: absolute;
  153. padding: 0;
  154. left: ${space(0.5)};
  155. width: calc(100% - ${space(0.5)} + 1px);
  156. outline: 0;
  157. height: 30px;
  158. min-height: unset;
  159. `;
  160. const LeftChevron = styled(IconChevron)`
  161. position: absolute;
  162. color: ${p => p.theme.subText};
  163. height: 10px;
  164. width: 10px;
  165. left: ${space(0.75)};
  166. `;
  167. const RightChevron = styled(LeftChevron)`
  168. left: ${space(1.5)};
  169. `;
  170. const GraphSection = styled('div')`
  171. grid-area: graph;
  172. display: flex;
  173. border-top: 1px solid ${p => p.theme.translucentBorder};
  174. `;
  175. const IssueTagsButton = styled(LinkButton)`
  176. display: block;
  177. flex: 0;
  178. height: unset;
  179. margin: ${space(1)} ${space(2)} ${space(1)} ${space(1)};
  180. padding: ${space(1)} ${space(1.5)};
  181. text-align: center;
  182. span {
  183. white-space: unset;
  184. }
  185. `;
  186. const SectionDivider = styled('div')`
  187. border-left: 1px solid ${p => p.theme.translucentBorder};
  188. display: flex;
  189. align-items: center;
  190. margin: ${space(1)};
  191. `;
  192. const PageErrorBoundary = styled(ErrorBoundary)`
  193. margin: 0;
  194. border: 0px solid ${p => p.theme.translucentBorder};
  195. border-width: 0 1px 1px 0;
  196. border-radius: 0;
  197. padding: ${space(1.5)} 24px;
  198. `;