httpSamplesPanel.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import {Link} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import * as qs from 'query-string';
  4. import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import {DurationUnit, RateUnit} from 'sentry/utils/discover/fields';
  8. import {PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
  9. import {decodeScalar} from 'sentry/utils/queryString';
  10. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. import useProjects from 'sentry/utils/useProjects';
  14. import useRouter from 'sentry/utils/useRouter';
  15. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  16. import {MetricReadout} from 'sentry/views/performance/metricReadout';
  17. import DetailPanel from 'sentry/views/starfish/components/detailPanel';
  18. import {getTimeSpentExplanation} from 'sentry/views/starfish/components/tableCells/timeSpentCell';
  19. import {useSpanMetrics} from 'sentry/views/starfish/queries/useSpanMetrics';
  20. import {
  21. ModuleName,
  22. SpanFunction,
  23. SpanMetricsField,
  24. type SpanMetricsQueryFilters,
  25. } from 'sentry/views/starfish/types';
  26. import {DataTitles, getThroughputTitle} from 'sentry/views/starfish/views/spans/types';
  27. type Query = {
  28. domain?: string;
  29. project?: string;
  30. transaction?: string;
  31. transactionMethod?: string;
  32. };
  33. export function HTTPSamplesPanel() {
  34. const location = useLocation<Query>();
  35. const query = location.query;
  36. const router = useRouter();
  37. const organization = useOrganization();
  38. const projectId = decodeScalar(query.project);
  39. const {projects} = useProjects();
  40. const project = projects.find(p => projectId === p.id);
  41. // `detailKey` controls whether the panel is open. If all required properties are available, concat them to make a key, otherwise set to `undefined` and hide the panel
  42. const detailKey =
  43. query.transaction && query.domain
  44. ? [query.domain, query.transactionMethod, query.transaction]
  45. .filter(Boolean)
  46. .join(':')
  47. : undefined;
  48. const isPanelOpen = Boolean(detailKey);
  49. const filters: SpanMetricsQueryFilters = {
  50. 'span.module': ModuleName.HTTP,
  51. 'span.domain': query.domain,
  52. transaction: query.transaction,
  53. };
  54. const {
  55. data: domainTransactionMetrics,
  56. isFetching: areDomainTransactionMetricsFetching,
  57. } = useSpanMetrics({
  58. search: MutableSearch.fromQueryObject(filters),
  59. fields: [
  60. `${SpanFunction.SPM}()`,
  61. `avg(${SpanMetricsField.SPAN_SELF_TIME})`,
  62. `sum(${SpanMetricsField.SPAN_SELF_TIME})`,
  63. 'http_response_rate(3)',
  64. 'http_response_rate(4)',
  65. 'http_response_rate(5)',
  66. `${SpanFunction.TIME_SPENT_PERCENTAGE}()`,
  67. ],
  68. enabled: isPanelOpen,
  69. referrer: 'api.starfish.http-module-samples-panel-metrics-ribbon',
  70. });
  71. const handleClose = () => {
  72. router.replace({
  73. pathname: router.location.pathname,
  74. query: {
  75. ...router.location.query,
  76. transaction: undefined,
  77. transactionMethod: undefined,
  78. },
  79. });
  80. };
  81. return (
  82. <PageAlertProvider>
  83. <DetailPanel detailKey={detailKey} onClose={handleClose}>
  84. <HeaderContainer>
  85. {project && (
  86. <SpanSummaryProjectAvatar
  87. project={project}
  88. direction="left"
  89. size={40}
  90. hasTooltip
  91. tooltip={project.slug}
  92. />
  93. )}
  94. <TitleContainer>
  95. <Title>
  96. <Link
  97. to={normalizeUrl(
  98. `/organizations/${organization.slug}/performance/summary?${qs.stringify(
  99. {
  100. project: query.project,
  101. transaction: query.transaction,
  102. }
  103. )}`
  104. )}
  105. >
  106. {query.transaction &&
  107. query.transactionMethod &&
  108. !query.transaction.startsWith(query.transactionMethod)
  109. ? `${query.transactionMethod} ${query.transaction}`
  110. : query.transaction}
  111. </Link>
  112. </Title>
  113. </TitleContainer>
  114. </HeaderContainer>
  115. <MetricsRibbon>
  116. <MetricReadout
  117. align="left"
  118. title={getThroughputTitle('http')}
  119. value={domainTransactionMetrics?.[0]?.[`${SpanFunction.SPM}()`]}
  120. unit={RateUnit.PER_MINUTE}
  121. isLoading={areDomainTransactionMetricsFetching}
  122. />
  123. <MetricReadout
  124. align="left"
  125. title={DataTitles.avg}
  126. value={
  127. domainTransactionMetrics?.[0]?.[`avg(${SpanMetricsField.SPAN_SELF_TIME})`]
  128. }
  129. unit={DurationUnit.MILLISECOND}
  130. isLoading={areDomainTransactionMetricsFetching}
  131. />
  132. <MetricReadout
  133. align="left"
  134. title={t('3XXs')}
  135. value={domainTransactionMetrics?.[0]?.[`http_response_rate(3)`]}
  136. unit="percentage"
  137. isLoading={areDomainTransactionMetricsFetching}
  138. />
  139. <MetricReadout
  140. align="left"
  141. title={t('4XXs')}
  142. value={domainTransactionMetrics?.[0]?.[`http_response_rate(4)`]}
  143. unit="percentage"
  144. isLoading={areDomainTransactionMetricsFetching}
  145. />
  146. <MetricReadout
  147. align="left"
  148. title={t('5XXs')}
  149. value={domainTransactionMetrics?.[0]?.[`http_response_rate(5)`]}
  150. unit="percentage"
  151. isLoading={areDomainTransactionMetricsFetching}
  152. />
  153. <MetricReadout
  154. align="left"
  155. title={DataTitles.timeSpent}
  156. value={domainTransactionMetrics?.[0]?.['sum(span.self_time)']}
  157. unit={DurationUnit.MILLISECOND}
  158. tooltip={getTimeSpentExplanation(
  159. domainTransactionMetrics?.[0]?.['time_spent_percentage()'],
  160. 'db'
  161. )}
  162. isLoading={areDomainTransactionMetricsFetching}
  163. />
  164. </MetricsRibbon>
  165. </DetailPanel>
  166. </PageAlertProvider>
  167. );
  168. }
  169. const SpanSummaryProjectAvatar = styled(ProjectAvatar)`
  170. padding-right: ${space(1)};
  171. `;
  172. const HeaderContainer = styled('div')`
  173. width: 100%;
  174. padding-bottom: ${space(2)};
  175. padding-top: ${space(1)};
  176. display: grid;
  177. grid-template-rows: auto auto auto;
  178. @media (min-width: ${p => p.theme.breakpoints.small}) {
  179. grid-template-rows: auto;
  180. grid-template-columns: auto 1fr auto;
  181. }
  182. `;
  183. const TitleContainer = styled('div')`
  184. width: 100%;
  185. position: relative;
  186. height: 40px;
  187. `;
  188. const Title = styled('h4')`
  189. position: absolute;
  190. bottom: 0;
  191. margin-bottom: 0;
  192. `;
  193. const MetricsRibbon = styled('div')`
  194. display: flex;
  195. flex-wrap: wrap;
  196. gap: ${space(4)};
  197. `;