Add fetchMissingUsers option to AtMention and messageHtmlToComponent (#29000)
* Add fetchMissingUsers option to AtMention and messageHtmlToComponent * Update snapshots
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ee20ac8f69
Коммит
e1b0f1d70e
13
webapp/channels/src/components/at_mention/actions.ts
Обычный файл
13
webapp/channels/src/components/at_mention/actions.ts
Обычный файл
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {getMissingProfilesByUsernames} from 'mattermost-redux/actions/users';
|
||||
import type {ActionFuncAsync} from 'mattermost-redux/types/actions';
|
||||
|
||||
import {getPotentialMentionsForName} from 'utils/post_utils';
|
||||
|
||||
export function getMissingMentionedUsers(text: string): ActionFuncAsync<Array<UserProfile['username']>> {
|
||||
return getMissingProfilesByUsernames(getPotentialMentionsForName(text));
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import AtMention from 'components/at_mention/at_mention';
|
||||
|
||||
import {render} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
/* eslint-disable global-require */
|
||||
@@ -28,7 +29,7 @@ describe('components/AtMention', () => {
|
||||
marketing: TestHelper.getGroupMock({id: 'qwerty2', name: 'marketing', allow_reference: false}),
|
||||
accounting: TestHelper.getGroupMock({id: 'qwerty3', name: 'accounting', allow_reference: true}),
|
||||
},
|
||||
dispatch: jest.fn(),
|
||||
getMissingMentionedUsers: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot when mentioning user', () => {
|
||||
@@ -227,4 +228,34 @@ describe('components/AtMention', () => {
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('fetchMissingUsers', () => {
|
||||
test('when fetchMissingUsers is true, should fetch an unloaded user on mount', () => {
|
||||
render(
|
||||
<AtMention
|
||||
{...baseProps}
|
||||
mentionName='someuser'
|
||||
fetchMissingUsers={true}
|
||||
>
|
||||
{'@someuser'}
|
||||
</AtMention>,
|
||||
);
|
||||
|
||||
expect(baseProps.getMissingMentionedUsers).toHaveBeenCalledWith('someuser');
|
||||
});
|
||||
|
||||
test('when fetchMissingUsers is false, should not fetch an unloaded user on mount', () => {
|
||||
shallow(
|
||||
<AtMention
|
||||
{...baseProps}
|
||||
mentionName='someuser'
|
||||
fetchMissingUsers={false}
|
||||
>
|
||||
{'@someuser'}
|
||||
</AtMention>,
|
||||
);
|
||||
|
||||
expect(baseProps.getMissingMentionedUsers).not.toHaveBeenCalledWith('someuser');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {useRef, useMemo, memo} from 'react';
|
||||
import React, {useRef, useMemo, memo, useEffect} from 'react';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
@@ -22,6 +22,7 @@ type OwnProps = {
|
||||
channelId?: string;
|
||||
disableHighlight?: boolean;
|
||||
disableGroupHighlight?: boolean;
|
||||
fetchMissingUsers?: boolean;
|
||||
}
|
||||
|
||||
type Props = OwnProps & PropsFromRedux;
|
||||
@@ -34,6 +35,12 @@ const AtMention = (props: Props) => {
|
||||
[props.mentionName, props.usersByUsername, props.groupsByName, props.disableGroupHighlight],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user && !group && props.fetchMissingUsers) {
|
||||
props.getMissingMentionedUsers(props.mentionName);
|
||||
}
|
||||
}, [props.mentionName]);
|
||||
|
||||
const returnFocus = () => {
|
||||
document.dispatchEvent(new CustomEvent<A11yFocusEventDetail>(
|
||||
A11yCustomEventTypes.FOCUS, {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {getCurrentUserId, getUsersByUsername} from 'mattermost-redux/selectors/e
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import {getMissingMentionedUsers} from './actions';
|
||||
import AtMention from './at_mention';
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
@@ -21,7 +22,11 @@ function mapStateToProps(state: GlobalState) {
|
||||
};
|
||||
}
|
||||
|
||||
const connector = connect(mapStateToProps);
|
||||
const mapDispatchToProps = {
|
||||
getMissingMentionedUsers,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default connector(AtMention);
|
||||
|
||||
@@ -139,7 +139,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
|
||||
>
|
||||
<Memo(AtMention)
|
||||
currentUserId="admin1"
|
||||
dispatch={[Function]}
|
||||
getMissingMentionedUsers={[Function]}
|
||||
groupsByName={Object {}}
|
||||
mentionName="user-0"
|
||||
teammateNameDisplay="username"
|
||||
@@ -153,7 +153,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
|
||||
>
|
||||
<Memo(AtMention)
|
||||
currentUserId="admin1"
|
||||
dispatch={[Function]}
|
||||
getMissingMentionedUsers={[Function]}
|
||||
groupsByName={Object {}}
|
||||
mentionName="user-1"
|
||||
teammateNameDisplay="username"
|
||||
@@ -316,7 +316,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
|
||||
>
|
||||
<Memo(AtMention)
|
||||
currentUserId="admin1"
|
||||
dispatch={[Function]}
|
||||
getMissingMentionedUsers={[Function]}
|
||||
groupsByName={Object {}}
|
||||
mentionName="user-0"
|
||||
teammateNameDisplay="username"
|
||||
@@ -330,7 +330,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
|
||||
>
|
||||
<Memo(AtMention)
|
||||
currentUserId="admin1"
|
||||
dispatch={[Function]}
|
||||
getMissingMentionedUsers={[Function]}
|
||||
groupsByName={Object {}}
|
||||
mentionName="user-1"
|
||||
teammateNameDisplay="username"
|
||||
@@ -553,7 +553,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
|
||||
>
|
||||
<Memo(AtMention)
|
||||
currentUserId="admin1"
|
||||
dispatch={[Function]}
|
||||
getMissingMentionedUsers={[Function]}
|
||||
groupsByName={Object {}}
|
||||
mentionName="user-0"
|
||||
teammateNameDisplay="username"
|
||||
@@ -873,7 +873,7 @@ exports[`components/channel_invite_modal/team_warning_banner should match snapsh
|
||||
>
|
||||
<Memo(AtMention)
|
||||
currentUserId="admin1"
|
||||
dispatch={[Function]}
|
||||
getMissingMentionedUsers={[Function]}
|
||||
groupsByName={Object {}}
|
||||
mentionName="user-0"
|
||||
teammateNameDisplay="username"
|
||||
|
||||
@@ -197,8 +197,8 @@ export function getMissingProfilesByIds(userIds: string[]): ActionFuncAsync<Arra
|
||||
|
||||
export function getMissingProfilesByUsernames(usernames: string[]): ActionFuncAsync<Array<UserProfile['username']>> {
|
||||
return async (dispatch, getState, {loaders}: any) => {
|
||||
if (!loaders.missingUsernameLoader) {
|
||||
loaders.missingUsernameLoader = new DelayedDataLoader<UserProfile['username']>({
|
||||
if (!loaders.userByUsernameLoader) {
|
||||
loaders.userByUsernameLoader = new DelayedDataLoader<UserProfile['username']>({
|
||||
fetchBatch: (usernames) => dispatch(getProfilesByUsernames(usernames)),
|
||||
maxBatchSize: maxUserIdsPerProfilesRequest,
|
||||
wait: missingProfilesWait,
|
||||
@@ -210,7 +210,7 @@ export function getMissingProfilesByUsernames(usernames: string[]): ActionFuncAs
|
||||
const missingUsernames = usernames.filter((username) => !usersByUsername[username]);
|
||||
|
||||
if (missingUsernames.length > 0) {
|
||||
await loaders.missingUsernameLoader.queueAndWait(missingUsernames);
|
||||
await loaders.userByUsernameLoader.queueAndWait(missingUsernames);
|
||||
}
|
||||
|
||||
return {data: missingUsernames};
|
||||
|
||||
@@ -35,6 +35,13 @@ export type Options = Partial<{
|
||||
images: boolean;
|
||||
atPlanMentions: boolean;
|
||||
channelId: string;
|
||||
|
||||
/**
|
||||
* Whether or not the AtMention component should attempt to fetch at-mentioned users if none can be found for
|
||||
* something that looks like an at-mention. This defaults to false because the web app currently loads at-mentioned
|
||||
* users automatically for all posts.
|
||||
*/
|
||||
fetchMissingUsers: boolean;
|
||||
}>
|
||||
|
||||
type ProcessingInstruction = {
|
||||
@@ -132,6 +139,7 @@ export function messageHtmlToComponent(html: string, options: Options = {}) {
|
||||
disableHighlight={!mentionHighlight}
|
||||
disableGroupHighlight={disableGroupHighlight}
|
||||
channelId={options.channelId}
|
||||
fetchMissingUsers={options.fetchMissingUsers}
|
||||
>
|
||||
{children}
|
||||
</AtMention>
|
||||
|
||||
@@ -757,19 +757,32 @@ export function makeGetIsReactionAlreadyAddedToPost(): (state: GlobalState, post
|
||||
);
|
||||
}
|
||||
|
||||
export function getMentionDetails(usersByUsername: Record<string, UserProfile | Group>, mentionName: string): UserProfile | Group | undefined {
|
||||
/**
|
||||
* Given the text of an at-mention without the @, returns an array containing that text and every substring of it that
|
||||
* can be made by removing trailing punctiuation.
|
||||
*
|
||||
* For example, getPotentialMentionsForName('username') returns ['username'] and
|
||||
* getPotentialMentionsForName('username..') return ['username..', 'username.', 'username'].
|
||||
*/
|
||||
export function getPotentialMentionsForName(mentionName: string): string[] {
|
||||
let mentionNameToLowerCase = mentionName.toLowerCase();
|
||||
|
||||
while (mentionNameToLowerCase.length > 0) {
|
||||
if (usersByUsername.hasOwnProperty(mentionNameToLowerCase)) {
|
||||
return usersByUsername[mentionNameToLowerCase];
|
||||
}
|
||||
const potentialMentions = [mentionNameToLowerCase];
|
||||
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
if ((/[._-]$/).test(mentionNameToLowerCase)) {
|
||||
mentionNameToLowerCase = mentionNameToLowerCase.substring(0, mentionNameToLowerCase.length - 1);
|
||||
} else {
|
||||
break;
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
while (mentionNameToLowerCase.length > 0 && (/[._-]$/).test(mentionNameToLowerCase)) {
|
||||
mentionNameToLowerCase = mentionNameToLowerCase.substring(0, mentionNameToLowerCase.length - 1);
|
||||
|
||||
potentialMentions.push(mentionNameToLowerCase);
|
||||
}
|
||||
|
||||
return potentialMentions;
|
||||
}
|
||||
|
||||
export function getMentionDetails<T extends UserProfile | Group>(entitiesByName: Record<string, T>, mentionName: string): T | undefined {
|
||||
for (const potentialMention of getPotentialMentionsForName(mentionName)) {
|
||||
if (Object.hasOwn(entitiesByName, potentialMention)) {
|
||||
return entitiesByName[potentialMention];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,11 +796,11 @@ export function getUserOrGroupFromMentionName(
|
||||
groupsDisabled?: boolean,
|
||||
getMention = getMentionDetails,
|
||||
): [UserProfile?, Group?] {
|
||||
const user = getMention(users, mentionName) as UserProfile | undefined;
|
||||
const user = getMention(users, mentionName);
|
||||
|
||||
// prioritizes user if user exists with the same name as a group.
|
||||
if (!user && !groupsDisabled) {
|
||||
const group = getMention(groups, mentionName) as Group | undefined;
|
||||
const group = getMention(groups, mentionName);
|
||||
if (group && !group.allow_reference) {
|
||||
return [undefined, undefined]; // remove group mention if not allowed to reference
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user