123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- import {Fragment} from 'react';
- import {browserHistory} from 'react-router';
- import {urlEncode} from '@sentry/utils';
- import {Location} from 'history';
- import GridEditable, {
- COL_WIDTH_UNDEFINED,
- GridColumnHeader,
- } from 'sentry/components/gridEditable';
- import SortLink from 'sentry/components/gridEditable/sortLink';
- import Link from 'sentry/components/links/link';
- import Pagination, {CursorHandler} from 'sentry/components/pagination';
- import {Organization} from 'sentry/types';
- import {MetaType} from 'sentry/utils/discover/eventView';
- import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
- import type {Sort} from 'sentry/utils/discover/fields';
- import {decodeScalar} from 'sentry/utils/queryString';
- import {useLocation} from 'sentry/utils/useLocation';
- import useOrganization from 'sentry/utils/useOrganization';
- import CountCell from 'sentry/views/starfish/components/tableCells/countCell';
- import DurationCell from 'sentry/views/starfish/components/tableCells/durationCell';
- import ThroughputCell from 'sentry/views/starfish/components/tableCells/throughputCell';
- import {TimeSpentCell} from 'sentry/views/starfish/components/tableCells/timeSpentCell';
- import {OverflowEllipsisTextContainer} from 'sentry/views/starfish/components/textAlign';
- import {useSpanList} from 'sentry/views/starfish/queries/useSpanList';
- import {ModuleName, SpanMetricsFields} from 'sentry/views/starfish/types';
- import {extractRoute} from 'sentry/views/starfish/utils/extractRoute';
- import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
- import {DataTitles} from 'sentry/views/starfish/views/spans/types';
- type Row = {
- 'http_error_count()': number;
- 'http_error_count_percent_change()': number;
- 'p95(span.self_time)': number;
- 'percentile_percent_change(span.self_time, 0.95)': number;
- 'span.description': string;
- 'span.domain': string;
- 'span.group': string;
- 'span.op': string;
- 'sps()': number;
- 'sps_percent_change()': number;
- 'time_spent_percentage()': number;
- };
- type Column = GridColumnHeader<keyof Row>;
- type ValidSort = Sort & {
- field: keyof Row;
- };
- type Props = {
- moduleName: ModuleName;
- sort: ValidSort;
- columnOrder?: Column[];
- endpoint?: string;
- limit?: number;
- method?: string;
- spanCategory?: string;
- };
- const {SPAN_SELF_TIME} = SpanMetricsFields;
- export const SORTABLE_FIELDS = new Set([
- `p95(${SPAN_SELF_TIME})`,
- `percentile_percent_change(${SPAN_SELF_TIME}, 0.95)`,
- 'sps()',
- 'sps_percent_change()',
- 'time_spent_percentage()',
- ]);
- export default function SpansTable({
- moduleName,
- sort,
- columnOrder,
- spanCategory,
- endpoint,
- method,
- limit = 25,
- }: Props) {
- const location = useLocation();
- const organization = useOrganization();
- const spansCursor = decodeScalar(location.query?.[QueryParameterNames.CURSOR]);
- const {isLoading, data, meta, pageLinks} = useSpanList(
- moduleName ?? ModuleName.ALL,
- undefined,
- spanCategory,
- [sort],
- limit,
- 'use-span-list',
- spansCursor
- );
- const handleCursor: CursorHandler = (cursor, pathname, query) => {
- browserHistory.push({
- pathname,
- query: {...query, [QueryParameterNames.CURSOR]: cursor},
- });
- };
- return (
- <Fragment>
- <GridEditable
- isLoading={isLoading}
- data={data as Row[]}
- columnOrder={columnOrder ?? getColumns(moduleName)}
- columnSortBy={[
- {
- key: sort.field,
- order: sort.kind,
- },
- ]}
- grid={{
- renderHeadCell: column => renderHeadCell(column, sort, location),
- renderBodyCell: (column, row) =>
- renderBodyCell(column, row, meta, location, organization, endpoint, method),
- }}
- location={location}
- />
- <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
- </Fragment>
- );
- }
- function renderHeadCell(column: Column, sort: Sort, location: Location) {
- return (
- <SortLink
- align="left"
- canSort={SORTABLE_FIELDS.has(column.key)}
- direction={sort.field === column.key ? sort.kind : undefined}
- title={column.name}
- generateSortLink={() => {
- return {
- ...location,
- query: {
- ...location.query,
- [QueryParameterNames.SORT]: `-${column.key}`,
- },
- };
- }}
- />
- );
- }
- function renderBodyCell(
- column: Column,
- row: Row,
- meta: MetaType | undefined,
- location: Location,
- organization: Organization,
- endpoint?: string,
- method?: string
- ): React.ReactNode {
- if (column.key === 'span.description') {
- return (
- <OverflowEllipsisTextContainer>
- {row['span.group'] ? (
- <Link
- to={`/starfish/${extractRoute(location)}/span/${row['span.group']}${
- endpoint && method ? `?${urlEncode({endpoint, method})}` : ''
- }`}
- >
- {row['span.description'] || '<null>'}
- </Link>
- ) : (
- row['span.description'] || '<null>'
- )}
- </OverflowEllipsisTextContainer>
- );
- }
- if (column.key === 'time_spent_percentage()') {
- return (
- <TimeSpentCell
- timeSpentPercentage={row['time_spent_percentage()']}
- totalSpanTime={row[`sum(${SPAN_SELF_TIME})`]}
- />
- );
- }
- if (column.key === 'sps()') {
- return <ThroughputCell throughputPerSecond={row['sps()']} />;
- }
- if (column.key === 'p95(span.self_time)') {
- return <DurationCell milliseconds={row['p95(span.self_time)']} />;
- }
- if (column.key === 'http_error_count()') {
- return <CountCell count={row['http_error_count()']} />;
- }
- if (!meta || !meta?.fields) {
- return row[column.key];
- }
- const renderer = getFieldRenderer(column.key, meta, false);
- const rendered = renderer(row, {location, organization});
- return rendered;
- }
- function getDomainHeader(moduleName: ModuleName) {
- if (moduleName === ModuleName.HTTP) {
- return 'Host';
- }
- if (moduleName === ModuleName.DB) {
- return 'Table';
- }
- return 'Domain';
- }
- function getDescriptionHeader(moduleName: ModuleName) {
- if (moduleName === ModuleName.HTTP) {
- return 'URL';
- }
- if (moduleName === ModuleName.DB) {
- return 'Query';
- }
- return 'Description';
- }
- function getColumns(moduleName: ModuleName): Column[] {
- const description = getDescriptionHeader(moduleName);
- const domain = getDomainHeader(moduleName);
- const order: Column[] = [
- {
- key: 'span.op',
- name: 'Operation',
- width: 120,
- },
- {
- key: 'span.description',
- name: description,
- width: COL_WIDTH_UNDEFINED,
- },
- ...(moduleName !== ModuleName.ALL
- ? [
- {
- key: 'span.domain',
- name: domain,
- width: COL_WIDTH_UNDEFINED,
- } as Column,
- ]
- : []),
- {
- key: 'sps()',
- name: 'Throughput',
- width: COL_WIDTH_UNDEFINED,
- },
- {
- key: 'sps_percent_change()',
- name: DataTitles.change,
- width: COL_WIDTH_UNDEFINED,
- },
- {
- key: `p95(${SPAN_SELF_TIME})`,
- name: DataTitles.p95,
- width: COL_WIDTH_UNDEFINED,
- },
- {
- key: `percentile_percent_change(${SPAN_SELF_TIME}, 0.95)`,
- name: DataTitles.change,
- width: COL_WIDTH_UNDEFINED,
- },
- ...(moduleName === ModuleName.HTTP
- ? [
- {
- key: 'http_error_count()',
- name: DataTitles.errorCount,
- width: COL_WIDTH_UNDEFINED,
- } as Column,
- {
- key: 'http_error_count_percent_change()',
- name: DataTitles.change,
- width: COL_WIDTH_UNDEFINED,
- } as Column,
- ]
- : []),
- {
- key: 'time_spent_percentage()',
- name: DataTitles.timeSpent,
- width: COL_WIDTH_UNDEFINED,
- },
- ];
- return order;
- }
- export function isAValidSort(sort: Sort): sort is ValidSort {
- return SORTABLE_FIELDS.has(sort.field);
- }
|