Unverified Commit 40280862 authored by Saturnino Abril's avatar Saturnino Abril Committed by GitHub
Browse files

[MM-10519] Remove channel from MoreChannels whenever a channel is converted...

[MM-10519] Remove channel from MoreChannels whenever a channel is converted form public to private (#1218)

* remove channel from MoreChannels whenever a channel is converted from public to private
* updated handling "updated channel" ws event and also other comments
* updated
* dispatch channel_id after receiving channel_convert event
* updated mattermost-redux to include related change
* move mapStateToProps on top of mapDispatchToProps
parent 63ec20a8
......@@ -5,7 +5,12 @@ import $ from 'jquery';
import {batchActions} from 'redux-batched-actions';
import {ChannelTypes, EmojiTypes, PostTypes, TeamTypes, UserTypes, RoleTypes, GeneralTypes, AdminTypes} from 'mattermost-redux/action_types';
import {WebsocketEvents, General} from 'mattermost-redux/constants';
import {getChannelAndMyMember, getChannelStats, viewChannel} from 'mattermost-redux/actions/channels';
import {
getChannel,
getChannelAndMyMember,
getChannelStats,
viewChannel,
} from 'mattermost-redux/actions/channels';
import {setServerVersion} from 'mattermost-redux/actions/general';
import {getPosts, getProfilesAndStatusesForPosts, getCustomEmojiForReaction} from 'mattermost-redux/actions/posts';
import * as TeamActions from 'mattermost-redux/actions/teams';
......@@ -212,6 +217,10 @@ function handleEvent(msg) {
handleChannelDeletedEvent(msg);
break;
case SocketEvents.CHANNEL_CONVERTED:
handleChannelConvertedEvent(msg);
break;
case SocketEvents.CHANNEL_UPDATED:
handleChannelUpdatedEvent(msg);
break;
......@@ -296,6 +305,11 @@ function handleEvent(msg) {
}
}
function handleChannelConvertedEvent(msg) {
const channelId = msg.data.channel_id;
dispatch(getChannel(channelId));
}
function handleChannelUpdatedEvent(msg) {
const channel = JSON.parse(msg.data.channel);
dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: channel});
......
......@@ -3,10 +3,24 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getChannels} from 'mattermost-redux/actions/channels';
import {getOtherChannels} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import MoreChannels from './more_channels.jsx';
function mapStateToProps(state) {
const team = getCurrentTeam(state) || {};
return {
channels: getOtherChannels(state) || [],
teamId: team.id,
teamName: team.name,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
......@@ -15,4 +29,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(null, mapDispatchToProps)(MoreChannels);
export default connect(mapStateToProps, mapDispatchToProps)(MoreChannels);
......@@ -3,15 +3,14 @@
import PropTypes from 'prop-types';
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import Permissions from 'mattermost-redux/constants/permissions';
import {browserHistory} from 'utils/browser_history';
import {joinChannel, searchMoreChannels} from 'actions/channel_actions.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import {getRelativeChannelURL} from 'utils/url.jsx';
import SearchableChannelList from 'components/searchable_channel_list.jsx';
import TeamPermissionGate from 'components/permissions_gates/team_permission_gate';
......@@ -22,6 +21,9 @@ const SEARCH_TIMEOUT_MILLISECONDS = 100;
export default class MoreChannels extends React.Component {
static propTypes = {
channels: PropTypes.array.isRequired,
teamId: PropTypes.string.isRequired,
teamName: PropTypes.string.isRequired,
onModalDismissed: PropTypes.func,
handleNewChannel: PropTypes.func,
actions: PropTypes.shape({
......@@ -32,25 +34,18 @@ export default class MoreChannels extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
this.searchTimeoutId = 0;
this.state = {
show: true,
search: false,
channels: null,
searchedChannels: [],
serverError: null,
};
}
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
this.props.actions.getChannels(TeamStore.getCurrentId(), 0, CHANNELS_CHUNK_SIZE * 2);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange);
this.props.actions.getChannels(this.props.teamId, 0, CHANNELS_CHUNK_SIZE * 2);
}
handleHide = () => {
......@@ -69,20 +64,20 @@ export default class MoreChannels extends React.Component {
}
this.setState({
channels: ChannelStore.getMoreChannelsList(),
searchedChannels: [],
serverError: null,
});
}
nextPage = (page) => {
this.props.actions.getChannels(TeamStore.getCurrentId(), page + 1, CHANNELS_PER_PAGE);
this.props.actions.getChannels(this.props.teamId, page + 1, CHANNELS_PER_PAGE);
}
handleJoin = (channel, done) => {
joinChannel(
channel,
() => {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
browserHistory.push(getRelativeChannelURL(this.props.teamName, channel.name));
if (done) {
done();
}
......@@ -103,7 +98,7 @@ export default class MoreChannels extends React.Component {
if (term === '') {
this.onChange(true);
this.setState({search: false});
this.setState({search: false, searchedChannels: []});
this.searchTimeoutId = '';
return;
}
......@@ -116,7 +111,7 @@ export default class MoreChannels extends React.Component {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
this.setState({search: true, channels});
this.setState({search: true, searchedChannels: channels});
}
);
},
......@@ -127,14 +122,26 @@ export default class MoreChannels extends React.Component {
}
render() {
const {
channels,
teamId,
} = this.props;
const {
search,
searchedChannels,
serverError: serverErrorState,
show,
} = this.state;
let serverError;
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
if (serverErrorState) {
serverError = <div className='form-group has-error'><label className='control-label'>{serverErrorState}</label></div>;
}
const createNewChannelButton = (
<TeamPermissionGate
teamId={TeamStore.getCurrentId()}
teamId={teamId}
permissions={[Permissions.CREATE_PUBLIC_CHANNEL]}
>
<button
......@@ -153,7 +160,7 @@ export default class MoreChannels extends React.Component {
const createChannelHelpText = (
<TeamPermissionGate
teamId={TeamStore.getCurrentId()}
teamId={teamId}
permissions={[Permissions.CREATE_PUBLIC_CHANNEL]}
>
<p className='secondary-message'>
......@@ -168,7 +175,7 @@ export default class MoreChannels extends React.Component {
return (
<Modal
dialogClassName='more-modal more-modal--action'
show={this.state.show}
show={show}
onHide={this.handleHide}
onExited={this.handleExit}
>
......@@ -183,10 +190,10 @@ export default class MoreChannels extends React.Component {
</Modal.Header>
<Modal.Body>
<SearchableChannelList
channels={this.state.channels}
channels={search ? searchedChannels : channels}
channelsPerPage={CHANNELS_PER_PAGE}
nextPage={this.nextPage}
isSearch={this.state.search}
isSearch={search}
search={this.search}
handleJoin={this.handleJoin}
noResultsText={createChannelHelpText}
......
......@@ -28,6 +28,7 @@ export default class QuickInput extends React.PureComponent {
static defaultProps = {
delayInputUpdate: false,
value: '',
};
componentDidUpdate(prevProps) {
......
......@@ -10869,8 +10869,8 @@
"dev": true
},
"mattermost-redux": {
"version": "github:mattermost/mattermost-redux#74440af4513c2a77c3a17ecede72cb5c9cd59774",
"from": "github:mattermost/mattermost-redux#74440af4513c2a77c3a17ecede72cb5c9cd59774",
"version": "github:mattermost/mattermost-redux#4e32d267a0a9ae72b04e55d3ce5f2bec7a1878d9",
"from": "github:mattermost/mattermost-redux#4e32d267a0a9ae72b04e55d3ce5f2bec7a1878d9",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "1.2.0",
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/MoreChannels should match snapshot and state 1`] = `
<Modal
animation={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="more-modal more-modal--action"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={true}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
>
<ModalTitle
bsClass="modal-title"
componentClass="h4"
>
<FormattedMessage
defaultMessage="More Channels"
id="more_channels.title"
values={Object {}}
/>
</ModalTitle>
<Connect(TeamPermissionGate)
permissions={
Array [
"create_public_channel",
]
}
teamId="team_id"
>
<button
className="btn btn-primary channel-create-btn"
id="createNewChannel"
onClick={[Function]}
type="button"
>
<FormattedMessage
defaultMessage="Create New Channel"
id="more_channels.create"
values={Object {}}
/>
</button>
</Connect(TeamPermissionGate)>
</ModalHeader>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<SearchableChannelList
channels={
Array [
Object {
"id": "channel_id_1",
},
]
}
channelsPerPage={50}
handleJoin={[Function]}
isSearch={false}
nextPage={[Function]}
noResultsText={
<Connect(TeamPermissionGate)
permissions={
Array [
"create_public_channel",
]
}
teamId="team_id"
>
<p
className="secondary-message"
>
<FormattedMessage
defaultMessage="Click 'Create New Channel' to make a new one"
id="more_channels.createClick"
values={Object {}}
/>
</p>
</Connect(TeamPermissionGate)>
}
search={[Function]}
/>
</ModalBody>
</Modal>
`;
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import MoreChannels from 'components/more_channels/more_channels.jsx';
describe('components/MoreChannels', () => {
const baseProps = {
channels: [{id: 'channel_id_1'}],
teamId: 'team_id',
teamName: 'team_name',
onModalDismissed: () => {}, // eslint-disable-line no-empty-function
handleNewChannel: () => {}, // eslint-disable-line no-empty-function
actions: {
getChannels: () => {}, // eslint-disable-line no-empty-function
},
};
test('should match snapshot and state', () => {
const actions = {getChannels: jest.fn()};
const props = {...baseProps, actions};
const wrapper = shallow(
<MoreChannels {...props}/>
);
expect(wrapper).toMatchSnapshot();
expect(wrapper.state('searchedChannels')).toEqual([]);
expect(wrapper.state('show')).toEqual(true);
expect(wrapper.state('search')).toEqual(false);
expect(wrapper.state('serverError')).toBeNull();
// on componentDidMount
expect(actions.getChannels).toHaveBeenCalledTimes(1);
expect(actions.getChannels).toHaveBeenCalledWith(props.teamId, 0, 100);
});
test('should match state on handleHide', () => {
const wrapper = shallow(
<MoreChannels {...baseProps}/>
);
wrapper.setState({show: true});
wrapper.instance().handleHide();
expect(wrapper.state('show')).toEqual(false);
});
test('should call props.onModalDismissed on handleExit', () => {
const props = {...baseProps, onModalDismissed: jest.fn()};
const wrapper = shallow(
<MoreChannels {...props}/>
);
wrapper.instance().handleExit();
expect(props.onModalDismissed).toHaveBeenCalledTimes(1);
expect(props.onModalDismissed).toHaveBeenCalledWith();
});
test('should match state on onChange', () => {
const wrapper = shallow(
<MoreChannels {...baseProps}/>
);
wrapper.setState({searchedChannels: [{id: 'other_channel_id'}]});
wrapper.instance().onChange();
expect(wrapper.state('searchedChannels')).toEqual([]);
// on search
wrapper.setState({search: true});
expect(wrapper.instance().onChange(false)).toEqual();
});
test('should call props.getChannels on nextPage', () => {
const actions = {getChannels: jest.fn()};
const props = {...baseProps, actions};
const wrapper = shallow(
<MoreChannels {...props}/>
);
wrapper.instance().nextPage(1);
expect(actions.getChannels).toHaveBeenCalledTimes(2);
expect(actions.getChannels).toHaveBeenCalledWith(props.teamId, 2, 50);
});
});
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getRelativeChannelURL} from 'utils/url.jsx';
describe('Utils.URL', function() {
test('getRelativeChannelURL', function() {
expect(getRelativeChannelURL('teamName', 'channelName')).toEqual('/teamName/channels/channelName');
});
});
......@@ -316,6 +316,7 @@ export const SocketEvents = {
POST_EDITED: 'post_edited',
POST_DELETED: 'post_deleted',
POST_UPDATED: 'post_updated',
CHANNEL_CONVERTED: 'channel_converted',
CHANNEL_CREATED: 'channel_created',
CHANNEL_DELETED: 'channel_deleted',
CHANNEL_UPDATED: 'channel_updated',
......
......@@ -28,6 +28,10 @@ export function getSiteURL() {
return window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
}
export function getRelativeChannelURL(teamName, channelName) {
return `/${teamName}/channels/${channelName}`;
}
export function isUrlSafe(url) {
let unescaped;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment