relatedTransactions.tsx 5.1 KB

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