MM-58349: Shared Channels in System Console (#28116)

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Miguel de la Cruz <miguel@mcrx.me>
Этот коммит содержится в:
Caleb Roseland
2024-10-09 07:53:19 -05:00
коммит произвёл GitHub
родитель 02e6851603
Коммит 0f200adbe0
39 изменённых файлов: 3338 добавлений и 96 удалений

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

@@ -26,7 +26,7 @@ import type {
ChannelSearchOpts,
ServerChannel,
} from '@mattermost/types/channels';
import type {Options, StatusOK, ClientResponse, FetchPaginatedThreadOptions} from '@mattermost/types/client4';
import type {Options, StatusOK, ClientResponse, FetchPaginatedThreadOptions, OptsSignalExt} from '@mattermost/types/client4';
import {LogLevel} from '@mattermost/types/client4';
import type {
Address,
@@ -110,12 +110,14 @@ import type {Post, PostList, PostSearchResults, PostsUsageResponse, TeamsUsageRe
import type {PreferenceType} from '@mattermost/types/preferences';
import type {ProductNotices} from '@mattermost/types/product_notices';
import type {Reaction} from '@mattermost/types/reactions';
import type {RemoteCluster, RemoteClusterAcceptInvite, RemoteClusterPatch, RemoteClusterWithPassword} from '@mattermost/types/remote_clusters';
import type {UserReport, UserReportFilter, UserReportOptions} from '@mattermost/types/reports';
import type {Role} from '@mattermost/types/roles';
import type {SamlCertificateStatus, SamlMetadataResponse} from '@mattermost/types/saml';
import type {Scheme} from '@mattermost/types/schemes';
import type {Session} from '@mattermost/types/sessions';
import type {CompleteOnboardingRequest} from '@mattermost/types/setup';
import type {SharedChannelRemote} from '@mattermost/types/shared_channels';
import type {
GetTeamMembersOpts,
Team,
@@ -140,7 +142,7 @@ import type {
GetFilteredUsersStatsOpts,
UserCustomStatus,
} from '@mattermost/types/users';
import type {DeepPartial, RelationOneToOne} from '@mattermost/types/utilities';
import type {DeepPartial, PartialExcept, RelationOneToOne} from '@mattermost/types/utilities';
import {cleanUrlForLogging} from './errors';
import {buildQueryString} from './helpers';
@@ -327,6 +329,14 @@ export default class Client4 {
return `${this.getBaseRoute()}/users/${userId}/teams/${teamId}/channels/categories`;
}
getRemoteClustersRoute() {
return `${this.getBaseRoute()}/remotecluster`;
}
getRemoteClusterRoute(remoteId: string) {
return `${this.getRemoteClustersRoute()}/${remoteId}`;
}
getPostsRoute() {
return `${this.getBaseRoute()}/posts`;
}
@@ -1942,9 +1952,9 @@ export default class Client4 {
);
};
searchAllChannels(term: string, opts: {page: number; per_page: number} & ChannelSearchOpts): Promise<ChannelsWithTotalCount>;
searchAllChannels(term: string, opts: Omit<ChannelSearchOpts, 'page' | 'per_page'> | undefined): Promise<ChannelWithTeamData[]>;
searchAllChannels(term: string, opts: ChannelSearchOpts = {}) {
searchAllChannels(term: string, opts: {page: number; per_page: number} & ChannelSearchOpts & OptsSignalExt): Promise<ChannelsWithTotalCount>;
searchAllChannels(term: string, opts: Omit<ChannelSearchOpts, 'page' | 'per_page'> & OptsSignalExt | undefined): Promise<ChannelWithTeamData[]>;
searchAllChannels(term: string, opts: ChannelSearchOpts & OptsSignalExt = {}) {
const body = {
term,
...opts,
@@ -1958,7 +1968,7 @@ export default class Client4 {
}
return this.doFetch<ChannelWithTeamData[] | ChannelsWithTotalCount>(
`${this.getChannelsRoute()}/search${buildQueryString(queryParams)}`,
{method: 'post', body: JSON.stringify(body)},
{method: 'post', body: JSON.stringify(body), signal: opts.signal},
);
}
@@ -2072,6 +2082,89 @@ export default class Client4 {
);
};
// Remote Clusters Routes
getRemoteClusters = (options: {excludePlugins: boolean}) => {
return this.doFetch<RemoteCluster[]>(
`${this.getRemoteClustersRoute()}${buildQueryString({exclude_plugins: options.excludePlugins})}`,
{method: 'GET'},
);
};
getRemoteCluster = (remoteId: string) => {
return this.doFetch<RemoteCluster>(
`${this.getRemoteClusterRoute(remoteId)}`,
{method: 'GET'},
);
};
createRemoteCluster = (remoteCluster: PartialExcept<RemoteClusterWithPassword, 'name' | 'display_name'>) => {
return this.doFetch<{invite: string; password: string; remote_cluster: RemoteCluster}>(
`${this.getRemoteClustersRoute()}`,
{method: 'POST', body: JSON.stringify(remoteCluster)},
);
};
patchRemoteCluster = (remoteId: string, patch: Partial<RemoteClusterPatch>) => {
return this.doFetch<RemoteCluster>(
`${this.getRemoteClusterRoute(remoteId)}`,
{method: 'PATCH', body: JSON.stringify(patch)},
);
};
deleteRemoteCluster = (remoteId: string) => {
return this.doFetch(
`${this.getRemoteClusterRoute(remoteId)}`,
{method: 'DELETE'},
);
};
acceptInviteRemoteCluster = (remoteClusterAcceptInvite: RemoteClusterAcceptInvite) => {
return this.doFetch<RemoteCluster>(
`${this.getRemoteClustersRoute()}/accept_invite`,
{method: 'POST', body: JSON.stringify(remoteClusterAcceptInvite)},
);
};
generateInviteRemoteCluster = (remoteId: string, remoteCluster: Partial<Pick<RemoteClusterWithPassword, 'password'>>) => {
return this.doFetch<string>(
`${this.getRemoteClusterRoute(remoteId)}/generate_invite`,
{method: 'POST', body: JSON.stringify(remoteCluster)},
);
};
// Shared Channels Routes
getSharedChannelRemotes = (
remoteId: string,
filters: {
exclude_remote?: boolean;
exclude_home?: boolean;
include_deleted?: boolean;
include_unconfirmed?: boolean;
exclude_confirmed?: boolean;
},
) => {
return this.doFetch<SharedChannelRemote[]>(
`${this.getRemoteClusterRoute(remoteId)}/sharedchannelremotes${buildQueryString(filters)}`,
{method: 'GET'},
);
};
sharedChannelRemoteInvite = (remoteId: string, channelId: string) => {
return this.doFetch<StatusOK>(
`${this.getRemoteClusterRoute(remoteId)}/channels/${channelId}/invite`,
{method: 'POST'},
);
};
sharedChannelRemoteUninvite = (remoteId: string, channelId: string) => {
return this.doFetch<StatusOK>(
`${this.getRemoteClusterRoute(remoteId)}/channels/${channelId}/uninvite`,
{method: 'POST'},
);
};
// Post Routes
createPost = async (post: Post) => {

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

@@ -31,7 +31,7 @@ export type Props = {
ariaLabel?: string;
errorText?: string | React.ReactNode;
compassDesign?: boolean;
backdrop?: boolean;
backdrop?: boolean | 'static';
backdropClassName?: string;
tabIndex?: number;
children: React.ReactNode;
@@ -40,6 +40,7 @@ export type Props = {
headerInput?: React.ReactNode;
bodyPadding?: boolean;
bodyDivider?: boolean;
bodyOverflowVisible?: boolean;
footerContent?: React.ReactNode;
footerDivider?: boolean;
appendedContent?: React.ReactNode;
@@ -180,7 +181,14 @@ export class GenericModal extends React.PureComponent<Props, State> {
role='dialog'
aria-label={this.props.ariaLabel}
aria-labelledby={this.props.ariaLabel ? undefined : 'genericModalLabel'}
dialogClassName={classNames('a11y__modal GenericModal', {GenericModal__compassDesign: this.props.compassDesign}, this.props.className)}
dialogClassName={classNames(
'a11y__modal GenericModal',
{
GenericModal__compassDesign: this.props.compassDesign,
'modal--overflow': this.props.bodyOverflowVisible,
},
this.props.className,
)}
show={this.state.show}
restoreFocus={true}
enforceFocus={this.props.enforceFocus}
@@ -204,7 +212,7 @@ export class GenericModal extends React.PureComponent<Props, State> {
</>
)}
</Modal.Header>
<Modal.Body className={classNames({divider: this.props.bodyDivider})}>
<Modal.Body className={classNames({divider: this.props.bodyDivider, 'overflow-visible': this.props.bodyOverflowVisible})}>
{this.props.compassDesign ? (
this.props.errorText && (
<div className='genericModalError'>

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

@@ -25,6 +25,8 @@ export type Options = {
duplex?: 'half'; /** Optional, but required for node clients. Must be 'half' for half-duplex fetch; 'full' is reserved for future use. See https://fetch.spec.whatwg.org/#dom-requestinit-duplex */
};
export type OptsSignalExt = {signal?: AbortSignal};
export type StatusOK = {
status: 'OK';
};

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

@@ -509,6 +509,13 @@ export type WranglerSettings = {
MoveThreadFromGroupMessageChannelEnable: boolean;
};
export type ConnectedWorkspacesSettings = {
EnableSharedChannels: boolean;
EnableRemoteClusterService: boolean;
DisableSharedChannelsStatusSync: boolean;
MaxPostsPerSync: number;
}
export type FileSettings = {
EnableFileAttachments: boolean;
EnableMobileUpload: boolean;
@@ -981,6 +988,7 @@ export type AdminConfig = {
ImportSettings: ImportSettings;
ExportSettings: ExportSettings;
WranglerSettings: WranglerSettings;
ConnectedWorkspacesSettings: ConnectedWorkspacesSettings;
};
export type ReplicaLagSetting = {

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

@@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export type RemoteClusterInvite = {
remote_id: string;
remote_team_id: string;
site_url: string;
token: string;
};
export type RemoteClusterAcceptInvite = {
name: string;
display_name: string;
invite: string;
password: string;
}
export type RemoteClusterPatch = Pick<RemoteCluster, 'display_name' | 'default_team_id'>
export function isRemoteClusterPatch(x: Partial<RemoteClusterPatch>): x is RemoteClusterPatch {
return (
(x.display_name !== undefined && x.display_name !== '') ||
x.default_team_id !== undefined
);
}
export type RemoteCluster = {
remote_id: string;
remote_team_id: string;
name: string;
display_name: string;
site_url: string;
create_at: number;
delete_at: number;
last_ping_at: number;
token?: string;
remote_token?: string;
topics: string;
creator_id: string;
plugin_id: string;
options: number;
default_team_id: string;
}
export type RemoteClusterWithPassword = RemoteCluster & {
password: string;
}

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

@@ -0,0 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export type SharedChannelRemote = {
id: string;
channel_id: string;
creator_id: string;
create_at: number;
update_at: number;
delete_at: number;
is_invite_accepted: boolean;
is_invite_confirmed: boolean;
remote_id: string;
last_post_update_at: number;
last_post_id: string;
last_post_create_at: number;
last_post_create_id: string;
}

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

@@ -43,3 +43,5 @@ Pick<T, Exclude<keyof T, Keys>> & {[K in Keys]-?: Required<Pick<T, K>> & Partial
export type Intersection<T1, T2> =
Omit<Omit<T1&T2, keyof(Omit<T1, keyof(T2)>)>, keyof(Omit<T2, keyof(T1)>)>;
export type PartialExcept<T extends Record<string, unknown>, TKeysNotPartial extends keyof T> = Partial<T> & Pick<T, TKeysNotPartial>;