MM-46494: Add filter options to browse channel modal (#24099)

This commit is contained in:
Sinan Sonmez (Chaush) 2023-08-31 06:07:41 +03:00 committed by GitHub
parent c9d49536f8
commit 53fc44e13a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 719 additions and 286 deletions

View File

@ -16,6 +16,7 @@ describe('Leave an archived channel', () => {
let testTeam;
let offTopicUrl;
const channelType = {
all: 'Channel Type: All',
public: 'Channel Type: Public',
archived: 'Channel Type: Archived',
};
@ -98,19 +99,18 @@ describe('Leave an archived channel', () => {
cy.get('#showMoreChannels').click();
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// # Click on dropdown
cy.findByText(channelType.public).should('be.visible').click();
cy.get('#browseChannelsModal').should('be.visible');
// # Click archived channels
cy.findByText('Archived Channels').click();
// # Click on dropdown
cy.findByText(channelType.all).should('be.visible').click();
// # Modal should contain created channel
cy.get('#moreChannelsList').should('contain', channel.display_name);
});
// # Click archived channels
cy.findByText('Archived channels').click();
cy.get('body').typeWithForce('{esc}');
// # Modal should contain created channel
cy.get('#moreChannelsList').should('contain', channel.display_name);
});
cy.get('body').typeWithForce('{esc}');
});
it('MM-T1699 - Browse Channels for all channel types shows archived channels option', () => {
@ -146,12 +146,12 @@ describe('Leave an archived channel', () => {
cy.get('#showMoreChannels').click();
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// # Public channel list opens by default
cy.findByText(channelType.public).should('be.visible').click();
cy.get('#browseChannelsModal').should('be.visible').then(() => {
// # All channel list opens by default
cy.findByText(channelType.all).should('be.visible').click();
// # Click on archived channels
cy.findByText('Archived Channels').click();
cy.findByText('Archived channels').click();
// # Channel list should contain newly created channels
cy.get('#moreChannelsList').should('contain', archivedPrivateChannel.name);
@ -199,12 +199,12 @@ describe('Leave an archived channel', () => {
cy.get('#showMoreChannels').click();
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// # Public channels are shown by default
cy.findByText(channelType.public).should('be.visible').click();
cy.get('#browseChannelsModal').should('be.visible').then(() => {
// # All channels are shown by default
cy.findByText(channelType.all).should('be.visible').click();
// # Go to archived channels
cy.findByText('Archived Channels').click();
cy.findByText('Archived channels').click();
// # Channel list should contain both archived public channels
cy.get('#moreChannelsList').should('contain', archivedPublicChannel1.display_name);
@ -253,12 +253,12 @@ describe('Leave an archived channel', () => {
cy.get('#showMoreChannels').click();
// # More channels modal opens
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// # Show public channels is visible by default
cy.findByText(channelType.public).should('be.visible').click();
cy.get('#browseChannelsModal').should('be.visible').then(() => {
// # Show all channels is visible by default
cy.findByText(channelType.all).should('be.visible').click();
// # Go to archived channels
cy.findByText('Archived Channels').click();
cy.findByText('Archived channels').click();
// # Channel list should contain only the private channel user is a member of
cy.get('#moreChannelsList').should('contain', archivedPrivateChannel1.name);
@ -287,12 +287,12 @@ describe('Leave an archived channel', () => {
// # Click on browse channels
cy.get('#showMoreChannels').click();
// # More channels modal opens and lands on public channels
cy.get('#browseChannelsModal').should('be.visible').within(() => {
cy.findByText(channelType.public).should('be.visible').click();
// # More channels modal opens and lands on all channels
cy.get('#browseChannelsModal').should('be.visible').then(() => {
cy.findByText(channelType.all).should('be.visible').click();
// # Go to archived channels
cy.findByText('Archived Channels').click();
cy.findByText('Archived channels').click();
// # More channels list should contain the archived channel
cy.get('#moreChannelsList').should('contain', archivedChannel.display_name);
@ -323,7 +323,8 @@ describe('Leave an archived channel', () => {
cy.get('#showMoreChannels').click();
// # Modal should not contain the created channel
cy.get('#channelsMoreDropdown').should('not.exist');
cy.findByText(channelType.all).should('be.visible').click();
cy.findByText('Archived channels').should('not.exist');
cy.get('#moreChannelsList').should('not.contain', channel.name);
});
cy.get('body').typeWithForce('{esc}');

View File

@ -15,6 +15,7 @@ import * as TIMEOUTS from '../../../fixtures/timeouts';
import {createPrivateChannel} from '../enterprise/elasticsearch_autocomplete/helpers';
const channelType = {
all: 'Channel Type: All',
public: 'Channel Type: Public',
archived: 'Channel Type: Archived',
};
@ -69,8 +70,8 @@ describe('Channels', () => {
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// * Dropdown should be visible, defaulting to "Public Channels"
cy.get('#channelsMoreDropdown').should('be.visible').and('contain', channelType.public).wait(TIMEOUTS.HALF_SEC);
// * Dropdown should be visible, defaulting to "All Channels"
cy.get('#menuWrapper').should('be.visible').and('contain', channelType.all).wait(TIMEOUTS.HALF_SEC);
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1).within(() => {
@ -111,27 +112,27 @@ describe('Channels', () => {
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// # CLick dropdown to open selection
cy.get('#channelsMoreDropdown').should('be.visible').click().within((el) => {
// # Click on archived channels item
cy.findByText('Archived Channels').should('be.visible').click();
cy.get('#browseChannelsModal').should('be.visible');
// * Channel test should be visible as an archived channel in the list
cy.wrap(el).should('contain', channelType.archived);
});
// # CLick dropdown to open selection
cy.get('#menuWrapper').should('be.visible').click();
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
cy.get('#moreChannelsList').children().should('have.length', 1).within(() => {
cy.findByText(testChannel.display_name).should('be.visible');
});
cy.get('#searchChannelsTextbox').clear();
// # Click on archived channels item
cy.findByText('Archived channels').should('be.visible').click();
// * Test channel should be visible as a archived channel in the list
cy.get('#moreChannelsList').should('be.visible').within(() => {
// # Click to view archived channel
cy.findByText(testChannel.display_name).scrollIntoView().should('be.visible').click();
});
// * Menu text should be updated to reflect the selection
cy.get('#menuWrapper').should('contain', channelType.archived);
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
cy.get('#moreChannelsList').children().should('have.length', 1).within(() => {
cy.findByText(testChannel.display_name).should('be.visible');
});
cy.get('#searchChannelsTextbox').clear();
// * Test channel should be visible as a archived channel in the list
cy.get('#moreChannelsList').should('be.visible').within(() => {
// # Click to view archived channel
cy.findByText(testChannel.display_name).scrollIntoView().should('be.visible').click();
});
// * Assert that channel is archived and new messages can't be posted.
@ -177,7 +178,7 @@ describe('Channels', () => {
});
});
it('MM-T1702 Search works when changing public/archived options in the dropdown', () => {
it('MM-T1702 Search works when changing public/all options in the dropdown', () => {
cy.apiAdminLogin();
cy.apiUpdateConfig({
TeamSettings: {
@ -231,32 +232,34 @@ describe('Channels', () => {
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
// * Dropdown should be visible, defaulting to "Public Channels"
cy.get('#channelsMoreDropdown').should('be.visible').within((el) => {
cy.wrap(el).should('contain', channelType.public);
// * Dropdown should be visible, defaulting to "All channels"
cy.get('#menuWrapper').should('be.visible').within((el) => {
cy.wrap(el).should('contain', channelType.all);
});
// * Users should be able to type and search
cy.get('#searchChannelsTextbox').should('be.visible').type('iv').wait(TIMEOUTS.HALF_SEC);
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1).within(() => {
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
cy.get('#moreChannelsList').should('be.visible').within(() => {
cy.findByText(newChannel.display_name).should('be.visible');
});
cy.get('#browseChannelsModal').should('be.visible').within(() => {
// * Users should be able to switch to "Archived Channels" list
cy.get('#channelsMoreDropdown').should('be.visible').and('contain', channelType.public).click().within((el) => {
// # Click on archived channels item
cy.findByText('Archived Channels').should('be.visible').click();
cy.get('#browseChannelsModal').should('be.visible');
// * Modal should show the archived channels list
cy.wrap(el).should('contain', channelType.archived);
}).wait(TIMEOUTS.HALF_SEC);
cy.get('#searchChannelsTextbox').clear();
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
cy.get('#moreChannelsList').within(() => {
cy.findByText(testArchivedChannel.display_name).should('be.visible');
cy.findByText(testPrivateArchivedChannel.display_name).should('be.visible');
});
// * Users should be able to switch to "Archived Channels" list
cy.get('#menuWrapper').should('be.visible').and('contain', channelType.all).click().wait(TIMEOUTS.HALF_SEC);
// # Click on archived channels item
cy.findByText('Archived channels').should('be.visible').click();
// * Modal menu should be updated accordingly
cy.get('#menuWrapper').should('contain', channelType.archived);
cy.get('#searchChannelsTextbox').clear();
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
cy.get('#moreChannelsList').within(() => {
cy.findByText(testArchivedChannel.display_name).should('be.visible');
cy.findByText(testPrivateArchivedChannel.display_name).should('be.visible');
});
});
});
@ -287,9 +290,10 @@ function verifyBrowseChannelsModal(isEnabled) {
// * Verify that the browse channels modal is open and with or without option to view archived channels
cy.get('#browseChannelsModal').should('be.visible').within(() => {
if (isEnabled) {
cy.get('#channelsMoreDropdown').should('be.visible').and('have.text', channelType.public);
cy.get('#menuWrapper').should('be.visible').and('have.text', channelType.all);
} else {
cy.get('#channelsMoreDropdown').should('not.exist');
cy.get('#menuWrapper').click();
cy.findByText('Archived channels').should('not.exist');
}
});
}

View File

@ -14,7 +14,7 @@ function verifyNoChannelToJoinMessage(isVisible) {
cy.findByText('No public channels').should(isVisible ? 'be.visible' : 'not.exist');
}
describe('more public channels', () => {
describe('browse public channels', () => {
let testUser;
let otherUser;
let testTeam;
@ -41,7 +41,7 @@ describe('more public channels', () => {
});
});
it('MM-T1664 Channels do not disappear from More Channels modal', () => {
it('MM-T1664 Channels do not disappear from Browse Channels modal', () => {
// # Login as other user
cy.apiLogin(otherUser);
@ -51,8 +51,14 @@ describe('more public channels', () => {
// # Go to LHS and click 'Browse channels'
cy.uiBrowseOrCreateChannel('Browse channels').click();
// * Assert that the moreChannelsModel is visible
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').within(() => {
// * Assert that the browse channel modal is visible
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {
// # Click on dropdown
cy.findByText('Channel Type: All').should('be.visible').click();
// # Click archived channels
cy.findByText('Public channels').should('be.visible').click();
// # Click hide joined checkbox if not already checked
cy.findByText('Hide Joined').should('be.visible').then(($checkbox) => {
if (!$checkbox.prop('checked')) {
@ -92,7 +98,13 @@ describe('more public channels', () => {
cy.uiBrowseOrCreateChannel('Browse channels').click();
// * Assert the moreChannelsModel is visible
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').within(() => {
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {
// # Click on dropdown
cy.findByText('Channel Type: All').should('be.visible').click();
// # Click archived channels
cy.findByText('Public channels').should('be.visible').click();
// * Assert that the "No more channels to join" message is visible
verifyNoChannelToJoinMessage(true);
});

View File

@ -86,7 +86,7 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
});
});
it('MM-T1467 Accessibility Support in More Channels Dialog screen', () => {
it('MM-T1467 Accessibility Support in Browse Channels Dialog screen', () => {
function getChannelAriaLabel(channel) {
return channel.display_name.toLowerCase() + ', ' + channel.purpose.toLowerCase();
}
@ -117,8 +117,8 @@ describe('Verify Accessibility Support in Modals & Dialogs', () => {
// # Hide already joined channels
cy.findByText('Hide Joined').click();
// # Focus on the Create Channel button and TAB three time
cy.get('#createNewChannelButton').focus().tab().tab().tab();
// # Focus on the Create Channel button and TAB four time
cy.get('#createNewChannelButton').focus().tab().tab().tab().tab();
// * Verify channel name is highlighted and reader reads the channel name and channel description
cy.get('#moreChannelsList').within(() => {

View File

@ -232,9 +232,9 @@ context('ldap', () => {
cy.visit(`/${testTeam.name}`);
cy.uiBrowseOrCreateChannel('Browse channels').click();
// * Search private channel name and make sure it isn't there in public channel directory
// * Search private channel name and make sure it is still visible
cy.get('#searchChannelsTextbox').type(testChannel.display_name);
cy.get('#moreChannelsList').should('include.text', 'No results for');
cy.get('#moreChannelsList').should('include.text', testChannel.display_name);
});
it('MM-T2629 - Private to public - More....', () => {

View File

@ -13,7 +13,7 @@
"@guyplusplus/turndown-plugin-gfm": "1.0.7",
"@mattermost/client": "*",
"@mattermost/compass-components": "^0.2.12",
"@mattermost/compass-icons": "0.1.34",
"@mattermost/compass-icons": "0.1.37",
"@mattermost/types": "*",
"@mui/base": "5.0.0-alpha.127",
"@mui/material": "5.11.16",

View File

@ -48,6 +48,89 @@ exports[`components/SearchableChannelList should match init snapshot 1`] = `
<div
id="modalPreferenceContainer"
>
<Menu
menu={
Object {
"aria-label": "Browse channels",
"id": "browseChannelsDropdown",
}
}
menuButton={
Object {
"children": <React.Fragment>
<Memo(MemoizedFormattedMessage)
defaultMessage="Channel Type: All"
id="more_channels.show_all_channels"
/>
<ChevronDownIcon
color="rgba(var(--center-channel-color-rgb), 0.64)"
size={16}
/>
</React.Fragment>,
"id": "menuWrapper",
}
}
>
<MenuItem
aria-label="All channel types"
id="channelsMoreDropdownAll"
key="channelsMoreDropdownAll"
labels={
<Memo(MemoizedFormattedMessage)
defaultMessage="All channel types"
id="suggestion.all"
/>
}
leadingElement={
<GlobeCheckedIcon
size={16}
/>
}
onClick={[Function]}
trailingElements={
<CheckIcon
color="var(--button-bg)"
size={18}
/>
}
/>
<MenuItem
aria-label="Public channels"
id="channelsMoreDropdownPublic"
key="channelsMoreDropdownPublic"
labels={
<Memo(MemoizedFormattedMessage)
defaultMessage="Public channels"
id="suggestion.public"
/>
}
leadingElement={
<GlobeIcon
size={16}
/>
}
onClick={[Function]}
trailingElements={null}
/>
<MenuItem
aria-label="Private channels"
id="channelsMoreDropdownPrivate"
key="channelsMoreDropdownPrivate"
labels={
<Memo(MemoizedFormattedMessage)
defaultMessage="Private channels"
id="suggestion.private"
/>
}
leadingElement={
<LockOutlineIcon
size={16}
/>
}
onClick={[Function]}
trailingElements={null}
/>
</Menu>
<div
id="hideJoinedPreferenceCheckbox"
onClick={[Function]}

View File

@ -16,7 +16,7 @@ exports[`components/BrowseChannels should match snapshot and state 1`] = `
"create_public_channel",
]
}
teamId="team_id"
teamId="team_1"
>
<button
aria-label="Create New Channel"
@ -45,8 +45,26 @@ exports[`components/BrowseChannels should match snapshot and state 1`] = `
>
<SearchableChannelList
canShowArchivedChannels={true}
changeFilter={[Function]}
channels={
Array [
Object {
"create_at": 0,
"creator_id": "id",
"delete_at": 0,
"display_name": "channel-3",
"group_constrained": false,
"header": "channel-3-header",
"id": "channel_id_3",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "channel-3",
"purpose": "channel-3-purpose",
"scheme_id": "id",
"team_id": "team_1",
"type": "P",
"update_at": 0,
},
Object {
"create_at": 0,
"creator_id": "id",
@ -68,11 +86,37 @@ exports[`components/BrowseChannels should match snapshot and state 1`] = `
}
channelsPerPage={50}
closeModal={[MockFunction]}
filter="All"
handleJoin={[Function]}
hideJoinedChannelsPreference={[Function]}
isSearch={false}
loading={false}
myChannelMemberships={Object {}}
myChannelMemberships={
Object {
"channel-id-3": Object {
"channel_id": "channel-id-3",
"last_update_at": 0,
"last_viewed_at": 0,
"mention_count": 0,
"mention_count_root": 0,
"msg_count": 0,
"msg_count_root": 0,
"notify_props": Object {
"channel_auto_follow_threads": "off",
"desktop": "default",
"email": "default",
"ignore_channel_mentions": "default",
"mark_unread": "all",
"push": "default",
},
"roles": "channel_user",
"scheme_admin": false,
"scheme_user": true,
"urgent_mention_count": 0,
"user_id": "user-1",
},
}
}
nextPage={[Function]}
noResultsText={
<React.Fragment>
@ -90,7 +134,7 @@ exports[`components/BrowseChannels should match snapshot and state 1`] = `
"create_public_channel",
]
}
teamId="team_id"
teamId="team_1"
>
<button
aria-label="Create New Channel"
@ -112,8 +156,6 @@ exports[`components/BrowseChannels should match snapshot and state 1`] = `
}
rememberHideJoinedChannelsChecked={false}
search={[Function]}
shouldShowArchivedChannels={false}
toggleArchivedChannels={[Function]}
/>
</GenericModal>
`;

View File

@ -58,23 +58,6 @@
line-height: 16px;
}
.MenuItem__primary-text {
width: 100%;
color: var(--center-channel-color);
font-size: 14px;
font-weight: 400;
line-height: 20px;
svg {
margin-left: auto;
}
}
.Menu__content {
border-color: rgba(var(--center-channel-color-rgb), 0.16);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
#channelCountLabel {
color: var(--center-channel-color);
font-size: 12px;
@ -100,32 +83,25 @@
cursor: pointer;
}
#channelsMoreDropdown {
margin: 0 8px;
}
#menuWrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 4px 6px 4px 8px;
border: none;
margin-right: 10px;
background-color: unset;
&[aria-expanded='true'] {
background-color: rgba(var(--button-bg-rgb), 0.12);
border-radius: 4px;
}
}
.MenuWrapper:hover {
#menuWrapper:hover {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
border-radius: 4px;
}
.MenuWrapper--open {
background-color: rgba(var(--button-bg-rgb), 0.12);
border-radius: 4px;
&:hover {
background-color: rgba(var(--button-bg-rgb), 0.12);
}
}
}
}
@ -295,3 +271,11 @@
padding: 10px 20px;
}
}
#browseChannelsDropdown {
.label-elements {
padding: 2px 4px;
font-size: 14px;
line-height: 20px;
}
}

View File

@ -7,7 +7,7 @@ import {shallow} from 'enzyme';
import {ActionResult} from 'mattermost-redux/types/actions';
import {Channel} from '@mattermost/types/channels';
import BrowseChannels, {Props} from 'components/browse_channels/browse_channels';
import BrowseChannels, {Filter, Props} from 'components/browse_channels/browse_channels';
import SearchableChannelList from 'components/searchable_channel_list';
import {getHistory} from 'utils/browser_history';
@ -20,25 +20,53 @@ describe('components/BrowseChannels', () => {
data: [{
id: 'channel-id-1',
name: 'channel-name-1',
team_id: 'team_1',
display_name: 'Channel 1',
delete_at: 0,
type: 'O',
}, {
id: 'channel-id-2',
name: 'archived-channel',
team_id: 'team_1',
display_name: 'Archived',
delete_at: 123,
type: 'O',
}, {
id: 'channel-id-3',
name: 'private-channel',
team_id: 'team_1',
display_name: 'Private',
delete_at: 0,
type: 'P',
}, {
id: 'channel-id-4',
name: 'private-channel-not-member',
team_id: 'team_1',
display_name: 'Private Not Member',
delete_at: 0,
type: 'P',
}],
};
const archivedChannel = TestHelper.getChannelMock({
id: 'channel_id_2',
team_id: 'channel_team_2',
team_id: 'team_1',
display_name: 'channel-2',
name: 'channel-2',
header: 'channel-2-header',
purpose: 'channel-2-purpose',
});
const privateChannel = TestHelper.getChannelMock({
id: 'channel_id_3',
team_id: 'team_1',
display_name: 'channel-3',
name: 'channel-3',
header: 'channel-3-header',
purpose: 'channel-3-purpose',
type: 'P',
});
const channelActions = {
joinChannelAction: (userId: string, teamId: string, channelId: string): Promise<ActionResult> => {
return new Promise((resolve) => {
@ -53,7 +81,7 @@ describe('components/BrowseChannels', () => {
return resolve({data: true});
});
},
searchMoreChannels: (term: string): Promise<ActionResult> => {
searchAllChannels: (term: string): Promise<ActionResult> => {
return new Promise((resolve) => {
if (term === 'fail') {
return resolve({
@ -85,18 +113,24 @@ describe('components/BrowseChannels', () => {
const baseProps: Props = {
channels: [TestHelper.getChannelMock({})],
archivedChannels: [archivedChannel],
privateChannels: [privateChannel],
currentUserId: 'user-1',
teamId: 'team_id',
teamId: 'team_1',
teamName: 'team_name',
channelsRequestStarted: false,
canShowArchivedChannels: true,
shouldHideJoinedChannels: false,
myChannelMemberships: {},
myChannelMemberships: {
'channel-id-3': TestHelper.getChannelMembershipMock({
channel_id: 'channel-id-3',
user_id: 'user-1',
}),
},
actions: {
getChannels: jest.fn(channelActions.getChannels),
getArchivedChannels: jest.fn(channelActions.getArchivedChannels),
joinChannel: jest.fn(channelActions.joinChannelAction),
searchMoreChannels: jest.fn(channelActions.searchMoreChannels),
searchAllChannels: jest.fn(channelActions.searchAllChannels),
openModal: jest.fn(),
closeModal: jest.fn(),
closeRightHandSide: jest.fn(),
@ -112,7 +146,6 @@ describe('components/BrowseChannels', () => {
expect(wrapper).toMatchSnapshot();
expect(wrapper.state('searchedChannels')).toEqual([]);
expect(wrapper.state('shouldShowArchivedChannels')).toEqual(false);
expect(wrapper.state('search')).toEqual(false);
expect(wrapper.state('serverError')).toBeNull();
expect(wrapper.state('searching')).toEqual(false);
@ -257,8 +290,8 @@ describe('components/BrowseChannels', () => {
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 100);
jest.runOnlyPendingTimers();
expect(wrapper.instance().props.actions.searchMoreChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchMoreChannels).toHaveBeenCalledWith('fail', false, false);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledWith('fail', {include_deleted: true, nonAdminSearch: true, team_ids: ['team_1']});
process.nextTick(() => {
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(false);
@ -284,12 +317,12 @@ describe('components/BrowseChannels', () => {
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 100);
jest.runOnlyPendingTimers();
expect(wrapper.instance().props.actions.searchMoreChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchMoreChannels).toHaveBeenCalledWith('channel', false, false);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledWith('channel', {include_deleted: true, nonAdminSearch: true, team_ids: ['team_1']});
process.nextTick(() => {
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(false);
expect(wrapper.state('searchedChannels')).toEqual([searchResults.data[0]]);
expect(wrapper.state('searchedChannels')).toEqual([searchResults.data[0], searchResults.data[1], searchResults.data[2]]);
done();
});
});
@ -300,7 +333,6 @@ describe('components/BrowseChannels', () => {
);
wrapper.instance().onChange = jest.fn();
wrapper.instance().setState({shouldShowArchivedChannels: true});
wrapper.instance().search('channel');
expect(clearTimeout).toHaveBeenCalledTimes(1);
expect(wrapper.instance().onChange).not.toHaveBeenCalled();
@ -309,10 +341,11 @@ describe('components/BrowseChannels', () => {
expect(wrapper.instance().searchTimeoutId).not.toEqual('');
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 100);
wrapper.instance().changeFilter(Filter.Archived);
jest.runOnlyPendingTimers();
expect(wrapper.instance().props.actions.searchMoreChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchMoreChannels).toHaveBeenCalledWith('channel', true, false);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledWith('channel', {include_deleted: true, nonAdminSearch: true, team_ids: ['team_1']});
process.nextTick(() => {
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(false);
@ -320,4 +353,138 @@ describe('components/BrowseChannels', () => {
done();
});
});
test('should perform search on private channels and set the correct state', (done) => {
const wrapper = shallow<BrowseChannels>(
<BrowseChannels {...baseProps}/>,
);
wrapper.instance().onChange = jest.fn();
wrapper.instance().search('channel');
expect(clearTimeout).toHaveBeenCalledTimes(1);
expect(wrapper.instance().onChange).not.toHaveBeenCalled();
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(true);
expect(wrapper.instance().searchTimeoutId).not.toEqual('');
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 100);
wrapper.instance().changeFilter(Filter.Private);
jest.runOnlyPendingTimers();
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledWith('channel', {include_deleted: true, nonAdminSearch: true, team_ids: ['team_1']});
process.nextTick(() => {
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(false);
expect(wrapper.state('searchedChannels')).toEqual([searchResults.data[2]]);
done();
});
});
test('should perform search on public channels and set the correct state', (done) => {
const wrapper = shallow<BrowseChannels>(
<BrowseChannels {...baseProps}/>,
);
wrapper.instance().onChange = jest.fn();
wrapper.instance().search('channel');
expect(clearTimeout).toHaveBeenCalledTimes(1);
expect(wrapper.instance().onChange).not.toHaveBeenCalled();
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(true);
expect(wrapper.instance().searchTimeoutId).not.toEqual('');
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 100);
wrapper.instance().changeFilter(Filter.Public);
jest.runOnlyPendingTimers();
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledWith('channel', {include_deleted: true, nonAdminSearch: true, team_ids: ['team_1']});
process.nextTick(() => {
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(false);
expect(wrapper.state('searchedChannels')).toEqual([searchResults.data[0]]);
done();
});
});
test('should perform search on all channels and set the correct state when shouldHideJoinedChannels is true', (done) => {
const props = {
...baseProps,
shouldHideJoinedChannels: true,
};
const wrapper = shallow<BrowseChannels>(
<BrowseChannels {...props}/>,
);
wrapper.instance().onChange = jest.fn();
wrapper.instance().search('channel');
expect(clearTimeout).toHaveBeenCalledTimes(1);
expect(wrapper.instance().onChange).not.toHaveBeenCalled();
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(true);
expect(wrapper.instance().searchTimeoutId).not.toEqual('');
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 100);
wrapper.instance().changeFilter(Filter.All);
jest.runOnlyPendingTimers();
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledWith('channel', {include_deleted: true, nonAdminSearch: true, team_ids: ['team_1']});
process.nextTick(() => {
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(false);
expect(wrapper.state('searchedChannels')).toEqual([searchResults.data[0], searchResults.data[1]]);
done();
});
});
test('should perform search on all channels and set the correct state when shouldHideJoinedChannels is true and filter is private', (done) => {
const props = {
...baseProps,
shouldHideJoinedChannels: true,
};
const wrapper = shallow<BrowseChannels>(
<BrowseChannels {...props}/>,
);
wrapper.instance().onChange = jest.fn();
wrapper.instance().search('channel');
expect(clearTimeout).toHaveBeenCalledTimes(1);
expect(wrapper.instance().onChange).not.toHaveBeenCalled();
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(true);
expect(wrapper.instance().searchTimeoutId).not.toEqual('');
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 100);
wrapper.instance().changeFilter(Filter.Private);
jest.runOnlyPendingTimers();
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledTimes(1);
expect(wrapper.instance().props.actions.searchAllChannels).toHaveBeenCalledWith('channel', {include_deleted: true, nonAdminSearch: true, team_ids: ['team_1']});
process.nextTick(() => {
expect(wrapper.state('search')).toEqual(true);
expect(wrapper.state('searching')).toEqual(false);
expect(wrapper.state('searchedChannels')).toEqual([]);
done();
});
});
it('should perform search on all channels and should not show private channels that user is not a member of', (done) => {
const wrapper = shallow<BrowseChannels>(
<BrowseChannels {...baseProps}/>,
);
wrapper.setState({search: true, searching: true});
wrapper.instance().onChange = jest.fn();
wrapper.instance().search('channel');
jest.runOnlyPendingTimers();
process.nextTick(() => {
expect(wrapper.state('searchedChannels')).toEqual([searchResults.data[0], searchResults.data[1], searchResults.data[2]]);
done();
});
});
});

View File

@ -4,37 +4,46 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import Permissions from 'mattermost-redux/constants/permissions';
import {ActionResult} from 'mattermost-redux/types/actions';
import {RelationOneToOne} from '@mattermost/types/utilities';
import {Channel, ChannelMembership} from '@mattermost/types/channels';
import Permissions from 'mattermost-redux/constants/permissions';
import {Channel, ChannelMembership, ChannelSearchOpts} from '@mattermost/types/channels';
import {GenericModal} from '@mattermost/components';
import classNames from 'classnames';
import NewChannelModal from 'components/new_channel_modal/new_channel_modal';
import SearchableChannelList from 'components/searchable_channel_list';
import TeamPermissionGate from 'components/permissions_gates/team_permission_gate';
import LoadingScreen from 'components/loading_screen';
import {ModalData} from 'types/actions';
import {RhsState} from 'types/store/rhs';
import {getHistory} from 'utils/browser_history';
import {ModalIdentifiers, RHSStates, StoragePrefixes} from 'utils/constants';
import Constants, {ModalIdentifiers, RHSStates, StoragePrefixes} from 'utils/constants';
import {getRelativeChannelURL} from 'utils/url';
import {GenericModal} from '@mattermost/components';
import classNames from 'classnames';
import {localizeMessage} from 'utils/utils';
import LoadingScreen from 'components/loading_screen';
import './browse_channels.scss';
const CHANNELS_CHUNK_SIZE = 50;
const CHANNELS_PER_PAGE = 50;
const SEARCH_TIMEOUT_MILLISECONDS = 100;
export enum Filter {
All = 'All',
Public = 'Public',
Private = 'Private',
Archived = 'Archived',
}
export type FilterType = keyof typeof Filter;
type Actions = {
getChannels: (teamId: string, page: number, perPage: number) => Promise<ActionResult<Channel[], Error>>;
getArchivedChannels: (teamId: string, page: number, channelsPerPage: number) => Promise<ActionResult<Channel[], Error>>;
joinChannel: (currentUserId: string, teamId: string, channelId: string) => Promise<ActionResult>;
searchMoreChannels: (term: string, shouldShowArchivedChannels: boolean, shouldHideJoinedChannels: boolean) => Promise<ActionResult>;
searchAllChannels: (term: string, opts?: ChannelSearchOpts) => Promise<ActionResult<Channel[], Error>>;
openModal: <P>(modalData: ModalData<P>) => void;
closeModal: (modalId: string) => void;
@ -49,12 +58,12 @@ type Actions = {
export type Props = {
channels: Channel[];
archivedChannels: Channel[];
privateChannels: Channel[];
currentUserId: string;
teamId: string;
teamName: string;
channelsRequestStarted?: boolean;
canShowArchivedChannels?: boolean;
morePublicChannelsModalType?: string;
myChannelMemberships: RelationOneToOne<Channel, ChannelMembership>;
shouldHideJoinedChannels: boolean;
rhsState?: RhsState;
@ -65,7 +74,7 @@ export type Props = {
type State = {
loading: boolean;
shouldShowArchivedChannels: boolean;
filter: FilterType;
search: boolean;
searchedChannels: Channel[];
serverError: React.ReactNode | string;
@ -84,7 +93,7 @@ export default class BrowseChannels extends React.PureComponent<Props, State> {
this.state = {
loading: true,
shouldShowArchivedChannels: this.props.morePublicChannelsModalType === 'private',
filter: Filter.All,
search: false,
searchedChannels: [],
serverError: null,
@ -191,7 +200,7 @@ export default class BrowseChannels extends React.PureComponent<Props, State> {
const searchTimeoutId = window.setTimeout(
async () => {
try {
const {data} = await this.props.actions.searchMoreChannels(term, this.state.shouldShowArchivedChannels, this.props.shouldHideJoinedChannels);
const {data} = await this.props.actions.searchAllChannels(term, {team_ids: [this.props.teamId], nonAdminSearch: true, include_deleted: true});
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
@ -201,7 +210,7 @@ export default class BrowseChannels extends React.PureComponent<Props, State> {
if (channelIDsForMemberCount.length > 0) {
this.props.actions.getChannelsMemberCount(channelIDsForMemberCount);
}
this.setSearchResults(data);
this.setSearchResults(data.filter((channel) => channel.team_id === this.props.teamId));
} else {
this.setState({searchedChannels: [], searching: false});
}
@ -216,13 +225,27 @@ export default class BrowseChannels extends React.PureComponent<Props, State> {
};
setSearchResults = (channels: Channel[]) => {
this.setState({searchedChannels: this.state.shouldShowArchivedChannels ? channels.filter((c) => c.delete_at !== 0) : channels.filter((c) => c.delete_at === 0), searching: false});
// filter out private channels that the user is not a member of
let searchedChannels = channels.filter((c) => c.type !== Constants.PRIVATE_CHANNEL || this.isMemberOfChannel(c.id));
if (this.state.filter === Filter.Private) {
searchedChannels = channels.filter((c) => c.type === Constants.PRIVATE_CHANNEL && this.isMemberOfChannel(c.id));
}
if (this.state.filter === Filter.Public) {
searchedChannels = channels.filter((c) => c.type === Constants.OPEN_CHANNEL && c.delete_at === 0);
}
if (this.state.filter === Filter.Archived) {
searchedChannels = channels.filter((c) => c.delete_at !== 0);
}
if (this.props.shouldHideJoinedChannels) {
searchedChannels = this.getChannelsWithoutJoined(searchedChannels);
}
this.setState({searchedChannels, searching: false});
};
toggleArchivedChannels = (shouldShowArchivedChannels: boolean) => {
changeFilter = (filter: FilterType) => {
// search again when switching channels to update search results
this.search(this.state.searchTerm);
this.setState({shouldShowArchivedChannels});
this.setState({filter});
};
isMemberOfChannel(channelId: string) {
@ -235,36 +258,37 @@ export default class BrowseChannels extends React.PureComponent<Props, State> {
this.props.actions.setGlobalItem(StoragePrefixes.HIDE_JOINED_CHANNELS, shouldHideJoinedChannels.toString());
};
render() {
const {
channels,
archivedChannels,
teamId,
channelsRequestStarted,
shouldHideJoinedChannels,
} = this.props;
getChannelsWithoutJoined = (channelList: Channel[]) => channelList.filter((channel) => !this.isMemberOfChannel(channel.id));
const {
search,
searchedChannels,
serverError: serverErrorState,
searching,
shouldShowArchivedChannels,
} = this.state;
getActiveChannels = () => {
const {channels, archivedChannels, shouldHideJoinedChannels, privateChannels} = this.props;
const {search, searchedChannels, filter} = this.state;
const otherChannelsWithoutJoined = channels.filter((channel) => !this.isMemberOfChannel(channel.id));
const archivedChannelsWithoutJoined = archivedChannels.filter((channel) => !this.isMemberOfChannel(channel.id));
const allChannels = channels.concat(privateChannels).sort((a, b) => a.display_name.localeCompare(b.display_name));
const allChannelsWithoutJoined = this.getChannelsWithoutJoined(allChannels);
const publicChannelsWithoutJoined = this.getChannelsWithoutJoined(channels);
const archivedChannelsWithoutJoined = this.getChannelsWithoutJoined(archivedChannels);
const privateChannelsWithoutJoined = this.getChannelsWithoutJoined(privateChannels);
if (shouldShowArchivedChannels && shouldHideJoinedChannels) {
this.activeChannels = search ? searchedChannels : archivedChannelsWithoutJoined;
} else if (shouldShowArchivedChannels && !shouldHideJoinedChannels) {
this.activeChannels = search ? searchedChannels : archivedChannels;
} else if (!shouldShowArchivedChannels && shouldHideJoinedChannels) {
this.activeChannels = search ? searchedChannels : otherChannelsWithoutJoined;
} else {
this.activeChannels = search ? searchedChannels : channels;
const filterOptions = {
[Filter.All]: shouldHideJoinedChannels ? allChannelsWithoutJoined : allChannels,
[Filter.Archived]: shouldHideJoinedChannels ? archivedChannelsWithoutJoined : archivedChannels,
[Filter.Private]: shouldHideJoinedChannels ? privateChannelsWithoutJoined : privateChannels,
[Filter.Public]: shouldHideJoinedChannels ? publicChannelsWithoutJoined : channels,
};
if (search) {
return searchedChannels;
}
return filterOptions[filter] || filterOptions[Filter.All];
};
render() {
const {teamId, channelsRequestStarted, shouldHideJoinedChannels} = this.props;
const {search, serverError: serverErrorState, searching} = this.state;
this.activeChannels = this.getActiveChannels();
let serverError;
if (serverErrorState) {
serverError =
@ -318,8 +342,8 @@ export default class BrowseChannels extends React.PureComponent<Props, State> {
handleJoin={this.handleJoin}
noResultsText={noResultsText}
loading={search ? searching : channelsRequestStarted}
toggleArchivedChannels={this.toggleArchivedChannels}
shouldShowArchivedChannels={this.state.shouldShowArchivedChannels}
changeFilter={this.changeFilter}
filter={this.state.filter}
canShowArchivedChannels={this.props.canShowArchivedChannels}
myChannelMemberships={this.props.myChannelMemberships}
closeModal={this.props.actions.closeModal}

View File

@ -6,15 +6,14 @@ import {ActionCreatorsMapObject, bindActionCreators, Dispatch} from 'redux';
import {createSelector} from 'mattermost-redux/selectors/create_selector';
import {RequestStatus} from 'mattermost-redux/constants';
import {Channel} from '@mattermost/types/channels';
import {Channel, ChannelSearchOpts} from '@mattermost/types/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {Action, ActionResult} from 'mattermost-redux/types/actions';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getChannels, getArchivedChannels, joinChannel, getChannelsMemberCount} from 'mattermost-redux/actions/channels';
import {getChannels, getArchivedChannels, joinChannel, getChannelsMemberCount, searchAllChannels} from 'mattermost-redux/actions/channels';
import {getChannelsInCurrentTeam, getMyChannelMemberships, getChannelsMemberCount as getChannelsMemberCountSelector} from 'mattermost-redux/selectors/entities/channels';
import {searchMoreChannels} from 'actions/channel_actions';
import {openModal, closeModal} from 'actions/views/modals';
import {closeRightHandSide} from 'actions/views/rhs';
@ -40,6 +39,12 @@ const getArchivedOtherChannels = createSelector(
(channels: Channel[]) => channels && channels.filter((c) => c.delete_at !== 0),
);
const getPrivateChannelsSelector = createSelector(
'getPrivateChannelsSelector',
getChannelsInCurrentTeam,
(channels: Channel[]) => channels && channels.filter((c) => c.type === Constants.PRIVATE_CHANNEL),
);
function mapStateToProps(state: GlobalState) {
const team = getCurrentTeam(state) || {};
const getGlobalItem = makeGetGlobalItem(StoragePrefixes.HIDE_JOINED_CHANNELS, 'false');
@ -47,6 +52,7 @@ function mapStateToProps(state: GlobalState) {
return {
channels: getChannelsWithoutArchived(state) || [],
archivedChannels: getArchivedOtherChannels(state) || [],
privateChannels: getPrivateChannelsSelector(state) || [],
currentUserId: getCurrentUserId(state),
teamId: team.id,
teamName: team.name,
@ -63,8 +69,9 @@ function mapStateToProps(state: GlobalState) {
type Actions = {
getChannels: (teamId: string, page: number, perPage: number) => Promise<ActionResult<Channel[], Error>>;
getArchivedChannels: (teamId: string, page: number, channelsPerPage: number) => Promise<ActionResult<Channel[], Error>>;
getPrivateChannels: (teamId: string, page: number, channelsPerPage: number) => Promise<ActionResult<Channel[], Error>>;
joinChannel: (currentUserId: string, teamId: string, channelId: string) => Promise<ActionResult>;
searchMoreChannels: (term: string, shouldShowArchivedChannels: boolean) => Promise<ActionResult>;
searchAllChannels: (term: string, opts?: ChannelSearchOpts) => Promise<ActionResult<Channel[], Error>>;
openModal: <P>(modalData: ModalData<P>) => void;
closeModal: (modalId: string) => void;
setGlobalItem: (name: string, value: string) => void;
@ -78,7 +85,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
getChannels,
getArchivedChannels,
joinChannel,
searchMoreChannels,
searchAllChannels,
openModal,
closeModal,
setGlobalItem,

View File

@ -5,6 +5,7 @@ import React from 'react';
import {shallow} from 'enzyme';
import SearchableChannelList from 'components/searchable_channel_list';
import {Filter} from './browse_channels/browse_channels';
describe('components/SearchableChannelList', () => {
const baseProps = {
@ -18,11 +19,12 @@ describe('components/SearchableChannelList', () => {
toggleArchivedChannels: jest.fn(),
closeModal: jest.fn(),
hideJoinedChannelsPreference: jest.fn(),
changeFilter: jest.fn(),
myChannelMemberships: {},
shouldShowArchivedChannels: false,
canShowArchivedChannels: false,
rememberHideJoinedChannelsChecked: false,
noResultsText: <>{'no channel found'}</>,
filter: Filter.All,
};
test('should match init snapshot', () => {

View File

@ -4,7 +4,7 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {ArchiveOutlineIcon, CheckIcon, ChevronDownIcon, GlobeIcon, LockOutlineIcon, MagnifyIcon, AccountOutlineIcon} from '@mattermost/compass-icons/components';
import {ArchiveOutlineIcon, CheckIcon, ChevronDownIcon, GlobeIcon, LockOutlineIcon, MagnifyIcon, AccountOutlineIcon, GlobeCheckedIcon} from '@mattermost/compass-icons/components';
import {Channel, ChannelMembership} from '@mattermost/types/channels';
import {RelationOneToOne} from '@mattermost/types/utilities';
import {isPrivateChannel} from 'mattermost-redux/utils/channel_utils';
@ -17,6 +17,7 @@ import QuickInput from 'components/quick_input';
import CheckboxCheckedIcon from 'components/widgets/icons/checkbox_checked_icon';
import LocalizedInput from 'components/localized_input/localized_input';
import MagnifyingGlassSVG from 'components/common/svg_images_components/magnifying_glass_svg';
import * as Menu from 'components/menu';
import * as UserAgent from 'utils/user_agent';
import Constants, {ModalIdentifiers} from 'utils/constants';
@ -24,9 +25,8 @@ import {localizeMessage, localizeAndFormatMessage} from 'utils/utils';
import {isArchivedChannel} from 'utils/channel_utils';
import {t} from 'utils/i18n';
import MenuWrapper from './widgets/menu/menu_wrapper';
import Menu from './widgets/menu/menu';
import {isKeyPressed} from 'utils/keyboard';
import {Filter, FilterType} from './browse_channels/browse_channels';
const NEXT_BUTTON_TIMEOUT_MILLISECONDS = 500;
@ -38,8 +38,8 @@ type Props = {
search: (term: string) => void;
handleJoin: (channel: Channel, done: () => void) => void;
noResultsText: JSX.Element;
toggleArchivedChannels: (shouldShowArchivedChannels: boolean) => void;
shouldShowArchivedChannels: boolean;
changeFilter: (filter: FilterType) => void;
filter: FilterType;
myChannelMemberships: RelationOneToOne<Channel, ChannelMembership>;
closeModal: (modalId: string) => void;
hideJoinedChannelsPreference: (shouldHideJoinedChannels: boolean) => void;
@ -250,12 +250,6 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
handleClear = () => {
this.setState({channelSearchValue: ''}, () => this.doSearch());
};
toggleArchivedChannelsOn = () => {
this.props.toggleArchivedChannels(true);
};
toggleArchivedChannelsOff = () => {
this.props.toggleArchivedChannels(false);
};
handleChecked = () => {
// If it was checked, and now we're unchecking it, clear the preference
if (this.props.rememberHideJoinedChannelsChecked) {
@ -264,6 +258,84 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
this.props.hideJoinedChannelsPreference(true);
}
};
getEmptyStateMessage = () => {
if (this.state.channelSearchValue.length > 0) {
return (
<FormattedMessage
id='more_channels.noMore'
tagName='strong'
defaultMessage='No results for {text}'
values={{text: this.state.channelSearchValue}}
/>
);
}
switch (this.props.filter) {
case Filter.Archived:
return (
<FormattedMessage
id={'more_channels.noArchived'}
tagName='strong'
defaultMessage={'No archived channels'}
/>
);
case Filter.Private:
return (
<FormattedMessage
id={'more_channels.noPrivate'}
tagName='strong'
defaultMessage={'No private channels'}
/>
);
case Filter.Public:
return (
<FormattedMessage
id={'more_channels.noPublic'}
tagName='strong'
defaultMessage={'No public channels'}
/>
);
default:
return (
<FormattedMessage
id={'more_channels.noChannels'}
tagName='strong'
defaultMessage={'No channels'}
/>
);
}
};
getFilterLabel = () => {
switch (this.props.filter) {
case Filter.Archived:
return (
<FormattedMessage
id='more_channels.show_archived_channels'
defaultMessage='Channel Type: Archived'
/>
);
case Filter.Public:
return (
<FormattedMessage
id='more_channels.show_public_channels'
defaultMessage='Channel Type: Public'
/>
);
case Filter.Private:
return (
<FormattedMessage
id='more_channels.show_private_channels'
defaultMessage='Channel Type: Private'
/>
);
default:
return (
<FormattedMessage
id='more_channels.show_all_channels'
defaultMessage='Channel Type: All'
/>
);
}
};
render() {
const channels = this.props.channels;
@ -271,27 +343,6 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
let nextButton;
let previousButton;
let emptyStateMessage = (
<FormattedMessage
id={this.props.shouldShowArchivedChannels ? t('more_channels.noArchived') : t('more_channels.noPublic')}
tagName='strong'
defaultMessage={this.props.shouldShowArchivedChannels ? 'No archived channels' : 'No public channels'}
/>
);
if (this.state.channelSearchValue.length > 0) {
emptyStateMessage = (
<FormattedMessage
id='more_channels.noMore'
tagName='strong'
defaultMessage='No results for {text}'
values={{
text: this.state.channelSearchValue,
}}
/>
);
}
if (this.props.loading && channels.length === 0) {
listContent = <LoadingScreen/>;
} else if (channels.length === 0) {
@ -305,7 +356,7 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
>
<MagnifyingGlassSVG/>
<h3 className='primary-message'>
{emptyStateMessage}
{this.getEmptyStateMessage()}
</h3>
{this.props.noResultsText}
</div>
@ -371,51 +422,98 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
</div>
);
let channelDropdown;
let checkIcon;
const checkIcon = (
<CheckIcon
size={18}
color={'var(--button-bg)'}
/>
);
const channelDropdownItems = [
<Menu.Item
key='channelsMoreDropdownAll'
id='channelsMoreDropdownAll'
onClick={() => this.props.changeFilter(Filter.All)}
leadingElement={<GlobeCheckedIcon size={16}/>}
labels={
<FormattedMessage
id='suggestion.all'
defaultMessage='All channel types'
/>
}
trailingElements={this.props.filter === Filter.All ? checkIcon : null}
aria-label={localizeMessage('suggestion.all', 'All channel types')}
/>,
<Menu.Item
key='channelsMoreDropdownPublic'
id='channelsMoreDropdownPublic'
onClick={() => this.props.changeFilter(Filter.Public)}
leadingElement={<GlobeIcon size={16}/>}
labels={
<FormattedMessage
id='suggestion.public'
defaultMessage='Public channels'
/>
}
trailingElements={this.props.filter === Filter.Public ? checkIcon : null}
aria-label={localizeMessage('suggestion.public', 'Public channels')}
/>,
<Menu.Item
key='channelsMoreDropdownPrivate'
id='channelsMoreDropdownPrivate'
onClick={() => this.props.changeFilter(Filter.Private)}
leadingElement={<LockOutlineIcon size={16}/>}
labels={
<FormattedMessage
id='suggestion.private'
defaultMessage='Private channels'
/>
}
trailingElements={this.props.filter === Filter.Private ? checkIcon : null}
aria-label={localizeMessage('suggestion.private', 'Private channels')}
/>,
];
if (this.props.canShowArchivedChannels) {
checkIcon = (
<CheckIcon
size={18}
color={'var(--button-bg)'}
/>
);
channelDropdown = (
<MenuWrapper id='channelsMoreDropdown'>
<button id='menuWrapper'>
<span>{this.props.shouldShowArchivedChannels ? localizeMessage('more_channels.show_archived_channels', 'Channel Type: Archived') : localizeMessage('more_channels.show_public_channels', 'Channel Type: Public')}</span>
<ChevronDownIcon
color={'rgba(var(--center-channel-color-rgb), 0.64)'}
size={16}
channelDropdownItems.push(
<Menu.Separator/>,
<Menu.Item
id='channelsMoreDropdownArchived'
onClick={() => this.props.changeFilter(Filter.Archived)}
leadingElement={<ArchiveOutlineIcon size={16}/>}
labels={
<FormattedMessage
id='suggestion.archive'
defaultMessage='Archived channels'
/>
</button>
<Menu
openLeft={false}
ariaLabel={localizeMessage('more_channels.title', 'Browse channels')}
>
<div id='modalPreferenceContainer'>
<Menu.ItemAction
id='channelsMoreDropdownPublic'
onClick={this.toggleArchivedChannelsOff}
icon={<GlobeIcon size={16}/>}
text={localizeMessage('suggestion.search.public', 'Public Channels')}
rightDecorator={this.props.shouldShowArchivedChannels ? null : checkIcon}
ariaLabel={localizeMessage('suggestion.search.public', 'Public Channels')}
/>
</div>
<Menu.ItemAction
id='channelsMoreDropdownArchived'
onClick={this.toggleArchivedChannelsOn}
icon={<ArchiveOutlineIcon size={16}/>}
text={localizeMessage('suggestion.archive', 'Archived Channels')}
rightDecorator={this.props.shouldShowArchivedChannels ? checkIcon : null}
ariaLabel={localizeMessage('suggestion.archive', 'Archived Channels')}
/>
</Menu>
</MenuWrapper>
}
trailingElements={this.props.filter === Filter.Archived ? checkIcon : null}
aria-label={localizeMessage('suggestion.archive', 'Archived channels')}
/>,
);
}
const menuButton = (
<>
{this.getFilterLabel()}
<ChevronDownIcon
color={'rgba(var(--center-channel-color-rgb), 0.64)'}
size={16}
/>
</>
);
const channelDropdown = (
<Menu.Container
menuButton={{
id: 'menuWrapper',
children: menuButton,
}}
menu={{
id: 'browseChannelsDropdown',
'aria-label': localizeMessage('more_channels.title', 'Browse channels'),
}}
>
{channelDropdownItems.map((item) => item)}
</Menu.Container >
);
const hideJoinedButtonClass = classNames('get-app__checkbox', {checked: this.props.rememberHideJoinedChannelsChecked});
const hideJoinedPreferenceCheckbox = (
@ -425,8 +523,7 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
>
<button
className={hideJoinedButtonClass}
aria-label={this.props.rememberHideJoinedChannelsChecked ? localizeMessage('more_channels.hide_joined_checked', 'Hide joined channels checkbox, checked') : localizeMessage('more_channels.hide_joined_not_checked', 'Hide joined channels checkbox, not checked')
}
aria-label={this.props.rememberHideJoinedChannelsChecked ? localizeMessage('more_channels.hide_joined_checked', 'Hide joined channels checkbox, checked') : localizeMessage('more_channels.hide_joined_not_checked', 'Hide joined channels checkbox, not checked')}
>
{this.props.rememberHideJoinedChannelsChecked ? <CheckboxCheckedIcon/> : null}
</button>
@ -483,8 +580,3 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
);
}
}
// SearchableChannelList.defaultProps = {
// channels: [],
// isSearch: false,
// };

View File

@ -60,7 +60,6 @@ const AddChannelsCtaButton = (): JSX.Element | null => {
dispatch(openModal({
modalId: ModalIdentifiers.MORE_CHANNELS,
dialogType: BrowseChannels,
dialogProps: {morePublicChannelsModalType: 'public'},
}));
trackEvent('ui', 'browse_channels_button_is_clicked');
};

View File

@ -150,7 +150,6 @@ export default class Sidebar extends React.PureComponent<Props, State> {
this.props.actions.openModal({
modalId: ModalIdentifiers.MORE_CHANNELS,
dialogType: BrowseChannels,
dialogProps: {morePublicChannelsModalType: 'public'},
});
trackEvent('ui', 'ui_channels_more_public_v2');
};

View File

@ -205,10 +205,10 @@ exports[`component/user_group_popover should match snapshot 1`] = `
<CloseIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -300,10 +300,10 @@ exports[`component/user_group_popover should match snapshot 1`] = `
<MagnifyIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -1263,10 +1263,10 @@ exports[`component/user_group_popover should match snapshot 1`] = `
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -1482,10 +1482,10 @@ exports[`component/user_group_popover should match snapshot 1`] = `
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -1701,10 +1701,10 @@ exports[`component/user_group_popover should match snapshot 1`] = `
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -1920,10 +1920,10 @@ exports[`component/user_group_popover should match snapshot 1`] = `
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -2139,10 +2139,10 @@ exports[`component/user_group_popover should match snapshot 1`] = `
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path

View File

@ -533,10 +533,10 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -752,10 +752,10 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -971,10 +971,10 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -1190,10 +1190,10 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
@ -1409,10 +1409,10 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
<SendIcon>
<svg
fill="currentColor"
height={24}
height="1em"
version="1.1"
viewBox="0 0 24 24"
width={24}
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path

View File

@ -4021,11 +4021,15 @@
"more_channels.membership_indicator": "Membership Indicator: Joined",
"more_channels.next": "Next",
"more_channels.noArchived": "No archived channels",
"more_channels.noChannels": "No channels",
"more_channels.noMore": "No results for \"{text}\"",
"more_channels.noPrivate": "No private channels",
"more_channels.noPublic": "No public channels",
"more_channels.prev": "Previous",
"more_channels.searchError": "Try searching different keywords, checking for typos or adjusting the filters.",
"more_channels.show_all_channels": "Channel Type: All",
"more_channels.show_archived_channels": "Channel Type: Archived",
"more_channels.show_private_channels": "Channel Type: Private",
"more_channels.show_public_channels": "Channel Type: Public",
"more_channels.title": "Browse Channels",
"more_channels.view": "View",
@ -4936,7 +4940,8 @@
"success_modal.return_to_workspace": "Return to workspace",
"success_modal.subtitle": "Your final bill will be prorated. Your workspace now has {plan} limits.",
"suggestion_list.no_matches": "No items match __{value}__",
"suggestion.archive": "Archived Channels",
"suggestion.all": "All channel types",
"suggestion.archive": "Archived channels",
"suggestion.commands": "Commands",
"suggestion.emoji": "Emoji",
"suggestion.group.members": "{member_count} {member_count, plural, one {member} other {members}}",
@ -4954,6 +4959,8 @@
"suggestion.mention.special": "Special Mentions",
"suggestion.mention.unread": "Unread",
"suggestion.mention.unread.channels": "Unread Channels",
"suggestion.private": "Private channels",
"suggestion.public": "Public channels",
"suggestion.search.direct": "Direct Messages",
"suggestion.search.group": "Group Mentions",
"suggestion.search.private": "Private Channels",

View File

@ -59,7 +59,7 @@
"@guyplusplus/turndown-plugin-gfm": "1.0.7",
"@mattermost/client": "*",
"@mattermost/compass-components": "^0.2.12",
"@mattermost/compass-icons": "0.1.34",
"@mattermost/compass-icons": "0.1.37",
"@mattermost/types": "*",
"@mui/base": "5.0.0-alpha.127",
"@mui/material": "5.11.16",
@ -229,6 +229,11 @@
"yup": "0.32.11"
}
},
"channels/node_modules/@mattermost/compass-icons": {
"version": "0.1.37",
"resolved": "https://registry.npmjs.org/@mattermost/compass-icons/-/compass-icons-0.1.37.tgz",
"integrity": "sha512-4me1W0hj1nu8A1gpdQA6cij/hyb2P7uIYMJQ+xrNvn5ImTRfQ67LEdyNOa/LY+oT1NO2ui6kKOs8oLUehIaneg=="
},
"channels/node_modules/@mui/base": {
"version": "5.0.0-alpha.127",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.127.tgz",
@ -35641,7 +35646,7 @@
"@hot-loader/react-dom": "17.0.2",
"@mattermost/client": "*",
"@mattermost/compass-components": "^0.2.12",
"@mattermost/compass-icons": "0.1.34",
"@mattermost/compass-icons": "0.1.37",
"@mattermost/types": "*",
"@mui/base": "5.0.0-alpha.127",
"@mui/material": "5.11.16",
@ -35807,6 +35812,11 @@
"zen-observable": "0.9.0"
},
"dependencies": {
"@mattermost/compass-icons": {
"version": "0.1.37",
"resolved": "https://registry.npmjs.org/@mattermost/compass-icons/-/compass-icons-0.1.37.tgz",
"integrity": "sha512-4me1W0hj1nu8A1gpdQA6cij/hyb2P7uIYMJQ+xrNvn5ImTRfQ67LEdyNOa/LY+oT1NO2ui6kKOs8oLUehIaneg=="
},
"@mui/base": {
"version": "5.0.0-alpha.127",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.127.tgz",