traceTable.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. import {useEffect, useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import Badge from 'sentry/components/badge';
  4. import {PanelTableHeader} from 'sentry/components/panels/panelTable';
  5. import {space} from 'sentry/styles/space';
  6. import EventView from 'sentry/utils/discover/eventView';
  7. import {useLocation} from 'sentry/utils/useLocation';
  8. import useOrganization from 'sentry/utils/useOrganization';
  9. import useRouter from 'sentry/utils/useRouter';
  10. import {
  11. generateProfileLink,
  12. generateReplayLink,
  13. generateTraceLink,
  14. generateTransactionLink,
  15. } from 'sentry/views/performance/transactionSummary/utils';
  16. import TransactionsTable from '../../components/discover/transactionsTable';
  17. // TODO(ddm): This is a placeholder component. Shown data is bogus.
  18. export function TraceTable() {
  19. const location = useLocation();
  20. const organization = useOrganization();
  21. const router = useRouter();
  22. const routerQuery = useMemo(() => router.location.query ?? {}, [router.location.query]);
  23. const eventView = EventView.fromLocation(location);
  24. const tableData: any = {
  25. data: [
  26. {
  27. 'profile.id': null,
  28. timestamp: '2023-10-30T07:04:57+00:00',
  29. 'spans.ui': 4817.500354,
  30. 'span_ops_breakdown.relative': '',
  31. replayId: '2a3ef28213b2f408ca2828b6a27149c2b',
  32. 'transaction.duration': 11893,
  33. 'spans.db': null,
  34. trace: '9fe2707d6a4dc45efa75439764f963188a',
  35. 'spans.http': 8814.999819,
  36. 'spans.resource': 2655.700206,
  37. id: 'ddb209c68a54846b19e3422309afb26ca3',
  38. 'user.display': 'alexandra.cota@sentry.io',
  39. 'spans.browser': 637.500286,
  40. 'project.name': 'javascript',
  41. },
  42. {
  43. 'profile.id': '2a3ef28213b2f408ca2828b6a27149c2b',
  44. timestamp: '2023-10-29T22:17:08+00:00',
  45. 'spans.ui': 19985.199929,
  46. 'span_ops_breakdown.relative': '',
  47. replayId: '',
  48. 'transaction.duration': 11888,
  49. 'spans.db': null,
  50. trace: '343267d658554f44be28a42e49f460f3',
  51. 'spans.http': 9248.399973,
  52. 'spans.resource': 1555.999995,
  53. id: 'e77387c5a7e043312aaaf7f406c6049a2',
  54. 'user.display': 'matej.minar@sentry.io',
  55. 'spans.browser': 534.999848,
  56. 'project.name': 'javascript',
  57. },
  58. {
  59. 'profile.id': null,
  60. timestamp: '2023-10-30T01:45:19+00:00',
  61. 'spans.ui': 5709.600449,
  62. 'span_ops_breakdown.relative': '',
  63. replayId: '',
  64. 'transaction.duration': 11863,
  65. 'spans.db': null,
  66. trace: '9ad958c8af21d4c0db58bb5542c41c4d13',
  67. 'spans.http': 9214.499951,
  68. 'spans.resource': 2082.90124,
  69. id: 'acc1854b13a04f84bd2f0fc5b803dd65',
  70. 'user.display': 'riccardo.busetti@sentry.io',
  71. 'spans.browser': 3389.699936,
  72. 'project.name': 'javascript',
  73. },
  74. {
  75. 'profile.id': '2a3ef28213b2f408ca2828b6a27149c2b',
  76. timestamp: '2023-10-29T16:59:16+00:00',
  77. 'spans.ui': 2638.000251,
  78. 'span_ops_breakdown.relative': '',
  79. replayId: '',
  80. 'transaction.duration': 11857,
  81. 'spans.db': null,
  82. trace: '548f0e7a4bd44a28aee52b890698440e',
  83. 'spans.http': 10524.60003,
  84. 'spans.resource': 222.597839,
  85. id: 'e491bd2be0734357ab9dcc690133a43b',
  86. 'user.display': 'ognjen.bostjancic@sentry.io',
  87. 'spans.browser': 9877.500056,
  88. 'project.name': 'javascript',
  89. },
  90. {
  91. 'profile.id': null,
  92. timestamp: '2023-10-30T11:39:40+00:00',
  93. 'spans.ui': 2214.000223,
  94. 'span_ops_breakdown.relative': '',
  95. replayId: '2a3ef28213b2f408ca2828b6a27149c2b',
  96. 'transaction.duration': 11852,
  97. 'spans.db': null,
  98. trace: 'eaad797b3c2c34b79bb705e5af2db688b',
  99. 'spans.http': 4308.000088,
  100. 'spans.resource': 16362.000228,
  101. id: 'd53b10ef8edc43023b92caf8d0a8a473d',
  102. 'user.display': 'arhur.knaus@sentry.io',
  103. 'spans.browser': 615.000009,
  104. 'project.name': 'javascript',
  105. },
  106. ],
  107. meta: {
  108. 'profile.id': 'string',
  109. timestamp: 'date',
  110. 'spans.ui': 'duration',
  111. 'span_ops_breakdown.relative': 'string',
  112. replayId: 'string',
  113. 'transaction.duration': 'duration',
  114. 'spans.db': 'duration',
  115. trace: 'string',
  116. 'spans.http': 'duration',
  117. 'spans.resource': 'duration',
  118. id: 'string',
  119. 'user.display': 'string',
  120. 'spans.browser': 'duration',
  121. 'project.name': 'string',
  122. units: {
  123. 'profile.id': null,
  124. timestamp: null,
  125. 'spans.ui': 'millisecond',
  126. 'span_ops_breakdown.relative': null,
  127. replayId: null,
  128. 'transaction.duration': 'millisecond',
  129. 'spans.db': 'millisecond',
  130. trace: null,
  131. 'spans.http': 'millisecond',
  132. 'spans.resource': 'millisecond',
  133. id: null,
  134. 'user.display': null,
  135. 'spans.browser': 'millisecond',
  136. 'project.name': null,
  137. },
  138. isMetricsData: false,
  139. tips: {
  140. query: null,
  141. columns: null,
  142. },
  143. datasetReason: 'unchanged',
  144. dataset: 'discover',
  145. },
  146. };
  147. const [rows, setRows] = useState(tableData.data);
  148. useEffect(() => {
  149. function shuffleArray(data) {
  150. const array = [...data];
  151. for (let i = array.length - 1; i > 0; i--) {
  152. const j = Math.floor(Math.random() * (i + 1));
  153. [array[i], array[j]] = [array[j], array[i]];
  154. }
  155. return array;
  156. }
  157. setRows(shuffleArray(tableData.data));
  158. // eslint-disable-next-line react-hooks/exhaustive-deps
  159. }, [routerQuery]);
  160. const columnOrder: any = [
  161. {
  162. key: 'id',
  163. name: 'id',
  164. type: 'string',
  165. isSortable: false,
  166. column: {
  167. kind: 'field',
  168. field: 'id',
  169. },
  170. width: -1,
  171. },
  172. {
  173. key: 'user.display',
  174. name: 'user.display',
  175. type: 'string',
  176. isSortable: false,
  177. column: {
  178. kind: 'field',
  179. field: 'user.display',
  180. },
  181. width: -1,
  182. },
  183. {
  184. key: 'span_ops_breakdown.relative',
  185. name: 'span_ops_breakdown.relative',
  186. type: 'never',
  187. isSortable: false,
  188. column: {
  189. kind: 'field',
  190. field: 'span_ops_breakdown.relative',
  191. },
  192. width: -1,
  193. },
  194. {
  195. key: 'transaction.duration',
  196. name: 'transaction.duration',
  197. type: 'duration',
  198. isSortable: false,
  199. column: {
  200. kind: 'field',
  201. field: 'transaction.duration',
  202. },
  203. width: -1,
  204. },
  205. {
  206. key: 'trace',
  207. name: 'trace',
  208. type: 'string',
  209. isSortable: false,
  210. column: {
  211. kind: 'field',
  212. field: 'trace',
  213. },
  214. width: -1,
  215. },
  216. {
  217. key: 'timestamp',
  218. name: 'timestamp',
  219. type: 'date',
  220. isSortable: false,
  221. column: {
  222. kind: 'field',
  223. field: 'timestamp',
  224. },
  225. width: -1,
  226. },
  227. {
  228. key: 'replayId',
  229. name: 'replayId',
  230. type: 'string',
  231. isSortable: false,
  232. column: {
  233. kind: 'field',
  234. field: 'replayId',
  235. },
  236. width: -1,
  237. },
  238. {
  239. key: 'profile.id',
  240. name: 'profile.id',
  241. type: 'string',
  242. isSortable: false,
  243. column: {
  244. kind: 'field',
  245. field: 'profile.id',
  246. },
  247. width: -1,
  248. },
  249. {
  250. key: 'spans.browser',
  251. name: 'spans.browser',
  252. type: 'duration',
  253. isSortable: false,
  254. column: {
  255. kind: 'field',
  256. field: 'spans.browser',
  257. },
  258. width: -1,
  259. },
  260. {
  261. key: 'spans.db',
  262. name: 'spans.db',
  263. type: 'duration',
  264. isSortable: false,
  265. column: {
  266. kind: 'field',
  267. field: 'spans.db',
  268. },
  269. width: -1,
  270. },
  271. {
  272. key: 'spans.http',
  273. name: 'spans.http',
  274. type: 'duration',
  275. isSortable: false,
  276. column: {
  277. kind: 'field',
  278. field: 'spans.http',
  279. },
  280. width: -1,
  281. },
  282. {
  283. key: 'spans.resource',
  284. name: 'spans.resource',
  285. type: 'duration',
  286. isSortable: false,
  287. column: {
  288. kind: 'field',
  289. field: 'spans.resource',
  290. },
  291. width: -1,
  292. },
  293. {
  294. key: 'spans.ui',
  295. name: 'spans.ui',
  296. type: 'duration',
  297. isSortable: false,
  298. column: {
  299. kind: 'field',
  300. field: 'spans.ui',
  301. },
  302. width: -1,
  303. },
  304. ];
  305. const widgetsQuery = JSON.parse((location?.query?.widgets as string) ?? '[]');
  306. const hasSelectedMris = widgetsQuery.filter(w => w.mri);
  307. if (!hasSelectedMris.length) {
  308. return null;
  309. }
  310. return (
  311. <TraceTableWrapper>
  312. <TitleOverlay>
  313. <StyledBadge type="alpha" color="white">
  314. Coming Soon
  315. </StyledBadge>
  316. <div>Sampled traces</div>
  317. </TitleOverlay>
  318. <TransactionsTable
  319. eventView={eventView}
  320. organization={organization}
  321. location={location}
  322. isLoading={false}
  323. tableData={{meta: tableData.meta, data: rows}}
  324. columnOrder={columnOrder}
  325. titles={[
  326. 'event id',
  327. 'user',
  328. 'operation duration',
  329. 'total duration',
  330. 'trace id',
  331. 'timestamp',
  332. 'replay',
  333. 'profile',
  334. ]}
  335. generateLink={{
  336. id: generateTransactionLink(''),
  337. trace: generateTraceLink(eventView.normalizeDateSelection(location)),
  338. replayId: generateReplayLink([]),
  339. 'profile.id': generateProfileLink(),
  340. }}
  341. useAggregateAlias
  342. />
  343. </TraceTableWrapper>
  344. );
  345. }
  346. const TitleOverlay = styled('span')`
  347. position: absolute;
  348. display: flex;
  349. gap: ${space(1)};
  350. align-items: center;
  351. z-index: 1;
  352. line-height: 1.1;
  353. height: 46px;
  354. max-width: 250px;
  355. padding: 0px 16px;
  356. background-color: rgb(250, 249, 251);
  357. color: rgb(128, 112, 143);
  358. font-size: 12px;
  359. font-weight: 600;
  360. text-transform: uppercase;
  361. user-select: none;
  362. border-top: 1px solid ${p => p.theme.border};
  363. border-bottom: 1px solid ${p => p.theme.border};
  364. border-left: 1px solid ${p => p.theme.border};
  365. border-top-left-radius: 4px;
  366. `;
  367. const StyledBadge = styled(Badge)`
  368. color: white !important;
  369. margin-top: -1px;
  370. `;
  371. const TraceTableWrapper = styled('div')`
  372. margin-top: ${space(3)};
  373. pointer-events: none;
  374. width: 100%;
  375. user-select: none;
  376. > div {
  377. width: 100%;
  378. }
  379. > div > div > div {
  380. filter: blur(3px);
  381. }
  382. > div > ${PanelTableHeader} {
  383. color: ${p => p.theme.backgroundSecondary};
  384. span {
  385. display: none;
  386. }
  387. }
  388. > span > div {
  389. pointer-events: none;
  390. }
  391. `;