123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- import {Fragment} from 'react';
- import type {Location} from 'history';
- import type {GridColumnHeader} from 'sentry/components/gridEditable';
- import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
- import Link from 'sentry/components/links/link';
- import Pagination, {type CursorHandler} from 'sentry/components/pagination';
- import {t} from 'sentry/locale';
- import type {Organization} from 'sentry/types/organization';
- import type {Project} from 'sentry/types/project';
- import {browserHistory} from 'sentry/utils/browserHistory';
- import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
- import type {ColumnType} from 'sentry/utils/discover/fields';
- import {Container as TableCellContainer} from 'sentry/utils/discover/styles';
- import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
- import {decodeScalar} from 'sentry/utils/queryString';
- import {MutableSearch} from 'sentry/utils/tokenizeSearch';
- import useLocationQuery from 'sentry/utils/url/useLocationQuery';
- import {useLocation} from 'sentry/utils/useLocation';
- import useOrganization from 'sentry/utils/useOrganization';
- import {renderHeadCell} from 'sentry/views/insights/common/components/tableCells/renderHeadCell';
- import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
- import {QueryParameterNames} from 'sentry/views/insights/common/views/queryParameters';
- import {
- type DomainView,
- useDomainViewFilters,
- } from 'sentry/views/insights/pages/useFilters';
- import {
- SpanMetricsField,
- type SpanMetricsQueryFilters,
- } from 'sentry/views/insights/types';
- import {spanDetailsRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionSpans/spanDetails/utils';
- import {useSpansTabTableSort} from 'sentry/views/performance/transactionSummary/transactionSpans/useSpansTabTableSort';
- type DataRow = {
- [SpanMetricsField.SPAN_OP]: string;
- [SpanMetricsField.SPAN_DESCRIPTION]: string;
- [SpanMetricsField.SPAN_GROUP]: string;
- 'avg(span.duration)': number;
- 'spm()': number;
- 'sum(span.duration)': number;
- };
- type ColumnKeys =
- | SpanMetricsField.SPAN_OP
- | SpanMetricsField.SPAN_DESCRIPTION
- | 'spm()'
- | `avg(${SpanMetricsField.SPAN_DURATION})`
- | `sum(${SpanMetricsField.SPAN_DURATION})`;
- type Column = GridColumnHeader<ColumnKeys>;
- const COLUMN_ORDER: Column[] = [
- {
- key: SpanMetricsField.SPAN_OP,
- name: t('Span Operation'),
- width: COL_WIDTH_UNDEFINED,
- },
- {
- key: SpanMetricsField.SPAN_DESCRIPTION,
- name: t('Span Description'),
- width: COL_WIDTH_UNDEFINED,
- },
- {
- key: 'spm()',
- name: t('Throughput'),
- width: COL_WIDTH_UNDEFINED,
- },
- {
- key: `avg(${SpanMetricsField.SPAN_DURATION})`,
- name: t('Avg Duration'),
- width: COL_WIDTH_UNDEFINED,
- },
- {
- key: `sum(${SpanMetricsField.SPAN_DURATION})`,
- name: t('Time Spent'),
- width: COL_WIDTH_UNDEFINED,
- },
- ];
- const COLUMN_TYPE: Record<ColumnKeys, ColumnType> = {
- [SpanMetricsField.SPAN_OP]: 'string',
- [SpanMetricsField.SPAN_DESCRIPTION]: 'string',
- ['spm()']: 'rate',
- [`avg(${SpanMetricsField.SPAN_DURATION})`]: 'duration',
- [`sum(${SpanMetricsField.SPAN_DURATION})`]: 'duration',
- };
- const LIMIT = 12;
- type Props = {
- project: Project | undefined;
- query: string;
- transactionName: string;
- };
- export default function SpanMetricsTable(props: Props) {
- const {project, transactionName, query: search} = props;
- const organization = useOrganization();
- const location = useLocation();
- const sort = useSpansTabTableSort();
- const domainViewFilters = useDomainViewFilters();
- const query = useLocationQuery({
- fields: {
- spansCursor: decodeScalar,
- spanOp: decodeScalar,
- },
- });
- const {spansCursor, spanOp} = query;
- const filters: SpanMetricsQueryFilters = {
- transaction: transactionName,
- ['span.op']: spanOp,
- };
- const handleCursor: CursorHandler = (cursor, pathname, q) => {
- browserHistory.push({
- pathname,
- query: {...q, [QueryParameterNames.SPANS_CURSOR]: cursor},
- });
- };
- const mutableSearch = MutableSearch.fromQueryObject(filters);
- mutableSearch.addStringMultiFilter(search);
- const {data, isPending, pageLinks} = useSpanMetrics(
- {
- search: mutableSearch,
- fields: [
- SpanMetricsField.SPAN_OP,
- SpanMetricsField.SPAN_DESCRIPTION,
- SpanMetricsField.SPAN_GROUP,
- `spm()`,
- `avg(${SpanMetricsField.SPAN_DURATION})`,
- `sum(${SpanMetricsField.SPAN_DURATION})`,
- ],
- sorts: [sort],
- cursor: spansCursor,
- limit: LIMIT,
- },
- 'api.performance.transaction-spans'
- );
- return (
- <Fragment>
- <VisuallyCompleteWithData
- id="TransactionSpans-SpanMetricsTable"
- hasData={!!data?.length}
- isLoading={isPending}
- >
- <GridEditable
- isLoading={isPending}
- data={data}
- columnOrder={COLUMN_ORDER}
- columnSortBy={[
- {
- key: sort.field,
- order: sort.kind,
- },
- ]}
- grid={{
- renderHeadCell: column =>
- renderHeadCell({
- column,
- location,
- sort,
- }),
- renderBodyCell: renderBodyCell(
- location,
- organization,
- transactionName,
- project,
- domainViewFilters?.view
- ),
- }}
- />
- </VisuallyCompleteWithData>
- <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
- </Fragment>
- );
- }
- function renderBodyCell(
- location: Location,
- organization: Organization,
- transactionName: string,
- project?: Project,
- view?: DomainView
- ) {
- return function (column: Column, dataRow: DataRow): React.ReactNode {
- if (column.key === SpanMetricsField.SPAN_OP) {
- const target = spanDetailsRouteWithQuery({
- orgSlug: organization.slug,
- transaction: transactionName,
- query: location.query,
- spanSlug: {op: dataRow['span.op'], group: ''},
- projectID: project?.id,
- view,
- });
- return (
- <TableCellContainer>
- <Link to={target}>{dataRow[column.key]}</Link>
- </TableCellContainer>
- );
- }
- if (column.key === SpanMetricsField.SPAN_DESCRIPTION) {
- if (!dataRow['span.group']) {
- return <TableCellContainer>{'\u2014'}</TableCellContainer>;
- }
- const target = spanDetailsRouteWithQuery({
- orgSlug: organization.slug,
- transaction: transactionName,
- query: location.query,
- spanSlug: {op: dataRow['span.op'], group: dataRow['span.group']},
- projectID: project?.id,
- view,
- });
- return (
- <TableCellContainer>
- <Link to={target}>{dataRow[column.key]}</Link>
- </TableCellContainer>
- );
- }
- const fieldRenderer = getFieldRenderer(column.key, COLUMN_TYPE, false);
- const rendered = fieldRenderer(dataRow, {location, organization});
- return rendered;
- };
- }
|