123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- import {Fragment} from 'react';
- import * as Sentry from '@sentry/react';
- import DateTime from 'sentry/components/dateTime';
- import GridEditable, {
- COL_WIDTH_UNDEFINED,
- GridColumnOrder,
- } from 'sentry/components/gridEditable';
- import ProjectBadge from 'sentry/components/idBadge/projectBadge';
- import Link from 'sentry/components/links/link';
- import PerformanceDuration from 'sentry/components/performanceDuration';
- import {IconCheckmark, IconClose} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {Trace} from 'sentry/types/profiling/core';
- import {defined} from 'sentry/utils';
- import {Container, NumberContainer} from 'sentry/utils/discover/styles';
- import {getShortEventId} from 'sentry/utils/events';
- import {
- generateProfileFlamechartRoute,
- generateProfileSummaryRouteWithQuery,
- } from 'sentry/utils/profiling/routes';
- import {renderTableHead} from 'sentry/utils/profiling/tableRenderer';
- import {useLocation} from 'sentry/utils/useLocation';
- import useOrganization from 'sentry/utils/useOrganization';
- import useProjects from 'sentry/utils/useProjects';
- const REQUIRE_PROJECT_COLUMNS: Set<TableColumnKey> = new Set([
- 'id',
- 'project_id',
- 'transaction_name',
- ]);
- interface ProfilesTableProps {
- error: string | null;
- isLoading: boolean;
- traces: Trace[];
- columnOrder?: Readonly<TableColumnKey[]>;
- }
- function ProfilesTable(props: ProfilesTableProps) {
- const location = useLocation();
- return (
- <Fragment>
- <GridEditable
- isLoading={props.isLoading}
- error={props.error}
- data={props.traces}
- columnOrder={(props.columnOrder ?? DEFAULT_COLUMN_ORDER).map(key => COLUMNS[key])}
- columnSortBy={[]}
- grid={{
- renderHeadCell: renderTableHead({rightAlignedColumns: RIGHT_ALIGNED_COLUMNS}),
- renderBodyCell: renderProfilesTableCell,
- }}
- location={location}
- />
- </Fragment>
- );
- }
- const RIGHT_ALIGNED_COLUMNS = new Set<TableColumnKey>(['trace_duration_ms']);
- function renderProfilesTableCell(
- column: TableColumn,
- dataRow: TableDataRow,
- rowIndex: number,
- columnIndex: number
- ) {
- return (
- <ProfilesTableCell
- column={column}
- dataRow={dataRow}
- rowIndex={rowIndex}
- columnIndex={columnIndex}
- />
- );
- }
- interface ProfilesTableCellProps {
- column: TableColumn;
- columnIndex: number;
- dataRow: TableDataRow;
- rowIndex: number;
- }
- function ProfilesTableCell({column, dataRow}: ProfilesTableCellProps) {
- const organization = useOrganization();
- const {projects} = useProjects();
- const location = useLocation();
- // Not all columns need the project, so small optimization to avoid
- // the linear lookup for every cell.
- const project = REQUIRE_PROJECT_COLUMNS.has(column.key)
- ? projects.find(proj => proj.id === dataRow.project_id)
- : undefined;
- if (REQUIRE_PROJECT_COLUMNS.has(column.key) && !defined(project)) {
- Sentry.withScope(scope => {
- scope.setFingerprint(['profiles table', 'cell', 'missing project']);
- scope.setTag('cell_key', column.key);
- scope.setTag('missing_project', dataRow.project_id);
- scope.setTag('available_project', projects.length);
- Sentry.captureMessage(`Project ${dataRow.project_id} missing for ${column.key}`);
- });
- }
- const value = dataRow[column.key];
- switch (column.key) {
- case 'id':
- if (!defined(project)) {
- // should never happen but just in case
- return <Container>{getShortEventId(dataRow.id)}</Container>;
- }
- const flamegraphTarget = generateProfileFlamechartRoute({
- orgSlug: organization.slug,
- projectSlug: project.slug,
- profileId: dataRow.id,
- });
- return (
- <Container>
- <Link to={flamegraphTarget}>{getShortEventId(dataRow.id)}</Link>
- </Container>
- );
- case 'project_id':
- if (!defined(project)) {
- // should never happen but just in case
- return <Container>{t('n/a')}</Container>;
- }
- return (
- <Container>
- <ProjectBadge project={project} avatarSize={16} />
- </Container>
- );
- case 'transaction_name':
- if (!defined(project)) {
- // should never happen but just in case
- return <Container>{t('n/a')}</Container>;
- }
- const profileSummaryTarget = generateProfileSummaryRouteWithQuery({
- query: location.query,
- orgSlug: organization.slug,
- projectSlug: project.slug,
- transaction: dataRow.transaction_name,
- });
- return (
- <Container>
- <Link to={profileSummaryTarget}>{value}</Link>
- </Container>
- );
- case 'version_name':
- return (
- <Container>
- {dataRow.version_code ? t('%s (build %s)', value, dataRow.version_code) : value}
- </Container>
- );
- case 'failed':
- return (
- <Container>
- {value ? (
- <IconClose size="sm" color="red300" isCircled />
- ) : (
- <IconCheckmark size="sm" color="green300" isCircled />
- )}
- </Container>
- );
- case 'timestamp':
- return (
- <Container>
- <DateTime date={value * 1000} year seconds timeZone />
- </Container>
- );
- case 'trace_duration_ms':
- return (
- <NumberContainer>
- <PerformanceDuration milliseconds={value} abbreviation />
- </NumberContainer>
- );
- default:
- return <Container>{value}</Container>;
- }
- }
- type TableColumnKey = keyof Trace;
- type NonTableColumnKey =
- | 'version_code'
- | 'device_locale'
- | 'device_manufacturer'
- | 'backtrace_available'
- | 'error_code'
- | 'error_code_name'
- | 'error_description'
- | 'span_annotations'
- | 'spans'
- | 'trace_annotations';
- type TableColumn = GridColumnOrder<TableColumnKey>;
- type TableDataRow = Omit<Record<TableColumnKey, any>, NonTableColumnKey> &
- Partial<Record<TableColumnKey, any>>;
- type TableColumnOrders = Omit<Record<TableColumnKey, TableColumn>, NonTableColumnKey>;
- const DEFAULT_COLUMN_ORDER: TableColumnKey[] = [
- 'failed',
- 'id',
- 'project_id',
- 'transaction_name',
- 'version_name',
- 'timestamp',
- 'trace_duration_ms',
- 'device_model',
- 'device_classification',
- ];
- const COLUMNS: TableColumnOrders = {
- id: {
- key: 'id',
- name: t('Profile ID'),
- width: COL_WIDTH_UNDEFINED,
- },
- project_id: {
- key: 'project_id',
- name: t('Project'),
- width: COL_WIDTH_UNDEFINED,
- },
- failed: {
- key: 'failed',
- name: t('Status'),
- width: 14, // make this as small as possible
- },
- version_name: {
- key: 'version_name',
- name: t('Version'),
- width: COL_WIDTH_UNDEFINED,
- },
- transaction_name: {
- key: 'transaction_name',
- name: t('Transaction Name'),
- width: COL_WIDTH_UNDEFINED,
- },
- timestamp: {
- key: 'timestamp',
- name: t('Timestamp'),
- width: COL_WIDTH_UNDEFINED,
- },
- trace_duration_ms: {
- key: 'trace_duration_ms',
- name: t('Duration'),
- width: COL_WIDTH_UNDEFINED,
- },
- device_model: {
- key: 'device_model',
- name: t('Device Model'),
- width: COL_WIDTH_UNDEFINED,
- },
- device_classification: {
- key: 'device_classification',
- name: t('Device Classification'),
- width: COL_WIDTH_UNDEFINED,
- },
- device_os_version: {
- key: 'device_os_version',
- name: t('Device OS Version'),
- width: COL_WIDTH_UNDEFINED,
- },
- };
- export {ProfilesTable};
|