|
@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
|
|
|
import {useQuery} from '@tanstack/react-query';
|
|
|
import moment from 'moment';
|
|
|
|
|
|
+import Duration from 'sentry/components/duration';
|
|
|
import GridEditable, {GridColumnHeader} from 'sentry/components/gridEditable';
|
|
|
import Link from 'sentry/components/links/link';
|
|
|
import {t} from 'sentry/locale';
|
|
@@ -11,8 +12,12 @@ import {useLocation} from 'sentry/utils/useLocation';
|
|
|
import Chart from 'sentry/views/starfish/components/chart';
|
|
|
import Detail from 'sentry/views/starfish/components/detailPanel';
|
|
|
import {HOST} from 'sentry/views/starfish/modules/APIModule/APIModuleView';
|
|
|
-import {renderHeadCell} from 'sentry/views/starfish/modules/APIModule/endpointTable';
|
|
|
import {
|
|
|
+ OverflowEllipsisTextContainer,
|
|
|
+ renderHeadCell,
|
|
|
+} from 'sentry/views/starfish/modules/APIModule/endpointTable';
|
|
|
+import {
|
|
|
+ getEndpointDetailErrorRateSeriesQuery,
|
|
|
getEndpointDetailQuery,
|
|
|
getEndpointDetailSeriesQuery,
|
|
|
} from 'sentry/views/starfish/modules/APIModule/queries';
|
|
@@ -38,7 +43,7 @@ const COLUMN_ORDER = [
|
|
|
{
|
|
|
key: 'transaction',
|
|
|
name: 'Transaction',
|
|
|
- width: 400,
|
|
|
+ width: 350,
|
|
|
},
|
|
|
{
|
|
|
key: 'count',
|
|
@@ -62,23 +67,33 @@ export default function EndpointDetail({
|
|
|
|
|
|
function EndpointDetailBody({row}: EndpointDetailBodyProps) {
|
|
|
const location = useLocation();
|
|
|
- const theme = useTheme();
|
|
|
const seriesQuery = getEndpointDetailSeriesQuery(row.description);
|
|
|
+ const errorRateSeriesQuery = getEndpointDetailErrorRateSeriesQuery(row.description);
|
|
|
const tableQuery = getEndpointDetailQuery(row.description);
|
|
|
const {isLoading: seriesIsLoading, data: seriesData} = useQuery({
|
|
|
- queryKey: ['endpointDetailSeries'],
|
|
|
+ queryKey: [seriesQuery],
|
|
|
queryFn: () => fetch(`${HOST}/?query=${seriesQuery}`).then(res => res.json()),
|
|
|
- retry: true,
|
|
|
+ retry: false,
|
|
|
+ initialData: [],
|
|
|
+ });
|
|
|
+ const {isLoading: errorRateSeriesIsLoading, data: errorRateSeriesData} = useQuery({
|
|
|
+ queryKey: [errorRateSeriesQuery],
|
|
|
+ queryFn: () =>
|
|
|
+ fetch(`${HOST}/?query=${errorRateSeriesQuery}`).then(res => res.json()),
|
|
|
+ retry: false,
|
|
|
initialData: [],
|
|
|
});
|
|
|
const {isLoading: tableIsLoading, data: tableData} = useQuery({
|
|
|
- queryKey: ['endpointDetailTable'],
|
|
|
+ queryKey: [tableQuery],
|
|
|
queryFn: () => fetch(`${HOST}/?query=${tableQuery}`).then(res => res.json()),
|
|
|
- retry: true,
|
|
|
+ retry: false,
|
|
|
initialData: [],
|
|
|
});
|
|
|
- const [countSeries, p50Series] = endpointDetailDataToChartData(seriesData).map(series =>
|
|
|
- zeroFillSeries(series, moment.duration(12, 'hours'))
|
|
|
+ const [p50Series, p95Series, countSeries] = endpointDetailDataToChartData(
|
|
|
+ seriesData
|
|
|
+ ).map(series => zeroFillSeries(series, moment.duration(12, 'hours')));
|
|
|
+ const [errorRateSeries] = endpointDetailDataToChartData(errorRateSeriesData).map(
|
|
|
+ series => zeroFillSeries(series, moment.duration(12, 'hours'))
|
|
|
);
|
|
|
|
|
|
return (
|
|
@@ -94,41 +109,44 @@ function EndpointDetailBody({row}: EndpointDetailBodyProps) {
|
|
|
<SubHeader>{t('Domain')}</SubHeader>
|
|
|
<pre>{row?.domain}</pre>
|
|
|
<FlexRowContainer>
|
|
|
+ <FlexRowItem>
|
|
|
+ <SubHeader>{t('Duration (P50)')}</SubHeader>
|
|
|
+ <SubSubHeader>{'123ms'}</SubSubHeader>
|
|
|
+ <APIDetailChart
|
|
|
+ series={p50Series}
|
|
|
+ isLoading={seriesIsLoading}
|
|
|
+ index={2}
|
|
|
+ outOf={4}
|
|
|
+ />
|
|
|
+ </FlexRowItem>
|
|
|
+ <FlexRowItem>
|
|
|
+ <SubHeader>{t('Duration (P95)')}</SubHeader>
|
|
|
+ <SubSubHeader>{'123ms'}</SubSubHeader>
|
|
|
+ <APIDetailChart
|
|
|
+ series={p95Series}
|
|
|
+ isLoading={seriesIsLoading}
|
|
|
+ index={3}
|
|
|
+ outOf={4}
|
|
|
+ />
|
|
|
+ </FlexRowItem>
|
|
|
<FlexRowItem>
|
|
|
<SubHeader>{t('Throughput')}</SubHeader>
|
|
|
<SubSubHeader>{row.count}</SubSubHeader>
|
|
|
- <Chart
|
|
|
- statsPeriod="24h"
|
|
|
- height={140}
|
|
|
- data={[countSeries]}
|
|
|
- start=""
|
|
|
- end=""
|
|
|
- loading={seriesIsLoading}
|
|
|
- utc={false}
|
|
|
- disableMultiAxis
|
|
|
- stacked
|
|
|
- isLineChart
|
|
|
- disableXAxis
|
|
|
- hideYAxisSplitLine
|
|
|
+ <APIDetailChart
|
|
|
+ series={countSeries}
|
|
|
+ isLoading={seriesIsLoading}
|
|
|
+ index={0}
|
|
|
+ outOf={4}
|
|
|
/>
|
|
|
</FlexRowItem>
|
|
|
<FlexRowItem>
|
|
|
- <SubHeader>{t('Duration (P50)')}</SubHeader>
|
|
|
- <SubSubHeader>{'123ms'}</SubSubHeader>
|
|
|
- <Chart
|
|
|
- statsPeriod="24h"
|
|
|
- height={140}
|
|
|
- data={[p50Series]}
|
|
|
- start=""
|
|
|
- end=""
|
|
|
- loading={seriesIsLoading}
|
|
|
- utc={false}
|
|
|
- chartColors={[theme.charts.getColorPalette(4)[3]]}
|
|
|
- disableMultiAxis
|
|
|
- stacked
|
|
|
- isLineChart
|
|
|
- disableXAxis
|
|
|
- hideYAxisSplitLine
|
|
|
+ <SubHeader>{t('Error Rate')}</SubHeader>
|
|
|
+ <SubSubHeader>{row.count}</SubSubHeader>
|
|
|
+ <APIDetailChart
|
|
|
+ series={errorRateSeries}
|
|
|
+ isLoading={errorRateSeriesIsLoading}
|
|
|
+ index={1}
|
|
|
+ outOf={4}
|
|
|
/>
|
|
|
</FlexRowItem>
|
|
|
</FlexRowContainer>
|
|
@@ -165,22 +183,72 @@ function renderBodyCell(
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- return row[column.key];
|
|
|
+ if (column.key.toString().match(/^p\d\d/)) {
|
|
|
+ return <Duration seconds={row[column.key] / 1000} fixedDigits={2} abbreviation />;
|
|
|
+ }
|
|
|
+
|
|
|
+ return <OverflowEllipsisTextContainer>{row[column.key]}</OverflowEllipsisTextContainer>;
|
|
|
}
|
|
|
|
|
|
function endpointDetailDataToChartData(data: any) {
|
|
|
- const countSeries = {seriesName: 'count()', data: [] as any[]};
|
|
|
- const p50Series = {seriesName: 'p50()', data: [] as any[]};
|
|
|
- data.forEach(({count, p50, interval}: any) => {
|
|
|
- countSeries.data.push({value: count, name: interval});
|
|
|
- p50Series.data.push({value: p50, name: interval});
|
|
|
+ const series = [] as any[];
|
|
|
+ if (data.length > 0) {
|
|
|
+ Object.keys(data[0])
|
|
|
+ .filter(key => key !== 'interval')
|
|
|
+ .forEach(key => {
|
|
|
+ series.push({seriesName: `${key}()`, data: [] as any[]});
|
|
|
+ });
|
|
|
+ }
|
|
|
+ data.forEach(point => {
|
|
|
+ Object.keys(point).forEach(key => {
|
|
|
+ if (key !== 'interval') {
|
|
|
+ series
|
|
|
+ .find(serie => serie.seriesName === `${key}()`)
|
|
|
+ ?.data.push({
|
|
|
+ name: point.interval,
|
|
|
+ value: point[key],
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
});
|
|
|
- return [countSeries, p50Series];
|
|
|
+ return series;
|
|
|
+}
|
|
|
+
|
|
|
+function APIDetailChart(props: {
|
|
|
+ index: number;
|
|
|
+ isLoading: boolean;
|
|
|
+ outOf: number;
|
|
|
+ series: any;
|
|
|
+}) {
|
|
|
+ const theme = useTheme();
|
|
|
+ return (
|
|
|
+ <Chart
|
|
|
+ statsPeriod="24h"
|
|
|
+ height={110}
|
|
|
+ data={props.series ? [props.series] : []}
|
|
|
+ start=""
|
|
|
+ end=""
|
|
|
+ loading={props.isLoading}
|
|
|
+ utc={false}
|
|
|
+ disableMultiAxis
|
|
|
+ stacked
|
|
|
+ isLineChart
|
|
|
+ disableXAxis
|
|
|
+ hideYAxisSplitLine
|
|
|
+ chartColors={[theme.charts.getColorPalette(props.outOf - 2)[props.index]]}
|
|
|
+ grid={{
|
|
|
+ left: '0',
|
|
|
+ right: '0',
|
|
|
+ top: '8px',
|
|
|
+ bottom: '16px',
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
const SubHeader = styled('h3')`
|
|
|
color: ${p => p.theme.gray300};
|
|
|
- font-size: ${p => p.theme.fontSizeLarge};
|
|
|
+ font-size: ${p => p.theme.fontSizeMedium};
|
|
|
margin: 0;
|
|
|
margin-bottom: ${space(1)};
|
|
|
`;
|
|
@@ -195,9 +263,15 @@ const FlexRowContainer = styled('div')`
|
|
|
& > div:last-child {
|
|
|
padding-right: ${space(1)};
|
|
|
}
|
|
|
+ flex-wrap: wrap;
|
|
|
`;
|
|
|
|
|
|
const FlexRowItem = styled('div')`
|
|
|
padding-right: ${space(4)};
|
|
|
flex: 1;
|
|
|
+ flex-grow: 0;
|
|
|
+ min-width: 280px;
|
|
|
+ & > h3 {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
`;
|