Browse Source

fix(dashboards): team rendering for Edit Access selector (#81059)

Problem:
The `useTeamsById()` hook only renders teams from the `TeamStore` which
causes the dashboard to error out when the Edit Access selector uses an
unavailable team.

Fix:
- Query the selected team by `teamId` to ensure it isn't undefined
- This PR also fixes the Search functionality by being able to search
for any team irrespective of `TeamStore`

---------

Co-authored-by: harshithadurai <harshi.durai@esentry.io>
Harshitha Durai 21 hours ago
parent
commit
ebae9665c7

+ 10 - 0
static/app/views/dashboards/editAccessSelector.spec.tsx

@@ -1,9 +1,11 @@
 import {DashboardFixture} from 'sentry-fixture/dashboard';
 import {DashboardFixture} from 'sentry-fixture/dashboard';
+import {OrganizationFixture} from 'sentry-fixture/organization';
 import {TeamFixture} from 'sentry-fixture/team';
 import {TeamFixture} from 'sentry-fixture/team';
 import {UserFixture} from 'sentry-fixture/user';
 import {UserFixture} from 'sentry-fixture/user';
 
 
 import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 
+import OrganizationStore from 'sentry/stores/organizationStore';
 import TeamStore from 'sentry/stores/teamStore';
 import TeamStore from 'sentry/stores/teamStore';
 import EditAccessSelector from 'sentry/views/dashboards/editAccessSelector';
 import EditAccessSelector from 'sentry/views/dashboards/editAccessSelector';
 
 
@@ -224,7 +226,15 @@ describe('When EditAccessSelector is rendered with Teams', function () {
   });
   });
 
 
   it('searches teams', async function () {
   it('searches teams', async function () {
+    const org = OrganizationFixture();
+    OrganizationStore.onUpdate(org, {replace: true});
+    MockApiClient.addMockResponse({
+      url: `/organizations/org-slug/teams/`,
+      method: 'GET',
+      body: teams,
+    });
     renderTestComponent();
     renderTestComponent();
+
     await userEvent.click(await screen.findByText('Edit Access:'));
     await userEvent.click(await screen.findByText('Edit Access:'));
     await userEvent.type(screen.getByPlaceholderText('Search Teams'), 'team2');
     await userEvent.type(screen.getByPlaceholderText('Search Teams'), 'team2');
 
 

+ 13 - 4
static/app/views/dashboards/editAccessSelector.tsx

@@ -1,5 +1,6 @@
 import {useState} from 'react';
 import {useState} from 'react';
 import styled from '@emotion/styled';
 import styled from '@emotion/styled';
+import debounce from 'lodash/debounce';
 import isEqual from 'lodash/isEqual';
 import isEqual from 'lodash/isEqual';
 
 
 import AvatarList from 'sentry/components/avatar/avatarList';
 import AvatarList from 'sentry/components/avatar/avatarList';
@@ -13,11 +14,13 @@ import {CheckWrap} from 'sentry/components/compactSelect/styles';
 import UserBadge from 'sentry/components/idBadge/userBadge';
 import UserBadge from 'sentry/components/idBadge/userBadge';
 import {InnerWrap, LeadingItems} from 'sentry/components/menuListItem';
 import {InnerWrap, LeadingItems} from 'sentry/components/menuListItem';
 import {Tooltip} from 'sentry/components/tooltip';
 import {Tooltip} from 'sentry/components/tooltip';
+import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
 import {t, tct} from 'sentry/locale';
 import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
 import type {Team} from 'sentry/types/organization';
 import type {Team} from 'sentry/types/organization';
 import type {User} from 'sentry/types/user';
 import type {User} from 'sentry/types/user';
 import {defined} from 'sentry/utils';
 import {defined} from 'sentry/utils';
+import {useTeams} from 'sentry/utils/useTeams';
 import {useTeamsById} from 'sentry/utils/useTeamsById';
 import {useTeamsById} from 'sentry/utils/useTeamsById';
 import {useUser} from 'sentry/utils/useUser';
 import {useUser} from 'sentry/utils/useUser';
 import type {DashboardDetails, DashboardPermissions} from 'sentry/views/dashboards/types';
 import type {DashboardDetails, DashboardPermissions} from 'sentry/views/dashboards/types';
@@ -35,10 +38,15 @@ function EditAccessSelector({dashboard, onChangeEditAccess}: EditAccessSelectorP
   const currentUser: User = useUser();
   const currentUser: User = useUser();
   const dashboardCreator: User | undefined = dashboard.createdBy;
   const dashboardCreator: User | undefined = dashboard.createdBy;
   const isCurrentUserDashboardOwner = dashboardCreator?.id === currentUser.id;
   const isCurrentUserDashboardOwner = dashboardCreator?.id === currentUser.id;
-  const {teams} = useTeamsById();
-  const teamIds: string[] = Object.values(teams).map(team => team.id);
+
+  // Retrieves teams from the team store, which may contain only a subset of all teams
+  const {teams: teamsToRender} = useTeamsById();
+  const {onSearch} = useTeams();
+  const teamIds: string[] = Object.values(teamsToRender).map(team => team.id);
+
   const [selectedOptions, setSelectedOptions] = useState<string[]>(getSelectedOptions());
   const [selectedOptions, setSelectedOptions] = useState<string[]>(getSelectedOptions());
   const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
   const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
+  const {teams: selectedTeam} = useTeamsById({ids: [selectedOptions[1]]});
 
 
   // Handles state change when dropdown options are selected
   // Handles state change when dropdown options are selected
   const onSelectOptions = newSelectedOptions => {
   const onSelectOptions = newSelectedOptions => {
@@ -131,7 +139,7 @@ function EditAccessSelector({dashboard, onChangeEditAccess}: EditAccessSelectorP
         key="avatar-list-2-badges"
         key="avatar-list-2-badges"
         typeAvatars="users"
         typeAvatars="users"
         users={[dashboardCreator]}
         users={[dashboardCreator]}
-        teams={[teams.find(team => team.id === selectedOptions[1])!]}
+        teams={selectedTeam ? selectedTeam : []}
         maxVisibleAvatars={1}
         maxVisibleAvatars={1}
         avatarSize={25}
         avatarSize={25}
         renderUsersFirst
         renderUsersFirst
@@ -162,7 +170,7 @@ function EditAccessSelector({dashboard, onChangeEditAccess}: EditAccessSelectorP
     {
     {
       value: '_teams',
       value: '_teams',
       label: t('Teams'),
       label: t('Teams'),
-      options: teams.map(makeTeamOption),
+      options: teamsToRender.map(makeTeamOption),
       showToggleAllButton: isCurrentUserDashboardOwner,
       showToggleAllButton: isCurrentUserDashboardOwner,
       disabled: !isCurrentUserDashboardOwner,
       disabled: !isCurrentUserDashboardOwner,
     },
     },
@@ -236,6 +244,7 @@ function EditAccessSelector({dashboard, onChangeEditAccess}: EditAccessSelectorP
         }
         }
       }}
       }}
       menuFooter={dropdownFooterButtons}
       menuFooter={dropdownFooterButtons}
+      onSearch={debounce(val => void onSearch(val), DEFAULT_DEBOUNCE_DURATION)}
     />
     />
   );
   );