Browse Source

feat(roles): display role names in members list (#46063)

Cathy Teng 1 year ago
parent
commit
636c38729f

+ 1 - 0
fixtures/js-stubs/member.js

@@ -6,6 +6,7 @@ export function Member(params = {}) {
     email: 'sentry1@test.com',
     name: 'Sentry 1 Name',
     orgRole: 'member',
+    orgRolesFromTeams: [],
     teamRoles: [],
     role: 'member',
     roleName: 'Member',

+ 3 - 0
fixtures/js-stubs/members.js

@@ -8,6 +8,7 @@ export function Members(params = []) {
       name: 'Sentry 2 Name',
       email: 'sentry2@test.com',
       orgRole: 'member',
+      orgRolesFromTeams: [],
       teamRoles: [],
       role: 'member',
       roleName: 'Member',
@@ -24,6 +25,7 @@ export function Members(params = []) {
       name: 'Sentry 3 Name',
       email: 'sentry3@test.com',
       orgRole: 'owner',
+      orgRolesFromTeams: [],
       teamRoles: [],
       role: 'owner',
       roleName: 'Owner',
@@ -46,6 +48,7 @@ export function Members(params = []) {
       name: 'Sentry 4 Name',
       email: 'sentry4@test.com',
       orgRole: 'owner',
+      orgRolesFromTeams: [],
       teamRoles: [],
       role: 'owner',
       roleName: 'Owner',

+ 59 - 2
static/app/views/settings/organizationMembers/organizationMemberRow.spec.jsx

@@ -1,4 +1,4 @@
-import {render, screen} from 'sentry-test/reactTestingLibrary';
+import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
 
 import OrganizationMemberRow from 'sentry/views/settings/organizationMembers/organizationMemberRow';
 
@@ -7,7 +7,7 @@ describe('OrganizationMemberRow', function () {
     id: '1',
     email: '',
     name: '',
-    role: 'member',
+    orgRole: 'member',
     roleName: 'Member',
     pending: false,
     flags: {
@@ -18,8 +18,25 @@ describe('OrganizationMemberRow', function () {
       has2fa: false,
       name: 'sentry@test.com',
     },
+    orgRolesFromTeams: [],
   };
 
+  const managerTeam = TestStubs.Team({
+    orgRole: 'manager',
+  });
+
+  const memberOnManagerTeam = TestStubs.Member({
+    id: '2',
+    orgRole: 'member',
+    teams: [managerTeam.slug],
+    orgRolesFromTeams: [
+      {
+        teamSlug: managerTeam.slug,
+        role: {name: 'Manager'},
+      },
+    ],
+  });
+
   const currentUser = {
     id: '2',
     email: 'currentUser@email.com',
@@ -280,4 +297,44 @@ describe('OrganizationMemberRow', function () {
       expect(removeButton()).toBeEnabled();
     });
   });
+
+  describe('render org role', function () {
+    it('renders org role without tooltip if no org roles from team membership', function () {
+      render(
+        <OrganizationMemberRow
+          {...defaultProps}
+          member={{
+            ...member,
+            user: {...member.user},
+          }}
+        />
+      );
+
+      expect(screen.getByText('Member')).toBeInTheDocument();
+
+      const questionTooltip = screen.queryByTestId('more-information');
+      expect(questionTooltip).not.toBeInTheDocument();
+    });
+  });
+
+  it('renders org role info tooltip if member has org roles from team membership', async function () {
+    render(
+      <OrganizationMemberRow
+        {...defaultProps}
+        member={{
+          ...memberOnManagerTeam,
+          user: {...memberOnManagerTeam.user},
+        }}
+      />
+    );
+
+    const questionTooltip = screen.getByTestId('more-information');
+    expect(questionTooltip).toBeInTheDocument();
+
+    await userEvent.hover(questionTooltip);
+    await waitFor(() => {
+      expect(screen.getByText(`#${managerTeam.slug}`)).toBeInTheDocument();
+      expect(screen.getByText(': Manager')).toBeInTheDocument();
+    });
+  });
 });

+ 3 - 3
static/app/views/settings/organizationMembers/organizationMemberRow.tsx

@@ -7,6 +7,7 @@ import Confirm from 'sentry/components/confirm';
 import HookOrDefault from 'sentry/components/hookOrDefault';
 import Link from 'sentry/components/links/link';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
+import {OrgRoleInfo} from 'sentry/components/orgRole';
 import {PanelItem} from 'sentry/components/panels';
 import {IconCheckmark, IconClose, IconFlag, IconMail, IconSubtract} from 'sentry/icons';
 import {t, tct} from 'sentry/locale';
@@ -70,12 +71,11 @@ export default class OrganizationMemberRow extends PureComponent<Props, State> {
     if (typeof onSendInvite !== 'function') {
       return;
     }
-
     onSendInvite(member);
   };
 
   renderMemberRole() {
-    const {member} = this.props;
+    const {member, organization} = this.props;
     const {roleName, pending, expired} = member;
     if (isMemberDisabledFromLimit(member)) {
       return <DisabledMemberTooltip>{t('Deactivated')}</DisabledMemberTooltip>;
@@ -88,7 +88,7 @@ export default class OrganizationMemberRow extends PureComponent<Props, State> {
         </InvitedRole>
       );
     }
-    return roleName;
+    return <OrgRoleInfo member={member} organization={organization} />;
   }
 
   render() {

+ 49 - 2
static/app/views/settings/organizationMembers/organizationMembersList.spec.jsx

@@ -34,10 +34,39 @@ const roles = [
     desc: 'This is the member role',
     allowed: true,
   },
+  {
+    id: 'owner',
+    name: 'Owner',
+    desc: 'This is the owner role',
+    allowed: true,
+  },
 ];
 
 describe('OrganizationMembersList', function () {
   const members = TestStubs.Members();
+
+  const ownerTeam = TestStubs.Team({slug: 'owner-team', orgRole: 'owner'});
+  const member = TestStubs.Member({
+    id: '5',
+    email: 'member@sentry.io',
+    teams: [ownerTeam.slug],
+    teamRoles: [
+      {
+        teamSlug: ownerTeam.slug,
+        role: null,
+      },
+    ],
+    flags: {
+      'sso:linked': true,
+    },
+    orgRolesFromTeams: [
+      {
+        teamSlug: ownerTeam.slug,
+        role: {id: 'owner'},
+      },
+    ],
+  });
+
   const currentUser = members[1];
   const organization = TestStubs.Organization({
     access: ['member:admin', 'org:admin', 'member:write'],
@@ -67,7 +96,11 @@ describe('OrganizationMembersList', function () {
     Client.addMockResponse({
       url: '/organizations/org-slug/members/',
       method: 'GET',
-      body: TestStubs.Members(),
+      body: [...TestStubs.Members(), member],
+    });
+    MockApiClient.addMockResponse({
+      url: `/organizations/org-slug/members/${member.id}/`,
+      body: member,
     });
     Client.addMockResponse({
       url: '/organizations/org-slug/access-requests/',
@@ -101,7 +134,7 @@ describe('OrganizationMembersList', function () {
     Client.addMockResponse({
       url: '/organizations/org-slug/teams/',
       method: 'GET',
-      body: TestStubs.Team(),
+      body: [TestStubs.Team(), ownerTeam],
     });
     Client.addMockResponse({
       url: '/organizations/org-slug/invite-requests/',
@@ -357,6 +390,20 @@ describe('OrganizationMembersList', function () {
     }
   });
 
+  it('can filter members with org roles from team membership', async function () {
+    const routerContext = TestStubs.routerContext();
+    render(<OrganizationMembersList {...defaultProps} />, {
+      context: routerContext,
+    });
+
+    await userEvent.click(screen.getByRole('button', {name: 'Filter'}));
+    await userEvent.click(screen.getByRole('checkbox', {name: 'Owner'}));
+    await userEvent.click(screen.getByRole('button', {name: 'Filter'}));
+
+    const owners = screen.queryAllByText('Owner');
+    expect(owners).toHaveLength(3);
+  });
+
   describe('OrganizationInviteRequests', function () {
     const inviteRequest = TestStubs.Member({
       id: '123',