Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 63 additions & 31 deletions static/app/views/seerExplorer/explorerMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import TimeSince from 'sentry/components/timeSince';
import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';
import {useExplorerSessions} from 'sentry/views/seerExplorer/hooks/useExplorerSessions';

type MenuMode = 'slash-commands-keyboard' | 'session-history' | 'hidden';
type MenuMode = 'slash-commands-keyboard' | 'session-history' | 'pr-widget' | 'hidden';

interface ExplorerMenuProps {
clearInput: () => void;
Expand All @@ -23,9 +23,12 @@ interface ExplorerMenuProps {
textAreaRef: React.RefObject<HTMLTextAreaElement | null>;
inputAnchorRef?: React.RefObject<HTMLElement | null>;
menuAnchorRef?: React.RefObject<HTMLElement | null>;
prWidgetAnchorRef?: React.RefObject<HTMLElement | null>;
prWidgetFooter?: React.ReactNode;
prWidgetItems?: MenuItemProps[];
}

interface MenuItemProps {
export interface MenuItemProps {
description: string | React.ReactNode;
handler: () => void;
key: string;
Expand All @@ -43,6 +46,9 @@ export function useExplorerMenu({
onChangeSession,
menuAnchorRef,
inputAnchorRef,
prWidgetAnchorRef,
prWidgetItems,
prWidgetFooter,
}: ExplorerMenuProps) {
const [menuMode, setMenuMode] = useState<MenuMode>('hidden');
const [menuPosition, setMenuPosition] = useState<{
Expand Down Expand Up @@ -74,10 +80,12 @@ export function useExplorerMenu({
return filteredSlashCommands;
case 'session-history':
return sessionItems;
case 'pr-widget':
return prWidgetItems ?? [];
default:
return [];
}
}, [menuMode, filteredSlashCommands, sessionItems]);
}, [menuMode, filteredSlashCommands, sessionItems, prWidgetItems]);

const close = useCallback(() => {
setMenuMode('hidden');
Expand Down Expand Up @@ -210,7 +218,11 @@ export function useExplorerMenu({
}

const anchorRef =
menuMode === 'slash-commands-keyboard' ? inputAnchorRef : menuAnchorRef;
menuMode === 'slash-commands-keyboard'
? inputAnchorRef
: menuMode === 'pr-widget'
? prWidgetAnchorRef
: menuAnchorRef;
const isSlashCommand = menuMode === 'slash-commands-keyboard';

if (!anchorRef?.current) {
Expand All @@ -233,24 +245,31 @@ export function useExplorerMenu({
const spacing = 8;
const relativeTop = rect.top - panelRect.top;
const relativeLeft = rect.left - panelRect.left;
const relativeRight = panelRect.right - rect.right;

setMenuPosition(
isSlashCommand
? {
bottom: `${panelRect.height - relativeTop + spacing}px`,
left: `${relativeLeft}px`,
}
: {
top: `${relativeTop + rect.height + spacing}px`,
left: `${relativeLeft}px`,
}
);
}, [isVisible, menuMode, menuAnchorRef, inputAnchorRef]);
if (isSlashCommand) {
setMenuPosition({
bottom: `${panelRect.height - relativeTop + spacing}px`,
left: `${relativeLeft}px`,
});
} else if (menuMode === 'pr-widget') {
// Position below anchor, aligned to right edge
setMenuPosition({
top: `${relativeTop + rect.height + spacing}px`,
right: `${relativeRight}px`,
});
} else {
setMenuPosition({
top: `${relativeTop + rect.height + spacing}px`,
left: `${relativeLeft}px`,
});
}
}, [isVisible, menuMode, menuAnchorRef, inputAnchorRef, prWidgetAnchorRef]);

const menu = (
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<MenuPanel panelSize={panelSize} style={menuPosition} data-seer-menu-panel="">
{menuItems.map((item, index) => (
{menuItems.map((item: MenuItemProps, index: number) => (
<MenuItem
key={item.key}
ref={el => {
Expand All @@ -274,6 +293,7 @@ export function useExplorerMenu({
</ItemName>
</MenuItem>
)}
{menuMode === 'pr-widget' && prWidgetFooter}
</MenuPanel>
</Activity>
);
Expand All @@ -288,12 +308,22 @@ export function useExplorerMenu({
}
}, [menuMode, close, refetchSessions]);

// Handler for opening PR widget from button
const openPRWidget = useCallback(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const openPRWidget = useCallback(() => {
const togglePRWidget = useCallback(() => {

same for openSessionHistory

if (menuMode === 'pr-widget') {
close();
} else {
setMenuMode('pr-widget');
}
}, [menuMode, close]);

return {
menu,
menuMode,
isMenuOpen: menuMode !== 'hidden',
closeMenu: close,
openSessionHistory,
openPRWidget,
};
}

Expand Down Expand Up @@ -370,20 +400,22 @@ function useSessions({
return [];
}

return data.data.map(session => ({
title: session.title,
key: session.run_id.toString(),
description: (
<TimeSince
tooltipPrefix="Last updated"
date={moment.utc(session.last_triggered_at).toDate()}
suffix="ago"
/>
),
handler: () => {
onChangeSession(session.run_id);
},
}));
return data.data.map(
(session: {last_triggered_at: moment.MomentInput; run_id: number; title: any}) => ({
title: session.title,
key: session.run_id.toString(),
description: (
<TimeSince
tooltipPrefix="Last updated"
date={moment.utc(session.last_triggered_at).toDate()}
suffix="ago"
/>
),
handler: () => {
onChangeSession(session.run_id);
},
})
);
}, [data, isPending, isError, onChangeSession]);

return {
Expand Down
1 change: 1 addition & 0 deletions static/app/views/seerExplorer/explorerPanel.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ describe('ExplorerPanel', () => {
runId: null,
setRunId: jest.fn(),
respondToUserInput: jest.fn(),
createPR: jest.fn(),
});

render(<ExplorerPanel isVisible />, {organization});
Expand Down
63 changes: 44 additions & 19 deletions static/app/views/seerExplorer/explorerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import InputSection from 'sentry/views/seerExplorer/inputSection';
import PanelContainers, {
BlocksContainer,
} from 'sentry/views/seerExplorer/panelContainers';
import {usePRWidgetData} from 'sentry/views/seerExplorer/prWidget';
import TopBar from 'sentry/views/seerExplorer/topBar';
import type {Block, ExplorerPanelProps} from 'sentry/views/seerExplorer/types';

Expand All @@ -36,8 +37,8 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
const userScrolledUpRef = useRef<boolean>(false);
const allowHoverFocusChange = useRef<boolean>(true);
const sessionHistoryButtonRef = useRef<HTMLButtonElement>(null);
const prWidgetButtonRef = useRef<HTMLButtonElement>(null);

// Custom hooks
const {panelSize, handleMaxSize, handleMedSize} = usePanelSizing();
const {
sessionData,
Expand All @@ -49,11 +50,25 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
interruptRequested,
setRunId,
respondToUserInput,
createPR,
} = useSeerExplorer();

// Extract repo_pr_states from session
const repoPRStates = useMemo(
() => sessionData?.repo_pr_states ?? {},
[sessionData?.repo_pr_states]
);

// Get blocks from session data or empty array
const blocks = useMemo(() => sessionData?.blocks || [], [sessionData]);

// Get PR widget data for menu
const {menuItems: prWidgetItems, menuFooter: prWidgetFooter} = usePRWidgetData({
blocks,
repoPRStates,
onCreatePR: createPR,
});

// Find the index of the last block that has todos (for special rendering)
const latestTodoBlockIndex = useMemo(() => {
for (let i = blocks.length - 1; i >= 0; i--) {
Expand Down Expand Up @@ -209,22 +224,26 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {

const openFeedbackForm = useFeedbackForm();

const {menu, isMenuOpen, menuMode, closeMenu, openSessionHistory} = useExplorerMenu({
clearInput: () => setInputValue(''),
inputValue,
focusInput,
textAreaRef: textareaRef,
panelSize,
panelVisible: isVisible,
slashCommandHandlers: {
onMaxSize: handleMaxSize,
onMedSize: handleMedSize,
onNew: startNewSession,
},
onChangeSession: setRunId,
menuAnchorRef: sessionHistoryButtonRef,
inputAnchorRef: textareaRef,
});
const {menu, isMenuOpen, menuMode, closeMenu, openSessionHistory, openPRWidget} =
useExplorerMenu({
clearInput: () => setInputValue(''),
inputValue,
focusInput,
textAreaRef: textareaRef,
panelSize,
panelVisible: isVisible,
slashCommandHandlers: {
onMaxSize: handleMaxSize,
onMedSize: handleMedSize,
onNew: startNewSession,
},
onChangeSession: setRunId,
menuAnchorRef: sessionHistoryButtonRef,
inputAnchorRef: textareaRef,
prWidgetAnchorRef: prWidgetButtonRef,
prWidgetItems,
prWidgetFooter,
});

const handlePanelBackgroundClick = useCallback(() => {
setIsMinimized(false);
Expand All @@ -241,10 +260,11 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
const target = event.target as Node;
const menuElement = document.querySelector('[data-seer-menu-panel]');

// Don't close if clicking on the menu itself or the button
// Don't close if clicking on the menu itself or the trigger buttons
if (
menuElement?.contains(target) ||
sessionHistoryButtonRef.current?.contains(target)
sessionHistoryButtonRef.current?.contains(target) ||
prWidgetButtonRef.current?.contains(target)
) {
return;
}
Expand Down Expand Up @@ -426,14 +446,19 @@ function ExplorerPanel({isVisible = false}: ExplorerPanelProps) {
onUnminimize={handleUnminimize}
>
<TopBar
blocks={blocks}
isEmptyState={isEmptyState}
isPolling={isPolling}
isSessionHistoryOpen={isMenuOpen && menuMode === 'session-history'}
onCreatePR={createPR}
onFeedbackClick={handleFeedbackClick}
onNewChatClick={startNewSession}
onPRWidgetClick={openPRWidget}
onSessionHistoryClick={openSessionHistory}
onSizeToggleClick={handleSizeToggle}
panelSize={panelSize}
prWidgetButtonRef={prWidgetButtonRef}
repoPRStates={repoPRStates}
sessionHistoryButtonRef={sessionHistoryButtonRef}
/>
{menu}
Expand Down
Loading
Loading