relatedTransactions.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import GridEditable, {
  5. COL_WIDTH_UNDEFINED,
  6. GridColumn,
  7. } from 'sentry/components/gridEditable';
  8. import type {Alignments} from 'sentry/components/gridEditable/sortLink';
  9. import Link from 'sentry/components/links/link';
  10. import type {Organization, Project} from 'sentry/types';
  11. import DiscoverQuery, {
  12. TableData,
  13. TableDataRow,
  14. } from 'sentry/utils/discover/discoverQuery';
  15. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  16. import {fieldAlignment} from 'sentry/utils/discover/fields';
  17. import type {MetricRule} from 'sentry/views/alerts/rules/metric/types';
  18. import {getMetricRuleDiscoverQuery} from 'sentry/views/alerts/utils/getMetricRuleDiscoverUrl';
  19. import type {TableColumn} from 'sentry/views/discover/table/types';
  20. import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';
  21. import {getProjectID} from 'sentry/views/performance/utils';
  22. import type {TimePeriodType} from './constants';
  23. interface RelatedTransactionsProps {
  24. filter: string;
  25. location: Location;
  26. organization: Organization;
  27. projects: Project[];
  28. rule: MetricRule;
  29. timePeriod: TimePeriodType;
  30. }
  31. function RelatedTransactions({
  32. organization,
  33. projects,
  34. timePeriod,
  35. rule,
  36. filter,
  37. location,
  38. }: RelatedTransactionsProps) {
  39. const [widths, setWidths] = useState<number[]>([]);
  40. const eventView = getMetricRuleDiscoverQuery({
  41. rule,
  42. timePeriod,
  43. projects,
  44. });
  45. const summaryConditions = `${rule.query} ${filter}`;
  46. function renderBodyCell(
  47. tableData: TableData | null,
  48. column: TableColumn<keyof TableDataRow>,
  49. dataRow: TableDataRow
  50. ): React.ReactNode {
  51. if (!tableData || !tableData.meta) {
  52. return dataRow[column.key];
  53. }
  54. const tableMeta = tableData.meta;
  55. const field = String(column.key);
  56. const fieldRenderer = getFieldRenderer(field, tableMeta, false);
  57. const rendered = fieldRenderer(dataRow, {organization, location});
  58. if (field === 'transaction') {
  59. const projectID = getProjectID(dataRow, projects);
  60. const summaryView = eventView!.clone();
  61. summaryView.query = summaryConditions;
  62. const target = transactionSummaryRouteWithQuery({
  63. orgSlug: organization.slug,
  64. transaction: String(dataRow.transaction) || '',
  65. query: summaryView.generateQueryStringObject(),
  66. projectID,
  67. });
  68. return <Link to={target}>{rendered}</Link>;
  69. }
  70. return rendered;
  71. }
  72. const renderBodyCellWithData = (tableData: TableData | null) => {
  73. return (
  74. column: TableColumn<keyof TableDataRow>,
  75. dataRow: TableDataRow
  76. ): React.ReactNode => renderBodyCell(tableData, column, dataRow);
  77. };
  78. function renderHeadCell(
  79. tableMeta: TableData['meta'],
  80. column: TableColumn<keyof TableDataRow>,
  81. title: React.ReactNode
  82. ): React.ReactNode {
  83. const align = fieldAlignment(column.name, column.type, tableMeta);
  84. const field = {field: column.name, width: column.width};
  85. return <HeaderCell align={align}>{title || field.field}</HeaderCell>;
  86. }
  87. const renderHeadCellWithMeta = (tableMeta: TableData['meta'], columnName: string) => {
  88. const columnTitles = ['transactions', 'project', columnName, 'users', 'user misery'];
  89. return (column: TableColumn<keyof TableDataRow>, index: number): React.ReactNode =>
  90. renderHeadCell(tableMeta, column, columnTitles[index]);
  91. };
  92. const handleResizeColumn = (columnIndex: number, nextColumn: GridColumn) => {
  93. const newWidths: number[] = [...widths];
  94. newWidths[columnIndex] = nextColumn.width
  95. ? Number(nextColumn.width)
  96. : COL_WIDTH_UNDEFINED;
  97. setWidths(newWidths);
  98. };
  99. if (!eventView) {
  100. return null;
  101. }
  102. const columnOrder = eventView
  103. .getColumns()
  104. .map((col: TableColumn<React.ReactText>, i: number) => {
  105. if (typeof widths[i] === 'number') {
  106. return {...col, width: widths[i]};
  107. }
  108. return col;
  109. });
  110. const sortedEventView = eventView.withSorts([...eventView.sorts]);
  111. const columnSortBy = sortedEventView.getSorts();
  112. return (
  113. <DiscoverQuery
  114. eventView={sortedEventView}
  115. orgSlug={organization.slug}
  116. location={location}
  117. >
  118. {({isLoading, tableData}) => (
  119. <GridEditable
  120. isLoading={isLoading}
  121. data={tableData ? tableData.data.slice(0, 5) : []}
  122. columnOrder={columnOrder}
  123. columnSortBy={columnSortBy}
  124. grid={{
  125. onResizeColumn: handleResizeColumn,
  126. renderHeadCell: renderHeadCellWithMeta(
  127. tableData?.meta,
  128. columnOrder[2]!.name
  129. ) as any,
  130. renderBodyCell: renderBodyCellWithData(tableData) as any,
  131. }}
  132. location={location}
  133. />
  134. )}
  135. </DiscoverQuery>
  136. );
  137. }
  138. export default RelatedTransactions;
  139. const HeaderCell = styled('div')<{align: Alignments}>`
  140. display: block;
  141. width: 100%;
  142. white-space: nowrap;
  143. ${(p: {align: Alignments}) => (p.align ? `text-align: ${p.align};` : '')}
  144. `;