PLT-2707 Adding option to show DM list from all of server (#2871)

* PLT-2707 Adding option to show DM list from all of server

* Fixing loc
Этот коммит содержится в:
Corey Hulen
2016-05-04 06:31:42 -07:00
коммит произвёл Christopher Speller
родитель 6b06f49e89
Коммит 6611229cd7
29 изменённых файлов: 505 добавлений и 72 удалений

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

@@ -68,7 +68,7 @@ func TestCliCreateUserWithTeam(t *testing.T) {
t.Fatal(err)
}
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesForTeam(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfiles(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
found := false
@@ -149,7 +149,7 @@ func TestCliJoinTeam(t *testing.T) {
t.Fatal(err)
}
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesForTeam(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfiles(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
found := false

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

@@ -55,6 +55,7 @@ func InitUser() {
BaseRoutes.Users.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST")
BaseRoutes.Users.Handle("/direct_profiles", ApiUserRequired(getDirectProfiles)).Methods("GET")
BaseRoutes.Users.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
BaseRoutes.Users.Handle("/profiles_for_dm_list/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfilesForDirectMessageList)).Methods("GET")
BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
BaseRoutes.Users.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET")
@@ -902,6 +903,49 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
func getProfilesForDirectMessageList(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id := params["id"]
var pchan store.StoreChannel
if *utils.Cfg.TeamSettings.RestrictDirectMessage == model.DIRECT_MESSAGE_TEAM {
if c.Session.GetTeamByTeamId(id) == nil {
if !c.HasSystemAdminPermissions("getProfiles") {
return
}
}
pchan = Srv.Store.User().GetProfiles(id)
} else {
pchan = Srv.Store.User().GetAllProfiles()
}
if result := <-pchan; result.Err != nil {
c.Err = result.Err
return
} else {
profiles := result.Data.(map[string]*model.User)
for k, p := range profiles {
options := utils.Cfg.GetSanitizeOptions()
options["passwordupdate"] = false
if c.IsSystemAdmin() {
options["fullname"] = true
options["email"] = true
} else {
p.ClearNonProfileFields()
}
p.Sanitize(options)
profiles[k] = p
}
w.Write([]byte(model.UserMapToJson(profiles)))
}
}
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id := params["id"]
@@ -940,7 +984,6 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(model.UserMapToJson(profiles)))
return
}
}
@@ -973,7 +1016,6 @@ func getDirectProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(model.UserMapToJson(profiles)))
return
}
}

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

@@ -395,6 +395,22 @@ func TestGetDirectProfiles(t *testing.T) {
}
}
func TestGetProfilesForDirectMessageList(t *testing.T) {
th := Setup().InitBasic()
th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id))
if result, err := th.BasicClient.GetProfilesForDirectMessageList(th.BasicTeam.Id); err != nil {
t.Fatal(err)
} else {
users := result.Data.(map[string]*model.User)
if len(users) < 1 {
t.Fatal("map was wrong length")
}
}
}
func TestGetAudits(t *testing.T) {
th := Setup()
Client := th.CreateClient()

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

@@ -34,7 +34,8 @@
"RestrictCreationToDomains": "",
"RestrictTeamNames": true,
"EnableCustomBrand": false,
"CustomBrandText": ""
"CustomBrandText": "",
"RestrictDirectMessage": "any"
},
"SqlSettings": {
"DriverName": "mysql",
@@ -155,4 +156,4 @@
"Directory": "./data/",
"EnableDaily": false
}
}
}

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

