Skip to content

Commit

Permalink
feat(autofix): Add fix instruction to root cause selection step (#78812)
Browse files Browse the repository at this point in the history
The user can optionally provide instructions when they select the
suggested root cause.
<img width="500" alt="Screenshot 2024-10-08 at 3 05 20 PM"
src="https://github.com/user-attachments/assets/988e8909-dd22-4692-b443-1aa5c87ef1c8">
  • Loading branch information
roaga authored Oct 9, 2024
1 parent 928b473 commit c9f4dfc
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 36 deletions.
40 changes: 34 additions & 6 deletions static/app/components/events/autofix/autofixMessageBox.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ describe('AutofixMessageBox', () => {
displayText: 'Test display text',
groupId: '123',
runId: '456',
inputPlaceholder: 'Test placeholder',
actionText: 'Send',
isDisabled: false,
allowEmptyMessage: false,
Expand All @@ -31,15 +30,15 @@ describe('AutofixMessageBox', () => {
render(<AutofixMessageBox {...defaultProps} />);

expect(screen.getByText('Test display text')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Test placeholder')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Say something...')).toBeInTheDocument();
expect(screen.getByRole('button', {name: 'Send'})).toBeInTheDocument();
});

it('calls onSend when provided and button is clicked', async () => {
const onSendMock = jest.fn();
render(<AutofixMessageBox {...defaultProps} onSend={onSendMock} />);

const input = screen.getByPlaceholderText('Test placeholder');
const input = screen.getByPlaceholderText('Say something...');
await userEvent.type(input, 'Test message');
await userEvent.click(screen.getByRole('button', {name: 'Send'}));

Expand All @@ -55,7 +54,7 @@ describe('AutofixMessageBox', () => {

render(<AutofixMessageBox {...defaultProps} />);

const input = screen.getByPlaceholderText('Test placeholder');
const input = screen.getByPlaceholderText('Say something...');
await userEvent.type(input, 'Test message');
await userEvent.click(screen.getByRole('button', {name: 'Send'}));

Expand All @@ -78,7 +77,7 @@ describe('AutofixMessageBox', () => {

render(<AutofixMessageBox {...defaultProps} />);

const input = screen.getByPlaceholderText('Test placeholder');
const input = screen.getByPlaceholderText('Say something...');
await userEvent.type(input, 'Test message');
await userEvent.click(screen.getByRole('button', {name: 'Send'}));

Expand All @@ -103,7 +102,7 @@ describe('AutofixMessageBox', () => {
it('disables input and button when isDisabled is true', () => {
render(<AutofixMessageBox {...defaultProps} isDisabled />);

expect(screen.getByPlaceholderText('Test placeholder')).toBeDisabled();
expect(screen.getByPlaceholderText('Say something...')).toBeDisabled();
expect(screen.getByRole('button', {name: 'Send'})).toBeDisabled();
});

Expand All @@ -114,4 +113,33 @@ describe('AutofixMessageBox', () => {
screen.getByPlaceholderText('Please answer to continue...')
).toBeInTheDocument();
});

it('handles suggested root cause selection correctly', async () => {
const onSendMock = jest.fn();
render(
<AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
);

// Test suggested root cause
const input = screen.getByPlaceholderText('Provide any instructions for the fix...');
await userEvent.type(input, 'Use this suggestion');
await userEvent.click(screen.getByRole('button', {name: 'Send'}));

expect(onSendMock).toHaveBeenCalledWith('Use this suggestion', false);
});

it('handles custom root cause selection correctly', async () => {
const onSendMock = jest.fn();
render(
<AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
);

// Test custom root cause
await userEvent.click(screen.getAllByText('Provide your own root cause')[0]);
const customInput = screen.getByPlaceholderText('Propose your own root cause...');
await userEvent.type(customInput, 'Custom root cause');
await userEvent.click(screen.getByRole('button', {name: 'Send'}));

expect(onSendMock).toHaveBeenCalledWith('Custom root cause', true);
});
});
56 changes: 45 additions & 11 deletions static/app/components/events/autofix/autofixMessageBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {Button} from 'sentry/components/button';
import {type AutofixStep, AutofixStepType} from 'sentry/components/events/autofix/types';
import Input from 'sentry/components/input';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {SegmentedControl} from 'sentry/components/segmentedControl';
import {
IconCheckmark,
IconChevron,
Expand Down Expand Up @@ -51,14 +52,12 @@ interface AutofixMessageBoxProps {
allowEmptyMessage: boolean;
displayText: string;
groupId: string;
inputPlaceholder: string;
isDisabled: boolean;
onSend: ((message: string) => void) | null;
onSend: ((message: string, isCustom?: boolean) => void) | null;
responseRequired: boolean;
runId: string;
step: AutofixStep | null;
emptyInfoText?: string;
notEmptyInfoText?: string;
isRootCauseSelectionStep?: boolean;
primaryAction?: boolean;
scrollIntoView?: (() => void) | null;
}
Expand Down Expand Up @@ -98,7 +97,6 @@ function StepIcon({step}: {step: AutofixStep}) {
function AutofixMessageBox({
displayText = '',
step = null,
inputPlaceholder = 'Say something...',
primaryAction = false,
responseRequired = false,
onSend,
Expand All @@ -107,20 +105,35 @@ function AutofixMessageBox({
isDisabled = false,
groupId,
runId,
emptyInfoText = '',
notEmptyInfoText = '',
scrollIntoView = null,
isRootCauseSelectionStep = false,
}: AutofixMessageBoxProps) {
const [message, setMessage] = useState('');
const {mutate: send} = useSendMessage({groupId, runId});

const [rootCauseMode, setRootCauseMode] = useState<
'suggested_root_cause' | 'custom_root_cause'
>('suggested_root_cause');

isDisabled =
isDisabled ||
step?.status === 'ERROR' ||
(step?.type === AutofixStepType.ROOT_CAUSE_ANALYSIS && step.causes?.length === 0);

const handleSend = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

if (isRootCauseSelectionStep && onSend) {
if (rootCauseMode === 'custom_root_cause' && message.trim() !== '') {
onSend?.(message, true);
setMessage('');
} else if (rootCauseMode === 'suggested_root_cause') {
onSend?.(message, false);
setMessage('');
}
return;
}

if (message.trim() !== '' || allowEmptyMessage) {
if (onSend != null) {
onSend(message);
Expand Down Expand Up @@ -162,7 +175,22 @@ function AutofixMessageBox({
}}
/>
<ActionBar>
<p>{message.length > 0 ? notEmptyInfoText : emptyInfoText}</p>
{isRootCauseSelectionStep && (
<Fragment>
<SegmentedControl
size="xs"
value={rootCauseMode}
onChange={setRootCauseMode}
>
<SegmentedControl.Item key="suggested_root_cause">
{t('Use suggested root cause')}
</SegmentedControl.Item>
<SegmentedControl.Item key="custom_root_cause">
{t('Provide your own root cause')}
</SegmentedControl.Item>
</SegmentedControl>
</Fragment>
)}
</ActionBar>
</DisplayArea>
<form onSubmit={handleSend}>
Expand All @@ -173,7 +201,13 @@ function AutofixMessageBox({
type="text"
value={message}
onChange={e => setMessage(e.target.value)}
placeholder={inputPlaceholder}
placeholder={
!isRootCauseSelectionStep
? 'Say something...'
: rootCauseMode === 'suggested_root_cause'
? 'Provide any instructions for the fix...'
: 'Propose your own root cause...'
}
disabled={isDisabled}
/>
<Button
Expand Down Expand Up @@ -286,8 +320,8 @@ const ProcessingStatusIndicator = styled(LoadingIndicator)`

const ActionBar = styled('div')`
position: absolute;
bottom: 3em;
color: ${p => p.theme.subText};
bottom: 4.25em;
left: ${space(2)};
`;

export default AutofixMessageBox;
2 changes: 2 additions & 0 deletions static/app/components/events/autofix/autofixRootCause.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function useSelectCause({groupId, runId}: {groupId: string; runId: string
params:
| {
causeId: string;
instruction?: string;
}
| {
customRootCause: string;
Expand All @@ -81,6 +82,7 @@ export function useSelectCause({groupId, runId}: {groupId: string; runId: string
payload: {
type: 'select_root_cause',
cause_id: params.causeId,
instruction: params.instruction,
},
},
});
Expand Down
8 changes: 3 additions & 5 deletions static/app/components/events/autofix/autofixSteps.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('AutofixSteps', () => {

expect(screen.getByText('Root cause 1')).toBeInTheDocument();
expect(
screen.getByPlaceholderText('Or propose your own root cause instead...')
screen.getByPlaceholderText('Provide any instructions for the fix...')
).toBeInTheDocument();
});

Expand All @@ -72,9 +72,7 @@ describe('AutofixSteps', () => {

render(<AutofixSteps {...defaultProps} />);

const input = screen.getByPlaceholderText(
'Or propose your own root cause instead...'
);
const input = screen.getByPlaceholderText('Provide any instructions for the fix...');
await userEvent.type(input, 'Custom root cause');
await userEvent.click(screen.getByRole('button', {name: 'Find a Fix'}));

Expand Down Expand Up @@ -143,7 +141,7 @@ describe('AutofixSteps', () => {
render(<AutofixSteps {...defaultProps} />);

const messageBox = screen.getByPlaceholderText(
'Or propose your own root cause instead...'
'Provide any instructions for the fix...'
);
expect(messageBox).toBeInTheDocument();

Expand Down
18 changes: 4 additions & 14 deletions static/app/components/events/autofix/autofixSteps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ export function AutofixSteps({data, groupId, runId, onRetry}: AutofixStepsProps)
const stepsRef = useRef<(HTMLDivElement | null)[]>([]);

const {mutate: handleSelectFix} = useSelectCause({groupId, runId});
const selectRootCause = (text: string) => {
if (text.length > 0) {
const selectRootCause = (text: string, isCustom?: boolean) => {
if (isCustom) {
handleSelectFix({customRootCause: text});
} else {
if (!steps) {
Expand All @@ -154,7 +154,7 @@ export function AutofixSteps({data, groupId, runId, onRetry}: AutofixStepsProps)
}
const cause = step.causes[0];
const id = cause.id;
handleSelectFix({causeId: id});
handleSelectFix({causeId: id, instruction: text});
}
};

Expand Down Expand Up @@ -214,11 +214,6 @@ export function AutofixSteps({data, groupId, runId, onRetry}: AutofixStepsProps)
<AutofixMessageBox
displayText={activeLog ?? ''}
step={lastStep}
inputPlaceholder={
!isRootCauseSelectionStep
? 'Say something...'
: 'Or propose your own root cause instead...'
}
responseRequired={lastStep.status === 'WAITING_FOR_USER_RESPONSE'}
onSend={!isRootCauseSelectionStep ? null : selectRootCause}
actionText={!isRootCauseSelectionStep ? 'Send' : 'Find a Fix'}
Expand All @@ -227,12 +222,7 @@ export function AutofixSteps({data, groupId, runId, onRetry}: AutofixStepsProps)
groupId={groupId}
runId={runId}
primaryAction={isRootCauseSelectionStep}
emptyInfoText={
!isRootCauseSelectionStep ? '' : 'Selected: suggested root cause above'
}
notEmptyInfoText={
!isRootCauseSelectionStep ? '' : 'Selected: your custom root cause below'
}
isRootCauseSelectionStep={isRootCauseSelectionStep}
scrollIntoView={
!lastStepVisible &&
(lastStep.type === AutofixStepType.ROOT_CAUSE_ANALYSIS ||
Expand Down

0 comments on commit c9f4dfc

Please sign in to comment.