Skip to content

Commit ca73c7d

Browse files
authored
fix(cursor-agent): ui handling of when you have more than 1 integration (#104007)
People installed multiple cursor integrations and previously this caused issues, this changes it on the UI so 1. You can configure which cursor integration to use for automation handoff: <img width="2916" height="458" alt="CleanShot 2025-11-26 at 03 39 24@2x" src="https://github.com/user-attachments/assets/30376d56-b723-4d15-a7ae-712981fe7013" /> 2. You can select which integration to manually call from the seer drawer: - Because existing integrations don't have unique names, there's no way to distinguish between them except their IDs, so as a fallback I have it show the ID in small text ONLY if at least 2 of the same integrations have the same name, otherwise #103989 will fix this and give it unique names) <img width="906" height="562" alt="CleanShot 2025-11-26 at 03 26 41@2x" src="https://github.com/user-attachments/assets/16a33cd0-953e-4f1a-a107-cd382f10b60a" />
1 parent 591b4ea commit ca73c7d

File tree

4 files changed

+554
-140
lines changed

4 files changed

+554
-140
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ describe('AutofixRootCause', () => {
147147
await userEvent.click(dropdownTrigger);
148148

149149
// Click the Cursor option in the dropdown
150-
await userEvent.click(await screen.findByText('Send to Cursor Cloud Agent'));
150+
await userEvent.click(await screen.findByText('Send to Cursor'));
151151

152152
expect(JSON.parse(localStorage.getItem('autofix:rootCauseActionPreference')!)).toBe(
153-
'cursor_background_agent'
153+
'cursor:cursor-integration-id'
154154
);
155155
});
156156

@@ -204,13 +204,13 @@ describe('AutofixRootCause', () => {
204204

205205
localStorage.setItem(
206206
'autofix:rootCauseActionPreference',
207-
JSON.stringify('cursor_background_agent')
207+
JSON.stringify('cursor:cursor-integration-id')
208208
);
209209

210210
render(<AutofixRootCause {...defaultProps} />);
211211

212212
expect(
213-
await screen.findByRole('button', {name: 'Send to Cursor Cloud Agent'})
213+
await screen.findByRole('button', {name: 'Send to Cursor'})
214214
).toBeInTheDocument();
215215

216216
// Verify Seer option is in the dropdown
@@ -249,6 +249,6 @@ describe('AutofixRootCause', () => {
249249
});
250250
await userEvent.click(dropdownTrigger);
251251

252-
expect(await screen.findByText('Send to Cursor Cloud Agent')).toBeInTheDocument();
252+
expect(await screen.findByText('Send to Cursor')).toBeInTheDocument();
253253
});
254254
});

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

Lines changed: 176 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,149 @@ function CopyRootCauseButton({
247247
);
248248
}
249249