@@ -2303,6 +2303,10 @@
"id": "model.config.is_valid.ldap_security.app_error",
"translation": "Invalid connection security for LDAP settings. Must be '', 'TLS', or 'STARTTLS'"
},
{
"id": "model.config.is_valid.restrict_direct_message.app_error",
"translation": "Invalid direct message restriction. Must be 'any', or 'team'"
},
{
"id": "model.config.is_valid.listen_address.app_error",
"translation": "Invalid listen address for service settings Must be set."

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

@@ -321,8 +321,8 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) {
}
}
func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) {
if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -330,8 +330,8 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
}
}
func (c *Client) GetProfilesForTeam(teamId string, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet("/users/profiles/"+teamId+"?skip_direct=true", "", etag); err != nil {
func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),

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

@@ -29,6 +29,9 @@ const (
GENERIC_NOTIFICATION = "generic"
FULL_NOTIFICATION = "full"
DIRECT_MESSAGE_ANY = "any"
DIRECT_MESSAGE_TEAM = "team"
FAKE_SETTING = "********************************"
)
@@ -162,6 +165,7 @@ type TeamSettings struct {
RestrictTeamNames *bool
EnableCustomBrand *bool
CustomBrandText *string
RestrictDirectMessage *string
}
type LdapSettings struct {
@@ -315,6 +319,11 @@ func (o *Config) SetDefaults() {
*o.TeamSettings.EnableOpenServer = false
}
if o.TeamSettings.RestrictDirectMessage == nil {
o.TeamSettings.RestrictDirectMessage = new(string)
*o.TeamSettings.RestrictDirectMessage = DIRECT_MESSAGE_ANY
}
if o.EmailSettings.EnableSignInWithEmail == nil {
o.EmailSettings.EnableSignInWithEmail = new(bool)
@@ -520,6 +529,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "")
}
if !(*o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_ANY || *o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_TEAM) {
return NewLocAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "")
}
if len(o.SqlSettings.AtRestEncryptKey) < 32 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "")
}

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

