MM-61032: Add default_team_id to accept invite flow (#28841)

* add default_team_id to accept invite api

* add team selector to accept invite flow UI

* e2e

* lint/i18n
Этот коммит содержится в:
Caleb Roseland
2024-10-18 05:26:38 -05:00
коммит произвёл GitHub
родитель 5a923e0b94
Коммит fcded9559c
16 изменённых файлов: 124 добавлений и 32 удалений

Просмотреть файл

@@ -156,6 +156,14 @@ export const ModalFieldsetWrapper = styled.div`
background: none !important;
height: 34px !important;
}
.Input_container {
margin-bottom: 10px;
}
.DropdownInput.Input_container {
margin-top: 0;
}
`;
const ModalLegend = styled.legend`

Просмотреть файл

@@ -116,7 +116,7 @@ export const useRemoteClusterAcceptInvite = () => {
modalId: ModalIdentifiers.SECURE_CONNECTION_ACCEPT_INVITE,
dialogType: SecureConnectionAcceptInviteModal,
dialogProps: {
onConfirm: async (acceptInvite: PartialExcept<RemoteClusterAcceptInvite, 'display_name' | 'invite' | 'password'>) => {
onConfirm: async (acceptInvite: PartialExcept<RemoteClusterAcceptInvite, 'display_name' | 'default_team_id' | 'invite' | 'password'>) => {
try {
setSaving(true);
const rc = await Client4.acceptInviteRemoteCluster({

Просмотреть файл

@@ -13,12 +13,13 @@ import LoadingScreen from 'components/loading_screen';
import Input from 'components/widgets/inputs/input/input';
import {ModalFieldset, ModalParagraph} from '../controls';
import {isErrorState, isPendingState} from '../utils';
import TeamSelector from '../team_selector';
import {isErrorState, isPendingState, useTeamOptions} from '../utils';
type Props = {
creating?: boolean;
password?: string;
onConfirm: (accept: PartialExcept<RemoteClusterAcceptInvite, 'display_name' | 'invite' | 'password'>) => Promise<RemoteCluster>;
onConfirm: (accept: PartialExcept<RemoteClusterAcceptInvite, 'display_name' | 'default_team_id' | 'invite' | 'password'>) => Promise<RemoteCluster>;
onCancel?: () => void;
onExited: () => void;
onHide: () => void;
@@ -34,12 +35,16 @@ function SecureConnectionAcceptInviteModal({
}: Props) {
const {formatMessage} = useIntl();
const [displayName, setDisplayName] = useState('');
const [defaultTeamId, setDefaultTeamId] = useState('');
const [inviteCode, setInviteCode] = useState('');
const [password, setPassword] = useState('');
const [saving, setSaving] = useState<boolean | ClientError>(false);
const teamsById = useTeamOptions();
const need = {
displayName: !displayName,
defaultTeamId: !defaultTeamId,
inviteCode: !inviteCode,
password: !password,
};
@@ -50,7 +55,12 @@ function SecureConnectionAcceptInviteModal({
setSaving(true);
try {
await onConfirm({display_name: displayName, invite: inviteCode, password});
await onConfirm({
display_name: displayName,
default_team_id: defaultTeamId,
invite: inviteCode,
password,
});
setSaving(false);
onHide();
} catch (err) {
@@ -90,6 +100,7 @@ function SecureConnectionAcceptInviteModal({
modalHeaderText={title}
onExited={onExited}
compassDesign={true}
bodyOverflowVisible={true}
autoCloseOnConfirmButton={false}
errorText={isErrorState(saving) && (
<FormattedMessage
@@ -120,6 +131,23 @@ function SecureConnectionAcceptInviteModal({
onChange={handleDisplayNameChange}
data-testid='display-name'
/>
<FormattedMessage
id={'admin.secure_connections.accept_invite.select_team'}
defaultMessage={'Please select the destination team where channels will be placed.'}
tagName={ModalParagraph}
/>
<TeamSelector
testId='destination-team-input'
value={defaultTeamId}
teamsById={teamsById}
onChange={setDefaultTeamId}
legend={formatMessage({
id: 'admin.secure_connections.accept_invite.select_team.legend',
defaultMessage: 'Select a team',
})}
/>
<FormattedMessage
id={'admin.secure_connections.accept_invite.prompt_invite_password'}
defaultMessage={'Enter the encrypted invitation code shared to you by the admin of the server you are connecting with.'}

Просмотреть файл

@@ -12,11 +12,8 @@ import styled from 'styled-components';
import {GlobeIcon, LockIcon, PlusIcon, ArchiveOutlineIcon} from '@mattermost/compass-icons/components';
import {isRemoteClusterPatch, type RemoteCluster} from '@mattermost/types/remote_clusters';
import type {Team} from '@mattermost/types/teams';
import type {IDMappedObjects} from '@mattermost/types/utilities';
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {getActiveTeamsList} from 'mattermost-redux/selectors/entities/teams';
import {setNavigationBlocked} from 'actions/admin_actions';
@@ -47,7 +44,7 @@ import {
import {useRemoteClusterCreate, useSharedChannelsAdd, useSharedChannelsRemove} from './modals/modal_utils';
import TeamSelector from './team_selector';
import type {SharedChannelRemoteRow} from './utils';
import {getEditLocation, isConfirmed, isErrorState, isPendingState, useRemoteClusterEdit, useSharedChannelRemoteRows} from './utils';
import {getEditLocation, isConfirmed, isErrorState, isPendingState, useRemoteClusterEdit, useSharedChannelRemoteRows, useTeamOptions} from './utils';
import {AdminConsoleListTable} from '../list_table';
import SaveChangesPanel from '../team_channel_settings/save_changes_panel';
@@ -73,6 +70,8 @@ export default function SecureConnectionDetail(props: Props) {
const {promptCreate, saving: creating} = useRemoteClusterCreate();
const teamsById = useTeamOptions();
useEffect(() => {
// keep history cache up to date
history.replace({...location, state: currentRemoteCluster});
@@ -87,8 +86,6 @@ export default function SecureConnectionDetail(props: Props) {
applyPatch({display_name: value});
};
const teams = useSelector(getActiveTeamsList);
const teamsById = useMemo(() => teams.reduce<IDMappedObjects<Team>>((teams, team) => ({...teams, [team.id]: team}), {}), [teams]);
const handleTeamChange = (teamId: string) => {
applyPatch({default_team_id: teamId});
};

Просмотреть файл

@@ -15,6 +15,7 @@ export type Props = {
teamsById: IDMappedObjects<Team>;
onChange: (teamId: string) => void;
testId: string;
legend?: string;
}
const TeamSelector = (props: Props): JSX.Element => {
@@ -40,6 +41,7 @@ const TeamSelector = (props: Props): JSX.Element => {
value={value ? {label: value.display_name, value: value.id} : undefined}
options={teamValues}
name='team_selector'
legend={props.legend}
/>
);
};

Просмотреть файл

@@ -3,8 +3,8 @@
import type {LocationDescriptor} from 'history';
import {DateTime, Interval} from 'luxon';
import {useCallback, useEffect, useState} from 'react';
import {useDispatch} from 'react-redux';
import {useCallback, useEffect, useMemo, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import type {ClientError} from '@mattermost/client';
import type {Channel} from '@mattermost/types/channels';
@@ -17,7 +17,7 @@ import {ChannelTypes} from 'mattermost-redux/action_types';
import {getChannel as fetchChannel} from 'mattermost-redux/actions/channels';
import {Client4} from 'mattermost-redux/client';
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {getTeam} from 'mattermost-redux/selectors/entities/teams';
import {getActiveTeamsList, getTeam} from 'mattermost-redux/selectors/entities/teams';
import type {ActionFuncAsync} from 'mattermost-redux/types/actions';
import type {GlobalState} from 'types/store';
@@ -228,6 +228,12 @@ export const useSharedChannelRemoteRows = (remoteId: string, opts: {filter: 'hom
return [sharedChannelRemotes, {loading, error, fetch}] as const;
};
export const useTeamOptions = () => {
const teams = useSelector(getActiveTeamsList);
const teamsById = useMemo(() => teams.reduce<IDMappedObjects<Team>>((teams, team) => ({...teams, [team.id]: team}), {}), [teams]);
return teamsById;
};
export const getEditLocation = (rc: RemoteCluster): LocationDescriptor<RemoteCluster> => {
return {pathname: `/admin_console/environment/secure_connections/${rc.remote_id}`, state: rc};
};

Просмотреть файл

@@ -2222,6 +2222,8 @@
"admin.secure_connections.accept_invite.prompt": "Accept a secure connection from another server",
"admin.secure_connections.accept_invite.prompt_invite_password": "Enter the encrypted invitation code shared to you by the admin of the server you are connecting with.",
"admin.secure_connections.accept_invite.saving_changes_error": "There was an error while accepting the invite.",
"admin.secure_connections.accept_invite.select_team": "Please select the destination team where channels will be placed.",
"admin.secure_connections.accept_invite.select_team.legend": "Select a team",
"admin.secure_connections.accept_invite.share_title": "Accept a connection invite",
"admin.secure_connections.confirm.delete.button": "Yes, delete",
"admin.secure_connections.confirm.delete.text": "Are you sure you want to delete the secure connection <strong>{displayName}</strong>?",

Просмотреть файл

@@ -11,6 +11,7 @@ export type RemoteClusterInvite = {
export type RemoteClusterAcceptInvite = {
name: string;
display_name: string;
default_team_id: string;
invite: string;
password: string;
}