httpDomainSummaryPage.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. import {Breadcrumbs} from 'sentry/components/breadcrumbs';
  4. import FloatingFeedbackWidget from 'sentry/components/feedback/widget/floatingFeedbackWidget';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  7. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  8. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {fromSorts} from 'sentry/utils/discover/eventView';
  12. import {DurationUnit, RateUnit} from 'sentry/utils/discover/fields';
  13. import {decodeScalar} from 'sentry/utils/queryString';
  14. import {useLocation} from 'sentry/utils/useLocation';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  17. import {
  18. DomainTransactionsTable,
  19. isAValidSort,
  20. } from 'sentry/views/performance/http/domainTransactionsTable';
  21. import {DurationChart} from 'sentry/views/performance/http/durationChart';
  22. import {ResponseRateChart} from 'sentry/views/performance/http/responseRateChart';
  23. import {ThroughputChart} from 'sentry/views/performance/http/throughputChart';
  24. import {MetricReadout} from 'sentry/views/performance/metricReadout';
  25. import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
  26. import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders';
  27. import {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
  28. import {useSpanMetrics} from 'sentry/views/starfish/queries/useSpanMetrics';
  29. import {useSpanMetricsSeries} from 'sentry/views/starfish/queries/useSpanMetricsSeries';
  30. import type {SpanMetricsQueryFilters} from 'sentry/views/starfish/types';
  31. import {ModuleName, SpanFunction, SpanMetricsField} from 'sentry/views/starfish/types';
  32. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  33. import {DataTitles, getThroughputTitle} from 'sentry/views/starfish/views/spans/types';
  34. type Query = {
  35. aggregate?: string;
  36. domain?: string;
  37. };
  38. export function HTTPDomainSummaryPage() {
  39. const location = useLocation<Query>();
  40. const organization = useOrganization();
  41. const sortField = decodeScalar(location.query?.[QueryParameterNames.TRANSACTIONS_SORT]);
  42. const sort = fromSorts(sortField).filter(isAValidSort).at(0) ?? DEFAULT_SORT;
  43. const {domain} = location.query;
  44. const filters: SpanMetricsQueryFilters = {
  45. 'span.module': ModuleName.HTTP,
  46. 'span.domain': domain,
  47. };
  48. const cursor = decodeScalar(location.query?.[QueryParameterNames.TRANSACTIONS_CURSOR]);
  49. const {data: domainMetrics, isLoading: areDomainMetricsLoading} = useSpanMetrics({
  50. filters,
  51. fields: [
  52. SpanMetricsField.SPAN_DOMAIN,
  53. `${SpanFunction.SPM}()`,
  54. `avg(${SpanMetricsField.SPAN_SELF_TIME})`,
  55. `sum(${SpanMetricsField.SPAN_SELF_TIME})`,
  56. `${SpanFunction.TIME_SPENT_PERCENTAGE}()`,
  57. ],
  58. enabled: Boolean(domain),
  59. referrer: 'api.starfish.http-module-domain-summary-metrics-ribbon',
  60. });
  61. const {
  62. isLoading: isThroughputDataLoading,
  63. data: throughputData,
  64. error: throughputError,
  65. } = useSpanMetricsSeries({
  66. filters,
  67. yAxis: ['spm()'],
  68. enabled: Boolean(domain),
  69. referrer: 'api.starfish.http-module-domain-summary-throughput-chart',
  70. });
  71. const {
  72. isLoading: isDurationDataLoading,
  73. data: durationData,
  74. error: durationError,
  75. } = useSpanMetricsSeries({
  76. filters,
  77. yAxis: [`avg(${SpanMetricsField.SPAN_SELF_TIME})`],
  78. enabled: Boolean(domain),
  79. referrer: 'api.starfish.http-module-domain-summary-duration-chart',
  80. });
  81. const {
  82. isLoading: isResponseCodeDataLoading,
  83. data: responseCodeData,
  84. error: responseCodeError,
  85. } = useSpanMetricsSeries({
  86. filters: filters,
  87. yAxis: ['http_response_rate(3)', 'http_response_rate(4)', 'http_response_rate(5)'],
  88. referrer: 'api.starfish.http-module-domain-summary-response-code-chart',
  89. });
  90. const {
  91. isLoading: isTransactionsListLoading,
  92. data: transactionsList,
  93. meta: transactionsListMeta,
  94. error: transactionsListError,
  95. pageLinks: transactionsListPageLinks,
  96. } = useSpanMetrics({
  97. filters,
  98. fields: [
  99. 'transaction',
  100. 'spm()',
  101. 'http_response_rate(2)',
  102. 'http_response_rate(4)',
  103. 'http_response_rate(5)',
  104. 'avg(span.self_time)',
  105. 'sum(span.self_time)',
  106. 'time_spent_percentage()',
  107. ],
  108. sorts: [sort],
  109. limit: TRANSACTIONS_TABLE_ROW_COUNT,
  110. cursor,
  111. referrer: 'api.starfish.http-module-domain-summary-transactions-list',
  112. });
  113. useSynchronizeCharts([!isThroughputDataLoading && !isDurationDataLoading]);
  114. return (
  115. <React.Fragment>
  116. <Layout.Header>
  117. <Layout.HeaderContent>
  118. <Breadcrumbs
  119. crumbs={[
  120. {
  121. label: 'Performance',
  122. to: normalizeUrl(`/organizations/${organization.slug}/performance/`),
  123. preservePageFilters: true,
  124. },
  125. {
  126. label: 'HTTP',
  127. to: normalizeUrl(`/organizations/${organization.slug}/performance/http`),
  128. preservePageFilters: true,
  129. },
  130. {
  131. label: 'Domain Summary',
  132. },
  133. ]}
  134. />
  135. <Layout.Title>{domain}</Layout.Title>
  136. </Layout.HeaderContent>
  137. </Layout.Header>
  138. <Layout.Body>
  139. <Layout.Main fullWidth>
  140. <FloatingFeedbackWidget />
  141. <ModuleLayout.Layout>
  142. <ModuleLayout.Full>
  143. <HeaderContainer>
  144. <PageFilterBar condensed>
  145. <EnvironmentPageFilter />
  146. <DatePageFilter />
  147. </PageFilterBar>
  148. <MetricsRibbon>
  149. <MetricReadout
  150. title={getThroughputTitle('http')}
  151. value={domainMetrics?.[0]?.[`${SpanFunction.SPM}()`]}
  152. unit={RateUnit.PER_MINUTE}
  153. isLoading={areDomainMetricsLoading}
  154. />
  155. <MetricReadout
  156. title={DataTitles.avg}
  157. value={
  158. domainMetrics?.[0]?.[`avg(${SpanMetricsField.SPAN_SELF_TIME})`]
  159. }
  160. unit={DurationUnit.MILLISECOND}
  161. isLoading={areDomainMetricsLoading}
  162. />
  163. </MetricsRibbon>
  164. </HeaderContainer>
  165. </ModuleLayout.Full>
  166. <ModuleLayout.Third>
  167. <ThroughputChart
  168. series={throughputData['spm()']}
  169. isLoading={isThroughputDataLoading}
  170. error={throughputError}
  171. />
  172. </ModuleLayout.Third>
  173. <ModuleLayout.Third>
  174. <DurationChart
  175. series={durationData[`avg(${SpanMetricsField.SPAN_SELF_TIME})`]}
  176. isLoading={isDurationDataLoading}
  177. error={durationError}
  178. />
  179. </ModuleLayout.Third>
  180. <ModuleLayout.Third>
  181. <ResponseRateChart
  182. series={[
  183. {
  184. ...responseCodeData[`http_response_rate(3)`],
  185. seriesName: t('3XX'),
  186. },
  187. {
  188. ...responseCodeData[`http_response_rate(4)`],
  189. seriesName: t('4XX'),
  190. },
  191. {
  192. ...responseCodeData[`http_response_rate(5)`],
  193. seriesName: t('5XX'),
  194. },
  195. ]}
  196. isLoading={isResponseCodeDataLoading}
  197. error={responseCodeError}
  198. />
  199. </ModuleLayout.Third>
  200. <ModuleLayout.Full>
  201. <DomainTransactionsTable
  202. data={transactionsList}
  203. error={transactionsListError}
  204. isLoading={isTransactionsListLoading}
  205. meta={transactionsListMeta}
  206. pageLinks={transactionsListPageLinks}
  207. sort={sort}
  208. />
  209. </ModuleLayout.Full>
  210. </ModuleLayout.Layout>
  211. </Layout.Main>
  212. </Layout.Body>
  213. </React.Fragment>
  214. );
  215. }
  216. const DEFAULT_SORT = {
  217. field: 'time_spent_percentage()' as const,
  218. kind: 'desc' as const,
  219. };
  220. const TRANSACTIONS_TABLE_ROW_COUNT = 20;
  221. const HeaderContainer = styled('div')`
  222. display: flex;
  223. justify-content: space-between;
  224. flex-wrap: wrap;
  225. `;
  226. const MetricsRibbon = styled('div')`
  227. display: flex;
  228. flex-wrap: wrap;
  229. gap: ${space(4)};
  230. `;
  231. function LandingPageWithProviders() {
  232. return (
  233. <ModulePageProviders
  234. baseURL="/performance/http"
  235. title={[t('Performance'), t('HTTP'), t('Domain Summary')].join(' — ')}
  236. features="performance-http-view"
  237. >
  238. <HTTPDomainSummaryPage />
  239. </ModulePageProviders>
  240. );
  241. }
  242. export default LandingPageWithProviders;