mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
229 lines
7.4 KiB
TypeScript
229 lines
7.4 KiB
TypeScript
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<keyof Row>;
|
|
|
|
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<keyof Row>[] = [
|
|
{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 (
|
|
<DescriptionWrapper>
|
|
<ResourceIcon fileExtension={fileExtension} spanOp={row[SPAN_OP]} />
|
|
<SpanDescriptionCell
|
|
moduleName={ModuleName.HTTP}
|
|
projectId={row[PROJECT_ID]}
|
|
spanOp={row[SPAN_OP]}
|
|
description={row[SPAN_DESCRIPTION]}
|
|
group={row[SPAN_GROUP]}
|
|
/>
|
|
</DescriptionWrapper>
|
|
);
|
|
}
|
|
if (key === 'spm()') {
|
|
return <ThroughputCell rate={row[key]} unit={RESOURCE_THROUGHPUT_UNIT} />;
|
|
}
|
|
if (key === 'avg(http.response_content_length)') {
|
|
return <ResourceSizeCell bytes={row[key]} />;
|
|
}
|
|
if (key === `avg(span.self_time)`) {
|
|
return <DurationCell milliseconds={row[key]} />;
|
|
}
|
|
if (key === SPAN_OP) {
|
|
const fileExtension = row[SPAN_DESCRIPTION].split('.').pop() || '';
|
|
const spanOp = row[key];
|
|
if (fileExtension === 'js' || spanOp === 'resource.script') {
|
|
return <span>{t('JavaScript')}</span>;
|
|
}
|
|
if (fileExtension === 'css') {
|
|
return <span>{t('Stylesheet')}</span>;
|
|
}
|
|
if (FONT_FILE_EXTENSIONS.includes(fileExtension)) {
|
|
return <span>{t('Font')}</span>;
|
|
}
|
|
return <span>{spanOp}</span>;
|
|
}
|
|
if (key === 'time_spent_percentage()') {
|
|
return (
|
|
<TimeSpentCell
|
|
percentage={row[key]}
|
|
total={row[`sum(${SPAN_SELF_TIME})`]}
|
|
op={row[SPAN_OP]}
|
|
/>
|
|
);
|
|
}
|
|
return <span>{row[key]}</span>;
|
|
};
|
|
|
|
const handleCursor: CursorHandler = (newCursor, pathname, query) => {
|
|
browserHistory.push({
|
|
pathname,
|
|
query: {...query, [QueryParameterNames.SPANS_CURSOR]: newCursor},
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Fragment>
|
|
<GridEditable
|
|
data={tableData}
|
|
isLoading={isLoading}
|
|
columnOrder={columnOrder}
|
|
columnSortBy={[
|
|
{
|
|
key: sort.field,
|
|
order: sort.kind,
|
|
},
|
|
]}
|
|
grid={{
|
|
renderHeadCell: column =>
|
|
renderHeadCell({
|
|
column,
|
|
location,
|
|
sort,
|
|
}),
|
|
renderBodyCell,
|
|
}}
|
|
location={location}
|
|
/>
|
|
<Pagination pageLinks={pageLinks} onCursor={handleCursor} />
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
function ResourceIcon(props: {fileExtension: string; spanOp: string}) {
|
|
const {spanOp, fileExtension} = props;
|
|
|
|
if (spanOp === ResourceSpanOps.SCRIPT) {
|
|
return <PlatformIcon platform="javascript" />;
|
|
}
|
|
if (fileExtension === 'css') {
|
|
return <PlatformIcon platform="css" />;
|
|
}
|
|
if (FONT_FILE_EXTENSIONS.includes(fileExtension)) {
|
|
return <PlatformIcon platform="font" />;
|
|
}
|
|
if (spanOp === ResourceSpanOps.IMAGE || IMAGE_FILE_EXTENSIONS.includes(fileExtension)) {
|
|
return <IconImage color="black" legacySize="20px" />;
|
|
}
|
|
return <PlatformIcon platform="unknown" />;
|
|
}
|
|
|
|
export default ResourceTable;
|
|
|
|
const DescriptionWrapper = styled('div')`
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: ${space(1)};
|
|
`;
|