250+
type CodingAgentIntegration = {
251+
id: string;
252+
name: string;
253+
provider: string;
254+
};
255+
256+
function SolutionActionButton({
257+
cursorIntegrations,
258+
preferredAction,
259+
primaryButtonPriority,
260+
isSelectingRootCause,
261+
isLaunchingAgent,
262+
isLoadingAgents,
263+
submitFindSolution,
264+
handleLaunchCodingAgent,
265+
findSolutionTitle,
266+
}: {
267+
cursorIntegrations: CodingAgentIntegration[];
268+
findSolutionTitle: string;
269+
handleLaunchCodingAgent: (integrationId: string, integrationName: string) => void;
270+
isLaunchingAgent: boolean;
271+
isLoadingAgents: boolean;
272+
isSelectingRootCause: boolean;
273+
preferredAction: string;
274+
primaryButtonPriority: React.ComponentProps<typeof Button>['priority'];
275+
submitFindSolution: () => void;
276+
}) {
277+
const preferredIntegration = preferredAction.startsWith('cursor:')
278+
? cursorIntegrations.find(i => i.id === preferredAction.replace('cursor:', ''))
279+
: null;
280+
281+
const effectivePreference =
282+
preferredAction === 'seer_solution' || !preferredIntegration
283+
? 'seer_solution'
284+
: preferredAction;
285+
286+
const isSeerPreferred = effectivePreference === 'seer_solution';
287+
288+
// Check if there are duplicate names among integrations (need to show ID to distinguish)
289+
const hasDuplicateNames =
290+
cursorIntegrations.length > 1 &&
291+
new Set(cursorIntegrations.map(i => i.name)).size < cursorIntegrations.length;
292+
293+
// If no integrations, show simple Seer button
294+
if (cursorIntegrations.length === 0) {
295+
return (
296+
<Button
297+
size="sm"
298+
priority={primaryButtonPriority}
299+
busy={isSelectingRootCause}
300+
onClick={submitFindSolution}
301+
title={findSolutionTitle}
302+
>
303+
{t('Find Solution')}
304+
</Button>
305+
);
306+
}
307+
308+
const dropdownItems = [
309+
...(isSeerPreferred
310+
? []
311+
: [
312+
{
313+
key: 'seer_solution',
314+
label: t('Find Solution with Seer'),
315+
onAction: submitFindSolution,
316+
disabled: isSelectingRootCause,
317+
},
318+
]),
319+
// Show all integrations except the currently preferred one
320+
...cursorIntegrations
321+
.filter(integration => `cursor:${integration.id}` !== effectivePreference)
322+
.map(integration => ({
323+
key: `cursor:${integration.id}`,
324+
label: (
325+
<Flex gap="md" align="center">
326+
<PluginIcon pluginId="cursor" size={20} />
327+
<div>{t('Send to %s', integration.name)}</div>
328+
{hasDuplicateNames && (
329+
<SmallIntegrationIdText>({integration.id})</SmallIntegrationIdText>
330+
)}
331+
</Flex>
332+
),
333+
onAction: () => handleLaunchCodingAgent(integration.id, integration.name),
334+
disabled: isLoadingAgents || isLaunchingAgent,
335+
})),
336+
];
337+
338+
const primaryButtonLabel = isSeerPreferred
339+
? t('Find Solution with Seer')
340+
: hasDuplicateNames
341+
? t('Send to %s (%s)', preferredIntegration!.name, preferredIntegration!.id)
342+
: t('Send to %s', preferredIntegration!.name);
343+
344+
const primaryButtonProps = isSeerPreferred
345+
? {
346+
onClick: submitFindSolution,
347+
busy: isSelectingRootCause,
348+
icon: undefined,
349+
children: primaryButtonLabel,
350+
}
351+
: {
352+
onClick: () =>
353+
handleLaunchCodingAgent(preferredIntegration!.id, preferredIntegration!.name),
354+
busy: isLaunchingAgent,
355+
icon: <PluginIcon pluginId="cursor" size={16} />,
356+
children: primaryButtonLabel,
357+
};
358+
359+
return (
360+
<ButtonBar merged gap="0">
361+
<Button
362+
size="sm"
363+
priority={primaryButtonPriority}
364+
disabled={isLoadingAgents}
365+
{...primaryButtonProps}
366+
>
367+
{primaryButtonProps.children}
368+
</Button>
369+
<DropdownMenu
370+
items={dropdownItems}
371+
trigger={(triggerProps, isOpen) => (
372+
<DropdownTrigger
373+
{...triggerProps}
374+
size="sm"
375+
priority={primaryButtonPriority}
376+
busy={isSelectingRootCause || isLaunchingAgent}
377+
disabled={isLoadingAgents}
378+
aria-label={t('More solution options')}
379+
icon={
380+
isSelectingRootCause || isLaunchingAgent ? (
381+
<LoadingIndicator size={12} />
382+
) : (
383+
<IconChevron direction={isOpen ? 'up' : 'down'} size="xs" />
384+
)
385+
}
386+
/>
387+
)}
388+
/>
389+
</ButtonBar>
390+
);
391+
}
392+
250393
function AutofixRootCauseDisplay({
251394
causes,
252395
groupId,
@@ -276,9 +419,11 @@ function AutofixRootCauseDisplay({
276419
runId
277420
);
278421

279-
const [preferredAction, setPreferredAction] = useLocalStorageState<
280-
'seer_solution' | 'cursor_background_agent'
281-
>('autofix:rootCauseActionPreference', 'seer_solution');
422+
// Stores 'seer_solution' or an integration ID (e.g., 'cursor:123')
423+
const [preferredAction, setPreferredAction] = useLocalStorageState<string>(
424+
'autofix:rootCauseActionPreference',
425+
'seer_solution'
426+
);
282427

283428
const handleSelectDescription = () => {
284429
if (descriptionRef.current) {
@@ -323,27 +468,29 @@ function AutofixRootCauseDisplay({
323468
});
324469
};
325470

326-
// Find Cursor integration specifically
327-
const cursorIntegration = codingAgentIntegrations.find(
471+
const cursorIntegrations = codingAgentIntegrations.filter(
328472
integration => integration.provider === 'cursor'
329473
);
330474

331-
const handleLaunchCodingAgent = () => {
332-
if (!cursorIntegration) {
475+
const handleLaunchCodingAgent = (integrationId: string, integrationName: string) => {
476+
const targetIntegration = cursorIntegrations.find(i => i.id === integrationId);
477+
478+
if (!targetIntegration) {
333479
return;
334480
}
335481

336-
// Save user preference
337-
setPreferredAction('cursor_background_agent');
482+
// Save user preference with specific integration ID
483+
setPreferredAction(`cursor:${integrationId}`);
338484

339-
// Show immediate loading toast
340-
addLoadingMessage(t('Launching %s...', cursorIntegration.name), {duration: 60000});
485+
addLoadingMessage(t('Launching %s...', integrationName), {
486+
duration: 60000,
487+
});
341488

342489
const instruction = solutionText.trim();
343490

344491
launchCodingAgent({
345-
integrationId: cursorIntegration.id,
346-
agentName: cursorIntegration.name,
492+
integrationId: targetIntegration.id,
493+
agentName: targetIntegration.name,
347494
triggerSource: 'root_cause',
348495
instruction: instruction || undefined,
349496
});
@@ -478,107 +625,17 @@ function AutofixRootCauseDisplay({
478625
/>
479626
<ButtonBar>
480627
<CopyRootCauseButton cause={cause} event={event} />
481-
{cursorIntegration ? (
482-
<ButtonBar merged gap="0">
483-
{preferredAction === 'cursor_background_agent' ? (
484-
<Fragment>
485-
<Button
486-
size="sm"
487-
priority={primaryButtonPriority}
488-
busy={isLaunchingAgent}
489-
disabled={isLoadingAgents}
490-
onClick={handleLaunchCodingAgent}
491-
title={t('Send to Cursor Cloud Agent')}
492-
icon={<PluginIcon pluginId="cursor" size={16} />}
493-
>
494-
{t('Send to Cursor Cloud Agent')}
495-
</Button>
496-
<DropdownMenu
497-
items={[
498-
{
499-
key: 'seer-solution',
500-
label: t('Find Solution with Seer'),
501-
onAction: submitFindSolution,
502-
disabled: isSelectingRootCause,
503-
},
504-
]}
505-
trigger={(triggerProps, isOpen) => (
506-
<DropdownTrigger
507-
{...triggerProps}
508-
size="sm"
509-
priority={primaryButtonPriority}
510-
busy={isSelectingRootCause}
511-
disabled={isLoadingAgents}
512-
aria-label={t('More solution options')}
513-
icon={
514-
isSelectingRootCause ? (
515-
<LoadingIndicator size={12} />
516-
) : (
517-
<IconChevron direction={isOpen ? 'up' : 'down'} size="xs" />
518-
)
519-
}
520-
/>
521-
)}
522-
/>
523-
</Fragment>
524-
) : (
525-
<Fragment>
526-
<Button
527-
size="sm"
528-
priority={primaryButtonPriority}
529-
busy={isSelectingRootCause}
530-
disabled={isLoadingAgents}
531-
onClick={submitFindSolution}
532-
title={findSolutionTitle}
533-
>
534-
{t('Find Solution with Seer')}
535-
</Button>
536-
<DropdownMenu
537-
items={[
538-
{
539-
key: 'cursor-agent',
540-
label: (
541-
<Flex gap="md" align="center">
542-
<PluginIcon pluginId="cursor" size={20} />
543-
<div>{t('Send to Cursor Cloud Agent')}</div>
544-
</Flex>
545-
),
546-
onAction: handleLaunchCodingAgent,
547-
disabled: isLoadingAgents || isLaunchingAgent,
548-
},
549-
]}
550-
trigger={(triggerProps, isOpen) => (
551-
<DropdownTrigger
552-
{...triggerProps}
553-
size="sm"
554-
priority={primaryButtonPriority}
555-
busy={isLaunchingAgent}
556-
disabled={isLoadingAgents}
557-
aria-label={t('More solution options')}
558-
icon={
559-
isLaunchingAgent ? (
560-
<LoadingIndicator size={12} />
561-
) : (
562-
<IconChevron direction={isOpen ? 'up' : 'down'} size="xs" />
563-
)
564-
}
565-
/>
566-
)}
567-
/>
568-
</Fragment>
569-
)}
570-
</ButtonBar>
571-
) : (
572-
<Button
573-
size="sm"
574-
priority={primaryButtonPriority}
575-
busy={isSelectingRootCause}
576-
onClick={submitFindSolution}
577-
title={findSolutionTitle}
578-
>
579-
{t('Find Solution')}
580-
</Button>
581-
)}
628+
<SolutionActionButton
629+
cursorIntegrations={cursorIntegrations}
630+
preferredAction={preferredAction}
631+
primaryButtonPriority={primaryButtonPriority}
632+
isSelectingRootCause={isSelectingRootCause}
633+
isLaunchingAgent={isLaunchingAgent}
634+
isLoadingAgents={isLoadingAgents}
635+
submitFindSolution={submitFindSolution}
636+
handleLaunchCodingAgent={handleLaunchCodingAgent}
637+
findSolutionTitle={findSolutionTitle}
638+
/>
582639
</ButtonBar>
583640
{status === AutofixStatus.COMPLETED && (
584641
<AutofixStepFeedback stepType="root_cause" groupId={groupId} runId={runId} />
@@ -697,3 +754,8 @@ const DropdownTrigger = styled(Button)`
697754
border-radius: 0 ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0;
698755
border-left: none;
699756
`;
757+
758+
const SmallIntegrationIdText = styled('div')`
759+
font-size: ${p => p.theme.fontSize.sm};
760+
color: ${p => p.theme.subText};
761+
`;

0 commit comments

Comments
 (0)