Browse Source

fix(autofix): Add error message when codebase indexing fails (#71263)

Malachi Willey 9 months ago
parent
commit
6ca80d0d8b

+ 79 - 48
static/app/components/events/autofix/autofixBanner.tsx

@@ -25,13 +25,10 @@ type Props = {
   triggerAutofix: (value: string) => void;
 };
 
-export function AutofixBanner({
+function SuccessfulSetup({
   groupId,
-  projectId,
   triggerAutofix,
-  hasSuccessfulSetup,
-}: Props) {
-  const isSentryEmployee = useIsSentryEmployee();
+}: Pick<Props, 'groupId' | 'triggerAutofix'>) {
   const onClickGiveInstructions = () => {
     openModal(deps => (
       <AutofixInstructionsModal
@@ -41,8 +38,79 @@ export function AutofixBanner({
       />
     ));
   };
+
+  return (
+    <Fragment>
+      <Button
+        onClick={() => triggerAutofix('')}
+        size="sm"
+        analyticsEventKey="autofix.start_fix_clicked"
+        analyticsEventName="Autofix: Start Fix Clicked"
+        analyticsParams={{group_id: groupId}}
+      >
+        {t('Gimme Fix')}
+      </Button>
+      <Button
+        onClick={onClickGiveInstructions}
+        size="sm"
+        analyticsEventKey="autofix.give_instructions_clicked"
+        analyticsEventName="Autofix: Give Instructions Clicked"
+        analyticsParams={{group_id: groupId}}
+      >
+        {t('Give Instructions')}
+      </Button>
+    </Fragment>
+  );
+}
+
+function AutofixBannerContent({
+  groupId,
+  triggerAutofix,
+  hasSuccessfulSetup,
+  projectId,
+}: Props) {
   const {status: indexingStatus} = useAutofixCodebaseIndexing({projectId, groupId});
 
+  if (hasSuccessfulSetup) {
+    return <SuccessfulSetup groupId={groupId} triggerAutofix={triggerAutofix} />;
+  }
+
+  if (indexingStatus === AutofixCodebaseIndexingStatus.INDEXING) {
+    return (
+      <RowStack>
+        <LoadingIndicator mini />
+        <LoadingMessage>
+          Indexing your repositories, hold tight this may take up to 30 minutes...
+        </LoadingMessage>
+      </RowStack>
+    );
+  }
+
+  return (
+    <Button
+      analyticsEventKey="autofix.setup_clicked"
+      analyticsEventName="Autofix: Setup Clicked"
+      analyticsParams={{group_id: groupId}}
+      onClick={() => {
+        openModal(deps => (
+          <AutofixSetupModal {...deps} groupId={groupId} projectId={projectId} />
+        ));
+      }}
+      size="sm"
+    >
+      Setup Autofix
+    </Button>
+  );
+}
+
+export function AutofixBanner({
+  groupId,
+  projectId,
+  triggerAutofix,
+  hasSuccessfulSetup,
+}: Props) {
+  const isSentryEmployee = useIsSentryEmployee();
+
   return (
     <Wrapper>
       <IllustrationContainer>
@@ -56,49 +124,12 @@ export function AutofixBanner({
           <SubTitle>{t('You might get lucky, but then again, maybe not...')}</SubTitle>
         </div>
         <ContextArea>
-          {hasSuccessfulSetup ? (
-            <Fragment>
-              <Button
-                onClick={() => triggerAutofix('')}
-                size="sm"
-                analyticsEventKey="autofix.start_fix_clicked"
-                analyticsEventName="Autofix: Start Fix Clicked"
-                analyticsParams={{group_id: groupId}}
-              >
-                {t('Gimme Fix')}
-              </Button>
-              <Button
-                onClick={onClickGiveInstructions}
-                size="sm"
-                analyticsEventKey="autofix.give_instructions_clicked"
-                analyticsEventName="Autofix: Give Instructions Clicked"
-                analyticsParams={{group_id: groupId}}
-              >
-                {t('Give Instructions')}
-              </Button>
-            </Fragment>
-          ) : indexingStatus === AutofixCodebaseIndexingStatus.INDEXING ? (
-            <RowStack>
-              <LoadingIndicator mini />
-              <LoadingMessage>
-                Indexing your repositories, hold tight this may take up to 30 minutes...
-              </LoadingMessage>
-            </RowStack>
-          ) : (
-            <Button
-              analyticsEventKey="autofix.setup_clicked"
-              analyticsEventName="Autofix: Setup Clicked"
-              analyticsParams={{group_id: groupId}}
-              onClick={() => {
-                openModal(deps => (
-                  <AutofixSetupModal {...deps} groupId={groupId} projectId={projectId} />
-                ));
-              }}
-              size="sm"
-            >
-              Setup Autofix
-            </Button>
-          )}
+          <AutofixBannerContent
+            groupId={groupId}
+            projectId={projectId}
+            triggerAutofix={triggerAutofix}
+            hasSuccessfulSetup={hasSuccessfulSetup}
+          />
         </ContextArea>
         {isSentryEmployee && hasSuccessfulSetup && (
           <Fragment>

+ 2 - 0
static/app/components/events/autofix/types.ts

@@ -23,6 +23,8 @@ export enum AutofixCodebaseIndexingStatus {
   UP_TO_DATE = 'up_to_date',
   INDEXING = 'indexing',
   NOT_INDEXED = 'not_indexed',
+  OUT_OF_DATE = 'out_of_date',
+  ERRORED = 'errored',
 }
 
 export type AutofixPullRequestDetails = {

+ 29 - 8
static/app/components/events/autofix/useAutofixCodebaseIndexing.tsx

@@ -1,13 +1,16 @@
 import {useCallback, useEffect} from 'react';
 
+import {addErrorMessage} from 'sentry/actionCreators/indicator';
 import {AutofixCodebaseIndexingStatus} from 'sentry/components/events/autofix/types';
 import {makeAutofixSetupQueryKey} from 'sentry/components/events/autofix/useAutofixSetup';
+import {t} from 'sentry/locale';
 import {
   type ApiQueryKey,
   setApiQueryData,
   useApiQuery,
   useQueryClient,
 } from 'sentry/utils/queryClient';
+import RequestError from 'sentry/utils/requestError/requestError';
 import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
 import usePrevious from 'sentry/utils/usePrevious';
@@ -30,6 +33,7 @@ const isPolling = (status: AutofixCodebaseIndexingStatus | undefined) =>
 
 export interface CodebaseIndexingStatusResponse {
   status: AutofixCodebaseIndexingStatus;
+  reason?: string;
 }
 
 export function useAutofixCodebaseIndexing({
@@ -60,19 +64,14 @@ export function useAutofixCodebaseIndexing({
   );
 
   const status = apiData?.status ?? null;
+  const reason = apiData?.reason ?? null;
   const prevStatus = usePrevious(status);
 
-  const startIndexing = useCallback(() => {
+  const startIndexing = useCallback(async () => {
     if (!projectSlug) {
       return;
     }
 
-    // Triggering the create endpoint on the seer side should make it start return indexing status...
-    api.requestPromise(makeCodebaseIndexCreateUrl(organization.slug, projectSlug), {
-      method: 'POST',
-    });
-
-    // We set it anyways to trigger the polling
     setApiQueryData<CodebaseIndexingStatusResponse>(
       queryClient,
       makeCodebaseIndexStatusQueryKey(organization.slug, projectSlug),
@@ -80,6 +79,28 @@ export function useAutofixCodebaseIndexing({
         status: AutofixCodebaseIndexingStatus.INDEXING,
       }
     );
+
+    // Triggering the create endpoint on the seer side should make it start return indexing status...
+    try {
+      await api.requestPromise(
+        makeCodebaseIndexCreateUrl(organization.slug, projectSlug),
+        {
+          method: 'POST',
+        }
+      );
+    } catch (e) {
+      const detail = e instanceof RequestError ? e.message : undefined;
+
+      addErrorMessage(detail ?? t('Autofix was unable to start indexing the codebase.'));
+
+      setApiQueryData<CodebaseIndexingStatusResponse>(
+        queryClient,
+        makeCodebaseIndexStatusQueryKey(organization.slug, projectSlug),
+        {
+          status: AutofixCodebaseIndexingStatus.ERRORED,
+        }
+      );
+    }
   }, [api, queryClient, organization.slug, projectSlug]);
 
   useEffect(() => {
@@ -91,5 +112,5 @@ export function useAutofixCodebaseIndexing({
     }
   }, [queryClient, groupId, prevStatus, status]);
 
-  return {status, startIndexing};
+  return {status, reason, startIndexing};
 }

+ 73 - 0
static/app/components/modals/autofixSetupModal.spec.tsx

@@ -1,9 +1,25 @@
+import {ProjectFixture} from 'sentry-fixture/project';
+
 import {act, renderGlobalModal, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 import {openModal} from 'sentry/actionCreators/modal';
+import {AutofixCodebaseIndexingStatus} from 'sentry/components/events/autofix/types';
 import {AutofixSetupModal} from 'sentry/components/modals/autofixSetupModal';
+import ProjectsStore from 'sentry/stores/projectsStore';
 
 describe('AutofixSetupModal', function () {
+  beforeEach(() => {
+    MockApiClient.clearMockResponses();
+    ProjectsStore.loadInitialData([ProjectFixture({id: '1'})]);
+
+    MockApiClient.addMockResponse({
+      url: '/projects/org-slug/project-slug/autofix/codebase-index/status/',
+      body: {
+        status: AutofixCodebaseIndexingStatus.NOT_INDEXED,
+      },
+    });
+  });
+
   it('renders the integration setup instructions', async function () {
     MockApiClient.addMockResponse({
       url: '/issues/1/autofix/setup/',
@@ -240,6 +256,63 @@ describe('AutofixSetupModal', function () {
     ).toBeInTheDocument();
   });
 
+  it('displays indexing error when one exists', async function () {
+    MockApiClient.addMockResponse({
+      url: '/issues/1/autofix/setup/',
+      body: {
+        genAIConsent: {ok: false},
+        integration: {ok: true},
+        githubWriteIntegration: {
+          ok: true,
+          repos: [
+            {
+              provider: 'integrations:github',
+              owner: 'getsentry',
+              name: 'sentry',
+              external_id: '123',
+              ok: true,
+            },
+            {
+              provider: 'integrations:github',
+              owner: 'getsentry',
+              name: 'seer',
+              external_id: '235',
+              ok: true,
+            },
+          ],
+        },
+        codebaseIndexing: {ok: false},
+      },
+    });
+
+    MockApiClient.addMockResponse({
+      url: '/projects/org-slug/project-slug/autofix/codebase-index/status/',
+      body: {
+        status: AutofixCodebaseIndexingStatus.ERRORED,
+        reason: 'Some error',
+      },
+    });
+
+    const closeModal = jest.fn();
+
+    renderGlobalModal();
+
+    act(() => {
+      openModal(
+        modalProps => <AutofixSetupModal {...modalProps} groupId="1" projectId="1" />,
+        {
+          onClose: closeModal,
+        }
+      );
+    });
+
+    await screen.findByText('Enable Autofix');
+
+    expect(
+      await screen.findByText(/Failed to index repositories: Some error/i)
+    ).toBeInTheDocument();
+  });
+
   it('shows success text when steps are done', async function () {
     MockApiClient.addMockResponse({
       url: '/issues/1/autofix/setup/',

+ 8 - 1
static/app/components/modals/autofixSetupModal.tsx

@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
 
 import type {ModalRenderProps} from 'sentry/actionCreators/modal';
 import {Button} from 'sentry/components/button';
+import {AutofixCodebaseIndexingStatus} from 'sentry/components/events/autofix/types';
 import {useAutofixCodebaseIndexing} from 'sentry/components/events/autofix/useAutofixCodebaseIndexing';
 import {
   type AutofixSetupRepoDefinition,
@@ -214,7 +215,10 @@ function AutofixCodebaseIndexingStep({
   groupId: string;
   projectId: string;
 }) {
-  const {startIndexing} = useAutofixCodebaseIndexing({projectId, groupId});
+  const {startIndexing, status, reason} = useAutofixCodebaseIndexing({
+    projectId,
+    groupId,
+  });
 
   const canIndex =
     autofixSetup.genAIConsent.ok &&
@@ -228,6 +232,9 @@ function AutofixCodebaseIndexingStep({
           'Sentry will index your repositories to enable Autofix. This process may take a few minutes.'
         )}
       </p>
+      {status === AutofixCodebaseIndexingStatus.ERRORED && reason ? (
+        <LoadingError message={t('Failed to index repositories: %s', reason)} />
+      ) : null}
       <GuidedSteps.StepButtons>
         <Button
           priority="primary"