Skip to content

Commit bf6f510

Browse files
authored
feat(browser-starfish): add resource summary charts, remove image tabs (#59628)
1. add resource module charts (@gggritso this required some minor changes that touches db module, so i'm requested a review from you) <img width="1254" alt="image" src="https://github.com/getsentry/sentry/assets/44422760/d204b867-1d1b-4105-a287-f6bbd2056577"> 2. Remove image tab for GA 3. Move domain filter up, and apply it to the resource charts
1 parent 3f00edf commit bf6f510

File tree

5 files changed

+87
-51
lines changed

5 files changed

+87
-51
lines changed

static/app/utils/discover/fields.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ export const RATE_UNIT_LABELS = {
137137
[RateUnits.PER_HOUR]: '/hr',
138138
};
139139

140+
export const RATE_UNIT_TITLE = {
141+
[RateUnits.PER_SECOND]: 'Per Second',
142+
[RateUnits.PER_MINUTE]: 'Per Minute',
143+
[RateUnits.PER_HOUR]: 'Per Hour',
144+
};
145+
140146
const CONDITIONS_ARGUMENTS: SelectValue<string>[] = [
141147
{
142148
label: 'is equal to',

static/app/views/performance/browser/resources/index.tsx

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {browserHistory} from 'react-router';
21
import styled from '@emotion/styled';
32

43
import Breadcrumbs from 'sentry/components/breadcrumbs';
@@ -7,25 +6,29 @@ import * as Layout from 'sentry/components/layouts/thirds';
76
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
87
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
98
import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
10-
import {TabList, Tabs} from 'sentry/components/tabs';
119
import {t} from 'sentry/locale';
1210
import {space} from 'sentry/styles/space';
13-
import {useLocation} from 'sentry/utils/useLocation';
11+
import {RateUnits} from 'sentry/utils/discover/fields';
1412
import useOrganization from 'sentry/utils/useOrganization';
1513
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
1614
import ImageView from 'sentry/views/performance/browser/resources/imageView';
17-
import JSCSSView from 'sentry/views/performance/browser/resources/jsCssView';
15+
import JSCSSView, {
16+
DEFAULT_RESOURCE_TYPES,
17+
FilterOptionsContainer,
18+
} from 'sentry/views/performance/browser/resources/jsCssView';
19+
import DomainSelector from 'sentry/views/performance/browser/resources/shared/domainSelector';
1820
import {
1921
BrowserStarfishFields,
2022
useResourceModuleFilters,
2123
} from 'sentry/views/performance/browser/resources/utils/useResourceFilters';
2224
import {ModulePageProviders} from 'sentry/views/performance/database/modulePageProviders';
2325

24-
const {SPAN_OP} = BrowserStarfishFields;
26+
const {SPAN_OP, SPAN_DOMAIN} = BrowserStarfishFields;
27+
28+
export const RESOURCE_THROUGHPUT_UNIT = RateUnits.PER_MINUTE;
2529

2630
function ResourcesLandingPage() {
2731
const organization = useOrganization();
28-
const location = useLocation();
2932
const filters = useResourceModuleFilters();
3033

3134
return (
@@ -53,33 +56,20 @@ function ResourcesLandingPage() {
5356
<FeatureBadge type="alpha" />
5457
</Layout.Title>
5558
</Layout.HeaderContent>
56-
<StyledTabs>
57-
<TabList
58-
hideBorder
59-
onSelectionChange={key => {
60-
browserHistory.push({
61-
...location,
62-
query: {
63-
...location.query,
64-
[SPAN_OP]: key,
65-
},
66-
});
67-
}}
68-
>
69-
<TabList.Item key="">{t('JS/CSS/Fonts')}</TabList.Item>
70-
<TabList.Item key="resource.img">{t('Images')}</TabList.Item>
71-
</TabList>
72-
</StyledTabs>
7359
</Layout.Header>
7460

7561
<Layout.Body>
7662
<Layout.Main fullWidth>
77-
<PaddedContainer>
63+
<FilterOptionsContainer>
7864
<PageFilterBar condensed>
7965
<ProjectPageFilter />
8066
<DatePageFilter />
8167
</PageFilterBar>
82-
</PaddedContainer>
68+
<DomainSelector
69+
value={filters[SPAN_DOMAIN] || ''}
70+
defaultResourceTypes={DEFAULT_RESOURCE_TYPES}
71+
/>
72+
</FilterOptionsContainer>
8373

8474
{(!filters[SPAN_OP] ||
8575
filters[SPAN_OP] === 'resource.script' ||
@@ -96,8 +86,4 @@ export const PaddedContainer = styled('div')`
9686
margin-bottom: ${space(2)};
9787
`;
9888

99-
const StyledTabs = styled(Tabs)`
100-
grid-column: 1/-1;
101-
`;
102-
10389
export default ResourcesLandingPage;

static/app/views/performance/browser/resources/jsCssView/index.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import SwitchButton from 'sentry/components/switchButton';
66
import {t} from 'sentry/locale';
77
import {space} from 'sentry/styles/space';
88
import {useLocation} from 'sentry/utils/useLocation';
9+
import {RESOURCE_THROUGHPUT_UNIT} from 'sentry/views/performance/browser/resources';
910
import ResourceTable from 'sentry/views/performance/browser/resources/jsCssView/resourceTable';
10-
import DomainSelector from 'sentry/views/performance/browser/resources/shared/domainSelector';
1111
import SelectControlWithProps from 'sentry/views/performance/browser/resources/shared/selectControlWithProps';
1212
import {
1313
BrowserStarfishFields,
1414
useResourceModuleFilters,
1515
} from 'sentry/views/performance/browser/resources/utils/useResourceFilters';
1616
import {useResourcePagesQuery} from 'sentry/views/performance/browser/resources/utils/useResourcePagesQuery';
1717
import {useResourceSort} from 'sentry/views/performance/browser/resources/utils/useResourceSort';
18+
import {ModuleName} from 'sentry/views/starfish/types';
19+
import {SpanTimeCharts} from 'sentry/views/starfish/views/spans/spanTimeCharts';
20+
import {ModuleFilters} from 'sentry/views/starfish/views/spans/useModuleFilters';
1821

1922
const {
2023
SPAN_OP: RESOURCE_TYPE,
@@ -23,7 +26,7 @@ const {
2326
RESOURCE_RENDER_BLOCKING_STATUS,
2427
} = BrowserStarfishFields;
2528

26-
const DEFAULT_RESOURCE_TYPES = ['resource.script', 'resource.css'];
29+
export const DEFAULT_RESOURCE_TYPES = ['resource.script', 'resource.css'];
2730

2831
type Option = {
2932
label: string;
@@ -35,6 +38,11 @@ function JSCSSView() {
3538
const sort = useResourceSort();
3639
const location = useLocation();
3740

41+
const spanTimeChartsFilters: ModuleFilters = {
42+
'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
43+
...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}),
44+
};
45+
3846
const handleBlockingToggle: MouseEventHandler = () => {
3947
const hasBlocking = filters[RESOURCE_RENDER_BLOCKING_STATUS] === 'blocking';
4048
const newBlocking = hasBlocking ? undefined : 'blocking';
@@ -49,11 +57,13 @@ function JSCSSView() {
4957

5058
return (
5159
<Fragment>
60+
<SpanTimeCharts
61+
moduleName={ModuleName.OTHER}
62+
appliedFilters={spanTimeChartsFilters}
63+
throughputUnit={RESOURCE_THROUGHPUT_UNIT}
64+
/>
65+
5266
<FilterOptionsContainer>
53-
<DomainSelector
54-
value={filters[SPAN_DOMAIN] || ''}
55-
defaultResourceTypes={DEFAULT_RESOURCE_TYPES}
56-
/>
5767
<ResourceTypeSelector value={filters[RESOURCE_TYPE] || ''} />
5868
<PageSelector
5969
value={filters[TRANSACTION] || ''}

static/app/views/starfish/views/spans/spanTimeCharts.tsx

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {ModuleName, SpanMetricsField} from 'sentry/views/starfish/types';
1616
import {STARFISH_CHART_INTERVAL_FIDELITY} from 'sentry/views/starfish/utils/constants';
1717
import {useSpansQuery} from 'sentry/views/starfish/utils/useSpansQuery';
1818
import {useErrorRateQuery as useErrorCountQuery} from 'sentry/views/starfish/views/spans/queries';
19+
import {EMPTY_OPTION_VALUE} from 'sentry/views/starfish/views/spans/selectors/emptyOption';
1920
import {
2021
DataTitles,
2122
getDurationChartTitle,
@@ -24,26 +25,33 @@ import {
2425
import {ModuleFilters} from 'sentry/views/starfish/views/spans/useModuleFilters';
2526
import {NULL_SPAN_CATEGORY} from 'sentry/views/starfish/views/webServiceView/spanGroupBreakdownContainer';
2627

27-
const {SPAN_SELF_TIME, SPAN_MODULE, SPAN_DESCRIPTION} = SpanMetricsField;
28+
const {SPAN_SELF_TIME, SPAN_MODULE, SPAN_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
2829

2930
const CHART_HEIGHT = 140;
3031

3132
type Props = {
3233
appliedFilters: ModuleFilters;
3334
moduleName: ModuleName;
3435
spanCategory?: string;
36+
throughputUnit?: RateUnits;
3537
};
3638

3739
type ChartProps = {
3840
filters: ModuleFilters;
3941
moduleName: ModuleName;
42+
throughtputUnit: RateUnits;
4043
};
4144

4245
function getSegmentLabel(moduleName: ModuleName) {
4346
return moduleName === ModuleName.DB ? 'Queries' : 'Requests';
4447
}
4548

46-
export function SpanTimeCharts({moduleName, appliedFilters, spanCategory}: Props) {
49+
export function SpanTimeCharts({
50+
moduleName,
51+
appliedFilters,
52+
spanCategory,
53+
throughputUnit = RateUnits.PER_MINUTE,
54+
}: Props) {
4755
const {selection} = usePageFilters();
4856

4957
const eventView = getEventView(moduleName, selection, appliedFilters, spanCategory);
@@ -61,7 +69,7 @@ export function SpanTimeCharts({moduleName, appliedFilters, spanCategory}: Props
6169
{Comp: (props: ChartProps) => JSX.Element; title: string}[]
6270
> = {
6371
[ModuleName.ALL]: [
64-
{title: getThroughputChartTitle(moduleName), Comp: ThroughputChart},
72+
{title: getThroughputChartTitle(moduleName, throughputUnit), Comp: ThroughputChart},
6573
{title: getDurationChartTitle(moduleName), Comp: DurationChart},
6674
],
6775
[ModuleName.DB]: [],
@@ -79,15 +87,23 @@ export function SpanTimeCharts({moduleName, appliedFilters, spanCategory}: Props
7987
{charts.map(({title, Comp}) => (
8088
<ChartsContainerItem key={title}>
8189
<ChartPanel title={title}>
82-
<Comp moduleName={moduleName} filters={appliedFilters} />
90+
<Comp
91+
moduleName={moduleName}
92+
filters={appliedFilters}
93+
throughtputUnit={throughputUnit}
94+
/>
8395
</ChartPanel>
8496
</ChartsContainerItem>
8597
))}
8698
</ChartsContainer>
8799
);
88100
}
89101

90-
function ThroughputChart({moduleName, filters}: ChartProps): JSX.Element {
102+
function ThroughputChart({
103+
moduleName,
104+
filters,
105+
throughtputUnit,
106+
}: ChartProps): JSX.Element {
91107
const pageFilters = usePageFilters();
92108
const eventView = getEventView(moduleName, pageFilters.selection, filters);
93109

@@ -108,10 +124,17 @@ function ThroughputChart({moduleName, filters}: ChartProps): JSX.Element {
108124
const throughputTimeSeries = Object.keys(dataByGroup).map(groupName => {
109125
const groupData = dataByGroup[groupName];
110126

127+
let throughputMultiplier = 1; // We're fetching per minute, so default is 1
128+
if (throughtputUnit === RateUnits.PER_SECOND) {
129+
throughputMultiplier = 1 / 60;
130+
} else if (throughtputUnit === RateUnits.PER_HOUR) {
131+
throughputMultiplier = 60;
132+
}
133+
111134
return {
112135
seriesName: label ?? 'Throughput',
113136
data: (groupData ?? []).map(datum => ({
114-
value: datum['spm()'],
137+
value: datum['spm()'] * throughputMultiplier,
115138
name: datum.interval,
116139
})),
117140
};
@@ -131,12 +154,12 @@ function ThroughputChart({moduleName, filters}: ChartProps): JSX.Element {
131154
}}
132155
definedAxisTicks={4}
133156
aggregateOutputFormat="rate"
134-
rateUnit={RateUnits.PER_MINUTE}
157+
rateUnit={throughtputUnit}
135158
stacked
136159
isLineChart
137160
chartColors={[THROUGHPUT_COLOR]}
138161
tooltipFormatterOptions={{
139-
valueFormatter: value => formatRate(value, RateUnits.PER_MINUTE),
162+
valueFormatter: value => formatRate(value, throughtputUnit),
140163
}}
141164
/>
142165
);
@@ -227,7 +250,7 @@ function ErrorChart({moduleName, filters}: ChartProps): JSX.Element {
227250
);
228251
}
229252

230-
const SPAN_FILTER_KEYS = ['span_operation', 'domain', 'action'];
253+
const SPAN_FILTER_KEYS = ['span_operation', SPAN_DOMAIN, 'action'];
231254

232255
const getEventView = (
233256
moduleName: ModuleName,
@@ -260,7 +283,11 @@ const buildDiscoverQueryConditions = (
260283
.filter(key => SPAN_FILTER_KEYS.includes(key))
261284
.filter(key => Boolean(appliedFilters[key]))
262285
.map(key => {
263-
return `${key}:${appliedFilters[key]}`;
286+
const value = appliedFilters[key];
287+
if (key === SPAN_DOMAIN && value === EMPTY_OPTION_VALUE) {
288+
return [`!has:${SPAN_DOMAIN}`];
289+
}
290+
return `${key}:${value}`;
264291
});
265292

266293
result.push(`has:${SPAN_DESCRIPTION}`);

static/app/views/starfish/views/spans/types.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {t} from 'sentry/locale';
22
import {defined} from 'sentry/utils';
3+
import {RATE_UNIT_TITLE, RateUnits} from 'sentry/utils/discover/fields';
34

45
export type DataKey =
56
| 'change'
@@ -38,12 +39,15 @@ export const DataTitles: Record<DataKey, string> = {
3839
'avg(http.response_transfer_size)': t('Avg Transfer Size'),
3940
};
4041

41-
export const getThroughputTitle = (spanOp?: string) => {
42+
export const getThroughputTitle = (
43+
spanOp?: string,
44+
throughputUnit = RateUnits.PER_MINUTE
45+
) => {
4246
if (spanOp?.startsWith('db')) {
43-
return t('Queries Per Min');
47+
return `${t('Queries')} ${RATE_UNIT_TITLE[throughputUnit]}`;
4448
}
4549
if (defined(spanOp)) {
46-
return t('Requests Per Sec');
50+
return `${t('Requests')} ${RATE_UNIT_TITLE[throughputUnit]}`;
4751
}
4852
return '--';
4953
};
@@ -56,12 +60,15 @@ export const getDurationChartTitle = (spanOp?: string) => {
5660
return '--';
5761
};
5862

59-
export const getThroughputChartTitle = (spanOp?: string) => {
63+
export const getThroughputChartTitle = (
64+
spanOp?: string,
65+
throughputUnit = RateUnits.PER_MINUTE
66+
) => {
6067
if (spanOp?.startsWith('db')) {
61-
return t('Queries Per Minute');
68+
return `${t('Queries')} ${RATE_UNIT_TITLE[throughputUnit]}`;
6269
}
6370
if (spanOp) {
64-
return t('Requests Per Minute');
71+
return `${t('Requests')} ${RATE_UNIT_TITLE[throughputUnit]}`;
6572
}
6673
return '--';
6774
};

0 commit comments

Comments
 (0)