content.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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. >
  124. {({tableData}) => {
  125. const totals: SpansTotalValues | null =
  126. (tableData?.data?.[0] as SpansTotalValues | undefined) ?? null;
  127. return (
  128. <SuspectSpansQuery
  129. location={location}
  130. orgSlug={organization.slug}
  131. eventView={spansView}
  132. limit={10}
  133. perSuspect={0}
  134. spanOps={defined(spanOp) ? [spanOp] : []}
  135. spanGroups={defined(spanGroup) ? [spanGroup] : []}
  136. >
  137. {({suspectSpans, isLoading, pageLinks}) => (
  138. <Fragment>
  139. <SuspectSpansTable
  140. location={location}
  141. organization={organization}
  142. transactionName={transactionName}
  143. project={projects.find(p => p.id === projectId)}
  144. isLoading={isLoading}
  145. suspectSpans={suspectSpans ?? []}
  146. totals={totals}
  147. sort={sort.field}
  148. />
  149. <Pagination pageLinks={pageLinks ?? null} />
  150. </Fragment>
  151. )}
  152. </SuspectSpansQuery>
  153. );
  154. }}
  155. </DiscoverQuery>
  156. </Layout.Main>
  157. );
  158. }
  159. function getSpansEventView(eventView: EventView, sort: SpanSort): EventView {
  160. eventView = eventView.clone();
  161. const fields = SPAN_SORT_TO_FIELDS[sort];
  162. eventView.fields = fields ? fields.map(field => ({field})) : [];
  163. return eventView;
  164. }
  165. const FilterActions = styled('div')`
  166. display: grid;
  167. gap: ${space(2)};
  168. margin-bottom: ${space(2)};
  169. @media (min-width: ${p => p.theme.breakpoints.small}) {
  170. grid-template-columns: repeat(3, min-content);
  171. }
  172. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  173. grid-template-columns: auto auto 1fr auto;
  174. }
  175. `;
  176. const StyledSearchBar = styled(SearchBar)`
  177. @media (min-width: ${p => p.theme.breakpoints.small}) {
  178. order: 1;
  179. grid-column: 1/5;
  180. }
  181. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  182. order: initial;
  183. grid-column: auto;
  184. }
  185. `;
  186. export default SpansContent;