From 833d8ac2a3638f5ef8e22dba3a9e4896e581a9bb Mon Sep 17 00:00:00 2001 From: mdtro <20070360+mdtro@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:58:23 +0100 Subject: [PATCH 1/5] rebase master --- .../inviteMembersModalview.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx b/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx index b2350000a2e386..be753d082a2d5d 100644 --- a/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx +++ b/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx @@ -20,6 +20,7 @@ import {IconAdd} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Member} from 'sentry/types/organization'; +import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser'; interface Props { Footer: ModalRenderProps['Footer']; @@ -64,8 +65,6 @@ export default function InviteMembersModalView({ willInvite, error, }: Props) { - const disableInputs = sendingInvites || complete; - const inviteEmails = invites.map(inv => inv.email); const hasDuplicateEmails = inviteEmails.length !== new Set(inviteEmails).size; const isValidInvites = invites.length > 0 && !hasDuplicateEmails; @@ -76,18 +75,29 @@ export default function InviteMembersModalView({ ) : null; + const userEmails = member?.user?.emails; + const isVerified = userEmails ? userEmails.some(element => element.is_verified) : false; + + const isSuperUser = isActiveSuperuser(); + + const disableInputs = sendingInvites || complete || !isVerified || !isSuperUser; + return ( {errorAlert} {t('Invite New Members')} - {willInvite ? ( - {t('Invite new members by email to join your organization.')} - ) : ( + {!isVerified && !isSuperUser ? ( + + {t('Please verify your email before inviting other users.')} + + ) : !willInvite && isVerified && !isSuperUser ? ( {t( 'You can’t invite users directly, but we’ll forward your request to an org owner or manager for approval.' )} + ) : ( + {t('Invite new members by email to join your organization.')} )} {headerInfo} From 91e042668c239c410927e85141a77793e551a7b7 Mon Sep 17 00:00:00 2001 From: mdtro <20070360+mdtro@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:31:52 +0100 Subject: [PATCH 2/5] fix: add emails to stub --- fixtures/js-stubs/user.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fixtures/js-stubs/user.tsx b/fixtures/js-stubs/user.tsx index c33f5f6359ed65..643a360463d844 100644 --- a/fixtures/js-stubs/user.tsx +++ b/fixtures/js-stubs/user.tsx @@ -22,7 +22,13 @@ export function UserFixture(params: Partial = {}): User { authenticators: [], canReset2fa: false, dateJoined: '2020-01-01T00:00:00.000Z', - emails: [], + emails: [ + { + "id": '1', + "email": 'foo@example.com', + "is_verified": true, + }, + ], experiments: [], has2fa: false, identities: [], From f8d0dba87b1a244fca8b4a158659d18d5fa95cd3 Mon Sep 17 00:00:00 2001 From: mdtro <20070360+mdtro@users.noreply.github.com> Date: Mon, 29 Apr 2024 18:33:19 -0500 Subject: [PATCH 3/5] fix tests --- .../modals/inviteMembersModal/index.spec.tsx | 41 +++++++++++++++---- .../inviteMembersModalview.tsx | 2 +- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/static/app/components/modals/inviteMembersModal/index.spec.tsx b/static/app/components/modals/inviteMembersModal/index.spec.tsx index f82ae6a6b4af47..c2bf4f16849ca9 100644 --- a/static/app/components/modals/inviteMembersModal/index.spec.tsx +++ b/static/app/components/modals/inviteMembersModal/index.spec.tsx @@ -12,9 +12,13 @@ import InviteMembersModal from 'sentry/components/modals/inviteMembersModal'; import {ORG_ROLES} from 'sentry/constants'; import TeamStore from 'sentry/stores/teamStore'; import type {DetailedTeam, Scope} from 'sentry/types'; +import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser'; import useOrganization from 'sentry/utils/useOrganization'; jest.mock('sentry/utils/useOrganization'); +jest.mock('sentry/utils/isActiveSuperuser', () => ({ + isActiveSuperuser: jest.fn(), +})); describe('InviteMembersModal', function () { const styledWrapper = styled(c => c.children); @@ -22,13 +26,32 @@ describe('InviteMembersModal', function () { type MockApiResponseFn = ( client: typeof MockApiClient, orgSlug: string, + is_superuser: boolean, + verified_email: boolean, roles?: object[] ) => jest.Mock; - const defaultMockOrganizationRoles: MockApiResponseFn = (client, orgSlug, roles) => { + const defaultMockOrganizationRoles: MockApiResponseFn = ( + client, + orgSlug, + is_superuser, + verified_email, + roles + ) => { return client.addMockResponse({ url: `/organizations/${orgSlug}/members/me/`, method: 'GET', - body: {orgRoleList: roles}, + body: { + user: { + isSuperuser: is_superuser, + emails: [ + { + email: 'test@dev.getsentry.net', + is_verified: verified_email, + }, + ], + }, + orgRoleList: roles, + }, }); }; @@ -50,6 +73,8 @@ describe('InviteMembersModal', function () { const setupView = ({ orgTeams = [TeamFixture()], orgAccess = ['member:write'], + is_superuser = false, + verified_email = false, roles = [ { id: 'admin', @@ -69,11 +94,13 @@ describe('InviteMembersModal', function () { modalProps = defaultMockModalProps, mockApiResponses = [defaultMockOrganizationRoles], }: { + is_superuser?: boolean; mockApiResponses?: MockApiResponseFn[]; modalProps?: ComponentProps; orgAccess?: Scope[]; orgTeams?: DetailedTeam[]; roles?: object[]; + verified_email?: boolean; } = {}) => { const org = OrganizationFixture({access: orgAccess, teams: orgTeams}); TeamStore.reset(); @@ -82,7 +109,9 @@ describe('InviteMembersModal', function () { MockApiClient.clearMockResponses(); const mocks: jest.Mock[] = []; mockApiResponses.forEach(mockApiResponse => { - mocks.push(mockApiResponse(MockApiClient, org.slug, roles)); + mocks.push( + mockApiResponse(MockApiClient, org.slug, is_superuser, verified_email, roles) + ); }); jest.mocked(useOrganization).mockReturnValue(org); @@ -106,7 +135,7 @@ describe('InviteMembersModal', function () { }; it('renders', async function () { - setupView(); + setupView({verified_email: true}); await waitFor(() => { // Starts with one invite row expect(screen.getByRole('listitem')).toBeInTheDocument(); @@ -120,9 +149,7 @@ describe('InviteMembersModal', function () { }); it('renders for superuser', async function () { - jest.mock('sentry/utils/isActiveSuperuser', () => ({ - isActiveSuperuser: jest.fn(), - })); + jest.mocked(isActiveSuperuser).mockReturnValue(true); const errorResponse: MockApiResponseFn = (client, orgSlug, _) => { return client.addMockResponse({ diff --git a/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx b/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx index be753d082a2d5d..64eff970a52b13 100644 --- a/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx +++ b/static/app/components/modals/inviteMembersModal/inviteMembersModalview.tsx @@ -80,7 +80,7 @@ export default function InviteMembersModalView({ const isSuperUser = isActiveSuperuser(); - const disableInputs = sendingInvites || complete || !isVerified || !isSuperUser; + const disableInputs = sendingInvites || complete || (!isVerified && !isSuperUser); return ( From f6dd77f0c88d926b9e50edbba8a17b99a96a0310 Mon Sep 17 00:00:00 2001 From: mdtro <20070360+mdtro@users.noreply.github.com> Date: Mon, 29 Apr 2024 18:37:41 -0500 Subject: [PATCH 4/5] return superuser once --- static/app/components/modals/inviteMembersModal/index.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/components/modals/inviteMembersModal/index.spec.tsx b/static/app/components/modals/inviteMembersModal/index.spec.tsx index c2bf4f16849ca9..5a12d79f26a903 100644 --- a/static/app/components/modals/inviteMembersModal/index.spec.tsx +++ b/static/app/components/modals/inviteMembersModal/index.spec.tsx @@ -149,7 +149,7 @@ describe('InviteMembersModal', function () { }); it('renders for superuser', async function () { - jest.mocked(isActiveSuperuser).mockReturnValue(true); + jest.mocked(isActiveSuperuser).mockReturnValueOnce(true); const errorResponse: MockApiResponseFn = (client, orgSlug, _) => { return client.addMockResponse({ From bf4005ddb3f495e01aad0553219643a233d79660 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 23:39:55 +0000 Subject: [PATCH 5/5] :hammer_and_wrench: apply eslint style fixes --- fixtures/js-stubs/user.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fixtures/js-stubs/user.tsx b/fixtures/js-stubs/user.tsx index 643a360463d844..1fcede9a24b6a1 100644 --- a/fixtures/js-stubs/user.tsx +++ b/fixtures/js-stubs/user.tsx @@ -24,9 +24,9 @@ export function UserFixture(params: Partial = {}): User { dateJoined: '2020-01-01T00:00:00.000Z', emails: [ { - "id": '1', - "email": 'foo@example.com', - "is_verified": true, + id: '1', + email: 'foo@example.com', + is_verified: true, }, ], experiments: [],