Skip to content

Commit 4f1e41d

Browse files
authored
feat(autofix): Thumbs up/down on root cause (#87145)
![CleanShot 2025-03-14 at 17 15 07@2x](https://github.com/user-attachments/assets/046aae80-bc8e-4536-9689-d4a8b3cf576e) ![CleanShot 2025-03-14 at 17 25 00@2x](https://github.com/user-attachments/assets/ee0901bb-4d91-4ac0-bfe7-adbf74efa601)
1 parent a227186 commit 4f1e41d

File tree

5 files changed

+224
-8
lines changed

5 files changed

+224
-8
lines changed

static/app/components/events/autofix/autofixRootCause.tsx

+135-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
99
import {Alert} from 'sentry/components/core/alert';
1010
import {Button} from 'sentry/components/core/button';
1111
import {
12+
type AutofixFeedback,
1213
type AutofixRepository,
1314
type AutofixRootCauseData,
1415
type AutofixRootCauseSelection,
@@ -20,13 +21,15 @@ import {
2021
type AutofixResponse,
2122
makeAutofixQueryKey,
2223
} from 'sentry/components/events/autofix/useAutofix';
23-
import {IconCheckmark, IconClose, IconEdit, IconFocus} from 'sentry/icons';
24+
import {IconCheckmark, IconClose, IconFocus, IconInput, IconThumb} from 'sentry/icons';
2425
import {t} from 'sentry/locale';
2526
import {space} from 'sentry/styles/space';
2627
import {singleLineRenderer} from 'sentry/utils/marked';
2728
import {setApiQueryData, useMutation, useQueryClient} from 'sentry/utils/queryClient';
2829
import testableTransition from 'sentry/utils/testableTransition';
2930
import useApi from 'sentry/utils/useApi';
31+
import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';
32+
import {Divider} from 'sentry/views/issueDetails/divider';
3033

3134
import AutofixHighlightPopup from './autofixHighlightPopup';
3235
import {AutofixTimeline} from './autofixTimeline';
@@ -39,6 +42,7 @@ type AutofixRootCauseProps = {
3942
rootCauseSelection: AutofixRootCauseSelection;
4043
runId: string;
4144
agentCommentThread?: CommentThread;
45+
feedback?: AutofixFeedback;
4246
previousDefaultStepIndex?: number;
4347
previousInsightCount?: number;
4448
terminationReason?: string;
@@ -144,6 +148,59 @@ export function useSelectCause({groupId, runId}: {groupId: string; runId: string
144148
});
145149
}
146150

151+
export function useUpdateRootCauseFeedback({
152+
groupId,
153+
runId,
154+
}: {
155+
groupId: string;
156+
runId: string;
157+
}) {
158+
const api = useApi();
159+
const queryClient = useQueryClient();
160+
161+
return useMutation({
162+
mutationFn: (params: {action: 'root_cause_thumbs_up' | 'root_cause_thumbs_down'}) => {
163+
return api.requestPromise(`/issues/${groupId}/autofix/update/`, {
164+
method: 'POST',
165+
data: {
166+
run_id: runId,
167+
payload: {
168+
type: 'feedback',
169+
action: params.action,
170+
},
171+
},
172+
});
173+
},
174+
onMutate: params => {
175+
queryClient.setQueryData(makeAutofixQueryKey(groupId), (data: AutofixResponse) => {
176+
if (!data || !data.autofix) {
177+
return data;
178+
}
179+
180+
return {
181+
...data,
182+
autofix: {
183+
...data.autofix,
184+
feedback: {
185+
...data.autofix.feedback,
186+
root_cause_thumbs_up: params.action === 'root_cause_thumbs_up',
187+
root_cause_thumbs_down: params.action === 'root_cause_thumbs_down',
188+
},
189+
},
190+
};
191+
});
192+
},
193+
onSuccess: () => {
194+
queryClient.invalidateQueries({
195+
queryKey: makeAutofixQueryKey(groupId),
196+
});
197+
},
198+
onError: () => {
199+
addErrorMessage(t('Something went wrong when updating the root cause feedback.'));
200+
},
201+
});
202+
}
203+
147204
export function replaceHeadersWithBold(markdown: string) {
148205
const headerRegex = /^(#{1,6})\s+(.*)$/gm;
149206
const boldMarkdown = markdown.replace(headerRegex, (_match, _hashes, content) => {
@@ -201,7 +258,7 @@ function RootCauseDescription({
201258
);
202259
}
203260

204-
function formatRootCauseText(
261+
export function formatRootCauseText(
205262
cause: AutofixRootCauseData | undefined,
206263
customRootCause?: string
207264
) {
@@ -259,9 +316,74 @@ function CopyRootCauseButton({
259316
return null;
260317
}
261318
const text = formatRootCauseText(cause, customRootCause);
262-
return <CopyToClipboardButton size="sm" text={text} borderless />;
319+
return (
320+
<CopyToClipboardButton
321+
size="sm"
322+
text={text}
323+
borderless
324+
title="Copy root cause as Markdown"
325+
/>
326+
);
263327
}
264328

329+
function ThumbsUpDownButtons({
330+
feedback,
331+
groupId,
332+
runId,
333+
}: {
334+
groupId: string;
335+
runId: string;
336+
feedback?: AutofixFeedback;
337+
}) {
338+
const {mutate: handleUpdateRootCauseFeedback} = useUpdateRootCauseFeedback({
339+
groupId,
340+
runId,
341+
});
342+
const openForm = useFeedbackForm();
343+
344+
return (
345+
<ButtonBar>
346+
<Button
347+
size="sm"
348+
borderless
349+
onClick={() => handleUpdateRootCauseFeedback({action: 'root_cause_thumbs_up'})}
350+
title={t('This root cause analysis is helpful')}
351+
>
352+
{
353+
<IconThumb
354+
color={feedback?.root_cause_thumbs_up ? 'green400' : 'gray300'}
355+
size="sm"
356+
direction="up"
357+
fill="red"
358+
/>
359+
}
360+
</Button>
361+
<Button
362+
size="sm"
363+
borderless
364+
onClick={() => {
365+
handleUpdateRootCauseFeedback({action: 'root_cause_thumbs_down'});
366+
openForm?.({
367+
messagePlaceholder: t('How can we make Autofix better for you?'),
368+
tags: {
369+
['feedback.source']: 'issue_details_ai_autofix_root_cause',
370+
['feedback.owner']: 'ml-ai',
371+
},
372+
});
373+
}}
374+
title={t('This root cause is incorrect or not helpful')}
375+
>
376+
{
377+
<IconThumb
378+
color={feedback?.root_cause_thumbs_down ? 'red400' : 'gray300'}
379+
size="sm"
380+
direction="down"
381+
/>
382+
}
383+
</Button>
384+
</ButtonBar>
385+
);
386+
}
265387
function AutofixRootCauseDisplay({
266388
causes,
267389
groupId,
@@ -270,6 +392,7 @@ function AutofixRootCauseDisplay({
270392
previousDefaultStepIndex,
271393
previousInsightCount,
272394
agentCommentThread,
395+
feedback,
273396
}: AutofixRootCauseProps) {
274397
const {mutate: handleContinue, isPending} = useSelectCause({groupId, runId});
275398
const [isEditing, setIsEditing] = useState(false);
@@ -318,6 +441,10 @@ function AutofixRootCauseDisplay({
318441
{t('Root Cause')}
319442
</HeaderText>
320443
<ButtonBar>
444+
<ThumbsUpDownButtons feedback={feedback} groupId={groupId} runId={runId} />
445+
<DividerWrapper>
446+
<Divider />
447+
</DividerWrapper>
321448
<CopyRootCauseButton cause={cause} isEditing={isEditing} />
322449
<EditButton
323450
size="sm"
@@ -333,7 +460,7 @@ function AutofixRootCauseDisplay({
333460
}
334461
}}
335462
>
336-
{isEditing ? <IconClose size="sm" /> : <IconEdit size="sm" />}
463+
{isEditing ? <IconClose size="sm" /> : <IconInput size="sm" />}
337464
</EditButton>
338465
{isEditing && (
339466
<Button
@@ -503,3 +630,7 @@ const TextArea = styled('textarea')`
503630
const EditButton = styled(Button)`
504631
color: ${p => p.theme.subText};
505632
`;
633+
634+
const DividerWrapper = styled('div')`
635+
margin: 0 ${space(1)};
636+
`;

static/app/components/events/autofix/autofixSolution.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ function getEventColor(isActive?: boolean, isSelected?: boolean): ColorConfig {
353353
};
354354
}
355355

356-
function formatSolutionText(
356+
export function formatSolutionText(
357357
solution: AutofixSolutionTimelineEvent[],
358358
customSolution?: string
359359
) {

static/app/components/events/autofix/autofixSteps.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import {AutofixSolution} from 'sentry/components/events/autofix/autofixSolution';
1313
import {
1414
type AutofixData,
15+
type AutofixFeedback,
1516
type AutofixProgressItem,
1617
type AutofixRepository,
1718
AutofixStatus,
@@ -37,6 +38,7 @@ interface StepProps {
3738
repos: AutofixRepository[];
3839
runId: string;
3940
step: AutofixStep;
41+
feedback?: AutofixFeedback;
4042
previousDefaultStepIndex?: number;
4143
previousInsightCount?: number;
4244
shouldCollapseByDefault?: boolean;
@@ -65,6 +67,7 @@ export function Step({
6567
shouldCollapseByDefault,
6668
previousDefaultStepIndex,
6769
previousInsightCount,
70+
feedback,
6871
}: StepProps) {
6972
return (
7073
<StepCard>
@@ -99,6 +102,7 @@ export function Step({
99102
repos={repos}
100103
previousDefaultStepIndex={previousDefaultStepIndex}
101104
previousInsightCount={previousInsightCount}
105+
feedback={feedback}
102106
/>
103107
)}
104108
{step.type === AutofixStepType.SOLUTION && (
@@ -222,6 +226,7 @@ export function AutofixSteps({data, groupId, runId}: AutofixStepsProps) {
222226
previousDefaultStepIndex >= 0 ? previousDefaultStepIndex : undefined
223227
}
224228
previousInsightCount={previousInsightCount}
229+
feedback={data.feedback}
225230
/>
226231
</div>
227232
);

static/app/components/events/autofix/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export type AutofixData = {
7575
};
7676
completed_at?: string | null;
7777
error_message?: string;
78+
feedback?: AutofixFeedback;
7879
options?: AutofixOptions;
7980
steps?: AutofixStep[];
8081
users?: Record<number, User>;
@@ -212,6 +213,11 @@ export type GroupWithAutofix = Group & {
212213
metadata?: EventMetadataWithAutofix;
213214
};
214215

216+
export type AutofixFeedback = {
217+
root_cause_thumbs_down?: boolean;
218+
root_cause_thumbs_up?: boolean;
219+
};
220+
215221
export type FilePatch = {
216222
added: number;
217223
hunks: Hunk[];

0 commit comments

Comments
 (0)