Skip to content
Draft
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
75 changes: 64 additions & 11 deletions static/app/components/core/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
isMDXStory,
useStoriesLoader,
useStoryBookFiles,
type MDXStoryDescriptor,
} from 'sentry/stories/view/useStoriesLoader';
import {trackAnalytics} from 'sentry/utils/analytics';
import {useContextMenu} from 'sentry/utils/profiling/hooks/useContextMenu';
Expand Down Expand Up @@ -535,6 +536,12 @@ function MenuItem(props: {
};
}, [props.subMenuPortalRef, setIsOpen]);

const importStatement = story
? isMDXStory(story)
? getImportStatement(story, props.componentName)
: undefined
: undefined;

return (
<Fragment>
<ProfilingContextMenuItemButton
Expand Down Expand Up @@ -592,17 +599,35 @@ function MenuItem(props: {
<ProfilingContextMenuGroup>
<ProfilingContextMenuHeading>{t('Actions')}</ProfilingContextMenuHeading>
{props.storybook ? (
<ProfilingContextMenuItemButton
{...props.contextMenu.getMenuItemProps({
onClick: () => {
window.open(`/stories/?name=${props.storybook}`, '_blank');
props.onAction();
},
})}
icon={<IconLink size="xs" />}
>
{t('View Storybook')}
</ProfilingContextMenuItemButton>
<Fragment>
<ProfilingContextMenuItemButton
{...props.contextMenu.getMenuItemProps({
onClick: () => {
if (!importStatement) return;
// We expect story to be present if props.storybook is truthy:
props.copyToClipboard(importStatement);
addSuccessMessage(t('Import statement copied to clipboard'));
props.onAction();
},
})}
disabled={!importStatement}
icon={<IconCopy size="xs" />}
>
{t('Copy Import Statement')}
</ProfilingContextMenuItemButton>

<ProfilingContextMenuItemButton
{...props.contextMenu.getMenuItemProps({
onClick: () => {
window.open(`/stories/?name=${props.storybook}`, '_blank');
props.onAction();
},
})}
icon={<IconLink size="xs" />}
>
{t('View Storybook')}
</ProfilingContextMenuItemButton>
</Fragment>
) : null}
<ProfilingContextMenuItemButton
{...props.contextMenu.getMenuItemProps({
Expand Down Expand Up @@ -776,6 +801,34 @@ function getSourcePathFromMouseEvent(event: MouseEvent): TraceElement[] | null {
return trace;
}

function getImportStatement(
story: MDXStoryDescriptor,
componentName: string
): string | undefined {
if (!story?.exports?.documentation?.exports) {
return undefined;
}

const names: string[] = [];
let importPath: string | undefined = undefined;

for (const moduleName in story.exports.documentation.exports) {
if (importPath) break;
for (const decl of story.exports.documentation.exports[moduleName] ?? []) {
if (decl.name === componentName || decl.name === `${componentName}Props`) {
names.push(decl.typeOnly ? `type ${decl.name}` : decl.name);
importPath = moduleName;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Import Statement Fails for Split Exports

The getImportStatement function may fail to collect all related exports when the component and its Props type are in different modules. The if (importPath) break; on line 816 exits the outer loop after finding the first module with a match, preventing collection of ${componentName}Props if it exists in a different module than componentName. This will result in incomplete import statements when exports are spread across multiple modules.

Fix in Cursor Fix in Web


if (!names.length || !importPath) {
return undefined;
}

return `import { ${names.join(', ')} } from '${importPath}';`;
}

function isCoreComponent(el: unknown): boolean {
if (!isTraceElement(el)) return false;
return el.dataset.sentrySourcePath?.includes('app/components/core') ?? false;
Expand Down
Loading