Displayed error message for already used channel name (#24860)
* WIP * Displayed existing channel name error * refactoring
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
f18002a8d6
Коммит
06765b1f44
@@ -697,6 +697,9 @@ func (a *App) UpdateChannel(c request.CTX, channel *model.Channel) (*model.Chann
|
|||||||
var invErr *store.ErrInvalidInput
|
var invErr *store.ErrInvalidInput
|
||||||
switch {
|
switch {
|
||||||
case errors.As(err, &invErr):
|
case errors.As(err, &invErr):
|
||||||
|
if invErr.Entity == "Channel" && invErr.Field == "Name" {
|
||||||
|
return nil, model.NewAppError("UpdateChannel", store.ChannelExistsError, nil, "", http.StatusBadRequest).Wrap(err)
|
||||||
|
}
|
||||||
return nil, model.NewAppError("UpdateChannel", "app.channel.update.bad_id", nil, "", http.StatusBadRequest).Wrap(err)
|
return nil, model.NewAppError("UpdateChannel", "app.channel.update.bad_id", nil, "", http.StatusBadRequest).Wrap(err)
|
||||||
case errors.As(err, &appErr):
|
case errors.As(err, &appErr):
|
||||||
return nil, appErr
|
return nil, appErr
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type Props = {
|
|||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
onErrorStateChange?: (isError: boolean) => void;
|
onErrorStateChange?: (isError: boolean) => void;
|
||||||
team?: Team;
|
team?: Team;
|
||||||
|
urlError?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
import './channel_name_form_field.scss';
|
import './channel_name_form_field.scss';
|
||||||
@@ -139,7 +140,7 @@ const ChannelNameFormField = (props: Props): JSX.Element => {
|
|||||||
pathInfo={url}
|
pathInfo={url}
|
||||||
limit={Constants.MAX_CHANNELNAME_LENGTH}
|
limit={Constants.MAX_CHANNELNAME_LENGTH}
|
||||||
shortenLength={Constants.DEFAULT_CHANNELURL_SHORTEN_LENGTH}
|
shortenLength={Constants.DEFAULT_CHANNELURL_SHORTEN_LENGTH}
|
||||||
error={urlError}
|
error={urlError || props.urlError}
|
||||||
onChange={handleOnURLChange}
|
onChange={handleOnURLChange}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -175,4 +175,43 @@ describe('component/ConvertGmToChannelModal', () => {
|
|||||||
fireEvent.click(confirmButton!);
|
fireEvent.click(confirmButton!);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('duplicate channel names should npt be allowed', async () => {
|
||||||
|
TestHelper.initBasic(Client4);
|
||||||
|
|
||||||
|
nock(Client4.getBaseRoute()).
|
||||||
|
get('/channels/channel_id_1/common_teams').
|
||||||
|
reply(200, [
|
||||||
|
{id: 'team_id_1', display_name: 'Team 1', name: 'team_1'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
baseProps.actions.convertGroupMessageToPrivateChannel.mockResolvedValueOnce({
|
||||||
|
error: {
|
||||||
|
server_error_id: 'store.sql_channel.save_channel.exists.app_error',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithFullContext(
|
||||||
|
<ConvertGmToChannelModal {...baseProps}/>,
|
||||||
|
baseState,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(
|
||||||
|
() => expect(screen.queryByText('Conversation history will be visible to any channel members')).toBeInTheDocument(),
|
||||||
|
{timeout: 1500},
|
||||||
|
);
|
||||||
|
|
||||||
|
const channelNameInput = screen.queryByPlaceholderText('Channel name');
|
||||||
|
expect(channelNameInput).toBeInTheDocument();
|
||||||
|
fireEvent.change(channelNameInput!, {target: {value: 'Channel'}});
|
||||||
|
|
||||||
|
const confirmButton = screen.queryByText('Convert to private channel');
|
||||||
|
expect(channelNameInput).toBeInTheDocument();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(confirmButton!);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByText('A channel with that URL already exists')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ import TeamSelector from 'components/convert_gm_to_channel_modal/team_selector/t
|
|||||||
import WarningTextSection from 'components/convert_gm_to_channel_modal/warning_text_section/warning_text_section';
|
import WarningTextSection from 'components/convert_gm_to_channel_modal/warning_text_section/warning_text_section';
|
||||||
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||||
|
|
||||||
|
const enum ServerErrorId {
|
||||||
|
CHANNEL_NAME_EXISTS = 'store.sql_channel.save_channel.exists.app_error',
|
||||||
|
}
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
onExited: () => void;
|
onExited: () => void;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
@@ -43,8 +47,11 @@ const ConvertGmToChannelModal = (props: Props) => {
|
|||||||
|
|
||||||
const [channelName, setChannelName] = useState<string>('');
|
const [channelName, setChannelName] = useState<string>('');
|
||||||
const channelURL = useRef<string>('');
|
const channelURL = useRef<string>('');
|
||||||
|
|
||||||
|
const [urlError, setURLError] = useState('');
|
||||||
const handleChannelURLChange = useCallback((newURL: string) => {
|
const handleChannelURLChange = useCallback((newURL: string) => {
|
||||||
channelURL.current = newURL;
|
channelURL.current = newURL;
|
||||||
|
setURLError('');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [channelMemberNames, setChannelMemberNames] = useState<string[]>([]);
|
const [channelMemberNames, setChannelMemberNames] = useState<string[]>([]);
|
||||||
@@ -112,7 +119,17 @@ const ConvertGmToChannelModal = (props: Props) => {
|
|||||||
const {error} = await props.actions.convertGroupMessageToPrivateChannel(props.channel.id, selectedTeamId, channelName.trim(), channelURL.current.trim());
|
const {error} = await props.actions.convertGroupMessageToPrivateChannel(props.channel.id, selectedTeamId, channelName.trim(), channelURL.current.trim());
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setConversionError(error.message);
|
if (error.server_error_id === ServerErrorId.CHANNEL_NAME_EXISTS) {
|
||||||
|
setURLError(
|
||||||
|
formatMessage({
|
||||||
|
id: 'channel_modal.alreadyExist',
|
||||||
|
defaultMessage: 'A channel with that URL already exists',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setConversionError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +139,7 @@ const ConvertGmToChannelModal = (props: Props) => {
|
|||||||
}, [selectedTeamId, props.channel.id, channelName, channelURL.current, props.actions.moveChannelsInSidebar]);
|
}, [selectedTeamId, props.channel.id, channelName, channelURL.current, props.actions.moveChannelsInSidebar]);
|
||||||
|
|
||||||
const showLoader = !commonTeamsFetched || !loadingAnimationTimeout;
|
const showLoader = !commonTeamsFetched || !loadingAnimationTimeout;
|
||||||
const canCreate = selectedTeamId !== undefined && channelName !== '' && !nameError;
|
const canCreate = selectedTeamId !== undefined && channelName !== '' && !nameError && !urlError;
|
||||||
const modalProps: Partial<ComponentProps<typeof GenericModal>> = {};
|
const modalProps: Partial<ComponentProps<typeof GenericModal>> = {};
|
||||||
let modalBody;
|
let modalBody;
|
||||||
|
|
||||||
@@ -171,6 +188,7 @@ const ConvertGmToChannelModal = (props: Props) => {
|
|||||||
onURLChange={handleChannelURLChange}
|
onURLChange={handleChannelURLChange}
|
||||||
onErrorStateChange={setNameError}
|
onErrorStateChange={setNameError}
|
||||||
team={selectedTeamId ? commonTeamsById[selectedTeamId] : undefined}
|
team={selectedTeamId ? commonTeamsById[selectedTeamId] : undefined}
|
||||||
|
urlError={urlError}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -81,7 +81,3 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-channel-modal__url {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ const NewChannelModal = () => {
|
|||||||
const [canCreateFromPluggable, setCanCreateFromPluggable] = useState(true);
|
const [canCreateFromPluggable, setCanCreateFromPluggable] = useState(true);
|
||||||
const [actionFromPluggable, setActionFromPluggable] = useState<((currentTeamId: string, channelId: string) => Promise<Board>) | undefined>(undefined);
|
const [actionFromPluggable, setActionFromPluggable] = useState<((currentTeamId: string, channelId: string) => Promise<Board>) | undefined>(undefined);
|
||||||
|
|
||||||
|
const handleURLChange = useCallback((newURL: string) => {
|
||||||
|
setURL(newURL);
|
||||||
|
setURLError('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleOnModalConfirm = async () => {
|
const handleOnModalConfirm = async () => {
|
||||||
if (!canCreate) {
|
if (!canCreate) {
|
||||||
return;
|
return;
|
||||||
@@ -265,8 +270,9 @@ const NewChannelModal = () => {
|
|||||||
name='new-channel-modal-name'
|
name='new-channel-modal-name'
|
||||||
placeholder={formatMessage({id: 'channel_modal.name.placeholder', defaultMessage: 'Enter a name for your new channel'})}
|
placeholder={formatMessage({id: 'channel_modal.name.placeholder', defaultMessage: 'Enter a name for your new channel'})}
|
||||||
onDisplayNameChange={setDisplayName}
|
onDisplayNameChange={setDisplayName}
|
||||||
onURLChange={setURL}
|
onURLChange={handleURLChange}
|
||||||
onErrorStateChange={setChannelInputError}
|
onErrorStateChange={setChannelInputError}
|
||||||
|
urlError={urlError}
|
||||||
/>
|
/>
|
||||||
<PublicPrivateSelector
|
<PublicPrivateSelector
|
||||||
className='new-channel-modal-type-selector'
|
className='new-channel-modal-type-selector'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
.url-input-label {
|
.url-input-label {
|
||||||
flex: none;
|
flex: none;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user