eventDetailsHeader.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import {Fragment} from 'react';
  2. import {css, useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {Button} from 'sentry/components/button';
  5. import {Flex} from 'sentry/components/container/flex';
  6. import ErrorBoundary from 'sentry/components/errorBoundary';
  7. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  8. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {Event} from 'sentry/types/event';
  12. import type {Group} from 'sentry/types/group';
  13. import type {Project} from 'sentry/types/project';
  14. import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
  15. import {useLocation} from 'sentry/utils/useLocation';
  16. import {useNavigate} from 'sentry/utils/useNavigate';
  17. import {EventGraph} from 'sentry/views/issueDetails/streamline/eventGraph';
  18. import {
  19. EventSearch,
  20. useEventQuery,
  21. } from 'sentry/views/issueDetails/streamline/eventSearch';
  22. import IssueTagsPreview from 'sentry/views/issueDetails/streamline/issueTagsPreview';
  23. import {OccurrenceSummary} from 'sentry/views/issueDetails/streamline/occurrenceSummary';
  24. import {ToggleSidebar} from 'sentry/views/issueDetails/streamline/sidebar/toggleSidebar';
  25. import {useEnvironmentsFromUrl} from 'sentry/views/issueDetails/utils';
  26. interface EventDetailsHeaderProps {
  27. group: Group;
  28. project: Project;
  29. event?: Event;
  30. }
  31. export function EventDetailsHeader({group, event, project}: EventDetailsHeaderProps) {
  32. const navigate = useNavigate();
  33. const location = useLocation();
  34. const environments = useEnvironmentsFromUrl();
  35. const searchQuery = useEventQuery({groupId: group.id});
  36. const issueTypeConfig = getConfigForIssueType(group, project);
  37. const searchText = t(
  38. 'Filter %s\u2026',
  39. issueTypeConfig.customCopy.eventUnits.toLocaleLowerCase()
  40. );
  41. return (
  42. <PageErrorBoundary mini message={t('There was an error loading the event filters')}>
  43. <FilterContainer
  44. role="group"
  45. aria-description={t('Event filtering controls')}
  46. hasFilterBar={issueTypeConfig.header.filterBar.enabled}
  47. >
  48. {issueTypeConfig.header.filterBar.enabled && (
  49. <Fragment>
  50. <EnvironmentSelector group={group} event={event} project={project} />
  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(
  64. {...location, query: {...location.query, query}},
  65. {replace: true}
  66. );
  67. }}
  68. environments={environments}
  69. query={searchQuery}
  70. queryBuilderProps={{
  71. disallowFreeText: true,
  72. placeholder: searchText,
  73. label: searchText,
  74. }}
  75. />
  76. <ToggleSidebar />
  77. </Flex>
  78. </Fragment>
  79. )}
  80. {issueTypeConfig.header.graph.enabled && (
  81. <GraphSection>
  82. <EventGraph event={event} group={group} style={{flex: 1}} />
  83. {issueTypeConfig.header.tagDistribution.enabled && (
  84. <IssueTagsPreview
  85. groupId={group.id}
  86. environments={environments}
  87. project={project}
  88. />
  89. )}
  90. </GraphSection>
  91. )}
  92. {issueTypeConfig.header.occurrenceSummary.enabled && (
  93. <OccurrenceSummarySection group={group} />
  94. )}
  95. </FilterContainer>
  96. </PageErrorBoundary>
  97. );
  98. }
  99. function EnvironmentSelector({group, event, project}: EventDetailsHeaderProps) {
  100. const theme = useTheme();
  101. const issueTypeConfig = getConfigForIssueType(group, project);
  102. const isFixedEnvironment = issueTypeConfig.header.filterBar.fixedEnvironment;
  103. const eventEnvironment = event?.tags?.find(tag => tag.key === 'environment')?.value;
  104. const environmentCss = css`
  105. grid-area: env;
  106. &:before {
  107. right: 0;
  108. top: ${space(1)};
  109. bottom: ${space(1)};
  110. width: 1px;
  111. content: '';
  112. position: absolute;
  113. background: ${theme.translucentInnerBorder};
  114. }
  115. `;
  116. return isFixedEnvironment ? (
  117. <Button
  118. disabled
  119. borderless
  120. title={t('This issue only occurs in a single environment')}
  121. css={environmentCss}
  122. >
  123. {eventEnvironment}
  124. </Button>
  125. ) : (
  126. <EnvironmentPageFilter
  127. css={environmentCss}
  128. triggerProps={{
  129. borderless: true,
  130. style: {
  131. borderRadius: 0,
  132. },
  133. }}
  134. />
  135. );
  136. }
  137. const FilterContainer = styled('div')<{
  138. hasFilterBar: boolean;
  139. }>`
  140. padding-left: 24px;
  141. display: grid;
  142. grid-template-columns: auto auto minmax(100px, 1fr) auto;
  143. grid-template-rows: ${p => (p.hasFilterBar ? 'minmax(38px, auto) auto auto' : 'auto')};
  144. grid-template-areas:
  145. 'env date search toggle'
  146. 'graph graph graph graph'
  147. 'timeline timeline timeline timeline';
  148. border: 0px solid ${p => p.theme.translucentBorder};
  149. border-width: 0 1px 1px 0;
  150. `;
  151. const SearchFilter = styled(EventSearch)`
  152. border-color: transparent;
  153. border-radius: 0;
  154. box-shadow: none;
  155. `;
  156. const DateFilter = styled(DatePageFilter)`
  157. grid-area: date;
  158. &:before {
  159. right: 0;
  160. top: ${space(1)};
  161. bottom: ${space(1)};
  162. width: 1px;
  163. content: '';
  164. position: absolute;
  165. background: ${p => p.theme.translucentInnerBorder};
  166. }
  167. `;
  168. const GraphSection = styled('div')`
  169. grid-area: graph;
  170. display: flex;
  171. &:not(:first-child) {
  172. border-top: 1px solid ${p => p.theme.translucentBorder};
  173. }
  174. `;
  175. const OccurrenceSummarySection = styled(OccurrenceSummary)`
  176. grid-area: timeline;
  177. padding: ${space(2)};
  178. padding-right: 0;
  179. &:not(:first-child) {
  180. border-top: 1px solid ${p => p.theme.translucentBorder};
  181. }
  182. `;
  183. const PageErrorBoundary = styled(ErrorBoundary)`
  184. margin: 0;
  185. border: 0px solid ${p => p.theme.translucentBorder};
  186. border-width: 0 1px 1px 0;
  187. border-radius: 0;
  188. padding: ${space(1.5)} 24px;
  189. `;