relatedTransactions.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import {Component, Fragment} 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 {Organization, Project} from 'sentry/types';
  11. import DiscoverQuery, {
  12. TableData,
  13. TableDataRow,
  14. } from 'sentry/utils/discover/discoverQuery';
  15. import EventView from 'sentry/utils/discover/eventView';
  16. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  17. import {fieldAlignment} from 'sentry/utils/discover/fields';
  18. import type {MetricRule} from 'sentry/views/alerts/rules/metric/types';
  19. import {getMetricRuleDiscoverQuery} from 'sentry/views/alerts/utils/getMetricRuleDiscoverUrl';
  20. import type {TableColumn} from 'sentry/views/discover/table/types';
  21. import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';
  22. import {getProjectID} from 'sentry/views/performance/utils';
  23. import type {TimePeriodType} from './constants';
  24. type TableProps = {
  25. eventView: EventView;
  26. location: Location;
  27. organization: Organization;
  28. projects: Project[];
  29. summaryConditions: string;
  30. };
  31. type TableState = {
  32. widths: number[];
  33. };
  34. class Table extends Component<TableProps, TableState> {
  35. state: TableState = {
  36. widths: [],
  37. };
  38. renderBodyCell(
  39. tableData: TableData | null,
  40. column: TableColumn<keyof TableDataRow>,
  41. dataRow: TableDataRow
  42. ): React.ReactNode {
  43. const {eventView, organization, projects, location, summaryConditions} = this.props;
  44. if (!tableData || !tableData.meta) {
  45. return dataRow[column.key];
  46. }
  47. const tableMeta = tableData.meta;
  48. const field = String(column.key);
  49. const fieldRenderer = getFieldRenderer(field, tableMeta, false);
  50. const rendered = fieldRenderer(dataRow, {organization, location});
  51. if (field === 'transaction') {
  52. const projectID = getProjectID(dataRow, projects);
  53. const summaryView = eventView.clone();
  54. summaryView.query = summaryConditions;
  55. const target = transactionSummaryRouteWithQuery({
  56. orgSlug: organization.slug,
  57. transaction: String(dataRow.transaction) || '',
  58. query: summaryView.generateQueryStringObject(),
  59. projectID,
  60. });
  61. return <Link to={target}>{rendered}</Link>;
  62. }
  63. return rendered;
  64. }
  65. renderBodyCellWithData = (tableData: TableData | null) => {
  66. return (
  67. column: TableColumn<keyof TableDataRow>,
  68. dataRow: TableDataRow
  69. ): React.ReactNode => this.renderBodyCell(tableData, column, dataRow);
  70. };
  71. renderHeadCell(
  72. tableMeta: TableData['meta'],
  73. column: TableColumn<keyof TableDataRow>,
  74. title: React.ReactNode
  75. ): React.ReactNode {
  76. const align = fieldAlignment(column.name, column.type, tableMeta);
  77. const field = {field: column.name, width: column.width};
  78. return <HeaderCell align={align}>{title || field.field}</HeaderCell>;
  79. }
  80. renderHeadCellWithMeta = (tableMeta: TableData['meta'], columnName: string) => {
  81. const columnTitles = ['transactions', 'project', columnName, 'users', 'user misery'];
  82. return (column: TableColumn<keyof TableDataRow>, index: number): React.ReactNode =>
  83. this.renderHeadCell(tableMeta, column, columnTitles[index]);
  84. };
  85. handleResizeColumn = (columnIndex: number, nextColumn: GridColumn) => {
  86. const widths: number[] = [...this.state.widths];
  87. widths[columnIndex] = nextColumn.width
  88. ? Number(nextColumn.width)
  89. : COL_WIDTH_UNDEFINED;
  90. this.setState({widths});
  91. };
  92. getSortedEventView() {
  93. const {eventView} = this.props;
  94. return eventView.withSorts([...eventView.sorts]);
  95. }
  96. render() {
  97. const {eventView, organization, location} = this.props;
  98. const {widths} = this.state;
  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 = this.getSortedEventView();
  108. const columnSortBy = sortedEventView.getSorts();
  109. return (
  110. <Fragment>
  111. <DiscoverQuery
  112. eventView={sortedEventView}
  113. orgSlug={organization.slug}
  114. location={location}
  115. >
  116. {({isLoading, tableData}) => (
  117. <GridEditable
  118. isLoading={isLoading}
  119. data={tableData ? tableData.data.slice(0, 5) : []}
  120. columnOrder={columnOrder}
  121. columnSortBy={columnSortBy}
  122. grid={{
  123. onResizeColumn: this.handleResizeColumn,
  124. renderHeadCell: this.renderHeadCellWithMeta(
  125. tableData?.meta,
  126. columnOrder[2].name as string
  127. ) as any,
  128. renderBodyCell: this.renderBodyCellWithData(tableData) as any,
  129. }}
  130. location={location}
  131. />
  132. )}
  133. </DiscoverQuery>
  134. </Fragment>
  135. );
  136. }
  137. }
  138. interface Props {
  139. filter: string;
  140. location: Location;
  141. organization: Organization;
  142. projects: Project[];
  143. rule: MetricRule;
  144. timePeriod: TimePeriodType;
  145. }
  146. function RelatedTransactions({
  147. rule,
  148. projects,
  149. filter,
  150. location,
  151. organization,
  152. timePeriod,
  153. }: Props) {
  154. const eventView = getMetricRuleDiscoverQuery({
  155. rule,
  156. timePeriod,
  157. projects,
  158. });
  159. if (!eventView) {
  160. return null;
  161. }
  162. return (
  163. <Table
  164. eventView={eventView}
  165. projects={projects}
  166. organization={organization}
  167. location={location}
  168. summaryConditions={`${rule.query} ${filter}`}
  169. />
  170. );
  171. }
  172. export default RelatedTransactions;
  173. const HeaderCell = styled('div')<{align: Alignments}>`
  174. display: block;
  175. width: 100%;
  176. white-space: nowrap;
  177. ${(p: {align: Alignments}) => (p.align ? `text-align: ${p.align};` : '')}
  178. `;