Browse Source

ref(github-growth): move slow API call into component that uses it (#59666)

Cathy Teng 1 year ago
parent
commit
427adb60df

+ 1 - 1
static/app/actionCreators/modal.tsx

@@ -250,7 +250,7 @@ export async function openInviteMembersModal({
 
 
 type InviteMissingMembersModalOptions = {
 type InviteMissingMembersModalOptions = {
   allowedRoles: OrgRole[];
   allowedRoles: OrgRole[];
-  missingMembers: {integration: string; users: MissingMember[]};
+  missingMembers: MissingMember[];
   onClose: () => void;
   onClose: () => void;
   organization: Organization;
   organization: Organization;
 };
 };

+ 3 - 3
static/app/components/modals/inviteMissingMembersModal/index.spec.tsx

@@ -31,7 +31,7 @@ describe('InviteMissingMembersModal', function () {
   const team = TestStubs.Team();
   const team = TestStubs.Team();
   const org = Organization({access: ['member:write'], teams: [team]});
   const org = Organization({access: ['member:write'], teams: [team]});
   TeamStore.loadInitialData([team]);
   TeamStore.loadInitialData([team]);
-  const missingMembers = {integration: 'github', users: MissingMembers()};
+  const missingMembers = MissingMembers();
 
 
   const styledWrapper = styled(c => c.children);
   const styledWrapper = styled(c => c.children);
   const modalProps: InviteMissingMembersModalProps = {
   const modalProps: InviteMissingMembersModalProps = {
@@ -41,7 +41,7 @@ describe('InviteMissingMembersModal', function () {
     closeModal: () => {},
     closeModal: () => {},
     CloseButton: makeCloseButton(() => {}),
     CloseButton: makeCloseButton(() => {}),
     organization: Organization(),
     organization: Organization(),
-    missingMembers: {integration: 'github', users: []},
+    missingMembers: [],
     allowedRoles: [],
     allowedRoles: [],
   };
   };
 
 
@@ -146,7 +146,7 @@ describe('InviteMissingMembersModal', function () {
     // Verify data sent to the backend
     // Verify data sent to the backend
     expect(createMemberMock).toHaveBeenCalledTimes(5);
     expect(createMemberMock).toHaveBeenCalledTimes(5);
 
 
-    missingMembers.users.forEach((member, i) => {
+    missingMembers.forEach((member, i) => {
       expect(createMemberMock).toHaveBeenNthCalledWith(
       expect(createMemberMock).toHaveBeenNthCalledWith(
         i + 1,
         i + 1,
         `/organizations/${org.slug}/members/?referrer=github_nudge_invite`,
         `/organizations/${org.slug}/members/?referrer=github_nudge_invite`,

+ 6 - 3
static/app/components/modals/inviteMissingMembersModal/index.tsx

@@ -29,7 +29,10 @@ import {StyledExternalLink} from 'sentry/views/settings/organizationMembers/invi
 
 
 export interface InviteMissingMembersModalProps extends ModalRenderProps {
 export interface InviteMissingMembersModalProps extends ModalRenderProps {
   allowedRoles: OrgRole[];
   allowedRoles: OrgRole[];
-  missingMembers: {integration: string; users: MissingMember[]};
+  // the API response returns {integration: "github", users: []}
+  // but we only ever return Github missing members at the moment
+  // so we can simplify the props and state to only store the users (missingMembers)
+  missingMembers: MissingMember[];
   organization: Organization;
   organization: Organization;
 }
 }
 
 
@@ -39,7 +42,7 @@ export function InviteMissingMembersModal({
   allowedRoles,
   allowedRoles,
   closeModal,
   closeModal,
 }: InviteMissingMembersModalProps) {
 }: InviteMissingMembersModalProps) {
-  const initialMemberInvites = (missingMembers.users || []).map(member => ({
+  const initialMemberInvites = (missingMembers || []).map(member => ({
     email: member.email,
     email: member.email,
     commitCount: member.commitCount,
     commitCount: member.commitCount,
     role: organization.defaultRole,
     role: organization.defaultRole,
@@ -49,7 +52,7 @@ export function InviteMissingMembersModal({
   }));
   }));
   const [memberInvites, setMemberInvites] =
   const [memberInvites, setMemberInvites] =
     useState<MissingMemberInvite[]>(initialMemberInvites);
     useState<MissingMemberInvite[]>(initialMemberInvites);
-  const referrer = missingMembers.integration + '_nudge_invite';
+  const referrer = 'github_nudge_invite';
   const [inviteStatus, setInviteStatus] = useState<InviteStatus>({});
   const [inviteStatus, setInviteStatus] = useState<InviteStatus>({});
   const [sendingInvites, setSendingInvites] = useState(false);
   const [sendingInvites, setSendingInvites] = useState(false);
   const [complete, setComplete] = useState(false);
   const [complete, setComplete] = useState(false);

+ 76 - 12
static/app/views/settings/organizationMembers/inviteBanner.spec.tsx

@@ -1,8 +1,9 @@
 import moment from 'moment';
 import moment from 'moment';
+import {Member} from 'sentry-fixture/member';
 import {MissingMembers} from 'sentry-fixture/missingMembers';
 import {MissingMembers} from 'sentry-fixture/missingMembers';
 import {Organization} from 'sentry-fixture/organization';
 import {Organization} from 'sentry-fixture/organization';
 
 
-import {render, screen} from 'sentry-test/reactTestingLibrary';
+import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 
 import {DEFAULT_SNOOZE_PROMPT_DAYS} from 'sentry/utils/promptIsDismissed';
 import {DEFAULT_SNOOZE_PROMPT_DAYS} from 'sentry/utils/promptIsDismissed';
 import {InviteBanner} from 'sentry/views/settings/organizationMembers/inviteBanner';
 import {InviteBanner} from 'sentry/views/settings/organizationMembers/inviteBanner';
@@ -23,7 +24,7 @@ describe('inviteBanner', function () {
     MockApiClient.addMockResponse({
     MockApiClient.addMockResponse({
       url: '/organizations/org-slug/missing-members/',
       url: '/organizations/org-slug/missing-members/',
       method: 'GET',
       method: 'GET',
-      body: [],
+      body: [missingMembers],
     });
     });
     MockApiClient.addMockResponse({
     MockApiClient.addMockResponse({
       url: '/prompts-activity/',
       url: '/prompts-activity/',
@@ -43,7 +44,6 @@ describe('inviteBanner', function () {
 
 
     render(
     render(
       <InviteBanner
       <InviteBanner
-        missingMembers={missingMembers}
         onSendInvite={() => {}}
         onSendInvite={() => {}}
         organization={org}
         organization={org}
         allowedRoles={[]}
         allowedRoles={[]}
@@ -67,7 +67,6 @@ describe('inviteBanner', function () {
 
 
     render(
     render(
       <InviteBanner
       <InviteBanner
-        missingMembers={missingMembers}
         onSendInvite={() => {}}
         onSendInvite={() => {}}
         organization={org}
         organization={org}
         allowedRoles={[]}
         allowedRoles={[]}
@@ -89,7 +88,6 @@ describe('inviteBanner', function () {
 
 
     render(
     render(
       <InviteBanner
       <InviteBanner
-        missingMembers={noMissingMembers}
         onSendInvite={() => {}}
         onSendInvite={() => {}}
         organization={org}
         organization={org}
         allowedRoles={[]}
         allowedRoles={[]}
@@ -110,9 +108,14 @@ describe('inviteBanner', function () {
       access: [],
       access: [],
     });
     });
 
 
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/missing-members/',
+      method: 'GET',
+      body: [noMissingMembers],
+    });
+
     render(
     render(
       <InviteBanner
       <InviteBanner
-        missingMembers={noMissingMembers}
         onSendInvite={() => {}}
         onSendInvite={() => {}}
         organization={org}
         organization={org}
         allowedRoles={[]}
         allowedRoles={[]}
@@ -147,7 +150,6 @@ describe('inviteBanner', function () {
 
 
     render(
     render(
       <InviteBanner
       <InviteBanner
-        missingMembers={missingMembers}
         onSendInvite={() => {}}
         onSendInvite={() => {}}
         organization={org}
         organization={org}
         allowedRoles={[]}
         allowedRoles={[]}
@@ -162,7 +164,7 @@ describe('inviteBanner', function () {
     ).toBeInTheDocument();
     ).toBeInTheDocument();
   });
   });
 
 
-  it('does not render banner if snoozed_ts days is shorter than threshold', function () {
+  it('does not render banner if snoozed_ts days is shorter than threshold', async function () {
     const org = Organization({
     const org = Organization({
       features: ['integrations-gh-invite'],
       features: ['integrations-gh-invite'],
       githubNudgeInvite: true,
       githubNudgeInvite: true,
@@ -180,9 +182,65 @@ describe('inviteBanner', function () {
       body: {data: promptResponse},
       body: {data: promptResponse},
     });
     });
 
 
+    await act(async () => {
+      await render(
+        <InviteBanner
+          onSendInvite={() => {}}
+          organization={org}
+          allowedRoles={[]}
+          onModalClose={() => {}}
+        />
+      );
+    });
+
+    expect(mockPrompt).toHaveBeenCalled();
+    expect(
+      screen.queryByRole('heading', {
+        name: 'Bring your full GitHub team on board in Sentry',
+      })
+    ).not.toBeInTheDocument();
+  });
+
+  it('invites member from banner', async function () {
+    const newMember = Member({
+      id: '6',
+      email: 'hello@sentry.io',
+      teams: [],
+      teamRoles: [],
+      flags: {
+        'idp:provisioned': false,
+        'idp:role-restricted': false,
+        'member-limit:restricted': false,
+        'partnership:restricted': false,
+        'sso:invalid': false,
+        'sso:linked': true,
+      },
+    });
+
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/missing-members/',
+      method: 'GET',
+      body: [
+        {
+          integration: 'github',
+          users: MissingMembers().slice(0, 5),
+        },
+      ],
+    });
+
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/members/?referrer=github_nudge_invite',
+      method: 'POST',
+      body: newMember,
+    });
+
+    const org = Organization({
+      features: ['integrations-gh-invite'],
+      githubNudgeInvite: true,
+    });
+
     render(
     render(
       <InviteBanner
       <InviteBanner
-        missingMembers={missingMembers}
         onSendInvite={() => {}}
         onSendInvite={() => {}}
         organization={org}
         organization={org}
         allowedRoles={[]}
         allowedRoles={[]}
@@ -190,11 +248,17 @@ describe('inviteBanner', function () {
       />
       />
     );
     );
 
 
-    expect(mockPrompt).toHaveBeenCalled();
     expect(
     expect(
-      screen.queryByRole('heading', {
+      await screen.findByRole('heading', {
         name: 'Bring your full GitHub team on board in Sentry',
         name: 'Bring your full GitHub team on board in Sentry',
       })
       })
-    ).not.toBeInTheDocument();
+    ).toBeInTheDocument();
+    expect(screen.queryAllByTestId('invite-missing-member')).toHaveLength(5);
+    expect(screen.getByText('See all 5 missing members')).toBeInTheDocument();
+
+    const inviteButton = screen.queryAllByTestId('invite-missing-member')[0];
+    await userEvent.click(inviteButton);
+    expect(screen.queryAllByTestId('invite-missing-member')).toHaveLength(4);
+    expect(screen.getByText('See all 4 missing members')).toBeInTheDocument();
   });
   });
 });
 });

+ 131 - 92
static/app/views/settings/organizationMembers/inviteBanner.tsx

@@ -1,7 +1,8 @@
-import {useCallback, useEffect, useState} from 'react';
+import {Fragment, useCallback, useEffect, useState} from 'react';
 import styled from '@emotion/styled';
 import styled from '@emotion/styled';
 import * as qs from 'query-string';
 import * as qs from 'query-string';
 
 
+import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
 import {openInviteMissingMembersModal} from 'sentry/actionCreators/modal';
 import {openInviteMissingMembersModal} from 'sentry/actionCreators/modal';
 import {promptsCheck, promptsUpdate} from 'sentry/actionCreators/prompts';
 import {promptsCheck, promptsUpdate} from 'sentry/actionCreators/prompts';
 import {Button} from 'sentry/components/button';
 import {Button} from 'sentry/components/button';
@@ -26,32 +27,30 @@ const MAX_MEMBERS_TO_SHOW = 5;
 
 
 type Props = {
 type Props = {
   allowedRoles: OrgRole[];
   allowedRoles: OrgRole[];
-  missingMembers: {integration: string; users: MissingMember[]};
   onModalClose: () => void;
   onModalClose: () => void;
-  onSendInvite: (email: string) => void;
+  onSendInvite: () => void;
   organization: Organization;
   organization: Organization;
 };
 };
 
 
 export function InviteBanner({
 export function InviteBanner({
-  missingMembers,
-  onSendInvite,
   organization,
   organization,
   allowedRoles,
   allowedRoles,
+  onSendInvite,
   onModalClose,
   onModalClose,
 }: Props) {
 }: Props) {
-  // NOTE: this is currently used for Github only
-
   const isEligibleForBanner =
   const isEligibleForBanner =
     organization.features.includes('integrations-gh-invite') &&
     organization.features.includes('integrations-gh-invite') &&
     organization.access.includes('org:write') &&
     organization.access.includes('org:write') &&
-    organization.githubNudgeInvite &&
-    missingMembers?.users?.length > 0;
+    organization.githubNudgeInvite;
   const [sendingInvite, setSendingInvite] = useState<boolean>(false);
   const [sendingInvite, setSendingInvite] = useState<boolean>(false);
   const [showBanner, setShowBanner] = useState<boolean>(false);
   const [showBanner, setShowBanner] = useState<boolean>(false);
+  const [missingMembers, setMissingMembers] = useState<MissingMember[]>(
+    [] as MissingMember[]
+  );
 
 
   const api = useApi();
   const api = useApi();
-  const integrationName = missingMembers?.integration;
-  const promptsFeature = `${integrationName}_missing_members`;
+  // NOTE: this is currently used for Github only
+  const promptsFeature = `github_missing_members`;
   const location = useLocation();
   const location = useLocation();
 
 
   const snoozePrompt = useCallback(async () => {
   const snoozePrompt = useCallback(async () => {
@@ -75,17 +74,37 @@ export function InviteBanner({
     });
     });
   }, [allowedRoles, missingMembers, organization, onModalClose]);
   }, [allowedRoles, missingMembers, organization, onModalClose]);
 
 
+  const fetchMissingMembers = useCallback(async () => {
+    try {
+      const data = await api.requestPromise(
+        `/organizations/${organization.slug}/missing-members/`,
+        {
+          method: 'GET',
+        }
+      );
+      const githubMissingMembers = data?.filter(
+        integrationMissingMembers => integrationMissingMembers.integration === 'github'
+      )[0];
+      setMissingMembers(githubMissingMembers?.users);
+    } catch (err) {
+      if (err.status !== 403) {
+        addErrorMessage(t('Unable to fetching missing commit authors'));
+      }
+    }
+  }, [api, organization]);
+
   useEffect(() => {
   useEffect(() => {
     if (!isEligibleForBanner) {
     if (!isEligibleForBanner) {
       return;
       return;
     }
     }
+    fetchMissingMembers();
     promptsCheck(api, {
     promptsCheck(api, {
       organizationId: organization.id,
       organizationId: organization.id,
       feature: promptsFeature,
       feature: promptsFeature,
     }).then(prompt => {
     }).then(prompt => {
       setShowBanner(!promptIsDismissed(prompt));
       setShowBanner(!promptIsDismissed(prompt));
     });
     });
-  }, [api, organization, promptsFeature, isEligibleForBanner]);
+  }, [api, organization, promptsFeature, isEligibleForBanner, fetchMissingMembers]);
 
 
   useEffect(() => {
   useEffect(() => {
     const {inviteMissingMembers} = qs.parse(location.search);
     const {inviteMissingMembers} = qs.parse(location.search);
@@ -95,13 +114,13 @@ export function InviteBanner({
     }
     }
   }, [openInviteModal, location, isEligibleForBanner]);
   }, [openInviteModal, location, isEligibleForBanner]);
 
 
-  if (isEligibleForBanner && showBanner) {
+  if (isEligibleForBanner && showBanner && missingMembers) {
     trackAnalytics('github_invite_banner.viewed', {
     trackAnalytics('github_invite_banner.viewed', {
       organization,
       organization,
-      members_shown: missingMembers.users.slice(0, MAX_MEMBERS_TO_SHOW).length,
+      members_shown: missingMembers.slice(0, MAX_MEMBERS_TO_SHOW).length,
     });
     });
   }
   }
-  if (!isEligibleForBanner || !showBanner) {
+  if (!isEligibleForBanner || !showBanner || missingMembers.length === 0) {
     return null;
     return null;
   }
   }
 
 
@@ -124,55 +143,26 @@ export function InviteBanner({
       return;
       return;
     }
     }
     setSendingInvite(true);
     setSendingInvite(true);
-    await onSendInvite(email);
+    try {
+      await api.requestPromise(
+        `/organizations/${organization.slug}/members/?referrer=github_nudge_invite`,
+        {
+          method: 'POST',
+          data: {email},
+        }
+      );
+      addSuccessMessage(tct('Sent invite to [email]', {email}));
+      onSendInvite();
+      const updatedMissingMembers = missingMembers.filter(
+        member => member.email !== email
+      );
+      setMissingMembers(updatedMissingMembers);
+    } catch {
+      addErrorMessage(t('Error sending invite'));
+    }
     setSendingInvite(false);
     setSendingInvite(false);
   };
   };
 
 
-  const users = missingMembers.users;
-
-  const cards = users.slice(0, MAX_MEMBERS_TO_SHOW).map(member => {
-    const username = member.externalId.split(':').pop();
-    return (
-      <MemberCard
-        key={member.externalId}
-        data-test-id={`member-card-${member.externalId}`}
-      >
-        <MemberCardContent>
-          <MemberCardContentRow>
-            <IconGithub size="sm" />
-            {/* TODO(cathy): create mapping from integration to lambda external link function */}
-            <StyledExternalLink href={`https://github.com/${username}`}>
-              @{username}
-            </StyledExternalLink>
-          </MemberCardContentRow>
-          <MemberCardContentRow>
-            <IconCommit size="xs" />
-            {tct('[commitCount] Recent Commits', {commitCount: member.commitCount})}
-          </MemberCardContentRow>
-          <MemberEmail>{member.email}</MemberEmail>
-        </MemberCardContent>
-        <Button
-          size="sm"
-          onClick={() => handleSendInvite(member.email)}
-          data-test-id="invite-missing-member"
-          icon={<IconMail />}
-          analyticsEventName="Github Invite Banner: Invite"
-          analyticsEventKey="github_invite_banner.invite"
-        >
-          {t('Invite')}
-        </Button>
-      </MemberCard>
-    );
-  });
-
-  cards.push(
-    <SeeMoreCard
-      key="see-more"
-      missingMembers={missingMembers}
-      openInviteModal={openInviteModal}
-    />
-  );
-
   return (
   return (
     <StyledCard>
     <StyledCard>
       <CardTitleContainer>
       <CardTitleContainer>
@@ -180,7 +170,7 @@ export function InviteBanner({
           <CardTitle>{t('Bring your full GitHub team on board in Sentry')}</CardTitle>
           <CardTitle>{t('Bring your full GitHub team on board in Sentry')}</CardTitle>
           <Subtitle>
           <Subtitle>
             {tct('[missingMemberCount] missing members', {
             {tct('[missingMemberCount] missing members', {
-              missingMemberCount: users.length,
+              missingMemberCount: missingMembers.length,
             })}
             })}
             <QuestionTooltip
             <QuestionTooltip
               title={t(
               title={t(
@@ -211,47 +201,96 @@ export function InviteBanner({
           />
           />
         </ButtonBar>
         </ButtonBar>
       </CardTitleContainer>
       </CardTitleContainer>
-      <Carousel>{cards}</Carousel>
+      <Carousel>
+        <MemberCards
+          missingMembers={missingMembers}
+          handleSendInvite={handleSendInvite}
+          openInviteModal={openInviteModal}
+        />
+      </Carousel>
     </StyledCard>
     </StyledCard>
   );
   );
 }
 }
 
 
 export default withOrganization(InviteBanner);
 export default withOrganization(InviteBanner);
 
 
-type SeeMoreCardProps = {
-  missingMembers: {integration: string; users: MissingMember[]};
+type MemberCardsProps = {
+  handleSendInvite: (email: string) => void;
+  missingMembers: MissingMember[];
   openInviteModal: () => void;
   openInviteModal: () => void;
 };
 };
 
 
-function SeeMoreCard({missingMembers, openInviteModal}: SeeMoreCardProps) {
-  const {users} = missingMembers;
-
+function MemberCards({
+  missingMembers,
+  handleSendInvite,
+  openInviteModal,
+}: MemberCardsProps) {
   return (
   return (
-    <MemberCard data-test-id="see-more-card">
-      <MemberCardContent>
-        <MemberCardContentRow>
-          <SeeMore>
-            {tct('See all [missingMembersCount] missing members', {
-              missingMembersCount: users.length,
+    <Fragment>
+      {missingMembers.slice(0, MAX_MEMBERS_TO_SHOW).map(member => {
+        const username = member.externalId.split(':').pop();
+        return (
+          <MemberCard
+            key={member.externalId}
+            data-test-id={`member-card-${member.externalId}`}
+          >
+            <MemberCardContent>
+              <MemberCardContentRow>
+                <IconGithub size="sm" />
+                {/* TODO(cathy): create mapping from integration to lambda external link function */}
+                <StyledExternalLink href={`https://github.com/${username}`}>
+                  @{username}
+                </StyledExternalLink>
+              </MemberCardContentRow>
+              <MemberCardContentRow>
+                <IconCommit size="xs" />
+                {tct('[commitCount] Recent Commits', {commitCount: member.commitCount})}
+              </MemberCardContentRow>
+              <MemberEmail>{member.email}</MemberEmail>
+            </MemberCardContent>
+            <Button
+              size="sm"
+              onClick={() => handleSendInvite(member.email)}
+              data-test-id="invite-missing-member"
+              icon={<IconMail />}
+              analyticsEventName="Github Invite Banner: Invite"
+              analyticsEventKey="github_invite_banner.invite"
+            >
+              {t('Invite')}
+            </Button>
+          </MemberCard>
+        );
+      })}
+
+      <MemberCard data-test-id="see-more-card" key="see-more">
+        <MemberCardContent>
+          <MemberCardContentRow>
+            <SeeMore>
+              {tct('See all [missingMembersCount] missing members', {
+                missingMembersCount: missingMembers.length,
+              })}
+            </SeeMore>
+          </MemberCardContentRow>
+          <Subtitle>
+            {tct('Accounting for [totalCommits] recent commits', {
+              totalCommits: missingMembers.reduce(
+                (acc, curr) => acc + curr.commitCount,
+                0
+              ),
             })}
             })}
-          </SeeMore>
-        </MemberCardContentRow>
-        <Subtitle>
-          {tct('Accounting for [totalCommits] recent commits', {
-            totalCommits: users.reduce((acc, curr) => acc + curr.commitCount, 0),
-          })}
-        </Subtitle>
-      </MemberCardContent>
-      <Button
-        size="sm"
-        priority="primary"
-        onClick={openInviteModal}
-        analyticsEventName="Github Invite Banner: View All"
-        analyticsEventKey="github_invite_banner.view_all"
-      >
-        {t('View All')}
-      </Button>
-    </MemberCard>
+          </Subtitle>
+        </MemberCardContent>
+        <Button
+          size="sm"
+          priority="primary"
+          onClick={openInviteModal}
+          analyticsEventName="Github Invite Banner: View All"
+          analyticsEventKey="github_invite_banner.view_all"
+        >
+          {t('View All')}
+        </Button>
+      </MemberCard>
+    </Fragment>
   );
   );
 }
 }
 
 

+ 0 - 51
static/app/views/settings/organizationMembers/organizationMembersList.spec.tsx

@@ -595,55 +595,4 @@ describe('OrganizationMembersList', function () {
       );
       );
     });
     });
   });
   });
-
-  // TODO(cathy): uncomment
-
-  // describe('inviteBanner', function () {
-  //   it('invites member from banner', async function () {
-  //     MockApiClient.addMockResponse({
-  //       url: '/organizations/org-slug/missing-members/',
-  //       method: 'GET',
-  //       body: missingMembers,
-  //     });
-
-  //     const newMember = TestStubs.Member({
-  //       id: '6',
-  //       email: 'hello@sentry.io',
-  //       teams: [],
-  //       teamRoles: [],
-  //       flags: {
-  //         'sso:linked': true,
-  //         'idp:provisioned': false,
-  //       },
-  //     });
-
-  //     MockApiClient.addMockResponse({
-  //       url: '/organizations/org-slug/members/?referrer=github_nudge_invite',
-  //       method: 'POST',
-  //       body: newMember,
-  //     });
-
-  //     const org = Organization({
-  //       features: ['integrations-gh-invite'],
-  //       githubNudgeInvite: true,
-  //     });
-
-  //     render(<OrganizationMembersList {...defaultProps} organization={org} />, {
-  //       context: TestStubs.routerContext([{organization: org}]),
-  //     });
-
-  //     expect(
-  //       await screen.findByRole('heading', {
-  //         name: 'Bring your full GitHub team on board in Sentry',
-  //       })
-  //     ).toBeInTheDocument();
-  //     expect(screen.queryAllByTestId('invite-missing-member')).toHaveLength(5);
-  //     expect(screen.getByText('See all 5 missing members')).toBeInTheDocument();
-
-  //     const inviteButton = screen.queryAllByTestId('invite-missing-member')[0];
-  //     await userEvent.click(inviteButton);
-  //     expect(screen.queryAllByTestId('invite-missing-member')).toHaveLength(4);
-  //     expect(screen.getByText('See all 4 missing members')).toBeInTheDocument();
-  //   });
-  // });
 });
 });

+ 2 - 37
static/app/views/settings/organizationMembers/organizationMembersList.tsx

@@ -173,30 +173,6 @@ class OrganizationMembersList extends DeprecatedAsyncView<Props, State> {
     this.setState(state => ({invited: {...state.invited, [id]: 'success'}}));
     this.setState(state => ({invited: {...state.invited, [id]: 'success'}}));
   };
   };
 
 
-  handleInviteMissingMember = async (email: string) => {
-    const {organization} = this.props;
-
-    try {
-      await this.api.requestPromise(
-        `/organizations/${organization.slug}/members/?referrer=github_nudge_invite`,
-        {
-          method: 'POST',
-          data: {email},
-        }
-      );
-      addSuccessMessage(tct('Sent invite to [email]', {email}));
-      this.fetchMembersList();
-      this.setState(state => ({
-        missingMembers: state.missingMembers.map(integrationMissingMembers => ({
-          ...integrationMissingMembers,
-          users: integrationMissingMembers.users.filter(member => member.email !== email),
-        })),
-      }));
-    } catch {
-      addErrorMessage(t('Error sending invite'));
-    }
-  };
-
   fetchMembersList = async () => {
   fetchMembersList = async () => {
     const {organization} = this.props;
     const {organization} = this.props;
 
 
@@ -300,13 +276,7 @@ class OrganizationMembersList extends DeprecatedAsyncView<Props, State> {
 
 
   renderBody() {
   renderBody() {
     const {organization} = this.props;
     const {organization} = this.props;
-    const {
-      membersPageLinks,
-      members,
-      member: currentMember,
-      inviteRequests,
-      missingMembers,
-    } = this.state;
+    const {membersPageLinks, members, member: currentMember, inviteRequests} = this.state;
     const {access} = organization;
     const {access} = organization;
 
 
     const canAddMembers = access.includes('member:write');
     const canAddMembers = access.includes('member:write');
@@ -334,15 +304,10 @@ class OrganizationMembersList extends DeprecatedAsyncView<Props, State> {
       </SearchWrapperWithFilter>
       </SearchWrapperWithFilter>
     );
     );
 
 
-    const githubMissingMembers = missingMembers?.filter(
-      integrationMissingMembers => integrationMissingMembers.integration === 'github'
-    )[0];
-
     return (
     return (
       <Fragment>
       <Fragment>
         <InviteBanner
         <InviteBanner
-          missingMembers={githubMissingMembers}
-          onSendInvite={this.handleInviteMissingMember}
+          onSendInvite={this.fetchMembersList}
           onModalClose={this.fetchData}
           onModalClose={this.fetchData}
           allowedRoles={currentMember ? currentMember.roles : ORG_ROLES}
           allowedRoles={currentMember ? currentMember.roles : ORG_ROLES}
         />
         />