import {Fragment, PureComponent} from 'react'; import styled from '@emotion/styled'; import type {Location, LocationDescriptor} from 'history'; import SortLink from 'sentry/components/gridEditable/sortLink'; import Link from 'sentry/components/links/link'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {PanelTable} from 'sentry/components/panels/panelTable'; import QuestionTooltip from 'sentry/components/questionTooltip'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; import {trackAnalytics} from 'sentry/utils/analytics'; import type {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery'; import type {MetaType} from 'sentry/utils/discover/eventView'; import type EventView from 'sentry/utils/discover/eventView'; import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; import type {Alignments} from 'sentry/utils/discover/fields'; import {fieldAlignment, getAggregateAlias} from 'sentry/utils/discover/fields'; import ViewReplayLink from 'sentry/utils/discover/viewReplayLink'; import {isEmptyObject} from 'sentry/utils/object/isEmptyObject'; import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry'; import type {Actions} from 'sentry/views/discover/table/cellAction'; import CellAction from 'sentry/views/discover/table/cellAction'; import type {TableColumn} from 'sentry/views/discover/table/types'; import {GridCell, GridCellNumber} from 'sentry/views/performance/styles'; import type {TrendsDataEvents} from 'sentry/views/performance/trends/types'; type Props = { columnOrder: TableColumn[]; eventView: EventView; isLoading: boolean; location: Location; organization: Organization; tableData: TableData | TrendsDataEvents | null; useAggregateAlias: boolean; generateLink?: Record< string, ( organization: Organization, tableRow: TableDataRow, location: Location ) => LocationDescriptor >; handleCellAction?: ( c: TableColumn ) => (a: Actions, v: React.ReactText) => void; referrer?: string; titles?: string[]; }; class TransactionsTable extends PureComponent { getTitles() { const {eventView, titles} = this.props; return titles ?? eventView.getFields(); } renderHeader() { const {tableData, columnOrder} = this.props; const tableMeta = tableData?.meta; const generateSortLink = () => undefined; const tableTitles = this.getTitles(); const headers = tableTitles.map((title, index) => { const column = columnOrder[index]; const align: Alignments = fieldAlignment(column.name, column.type, tableMeta); if (column.key === 'span_ops_breakdown.relative') { return ( {title} ) : ( title ) } direction={undefined} canSort={false} generateSortLink={generateSortLink} /> ); } return ( ); }); return headers; } renderRow( row: TableDataRow, rowIndex: number, columnOrder: TableColumn[], tableMeta: MetaType ): React.ReactNode[] { const { eventView, organization, location, generateLink, handleCellAction, titles, useAggregateAlias, referrer, } = this.props; const fields = eventView.getFields(); if (titles?.length) { // Slice to match length of given titles columnOrder = columnOrder.slice(0, titles.length); } const resultsRow = columnOrder.map((column, index) => { const field = String(column.key); // TODO add a better abstraction for this in fieldRenderers. const fieldName = useAggregateAlias ? getAggregateAlias(field) : field; const fieldType = tableMeta[fieldName]; const fieldRenderer = getFieldRenderer(field, tableMeta, useAggregateAlias); let rendered = fieldRenderer(row, {organization, location}); const target = generateLink?.[field]?.(organization, row, location); if (target && !isEmptyObject(target)) { if (fields[index] === 'replayId') { rendered = ( {rendered} ); } else if (fields[index] === 'profile.id') { rendered = ( {rendered} ); } else { rendered = ( {rendered} ); } } const isNumeric = ['integer', 'number', 'duration'].includes(fieldType); const key = `${rowIndex}:${column.key}:${index}`; rendered = isNumeric ? ( {rendered} ) : ( {rendered} ); if (handleCellAction) { rendered = ( {rendered} ); } return {rendered}; }); return resultsRow; } renderResults() { const {isLoading, tableData, columnOrder} = this.props; let cells: React.ReactNode[] = []; if (isLoading) { return cells; } if (!tableData || !tableData.meta || !tableData.data) { return cells; } tableData.data.forEach((row, i: number) => { // Another check to appease tsc if (!tableData.meta) { return; } cells = cells.concat(this.renderRow(row, i, columnOrder, tableData.meta)); }); return cells; } render() { const {isLoading, tableData} = this.props; const hasResults = tableData?.meta && tableData.data?.length > 0; // Custom set the height so we don't have layout shift when results are loaded. const loader = ; return ( {this.renderResults()} ); } } function getProfileAnalyticsHandler(organization: Organization, referrer?: string) { return () => { let source; if (referrer === 'performance.transactions_summary') { source = 'performance.transactions_summary.overview'; } else { source = 'discover.transactions_table'; } trackAnalytics('profiling_views.go_to_flamegraph', { organization, source, }); }; } const HeadCellContainer = styled('div')` padding: ${space(2)}; `; const BodyCellContainer = styled('div')` padding: ${space(1)} ${space(2)}; ${p => p.theme.overflowEllipsis}; `; const StyledIconQuestion = styled(QuestionTooltip)` position: relative; top: 1px; left: 4px; `; export default TransactionsTable;