resourceSummaryTable.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import {Fragment} from 'react';
  2. import {browserHistory, Link} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import type {GridColumnHeader, GridColumnOrder} from 'sentry/components/gridEditable';
  5. import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
  6. import type {CursorHandler} from 'sentry/components/pagination';
  7. import Pagination from 'sentry/components/pagination';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import {decodeScalar} from 'sentry/utils/queryString';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import {useParams} from 'sentry/utils/useParams';
  13. import {RESOURCE_THROUGHPUT_UNIT} from 'sentry/views/performance/browser/resources';
  14. import {useResourceModuleFilters} from 'sentry/views/performance/browser/resources/utils/useResourceFilters';
  15. import {useResourcePagesQuery} from 'sentry/views/performance/browser/resources/utils/useResourcePageQuery';
  16. import {useResourceSummarySort} from 'sentry/views/performance/browser/resources/utils/useResourceSummarySort';
  17. import {FullSpanDescription} from 'sentry/views/starfish/components/fullSpanDescription';
  18. import {DurationCell} from 'sentry/views/starfish/components/tableCells/durationCell';
  19. import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell';
  20. import ResourceSizeCell from 'sentry/views/starfish/components/tableCells/resourceSizeCell';
  21. import {WiderHovercard} from 'sentry/views/starfish/components/tableCells/spanDescriptionCell';
  22. import {ThroughputCell} from 'sentry/views/starfish/components/tableCells/throughputCell';
  23. import {SpanIndexedField, SpanMetricsField} from 'sentry/views/starfish/types';
  24. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  25. import {DataTitles, getThroughputTitle} from 'sentry/views/starfish/views/spans/types';
  26. const {
  27. RESOURCE_RENDER_BLOCKING_STATUS,
  28. SPAN_SELF_TIME,
  29. HTTP_RESPONSE_CONTENT_LENGTH,
  30. TRANSACTION,
  31. } = SpanMetricsField;
  32. type Row = {
  33. 'avg(http.response_content_length)': number;
  34. 'avg(span.self_time)': number;
  35. 'resource.render_blocking_status': '' | 'non-blocking' | 'blocking';
  36. 'spm()': number;
  37. transaction: string;
  38. };
  39. type Column = GridColumnHeader<keyof Row>;
  40. function ResourceSummaryTable() {
  41. const location = useLocation();
  42. const {groupId} = useParams();
  43. const sort = useResourceSummarySort();
  44. const filters = useResourceModuleFilters();
  45. const cursor = decodeScalar(location.query?.[QueryParameterNames.PAGES_CURSOR]);
  46. const {data, isLoading, pageLinks} = useResourcePagesQuery(groupId, {
  47. sort,
  48. cursor,
  49. renderBlockingStatus: filters[RESOURCE_RENDER_BLOCKING_STATUS],
  50. });
  51. const columnOrder: GridColumnOrder<keyof Row>[] = [
  52. {key: 'transaction', width: COL_WIDTH_UNDEFINED, name: 'Found on page'},
  53. {
  54. key: 'spm()',
  55. width: COL_WIDTH_UNDEFINED,
  56. name: getThroughputTitle('http'),
  57. },
  58. {
  59. key: `avg(${SPAN_SELF_TIME})`,
  60. width: COL_WIDTH_UNDEFINED,
  61. name: t('Avg Duration'),
  62. },
  63. {
  64. key: `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
  65. width: COL_WIDTH_UNDEFINED,
  66. name: DataTitles[`avg(${HTTP_RESPONSE_CONTENT_LENGTH})`],
  67. },
  68. {
  69. key: RESOURCE_RENDER_BLOCKING_STATUS,
  70. width: COL_WIDTH_UNDEFINED,
  71. name: t('Render Blocking'),
  72. },
  73. ];
  74. const renderBodyCell = (col: Column, row: Row) => {
  75. const {key} = col;
  76. if (key === 'spm()') {
  77. return <ThroughputCell rate={row[key]} unit={RESOURCE_THROUGHPUT_UNIT} />;
  78. }
  79. if (key === 'avg(span.self_time)') {
  80. return <DurationCell milliseconds={row[key]} />;
  81. }
  82. if (key === 'avg(http.response_content_length)') {
  83. return <ResourceSizeCell bytes={row[key]} />;
  84. }
  85. if (key === 'transaction') {
  86. const blockingStatus = row['resource.render_blocking_status'];
  87. let query = `!has:${RESOURCE_RENDER_BLOCKING_STATUS}`;
  88. if (blockingStatus) {
  89. query = `${RESOURCE_RENDER_BLOCKING_STATUS}:${blockingStatus}`;
  90. }
  91. const link = (
  92. <Link
  93. to={{
  94. pathname: location.pathname,
  95. query: {
  96. ...location.query,
  97. transaction: row[key],
  98. query: [query],
  99. },
  100. }}
  101. >
  102. {row[key]}
  103. </Link>
  104. );
  105. return (
  106. <DescriptionWrapper>
  107. <WiderHovercard
  108. position="right"
  109. body={
  110. <Fragment>
  111. <TitleWrapper>{t('Example')}</TitleWrapper>
  112. <FullSpanDescription
  113. group={groupId}
  114. language="http"
  115. filters={{
  116. [SpanIndexedField.RESOURCE_RENDER_BLOCKING_STATUS]:
  117. row[RESOURCE_RENDER_BLOCKING_STATUS],
  118. [SpanIndexedField.TRANSACTION]: row[TRANSACTION],
  119. }}
  120. />
  121. </Fragment>
  122. }
  123. >
  124. {link}
  125. </WiderHovercard>
  126. </DescriptionWrapper>
  127. );
  128. }
  129. if (key === RESOURCE_RENDER_BLOCKING_STATUS) {
  130. const value = row[key];
  131. if (value === 'blocking') {
  132. return <span>{t('Yes')}</span>;
  133. }
  134. if (value === 'non-blocking') {
  135. return <span>{t('No')}</span>;
  136. }
  137. return <span>{'-'}</span>;
  138. }
  139. return <span>{row[key]}</span>;
  140. };
  141. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  142. browserHistory.push({
  143. pathname,
  144. query: {...query, [QueryParameterNames.PAGES_CURSOR]: newCursor},
  145. });
  146. };
  147. return (
  148. <Fragment>
  149. <GridEditable
  150. data={data || []}
  151. isLoading={isLoading}
  152. columnOrder={columnOrder}
  153. columnSortBy={[
  154. {
  155. key: sort.field,
  156. order: sort.kind,
  157. },
  158. ]}
  159. grid={{
  160. renderHeadCell: column =>
  161. renderHeadCell({
  162. column,
  163. location,
  164. sort,
  165. }),
  166. renderBodyCell,
  167. }}
  168. location={location}
  169. />
  170. <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
  171. </Fragment>
  172. );
  173. }
  174. export const getActionName = (transactionOp: string) => {
  175. switch (transactionOp) {
  176. case 'ui.action.click':
  177. return 'Click';
  178. default:
  179. return transactionOp;
  180. }
  181. };
  182. const TitleWrapper = styled('div')`
  183. margin-bottom: ${space(1)};
  184. `;
  185. const DescriptionWrapper = styled('div')`
  186. .inline-flex {
  187. display: inline-flex;
  188. }
  189. `;
  190. export default ResourceSummaryTable;