123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- import type {CSSProperties} from 'react';
- import {Fragment} from 'react';
- import styled from '@emotion/styled';
- import DateTime from 'sentry/components/dateTime';
- import type {GridColumnHeader} from 'sentry/components/gridEditable';
- import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
- import Link from 'sentry/components/links/link';
- import QuestionTooltip from 'sentry/components/questionTooltip';
- import {t} from 'sentry/locale';
- import type {NewQuery} from 'sentry/types';
- import EventView from 'sentry/utils/discover/eventView';
- import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
- import {SPAN_OP_RELATIVE_BREAKDOWN_FIELD} from 'sentry/utils/discover/fields';
- import {DiscoverDatasets} from 'sentry/utils/discover/types';
- import {MutableSearch} from 'sentry/utils/tokenizeSearch';
- import {useLocation} from 'sentry/utils/useLocation';
- import useOrganization from 'sentry/utils/useOrganization';
- import {normalizeUrl} from 'sentry/utils/withDomainRequired';
- import {DurationComparisonCell} from 'sentry/views/starfish/components/samplesTable/common';
- import useErrorSamples from 'sentry/views/starfish/components/samplesTable/useErrorSamples';
- import {DurationCell} from 'sentry/views/starfish/components/tableCells/durationCell';
- import {
- OverflowEllipsisTextContainer,
- TextAlignLeft,
- TextAlignRight,
- } from 'sentry/views/starfish/components/textAlign';
- import {DataTitles} from 'sentry/views/starfish/views/spans/types';
- import type {SampleFilter} from 'sentry/views/starfish/views/webServiceView/endpointOverview';
- type Keys =
- | 'id'
- | 'profile_id'
- | 'timestamp'
- | 'transaction.duration'
- | 'avg_comparison'
- | 'span_ops_breakdown.relative'
- | 'http.status_code'
- | 'transaction.status';
- type TableColumnHeader = GridColumnHeader<Keys>;
- type Props = {
- data: DataRow[];
- isLoading: boolean;
- queryConditions: string[];
- sampleFilter: SampleFilter;
- setHighlightedId: (string) => void;
- averageDuration?: number;
- highlightedId?: string;
- };
- export type DataRow = {
- 'http.status_code': number;
- id: string;
- profile_id: string;
- 'spans.browser': number;
- 'spans.db': number;
- 'spans.http': number;
- 'spans.resource': number;
- 'spans.ui': number;
- timestamp: string;
- 'transaction.duration': number;
- 'transaction.status': string;
- };
- export function TransactionSamplesTable({
- queryConditions,
- sampleFilter,
- data,
- isLoading,
- averageDuration,
- setHighlightedId,
- highlightedId,
- }: Props) {
- const location = useLocation();
- const organization = useOrganization();
- const query = new MutableSearch(queryConditions);
- const savedQuery: NewQuery = {
- id: undefined,
- name: 'Endpoint Overview Samples',
- query: query.formatString(),
- fields: [],
- dataset: DiscoverDatasets.DISCOVER,
- version: 2,
- };
- const columnOrder: TableColumnHeader[] = [
- {
- key: 'id',
- name: 'Event ID',
- width: 100,
- },
- ...(sampleFilter === 'ALL'
- ? [
- {
- key: 'profile_id',
- name: 'Profile ID',
- width: 140,
- } as TableColumnHeader,
- {
- key: SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
- name: 'Operation Duration',
- width: 180,
- } as TableColumnHeader,
- {
- key: 'timestamp',
- name: 'Timestamp',
- width: 230,
- } as TableColumnHeader,
- {
- key: 'transaction.duration',
- name: DataTitles.duration,
- width: 100,
- } as TableColumnHeader,
- {
- key: 'avg_comparison',
- name: 'Compared to Average',
- width: 100,
- } as TableColumnHeader,
- ]
- : [
- {
- key: 'http.status_code',
- name: 'Response Code',
- width: COL_WIDTH_UNDEFINED,
- } as TableColumnHeader,
- {
- key: 'transaction.status',
- name: 'Status',
- width: COL_WIDTH_UNDEFINED,
- } as TableColumnHeader,
- {
- key: 'timestamp',
- name: 'Timestamp',
- width: 230,
- } as TableColumnHeader,
- ]),
- ];
- const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
- const {isLoading: isErrorSamplesLoading, data: errorSamples} =
- useErrorSamples(eventView);
- function renderHeadCell(column: GridColumnHeader): React.ReactNode {
- if (column.key === 'avg_comparison' || column.key === 'transaction.duration') {
- return (
- <TextAlignRight>
- <OverflowEllipsisTextContainer>{column.name}</OverflowEllipsisTextContainer>
- </TextAlignRight>
- );
- }
- if (column.key === SPAN_OP_RELATIVE_BREAKDOWN_FIELD) {
- return (
- <Fragment>
- {column.name}
- <StyledIconQuestion
- size="xs"
- position="top"
- title={t(
- `Span durations are summed over the course of an entire transaction. Any overlapping spans are only counted once.`
- )}
- />
- </Fragment>
- );
- }
- return <OverflowEllipsisTextContainer>{column.name}</OverflowEllipsisTextContainer>;
- }
- function renderBodyCell(column: TableColumnHeader, row: DataRow): React.ReactNode {
- const commonProps = {
- style: (row.id === highlightedId
- ? {fontWeight: 'bold'}
- : {}) satisfies CSSProperties,
- onMouseEnter: () => setHighlightedId(row.id),
- };
- if (column.key === 'id') {
- return (
- <Link
- {...commonProps}
- to={normalizeUrl(
- `/organizations/${organization.slug}/performance/${row['project.name']}:${row.id}`
- )}
- >
- {row.id.slice(0, 8)}
- </Link>
- );
- }
- if (column.key === 'profile_id') {
- return row.profile_id ? (
- <Link
- {...commonProps}
- to={normalizeUrl(
- `/organizations/${organization.slug}/profiling/profile/${row['project.name']}/${row.profile_id}/flamegraph/`
- )}
- >
- {row.profile_id.slice(0, 8)}
- </Link>
- ) : (
- <div {...commonProps}>(no value)</div>
- );
- }
- if (column.key === 'transaction.duration') {
- return <DurationCell milliseconds={row['transaction.duration']} />;
- }
- if (column.key === 'timestamp') {
- return <DateTime {...commonProps} date={row[column.key]} year timeZone seconds />;
- }
- if (column.key === 'avg_comparison') {
- return (
- <DurationComparisonContainer {...commonProps}>
- <DurationComparisonCell
- duration={row['transaction.duration']}
- compareToDuration={averageDuration ?? 0}
- />
- </DurationComparisonContainer>
- );
- }
- if (column.key === SPAN_OP_RELATIVE_BREAKDOWN_FIELD) {
- return getFieldRenderer(column.key, {})(row, {
- location,
- organization,
- eventView,
- });
- }
- return <TextAlignLeft {...commonProps}>{row[column.key]}</TextAlignLeft>;
- }
- return (
- <div onMouseLeave={() => setHighlightedId(undefined)}>
- <GridEditable
- isLoading={sampleFilter === 'ALL' ? isLoading : isErrorSamplesLoading}
- data={sampleFilter === 'ALL' ? (data as DataRow[]) : (errorSamples as DataRow[])}
- columnOrder={columnOrder}
- columnSortBy={[]}
- location={location}
- grid={{
- renderHeadCell,
- renderBodyCell,
- }}
- />
- </div>
- );
- }
- const DurationComparisonContainer = styled('div')`
- text-align: right;
- width: 100%;
- display: inline-block;
- `;
- const StyledIconQuestion = styled(QuestionTooltip)`
- position: relative;
- left: 4px;
- `;
|