relatedTransactions.tsx 5.1 KB

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