import {Fragment, useEffect} from 'react'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import {PlatformIcon} from 'platformicons'; import type {GridColumnHeader, GridColumnOrder} from 'sentry/components/gridEditable'; import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable'; import type {CursorHandler} from 'sentry/components/pagination'; import Pagination from 'sentry/components/pagination'; import {IconImage} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {DismissId, usePageAlert} from 'sentry/utils/performance/contexts/pageAlert'; import {decodeScalar} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import {RESOURCE_THROUGHPUT_UNIT} from 'sentry/views/performance/browser/resources'; import { FONT_FILE_EXTENSIONS, IMAGE_FILE_EXTENSIONS, } from 'sentry/views/performance/browser/resources/shared/constants'; import {ResourceSpanOps} from 'sentry/views/performance/browser/resources/shared/types'; import type {ValidSort} from 'sentry/views/performance/browser/resources/utils/useResourceSort'; import {useResourcesQuery} from 'sentry/views/performance/browser/resources/utils/useResourcesQuery'; import {DurationCell} from 'sentry/views/starfish/components/tableCells/durationCell'; import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell'; import ResourceSizeCell from 'sentry/views/starfish/components/tableCells/resourceSizeCell'; import {SpanDescriptionCell} from 'sentry/views/starfish/components/tableCells/spanDescriptionCell'; import {ThroughputCell} from 'sentry/views/starfish/components/tableCells/throughputCell'; import {TimeSpentCell} from 'sentry/views/starfish/components/tableCells/timeSpentCell'; import {ModuleName, SpanFunction, SpanMetricsField} from 'sentry/views/starfish/types'; import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters'; import {DataTitles, getThroughputTitle} from 'sentry/views/starfish/views/spans/types'; const { SPAN_DESCRIPTION, SPAN_OP, SPAN_SELF_TIME, HTTP_RESPONSE_CONTENT_LENGTH, PROJECT_ID, SPAN_GROUP, } = SpanMetricsField; const {TIME_SPENT_PERCENTAGE} = SpanFunction; const {SPM} = SpanFunction; const RESOURCE_SIZE_ALERT = t( `If you're noticing unusually large resource sizes, try updating to SDK version 7.82.0 or higher.` ); type Row = { 'avg(http.response_content_length)': number; 'avg(span.self_time)': number; 'project.id': number; 'span.description': string; 'span.group': string; 'span.op': `resource.${'script' | 'img' | 'css' | 'iframe' | string}`; 'spm()': number; 'sum(span.self_time)': number; 'time_spent_percentage()': number; }; type Column = GridColumnHeader; type Props = { sort: ValidSort; defaultResourceTypes?: string[]; }; function ResourceTable({sort, defaultResourceTypes}: Props) { const location = useLocation(); const cursor = decodeScalar(location.query?.[QueryParameterNames.SPANS_CURSOR]); const {setPageInfo, pageAlert} = usePageAlert(); const {data, isLoading, pageLinks} = useResourcesQuery({ sort, defaultResourceTypes, cursor, referrer: 'api.performance.browser.resources.main-table', }); const columnOrder: GridColumnOrder[] = [ {key: SPAN_DESCRIPTION, width: COL_WIDTH_UNDEFINED, name: t('Resource Description')}, { key: `${SPM}()`, width: COL_WIDTH_UNDEFINED, name: getThroughputTitle('http'), }, {key: `avg(${SPAN_SELF_TIME})`, width: COL_WIDTH_UNDEFINED, name: DataTitles.avg}, { key: `${TIME_SPENT_PERCENTAGE}()`, width: COL_WIDTH_UNDEFINED, name: DataTitles.timeSpent, }, { key: `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`, width: COL_WIDTH_UNDEFINED, name: DataTitles['avg(http.response_content_length)'], }, ]; const tableData: Row[] = data; useEffect(() => { if (pageAlert?.message !== RESOURCE_SIZE_ALERT) { for (const row of tableData) { const encodedSize = row[`avg(${HTTP_RESPONSE_CONTENT_LENGTH})`]; if (encodedSize >= 2147483647) { setPageInfo(RESOURCE_SIZE_ALERT, {dismissId: DismissId.RESOURCE_SIZE_ALERT}); break; } } } }, [tableData, setPageInfo, pageAlert?.message]); const renderBodyCell = (col: Column, row: Row) => { const {key} = col; if (key === SPAN_DESCRIPTION) { const fileExtension = row[SPAN_DESCRIPTION].split('.').pop() || ''; return ( ); } if (key === 'spm()') { return ; } if (key === 'avg(http.response_content_length)') { return ; } if (key === `avg(span.self_time)`) { return ; } if (key === SPAN_OP) { const fileExtension = row[SPAN_DESCRIPTION].split('.').pop() || ''; const spanOp = row[key]; if (fileExtension === 'js' || spanOp === 'resource.script') { return {t('JavaScript')}; } if (fileExtension === 'css') { return {t('Stylesheet')}; } if (FONT_FILE_EXTENSIONS.includes(fileExtension)) { return {t('Font')}; } return {spanOp}; } if (key === 'time_spent_percentage()') { return ( ); } return {row[key]}; }; const handleCursor: CursorHandler = (newCursor, pathname, query) => { browserHistory.push({ pathname, query: {...query, [QueryParameterNames.SPANS_CURSOR]: newCursor}, }); }; return ( renderHeadCell({ column, location, sort, }), renderBodyCell, }} location={location} /> ); } function ResourceIcon(props: {fileExtension: string; spanOp: string}) { const {spanOp, fileExtension} = props; if (spanOp === ResourceSpanOps.SCRIPT) { return ; } if (fileExtension === 'css') { return ; } if (FONT_FILE_EXTENSIONS.includes(fileExtension)) { return ; } if (spanOp === ResourceSpanOps.IMAGE || IMAGE_FILE_EXTENSIONS.includes(fileExtension)) { return ; } return ; } export default ResourceTable; const DescriptionWrapper = styled('div')` display: flex; flex-wrap: wrap; gap: ${space(1)}; `;