content.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import {Fragment} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import omit from 'lodash/omit';
  6. import CompactSelect from 'sentry/components/compactSelect';
  7. import DatePageFilter from 'sentry/components/datePageFilter';
  8. import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
  9. import SearchBar from 'sentry/components/events/searchBar';
  10. import * as Layout from 'sentry/components/layouts/thirds';
  11. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  12. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  13. import Pagination from 'sentry/components/pagination';
  14. import space from 'sentry/styles/space';
  15. import {Organization} from 'sentry/types';
  16. import {defined} from 'sentry/utils';
  17. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  18. import DiscoverQuery from 'sentry/utils/discover/discoverQuery';
  19. import EventView from 'sentry/utils/discover/eventView';
  20. import SuspectSpansQuery from 'sentry/utils/performance/suspectSpans/suspectSpansQuery';
  21. import {decodeScalar} from 'sentry/utils/queryString';
  22. import useProjects from 'sentry/utils/useProjects';
  23. import {SetStateAction} from '../types';
  24. import OpsFilter from './opsFilter';
  25. import SuspectSpansTable from './suspectSpansTable';
  26. import {SpanSort, SpansTotalValues} from './types';
  27. import {
  28. getSuspectSpanSortFromEventView,
  29. getTotalsView,
  30. SPAN_RELATIVE_PERIODS,
  31. SPAN_RETENTION_DAYS,
  32. SPAN_SORT_OPTIONS,
  33. SPAN_SORT_TO_FIELDS,
  34. } from './utils';
  35. const ANALYTICS_VALUES = {
  36. spanOp: (organization: Organization, value: string | undefined) =>
  37. trackAdvancedAnalyticsEvent('performance_views.spans.change_op', {
  38. organization,
  39. operation_name: value,
  40. }),
  41. sort: (organization: Organization, value: string | undefined) =>
  42. trackAdvancedAnalyticsEvent('performance_views.spans.change_sort', {
  43. organization,
  44. sort_column: value,
  45. }),
  46. };
  47. type Props = {
  48. eventView: EventView;
  49. location: Location;
  50. organization: Organization;
  51. projectId: string;
  52. setError: SetStateAction<string | undefined>;
  53. transactionName: string;
  54. };
  55. function SpansContent(props: Props) {
  56. const {location, organization, eventView, projectId, transactionName} = props;
  57. const query = decodeScalar(location.query.query, '');
  58. function handleChange(key: string) {
  59. return function (value: string | undefined) {
  60. ANALYTICS_VALUES[key]?.(organization, value);
  61. const queryParams = normalizeDateTimeParams({
  62. ...(location.query || {}),
  63. [key]: value,
  64. });
  65. // do not propagate pagination when making a new search
  66. const toOmit = ['cursor'];
  67. if (!defined(value)) {
  68. toOmit.push(key);
  69. }
  70. const searchQueryParams = omit(queryParams, toOmit);
  71. browserHistory.push({
  72. ...location,
  73. query: searchQueryParams,
  74. });
  75. };
  76. }
  77. const spanOp = decodeScalar(location.query.spanOp);
  78. const spanGroup = decodeScalar(location.query.spanGroup);
  79. const sort = getSuspectSpanSortFromEventView(eventView);
  80. const spansView = getSpansEventView(eventView, sort.field);
  81. const totalsView = getTotalsView(eventView);
  82. const {projects} = useProjects();
  83. return (
  84. <Layout.Main fullWidth>
  85. <FilterActions>
  86. <OpsFilter
  87. location={location}
  88. eventView={eventView}
  89. organization={organization}
  90. handleOpChange={handleChange('spanOp')}
  91. transactionName={transactionName}
  92. />
  93. <PageFilterBar condensed>
  94. <EnvironmentPageFilter />
  95. <DatePageFilter
  96. alignDropdown="left"
  97. maxPickableDays={SPAN_RETENTION_DAYS}
  98. relativeOptions={SPAN_RELATIVE_PERIODS}
  99. />
  100. </PageFilterBar>
  101. <StyledSearchBar
  102. organization={organization}
  103. projectIds={eventView.project}
  104. query={query}
  105. fields={eventView.fields}
  106. onSearch={handleChange('query')}
  107. />
  108. <CompactSelect
  109. value={sort.field}
  110. options={SPAN_SORT_OPTIONS.map(opt => ({value: opt.field, label: opt.label}))}
  111. onChange={opt => handleChange('sort')(opt.value)}
  112. triggerProps={{prefix: sort.prefix}}
  113. triggerLabel={sort.label}
  114. />
  115. </FilterActions>
  116. <DiscoverQuery
  117. eventView={totalsView}
  118. orgSlug={organization.slug}
  119. location={location}
  120. referrer="api.performance.transaction-spans"
  121. cursor="0:0:1"
  122. noPagination
  123. useEvents
  124. >
  125. {({tableData}) => {
  126. const totals: SpansTotalValues | null =
  127. (tableData?.data?.[0] as SpansTotalValues | undefined) ?? null;
  128. return (
  129. <SuspectSpansQuery
  130. location={location}
  131. orgSlug={organization.slug}
  132. eventView={spansView}
  133. limit={10}
  134. perSuspect={0}
  135. spanOps={defined(spanOp) ? [spanOp] : []}
  136. spanGroups={defined(spanGroup) ? [spanGroup] : []}
  137. >
  138. {({suspectSpans, isLoading, pageLinks}) => (
  139. <Fragment>
  140. <SuspectSpansTable
  141. location={location}
  142. organization={organization}
  143. transactionName={transactionName}
  144. project={projects.find(p => p.id === projectId)}
  145. isLoading={isLoading}
  146. suspectSpans={suspectSpans ?? []}
  147. totals={totals}
  148. sort={sort.field}
  149. />
  150. <Pagination pageLinks={pageLinks ?? null} />
  151. </Fragment>
  152. )}
  153. </SuspectSpansQuery>
  154. );
  155. }}
  156. </DiscoverQuery>
  157. </Layout.Main>
  158. );
  159. }
  160. function getSpansEventView(eventView: EventView, sort: SpanSort): EventView {
  161. eventView = eventView.clone();
  162. const fields = SPAN_SORT_TO_FIELDS[sort];
  163. eventView.fields = fields ? fields.map(field => ({field})) : [];
  164. return eventView;
  165. }
  166. const FilterActions = styled('div')`
  167. display: grid;
  168. gap: ${space(2)};
  169. margin-bottom: ${space(2)};
  170. @media (min-width: ${p => p.theme.breakpoints.small}) {
  171. grid-template-columns: repeat(3, min-content);
  172. }
  173. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  174. grid-template-columns: auto auto 1fr auto;
  175. }
  176. `;
  177. const StyledSearchBar = styled(SearchBar)`
  178. @media (min-width: ${p => p.theme.breakpoints.small}) {
  179. order: 1;
  180. grid-column: 1/5;
  181. }
  182. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  183. order: initial;
  184. grid-column: auto;
  185. }
  186. `;
  187. export default SpansContent;