Просмотр исходного кода

feat(issues): GA stack trace link in frame (#65078)

Scott Cooper 1 год назад
Родитель
Сommit
9a1185a82f

+ 1 - 1
static/app/components/events/interfaces/crashContent/exception/banners/stacktraceBanners.spec.tsx

@@ -16,7 +16,7 @@ import {StacktraceBanners} from './stacktraceBanners';
 
 describe('StacktraceBanners', () => {
   const org = OrganizationFixture({
-    features: ['codecov-integration', 'issue-details-stacktrace-link-in-frame'],
+    features: ['codecov-integration'],
   });
   const project = ProjectFixture({});
 

+ 2 - 7
static/app/components/events/interfaces/crashContent/exception/banners/stacktraceBanners.tsx

@@ -7,10 +7,7 @@ import {
   usePromptsCheck,
 } from 'sentry/actionCreators/prompts';
 import useStacktraceLink from 'sentry/components/events/interfaces/frame/useStacktraceLink';
-import {
-  hasFileExtension,
-  hasStacktraceLinkInFrameFeature,
-} from 'sentry/components/events/interfaces/frame/utils';
+import {hasFileExtension} from 'sentry/components/events/interfaces/frame/utils';
 import type {
   Event,
   Frame,
@@ -71,9 +68,7 @@ export function StacktraceBanners({stacktrace, event}: StacktraceBannersProps) {
     [projects, event]
   );
 
-  const hasInFrameFeature = hasStacktraceLinkInFrameFeature(organization);
-  const enabled =
-    !!organization && !!expectedDefaultFrame && !!project && hasInFrameFeature;
+  const enabled = !!organization && !!expectedDefaultFrame && !!project;
 
   const {data: stacktraceLink} = useStacktraceLink(
     {

+ 3 - 35
static/app/components/events/interfaces/frame/context.tsx

@@ -3,11 +3,6 @@ import styled from '@emotion/styled';
 import keyBy from 'lodash/keyBy';
 
 import ClippedBox from 'sentry/components/clippedBox';
-import ErrorBoundary from 'sentry/components/errorBoundary';
-import {StacktraceLink} from 'sentry/components/events/interfaces/frame/stacktraceLink';
-import {usePrismTokensSourceContext} from 'sentry/components/events/interfaces/frame/usePrismTokensSourceContext';
-import {useStacktraceCoverage} from 'sentry/components/events/interfaces/frame/useStacktraceCoverage';
-import {hasStacktraceLinkInFrameFeature} from 'sentry/components/events/interfaces/frame/utils';
 import {IconFlag} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
@@ -31,7 +26,8 @@ import {Assembly} from './assembly';
 import ContextLineNumber from './contextLineNumber';
 import {FrameRegisters} from './frameRegisters';
 import {FrameVariables} from './frameVariables';
-import {OpenInContextLine} from './openInContextLine';
+import {usePrismTokensSourceContext} from './usePrismTokensSourceContext';
+import {useStacktraceCoverage} from './useStacktraceCoverage';
 
 type Props = {
   components: SentryAppComponent<SentryAppSchemaStacktraceLink>[];
@@ -73,19 +69,13 @@ function Context({
   hasAssembly = false,
   emptySourceNotation = false,
   registers,
-  components,
   frame,
   event,
   className,
   frameMeta,
   registersMeta,
 }: Props) {
-  const organization = useOrganization({allowNull: true});
-  const hasInFrameFeature = hasStacktraceLinkInFrameFeature(organization);
-
-  // This is the old design. Only show if the feature flag is not enabled for this organization.
-  const hasStacktraceLink =
-    frame.inApp && !!frame.filename && isExpanded && !hasInFrameFeature;
+  const organization = useOrganization();
 
   const {projects} = useProjects();
   const project = useMemo(
@@ -167,8 +157,6 @@ function Context({
               {lines.map((line, i) => {
                 const contextLine = contextLines[i];
                 const isActive = activeLineNumber === contextLine[0];
-                const hasComponents = isActive && components.length > 0;
-                const showStacktraceLink = hasStacktraceLink && isActive;
 
                 return (
                   <Fragment key={i}>
@@ -186,26 +174,6 @@ function Context({
                         ))}
                       </ContextLineCode>
                     </ContextLineWrapper>
-                    {!hasInFrameFeature && hasComponents && (
-                      <ErrorBoundary mini>
-                        <OpenInContextLine
-                          key={i}
-                          lineNo={contextLine[0]}
-                          filename={frame.filename || ''}
-                          components={components}
-                        />
-                      </ErrorBoundary>
-                    )}
-                    {showStacktraceLink && (
-                      <ErrorBoundary customComponent={null}>
-                        <StacktraceLink
-                          key={i}
-                          line={contextLine[1]}
-                          frame={frame}
-                          event={event}
-                        />
-                      </ErrorBoundary>
-                    )}
                   </Fragment>
                 );
               })}

+ 2 - 5
static/app/components/events/interfaces/frame/deprecatedLine.tsx

@@ -45,7 +45,6 @@ import {
   hasContextRegisters,
   hasContextSource,
   hasContextVars,
-  hasStacktraceLinkInFrameFeature,
   isExpandable,
 } from './utils';
 
@@ -343,15 +342,13 @@ export class DeprecatedLine extends Component<Props, State> {
 
     const activeLineNumber = data.lineNo;
     const contextLine = (data?.context || []).find(l => l[0] === activeLineNumber);
-    const hasInFrameFeature = hasStacktraceLinkInFrameFeature(organization);
     // InApp or .NET because of: https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink
     const hasStacktraceLink =
       (data.inApp || event.platform === 'csharp') &&
       !!data.filename &&
       (isHovering || isExpanded);
-    const showStacktraceLinkInFrame = hasStacktraceLink && hasInFrameFeature;
     const showSentryAppStacktraceLinkInFrame =
-      showStacktraceLinkInFrame && this.props.components.length > 0;
+      hasStacktraceLink && this.props.components.length > 0;
 
     return (
       <StrictClick onClick={this.isExpandable() ? this.toggleContext : undefined}>
@@ -383,7 +380,7 @@ export class DeprecatedLine extends Component<Props, State> {
                 {t('Suspect Frame')}
               </Tag>
             ) : null}
-            {showStacktraceLinkInFrame && !shouldShowSourceMapDebuggerButton && (
+            {hasStacktraceLink && !shouldShowSourceMapDebuggerButton && (
               <ErrorBoundary>
                 <StacktraceLink
                   frame={data}

+ 0 - 12
static/app/components/events/interfaces/frame/openInContextLine.spec.tsx

@@ -1,5 +1,4 @@
 import {GroupFixture} from 'sentry-fixture/group';
-import {OrganizationFixture} from 'sentry-fixture/organization';
 import {SentryAppInstallationFixture} from 'sentry-fixture/sentryAppInstallation';
 
 import {render, screen} from 'sentry-test/reactTestingLibrary';
@@ -67,17 +66,6 @@ describe('OpenInContextLine', function () {
         'href',
         'http://localhost:4000/something?filename=%2Fsentry%2Fapp.py&installationId=25d10adb-7b89-45ac-99b5-edaa714341ba&lineNo=233&projectSlug=internal'
       );
-    });
-
-    it('renders only app icons with issue-details-stacktrace-link-in-frame feature', function () {
-      const organization = OrganizationFixture({
-        features: ['issue-details-stacktrace-link-in-frame'],
-      });
-      render(
-        <OpenInContextLine filename={filename} lineNo={lineNo} components={components} />,
-        {organization}
-      );
-
       expect(screen.getByRole('link', {name: 'Foo'})).toHaveTextContent('');
       expect(screen.getByRole('link', {name: 'Tesla'})).toHaveTextContent('');
     });

+ 6 - 38
static/app/components/events/interfaces/frame/openInContextLine.tsx

@@ -1,7 +1,6 @@
-import {css, keyframes} from '@emotion/react';
+import {keyframes} from '@emotion/react';
 import styled from '@emotion/styled';
 
-import {hasStacktraceLinkInFrameFeature} from 'sentry/components/events/interfaces/frame/utils';
 import ExternalLink from 'sentry/components/links/externalLink';
 import SentryAppComponentIcon from 'sentry/components/sentryAppComponentIcon';
 import {Tooltip} from 'sentry/components/tooltip';
@@ -9,7 +8,6 @@ import {space} from 'sentry/styles/space';
 import type {SentryAppComponent, SentryAppSchemaStacktraceLink} from 'sentry/types';
 import {addQueryParamsToExistingUrl} from 'sentry/utils/queryString';
 import {recordInteraction} from 'sentry/utils/recordSentryAppInteraction';
-import useOrganization from 'sentry/utils/useOrganization';
 
 type Props = {
   components: SentryAppComponent<SentryAppSchemaStacktraceLink>[];
@@ -18,9 +16,6 @@ type Props = {
 };
 
 function OpenInContextLine({lineNo, filename, components}: Props) {
-  const organization = useOrganization({allowNull: true});
-  const hasInFrameFeature = hasStacktraceLinkInFrameFeature(organization);
-
   const handleRecordInteraction =
     (slug: SentryAppComponent<SentryAppSchemaStacktraceLink>['sentryApp']['slug']) =>
     () => {
@@ -34,21 +29,13 @@ function OpenInContextLine({lineNo, filename, components}: Props) {
   };
 
   return (
-    <OpenInContainer
-      columnQuantity={components.length + 1}
-      hasInFrameFeature={hasInFrameFeature}
-    >
+    <OpenInContainer>
       {components.map(component => {
         const url = getUrl(component.schema.url);
         const {slug} = component.sentryApp;
         const onClickRecordInteraction = handleRecordInteraction(slug);
         return (
-          <Tooltip
-            key={component.uuid}
-            title={component.sentryApp.name}
-            disabled={!hasInFrameFeature}
-            skipWrapper
-          >
+          <Tooltip key={component.uuid} title={component.sentryApp.name} skipWrapper>
             <OpenInLink
               aria-label={component.sentryApp.name}
               href={url}
@@ -56,11 +43,7 @@ function OpenInContextLine({lineNo, filename, components}: Props) {
               onContextMenu={onClickRecordInteraction}
               openInNewTab
             >
-              <SentryAppComponentIcon
-                sentryAppComponent={component}
-                size={hasInFrameFeature ? 16 : 20}
-              />
-              {!hasInFrameFeature && <span>{component.sentryApp.name}</span>}
+              <SentryAppComponentIcon sentryAppComponent={component} size={16} />
             </OpenInLink>
           </Tooltip>
         );
@@ -76,10 +59,7 @@ const fadeIn = keyframes`
   to { opacity: 1; }
 `;
 
-const OpenInContainer = styled('div')<{
-  columnQuantity: number;
-  hasInFrameFeature: boolean;
-}>`
+const OpenInContainer = styled('div')`
   display: flex;
   gap: ${space(1)};
   align-items: center;
@@ -88,19 +68,7 @@ const OpenInContainer = styled('div')<{
   text-indent: initial;
   overflow: auto;
   white-space: nowrap;
-  ${p =>
-    p.hasInFrameFeature
-      ? css`
-          animation: ${fadeIn} 0.2s ease-in-out forwards;
-          padding: 0;
-        `
-      : css`
-          color: ${p.theme.subText};
-          background-color: ${p.theme.background};
-          border-bottom: 1px solid ${p.theme.border};
-          padding: ${space(0.25)} ${space(3)};
-          box-shadow: ${p.theme.dropShadowLight};
-        `}
+  animation: ${fadeIn} 0.2s ease-in-out forwards;
 `;
 
 const OpenInLink = styled(ExternalLink)`

+ 12 - 142
static/app/components/events/interfaces/frame/stacktraceLink.spec.tsx

@@ -6,7 +6,6 @@ import {ProjectFixture} from 'sentry-fixture/project';
 import {ReleaseFixture} from 'sentry-fixture/release';
 import {RepositoryFixture} from 'sentry-fixture/repository';
 import {RepositoryProjectPathConfigFixture} from 'sentry-fixture/repositoryProjectPathConfig';
-import {RouterContextFixture} from 'sentry-fixture/routerContextFixture';
 
 import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
 
@@ -32,97 +31,25 @@ describe('StacktraceLink', function () {
 
   const frame = {filename: '/sentry/app.py', lineNo: 233, inApp: true} as Frame;
   const config = RepositoryProjectPathConfigFixture({project, repo, integration});
-  let promptActivity: jest.Mock;
 
   const analyticsSpy = jest.spyOn(analytics, 'trackAnalytics');
 
   beforeEach(function () {
     jest.clearAllMocks();
     MockApiClient.clearMockResponses();
-    promptActivity = MockApiClient.addMockResponse({
-      method: 'GET',
-      url: `/organizations/${org.slug}/prompts-activity/`,
-      body: {},
-    });
     ProjectsStore.loadInitialData([project]);
     HookStore.init?.();
   });
 
-  it('renders ask to setup integration', async function () {
-    const stacktraceLinkMock = MockApiClient.addMockResponse({
-      url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
-      body: {config: null, sourceUrl: null, integrations: []},
-    });
-    render(<StacktraceLink frame={frame} event={event} line="" />, {
-      context: RouterContextFixture(),
-    });
-    expect(
-      await screen.findByText(
-        'Add the GitHub or GitLab integration to jump straight to your source code'
-      )
-    ).toBeInTheDocument();
-    expect(stacktraceLinkMock).toHaveBeenCalledTimes(1);
-    expect(stacktraceLinkMock).toHaveBeenCalledWith(
-      `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
-      expect.objectContaining({
-        query: {
-          commitId: event.release?.lastCommit?.id,
-          file: frame.filename,
-          platform,
-          lineNo: frame.lineNo,
-          groupId: event.groupID,
-        },
-      })
-    );
-    expect(promptActivity).toHaveBeenCalledTimes(1);
-    expect(promptActivity).toHaveBeenCalledWith(
-      `/organizations/${org.slug}/prompts-activity/`,
-      expect.objectContaining({
-        query: {
-          feature: 'stacktrace_link',
-          organization_id: org.id,
-          project_id: project.id,
-        },
-      })
-    );
-  });
-
-  it('can dismiss stacktrace link CTA', async function () {
+  it('renders nothing when missing integrations', async function () {
     MockApiClient.addMockResponse({
       url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
       body: {config: null, sourceUrl: null, integrations: []},
     });
-    const dismissPrompt = MockApiClient.addMockResponse({
-      method: 'PUT',
-      url: `/organizations/${org.slug}/prompts-activity/`,
-      body: {},
-    });
-    const {container} = render(<StacktraceLink frame={frame} event={event} line="" />, {
-      context: RouterContextFixture(),
-    });
-    expect(
-      await screen.findByText(
-        'Add the GitHub or GitLab integration to jump straight to your source code'
-      )
-    ).toBeInTheDocument();
-
-    await userEvent.click(screen.getByRole('button'));
-
+    const {container} = render(<StacktraceLink frame={frame} event={event} line="" />);
     await waitFor(() => {
       expect(container).toBeEmptyDOMElement();
     });
-
-    expect(dismissPrompt).toHaveBeenCalledWith(
-      `/organizations/${org.slug}/prompts-activity/`,
-      expect.objectContaining({
-        data: {
-          feature: 'stacktrace_link',
-          status: 'dismissed',
-          organization_id: org.id,
-          project_id: project.id,
-        },
-      })
-    );
   });
 
   it('renders setup CTA with integration but no configs', async function () {
@@ -130,9 +57,7 @@ describe('StacktraceLink', function () {
       url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
       body: {config: null, sourceUrl: null, integrations: [integration]},
     });
-    render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
-      context: RouterContextFixture(),
-    });
+    render(<StacktraceLink frame={frame} event={event} line="foo()" />);
     expect(await screen.findByText('Set up Code Mapping')).toBeInTheDocument();
   });
 
@@ -141,9 +66,7 @@ describe('StacktraceLink', function () {
       url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
       body: {config, sourceUrl: 'https://something.io', integrations: [integration]},
     });
-    render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
-      context: RouterContextFixture(),
-    });
+    render(<StacktraceLink frame={frame} event={event} line="foo()" />);
     const link = await screen.findByRole('link', {name: 'Open this line in GitHub'});
     expect(link).toBeInTheDocument();
     expect(link).toHaveAttribute('href', 'https://something.io#L233');
@@ -158,9 +81,7 @@ describe('StacktraceLink', function () {
         integrations: [integration],
       },
     });
-    render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
-      context: RouterContextFixture(),
-    });
+    render(<StacktraceLink frame={frame} event={event} line="foo()" />);
     expect(
       await screen.findByRole('button', {name: 'Set up Code Mapping'})
     ).toBeInTheDocument();
@@ -180,8 +101,7 @@ describe('StacktraceLink', function () {
         frame={frame}
         event={{...event, platform: 'javascript'}}
         line="{snip} somethingInsane=e.IsNotFound {snip}"
-      />,
-      {context: RouterContextFixture()}
+      />
     );
     await waitFor(() => {
       expect(container).toBeEmptyDOMElement();
@@ -198,8 +118,7 @@ describe('StacktraceLink', function () {
       },
     });
     const {container} = render(
-      <StacktraceLink frame={frame} event={{...event, platform: 'unreal'}} line="" />,
-      {context: RouterContextFixture()}
+      <StacktraceLink frame={frame} event={{...event, platform: 'unreal'}} line="" />
     );
     await waitFor(() => {
       expect(container).toBeEmptyDOMElement();
@@ -229,7 +148,6 @@ describe('StacktraceLink', function () {
       },
     });
     render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
-      context: RouterContextFixture(),
       organization,
     });
 
@@ -266,56 +184,19 @@ describe('StacktraceLink', function () {
       body: {status: CodecovStatusCode.NO_COVERAGE_DATA},
     });
     render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
-      context: RouterContextFixture(),
       organization,
     });
     expect(await screen.findByText('Code Coverage not found')).toBeInTheDocument();
   });
 
-  it('renders the codecov prompt', async function () {
-    HookStore.add(
-      'component:codecov-integration-stacktrace-link',
-      () =>
-        function () {
-          return <div data-test-id="codecov-link" />;
-        }
-    );
-    const organization = {
-      ...org,
-      features: ['codecov-integration'],
-      codecovAccess: false,
-    };
-    MockApiClient.addMockResponse({
-      url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
-      body: {
-        config,
-        sourceUrl: 'https://github.com/username/path/to/file.py',
-        integrations: [integration],
-      },
-    });
-    MockApiClient.addMockResponse({
-      url: `/projects/${org.slug}/${project.slug}/stacktrace-coverage/`,
-      body: {},
-    });
-    render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
-      context: RouterContextFixture(),
-      organization,
-    });
-    expect(await screen.findByTestId('codecov-link')).toBeInTheDocument();
-  });
-
   it('renders the link using a valid sourceLink for a .NET project', async function () {
     const dotnetFrame = {
       filename: 'path/to/file.py',
       sourceLink: 'https://www.github.com/username/path/to/file.py#L100',
       lineNo: '100',
     } as unknown as Frame;
-    const organization = {
-      ...org,
-      features: ['issue-details-stacktrace-link-in-frame'],
-    };
     MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/${project.slug}/stacktrace-link/`,
+      url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
       body: {
         config,
         integrations: [integration],
@@ -326,11 +207,7 @@ describe('StacktraceLink', function () {
         frame={dotnetFrame}
         event={{...event, platform: 'csharp'}}
         line="foo()"
-      />,
-      {
-        context: RouterContextFixture(),
-        organization,
-      }
+      />
     );
     const link = await screen.findByRole('link', {name: 'GitHub'});
     expect(link).toBeInTheDocument();
@@ -349,8 +226,7 @@ describe('StacktraceLink', function () {
       },
     });
     const {container} = render(
-      <StacktraceLink frame={frame} event={{...event, platform: 'csharp'}} line="" />,
-      {context: RouterContextFixture()}
+      <StacktraceLink frame={frame} event={{...event, platform: 'csharp'}} line="" />
     );
     await waitFor(() => {
       expect(container).toBeEmptyDOMElement();
@@ -358,17 +234,11 @@ describe('StacktraceLink', function () {
   });
 
   it('renders in-frame stacktrace links and fetches data with 100ms delay', async function () {
-    const organization = OrganizationFixture({
-      features: ['issue-details-stacktrace-link-in-frame'],
-    });
     const mockRequest = MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/${project.slug}/stacktrace-link/`,
+      url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
       body: {config, sourceUrl: 'https://something.io', integrations: [integration]},
     });
-    render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
-      context: RouterContextFixture([{organization}]),
-      organization,
-    });
+    render(<StacktraceLink frame={frame} event={event} line="foo()" />);
 
     const link = await screen.findByRole('link', {name: 'Open this line in GitHub'});
     expect(link).toBeInTheDocument();

+ 34 - 245
static/app/components/events/interfaces/frame/stacktraceLink.tsx

@@ -3,24 +3,13 @@ import {css, keyframes} from '@emotion/react';
 import styled from '@emotion/styled';
 
 import {openModal} from 'sentry/actionCreators/modal';
-import type {PromptResponse} from 'sentry/actionCreators/prompts';
-import {
-  makePromptsCheckQueryKey,
-  promptsUpdate,
-  usePromptsCheck,
-} from 'sentry/actionCreators/prompts';
 import {Button} from 'sentry/components/button';
 import {useStacktraceCoverage} from 'sentry/components/events/interfaces/frame/useStacktraceCoverage';
-import {
-  hasFileExtension,
-  hasStacktraceLinkInFrameFeature,
-} from 'sentry/components/events/interfaces/frame/utils';
-import HookOrDefault from 'sentry/components/hookOrDefault';
+import {hasFileExtension} from 'sentry/components/events/interfaces/frame/utils';
 import ExternalLink from 'sentry/components/links/externalLink';
-import Link from 'sentry/components/links/link';
 import Placeholder from 'sentry/components/placeholder';
 import {Tooltip} from 'sentry/components/tooltip';
-import {IconClose, IconWarning} from 'sentry/icons';
+import {IconWarning} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {
@@ -28,27 +17,19 @@ import type {
   Frame,
   Organization,
   PlatformKey,
-  Project,
   StacktraceLinkResult,
 } from 'sentry/types';
 import {CodecovStatusCode} from 'sentry/types';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {getAnalyticsDataForEvent} from 'sentry/utils/events';
 import {getIntegrationIcon, getIntegrationSourceUrl} from 'sentry/utils/integrationUtil';
-import {promptIsDismissed} from 'sentry/utils/promptIsDismissed';
-import {setApiQueryData, useQueryClient} from 'sentry/utils/queryClient';
 import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
-import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
 import useProjects from 'sentry/utils/useProjects';
 
 import StacktraceLinkModal from './stacktraceLinkModal';
 import useStacktraceLink from './useStacktraceLink';
 
-const HookCodecovStacktraceLink = HookOrDefault({
-  hookName: 'component:codecov-integration-stacktrace-link',
-});
-
 const supportedStacktracePlatforms: PlatformKey[] = [
   'go',
   'javascript',
@@ -59,68 +40,6 @@ const supportedStacktracePlatforms: PlatformKey[] = [
   'elixir',
 ];
 
-interface StacktraceLinkSetupProps {
-  event: Event;
-  hasInFrameFeature: boolean;
-  organization: Organization;
-  project?: Project;
-}
-
-function StacktraceLinkSetup({
-  organization,
-  project,
-  event,
-  hasInFrameFeature,
-}: StacktraceLinkSetupProps) {
-  const api = useApi();
-  const queryClient = useQueryClient();
-
-  const dismissPrompt = () => {
-    promptsUpdate(api, {
-      organization,
-      projectId: project?.id,
-      feature: 'stacktrace_link',
-      status: 'dismissed',
-    });
-
-    // Update cached query data
-    // Will set prompt to dismissed
-    setApiQueryData<PromptResponse>(
-      queryClient,
-      makePromptsCheckQueryKey({
-        organization,
-        feature: 'stacktrace_link',
-        projectId: project?.id,
-      }),
-      () => {
-        const dimissedTs = new Date().getTime() / 1000;
-        return {
-          data: {dismissed_ts: dimissedTs},
-          features: {stacktrace_link: {dismissed_ts: dimissedTs}},
-        };
-      }
-    );
-
-    trackAnalytics('integrations.stacktrace_link_cta_dismissed', {
-      view: 'stacktrace_issue_details',
-      organization,
-      ...getAnalyticsDataForEvent(event),
-    });
-  };
-
-  return (
-    <StacktraceLinkWrapper hasInFrameFeature={hasInFrameFeature}>
-      <StyledLink to={`/settings/${organization.slug}/integrations/`}>
-        <StyledIconWrapper>{getIntegrationIcon('github', 'sm')}</StyledIconWrapper>
-        {t('Add the GitHub or GitLab integration to jump straight to your source code')}
-      </StyledLink>
-      <CloseButton priority="link" onClick={dismissPrompt}>
-        <IconClose size="xs" aria-label={t('Close')} />
-      </CloseButton>
-    </StacktraceLinkWrapper>
-  );
-}
-
 function shouldShowCodecovFeatures(
   organization: Organization,
   match: StacktraceLinkResult,
@@ -133,22 +52,8 @@ function shouldShowCodecovFeatures(
   );
 }
 
-/**
- * TODO(scttcper): Should be removed w/ GA issue-details-stacktrace-link-in-frame
- */
-function shouldShowCodecovPrompt(
-  organization: Organization,
-  match: StacktraceLinkResult
-) {
-  const enabled =
-    organization.features.includes('codecov-integration') && !organization.codecovAccess;
-
-  return enabled && match.config?.provider.key === 'github';
-}
-
 interface CodecovLinkProps {
   event: Event;
-  hasInFrameFeature: boolean;
   organization: Organization;
   coverageUrl?: string;
   status?: CodecovStatusCode;
@@ -159,7 +64,6 @@ function CodecovLink({
   status = CodecovStatusCode.COVERAGE_EXISTS,
   organization,
   event,
-  hasInFrameFeature,
 }: CodecovLinkProps) {
   if (status === CodecovStatusCode.NO_COVERAGE_DATA) {
     return (
@@ -189,13 +93,11 @@ function CodecovLink({
       href={coverageUrl}
       openInNewTab
       onClick={onOpenCodecovLink}
-      aria-label={hasInFrameFeature ? t('Open in Codecov') : undefined}
-      hasInFrameFeature={hasInFrameFeature}
+      aria-label={t('Open in Codecov')}
     >
-      <Tooltip title={t('Open in Codecov')} disabled={!hasInFrameFeature} skipWrapper>
+      <Tooltip title={t('Open in Codecov')} skipWrapper>
         <StyledIconWrapper>{getIntegrationIcon('codecov', 'sm')}</StyledIconWrapper>
       </Tooltip>
-      {hasInFrameFeature ? null : t('Open in Codecov')}
     </OpenInLink>
   );
 }
@@ -212,7 +114,6 @@ interface StacktraceLinkProps {
 export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
   const organization = useOrganization();
   const {projects} = useProjects();
-  const hasInFrameFeature = hasStacktraceLinkInFrameFeature(organization);
   const validFilePath = hasFileExtension(frame.absPath || frame.filename || '');
   // TODO: Currently we only support GitHub links. Implement support for other source code providers.
   // Related comment: https://github.com/getsentry/sentry/pull/62596#discussion_r1443025242
@@ -220,41 +121,27 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
     'https://www.github.com/'
   );
   const [isQueryEnabled, setIsQueryEnabled] = useState(
-    hasGithubSourceLink ? false : !frame.inApp ? false : !hasInFrameFeature
+    hasGithubSourceLink ? false : !frame.inApp
   );
   const project = useMemo(
     () => projects.find(p => p.id === event.projectID),
     [projects, event]
   );
 
-  const prompt = usePromptsCheck({
-    organization,
-    feature: 'stacktrace_link',
-    projectId: project?.id,
-  });
-  const isPromptDismissed =
-    prompt.isSuccess && prompt.data.data
-      ? promptIsDismissed({
-          dismissedTime: prompt.data.data.dismissed_ts,
-          snoozedTime: prompt.data.data.snoozed_ts,
-        })
-      : false;
-
   useEffect(() => {
     let timer: ReturnType<typeof setTimeout> | undefined;
     if (!validFilePath) {
       return setIsQueryEnabled(false);
     }
     // Skip fetching if we already have the Source Link
-    if (hasInFrameFeature && !hasGithubSourceLink && frame.inApp) {
+    if (!hasGithubSourceLink && frame.inApp) {
       // Introduce a delay before enabling the query
-
       timer = setTimeout(() => {
         setIsQueryEnabled(true);
       }, 100); // Delay of 100ms
     }
     return () => timer && clearTimeout(timer);
-  }, [hasInFrameFeature, validFilePath, hasGithubSourceLink, frame]);
+  }, [validFilePath, hasGithubSourceLink, frame]);
 
   const {
     data: match,
@@ -293,9 +180,7 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
             ? 'match'
             : match.error || match.integrations.length
               ? 'no_match'
-              : !isPromptDismissed
-                ? 'prompt'
-                : 'empty',
+              : 'empty',
         }
       : {}
   );
@@ -345,21 +230,14 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
 
   // Render the provided `sourceLink` for all the non-inapp frames for `csharp` platform Issues
   // We skip fetching from the API for these frames.
-  if (
-    !match &&
-    hasGithubSourceLink &&
-    !frame.inApp &&
-    frame.sourceLink &&
-    hasInFrameFeature
-  ) {
+  if (!match && hasGithubSourceLink && !frame.inApp && frame.sourceLink) {
     return (
-      <StacktraceLinkWrapper hasInFrameFeature={hasInFrameFeature}>
+      <StacktraceLinkWrapper>
         <Tooltip title={t('Open this line in GitHub')} skipWrapper>
           <OpenInLink
             onClick={e => onOpenLink(e, frame.sourceLink)}
             href={frame.sourceLink}
             openInNewTab
-            hasInFrameFeature={hasInFrameFeature}
             aria-label={t('GitHub')}
           >
             <StyledIconWrapper>{getIntegrationIcon('github', 'sm')}</StyledIconWrapper>
@@ -371,19 +249,17 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
 
   if (isLoading || !match) {
     return (
-      <StacktraceLinkWrapper hasInFrameFeature={hasInFrameFeature}>
-        <Placeholder
-          height={hasInFrameFeature ? '14px' : '24px'}
-          width={hasInFrameFeature ? '40px' : '120px'}
-        />
+      <StacktraceLinkWrapper>
+        <Placeholder height="14px" width="40px" />
       </StacktraceLinkWrapper>
     );
   }
 
   // Match found - display link to source
   if (match.config && match.sourceUrl) {
+    const label = t('Open this line in %s', match.config.provider.name);
     return (
-      <StacktraceLinkWrapper hasInFrameFeature={hasInFrameFeature}>
+      <StacktraceLinkWrapper>
         <OpenInLink
           onClick={onOpenLink}
           href={getIntegrationSourceUrl(
@@ -392,25 +268,13 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
             frame.lineNo
           )}
           openInNewTab
-          aria-label={
-            hasInFrameFeature
-              ? t('Open this line in %s', match.config.provider.name)
-              : undefined
-          }
-          hasInFrameFeature={hasInFrameFeature}
+          aria-label={label}
         >
-          <Tooltip
-            disabled={!hasInFrameFeature}
-            title={t('Open this line in %s', match.config.provider.name)}
-            skipWrapper
-          >
+          <Tooltip title={label} skipWrapper>
             <StyledIconWrapper>
               {getIntegrationIcon(match.config.provider.key, 'sm')}
             </StyledIconWrapper>
           </Tooltip>
-          {hasInFrameFeature
-            ? null
-            : t('Open this line in %s', match.config.provider.name)}
         </OpenInLink>
         {coverageEnabled && isLoadingCoverage ? (
           <Placeholder height="14px" width="14px" />
@@ -421,10 +285,7 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
             status={coverage.status}
             organization={organization}
             event={event}
-            hasInFrameFeature={hasInFrameFeature}
           />
-        ) : shouldShowCodecovPrompt(organization, match) && !hasInFrameFeature ? (
-          <HookCodecovStacktraceLink organization={organization} />
         ) : null}
       </StacktraceLinkWrapper>
     );
@@ -446,16 +307,10 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
     (match.error || match.integrations.length > 0)
   ) {
     return (
-      <StacktraceLinkWrapper hasInFrameFeature={hasInFrameFeature}>
-        <Tooltip title={t('GitHub')} disabled={!hasInFrameFeature} skipWrapper>
-          <OpenInLink
-            onClick={onOpenLink}
-            href={frame.sourceLink}
-            openInNewTab
-            hasInFrameFeature={hasInFrameFeature}
-          >
+      <StacktraceLinkWrapper>
+        <Tooltip title={t('GitHub')} skipWrapper>
+          <OpenInLink onClick={onOpenLink} href={frame.sourceLink} openInNewTab>
             <StyledIconWrapper>{getIntegrationIcon('github', 'sm')}</StyledIconWrapper>
-            {hasInFrameFeature ? null : t('Open this line in GitHub')}
           </OpenInLink>
         </Tooltip>
         {coverageEnabled && isLoadingCoverage ? (
@@ -467,10 +322,7 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
             status={coverage.status}
             organization={organization}
             event={event}
-            hasInFrameFeature={hasInFrameFeature}
           />
-        ) : shouldShowCodecovPrompt(organization, match) && !hasInFrameFeature ? (
-          <HookCodecovStacktraceLink organization={organization} />
         ) : null}
       </StacktraceLinkWrapper>
     );
@@ -487,10 +339,9 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
       ['github', 'gitlab'].includes(integration.provider?.key)
     );
     return (
-      <StacktraceLinkWrapper hasInFrameFeature={hasInFrameFeature}>
+      <StacktraceLinkWrapper>
         <FixMappingButton
           priority="link"
-          hasInFrameFeature={hasInFrameFeature}
           icon={
             sourceCodeProviders.length === 1
               ? getIntegrationIcon(sourceCodeProviders[0].provider.key, 'sm')
@@ -527,20 +378,7 @@ export function StacktraceLink({frame, event, line}: StacktraceLinkProps) {
     );
   }
 
-  // No integrations, but prompt is dismissed or hidden
-  if (hideErrors || isPromptDismissed) {
-    return null;
-  }
-
-  // No integrations
-  return (
-    <StacktraceLinkSetup
-      event={event}
-      project={project}
-      organization={organization}
-      hasInFrameFeature={hasInFrameFeature}
-    />
-  );
+  return null;
 }
 
 const fadeIn = keyframes`
@@ -548,51 +386,20 @@ const fadeIn = keyframes`
   to { opacity: 1; }
 `;
 
-const StacktraceLinkWrapper = styled('div')<{
-  hasInFrameFeature: boolean;
-}>`
+const StacktraceLinkWrapper = styled('div')`
   display: flex;
-  gap: ${space(2)};
+  gap: ${space(1)};
   align-items: center;
   color: ${p => p.theme.subText};
   font-family: ${p => p.theme.text.family};
-
-  ${p =>
-    p.hasInFrameFeature
-      ? css`
-      padding: ${space(0)} ${space(1)};
-      gap: ${space(1)}
-    `
-      : css`
-      background-color: ${p.theme.background};
-      border-bottom: 1px solid ${p.theme.border};
-      padding: ${space(0.25)} ${space(3)};
-      box-shadow: ${p.theme.dropShadowLight};
-      min-height: 28px;
-
-      `}
-`;
-
-const FixMappingButton = styled(Button)<{
-  hasInFrameFeature: boolean;
-}>`
-  color: ${p => p.theme.subText};
-
-  ${p =>
-    p.hasInFrameFeature
-      ? css`
-      &:hover {
-        color: ${p.theme.subText};
-        text-decoration: underline;
-        text-decoration-color: ${p.theme.subText};
-        text-underline-offset: ${space(0.5)};
-      }
-    `
-      : ``}
+  padding: ${space(0)} ${space(1)};
 `;
 
-const CloseButton = styled(Button)`
+const FixMappingButton = styled(Button)`
   color: ${p => p.theme.subText};
+  &:hover {
+    color: ${p => p.theme.subText};
+  }
 `;
 
 const StyledIconWrapper = styled('span')`
@@ -606,31 +413,13 @@ const LinkStyles = css`
   gap: ${space(0.75)};
 `;
 
-const OpenInLink = styled(ExternalLink)<{
-  hasInFrameFeature: boolean;
-}>`
+const OpenInLink = styled(ExternalLink)`
   ${LinkStyles}
-  ${p =>
-    p.hasInFrameFeature
-      ? css`
-          color: ${p.theme.subText};
-          animation: ${fadeIn} 0.2s ease-in-out forwards;
-          width: max-content;
-          &:hover {
-            text-decoration: underline;
-            text-decoration-color: ${p.theme.textColor};
-            text-underline-offset: ${space(0.5)};
-            color: ${p.theme.textColor};
-          }
-        `
-      : css`
-          color: ${p.theme.gray300};
-        `}
-`;
-
-const StyledLink = styled(Link)`
-  ${LinkStyles}
-  color: ${p => p.theme.gray300};
+  color: ${p => p.theme.subText};
+  animation: ${fadeIn} 0.2s ease-in-out forwards;
+  &:hover {
+    color: ${p => p.theme.textColor};
+  }
 `;
 
 const CodecovWarning = styled('div')`

+ 1 - 9
static/app/components/events/interfaces/frame/utils.tsx

@@ -1,6 +1,6 @@
 import {IconQuestion, IconWarning} from 'sentry/icons';
 import {t} from 'sentry/locale';
-import type {Event, Frame, Organization, PlatformKey} from 'sentry/types';
+import type {Event, Frame, PlatformKey} from 'sentry/types';
 import {EventOrGroupType} from 'sentry/types';
 import {defined, objectIsEmpty} from 'sentry/utils';
 
@@ -147,11 +147,3 @@ export function hasFileExtension(filepath: string) {
   // Check if the filepath matches the pattern
   return fileExtensionPattern.test(filepath);
 }
-
-export function hasStacktraceLinkInFrameFeature(
-  organization?: Organization | null
-): boolean {
-  return (
-    organization?.features?.includes('issue-details-stacktrace-link-in-frame') ?? false
-  );
-}

+ 2 - 9
static/app/components/events/interfaces/nativeFrame.tsx

@@ -13,7 +13,6 @@ import {
   hasContextRegisters,
   hasContextSource,
   hasContextVars,
-  hasStacktraceLinkInFrameFeature,
   isExpandable,
   trimPackage,
 } from 'sentry/components/events/interfaces/frame/utils';
@@ -39,7 +38,6 @@ import type {
 } from 'sentry/types';
 import type {Event} from 'sentry/types/event';
 import {defined} from 'sentry/utils';
-import useOrganization from 'sentry/utils/useOrganization';
 import withSentryAppComponents from 'sentry/utils/withSentryAppComponents';
 
 import type DebugImage from './debugMeta/debugImage';
@@ -101,8 +99,6 @@ function NativeFrame({
    */
   isHoverPreviewed = false,
 }: Props) {
-  const organization = useOrganization();
-
   const traceEventDataSectionContext = useContext(TraceEventDataSectionContext);
 
   const absolute = traceEventDataSectionContext?.display.includes('absolute-addresses');
@@ -151,10 +147,7 @@ function NativeFrame({
 
   const contextLine = (frame?.context || []).find(l => l[0] === frame.lineNo);
   const hasStacktraceLink = frame.inApp && !!frame.filename && (isHovering || expanded);
-  const hasInFrameFeature = hasStacktraceLinkInFrameFeature(organization);
-  const showStacktraceLinkInFrame = hasStacktraceLink && hasInFrameFeature;
-  const showSentryAppStacktraceLinkInFrame =
-    showStacktraceLinkInFrame && components.length > 0;
+  const showSentryAppStacktraceLinkInFrame = hasStacktraceLink && components.length > 0;
 
   const handleMouseEnter = () => setHovering(true);
 
@@ -381,7 +374,7 @@ function NativeFrame({
             </ShowHideButton>
           ) : null}
           <GenericCellWrapper>
-            {showStacktraceLinkInFrame && (
+            {hasStacktraceLink && (
               <ErrorBoundary>
                 <StacktraceLink
                   frame={frame}