From c3b3a36dc39659ea4c1eb0abdb347a908fb04bb8 Mon Sep 17 00:00:00 2001 From: Mattermost Build Date: Mon, 6 Jun 2016 21:57:07 +0000 Subject: [PATCH 01/17] Bumping version to: 3.1.0-rc1 --- model/version.go | 1 + 1 file changed, 1 insertion(+) diff --git a/model/version.go b/model/version.go index dde9eccd7d..d486f5c57b 100644 --- a/model/version.go +++ b/model/version.go @@ -13,6 +13,7 @@ import ( // It should be maitained in chronological order with most current // release at the front of the list. var versions = []string{ + "3.1.0", "3.0.0", "2.2.0", "2.1.0", From 52b5db3a0b5d2ed3b96a72d8e8865d61d865320e Mon Sep 17 00:00:00 2001 From: Corey Hulen Date: Tue, 7 Jun 2016 14:42:58 -0700 Subject: [PATCH 02/17] PLT-3215 Moving custom branding in admin console (#3286) --- .../admin_console/admin_sidebar.jsx | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index d760e3db9c..c947be5cb1 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -239,6 +239,22 @@ export default class AdminSidebar extends React.Component { ); } + let customBranding = null; + + if (window.mm_license.IsLicensed === 'true') { + customBranding = ( + + } + /> + ); + } + return (
@@ -515,16 +531,7 @@ export default class AdminSidebar extends React.Component { /> } > - - - } - /> + {customBranding} Date: Tue, 7 Jun 2016 17:43:06 -0400 Subject: [PATCH 03/17] Auto join teams if coming from team sign-up page to login for GitLab (#3284) --- api/oauth.go | 76 ++++++++++++-------- api/team.go | 12 ++++ api/user.go | 23 ++---- store/sql_team_store.go | 8 ++- webapp/components/login/login_controller.jsx | 4 +- 5 files changed, 73 insertions(+), 50 deletions(-) diff --git a/api/oauth.go b/api/oauth.go index 30efbdce3a..0726993217 100644 --- a/api/oauth.go +++ b/api/oauth.go @@ -204,7 +204,10 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } break case model.OAUTH_ACTION_LOGIN: - LoginByOAuth(c, w, r, service, body) + user := LoginByOAuth(c, w, r, service, body) + if len(teamId) > 0 { + c.Err = JoinUserToTeamById(teamId, user) + } if c.Err == nil { http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect) } @@ -424,8 +427,17 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { service := params["service"] loginHint := r.URL.Query().Get("login_hint") + teamId, err := getTeamIdFromQuery(r.URL.Query()) + if err != nil { + c.Err = err + return + } + stateProps := map[string]string{} stateProps["action"] = model.OAUTH_ACTION_LOGIN + if len(teamId) != 0 { + stateProps["team_id"] = teamId + } if authUrl, err := GetAuthorizationCode(c, service, stateProps, loginHint); err != nil { c.Err = err @@ -435,6 +447,36 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } } +func getTeamIdFromQuery(query url.Values) (string, *model.AppError) { + hash := query.Get("h") + inviteId := query.Get("id") + + if len(hash) > 0 { + data := query.Get("d") + props := model.MapFromJson(strings.NewReader(data)) + + if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) { + return "", model.NewLocAppError("getTeamIdFromQuery", "web.singup_with_oauth.invalid_link.app_error", nil, "") + } + + t, err := strconv.ParseInt(props["time"], 10, 64) + if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours + return "", model.NewLocAppError("getTeamIdFromQuery", "web.singup_with_oauth.expired_link.app_error", nil, "") + } + + return props["id"], nil + } else if len(inviteId) > 0 { + if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { + // soft fail, so we still create user but don't auto-join team + l4g.Error("%v", result.Err) + } else { + return result.Data.(*model.Team).Id, nil + } + } + + return "", nil +} + func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) service := params["service"] @@ -445,34 +487,10 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { return } - hash := r.URL.Query().Get("h") - - teamId := "" - inviteId := r.URL.Query().Get("id") - - if len(hash) > 0 { - data := r.URL.Query().Get("d") - props := model.MapFromJson(strings.NewReader(data)) - - if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) { - c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_link.app_error", nil, "") - return - } - - t, err := strconv.ParseInt(props["time"], 10, 64) - if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours - c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.expired_link.app_error", nil, "") - return - } - - teamId = props["id"] - } else if len(inviteId) != 0 { - if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { - // soft fail, so we still create user but don't auto-join team - l4g.Error("%v", result.Err) - } else { - teamId = result.Data.(*model.Team).Id - } + teamId, err := getTeamIdFromQuery(r.URL.Query()) + if err != nil { + c.Err = err + return } stateProps := map[string]string{} diff --git a/api/team.go b/api/team.go index 8eb7c4fefc..46bff617b9 100644 --- a/api/team.go +++ b/api/team.go @@ -17,6 +17,7 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) @@ -247,6 +248,14 @@ func CreateTeam(c *Context, team *model.Team) *model.Team { } } +func JoinUserToTeamById(teamId string, user *model.User) *model.AppError { + if result := <-Srv.Store.Team().Get(teamId); result.Err != nil { + return result.Err + } else { + return JoinUserToTeam(result.Data.(*model.Team), user) + } +} + func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError { tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id} @@ -258,6 +267,9 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError { } if tmr := <-Srv.Store.Team().SaveMember(tm); tmr.Err != nil { + if tmr.Err.Id == store.TEAM_MEMBER_EXISTS_ERROR { + return nil + } return tmr.Err } diff --git a/api/user.go b/api/user.go index de7a560bf3..aae3dffa5a 100644 --- a/api/user.go +++ b/api/user.go @@ -285,11 +285,6 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service suchan := Srv.Store.User().GetByAuth(user.AuthData, service) euchan := Srv.Store.User().GetByEmail(user.Email) - var tchan store.StoreChannel - if len(teamId) != 0 { - tchan = Srv.Store.Team().Get(teamId) - } - found := true count := 0 for found { @@ -319,20 +314,14 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service return nil } - if tchan != nil { - if result := <-tchan; result.Err != nil { - c.Err = result.Err + if len(teamId) > 0 { + err = JoinUserToTeamById(teamId, user) + if err != nil { + c.Err = err return nil - } else { - team := result.Data.(*model.Team) - err = JoinUserToTeam(team, user) - if err != nil { - c.Err = err - return nil - } - - go addDirectChannels(team.Id, user) } + + go addDirectChannels(teamId, user) } doLogin(c, w, r, ruser, "") diff --git a/store/sql_team_store.go b/store/sql_team_store.go index 6e1deeb209..c668988dcf 100644 --- a/store/sql_team_store.go +++ b/store/sql_team_store.go @@ -10,6 +10,10 @@ import ( "github.com/mattermost/platform/utils" ) +const ( + TEAM_MEMBER_EXISTS_ERROR = "store.sql_team.save_member.exists.app_error" +) + type SqlTeamStore struct { *SqlStore } @@ -372,8 +376,8 @@ func (s SqlTeamStore) SaveMember(member *model.TeamMember) StoreChannel { } if err := s.GetMaster().Insert(member); err != nil { - if IsUniqueConstraintError(err.Error(), []string{"TeamId", "teammembers_pkey"}) { - result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.exists.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error()) + if IsUniqueConstraintError(err.Error(), []string{"TeamId", "teammembers_pkey", "PRIMARY"}) { + result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", TEAM_MEMBER_EXISTS_ERROR, nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error()) } else { result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error()) } diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx index dd4a92f924..27b1cc39b3 100644 --- a/webapp/components/login/login_controller.jsx +++ b/webapp/components/login/login_controller.jsx @@ -444,7 +444,7 @@ export default class LoginController extends React.Component { @@ -529,4 +529,4 @@ export default class LoginController extends React.Component {
); } -} \ No newline at end of file +} From bde432645882c2c328fd3821c8dbcd968677a13a Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 7 Jun 2016 17:43:19 -0400 Subject: [PATCH 04/17] Properly removed hashtags and at mentions inside of markdown links (#3283) --- webapp/utils/markdown.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/utils/markdown.jsx b/webapp/utils/markdown.jsx index 73af4ae91a..bd1e998b4a 100644 --- a/webapp/utils/markdown.jsx +++ b/webapp/utils/markdown.jsx @@ -163,7 +163,7 @@ class MattermostMarkdownRenderer extends marked.Renderer { } // remove any links added to the text by hashtag or mention parsing since they'll break this link - output += '>' + text.replace(/<\/?a[^>]*>/, '') + ''; + output += '>' + text.replace(/<\/?a[^>]*>/g, '') + ''; return output; } From 3a6694cc6b52ba0022a92373df9f07b7bf080240 Mon Sep 17 00:00:00 2001 From: enahum Date: Tue, 7 Jun 2016 18:43:36 -0300 Subject: [PATCH 05/17] PLT-3203 Commenting on the RHS while in permalink view makes the message not show up in the RHS (#3282) --- webapp/actions/global_actions.jsx | 4 ++-- webapp/components/create_comment.jsx | 3 +++ webapp/components/edit_post_modal.jsx | 1 + webapp/stores/post_store.jsx | 2 +- webapp/utils/async_client.jsx | 10 ++++++---- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx index 0b264a9b3c..165d655e4f 100644 --- a/webapp/actions/global_actions.jsx +++ b/webapp/actions/global_actions.jsx @@ -138,8 +138,8 @@ export function doFocusPost(channelId, postId, data) { }); AsyncClient.getChannels(true); AsyncClient.getChannelExtraInfo(channelId); - AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS); - AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS); + AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true); + AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true); } export function emitPostFocusEvent(postId) { diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx index 73758e73c8..a9cf688332 100644 --- a/webapp/components/create_comment.jsx +++ b/webapp/components/create_comment.jsx @@ -5,6 +5,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import Client from 'utils/web_client.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; import PostDeletedModal from './post_deleted_modal.jsx'; @@ -150,6 +151,8 @@ class CreateComment extends React.Component { Client.createPost( post, (data) => { + AsyncClient.getPosts(this.props.channelId); + const channel = ChannelStore.get(this.props.channelId); const member = ChannelStore.getMember(this.props.channelId); member.msg_count = channel.total_msg_count; diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx index bdd540b09b..4bd23a26d9 100644 --- a/webapp/components/edit_post_modal.jsx +++ b/webapp/components/edit_post_modal.jsx @@ -67,6 +67,7 @@ class EditPostModal extends React.Component { Client.updatePost( updatedPost, () => { + AsyncClient.getPosts(updatedPost.channel_id); window.scrollTo(0, 0); }, (err) => { diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx index 062997f2ba..b5e73b6b41 100644 --- a/webapp/stores/post_store.jsx +++ b/webapp/stores/post_store.jsx @@ -551,7 +551,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => { switch (action.type) { case ActionTypes.RECEIVED_POSTS: { - const id = PostStore.currentFocusedPostId == null ? action.id : PostStore.currentFocusedPostId; + const id = PostStore.currentFocusedPostId !== null && action.isPost ? PostStore.currentFocusedPostId : action.id; PostStore.storePosts(id, makePostListNonNull(action.post_list)); PostStore.checkBounds(id, action.numRequested, makePostListNonNull(action.post_list), action.before); PostStore.emitChange(); diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx index 6f5f8a2cd0..65cb2d2587 100644 --- a/webapp/utils/async_client.jsx +++ b/webapp/utils/async_client.jsx @@ -603,7 +603,7 @@ export function getPosts(id) { ); } -export function getPostsBefore(postId, offset, numPost) { +export function getPostsBefore(postId, offset, numPost, isPost) { const channelId = ChannelStore.getCurrentId(); if (channelId == null) { return; @@ -624,7 +624,8 @@ export function getPostsBefore(postId, offset, numPost) { id: channelId, before: true, numRequested: numPost, - post_list: data + post_list: data, + isPost }); getProfiles(); @@ -638,7 +639,7 @@ export function getPostsBefore(postId, offset, numPost) { ); } -export function getPostsAfter(postId, offset, numPost) { +export function getPostsAfter(postId, offset, numPost, isPost) { const channelId = ChannelStore.getCurrentId(); if (channelId == null) { return; @@ -659,7 +660,8 @@ export function getPostsAfter(postId, offset, numPost) { id: channelId, before: false, numRequested: numPost, - post_list: data + post_list: data, + isPost }); getProfiles(); From ce5f3aeee15f0537ccdbb5314842506959423676 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 7 Jun 2016 17:43:46 -0400 Subject: [PATCH 06/17] Changed default LocalizationSettings.AvailableLocales to include all locales (#3280) --- model/config.go | 12 +++++++++++- webapp/i18n/i18n.jsx | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/model/config.go b/model/config.go index 7e810be02f..e63dca073d 100644 --- a/model/config.go +++ b/model/config.go @@ -6,6 +6,7 @@ package model import ( "encoding/json" "io" + "strings" ) const ( @@ -35,6 +36,15 @@ const ( FAKE_SETTING = "********************************" ) +// should match the values in webapp/i18n/i18n.jsx +var LOCALES = []string{ + "en", + "es", + "fr", + "ja", + "pt-BR", +} + type ServiceSettings struct { ListenAddress string MaximumLoginAttempts int @@ -541,7 +551,7 @@ func (o *Config) SetDefaults() { if o.LocalizationSettings.AvailableLocales == nil { o.LocalizationSettings.AvailableLocales = new(string) - *o.LocalizationSettings.AvailableLocales = *o.LocalizationSettings.DefaultClientLocale + *o.LocalizationSettings.AvailableLocales = strings.Join(LOCALES, ",") } } diff --git a/webapp/i18n/i18n.jsx b/webapp/i18n/i18n.jsx index 783cef975b..3a66c5c338 100644 --- a/webapp/i18n/i18n.jsx +++ b/webapp/i18n/i18n.jsx @@ -13,6 +13,7 @@ import frLocaleData from 'react-intl/locale-data/fr'; import jaLocaleData from 'react-intl/locale-data/ja'; import ptLocaleData from 'react-intl/locale-data/pt'; +// should match the values in model/config.go const languages = { en: { value: 'en', From 935e0cde8b45c3c4158fa4a0b7358b3c031f63e4 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 7 Jun 2016 17:43:56 -0400 Subject: [PATCH 07/17] Lock Javascript driver to 3.1 branch (#3277) --- webapp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/package.json b/webapp/package.json index 7535f5e04e..5bc9445579 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -18,7 +18,7 @@ "keymirror": "0.1.1", "marked": "mattermost/marked#2d92082cd542dc0adc175a1407194f6a4c3cb621", "match-at": "0.1.0", - "mattermost": "mattermost/mattermost-javascript#master", + "mattermost": "mattermost/mattermost-javascript#release-3.1", "object-assign": "4.1.0", "perfect-scrollbar": "0.6.11", "react": "15.0.2", From 99dd271c06719de67b711329bc45c820504e0b2b Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Tue, 7 Jun 2016 18:16:32 -0400 Subject: [PATCH 08/17] Join team correctly when signing up with LDAP (#3287) --- webapp/components/signup_user_complete.jsx | 34 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/webapp/components/signup_user_complete.jsx b/webapp/components/signup_user_complete.jsx index b3825f72f3..a9d96e320a 100644 --- a/webapp/components/signup_user_complete.jsx +++ b/webapp/components/signup_user_complete.jsx @@ -173,11 +173,24 @@ export default class SignupUserComplete extends React.Component { this.state.ldapPassword, null, () => { - GlobalActions.emitInitialLoad( - () => { - browserHistory.push('/select_team'); - } - ); + if (this.props.location.query.id || this.props.location.query.h) { + Client.addUserToTeamFromInvite( + this.props.location.query.d, + this.props.location.query.h, + this.props.location.query.id, + () => { + this.finishSignup(); + }, + () => { + // there's not really a good way to deal with this, so just let the user log in like normal + this.finishSignup(); + } + ); + + return; + } + + this.finishSignup(); }, (err) => { if (err.id === 'ent.ldap.do_login.user_not_registered.app_error' || err.id === 'ent.ldap.do_login.user_filtered.app_error') { @@ -205,6 +218,15 @@ export default class SignupUserComplete extends React.Component { ); } + finishSignup() { + GlobalActions.emitInitialLoad( + () => { + GlobalActions.loadDefaultLocale(); + browserHistory.push('/select_team'); + } + ); + } + handleUserCreated(user, data) { track('signup', 'signup_user_02_complete'); Client.loginById( @@ -761,4 +783,4 @@ export default class SignupUserComplete extends React.Component { ); } -} \ No newline at end of file +} From 6a3e742e9b52ab82e192085c648782aa52195bea Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 9 Jun 2016 20:08:14 +0500 Subject: [PATCH 09/17] Fixing attachment in RHS root post for compact view (#3289) * Fixing attachment in RHS root post for compact view * Improving spacing for mentions --- webapp/components/rhs_root_post.jsx | 1 + webapp/sass/components/_mentions.scss | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx index 8996381bae..ff64520354 100644 --- a/webapp/components/rhs_root_post.jsx +++ b/webapp/components/rhs_root_post.jsx @@ -175,6 +175,7 @@ export default class RhsRootPost extends React.Component { filenames={post.filenames} channelId={post.channel_id} userId={post.user_id} + compactDisplay={this.props.compactDisplay} /> ); } diff --git a/webapp/sass/components/_mentions.scss b/webapp/sass/components/_mentions.scss index 9e47e2a713..82f626eafe 100644 --- a/webapp/sass/components/_mentions.scss +++ b/webapp/sass/components/_mentions.scss @@ -13,10 +13,10 @@ @include clearfix; cursor: pointer; font-size: 13px; - height: 36px; - line-height: 36px; - margin: 5px 0; - padding: 2px; + height: 39px; + line-height: 35px; + margin: 0; + padding: 3px 8px; position: relative; white-space: nowrap; width: 100%; From 1b2fd5af5e55b6da3a2da5534570f357b0c643f3 Mon Sep 17 00:00:00 2001 From: enahum Date: Thu, 9 Jun 2016 12:08:20 -0300 Subject: [PATCH 10/17] PLT-3257 Fix Team Statistics (#3290) --- webapp/components/analytics/doughnut_chart.jsx | 12 ++++++------ webapp/components/analytics/line_chart.jsx | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/webapp/components/analytics/doughnut_chart.jsx b/webapp/components/analytics/doughnut_chart.jsx index b946ad8bbc..5834e290a3 100644 --- a/webapp/components/analytics/doughnut_chart.jsx +++ b/webapp/components/analytics/doughnut_chart.jsx @@ -23,26 +23,26 @@ export default class DoughnutChart extends React.Component { componentDidUpdate(prevProps) { if (!Utils.areObjectsEqual(prevProps.data, this.props.data) || !Utils.areObjectsEqual(prevProps.options, this.props.options)) { - if (this.chart) { - this.chart.destroy(); - } - this.initChart(); + this.initChart(true); } } componentWillUnmount() { - if (this.chart) { + if (this.chart && this.refs.canvas) { this.chart.destroy(); } } - initChart() { + initChart(update) { if (!this.refs.canvas) { return; } var el = ReactDOM.findDOMNode(this.refs.canvas); var ctx = el.getContext('2d'); this.chart = new Chart(ctx, {type: 'doughnut', data: this.props.data, options: this.props.options || {}}); //eslint-disable-line new-cap + if (update) { + this.chart.update(); + } } render() { diff --git a/webapp/components/analytics/line_chart.jsx b/webapp/components/analytics/line_chart.jsx index bebeb0223c..aa603d819b 100644 --- a/webapp/components/analytics/line_chart.jsx +++ b/webapp/components/analytics/line_chart.jsx @@ -23,26 +23,26 @@ export default class LineChart extends React.Component { componentDidUpdate(prevProps) { if (!Utils.areObjectsEqual(prevProps.data, this.props.data) || !Utils.areObjectsEqual(prevProps.options, this.props.options)) { - if (this.chart) { - this.chart.destroy(); - } - this.initChart(); + this.initChart(true); } } componentWillUnmount() { - if (this.chart) { + if (this.chart && this.refs.canvas) { this.chart.destroy(); } } - initChart() { + initChart(update) { if (!this.refs.canvas) { return; } var el = ReactDOM.findDOMNode(this.refs.canvas); var ctx = el.getContext('2d'); this.chart = new Chart(ctx, {type: 'line', data: this.props.data, options: this.props.options || {}}); //eslint-disable-line new-cap + if (update) { + this.chart.update(); + } } render() { From eefde7d7b6231d2ba143aa496f698d0c9f758bdd Mon Sep 17 00:00:00 2001 From: enahum Date: Thu, 9 Jun 2016 12:08:27 -0300 Subject: [PATCH 11/17] PLT-3236 Click in little x for admin console localization settings causes error (#3292) --- webapp/components/admin_console/multiselect_settings.jsx | 1 + webapp/i18n/en.json | 2 ++ webapp/i18n/es.json | 2 ++ 3 files changed, 5 insertions(+) diff --git a/webapp/components/admin_console/multiselect_settings.jsx b/webapp/components/admin_console/multiselect_settings.jsx index deba983de4..ca0bdc9aad 100644 --- a/webapp/components/admin_console/multiselect_settings.jsx +++ b/webapp/components/admin_console/multiselect_settings.jsx @@ -50,6 +50,7 @@ export default class MultiSelectSetting extends React.Component { labelKey='text' options={this.props.values} joinValues={true} + clearable={false} disabled={this.props.disabled} noResultsText={this.props.noResultText} onChange={this.handleChange} diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 783aea8636..1883b234a6 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -180,6 +180,7 @@ "admin.email.smtpUsernameTitle": "SMTP Username:", "admin.email.testing": "Testing...", "admin.false": "false", + "admin.general.localization": "Localization", "admin.general.localization.availableLocalesDescription": "Determines which languages are available for users in Account Settings.", "admin.general.localization.clientLocaleDescription": "Default language for newly created users and pages where the user hasn't logged in.", "admin.general.localization.serverLocaleDescription": "Default language for system messages and logs. Changing this will require a server restart before taking effect.", @@ -430,6 +431,7 @@ "admin.sidebar.integrations": "Integrations", "admin.sidebar.ldap": "LDAP", "admin.sidebar.license": "Edition and License", + "admin.sidebar.localization": "Localization", "admin.sidebar.logging": "Logging", "admin.sidebar.login": "Login", "admin.sidebar.logs": "Logs", diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json index 4e3b4d9169..95ef986db6 100644 --- a/webapp/i18n/es.json +++ b/webapp/i18n/es.json @@ -180,6 +180,7 @@ "admin.email.smtpUsernameTitle": "Usuario SMTP:", "admin.email.testing": "Probando...", "admin.false": "falso", + "admin.general.localization": "Idiomas", "admin.general.localization.availableLocalesDescription": "Determina qué idiomas están disponibles para los usuarios en la Configuración de la Cuenta.", "admin.general.localization.clientLocaleDescription": "Idioma predeterminado para nuevos usuarios y páginas donde el usuario no ha iniciado sesión.", "admin.general.localization.serverLocaleDescription": "Idioma predeterminado para los mensajes del sistema y los registros. Cambiar esto requerirá un reinicio del servidor antes de tomar efecto.", @@ -430,6 +431,7 @@ "admin.sidebar.integrations": "Integraciones", "admin.sidebar.ldap": "LDAP", "admin.sidebar.license": "Edición y Licencia", + "admin.sidebar.localization": "Idiomas", "admin.sidebar.logging": "Registros", "admin.sidebar.login": "Inicio de Sesión", "admin.sidebar.logs": "Registros", From 6f490b49c2a01a6a873f8fa781a796b6f697ced1 Mon Sep 17 00:00:00 2001 From: enahum Date: Thu, 9 Jun 2016 12:12:32 -0300 Subject: [PATCH 12/17] PLT-3254 Javascript error and can't login with Spanish Client language (#3298) --- webapp/stores/user_store.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx index f57ecf1cd6..218a7f1dbd 100644 --- a/webapp/stores/user_store.jsx +++ b/webapp/stores/user_store.jsx @@ -104,7 +104,7 @@ class UserStoreClass extends EventEmitter { this.currentUserId = user.id; global.window.mm_current_user_id = this.currentUserId; if (LocalizationStore.getLocale() !== user.locale) { - GlobalActions.newLocalizationSelected(user.locale); + setTimeout(() => GlobalActions.newLocalizationSelected(user.locale), 0); } } From de907280a7932a387909fc9310e6bd50269bbd98 Mon Sep 17 00:00:00 2001 From: jasonblais Date: Thu, 9 Jun 2016 11:44:13 -0400 Subject: [PATCH 13/17] Add server restart note to LDAP sync setting (#3303) * Add server restart note to LDAP sync setting * Add server restart note to LDAP sync setting --- webapp/components/admin_console/ldap_settings.jsx | 2 +- webapp/i18n/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx index acda672d8c..01d4b0a677 100644 --- a/webapp/components/admin_console/ldap_settings.jsx +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -343,7 +343,7 @@ export default class LdapSettings extends AdminSettings { helpText={ } value={this.state.syncIntervalMinutes} diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 1883b234a6..a858e9b942 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -283,7 +283,7 @@ "admin.ldap.serverTitle": "LDAP Server:", "admin.ldap.skipCertificateVerification": "Skip Certificate Verification", "admin.ldap.skipCertificateVerificationDesc": "Skips the certificate verification step for TLS or STARTTLS connections. Not recommended for production environments where TLS is required. For testing only.", - "admin.ldap.syncIntervalHelpText": "LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour.", + "admin.ldap.syncIntervalHelpText": "LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour. Changing this will require a server restart before taking effect.", "admin.ldap.syncIntervalTitle": "Synchronization Interval (In Minutes)", "admin.ldap.uernameAttrDesc": "The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.", "admin.ldap.userFilterDisc": "Optionally enter an LDAP Filter to use when searching for user objects. Only the users selected by the query will be able to access Mattermost. For Active Directory, the query to filter out disabled users is (&(objectCategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).", From f34a820a223c9ec7bfac5996b6245d364d25fe1a Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 9 Jun 2016 12:08:02 -0400 Subject: [PATCH 14/17] Correctly set defaults for all LDAPSettings in the config (#3301) --- model/config.go | 88 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/model/config.go b/model/config.go index e63dca073d..55606ee8d2 100644 --- a/model/config.go +++ b/model/config.go @@ -429,19 +429,39 @@ func (o *Config) SetDefaults() { *o.SupportSettings.SupportEmail = "feedback@mattermost.com" } + if o.LdapSettings.Enable == nil { + o.LdapSettings.Enable = new(bool) + *o.LdapSettings.Enable = false + } + + if o.LdapSettings.LdapServer == nil { + o.LdapSettings.LdapServer = new(string) + *o.LdapSettings.LdapServer = "" + } + if o.LdapSettings.LdapPort == nil { o.LdapSettings.LdapPort = new(int) *o.LdapSettings.LdapPort = 389 } - if o.LdapSettings.QueryTimeout == nil { - o.LdapSettings.QueryTimeout = new(int) - *o.LdapSettings.QueryTimeout = 60 + if o.LdapSettings.ConnectionSecurity == nil { + o.LdapSettings.ConnectionSecurity = new(string) + *o.LdapSettings.ConnectionSecurity = "" } - if o.LdapSettings.Enable == nil { - o.LdapSettings.Enable = new(bool) - *o.LdapSettings.Enable = false + if o.LdapSettings.BaseDN == nil { + o.LdapSettings.BaseDN = new(string) + *o.LdapSettings.BaseDN = "" + } + + if o.LdapSettings.BindUsername == nil { + o.LdapSettings.BindUsername = new(string) + *o.LdapSettings.BindUsername = "" + } + + if o.LdapSettings.BindPassword == nil { + o.LdapSettings.BindPassword = new(string) + *o.LdapSettings.BindPassword = "" } if o.LdapSettings.UserFilter == nil { @@ -449,9 +469,29 @@ func (o *Config) SetDefaults() { *o.LdapSettings.UserFilter = "" } - if o.LdapSettings.LoginFieldName == nil { - o.LdapSettings.LoginFieldName = new(string) - *o.LdapSettings.LoginFieldName = "" + if o.LdapSettings.FirstNameAttribute == nil { + o.LdapSettings.FirstNameAttribute = new(string) + *o.LdapSettings.FirstNameAttribute = "" + } + + if o.LdapSettings.LastNameAttribute == nil { + o.LdapSettings.LastNameAttribute = new(string) + *o.LdapSettings.LastNameAttribute = "" + } + + if o.LdapSettings.EmailAttribute == nil { + o.LdapSettings.EmailAttribute = new(string) + *o.LdapSettings.EmailAttribute = "" + } + + if o.LdapSettings.NicknameAttribute == nil { + o.LdapSettings.NicknameAttribute = new(string) + *o.LdapSettings.NicknameAttribute = "" + } + + if o.LdapSettings.IdAttribute == nil { + o.LdapSettings.IdAttribute = new(string) + *o.LdapSettings.IdAttribute = "" } if o.LdapSettings.SyncIntervalMinutes == nil { @@ -459,6 +499,21 @@ func (o *Config) SetDefaults() { *o.LdapSettings.SyncIntervalMinutes = 60 } + if o.LdapSettings.SkipCertificateVerification == nil { + o.LdapSettings.SkipCertificateVerification = new(bool) + *o.LdapSettings.SkipCertificateVerification = false + } + + if o.LdapSettings.QueryTimeout == nil { + o.LdapSettings.QueryTimeout = new(int) + *o.LdapSettings.QueryTimeout = 60 + } + + if o.LdapSettings.LoginFieldName == nil { + o.LdapSettings.LoginFieldName = new(string) + *o.LdapSettings.LoginFieldName = "" + } + if o.ServiceSettings.SessionLengthWebInDays == nil { o.ServiceSettings.SessionLengthWebInDays = new(int) *o.ServiceSettings.SessionLengthWebInDays = 30 @@ -524,21 +579,6 @@ func (o *Config) SetDefaults() { *o.ComplianceSettings.EnableDaily = false } - if o.LdapSettings.ConnectionSecurity == nil { - o.LdapSettings.ConnectionSecurity = new(string) - *o.LdapSettings.ConnectionSecurity = "" - } - - if o.LdapSettings.SkipCertificateVerification == nil { - o.LdapSettings.SkipCertificateVerification = new(bool) - *o.LdapSettings.SkipCertificateVerification = false - } - - if o.LdapSettings.NicknameAttribute == nil { - o.LdapSettings.NicknameAttribute = new(string) - *o.LdapSettings.NicknameAttribute = "" - } - if o.LocalizationSettings.DefaultServerLocale == nil { o.LocalizationSettings.DefaultServerLocale = new(string) *o.LocalizationSettings.DefaultServerLocale = DEFAULT_LOCALE From 3ff2e578b537f8fca51c95275d1eab8218b5ccb0 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Fri, 10 Jun 2016 12:08:19 -0400 Subject: [PATCH 15/17] Stopped trimming passwords on login (#3304) --- webapp/components/login/login_controller.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx index 27b1cc39b3..ab8b49392d 100644 --- a/webapp/components/login/login_controller.jsx +++ b/webapp/components/login/login_controller.jsx @@ -69,13 +69,13 @@ export default class LoginController extends React.Component { this.setState({loginId}); } - let password = this.refs.password.value; + const password = this.refs.password.value; if (password !== this.state.password) { this.setState({password}); } + // don't trim the password since we support spaces in passwords loginId = loginId.trim(); - password = password.trim(); if (!loginId) { // it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements From b353b9fbcfd924ca8ca01c416d1724ccd6113169 Mon Sep 17 00:00:00 2001 From: Corey Hulen Date: Mon, 13 Jun 2016 09:36:00 -0800 Subject: [PATCH 16/17] Adding DB schema version to CLI (#3315) --- mattermost.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mattermost.go b/mattermost.go index 143be67839..712faf83b7 100644 --- a/mattermost.go +++ b/mattermost.go @@ -854,6 +854,7 @@ func cmdVersion() { fmt.Fprintln(os.Stderr, "Build Date: "+model.BuildDate) fmt.Fprintln(os.Stderr, "Build Hash: "+model.BuildHash) fmt.Fprintln(os.Stderr, "Build Enterprise Ready: "+model.BuildEnterpriseReady) + fmt.Fprintln(os.Stderr, "DB Version: "+api.Srv.Store.(*store.SqlStore).SchemaVersion) os.Exit(0) } From 974238231b9cdbd39a825ec8e9299fbb0b51f6b8 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Mon, 13 Jun 2016 14:36:40 -0400 Subject: [PATCH 17/17] PLT-3270 Comment out max file size setting in system console (#3324) * Comment out max file size settting in system console * Remove pending post after successful create post request --- .../admin_console/storage_settings.jsx | 41 ++++++++++--------- webapp/components/create_post.jsx | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/webapp/components/admin_console/storage_settings.jsx b/webapp/components/admin_console/storage_settings.jsx index 60eedc2d24..077d75c737 100644 --- a/webapp/components/admin_console/storage_settings.jsx +++ b/webapp/components/admin_console/storage_settings.jsx @@ -22,8 +22,8 @@ export default class StorageSettings extends AdminSettings { this.renderSettings = this.renderSettings.bind(this); + //maxFileSize: props.config.FileSettings.MaxFileSize, this.state = Object.assign(this.state, { - maxFileSize: props.config.FileSettings.MaxFileSize, driverName: props.config.FileSettings.DriverName, directory: props.config.FileSettings.Directory, amazonS3AccessKeyId: props.config.FileSettings.AmazonS3AccessKeyId, @@ -34,7 +34,7 @@ export default class StorageSettings extends AdminSettings { } getConfigFromState(config) { - config.FileSettings.MaxFileSize = this.parseInt(this.state.maxFileSize); + //config.FileSettings.MaxFileSize = this.parseInt(this.state.maxFileSize); config.FileSettings.DriverName = this.state.driverName; config.FileSettings.Directory = this.state.directory; config.FileSettings.AmazonS3AccessKeyId = this.state.amazonS3AccessKeyId; @@ -57,26 +57,27 @@ export default class StorageSettings extends AdminSettings { } renderSettings() { + /* + } + placeholder={Utils.localizeMessage('admin.image.maxFileSizeExample', 'Ex "52428800"')} + helpText={ + + } + value={this.state.maxFileSize} + onChange={this.handleChange} + />*/ + return ( - - } - placeholder={Utils.localizeMessage('admin.image.maxFileSizeExample', 'Ex "52428800"')} - helpText={ - - } - value={this.state.maxFileSize} - onChange={this.handleChange} - /> { - // DO nothing. Websockets will handle this. + PostStore.removePendingPost(post.pending_post_id); }, (err) => { if (err.id === 'api.post.create_post.root_id.app_error') {