@@ -454,7 +454,14 @@ func (s SqlUserStore) GetEtagForDirectProfiles(userId string) StoreChannel {
Channels.Type = 'D'
AND Channels.Id = ChannelMembers.ChannelId
AND ChannelMembers.UserId = :UserId))
`, map[string]interface{}{"UserId": userId})
OR Id IN (SELECT
Name
FROM
Preferences
WHERE
UserId = :UserId
AND Category = 'direct_channel_show')
`, map[string]interface{}{"UserId": userId})
if err != nil {
result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
} else {
@@ -468,6 +475,57 @@ func (s SqlUserStore) GetEtagForDirectProfiles(userId string) StoreChannel {
return storeChannel
}
func (s SqlUserStore) GetEtagForAllProfiles() StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
updateAt, err := s.GetReplica().SelectInt("SELECT UpdateAt FROM Users ORDER BY UpdateAt DESC LIMIT 1")
if err != nil {
result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, model.GetMillis())
} else {
result.Data = fmt.Sprintf("%v.%v", model.CurrentVersion, updateAt)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) GetAllProfiles() StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var users []*model.User
if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users"); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {
userMap := make(map[string]*model.User)
for _, u := range users {
u.Password = ""
u.AuthData = ""
userMap[u.Id] = u
}
result.Data = userMap
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlUserStore) GetEtagForProfiles(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
@@ -548,7 +606,15 @@ func (us SqlUserStore) GetDirectProfiles(userId string) StoreChannel {
WHERE
Channels.Type = 'D'
AND Channels.Id = ChannelMembers.ChannelId
AND ChannelMembers.UserId = :UserId))`, map[string]interface{}{"UserId": userId}); err != nil {
AND ChannelMembers.UserId = :UserId))
OR Id IN (SELECT
Name
FROM
Preferences
WHERE
UserId = :UserId
AND Category = 'direct_channel_show')
`, map[string]interface{}{"UserId": userId}); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.GetDirectProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
} else {

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

@@ -263,6 +263,31 @@ func TestActiveUserCount(t *testing.T) {
}
}
func TestUserStoreGetAllProfiles(t *testing.T) {
Setup()
teamId := model.NewId()
u1 := &model.User{}
u1.Email = model.NewId()
Must(store.User().Save(u1))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
u2 := &model.User{}
u2.Email = model.NewId()
Must(store.User().Save(u2))
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
if r1 := <-store.User().GetAllProfiles(); r1.Err != nil {
t.Fatal(r1.Err)
} else {
users := r1.Data.(map[string]*model.User)
if len(users) < 2 {
t.Fatal("invalid returned users")
}
}
}
func TestUserStoreGetProfiles(t *testing.T) {
Setup()

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

@@ -131,6 +131,7 @@ type UserStore interface {
UpdateMfaActive(userId string, active bool) StoreChannel
Get(id string) StoreChannel
GetAll() StoreChannel
GetAllProfiles() StoreChannel
GetProfiles(teamId string) StoreChannel
GetDirectProfiles(userId string) StoreChannel
GetProfileByIds(userId []string) StoreChannel
@@ -139,6 +140,7 @@ type UserStore interface {
GetByUsername(username string) StoreChannel
GetForLogin(loginId string, allowSignInWithUsername, allowSignInWithEmail, ldapEnabled bool) StoreChannel
VerifyEmail(userId string) StoreChannel
GetEtagForAllProfiles() StoreChannel
GetEtagForProfiles(teamId string) StoreChannel
GetEtagForDirectProfiles(userId string) StoreChannel
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel

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

@@ -209,6 +209,7 @@ func getClientConfig(c *model.Config) map[string]string {
props["EnableUserCreation"] = strconv.FormatBool(c.TeamSettings.EnableUserCreation)
props["EnableOpenServer"] = strconv.FormatBool(*c.TeamSettings.EnableOpenServer)
props["RestrictTeamNames"] = strconv.FormatBool(*c.TeamSettings.RestrictTeamNames)
props["RestrictDirectMessage"] = *c.TeamSettings.RestrictDirectMessage
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey

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

@@ -157,6 +157,11 @@ export function emitPostFocusEvent(postId) {
);
}
export function emitProfilesForDmList() {
AsyncClient.getProfilesForDirectMessageList();
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
}
export function emitCloseRightHandSide() {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH,
@@ -335,6 +340,10 @@ export function emitClearSuggestions(suggestionId) {
}
export function emitPreferenceChangedEvent(preference) {
if (preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW) {
AsyncClient.getDirectProfiles();
}
AppDispatcher.handleServerAction({
type: Constants.ActionTypes.RECEIVED_PREFERENCE,
preference

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

@@ -862,13 +862,22 @@ export default class Client {
getProfilesForTeam = (teamId, success, error) => {
request.
get(`${this.getUsersRoute()}/profiles/${teamId}?skip_direct=true`).
get(`${this.getUsersRoute()}/profiles/${teamId}`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
end(this.handleResponse.bind(this, 'getProfilesForTeam', success, error));
}
getProfilesForDirectMessageList = (success, error) => {
request.
get(`${this.getUsersRoute()}/profiles_for_dm_list/${this.getTeamId()}`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
end(this.handleResponse.bind(this, 'getProfilesForDirectMessageList', success, error));
}
getStatuses = (ids, success, error) => {
request.
post(`${this.getUsersRoute()}/status`).

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

@@ -24,6 +24,14 @@ const holders = defineMessages({
saving: {
id: 'admin.team.saving',
defaultMessage: 'Saving Config...'
},
restrictDirectMessageAny: {
id: 'admin.team.restrict_direct_message_any',
defaultMessage: 'Any user on the Mattermost server'
},
restrictDirectMessageTeam: {
id: 'admin.team.restrict_direct_message_team',
defaultMessage: 'Any member of the team'
}
});
@@ -48,6 +56,7 @@ class TeamSettings extends React.Component {
saveNeeded: false,
brandImageExists: false,
enableCustomBrand: this.props.config.TeamSettings.EnableCustomBrand,
restrictDirectMessage: this.props.config.TeamSettings.RestrictDirectMessage,
serverError: null
};
}
@@ -104,6 +113,7 @@ class TeamSettings extends React.Component {
config.TeamSettings.EnableUserCreation = this.refs.EnableUserCreation.checked;
config.TeamSettings.EnableOpenServer = this.refs.EnableOpenServer.checked;
config.TeamSettings.RestrictTeamNames = this.refs.RestrictTeamNames.checked;
config.TeamSettings.RestrictDirectMessage = this.refs.RestrictDirectMessage.value.trim();
if (this.refs.EnableCustomBrand) {
config.TeamSettings.EnableCustomBrand = this.refs.EnableCustomBrand.checked;
@@ -660,6 +670,36 @@ class TeamSettings extends React.Component {
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='restrictDirectMessage'
>
<FormattedMessage
id='admin.team.restrictDirectMessage'
defaultMessage='Enable users to open Direct Message channels with:'
/>
</label>
<div className='col-sm-8'>
<select
className='form-control'
id='restrictDirectMessage'
ref='RestrictDirectMessage'
defaultValue={this.props.config.TeamSettings.RestrictDirectMessage}
onChange={this.handleChange.bind(this, 'restrictDirectMessage')}
>
<option value='any'>{formatMessage(holders.restrictDirectMessageAny)}</option>
<option value='team'>{formatMessage(holders.restrictDirectMessageTeam)}</option>
</select>
<p className='help-text'>
<FormattedHTMLMessage
id='admin.team.restrictDirectMessageDesc'
defaultMessage='"Any user on the Mattermost server" enables users to open a Direct Message channel with any user on the server, even if they are not on any teams together. "Any member of the team" limits the ability to open Direct Message channels to only users who are in the same team.'
/>
</p>
</div>
</div>
{brand}
<div className='form-group'>

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

@@ -5,6 +5,7 @@ import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import Constants from 'utils/constants.jsx';
@@ -99,6 +100,7 @@ class TeamUrl extends React.Component {
(team) => {
Client.track('signup', 'signup_team_08_complete');
$('#sign-up-button').button('reset');
AsyncClient.getDirectProfiles();
TeamStore.saveTeam(team);
TeamStore.appendTeamMember({team_id: team.id, user_id: UserStore.getCurrentId(), roles: 'admin'});
TeamStore.emitChange();

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

@@ -15,6 +15,14 @@ const holders = defineMessages({
search: {
id: 'filtered_user_list.search',
defaultMessage: 'Search members'
},
anyTeam: {
id: 'filtered_user_list.any_team',
defaultMessage: 'All Users'
},
teamOnly: {
id: 'filtered_user_list.team_only',
defaultMessage: 'Members of this Team'
}
});
@@ -25,9 +33,13 @@ class FilteredUserList extends React.Component {
super(props);
this.handleFilterChange = this.handleFilterChange.bind(this);
this.handleListChange = this.handleListChange.bind(this);
this.filterUsers = this.filterUsers.bind(this);
this.state = {
filter: ''
filter: '',
users: this.filterUsers(props.teamMembers, props.users),
selected: 'team'
};
}
@@ -37,18 +49,49 @@ class FilteredUserList extends React.Component {
}
}
filterUsers(teamMembers, users) {
if (!teamMembers || teamMembers.length === 0) {
return users;
}
var filteredUsers = users.filter((user) => {
for (const index in teamMembers) {
if (teamMembers.hasOwnProperty(index) && teamMembers[index].user_id === user.id) {
return true;
}
}
return false;
});
return filteredUsers;
}
handleFilterChange(e) {
this.setState({
filter: e.target.value
});
}
handleListChange(e) {
var users = this.props.users;
if (e.target.value === 'team') {
users = this.filterUsers(this.props.teamMembers, this.props.users);
}
this.setState({
selected: e.target.value,
users
});
}
render() {
const {formatMessage} = this.props.intl;
let users = this.props.users;
let users = this.state.users;
if (this.state.filter) {
if (this.state.filter && this.state.filter.length > 0) {
const filter = this.state.filter.toLowerCase();
users = users.filter((user) => {
@@ -60,7 +103,7 @@ class FilteredUserList extends React.Component {
}
let count;
if (users.length === this.props.users.length) {
if (users.length === this.state.users.length) {
count = (
<FormattedMessage
id='filtered_user_list.count'
@@ -77,12 +120,42 @@ class FilteredUserList extends React.Component {
defaultMessage='{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} Total'
values={{
count: users.length,
total: this.props.users.length
total: this.state.users.length
}}
/>
);
}
let teamToggle;
let teamMembers = this.props.teamMembers;
if (this.props.showTeamToggle) {
teamMembers = [];
teamToggle = (
<div className='col-sm-6'>
<select
className='form-control member-select'
id='restrictList'
ref='restrictList'
defaultValue='team'
onChange={this.handleListChange}
>
<option value='any'>{formatMessage(holders.anyTeam)}</option>
<option value='team'>{formatMessage(holders.teamOnly)}</option>
</select>
<span
className='member-show'
>
<FormattedMessage
id='filtered_user_list.show'
defaultMessage='Show'
/>
</span>
</div>
);
}
return (
<div
className='filtered-user-list'
@@ -100,6 +173,7 @@ class FilteredUserList extends React.Component {
<div className='col-sm-6'>
<span className='member-count'>{count}</span>
</div>
{teamToggle}
</div>
<div
ref='userList'
@@ -107,7 +181,7 @@ class FilteredUserList extends React.Component {
>
<UserList
users={users}
teamMembers={this.props.teamMembers}
teamMembers={teamMembers}
actions={this.props.actions}
actionProps={this.props.actionProps}
/>
@@ -121,7 +195,8 @@ FilteredUserList.defaultProps = {
users: [],
teamMembers: [],
actions: [],
actionProps: {}
actionProps: {},
showTeamToggle: false
};
FilteredUserList.propTypes = {
@@ -130,6 +205,7 @@ FilteredUserList.propTypes = {
teamMembers: React.PropTypes.arrayOf(React.PropTypes.object),
actions: React.PropTypes.arrayOf(React.PropTypes.func),
actionProps: React.PropTypes.object,
showTeamToggle: React.PropTypes.bool,
style: React.PropTypes.object
};

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

@@ -4,11 +4,14 @@
import {Modal} from 'react-bootstrap';
import FilteredUserList from './filtered_user_list.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import * as Utils from 'utils/utils.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router';
import SpinnerButton from 'components/spinner_button.jsx';
import LoadingScreen from 'components/loading_screen.jsx';
import React from 'react';
@@ -17,38 +20,27 @@ export default class MoreDirectChannels extends React.Component {
super(props);
this.handleHide = this.handleHide.bind(this);
this.handleOnEnter = this.handleOnEnter.bind(this);
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
this.handleUserChange = this.handleUserChange.bind(this);
this.onTeamChange = this.onTeamChange.bind(this);
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
this.state = {
users: this.getUsersFromStore(),
users: null,
teamMembers: null,
loadingDMChannel: -1
};
}
getUsersFromStore() {
const currentId = UserStore.getCurrentId();
const profiles = UserStore.getActiveOnlyProfiles();
const users = [];
for (const id in profiles) {
if (id !== currentId) {
users.push(profiles[id]);
}
}
users.sort((a, b) => a.username.localeCompare(b.username));
return users;
}
componentDidMount() {
UserStore.addChangeListener(this.handleUserChange);
UserStore.addDmListChangeListener(this.handleUserChange);
TeamStore.addChangeListener(this.onTeamChange);
}
componentWillUnmount() {
UserStore.removeChangeListener(this.handleUserChange);
UserStore.removeDmListChangeListener(this.handleUserChange);
TeamStore.removeChangeListener(this.onTeamChange);
}
shouldComponentUpdate(nextProps, nextState) {
@@ -77,6 +69,17 @@ export default class MoreDirectChannels extends React.Component {
}
}
handleOnEnter() {
this.setState({
users: null,
teamMembers: null
});
}
handleOnEntered() {
GlobalActions.emitProfilesForDmList();
}
handleShowDirectChannel(teammate, e) {
e.preventDefault();
@@ -99,7 +102,15 @@ export default class MoreDirectChannels extends React.Component {
}
handleUserChange() {
this.setState({users: this.getUsersFromStore()});
this.setState({
users: UserStore.getProfilesForDmList()
});
}
onTeamChange() {
this.setState({
teamMembers: TeamStore.getMembersForTeam()
});
}
createJoinDirectChannelButton({user}) {
@@ -123,11 +134,33 @@ export default class MoreDirectChannels extends React.Component {
maxHeight = Utils.windowHeight() - 300;
}
var body = null;
if (this.state.users == null || this.state.teamMembers == null) {
body = (<LoadingScreen/>);
} else {
var showTeamToggle = false;
if (global.window.mm_config.RestrictDirectMessage === 'any') {
showTeamToggle = true;
}
body = (
<FilteredUserList
style={{maxHeight}}
users={this.state.users}
teamMembers={this.state.teamMembers}
actions={[this.createJoinDirectChannelButton]}
showTeamToggle={showTeamToggle}
/>
);
}
return (
<Modal
dialogClassName='more-modal more-direct-channels'
show={this.props.show}
onHide={this.handleHide}
onEnter={this.handleOnEnter}
onEntered={this.handleOnEntered}
>
<Modal.Header closeButton={true}>
<Modal.Title>
@@ -138,11 +171,7 @@ export default class MoreDirectChannels extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
<FilteredUserList
style={{maxHeight}}
users={this.state.users}
actions={[this.createJoinDirectChannelButton]}
/>
{body}
</Modal.Body>
<Modal.Footer>
<button

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

@@ -124,8 +124,6 @@ export default class Sidebar extends React.Component {
directChannels.sort(this.sortChannelsByDisplayName);
const hiddenDirectChannelCount = UserStore.getActiveOnlyProfileList(true).length - directChannels.length;
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
return {
@@ -134,7 +132,6 @@ export default class Sidebar extends React.Component {
publicChannels,
privateChannels,
directChannels,
hiddenDirectChannelCount,
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER,
currentTeam: TeamStore.getCurrent(),
@@ -527,25 +524,19 @@ export default class Sidebar extends React.Component {
}
head.appendChild(link);
var directMessageMore = null;
if (this.state.hiddenDirectChannelCount > 0) {
directMessageMore = (
<li key='more'>
<a
href='#'
onClick={this.showMoreDirectChannelsModal}
>
<FormattedMessage
id='sidebar.more'
defaultMessage='More ({count})'
values={{
count: this.state.hiddenDirectChannelCount
}}
/>
</a>
</li>
);
}
var directMessageMore = (
<li key='more'>
<a
href='#'
onClick={this.showMoreDirectChannelsModal}
>
<FormattedMessage
id='sidebar.more'
defaultMessage='More'
/>
</a>
</li>
);
let showChannelModal = false;
if (this.state.newChannelModalType !== '') {

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

@@ -542,6 +542,10 @@
"admin.team.userCreationTitle": "Enable User Creation: ",
"admin.team_analytics.activeUsers": "Active Users With Posts",
"admin.team_analytics.totalPosts": "Total Posts",
"admin.team.restrictDirectMessage": "Enable users to open Direct Message channels with:",
"admin.team.restrictDirectMessageDesc": "'Any user on the Mattermost server' enables users to open a Direct Message channel with any user on the server, even if they are not on any teams together. 'Any member of the team' limits the ability to open Direct Message channels to only users who are in the same team.",
"admin.team.restrict_direct_message_any": "Any user on the Mattermost server",
"admin.team.restrict_direct_message_team": "Any member of the team",
"admin.userList.title": "Users for {team}",
"admin.userList.title2": "Users for {team} ({count})",
"admin.user_item.authServiceEmail": ", <strong>Sign-in Method:</strong> Email",
@@ -859,6 +863,9 @@
"file_upload.pasted": "Image Pasted at ",
"filtered_user_list.count": "{count} {count, plural, =0 {0 members} one {member} other {members}}",
"filtered_user_list.countTotal": "{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} Total",
"filtered_user_list.show": "Show",
"filtered_user_list.any_team": "All Users",
"filtered_user_list.team_only": "Members of this Team",
"filtered_user_list.member": "Member",
"filtered_user_list.search": "Search members",
"find_team.email": "Email",
@@ -1120,7 +1127,7 @@
"sidebar.createChannel": "Create new channel",
"sidebar.createGroup": "Create new group",
"sidebar.direct": "Direct Messages",
"sidebar.more": "More ({count})",
"sidebar.more": "More",
"sidebar.moreElips": "More...",
"sidebar.pg": "Private Groups",
"sidebar.removeList": "Remove from list",

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

@@ -1118,7 +1118,7 @@
"sidebar.createChannel": "Crear un nuevo canal",
"sidebar.createGroup": "Crear un nuevo grupo",
"sidebar.direct": "Mensajes Directos",
"sidebar.more": "Más ({count})",
"sidebar.more": "Más",
"sidebar.moreElips": "Más...",
"sidebar.pg": "Grupos Privados",
"sidebar.removeList": "Remover de la lista",

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

@@ -976,7 +976,7 @@
"sidebar.createChannel": "Créer un nouveau canal",
"sidebar.createGroup": "Créer un nouveau groupe",
"sidebar.direct": "Messages privés",
"sidebar.more": "Plus ({count})",
"sidebar.more": "Plus",
"sidebar.moreElips": "Plus...",
"sidebar.pg": "Groupes privés",
"sidebar.removeList": "Retirer de la liste",

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

@@ -1078,7 +1078,7 @@
"sidebar.createChannel": "新しいチャンネルを作成する",
"sidebar.createGroup": "新しいグループを作成する",
"sidebar.direct": "ダイレクトメッセージ",
"sidebar.more": "もっと表示する({count})",
"sidebar.more": "もっと表示する",
"sidebar.moreElips": "もっと…",
"sidebar.pg": "非公開グループ",
"sidebar.removeList": "一覧から削除する",

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

@@ -1120,7 +1120,7 @@
"sidebar.createChannel": "Criar novo canal",
"sidebar.createGroup": "Criar um novo grupo",
"sidebar.direct": "Mensagens Diretas",
"sidebar.more": "Mais ({count})",
"sidebar.more": "Mais",
"sidebar.moreElips": "Mais...",
"sidebar.pg": "Grupos Privados",
"sidebar.removeList": "Remover da lista",

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

@@ -436,6 +436,21 @@
margin-top: 5px;
}
.member-show {
@include opacity(.8);
float: right;
margin-top: 12px;
margin-right: 3px;
}
.member-select {
@include opacity(.8);
float: right;
width: auto;
margin-top: 5px;
margin-right: 5px;
}
.more-purpose {
@include opacity(.7);
}

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

@@ -7,6 +7,7 @@ import EventEmitter from 'events';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
const CHANGE_EVENT_DM_LIST = 'change_dm_list';
const CHANGE_EVENT = 'change';
const CHANGE_EVENT_SESSIONS = 'change_sessions';
const CHANGE_EVENT_AUDITS = 'change_audits';
@@ -19,6 +20,7 @@ class UserStoreClass extends EventEmitter {
}
clear() {
this.profiles_for_dm_list = {};
this.profiles = {};
this.direct_profiles = {};
this.statuses = {};
@@ -40,6 +42,18 @@ class UserStoreClass extends EventEmitter {
this.removeListener(CHANGE_EVENT, callback);
}
emitDmListChange() {
this.emit(CHANGE_EVENT_DM_LIST);
}
addDmListChangeListener(callback) {
this.on(CHANGE_EVENT_DM_LIST, callback);
}
removeDmListChangeListener(callback) {
this.removeListener(CHANGE_EVENT_DM_LIST, callback);
}
emitSessionsChange() {
this.emit(CHANGE_EVENT_SESSIONS);
}
@@ -190,6 +204,29 @@ class UserStoreClass extends EventEmitter {
}
}
getProfilesForDmList() {
const currentId = this.getCurrentId();
const profiles = [];
for (const id in this.profiles_for_dm_list) {
if (this.profiles_for_dm_list.hasOwnProperty(id) && id !== currentId) {
var profile = this.profiles_for_dm_list[id];
if (profile.delete_at === 0) {
profiles.push(profile);
}
}
}
profiles.sort((a, b) => a.username.localeCompare(b.username));
return profiles;
}
saveProfilesForDmList(profiles) {
this.profiles_for_dm_list = profiles;
}
setSessions(sessions) {
this.sessions = sessions;
}
@@ -278,6 +315,10 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
switch (action.type) {
case ActionTypes.RECEIVED_PROFILES_FOR_DM_LIST:
UserStore.saveProfilesForDmList(action.profiles);
UserStore.emitDmListChange();
break;
case ActionTypes.RECEIVED_PROFILES:
UserStore.saveProfiles(action.profiles);
UserStore.emitChange();

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

@@ -480,6 +480,20 @@ describe('Client.User', function() {
});
});
it('getProfilesForDirectMessageList', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().getProfilesForDirectMessageList(
function(data) {
assert.equal(Object.keys(data).length > 0, true);
done();
},
function(err) {
done(new Error(err.message));
}
);
});
});
it('getStatuses', function(done) {
TestHelper.initBasic(() => {
var ids = [];

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

@@ -224,6 +224,28 @@ export function getTeamMembers(teamId) {
);
}
export function getProfilesForDirectMessageList() {
if (isCallInProgress('getProfilesForDirectMessageList')) {
return;
}
callTracker.getProfilesForDirectMessageList = utils.getTimestamp();
Client.getProfilesForDirectMessageList(
(data) => {
callTracker.getProfilesForDirectMessageList = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_FOR_DM_LIST,
profiles: data
});
},
(err) => {
callTracker.getProfilesForDirectMessageList = 0;
dispatchError(err, 'getProfilesForDirectMessageList');
}
);
}
export function getProfiles() {
if (isCallInProgress('getProfiles')) {
return;

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

@@ -60,6 +60,7 @@ export default {
RECEIVED_MENTION_DATA: null,
RECEIVED_ADD_MENTION: null,
RECEIVED_PROFILES_FOR_DM_LIST: null,
RECEIVED_PROFILES: null,
RECEIVED_DIRECT_PROFILES: null,
RECEIVED_ME: null,

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

@@ -1247,6 +1247,13 @@ export function openDirectChannelToUser(user, successCb, errorCb) {
'true'
);
// if the user in another team and isn't already in the direct message
// list then we should add him so his name shows up correctly.
var profileUser = UserStore.getProfile(user.id);
if (!profileUser) {
UserStore.getDirectProfiles()[user.id] = user;
}
const channelName = this.getDirectChannelName(UserStore.getCurrentId(), user.id);
let channel = ChannelStore.getByName(channelName);