diff --git a/webapp/channels/src/components/__snapshots__/textbox.test.tsx.snap b/webapp/channels/src/components/__snapshots__/textbox.test.tsx.snap index da8232ae7e..5f2428aed3 100644 --- a/webapp/channels/src/components/__snapshots__/textbox.test.tsx.snap +++ b/webapp/channels/src/components/__snapshots__/textbox.test.tsx.snap @@ -8,7 +8,7 @@ exports[`components/TextBox should match snapshot with additional, optional prop className="form-control custom-textarea textbox-preview-area" onBlur={[Function]} onKeyDown={[Function]} - onKeyPress={[Function]} + onKeyPress={[MockFunction]} tabIndex={0} > { - const userid10 = {id: 'userid10', username: 'nicknamer', first_name: '', last_name: '', nickname: 'Z'}; - const userid3 = {id: 'userid3', username: 'other', first_name: 'X', last_name: 'Y', nickname: 'Z'}; - const userid1 = {id: 'userid1', username: 'user', first_name: 'a', last_name: 'b', nickname: 'c', isCurrentUser: true}; - const userid2 = {id: 'userid2', username: 'user2', first_name: 'd', last_name: 'e', nickname: 'f'}; - const userid4 = {id: 'userid4', username: 'user4', first_name: 'X', last_name: 'Y', nickname: 'Z'}; - const userid5 = {id: 'userid5', username: 'user5', first_name: 'out', last_name: 'out', nickname: 'out'}; - const userid6 = {id: 'userid6', username: 'user6.six-split', first_name: 'out Junior', last_name: 'out', nickname: 'out'}; - const userid7 = {id: 'userid7', username: 'xuser7', first_name: '', last_name: '', nickname: 'x'}; - const userid8 = {id: 'userid8', username: 'xuser8', first_name: 'Robert', last_name: 'Ward', nickname: 'nickname'}; + const userid10 = TestHelper.getUserMock({id: 'userid10', username: 'nicknamer', first_name: '', last_name: '', nickname: 'Z'}); + const userid3 = TestHelper.getUserMock({id: 'userid3', username: 'other', first_name: 'X', last_name: 'Y', nickname: 'Z'}); + const userid1 = {...TestHelper.getUserMock({id: 'userid1', username: 'user', first_name: 'a', last_name: 'b', nickname: 'c'}), isCurrentUser: true}; + const userid2 = TestHelper.getUserMock({id: 'userid2', username: 'user2', first_name: 'd', last_name: 'e', nickname: 'f'}); + const userid4 = TestHelper.getUserMock({id: 'userid4', username: 'user4', first_name: 'X', last_name: 'Y', nickname: 'Z'}); + const userid5 = TestHelper.getUserMock({id: 'userid5', username: 'user5', first_name: 'out', last_name: 'out', nickname: 'out'}); + const userid6 = TestHelper.getUserMock({id: 'userid6', username: 'user6.six-split', first_name: 'out Junior', last_name: 'out', nickname: 'out'}); + const userid7 = TestHelper.getUserMock({id: 'userid7', username: 'xuser7', first_name: '', last_name: '', nickname: 'x'}); + const userid8 = TestHelper.getUserMock({id: 'userid8', username: 'xuser8', first_name: 'Robert', last_name: 'Ward', nickname: 'nickname'}); - const groupid1 = {id: 'groupid1', name: 'board', display_name: 'board'}; - const groupid2 = {id: 'groupid2', name: 'developers', display_name: 'developers'}; - const groupid3 = {id: 'groupid3', name: 'software-engineers', display_name: 'software engineers'}; + const groupid1 = TestHelper.getGroupMock({id: 'groupid1', name: 'board', display_name: 'board'}); + const groupid2 = TestHelper.getGroupMock({id: 'groupid2', name: 'developers', display_name: 'developers'}); + const groupid3 = TestHelper.getGroupMock({id: 'groupid3', name: 'software-engineers', display_name: 'software engineers'}); - const baseParams = { + const baseParams: Props = { currentUserId: 'userid1', channelId: 'channelid1', autocompleteUsersInChannel: jest.fn().mockResolvedValue(false), autocompleteGroups: [groupid1, groupid2, groupid3], useChannelMentions: true, searchAssociatedGroupsForReference: jest.fn().mockResolvedValue(false), + priorityProfiles: [], }; it('should ignore pretexts that are not at-mentions', () => { @@ -169,8 +171,8 @@ describe('components/suggestion/at_mention_provider/AtMentionProvider', () => { }); it('should have priorityProfiles at the top', async () => { - const userid11 = {id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'}; - const userid12 = {id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'}; + const userid11 = TestHelper.getUserMock({id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'}); + const userid12 = TestHelper.getUserMock({id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'}); const pretext = '@'; const matchedPretext = '@'; @@ -315,8 +317,8 @@ describe('components/suggestion/at_mention_provider/AtMentionProvider', () => { }); it('should remove duplicates from results', async () => { - const userid11 = {id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'}; - const userid12 = {id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'}; + const userid11 = TestHelper.getUserMock({id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'}); + const userid12 = TestHelper.getUserMock({id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'}); const pretext = '@'; const matchedPretext = '@'; @@ -461,8 +463,8 @@ describe('components/suggestion/at_mention_provider/AtMentionProvider', () => { }); it('should sort results based on last_viewed_at', async () => { - const userid11 = {id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'}; - const userid12 = {id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'}; + const userid11 = TestHelper.getUserMock({id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'}); + const userid12 = TestHelper.getUserMock({id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'}); const pretext = '@'; const matchedPretext = '@'; diff --git a/webapp/channels/src/components/suggestion/at_mention_provider/at_mention_provider.jsx b/webapp/channels/src/components/suggestion/at_mention_provider/at_mention_provider.tsx similarity index 73% rename from webapp/channels/src/components/suggestion/at_mention_provider/at_mention_provider.jsx rename to webapp/channels/src/components/suggestion/at_mention_provider/at_mention_provider.tsx index 86106d9504..4138ffa149 100644 --- a/webapp/channels/src/components/suggestion/at_mention_provider/at_mention_provider.jsx +++ b/webapp/channels/src/components/suggestion/at_mention_provider/at_mention_provider.tsx @@ -1,14 +1,21 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import type {Group} from '@mattermost/types/groups'; +import type {UserProfile} from '@mattermost/types/users'; + +import type {Filters} from 'mattermost-redux/selectors/entities/users'; import {makeGetProfilesInChannel} from 'mattermost-redux/selectors/entities/users'; import {makeAddLastViewAtToProfiles} from 'mattermost-redux/selectors/entities/utils'; +import type {ActionResult} from 'mattermost-redux/types/actions'; import {getSuggestionsSplitBy, getSuggestionsSplitByMultiple} from 'mattermost-redux/utils/user_utils'; import store from 'stores/redux_store'; import {Constants} from 'utils/constants'; +import type {GlobalState} from 'types/store'; + import AtMentionSuggestion from './at_mention_suggestion'; import Provider from '../provider'; @@ -16,14 +23,65 @@ import Provider from '../provider'; const profilesInChannelOptions = {active: true}; const regexForAtMention = /(?:^|\W)@([\p{L}\d\-_. ]*)$/iu; +type UserProfileWithLastViewAt = UserProfile & {last_viewed_at?: number}; + +type CreatedProfile = UserProfile & { + type: string; + isCurrentUser?: boolean; + last_viewed_at?: number; +}; + +type CreatedGroup = Group & {type: string}; + +type Results = { + matchedPretext: string; + terms: string[]; + items: any; + component: React.ElementType; +}; + +type ResultsCallback = (results: Results) => void; + +export type Props = { + currentUserId: string; + channelId: string; + autocompleteUsersInChannel: (prefix: string) => Promise; + useChannelMentions: boolean; + autocompleteGroups: Group[] | null; + searchAssociatedGroupsForReference: (prefix: string) => Promise<{data: any}>; + priorityProfiles: UserProfile[] | undefined; +} + // The AtMentionProvider provides matches for at mentions, including @here, @channel, @all, // users in the channel and users not in the channel. It mixes together results from the local // store with results fetched from the server. export default class AtMentionProvider extends Provider { - constructor(props) { + public currentUserId: string; + public channelId: string; + public autocompleteUsersInChannel: (prefix: string) => Promise; + public useChannelMentions: boolean; + public autocompleteGroups: Group[] | null; + public searchAssociatedGroupsForReference: (prefix: string) => Promise<{data: any}>; + public priorityProfiles: UserProfile[] | undefined; + + public data: any; + public lastCompletedWord: string; + public lastPrefixWithNoResults: string; + public getProfilesInChannel: (state: GlobalState, channelId: string, filters?: Filters | undefined) => UserProfile[]; + public addLastViewAtToProfiles: (state: GlobalState, profiles: UserProfile[]) => UserProfileWithLastViewAt[]; + + constructor(props: Props) { super(); - this.setProps(props); + const {currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles} = props; + + this.currentUserId = currentUserId; + this.channelId = channelId; + this.autocompleteUsersInChannel = autocompleteUsersInChannel; + this.useChannelMentions = useChannelMentions; + this.autocompleteGroups = autocompleteGroups; + this.searchAssociatedGroupsForReference = searchAssociatedGroupsForReference; + this.priorityProfiles = priorityProfiles; this.data = null; this.lastCompletedWord = ''; @@ -33,9 +91,7 @@ export default class AtMentionProvider extends Provider { this.addLastViewAtToProfiles = makeAddLastViewAtToProfiles(); } - // setProps gives the provider additional context for matching pretexts. Ideally this would - // just be something akin to a connected component with access to the store itself. - setProps({currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles}) { + setProps({currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles}: Props) { this.currentUserId = currentUserId; this.channelId = channelId; this.autocompleteUsersInChannel = autocompleteUsersInChannel; @@ -61,8 +117,8 @@ export default class AtMentionProvider extends Provider { // retrieves the parts of the profile that should be checked // against the term - getProfileSuggestions(profile) { - const profileSuggestions = []; + getProfileSuggestions(profile: UserProfile) { + const profileSuggestions: string[] = []; if (!profile) { return profileSuggestions; } @@ -82,8 +138,8 @@ export default class AtMentionProvider extends Provider { // retrieves the parts of the group mention that should be checked // against the term - getGroupSuggestions(group) { - const groupSuggestions = []; + getGroupSuggestions(group: Group) { + const groupSuggestions: string[] = []; if (!group) { return groupSuggestions; } @@ -101,12 +157,12 @@ export default class AtMentionProvider extends Provider { } // normalizeString performs a unicode normalization to a string - normalizeString(name) { + normalizeString(name: string) { return name.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); } // filterProfile constrains profiles to those matching the latest prefix. - filterProfile(profile) { + filterProfile(profile: UserProfile | UserProfileWithLastViewAt) { if (!profile) { return false; } @@ -119,7 +175,7 @@ export default class AtMentionProvider extends Provider { } // filterGroup constrains group mentions to those matching the latest prefix. - filterGroup(group) { + filterGroup(group: Group) { if (!group) { return false; } @@ -182,8 +238,8 @@ export default class AtMentionProvider extends Provider { } const remoteMembers = (this.data.users || []). - filter((profile) => this.filterProfile(profile)). - map((profile) => this.createFromProfile(profile, Constants.MENTION_MEMBERS)); + filter((profile: UserProfileWithLastViewAt) => this.filterProfile(profile)). + map((profile: UserProfileWithLastViewAt) => this.createFromProfile(profile, Constants.MENTION_MEMBERS)); return remoteMembers; } @@ -193,30 +249,30 @@ export default class AtMentionProvider extends Provider { if (!this.data) { return []; } - const remoteGroups = (this.data.groups || []). - filter((group) => this.filterGroup(group)). - map((group) => this.createFromGroup(group, Constants.MENTION_GROUPS)); + const remoteGroups = ((this.data.groups || []) as Group[]). + filter((group: Group) => this.filterGroup(group)). + map((group: Group) => this.createFromGroup(group, Constants.MENTION_GROUPS)); return remoteGroups; } // remoteNonMembers matches users listed as not in the channel by the server. // listed in the channel from local results. - remoteNonMembers() { + remoteNonMembers(): CreatedProfile[] { if (!this.data) { return []; } return (this.data.out_of_channel || []). - filter((profile) => this.filterProfile(profile)). - map((profile) => ({ + filter((profile: UserProfileWithLastViewAt) => this.filterProfile(profile)). + map((profile: UserProfileWithLastViewAt) => ({ type: Constants.MENTION_NONMEMBERS, ...profile, })); } items() { - const priorityProfilesIds = {}; + const priorityProfilesIds: Record = {}; const priorityProfiles = this.filterPriorityProfiles(); priorityProfiles.forEach((member) => { @@ -226,16 +282,16 @@ export default class AtMentionProvider extends Provider { const specialMentions = this.specialMentions(); const localMembers = this.localMembers().filter((member) => !priorityProfilesIds[member.id]); - const localUserIds = {}; + const localUserIds: Record = {}; localMembers.forEach((member) => { localUserIds[member.id] = true; }); - const remoteMembers = this.remoteMembers().filter((member) => !localUserIds[member.id] && !priorityProfilesIds[member.id]); + const remoteMembers = this.remoteMembers().filter((member: CreatedProfile) => !localUserIds[member.id] && !priorityProfilesIds[member.id]); // comparator which prioritises users with usernames starting with search term - const orderUsers = (a, b) => { + const orderUsers = (a: CreatedProfile, b: CreatedProfile) => { const aStartsWith = a.username.startsWith(this.latestPrefix); const bStartsWith = b.username.startsWith(this.latestPrefix); @@ -263,7 +319,7 @@ export default class AtMentionProvider extends Provider { // handle groups const localGroups = this.localGroups(); - const localGroupIds = {}; + const localGroupIds: Record = {}; localGroups.forEach((group) => { localGroupIds[group.id] = true; }); @@ -271,7 +327,7 @@ export default class AtMentionProvider extends Provider { const remoteGroups = this.remoteGroups().filter((group) => !localGroupIds[group.id]); // comparator which prioritises users with usernames starting with search term - const orderGroups = (a, b) => { + const orderGroups = (a: CreatedGroup, b: CreatedGroup) => { const aStartsWith = a.name.startsWith(this.latestPrefix); const bStartsWith = b.name.startsWith(this.latestPrefix); @@ -294,11 +350,11 @@ export default class AtMentionProvider extends Provider { filter((member) => !localUserIds[member.id]). sort(orderUsers); - return priorityProfiles.concat(localAndRemoteMembers).concat(localAndRemoteGroups).concat(specialMentions).concat(remoteNonMembers); + return [...priorityProfiles, ...localAndRemoteMembers, ...localAndRemoteGroups, ...specialMentions, ...remoteNonMembers]; } // updateMatches invokes the resultCallback with the metadata for rendering at mentions - updateMatches(resultCallback, items) { + updateMatches(resultCallback: ResultsCallback, items: any[]) { if (items.length === 0) { this.lastPrefixWithNoResults = this.latestPrefix; } else if (this.lastPrefixWithNoResults === this.latestPrefix) { @@ -321,7 +377,7 @@ export default class AtMentionProvider extends Provider { }); } - handlePretextChanged(pretext, resultCallback) { + handlePretextChanged(pretext: string, resultCallback: ResultsCallback) { const captured = regexForAtMention.exec(pretext.toLowerCase()); if (!captured) { return false; @@ -342,15 +398,15 @@ export default class AtMentionProvider extends Provider { this.updateMatches(resultCallback, this.items()); // If we haven't gotten server-side results in 500 ms, add the loading indicator. - let showLoadingIndicator = setTimeout(() => { + let showLoadingIndicator: NodeJS.Timeout | null = setTimeout(() => { if (this.shouldCancelDispatch(prefix)) { return; } - this.updateMatches(resultCallback, this.items().concat([{ + this.updateMatches(resultCallback, [...this.items(), ...[{ type: Constants.MENTION_MORE_MEMBERS, loading: true, - }])); + }]]); showLoadingIndicator = null; }, 500); @@ -375,12 +431,12 @@ export default class AtMentionProvider extends Provider { return true; } - handleCompleteWord(term) { + handleCompleteWord(term: string) { this.lastCompletedWord = term; this.lastPrefixWithNoResults = ''; } - createFromProfile(profile, type) { + createFromProfile(profile: UserProfile | UserProfileWithLastViewAt, type: string): CreatedProfile { if (profile.id === this.currentUserId) { return { type, @@ -395,7 +451,7 @@ export default class AtMentionProvider extends Provider { }; } - createFromGroup(group, type) { + createFromGroup(group: Group, type: string): CreatedGroup { return { type, ...group, diff --git a/webapp/channels/src/components/suggestion/at_mention_provider/index.js b/webapp/channels/src/components/suggestion/at_mention_provider/index.js index a3355587c1..ef4f21d433 100644 --- a/webapp/channels/src/components/suggestion/at_mention_provider/index.js +++ b/webapp/channels/src/components/suggestion/at_mention_provider/index.js @@ -1,6 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import AtMentionProvider from './at_mention_provider.jsx'; +import AtMentionProvider from './at_mention_provider'; export default AtMentionProvider; diff --git a/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.test.jsx b/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.test.jsx index 98e1b0346b..a59467b5cc 100644 --- a/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.test.jsx +++ b/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.test.jsx @@ -4,7 +4,7 @@ import {shallow, mount} from 'enzyme'; import React from 'react'; -import AtMentionProvider from 'components/suggestion/at_mention_provider/at_mention_provider.jsx'; +import AtMentionProvider from 'components/suggestion/at_mention_provider/at_mention_provider'; import CommandProvider from 'components/suggestion/command_provider/command_provider'; import SuggestionBox from 'components/suggestion/suggestion_box/suggestion_box'; import SuggestionList from 'components/suggestion/suggestion_list'; diff --git a/webapp/channels/src/components/textbox.test.tsx b/webapp/channels/src/components/textbox.test.tsx index 77a1cedcbd..dee749c837 100644 --- a/webapp/channels/src/components/textbox.test.tsx +++ b/webapp/channels/src/components/textbox.test.tsx @@ -4,22 +4,20 @@ import {shallow} from 'enzyme'; import React from 'react'; -import Textbox from 'components/textbox/textbox'; +import Textbox, {type Props} from 'components/textbox/textbox'; + +import {TestHelper} from 'utils/test_helper'; describe('components/TextBox', () => { - const baseProps = { + const baseProps: Props = { channelId: 'channelId', rootId: 'rootId', currentUserId: 'currentUserId', currentTeamId: 'currentTeamId', - profilesInChannel: [ - {id: 'id1'}, - {id: 'id2'}, - ], delayChannelAutocomplete: false, autocompleteGroups: [ - {id: 'gid1'}, - {id: 'gid2'}, + TestHelper.getGroupMock({id: 'gid1'}), + TestHelper.getGroupMock({id: 'gid2'}), ], actions: { autocompleteUsersInChannel: jest.fn(), @@ -28,79 +26,89 @@ describe('components/TextBox', () => { }, useChannelMentions: true, tabIndex: 0, + id: '', + value: '', + onChange: jest.fn(), + onKeyPress: jest.fn(), + createMessage: '', + characterLimit: 0, }; test('should match snapshot with required props', () => { - function emptyFunction() {} //eslint-disable-line no-empty-function + const emptyFunction = jest.fn(); + const props = { + ...baseProps, + id: 'someid', + value: 'some test text', + onChange: emptyFunction, + onKeyPress: emptyFunction, + characterLimit: 400, + createMessage: 'placeholder text', + supportsCommands: false, + }; const wrapper = shallow( - , + , ); expect(wrapper).toMatchSnapshot(); }); test('should match snapshot with additional, optional props', () => { - function emptyFunction() {} //eslint-disable-line no-empty-function + const emptyFunction = jest.fn(); + const props = { + ...baseProps, + id: 'someid', + value: 'some test text', + onChange: emptyFunction, + onKeyPress: emptyFunction, + characterLimit: 4000, + createMessage: 'placeholder text', + supportsCommands: false, + rootId: 'root_id', + onComposition: jest.fn().mockReturnValue({}), + onHeightChange: jest.fn().mockReturnValue({}), + onKeyDown: jest.fn().mockReturnValue({}), + onMouseUp: jest.fn().mockReturnValue({}), + onKeyUp: jest.fn().mockReturnValue({}), + onBlur: jest.fn().mockReturnValue({}), + handlePostError: jest.fn().mockReturnValue({}), + suggestionListPosition: 'top' as const, + emojiEnabled: true, + disabled: true, + badConnection: true, + preview: true, + openWhenEmpty: true, + }; const wrapper = shallow( - {}} - onHeightChange={() => {}} - onKeyDown={() => {}} - onMouseUp={() => {}} - onKeyUp={() => {}} - onBlur={() => {}} - handlePostError={() => {}} - suggestionListPosition='top' - emojiEnabled={true} - disabled={true} - badConnection={true} - preview={true} - openWhenEmpty={true} - />, + , ); expect(wrapper).toMatchSnapshot(); }); test('should throw error when value is too long', () => { - function emptyFunction() {} //eslint-disable-line no-empty-function + const emptyFunction = jest.fn(); // this mock function should be called when the textbox value is too long let gotError = false; - function handlePostError(msg: React.ReactNode) { + const handlePostError = jest.fn((msg: React.ReactNode) => { gotError = msg !== null; - } + }); + + const props = { + ...baseProps, + id: 'someid', + value: 'some test text that exceeds char limit', + onChange: emptyFunction, + onKeyPress: emptyFunction, + characterLimit: 14, + createMessage: 'placeholder text', + supportsCommands: false, + handlePostError, + }; const wrapper = shallow( - , + , ); expect(gotError).toEqual(true); @@ -108,26 +116,28 @@ describe('components/TextBox', () => { }); test('should throw error when new property is too long', () => { - function emptyFunction() {} //eslint-disable-line no-empty-function + const emptyFunction = jest.fn(); // this mock function should be called when the textbox value is too long let gotError = false; - function handlePostError(msg: React.ReactNode) { + const handlePostError = jest.fn((msg: React.ReactNode) => { gotError = msg !== null; - } + }); + + const props = { + ...baseProps, + id: 'someid', + value: 'some test text', + onChange: emptyFunction, + onKeyPress: emptyFunction, + characterLimit: 14, + createMessage: 'placeholder text', + supportsCommands: false, + handlePostError, + }; const wrapper = shallow( - , + , ); wrapper.setProps({value: 'some test text that exceeds char limit'}); diff --git a/webapp/channels/src/components/textbox/textbox.tsx b/webapp/channels/src/components/textbox/textbox.tsx index 8b11334fed..7ada967ea2 100644 --- a/webapp/channels/src/components/textbox/textbox.tsx +++ b/webapp/channels/src/components/textbox/textbox.tsx @@ -7,6 +7,7 @@ import type {ChangeEvent, ElementType, FocusEvent, KeyboardEvent, MouseEvent} fr import {FormattedMessage} from 'react-intl'; import type {Channel} from '@mattermost/types/channels'; +import type {Group} from '@mattermost/types/groups'; import type {UserProfile} from '@mattermost/types/users'; import type {ActionResult} from 'mattermost-redux/types/actions'; @@ -59,7 +60,7 @@ export type Props = { currentUserId: string; currentTeamId: string; preview?: boolean; - autocompleteGroups: Array<{ id: string }> | null; + autocompleteGroups: Group[] | null; delayChannelAutocomplete: boolean; actions: { autocompleteUsersInChannel: (prefix: string, channelId: string) => Promise; diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts index 03ff4a85ba..f92c370f93 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts @@ -41,7 +41,7 @@ import { export {getCurrentUser, getCurrentUserId, getUsers}; -type Filters = { +export type Filters = { role?: string; inactive?: boolean; active?: boolean;