Skip to content

Commit 36f0a18

Browse files
authored
feat(ddm): code locations (#60539)
1 parent 5f7a856 commit 36f0a18

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-0
lines changed

static/app/utils/metrics/features.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {Organization} from 'sentry/types';
22
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
33

4+
// TODO(ddm): rename this since dashboards and code locations are also behind it
45
export function hasDdmAlertsSupport(organization: Organization) {
56
return organization.features.includes('ddm-experimental');
67
}

static/app/utils/metrics/index.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ export type MetricsQuery = {
7070
query?: string;
7171
};
7272

73+
export type MetricMetaCodeLocation = {
74+
frames: {
75+
absPath?: string;
76+
filename?: string;
77+
function?: string;
78+
lineNo?: number;
79+
module?: string;
80+
}[];
81+
mri: string;
82+
timestamp: number;
83+
};
7384
export function getDdmUrl(
7485
orgSlug: string,
7586
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import isArray from 'lodash/isArray';
2+
3+
import {PageFilters} from 'sentry/types';
4+
import {MetricMetaCodeLocation} from 'sentry/utils/metrics';
5+
import {useApiQuery} from 'sentry/utils/queryClient';
6+
import useOrganization from 'sentry/utils/useOrganization';
7+
8+
export function useMetricsCodeLocations(
9+
mri: string | undefined,
10+
projects?: PageFilters['projects']
11+
) {
12+
const organization = useOrganization();
13+
14+
const {data, isLoading} = useApiQuery<{codeLocations: MetricMetaCodeLocation[]}>(
15+
[
16+
`/organizations/${organization.slug}/ddm/meta/`,
17+
{query: {metric: mri, project: projects}},
18+
],
19+
{
20+
enabled: !!mri,
21+
staleTime: Infinity,
22+
}
23+
);
24+
25+
if (data && isArray(data?.codeLocations)) {
26+
data.codeLocations = data.codeLocations.filter(
27+
codeLocation => codeLocation.mri === mri
28+
);
29+
}
30+
31+
return {data, isLoading};
32+
}
+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {useState} from 'react';
2+
import styled from '@emotion/styled';
3+
import isArray from 'lodash/isArray';
4+
5+
import {Button} from 'sentry/components/button';
6+
import DefaultTitle from 'sentry/components/events/interfaces/frame/defaultTitle';
7+
import {tn} from 'sentry/locale';
8+
import {space} from 'sentry/styles/space';
9+
import {Frame} from 'sentry/types';
10+
import {hasDdmAlertsSupport} from 'sentry/utils/metrics/features';
11+
import {useMetricsCodeLocations} from 'sentry/utils/metrics/useMetricsCodeLocations';
12+
import useOrganization from 'sentry/utils/useOrganization';
13+
14+
export function CodeLocations({mri}: {mri: string}) {
15+
const {data} = useMetricsCodeLocations(mri);
16+
const [isExpanded, setIsExpanded] = useState(false);
17+
const organization = useOrganization();
18+
19+
if (!hasDdmAlertsSupport(organization)) {
20+
return null;
21+
}
22+
23+
if (!isArray(data?.codeLocations) || data?.codeLocations.length === 0) {
24+
return null;
25+
}
26+
27+
const frameToShow = data?.codeLocations[0].frames[0];
28+
const otherFrames = data?.codeLocations[0].frames.slice(1) ?? [];
29+
30+
if (!frameToShow) {
31+
return null;
32+
}
33+
34+
return (
35+
<Wrapper>
36+
<DefaultLine className="title">
37+
<DefaultLineTitleWrapper>
38+
<LeftLineTitle>
39+
<DefaultTitle
40+
frame={frameToShow as Frame}
41+
platform="other"
42+
isHoverPreviewed={false}
43+
/>
44+
</LeftLineTitle>
45+
</DefaultLineTitleWrapper>
46+
<ToggleButton size="xs" borderless onClick={() => setIsExpanded(curr => !curr)}>
47+
{isExpanded
48+
? tn(
49+
'Hide %s more code location',
50+
'Hide %s more code locations',
51+
otherFrames.length
52+
)
53+
: tn(
54+
'Show %s more code location',
55+
'Show %s more code locations',
56+
otherFrames.length
57+
)}
58+
</ToggleButton>
59+
</DefaultLine>
60+
{isExpanded &&
61+
otherFrames.map(frame => (
62+
<DefaultLine className="title" key={frame.absPath}>
63+
<DefaultLineTitleWrapper>
64+
<LeftLineTitle>
65+
<DefaultTitle
66+
frame={frame as Frame}
67+
platform="other"
68+
isHoverPreviewed={false}
69+
/>
70+
</LeftLineTitle>
71+
</DefaultLineTitleWrapper>
72+
</DefaultLine>
73+
))}
74+
</Wrapper>
75+
);
76+
}
77+
78+
const ToggleButton = styled(Button)`
79+
color: ${p => p.theme.subText};
80+
font-style: italic;
81+
font-weight: normal;
82+
padding: ${space(0.25)} ${space(0.5)};
83+
84+
&:hover {
85+
color: ${p => p.theme.subText};
86+
}
87+
`;
88+
89+
const Wrapper = styled('div')`
90+
margin-top: ${space(1)};
91+
background-color: ${p => p.theme.backgroundTertiary};
92+
padding: ${space(0.25)} ${space(0.5)};
93+
border-radius: ${p => p.theme.borderRadius};
94+
`;
95+
96+
const DefaultLine = styled('div')`
97+
display: flex;
98+
justify-content: space-between;
99+
align-items: center;
100+
`;
101+
102+
const DefaultLineTitleWrapper = styled('div')`
103+
display: flex;
104+
align-items: center;
105+
justify-content: space-between;
106+
font-style: italic;
107+
`;
108+
109+
const LeftLineTitle = styled('div')`
110+
display: flex;
111+
align-items: center;
112+
`;

static/app/views/ddm/widget.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {decodeList} from 'sentry/utils/queryString';
2828
import theme from 'sentry/utils/theme';
2929
import useRouter from 'sentry/utils/useRouter';
3030
import {MetricChart} from 'sentry/views/ddm/chart';
31+
import {CodeLocations} from 'sentry/views/ddm/codeLocations';
3132
import {MetricWidgetContextMenu} from 'sentry/views/ddm/contextMenu';
3233
import {QueryBuilder} from 'sentry/views/ddm/queryBuilder';
3334
import {SummaryTable} from 'sentry/views/ddm/summaryTable';
@@ -247,6 +248,7 @@ function MetricWidgetBody({
247248
setHoveredLegend={focusedSeries ? undefined : setHoveredLegend}
248249
/>
249250
)}
251+
<CodeLocations mri={metricsQuery.mri} />
250252
</StyledMetricWidgetBody>
251253
);
252254
}

0 commit comments

Comments
 (0)