Skip to content

Commit 9e9c917

Browse files
evanpurkhiserconstantinius
authored andcommitted
ref(ui): Replace DropdownAutoComplete use in team selector (#98067)
1 parent d9d805e commit 9e9c917

File tree

3 files changed

+63
-147
lines changed

3 files changed

+63
-147
lines changed

static/app/views/settings/components/teamSelect/teamSelectForProject.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ function TeamSelect({
9494
<DropdownAddTeam
9595
disabled={disabled}
9696
isLoadingTeams={isLoadingTeams}
97-
isAddingTeamToProject
9897
canCreateTeam={canCreateTeam}
9998
onSearch={onSearch}
10099
onSelect={onAddTeam}
Lines changed: 57 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
import {css} from '@emotion/react';
2-
import styled from '@emotion/styled';
3-
import debounce from 'lodash/debounce';
4-
51
import {openCreateTeamModal} from 'sentry/actionCreators/modal';
62
import {hasEveryAccess} from 'sentry/components/acl/access';
7-
import {Link} from 'sentry/components/core/link';
8-
import {Tooltip} from 'sentry/components/core/tooltip';
9-
import DropdownAutoComplete from 'sentry/components/dropdownAutoComplete';
10-
import type {Item, ItemsBeforeFilter} from 'sentry/components/dropdownAutoComplete/types';
11-
import DropdownButton from 'sentry/components/dropdownButton';
12-
import {TeamBadge} from 'sentry/components/idBadge/teamBadge';
13-
import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
3+
import {TeamAvatar} from 'sentry/components/core/avatar/teamAvatar';
4+
import {Button} from 'sentry/components/core/button';
5+
import {CompactSelect, type SelectOption} from 'sentry/components/core/compactSelect';
146
import {t} from 'sentry/locale';
15-
import {space} from 'sentry/styles/space';
167
import type {Organization, Team} from 'sentry/types/organization';
178
import type {Project} from 'sentry/types/project';
189
import {getButtonHelpText} from 'sentry/views/settings/organizationTeams/utils';
@@ -45,7 +36,6 @@ export function DropdownAddTeam({
4536
disabled,
4637
isLoadingTeams,
4738
isAddingTeamToMember = false,
48-
isAddingTeamToProject = false,
4939
onSearch,
5040
onSelect,
5141
onCreateTeam,
@@ -63,142 +53,69 @@ export function DropdownAddTeam({
6353
teams: Team[];
6454
canCreateTeam?: boolean;
6555
isAddingTeamToMember?: boolean;
66-
isAddingTeamToProject?: boolean;
6756
onCreateTeam?: (team: Team) => void;
6857
project?: Project;
6958
}) {
70-
const dropdownItems: ItemsBeforeFilter = teams
59+
const dropdownItems = teams
7160
.filter(team => !selectedTeams.includes(team.slug))
72-
.map((team, index) =>
73-
getDropdownOption({
74-
isAddingTeamToMember,
75-
isAddingTeamToProject,
76-
team,
77-
index,
78-
disabled,
79-
})
80-
);
81-
82-
const onDropdownChange = debounce<(e: React.ChangeEvent<HTMLInputElement>) => void>(
83-
e => onSearch(e.target.value),
84-
DEFAULT_DEBOUNCE_DURATION
85-
);
86-
87-
return (
88-
<DropdownAutoComplete
89-
items={dropdownItems}
90-
busyItemsStillVisible={isLoadingTeams}
91-
onChange={onDropdownChange}
92-
onSelect={(option: Item) => onSelect(option.value)}
93-
emptyMessage={t('No teams')}
94-
menuHeader={renderDropdownHeader({
95-
organization,
96-
project,
97-
onCreateTeam,
98-
})}
99-
disabled={disabled}
100-
alignMenu="right"
101-
>
102-
{({isOpen}) => (
103-
<DropdownButton isOpen={isOpen} size="xs" disabled={disabled}>
104-
{t('Add Team')}
105-
</DropdownButton>
106-
)}
107-
</DropdownAutoComplete>
108-
);
109-
}
61+
.map<SelectOption<string>>(team => {
62+
const isIdpProvisioned = isAddingTeamToMember && team.flags['idp:provisioned'];
11063

111-
function getDropdownOption({
112-
disabled,
113-
index,
114-
isAddingTeamToMember,
115-
team,
116-
}: {
117-
disabled: boolean;
118-
index: number;
119-
isAddingTeamToMember: boolean;
120-
isAddingTeamToProject: boolean;
121-
team: Team;
122-
}): ItemsBeforeFilter[number] {
123-
const isIdpProvisioned = isAddingTeamToMember && team.flags['idp:provisioned'];
124-
const label = isIdpProvisioned ? (
125-
<Tooltip title={getButtonHelpText(isIdpProvisioned)}>
126-
<DisabledTeam avatarSize={18} team={team} />
127-
</Tooltip>
128-
) : (
129-
<TeamBadge avatarSize={18} team={team} />
130-
);
131-
132-
return {
133-
index,
134-
value: team.slug,
135-
searchKey: team.slug,
136-
label,
137-
disabled: disabled || isIdpProvisioned,
138-
};
139-
}
64+
return {
65+
value: team.slug,
66+
textValue: team.slug,
67+
leadingItems: <TeamAvatar team={team} size={16} />,
68+
label: `#${team.slug}`,
69+
disabled: disabled || isIdpProvisioned,
70+
tooltip: getButtonHelpText(isIdpProvisioned),
71+
hideCheck: true,
72+
};
73+
});
14074

141-
function renderDropdownHeader({
142-
organization,
143-
project,
144-
onCreateTeam,
145-
}: {
146-
organization: Organization;
147-
onCreateTeam?: (team: any) => void;
148-
project?: Project;
149-
}) {
15075
const canCreateTeam = hasEveryAccess(['org:write'], {organization, project});
15176

152-
return (
153-
<StyledTeamsLabel>
154-
<span>{t('Teams')}</span>
77+
const createTeam = (
78+
<Button
79+
title={
80+
canCreateTeam ? undefined : t('You must be a Org Owner/Manager to create teams')
81+
}
82+
borderless
83+
priority="link"
84+
size="zero"
85+
disabled={!canCreateTeam}
86+
onClick={(e: React.MouseEvent) => {
87+
e.stopPropagation();
88+
e.preventDefault();
15589

156-
<Tooltip
157-
disabled={canCreateTeam}
158-
title={t('You must be a Org Owner/Manager to create teams')}
159-
position="top"
160-
>
161-
<CreateTeamLink
162-
to="#create-team"
163-
disabled={!canCreateTeam}
164-
onClick={(e: React.MouseEvent) => {
165-
e.stopPropagation();
166-
e.preventDefault();
90+
openCreateTeamModal({
91+
organization,
92+
project,
93+
onClose: onCreateTeam,
94+
});
95+
}}
96+
>
97+
{t('Create Team')}
98+
</Button>
99+
);
167100

168-
openCreateTeamModal({
169-
organization,
170-
project,
171-
onClose: onCreateTeam,
172-
});
173-
}}
174-
>
175-
{t('Create Team')}
176-
</CreateTeamLink>
177-
</Tooltip>
178-
</StyledTeamsLabel>
101+
return (
102+
<CompactSelect
103+
size="xs"
104+
menuWidth={300}
105+
options={dropdownItems}
106+
value=""
107+
disabled={false}
108+
onClose={() => onSearch('')}
109+
onChange={selection => onSelect(selection.value)}
110+
menuTitle={t('Teams')}
111+
triggerLabel={t('Add Team')}
112+
searchPlaceholder={t('Search Teams')}
113+
emptyMessage={t('No Teams')}
114+
loading={isLoadingTeams}
115+
searchable
116+
disableSearchFilter
117+
onSearch={onSearch}
118+
menuHeaderTrailingItems={createTeam}
119+
/>
179120
);
180121
}
181-
182-
const DisabledTeam = styled(TeamBadge)`
183-
filter: grayscale(1);
184-
`;
185-
186-
const StyledTeamsLabel = styled('div')`
187-
display: flex;
188-
flex-direction: row;
189-
justify-content: space-between;
190-
font-size: 0.875em;
191-
padding: ${space(0.5)} 0px;
192-
text-transform: uppercase;
193-
`;
194-
195-
const CreateTeamLink = styled(Link)`
196-
float: right;
197-
${p =>
198-
p.disabled &&
199-
css`
200-
cursor: not-allowed;
201-
color: ${p.theme.disabled};
202-
opacity: 0.6;
203-
`};
204-
`;

static/app/views/settings/project/projectTeams.spec.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
screen,
88
userEvent,
99
waitFor,
10+
within,
1011
} from 'sentry-test/reactTestingLibrary';
1112

1213
import TeamStore from 'sentry/stores/teamStore';
@@ -246,8 +247,8 @@ describe('ProjectTeams', () => {
246247
expect(mock).not.toHaveBeenCalled();
247248

248249
// Add a team
249-
await userEvent.click(screen.getAllByRole('button', {name: 'Add Team'})[1]!);
250-
await userEvent.click(screen.getByText('#team-slug-2'));
250+
await userEvent.click(screen.getByRole('button', {name: 'Add Team'}));
251+
await userEvent.click(await screen.findByText('#team-slug-2'));
251252

252253
await waitFor(() => {
253254
expect(mock).toHaveBeenCalledWith(
@@ -285,14 +286,13 @@ describe('ProjectTeams', () => {
285286
// Add new team
286287
await userEvent.click(screen.getAllByRole('button', {name: 'Add Team'})[1]!);
287288

288-
// XXX(epurkhiser): Create Team should really be a button
289-
await userEvent.click(screen.getByRole('link', {name: 'Create Team'}));
289+
await userEvent.click(screen.getByRole('button', {name: 'Create Team'}));
290290

291291
renderGlobalModal();
292-
await screen.findByRole('dialog');
292+
const modal = await screen.findByRole('dialog');
293293

294294
await userEvent.type(screen.getByRole('textbox', {name: 'Team Name'}), 'new-team');
295-
await userEvent.click(screen.getByRole('button', {name: 'Create Team'}));
295+
await userEvent.click(within(modal).getByRole('button', {name: 'Create Team'}));
296296

297297
await waitFor(() => expect(createTeam).toHaveBeenCalledTimes(1));
298298

0 commit comments

Comments
 (0)