From 4bc625e8d10ab7735b76814fe9bbf3fb9144d4e1 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 22 Sep 2015 12:00:34 -0400 Subject: [PATCH 01/22] Added 'default' option to channel notification settings that just uses the user's notification level --- api/channel.go | 12 ++-- model/channel_member.go | 9 ++- .../components/channel_notifications.jsx | 67 ++++++++++++------- web/react/components/sidebar.jsx | 14 ++-- .../user_settings_notifications.jsx | 2 +- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/api/channel.go b/api/channel.go index 896e227932..17322179d8 100644 --- a/api/channel.go +++ b/api/channel.go @@ -76,7 +76,7 @@ func CreateChannel(c *Context, channel *model.Channel, addMember bool) (*model.C if addMember { cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId, - Roles: model.CHANNEL_ROLE_ADMIN, NotifyLevel: model.CHANNEL_NOTIFY_ALL} + Roles: model.CHANNEL_ROLE_ADMIN, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -135,7 +135,7 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model return nil, err } else { cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, - Roles: "", NotifyLevel: model.CHANNEL_NOTIFY_ALL} + Roles: "", NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -372,7 +372,7 @@ func JoinChannel(c *Context, channelId string, role string) { } if channel.Type == model.CHANNEL_OPEN { - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: role} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, Roles: role} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { c.Err = cmresult.Err @@ -405,7 +405,7 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { if result := <-Srv.Store.Channel().GetByName(user.TeamId, "town-square"); result.Err != nil { err = result.Err } else { - cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: channelRole} + cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, Roles: channelRole} if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err } @@ -414,7 +414,7 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { if result := <-Srv.Store.Channel().GetByName(user.TeamId, "off-topic"); result.Err != nil { err = result.Err } else { - cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: channelRole} + cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, Roles: channelRole} if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err } @@ -694,7 +694,7 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { } else { oUser := oresult.Data.(*model.User) - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyLevel: model.CHANNEL_NOTIFY_ALL} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { l4g.Error("Failed to add member user_id=%v channel_id=%v err=%v", userId, id, cmresult.Err) diff --git a/model/channel_member.go b/model/channel_member.go index 50f51304bc..ac0b94ec44 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -11,10 +11,11 @@ import ( const ( CHANNEL_ROLE_ADMIN = "admin" + CHANNEL_NOTIFY_DEFAULT = "default" CHANNEL_NOTIFY_ALL = "all" CHANNEL_NOTIFY_MENTION = "mention" CHANNEL_NOTIFY_NONE = "none" - CHANNEL_NOTIFY_QUIET = "quiet" + CHANNEL_NOTIFY_QUIET = "quiet" // TODO deprecate me ) type ChannelMember struct { @@ -76,5 +77,9 @@ func (o *ChannelMember) PreSave() { } func IsChannelNotifyLevelValid(notifyLevel string) bool { - return notifyLevel == CHANNEL_NOTIFY_ALL || notifyLevel == CHANNEL_NOTIFY_MENTION || notifyLevel == CHANNEL_NOTIFY_NONE || notifyLevel == CHANNEL_NOTIFY_QUIET + return notifyLevel == CHANNEL_NOTIFY_DEFAULT || + notifyLevel == CHANNEL_NOTIFY_ALL || + notifyLevel == CHANNEL_NOTIFY_MENTION || + notifyLevel == CHANNEL_NOTIFY_NONE || + notifyLevel == CHANNEL_NOTIFY_QUIET } diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 9eda68b38b..347d024789 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -104,14 +104,28 @@ export default class ChannelNotifications extends React.Component { createDesktopSection(serverError) { var handleUpdateSection; + const user = UserStore.getCurrentUser(); + const globalNotifyLevel = user.notify_props.desktop; + + let globalNotifyLevelName; + if (globalNotifyLevel === 'all') { + globalNotifyLevelName = 'For all activity'; + } else if (globalNotifyLevel === 'mention') { + globalNotifyLevelName = 'Only for mentions'; + } else { + globalNotifyLevelName = 'Never'; + } + if (this.state.activeSection === 'desktop') { - var notifyActive = [false, false, false]; - if (this.state.notifyLevel === 'mention') { - notifyActive[1] = true; - } else if (this.state.notifyLevel === 'all') { + var notifyActive = [false, false, false, false]; + if (this.state.notifyLevel === 'default') { notifyActive[0] = true; - } else { + } else if (this.state.notifyLevel === 'all') { + notifyActive[1] = true; + } else if (this.state.notifyLevel === 'mention') { notifyActive[2] = true; + } else { + notifyActive[3] = true; } var inputs = []; @@ -123,9 +137,9 @@ export default class ChannelNotifications extends React.Component { - For all activity + {`Global default (${globalNotifyLevelName})`}
@@ -135,9 +149,9 @@ export default class ChannelNotifications extends React.Component { - Only for mentions + {'For all activity'}
@@ -147,9 +161,21 @@ export default class ChannelNotifications extends React.Component { + {'Only for mentions'} + + +
+ +
+
@@ -162,25 +188,14 @@ export default class ChannelNotifications extends React.Component { e.preventDefault(); }.bind(this); - let curChannel = ChannelStore.get(this.state.channelId); - let extraInfo = ( + const extraInfo = ( - These settings will override the global notification settings. + {'Selecting an option other than "Default" will override the global notification settings.'}
- Desktop notifications are available on Firefox, Safari, and Chrome. + {'Desktop notifications are available on Firefox, Safari, and Chrome.'}
); - if (curChannel && curChannel.display_name) { - extraInfo = ( - - These settings will override the global notification settings for the {curChannel.display_name} channel. -
- Desktop notifications are available on Firefox, Safari, and Chrome. -
- ); - } - return ( Date: Wed, 23 Sep 2015 11:38:08 -0400 Subject: [PATCH 02/22] Removed UI for quiet mode and added UI to set when a channel will be marked unread in the sidebar --- api/channel.go | 60 ++++- model/channel_member.go | 39 ++-- store/sql_channel_store.go | 30 +++ store/store.go | 1 + .../components/channel_notifications.jsx | 216 ++++++++++-------- web/react/utils/client.jsx | 15 ++ 6 files changed, 245 insertions(+), 116 deletions(-) diff --git a/api/channel.go b/api/channel.go index 17322179d8..b69fe6ea0e 100644 --- a/api/channel.go +++ b/api/channel.go @@ -24,6 +24,7 @@ func InitChannel(r *mux.Router) { sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST") sr.Handle("/update_desc", ApiUserRequired(updateChannelDesc)).Methods("POST") sr.Handle("/update_notify_level", ApiUserRequired(updateNotifyLevel)).Methods("POST") + sr.Handle("/update_mark_unread_level", ApiUserRequired(updateMarkUnreadLevel)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(joinChannel)).Methods("POST") @@ -75,8 +76,8 @@ func CreateChannel(c *Context, channel *model.Channel, addMember bool) (*model.C sc := result.Data.(*model.Channel) if addMember { - cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId, - Roles: model.CHANNEL_ROLE_ADMIN, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT} + cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId, Roles: model.CHANNEL_ROLE_ADMIN, + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -134,8 +135,8 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model if sc, err := CreateChannel(c, channel, true); err != nil { return nil, err } else { - cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, - Roles: "", NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT} + cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, Roles: "", + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -372,7 +373,8 @@ func JoinChannel(c *Context, channelId string, role string) { } if channel.Type == model.CHANNEL_OPEN { - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, Roles: role} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, Roles: role, + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { c.Err = cmresult.Err @@ -405,7 +407,9 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { if result := <-Srv.Store.Channel().GetByName(user.TeamId, "town-square"); result.Err != nil { err = result.Err } else { - cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, Roles: channelRole} + cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, Roles: channelRole, + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err } @@ -414,7 +418,9 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { if result := <-Srv.Store.Channel().GetByName(user.TeamId, "off-topic"); result.Err != nil { err = result.Err } else { - cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, Roles: channelRole} + cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, Roles: channelRole, + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err } @@ -694,7 +700,7 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { } else { oUser := oresult.Data.(*model.User) - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { l4g.Error("Failed to add member user_id=%v channel_id=%v err=%v", userId, id, cmresult.Err) @@ -821,3 +827,41 @@ func updateNotifyLevel(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(data))) } + +func updateMarkUnreadLevel(c *Context, w http.ResponseWriter, r *http.Request) { + data := model.MapFromJson(r.Body) + userId := data["user_id"] + if len(userId) != 26 { + c.SetInvalidParam("updateMarkUnreadLevel", "user_id") + return + } + + channelId := data["channel_id"] + if len(channelId) != 26 { + c.SetInvalidParam("updateMarkUnreadLevel", "channel_id") + return + } + + markUnreadLevel := data["mark_unread_level"] + if len(markUnreadLevel) == 0 || !model.IsChannelMarkUnreadLevelValid(markUnreadLevel) { + c.SetInvalidParam("updateMarkUnreadLevel", "mark_unread_level") + return + } + + cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) + + if !c.HasPermissionsToUser(userId, "updateMarkUnreadLevel") { + return + } + + if !c.HasPermissionsToChannel(cchan, "updateMarkUnreadLevel") { + return + } + + if result := <-Srv.Store.Channel().UpdateMarkUnreadLevel(channelId, userId, markUnreadLevel); result.Err != nil { + c.Err = result.Err + return + } + + w.Write([]byte(model.MapToJson(data))) +} diff --git a/model/channel_member.go b/model/channel_member.go index ac0b94ec44..1aafee43d8 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -10,23 +10,26 @@ import ( ) const ( - CHANNEL_ROLE_ADMIN = "admin" - CHANNEL_NOTIFY_DEFAULT = "default" - CHANNEL_NOTIFY_ALL = "all" - CHANNEL_NOTIFY_MENTION = "mention" - CHANNEL_NOTIFY_NONE = "none" - CHANNEL_NOTIFY_QUIET = "quiet" // TODO deprecate me + CHANNEL_ROLE_ADMIN = "admin" + CHANNEL_NOTIFY_DEFAULT = "default" + CHANNEL_NOTIFY_ALL = "all" + CHANNEL_NOTIFY_MENTION = "mention" + CHANNEL_NOTIFY_NONE = "none" + CHANNEL_NOTIFY_QUIET = "quiet" // no longer used, should be considered functionally equivalent to CHANNEL_NOTIFY_NONE + CHANNEL_MARK_UNREAD_ALL = "all" + CHANNEL_MARK_UNREAD_MENTION = "mention" ) type ChannelMember struct { - ChannelId string `json:"channel_id"` - UserId string `json:"user_id"` - Roles string `json:"roles"` - LastViewedAt int64 `json:"last_viewed_at"` - MsgCount int64 `json:"msg_count"` - MentionCount int64 `json:"mention_count"` - NotifyLevel string `json:"notify_level"` - LastUpdateAt int64 `json:"last_update_at"` + ChannelId string `json:"channel_id"` + UserId string `json:"user_id"` + Roles string `json:"roles"` + LastViewedAt int64 `json:"last_viewed_at"` + MsgCount int64 `json:"msg_count"` + MentionCount int64 `json:"mention_count"` + NotifyLevel string `json:"notify_level"` + MarkUnreadLevel string `json:"mark_unread_level"` + LastUpdateAt int64 `json:"last_update_at"` } func (o *ChannelMember) ToJson() string { @@ -69,6 +72,10 @@ func (o *ChannelMember) IsValid() *AppError { return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+o.NotifyLevel) } + if len(o.MarkUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(o.MarkUnreadLevel) { + return NewAppError("ChannelMember.IsValid", "Invalid mark unread level", "mark_unread_level="+o.MarkUnreadLevel) + } + return nil } @@ -83,3 +90,7 @@ func IsChannelNotifyLevelValid(notifyLevel string) bool { notifyLevel == CHANNEL_NOTIFY_NONE || notifyLevel == CHANNEL_NOTIFY_QUIET } + +func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool { + return markUnreadLevel == CHANNEL_MARK_UNREAD_ALL || markUnreadLevel == CHANNEL_MARK_UNREAD_MENTION +} diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index cb686090e8..892ee1398b 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -37,6 +37,7 @@ func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore { } func (s SqlChannelStore) UpgradeSchemaIfNeeded() { + s.CreateColumnIfNotExists("ChannelMembers", "MarkUnreadLevel", "varchar(20)", "varchar(20)", model.CHANNEL_MARK_UNREAD_ALL) } func (s SqlChannelStore) CreateIndexesIfNotExists() { @@ -678,6 +679,35 @@ func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string return storeChannel } +func (s SqlChannelStore) UpdateMarkUnreadLevel(channelId, userId, markUnreadLevel string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + updateAt := model.GetMillis() + + _, err := s.GetMaster().Exec( + `UPDATE + ChannelMembers + SET + MarkUnreadLevel = :MarkUnreadLevel, + LastUpdateAt = :LastUpdateAt + WHERE + UserId = :UserId + AND ChannelId = :ChannelId`, + map[string]interface{}{"ChannelId": channelId, "UserId": userId, "MarkUnreadLevel": markUnreadLevel, "LastUpdateAt": updateAt}) + if err != nil { + result.Err = model.NewAppError("SqlChannelStore.UpdateMarkUnreadLevel", "We couldn't update the mark unread level", "channel_id="+channelId+", user_id="+userId+", "+err.Error()) + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + func (s SqlChannelStore) GetForExport(teamId string) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/store.go b/store/store.go index 23580f4525..83366b79e0 100644 --- a/store/store.go +++ b/store/store.go @@ -72,6 +72,7 @@ type ChannelStore interface { UpdateLastViewedAt(channelId string, userId string) StoreChannel IncrementMentionCount(channelId string, userId string) StoreChannel UpdateNotifyLevel(channelId string, userId string, notifyLevel string) StoreChannel + UpdateMarkUnreadLevel(channelId string, userId string, markUnreadLevel string) StoreChannel } type PostStore interface { diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 347d024789..c49e763330 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -15,14 +15,24 @@ export default class ChannelNotifications extends React.Component { this.onListenerChange = this.onListenerChange.bind(this); this.updateSection = this.updateSection.bind(this); - this.handleUpdate = this.handleUpdate.bind(this); - this.handleRadioClick = this.handleRadioClick.bind(this); - this.handleQuietToggle = this.handleQuietToggle.bind(this); - this.createDesktopSection = this.createDesktopSection.bind(this); - this.createQuietSection = this.createQuietSection.bind(this); - this.state = {notifyLevel: '', title: '', channelId: '', activeSection: ''}; + this.handleSubmitNotifyLevel = this.handleSubmitNotifyLevel.bind(this); + this.handleUpdateNotifyLevel = this.handleUpdateNotifyLevel.bind(this); + this.createNotifyLevelSection = this.createNotifyLevelSection.bind(this); + + this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this); + this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this); + this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this); + + this.state = { + notifyLevel: '', + markUnreadLevel: '', + title: '', + channelId: '', + activeSection: '' + }; } + componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); @@ -30,33 +40,34 @@ export default class ChannelNotifications extends React.Component { var button = e.relatedTarget; var channelId = button.getAttribute('data-channelid'); - var notifyLevel = ChannelStore.getMember(channelId).notify_level; - var quietMode = false; + const member = ChannelStore.getMember(channelId); + var notifyLevel = member.notify_level; + var markUnreadLevel = member.mark_unread_level; - if (notifyLevel === 'quiet') { - quietMode = true; - } - - this.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId}); + this.setState({ + notifyLevel, + markUnreadLevel, + title: button.getAttribute('data-title'), + channelId: channelId + }); }.bind(this)); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); } + onListenerChange() { if (!this.state.channelId) { return; } - var notifyLevel = ChannelStore.getMember(this.state.channelId).notify_level; - var quietMode = false; - if (notifyLevel === 'quiet') { - quietMode = true; - } + const member = ChannelStore.getMember(this.state.channelId); + var notifyLevel = member.notify_level; + var markUnreadLevel = member.mark_unread_level; var newState = this.state; newState.notifyLevel = notifyLevel; - newState.quietMode = quietMode; + newState.markUnreadLevel = markUnreadLevel; if (!Utils.areStatesEqual(this.state, newState)) { this.setState(newState); @@ -65,12 +76,10 @@ export default class ChannelNotifications extends React.Component { updateSection(section) { this.setState({activeSection: section}); } - handleUpdate() { + + handleSubmitNotifyLevel() { var channelId = this.state.channelId; var notifyLevel = this.state.notifyLevel; - if (this.state.quietMode) { - notifyLevel = 'quiet'; - } var data = {}; data.channel_id = channelId; @@ -82,26 +91,24 @@ export default class ChannelNotifications extends React.Component { } Client.updateNotifyLevel(data, - function success() { + () => { var member = ChannelStore.getMember(channelId); member.notify_level = notifyLevel; ChannelStore.setChannelMember(member); this.updateSection(''); - }.bind(this), - function error(err) { + }, + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } - handleRadioClick(notifyLevel) { - this.setState({notifyLevel: notifyLevel, quietMode: false}); + + handleUpdateNotifyLevel(notifyLevel) { + this.setState({notifyLevel}); React.findDOMNode(this.refs.modal).focus(); } - handleQuietToggle(quietMode) { - this.setState({notifyLevel: 'none', quietMode: quietMode}); - React.findDOMNode(this.refs.modal).focus(); - } - createDesktopSection(serverError) { + + createNotifyLevelSection(serverError) { var handleUpdateSection; const user = UserStore.getCurrentUser(); @@ -137,7 +144,7 @@ export default class ChannelNotifications extends React.Component { {`Global default (${globalNotifyLevelName})`} @@ -149,7 +156,7 @@ export default class ChannelNotifications extends React.Component { {'For all activity'} @@ -161,7 +168,7 @@ export default class ChannelNotifications extends React.Component { {'Only for mentions'} @@ -173,7 +180,7 @@ export default class ChannelNotifications extends React.Component { {'Never'} @@ -200,7 +207,7 @@ export default class ChannelNotifications extends React.Component { ); } - createQuietSection(serverError) { - var handleUpdateSection; - if (this.state.activeSection === 'quiet') { - var quietActive = [false, false]; - if (this.state.quietMode) { - quietActive[0] = true; - } else { - quietActive[1] = true; + + handleSubmitMarkUnreadLevel() { + const channelId = this.state.channelId; + const markUnreadLevel = this.state.markUnreadLevel; + + const data = { + channel_id: channelId, + user_id: UserStore.getCurrentId(), + mark_unread_level: markUnreadLevel + }; + + if (!data.mark_unread_level || data.mark_unread_level.length === 0) { + return; + } + + Client.updateMarkUnreadLevel(data, + () => { + var member = ChannelStore.getMember(channelId); + member.mark_unread_level = markUnreadLevel; + ChannelStore.setChannelMember(member); + this.updateSection(''); + }, + (err) => { + this.setState({serverError: err.message}); } + ); + } - var inputs = []; + handleUpdateMarkUnreadLevel(markUnreadLevel) { + this.setState({markUnreadLevel}); + React.findDOMNode(this.refs.modal).focus(); + } - inputs.push( + createMarkUnreadLevelSection(serverError) { + let content; + + if (this.state.activeSection === 'markUnreadLevel') { + const inputs = [(
-
+
-
+
- ); + )]; - inputs.push( -
-
- Enabling quiet mode will turn off desktop notifications and only mark the channel as unread if you have been mentioned. -
- ); - - handleUpdateSection = function updateSection(e) { + const handleUpdateSection = function handleUpdateSection(e) { this.updateSection(''); this.onListenerChange(); e.preventDefault(); }.bind(this); - return ( + const extraInfo = {'The channel name is bolded in the sidebar when there are unread messages. Selecting "Only for mentions" will bold the channel only when you are mentioned.'}; + + content = ( + ); + } else { + let describe; + + if (!this.state.markUnreadLevel || this.state.markUnreadLevel === 'all') { + describe = 'For all unread messages'; + } else { + describe = 'Only for mentions'; + } + + const handleUpdateSection = function handleUpdateSection(e) { + this.updateSection('markUnreadLevel'); + e.preventDefault(); + }.bind(this); + + content = ( + ); } - var describe; - if (this.state.quietMode) { - describe = 'On'; - } else { - describe = 'Off'; - } - - handleUpdateSection = function updateSection(e) { - this.updateSection('quiet'); - e.preventDefault(); - }.bind(this); - - return ( - - ); + return content; } + render() { var serverError = null; if (this.state.serverError) { serverError =
; } - var desktopSection = this.createDesktopSection(serverError); - - var quietSection = this.createQuietSection(serverError); - return (

- {desktopSection} + {this.createNotifyLevelSection(serverError)}
- {quietSection} + {this.createMarkUnreadLevelSection(serverError)}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 715e26197b..ce831be0d1 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -583,6 +583,21 @@ export function updateNotifyLevel(data, success, error) { }); } +export function updateMarkUnreadLevel(data, success, error) { + $.ajax({ + url: '/api/v1/channels/update_mark_unread_level', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(data), + success, + error: function onError(xhr, status, err) { + var e = handleError('updateMarkUnreadLevel', xhr, status, err); + error(e); + } + }); +} + export function joinChannel(id, success, error) { $.ajax({ url: '/api/v1/channels/' + id + '/join', From 9995c7ccfcb9ad0787439f476db55ac56fbecd04 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 23 Sep 2015 14:42:07 -0400 Subject: [PATCH 03/22] Added additional test cases and fixed existing ones for ChannelMember.MarkUnreadLevel --- api/channel_test.go | 82 +++++++++++++++++++++++++++++++++ model/channel_member_test.go | 11 +++++ model/client.go | 9 ++++ store/sql_channel_store_test.go | 16 +++++++ store/sql_post_store_test.go | 1 + 5 files changed, 119 insertions(+) diff --git a/api/channel_test.go b/api/channel_test.go index 7845ac499a..3276022fa4 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -885,6 +885,88 @@ func TestUpdateNotifyLevel(t *testing.T) { } } +func TestUpdateMarkUnreadLevel(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + data := make(map[string]string) + data["channel_id"] = channel1.Id + data["user_id"] = user.Id + data["mark_unread_level"] = model.CHANNEL_MARK_UNREAD_MENTION + + timeBeforeUpdate := model.GetMillis() + time.Sleep(100 * time.Millisecond) + + if _, err := Client.UpdateMarkUnreadLevel(data); err != nil { + t.Fatal(err) + } + + rget := Client.Must(Client.GetChannels("")) + rdata := rget.Data.(*model.ChannelList) + if len(rdata.Members) == 0 || rdata.Members[channel1.Id].MarkUnreadLevel != data["mark_unread_level"] { + t.Fatal("MarkUnreadLevel did not update properly") + } + + if rdata.Members[channel1.Id].LastUpdateAt <= timeBeforeUpdate { + t.Fatal("LastUpdateAt did not update") + } + + data["user_id"] = "junk" + if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + t.Fatal("Should have errored - bad user id") + } + + data["user_id"] = "12345678901234567890123456" + if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + t.Fatal("Should have errored - bad user id") + } + + data["user_id"] = user.Id + data["channel_id"] = "junk" + if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + t.Fatal("Should have errored - bad channel id") + } + + data["channel_id"] = "12345678901234567890123456" + if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + t.Fatal("Should have errored - bad channel id") + } + + data["channel_id"] = channel1.Id + data["mark_unread_level"] = "" + if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + t.Fatal("Should have errored - empty notify level") + } + + data["mark_unread_level"] = "junk" + if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + t.Fatal("Should have errored - bad notify level") + } + + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) + + Client.LoginByEmail(team.Name, user2.Email, "pwd") + + data["channel_id"] = channel1.Id + data["user_id"] = user2.Id + data["mark_unread_level"] = model.CHANNEL_MARK_UNREAD_MENTION + if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + t.Fatal("Should have errored - user not in channel") + } +} + func TestFuzzyChannel(t *testing.T) { Setup() diff --git a/model/channel_member_test.go b/model/channel_member_test.go index 3b64ffbf78..ae3da73ccb 100644 --- a/model/channel_member_test.go +++ b/model/channel_member_test.go @@ -32,6 +32,7 @@ func TestChannelMemberIsValid(t *testing.T) { o.Roles = "missing" o.NotifyLevel = CHANNEL_NOTIFY_ALL + o.MarkUnreadLevel = CHANNEL_MARK_UNREAD_ALL o.UserId = NewId() if err := o.IsValid(); err == nil { t.Fatal("should be invalid") @@ -53,6 +54,16 @@ func TestChannelMemberIsValid(t *testing.T) { t.Fatal(err) } + o.MarkUnreadLevel = "123456789012345678901" + if err := o.IsValid(); err == nil { + t.Fatal("should be invalid") + } + + o.MarkUnreadLevel = CHANNEL_MARK_UNREAD_ALL + if err := o.IsValid(); err != nil { + t.Fatal(err) + } + o.Roles = "" if err := o.IsValid(); err != nil { t.Fatal(err) diff --git a/model/client.go b/model/client.go index 26e00864d2..1d1c5a52c3 100644 --- a/model/client.go +++ b/model/client.go @@ -459,6 +459,15 @@ func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError) } } +func (c *Client) UpdateMarkUnreadLevel(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/channels/update_mark_unread_level", MapToJson(data)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil + } +} + func (c *Client) GetChannels(etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/channels/", "", etag); err != nil { return nil, err diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go index dabe399040..b974403067 100644 --- a/store/sql_channel_store_test.go +++ b/store/sql_channel_store_test.go @@ -136,12 +136,14 @@ func TestChannelStoreDelete(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o2.Id m2.UserId = m1.UserId m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m2)) if r := <-store.Channel().Delete(o1.Id, model.GetMillis()); r.Err != nil { @@ -223,12 +225,14 @@ func TestChannelMemberStore(t *testing.T) { o1.ChannelId = c1.Id o1.UserId = u1.Id o1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + o1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&o1)) o2 := model.ChannelMember{} o2.ChannelId = c1.Id o2.UserId = u2.Id o2.NotifyLevel = model.CHANNEL_NOTIFY_ALL + o2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&o2)) c1t2 := (<-store.Channel().Get(c1.Id)).Data.(*model.Channel) @@ -292,6 +296,7 @@ func TestChannelStorePermissionsTo(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) count := (<-store.Channel().CheckPermissionsTo(o1.TeamId, o1.Id, m1.UserId)).Data.(int64) @@ -372,18 +377,21 @@ func TestChannelStoreGetChannels(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m3.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m3)) cresult := <-store.Channel().GetChannels(o1.TeamId, m1.UserId) @@ -415,18 +423,21 @@ func TestChannelStoreGetMoreChannels(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m3.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m3)) o3 := model.Channel{} @@ -483,18 +494,21 @@ func TestChannelStoreGetChannelCounts(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m3.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m3)) cresult := <-store.Channel().GetChannelCounts(o1.TeamId, m1.UserId) @@ -524,6 +538,7 @@ func TestChannelStoreUpdateLastViewedAt(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) err := (<-store.Channel().UpdateLastViewedAt(m1.ChannelId, m1.UserId)).Err @@ -552,6 +567,7 @@ func TestChannelStoreIncrementMentionCount(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) err := (<-store.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId)).Err diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go index 257054033d..39761c51c5 100644 --- a/store/sql_post_store_test.go +++ b/store/sql_post_store_test.go @@ -485,6 +485,7 @@ func TestPostStoreSearch(t *testing.T) { m1.ChannelId = c1.Id m1.UserId = userId m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL + m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL Must(store.Channel().SaveMember(&m1)) c2 := &model.Channel{} From c4d50885a7d9c1716bc2cfe4e18a9065130bd045 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 23 Sep 2015 14:43:36 -0400 Subject: [PATCH 04/22] Implemented ChannelMember.MarkUnreadLevel so that it actually functions --- web/react/components/notify_counts.jsx | 2 +- web/react/components/sidebar.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/react/components/notify_counts.jsx b/web/react/components/notify_counts.jsx index 0b7c41b621..3df661d704 100644 --- a/web/react/components/notify_counts.jsx +++ b/web/react/components/notify_counts.jsx @@ -15,7 +15,7 @@ function getCountsStateFromStores() { count += channel.total_msg_count - channelMember.msg_count; } else if (channelMember.mention_count > 0) { count += channelMember.mention_count; - } else if (channelMember.notify_level !== 'quiet' && channel.total_msg_count - channelMember.msg_count > 0) { + } else if (channelMember.mark_unread_level !== 'mention' && channel.total_msg_count - channelMember.msg_count > 0) { count += 1; } }); diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index bc66ee03c9..5a4fb11697 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -334,7 +334,7 @@ export default class Sidebar extends React.Component { var unread = false; if (channelMember) { msgCount = channel.total_msg_count - channelMember.msg_count; - unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0; + unread = (msgCount > 0 && channelMember.mark_unread_level !== 'mention') || channelMember.mention_count > 0; } var titleClass = ''; From 101068140734ebc1a310560b8719fe21dacfb277 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 23 Sep 2015 14:44:17 -0400 Subject: [PATCH 05/22] Added channel id to updateLastViewed api calls to prevent multiple updates to different channels from clashing --- web/react/utils/async_client.jsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index ab29650003..7db3ef30d3 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -152,21 +152,23 @@ export function getChannel(id) { } export function updateLastViewedAt() { - if (isCallInProgress('updateLastViewed')) { + const channelId = ChannelStore.getCurrentId(); + + if (channelId === null) { return; } - if (ChannelStore.getCurrentId() == null) { + if (isCallInProgress(`updateLastViewed${channelId}`)) { return; } - callTracker.updateLastViewed = utils.getTimestamp(); + callTracker[`updateLastViewed${channelId}`] = utils.getTimestamp(); client.updateLastViewedAt( - ChannelStore.getCurrentId(), - function updateLastViewedAtSuccess() { + channelId, + () => { callTracker.updateLastViewed = 0; }, - function updateLastViewdAtFailure(err) { + (err) => { callTracker.updateLastViewed = 0; dispatchError(err, 'updateLastViewedAt'); } @@ -634,4 +636,4 @@ export function getMyTeam() { dispatchError(err, 'getMyTeam'); } ); -} \ No newline at end of file +} From 284a660a4784d43c18f02d3741acef9604455f30 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 23 Sep 2015 16:58:23 -0400 Subject: [PATCH 06/22] Removed deprecated constants related to quiet mode --- model/channel_member.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/model/channel_member.go b/model/channel_member.go index 1aafee43d8..58c73d54a0 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -15,7 +15,6 @@ const ( CHANNEL_NOTIFY_ALL = "all" CHANNEL_NOTIFY_MENTION = "mention" CHANNEL_NOTIFY_NONE = "none" - CHANNEL_NOTIFY_QUIET = "quiet" // no longer used, should be considered functionally equivalent to CHANNEL_NOTIFY_NONE CHANNEL_MARK_UNREAD_ALL = "all" CHANNEL_MARK_UNREAD_MENTION = "mention" ) @@ -87,8 +86,7 @@ func IsChannelNotifyLevelValid(notifyLevel string) bool { return notifyLevel == CHANNEL_NOTIFY_DEFAULT || notifyLevel == CHANNEL_NOTIFY_ALL || notifyLevel == CHANNEL_NOTIFY_MENTION || - notifyLevel == CHANNEL_NOTIFY_NONE || - notifyLevel == CHANNEL_NOTIFY_QUIET + notifyLevel == CHANNEL_NOTIFY_NONE } func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool { From 111fbb2495e88d69bec29971da8ddf086ac3f3b6 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 23 Sep 2015 16:58:50 -0400 Subject: [PATCH 07/22] Changed notification preference submission to only be updated when options have actually changed --- web/react/components/channel_notifications.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index c49e763330..eaa1c82556 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -81,6 +81,11 @@ export default class ChannelNotifications extends React.Component { var channelId = this.state.channelId; var notifyLevel = this.state.notifyLevel; + if (ChannelStore.getMember(channelId).notify_level === notifyLevel) { + this.updateSection(''); + return; + } + var data = {}; data.channel_id = channelId; data.user_id = UserStore.getCurrentId(); @@ -244,6 +249,11 @@ export default class ChannelNotifications extends React.Component { const channelId = this.state.channelId; const markUnreadLevel = this.state.markUnreadLevel; + if (ChannelStore.getMember(channelId).mark_unread_level === markUnreadLevel) { + this.updateSection(''); + return; + } + const data = { channel_id: channelId, user_id: UserStore.getCurrentId(), From c16b9de8dc4924cf2fb243579284e67f55cf3a47 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 30 Sep 2015 11:08:36 -0400 Subject: [PATCH 08/22] Replaced ChannelMember.MarkUnreadLevel with ChannelMember.NotifyProps --- api/channel.go | 52 ++++++--- api/channel_test.go | 82 ++++++++++---- model/channel_member.go | 34 ++++-- model/channel_member_test.go | 6 +- model/client.go | 4 +- store/sql_channel_store.go | 105 ++++++++++++------ store/sql_channel_store_test.go | 32 +++--- store/sql_post_store_test.go | 2 +- store/store.go | 2 +- .../components/channel_notifications.jsx | 16 +-- web/react/components/notify_counts.jsx | 2 +- web/react/components/sidebar.jsx | 2 +- web/react/utils/client.jsx | 7 +- 13 files changed, 227 insertions(+), 119 deletions(-) diff --git a/api/channel.go b/api/channel.go index b69fe6ea0e..5d54cdc6fe 100644 --- a/api/channel.go +++ b/api/channel.go @@ -24,7 +24,7 @@ func InitChannel(r *mux.Router) { sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST") sr.Handle("/update_desc", ApiUserRequired(updateChannelDesc)).Methods("POST") sr.Handle("/update_notify_level", ApiUserRequired(updateNotifyLevel)).Methods("POST") - sr.Handle("/update_mark_unread_level", ApiUserRequired(updateMarkUnreadLevel)).Methods("POST") + sr.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(joinChannel)).Methods("POST") @@ -77,7 +77,7 @@ func CreateChannel(c *Context, channel *model.Channel, addMember bool) (*model.C if addMember { cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId, Roles: model.CHANNEL_ROLE_ADMIN, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -136,7 +136,7 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model return nil, err } else { cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, Roles: "", - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -374,7 +374,7 @@ func JoinChannel(c *Context, channelId string, role string) { if channel.Type == model.CHANNEL_OPEN { cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, Roles: role, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { c.Err = cmresult.Err @@ -408,7 +408,7 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { err = result.Err } else { cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, Roles: channelRole, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err @@ -419,7 +419,7 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { err = result.Err } else { cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, Roles: channelRole, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err @@ -700,7 +700,8 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { } else { oUser := oresult.Data.(*model.User) - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, MarkUnreadLevel: model.CHANNEL_MARK_UNREAD_ALL} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, + NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { l4g.Error("Failed to add member user_id=%v channel_id=%v err=%v", userId, id, cmresult.Err) @@ -790,6 +791,7 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { } +// TODO remove me func updateNotifyLevel(c *Context, w http.ResponseWriter, r *http.Request) { data := model.MapFromJson(r.Body) userId := data["user_id"] @@ -828,8 +830,9 @@ func updateNotifyLevel(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(data))) } -func updateMarkUnreadLevel(c *Context, w http.ResponseWriter, r *http.Request) { +func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) { data := model.MapFromJson(r.Body) + userId := data["user_id"] if len(userId) != 26 { c.SetInvalidParam("updateMarkUnreadLevel", "user_id") @@ -842,26 +845,39 @@ func updateMarkUnreadLevel(c *Context, w http.ResponseWriter, r *http.Request) { return } - markUnreadLevel := data["mark_unread_level"] - if len(markUnreadLevel) == 0 || !model.IsChannelMarkUnreadLevelValid(markUnreadLevel) { - c.SetInvalidParam("updateMarkUnreadLevel", "mark_unread_level") - return - } - cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) - if !c.HasPermissionsToUser(userId, "updateMarkUnreadLevel") { + if !c.HasPermissionsToUser(userId, "updateNotifyLevel") { return } - if !c.HasPermissionsToChannel(cchan, "updateMarkUnreadLevel") { + if !c.HasPermissionsToChannel(cchan, "updateNotifyLevel") { return } - if result := <-Srv.Store.Channel().UpdateMarkUnreadLevel(channelId, userId, markUnreadLevel); result.Err != nil { + result := <-Srv.Store.Channel().GetMember(channelId, userId) + if result.Err != nil { c.Err = result.Err return } - w.Write([]byte(model.MapToJson(data))) + member := result.Data.(model.ChannelMember) + + // update whichever notify properties have been provided, but don't change the others + if markUnread, exists := data["mark_unread"]; exists { + member.NotifyProps["mark_unread"] = markUnread + } + + if desktop, exists := data["desktop"]; exists { + member.NotifyProps["desktop"] = desktop + } + + if result := <-Srv.Store.Channel().UpdateMember(&member); result.Err != nil { + c.Err = result.Err + return + } else { + // return the updated notify properties including any unchanged ones + w.Write([]byte(model.MapToJson(member.NotifyProps))) + } + } diff --git a/api/channel_test.go b/api/channel_test.go index 3276022fa4..0e9dbbd416 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -885,7 +885,7 @@ func TestUpdateNotifyLevel(t *testing.T) { } } -func TestUpdateMarkUnreadLevel(t *testing.T) { +func TestUpdateNotifyProps(t *testing.T) { Setup() team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} @@ -903,55 +903,94 @@ func TestUpdateMarkUnreadLevel(t *testing.T) { data := make(map[string]string) data["channel_id"] = channel1.Id data["user_id"] = user.Id - data["mark_unread_level"] = model.CHANNEL_MARK_UNREAD_MENTION + data["desktop"] = model.CHANNEL_NOTIFY_MENTION timeBeforeUpdate := model.GetMillis() time.Sleep(100 * time.Millisecond) - if _, err := Client.UpdateMarkUnreadLevel(data); err != nil { + // test updating desktop + if result, err := Client.UpdateNotifyProps(data); err != nil { t.Fatal(err) + } else if notifyProps := result.Data.(map[string]string); notifyProps["desktop"] != model.CHANNEL_NOTIFY_MENTION { + t.Fatal("NotifyProps[\"desktop\"] did not update properly") + } else if notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_ALL { + t.Fatalf("NotifyProps[\"mark_unread\"] changed to %v", notifyProps["mark_unread"]) } rget := Client.Must(Client.GetChannels("")) rdata := rget.Data.(*model.ChannelList) - if len(rdata.Members) == 0 || rdata.Members[channel1.Id].MarkUnreadLevel != data["mark_unread_level"] { - t.Fatal("MarkUnreadLevel did not update properly") - } - - if rdata.Members[channel1.Id].LastUpdateAt <= timeBeforeUpdate { + if len(rdata.Members) == 0 || rdata.Members[channel1.Id].NotifyProps["desktop"] != data["desktop"] { + t.Fatal("NotifyProps[\"desktop\"] did not update properly") + } else if rdata.Members[channel1.Id].LastUpdateAt <= timeBeforeUpdate { t.Fatal("LastUpdateAt did not update") } + // test an empty update + delete(data, "desktop") + + if result, err := Client.UpdateNotifyProps(data); err != nil { + t.Fatal(err) + } else if notifyProps := result.Data.(map[string]string); notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_ALL { + t.Fatalf("NotifyProps[\"mark_unread\"] changed to %v", notifyProps["mark_unread"]) + } else if notifyProps["desktop"] != model.CHANNEL_NOTIFY_MENTION { + t.Fatalf("NotifyProps[\"desktop\"] changed to %v", notifyProps["desktop"]) + } + + // test updating mark unread + data["mark_unread"] = model.CHANNEL_MARK_UNREAD_MENTION + + if result, err := Client.UpdateNotifyProps(data); err != nil { + t.Fatal(err) + } else if notifyProps := result.Data.(map[string]string); notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_MENTION { + t.Fatal("NotifyProps[\"mark_unread\"] did not update properly") + } else if notifyProps["desktop"] != model.CHANNEL_NOTIFY_MENTION { + t.Fatalf("NotifyProps[\"desktop\"] changed to %v", notifyProps["desktop"]) + } + + // test updating both + data["desktop"] = model.CHANNEL_NOTIFY_NONE + data["mark_unread"] = model.CHANNEL_MARK_UNREAD_MENTION + + if result, err := Client.UpdateNotifyProps(data); err != nil { + t.Fatal(err) + } else if notifyProps := result.Data.(map[string]string); notifyProps["desktop"] != model.CHANNEL_NOTIFY_NONE { + t.Fatal("NotifyProps[\"desktop\"] did not update properly") + } else if notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_MENTION { + t.Fatal("NotifyProps[\"mark_unread\"] did not update properly") + } + + // test error cases data["user_id"] = "junk" - if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + if _, err := Client.UpdateNotifyProps(data); err == nil { t.Fatal("Should have errored - bad user id") } data["user_id"] = "12345678901234567890123456" - if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + if _, err := Client.UpdateNotifyProps(data); err == nil { t.Fatal("Should have errored - bad user id") } data["user_id"] = user.Id data["channel_id"] = "junk" - if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + if _, err := Client.UpdateNotifyProps(data); err == nil { t.Fatal("Should have errored - bad channel id") } data["channel_id"] = "12345678901234567890123456" - if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + if _, err := Client.UpdateNotifyProps(data); err == nil { t.Fatal("Should have errored - bad channel id") } - data["channel_id"] = channel1.Id - data["mark_unread_level"] = "" - if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { - t.Fatal("Should have errored - empty notify level") + data["desktop"] = "junk" + data["mark_unread"] = model.CHANNEL_MARK_UNREAD_ALL + if _, err := Client.UpdateNotifyProps(data); err == nil { + t.Fatal("Should have errored - bad desktop notify level") } - data["mark_unread_level"] = "junk" - if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { - t.Fatal("Should have errored - bad notify level") + data["desktop"] = model.CHANNEL_NOTIFY_ALL + data["mark_unread"] = "junk" + if _, err := Client.UpdateNotifyProps(data); err == nil { + t.Fatal("Should have errored - bad mark unread level") } user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} @@ -961,8 +1000,9 @@ func TestUpdateMarkUnreadLevel(t *testing.T) { data["channel_id"] = channel1.Id data["user_id"] = user2.Id - data["mark_unread_level"] = model.CHANNEL_MARK_UNREAD_MENTION - if _, err := Client.UpdateMarkUnreadLevel(data); err == nil { + data["desktop"] = model.CHANNEL_NOTIFY_MENTION + data["mark_unread"] = model.CHANNEL_MARK_UNREAD_MENTION + if _, err := Client.UpdateNotifyLevel(data); err == nil { t.Fatal("Should have errored - user not in channel") } } diff --git a/model/channel_member.go b/model/channel_member.go index 58c73d54a0..f4a9a98363 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -20,15 +20,15 @@ const ( ) type ChannelMember struct { - ChannelId string `json:"channel_id"` - UserId string `json:"user_id"` - Roles string `json:"roles"` - LastViewedAt int64 `json:"last_viewed_at"` - MsgCount int64 `json:"msg_count"` - MentionCount int64 `json:"mention_count"` - NotifyLevel string `json:"notify_level"` - MarkUnreadLevel string `json:"mark_unread_level"` - LastUpdateAt int64 `json:"last_update_at"` + ChannelId string `json:"channel_id"` + UserId string `json:"user_id"` + Roles string `json:"roles"` + LastViewedAt int64 `json:"last_viewed_at"` + MsgCount int64 `json:"msg_count"` + MentionCount int64 `json:"mention_count"` + NotifyProps StringMap `json:"notify_props"` + NotifyLevel string `json:"notify_level"` + LastUpdateAt int64 `json:"last_update_at"` } func (o *ChannelMember) ToJson() string { @@ -71,8 +71,9 @@ func (o *ChannelMember) IsValid() *AppError { return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+o.NotifyLevel) } - if len(o.MarkUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(o.MarkUnreadLevel) { - return NewAppError("ChannelMember.IsValid", "Invalid mark unread level", "mark_unread_level="+o.MarkUnreadLevel) + markUnreadLevel := o.NotifyProps["mark_unread"] + if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) { + return NewAppError("ChannelMember.IsValid", "Invalid mark unread level", "mark_unread_level="+markUnreadLevel) } return nil @@ -82,6 +83,10 @@ func (o *ChannelMember) PreSave() { o.LastUpdateAt = GetMillis() } +func (o *ChannelMember) PreUpdate() { + o.LastUpdateAt = GetMillis() +} + func IsChannelNotifyLevelValid(notifyLevel string) bool { return notifyLevel == CHANNEL_NOTIFY_DEFAULT || notifyLevel == CHANNEL_NOTIFY_ALL || @@ -92,3 +97,10 @@ func IsChannelNotifyLevelValid(notifyLevel string) bool { func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool { return markUnreadLevel == CHANNEL_MARK_UNREAD_ALL || markUnreadLevel == CHANNEL_MARK_UNREAD_MENTION } + +func GetDefaultChannelNotifyProps() StringMap { + return StringMap{ + "desktop": CHANNEL_NOTIFY_DEFAULT, + "mark_unread": CHANNEL_MARK_UNREAD_ALL, + } +} diff --git a/model/channel_member_test.go b/model/channel_member_test.go index ae3da73ccb..5dc34ae566 100644 --- a/model/channel_member_test.go +++ b/model/channel_member_test.go @@ -32,7 +32,7 @@ func TestChannelMemberIsValid(t *testing.T) { o.Roles = "missing" o.NotifyLevel = CHANNEL_NOTIFY_ALL - o.MarkUnreadLevel = CHANNEL_MARK_UNREAD_ALL + o.NotifyProps = GetDefaultChannelNotifyProps() o.UserId = NewId() if err := o.IsValid(); err == nil { t.Fatal("should be invalid") @@ -54,12 +54,12 @@ func TestChannelMemberIsValid(t *testing.T) { t.Fatal(err) } - o.MarkUnreadLevel = "123456789012345678901" + o.NotifyProps["mark_unread"] = "123456789012345678901" if err := o.IsValid(); err == nil { t.Fatal("should be invalid") } - o.MarkUnreadLevel = CHANNEL_MARK_UNREAD_ALL + o.NotifyProps["mark_unread"] = CHANNEL_MARK_UNREAD_ALL if err := o.IsValid(); err != nil { t.Fatal(err) } diff --git a/model/client.go b/model/client.go index 1d1c5a52c3..bba839809c 100644 --- a/model/client.go +++ b/model/client.go @@ -459,8 +459,8 @@ func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError) } } -func (c *Client) UpdateMarkUnreadLevel(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/channels/update_mark_unread_level", MapToJson(data)); err != nil { +func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/channels/update_notify_props", MapToJson(data)); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 892ee1398b..f453c35897 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -4,6 +4,7 @@ package store import ( + l4g "code.google.com/p/log4go" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" ) @@ -30,14 +31,56 @@ func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore { tablem.ColMap("ChannelId").SetMaxSize(26) tablem.ColMap("UserId").SetMaxSize(26) tablem.ColMap("Roles").SetMaxSize(64) - tablem.ColMap("NotifyLevel").SetMaxSize(20) + tablem.ColMap("NotifyProps").SetMaxSize(2000) } return s } func (s SqlChannelStore) UpgradeSchemaIfNeeded() { - s.CreateColumnIfNotExists("ChannelMembers", "MarkUnreadLevel", "varchar(20)", "varchar(20)", model.CHANNEL_MARK_UNREAD_ALL) + if s.CreateColumnIfNotExists("ChannelMembers", "NotifyProps", "varchar(2000)", "varchar(2000)", "{}") { + // populate NotifyProps from existing NotifyLevel field + + // set default values + _, err := s.GetMaster().Exec( + `UPDATE + ChannelMembers + SET + NotifyProps = CONCAT('{"desktop":"', CONCAT(NotifyLevel, '","mark_unread":"` + model.CHANNEL_MARK_UNREAD_ALL + `"}'))`) + if err != nil { + l4g.Error("Unable to set default values for ChannelMembers.NotifyProps") + l4g.Error(err.Error()) + } + + // assume channels with all notifications enabled are just using the default settings + _, err = s.GetMaster().Exec( + `UPDATE + ChannelMembers + SET + NotifyProps = '{"desktop":"` + model.CHANNEL_NOTIFY_DEFAULT + `","mark_unread":"` + model.CHANNEL_MARK_UNREAD_ALL + `"}' + WHERE + NotifyLevel = '` + model.CHANNEL_NOTIFY_ALL + `'`) + if err != nil { + l4g.Error("Unable to set values for ChannelMembers.NotifyProps when members previously had notifyLevel=all") + l4g.Error(err.Error()) + } + + // set quiet mode channels to have no notifications and only mark the channel unread on mentions + _, err = s.GetMaster().Exec( + `UPDATE + ChannelMembers + SET + NotifyProps = '{"desktop":"` + model.CHANNEL_NOTIFY_NONE + `","mark_unread":"` + model.CHANNEL_MARK_UNREAD_MENTION + `"}' + WHERE + NotifyLevel = 'quiet'`) + if err != nil { + l4g.Error("Unable to set values for ChannelMembers.NotifyProps when members previously had notifyLevel=quiet") + l4g.Error(err.Error()) + } + + // TODO uncomment me + // s.RemoveColumnIfExists("ChannelMembers", "NotifyLevel") + } } func (s SqlChannelStore) CreateIndexesIfNotExists() { @@ -387,6 +430,34 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel { return storeChannel } +func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + member.PreUpdate() + + if result.Err = member.IsValid(); result.Err != nil { + storeChannel <- result + close(storeChannel) + return + } + + if _, err := s.GetMaster().Update(member); err != nil { + result.Err = model.NewAppError("SqlChannelStore.UpdateMember", "We encounted an error updating the channel member", + "channel_id="+member.ChannelId+", "+"user_id="+member.UserId+", "+err.Error()) + } else { + result.Data = member + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + func (s SqlChannelStore) GetMembers(channelId string) StoreChannel { storeChannel := make(StoreChannel) @@ -650,6 +721,7 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string) return storeChannel } +// TODO remove me func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string) StoreChannel { storeChannel := make(StoreChannel) @@ -679,35 +751,6 @@ func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string return storeChannel } -func (s SqlChannelStore) UpdateMarkUnreadLevel(channelId, userId, markUnreadLevel string) StoreChannel { - storeChannel := make(StoreChannel) - - go func() { - result := StoreResult{} - - updateAt := model.GetMillis() - - _, err := s.GetMaster().Exec( - `UPDATE - ChannelMembers - SET - MarkUnreadLevel = :MarkUnreadLevel, - LastUpdateAt = :LastUpdateAt - WHERE - UserId = :UserId - AND ChannelId = :ChannelId`, - map[string]interface{}{"ChannelId": channelId, "UserId": userId, "MarkUnreadLevel": markUnreadLevel, "LastUpdateAt": updateAt}) - if err != nil { - result.Err = model.NewAppError("SqlChannelStore.UpdateMarkUnreadLevel", "We couldn't update the mark unread level", "channel_id="+channelId+", user_id="+userId+", "+err.Error()) - } - - storeChannel <- result - close(storeChannel) - }() - - return storeChannel -} - func (s SqlChannelStore) GetForExport(teamId string) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go index b974403067..45c05645e4 100644 --- a/store/sql_channel_store_test.go +++ b/store/sql_channel_store_test.go @@ -136,14 +136,14 @@ func TestChannelStoreDelete(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o2.Id m2.UserId = m1.UserId m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) if r := <-store.Channel().Delete(o1.Id, model.GetMillis()); r.Err != nil { @@ -225,14 +225,14 @@ func TestChannelMemberStore(t *testing.T) { o1.ChannelId = c1.Id o1.UserId = u1.Id o1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - o1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + o1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&o1)) o2 := model.ChannelMember{} o2.ChannelId = c1.Id o2.UserId = u2.Id o2.NotifyLevel = model.CHANNEL_NOTIFY_ALL - o2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + o2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&o2)) c1t2 := (<-store.Channel().Get(c1.Id)).Data.(*model.Channel) @@ -296,7 +296,7 @@ func TestChannelStorePermissionsTo(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) count := (<-store.Channel().CheckPermissionsTo(o1.TeamId, o1.Id, m1.UserId)).Data.(int64) @@ -377,21 +377,21 @@ func TestChannelStoreGetChannels(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m3.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m3.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m3)) cresult := <-store.Channel().GetChannels(o1.TeamId, m1.UserId) @@ -423,21 +423,21 @@ func TestChannelStoreGetMoreChannels(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m3.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m3.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m3)) o3 := model.Channel{} @@ -494,21 +494,21 @@ func TestChannelStoreGetChannelCounts(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m2.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m3.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m3.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m3)) cresult := <-store.Channel().GetChannelCounts(o1.TeamId, m1.UserId) @@ -538,7 +538,7 @@ func TestChannelStoreUpdateLastViewedAt(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) err := (<-store.Channel().UpdateLastViewedAt(m1.ChannelId, m1.UserId)).Err @@ -567,7 +567,7 @@ func TestChannelStoreIncrementMentionCount(t *testing.T) { m1.ChannelId = o1.Id m1.UserId = model.NewId() m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) err := (<-store.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId)).Err diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go index 39761c51c5..c8f7e75875 100644 --- a/store/sql_post_store_test.go +++ b/store/sql_post_store_test.go @@ -485,7 +485,7 @@ func TestPostStoreSearch(t *testing.T) { m1.ChannelId = c1.Id m1.UserId = userId m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL - m1.MarkUnreadLevel = model.CHANNEL_MARK_UNREAD_ALL + m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) c2 := &model.Channel{} diff --git a/store/store.go b/store/store.go index 83366b79e0..2cffe99dea 100644 --- a/store/store.go +++ b/store/store.go @@ -62,6 +62,7 @@ type ChannelStore interface { GetForExport(teamId string) StoreChannel SaveMember(member *model.ChannelMember) StoreChannel + UpdateMember(member *model.ChannelMember) StoreChannel GetMembers(channelId string) StoreChannel GetMember(channelId string, userId string) StoreChannel RemoveMember(channelId string, userId string) StoreChannel @@ -72,7 +73,6 @@ type ChannelStore interface { UpdateLastViewedAt(channelId string, userId string) StoreChannel IncrementMentionCount(channelId string, userId string) StoreChannel UpdateNotifyLevel(channelId string, userId string, notifyLevel string) StoreChannel - UpdateMarkUnreadLevel(channelId string, userId string, markUnreadLevel string) StoreChannel } type PostStore interface { diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index eaa1c82556..55c0b54387 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -42,7 +42,7 @@ export default class ChannelNotifications extends React.Component { const member = ChannelStore.getMember(channelId); var notifyLevel = member.notify_level; - var markUnreadLevel = member.mark_unread_level; + var markUnreadLevel = member.notify_props.mark_unread; this.setState({ notifyLevel, @@ -63,7 +63,7 @@ export default class ChannelNotifications extends React.Component { const member = ChannelStore.getMember(this.state.channelId); var notifyLevel = member.notify_level; - var markUnreadLevel = member.mark_unread_level; + var markUnreadLevel = member.notify_props.mark_unread; var newState = this.state; newState.notifyLevel = notifyLevel; @@ -249,7 +249,7 @@ export default class ChannelNotifications extends React.Component { const channelId = this.state.channelId; const markUnreadLevel = this.state.markUnreadLevel; - if (ChannelStore.getMember(channelId).mark_unread_level === markUnreadLevel) { + if (ChannelStore.getMember(channelId).notify_props.mark_unread === markUnreadLevel) { this.updateSection(''); return; } @@ -257,17 +257,13 @@ export default class ChannelNotifications extends React.Component { const data = { channel_id: channelId, user_id: UserStore.getCurrentId(), - mark_unread_level: markUnreadLevel + mark_unread: markUnreadLevel }; - if (!data.mark_unread_level || data.mark_unread_level.length === 0) { - return; - } - - Client.updateMarkUnreadLevel(data, + Client.updateNotifyProps(data, () => { var member = ChannelStore.getMember(channelId); - member.mark_unread_level = markUnreadLevel; + member.notify_props.mark_unread = markUnreadLevel; ChannelStore.setChannelMember(member); this.updateSection(''); }, diff --git a/web/react/components/notify_counts.jsx b/web/react/components/notify_counts.jsx index 3df661d704..f34b4669f1 100644 --- a/web/react/components/notify_counts.jsx +++ b/web/react/components/notify_counts.jsx @@ -15,7 +15,7 @@ function getCountsStateFromStores() { count += channel.total_msg_count - channelMember.msg_count; } else if (channelMember.mention_count > 0) { count += channelMember.mention_count; - } else if (channelMember.mark_unread_level !== 'mention' && channel.total_msg_count - channelMember.msg_count > 0) { + } else if (channelMember.notify_props.mark_unread !== 'mention' && channel.total_msg_count - channelMember.msg_count > 0) { count += 1; } }); diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 5a4fb11697..821d7fd4aa 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -334,7 +334,7 @@ export default class Sidebar extends React.Component { var unread = false; if (channelMember) { msgCount = channel.total_msg_count - channelMember.msg_count; - unread = (msgCount > 0 && channelMember.mark_unread_level !== 'mention') || channelMember.mention_count > 0; + unread = (msgCount > 0 && channelMember.notify_props.mark_unread !== 'mention') || channelMember.mention_count > 0; } var titleClass = ''; diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index ce831be0d1..041c00516c 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -568,6 +568,7 @@ export function updateChannelDesc(data, success, error) { track('api', 'api_channels_desc'); } +// TODO remove me export function updateNotifyLevel(data, success, error) { $.ajax({ url: '/api/v1/channels/update_notify_level', @@ -583,16 +584,16 @@ export function updateNotifyLevel(data, success, error) { }); } -export function updateMarkUnreadLevel(data, success, error) { +export function updateNotifyProps(data, success, error) { $.ajax({ - url: '/api/v1/channels/update_mark_unread_level', + url: '/api/v1/channels/update_notify_props', dataType: 'json', contentType: 'application/json', type: 'POST', data: JSON.stringify(data), success, error: function onError(xhr, status, err) { - var e = handleError('updateMarkUnreadLevel', xhr, status, err); + var e = handleError('updateNotifyProps', xhr, status, err); error(e); } }); From c9a0030551f289241407743fbd21080cd8a358a4 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 30 Sep 2015 12:28:28 -0400 Subject: [PATCH 09/22] Moved ChannelMember.NotifyLevel into ChannelMember.NotifyProps --- api/channel.go | 62 +++----------- api/channel_benchmark_test.go | 11 +-- api/channel_test.go | 84 +------------------ model/channel_member.go | 6 +- model/channel_member_test.go | 7 +- model/client.go | 9 -- store/sql_channel_store.go | 33 +------- store/sql_channel_store_test.go | 16 ---- store/sql_post_store_test.go | 1 - store/store.go | 1 - .../components/channel_notifications.jsx | 16 ++-- web/react/components/sidebar.jsx | 2 +- web/react/utils/client.jsx | 16 ---- 13 files changed, 31 insertions(+), 233 deletions(-) diff --git a/api/channel.go b/api/channel.go index 5d54cdc6fe..6494e35289 100644 --- a/api/channel.go +++ b/api/channel.go @@ -23,7 +23,6 @@ func InitChannel(r *mux.Router) { sr.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST") sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST") sr.Handle("/update_desc", ApiUserRequired(updateChannelDesc)).Methods("POST") - sr.Handle("/update_notify_level", ApiUserRequired(updateNotifyLevel)).Methods("POST") sr.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET") @@ -76,8 +75,8 @@ func CreateChannel(c *Context, channel *model.Channel, addMember bool) (*model.C sc := result.Data.(*model.Channel) if addMember { - cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId, Roles: model.CHANNEL_ROLE_ADMIN, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} + cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId, + Roles: model.CHANNEL_ROLE_ADMIN, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -135,8 +134,7 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model if sc, err := CreateChannel(c, channel, true); err != nil { return nil, err } else { - cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, Roles: "", - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} + cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, Roles: "", NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err @@ -373,8 +371,8 @@ func JoinChannel(c *Context, channelId string, role string) { } if channel.Type == model.CHANNEL_OPEN { - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, Roles: role, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, + Roles: role, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { c.Err = cmresult.Err @@ -407,8 +405,8 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { if result := <-Srv.Store.Channel().GetByName(user.TeamId, "town-square"); result.Err != nil { err = result.Err } else { - cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, Roles: channelRole, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} + cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, + Roles: channelRole, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err @@ -418,8 +416,8 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError { if result := <-Srv.Store.Channel().GetByName(user.TeamId, "off-topic"); result.Err != nil { err = result.Err } else { - cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, Roles: channelRole, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} + cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, + Roles: channelRole, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { err = cmResult.Err @@ -700,8 +698,7 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { } else { oUser := oresult.Data.(*model.User) - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, - NotifyLevel: model.CHANNEL_NOTIFY_DEFAULT, NotifyProps: model.GetDefaultChannelNotifyProps()} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyProps: model.GetDefaultChannelNotifyProps()} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { l4g.Error("Failed to add member user_id=%v channel_id=%v err=%v", userId, id, cmresult.Err) @@ -791,45 +788,6 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { } -// TODO remove me -func updateNotifyLevel(c *Context, w http.ResponseWriter, r *http.Request) { - data := model.MapFromJson(r.Body) - userId := data["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updateNotifyLevel", "user_id") - return - } - - channelId := data["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("updateNotifyLevel", "channel_id") - return - } - - notifyLevel := data["notify_level"] - if len(notifyLevel) == 0 || !model.IsChannelNotifyLevelValid(notifyLevel) { - c.SetInvalidParam("updateNotifyLevel", "notify_level") - return - } - - cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) - - if !c.HasPermissionsToUser(userId, "updateNotifyLevel") { - return - } - - if !c.HasPermissionsToChannel(cchan, "updateNotifyLevel") { - return - } - - if result := <-Srv.Store.Channel().UpdateNotifyLevel(channelId, userId, notifyLevel); result.Err != nil { - c.Err = result.Err - return - } - - w.Write([]byte(model.MapToJson(data))) -} - func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) { data := model.MapFromJson(r.Body) diff --git a/api/channel_benchmark_test.go b/api/channel_benchmark_test.go index 77e679c147..7820f4a034 100644 --- a/api/channel_benchmark_test.go +++ b/api/channel_benchmark_test.go @@ -255,7 +255,7 @@ func BenchmarkRemoveChannelMember(b *testing.B) { } } -func BenchmarkUpdateNotifyLevel(b *testing.B) { +func BenchmarkUpdateNotifyProps(b *testing.B) { var ( NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS} ) @@ -271,9 +271,10 @@ func BenchmarkUpdateNotifyLevel(b *testing.B) { for i := range data { newmap := map[string]string{ - "channel_id": channels[i].Id, - "user_id": user.Id, - "notify_level": model.CHANNEL_NOTIFY_MENTION, + "channel_id": channels[i].Id, + "user_id": user.Id, + "desktop": model.CHANNEL_NOTIFY_MENTION, + "mark_unread": model.CHANNEL_MARK_UNREAD_MENTION, } data[i] = newmap } @@ -282,7 +283,7 @@ func BenchmarkUpdateNotifyLevel(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { for j := range channels { - Client.Must(Client.UpdateNotifyLevel(data[j])) + Client.Must(Client.UpdateNotifyProps(data[j])) } } } diff --git a/api/channel_test.go b/api/channel_test.go index 0e9dbbd416..e6c7ed80e6 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -803,88 +803,6 @@ func TestRemoveChannelMember(t *testing.T) { } -func TestUpdateNotifyLevel(t *testing.T) { - Setup() - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user.Id)) - - Client.LoginByEmail(team.Name, user.Email, "pwd") - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - data := make(map[string]string) - data["channel_id"] = channel1.Id - data["user_id"] = user.Id - data["notify_level"] = model.CHANNEL_NOTIFY_MENTION - - timeBeforeUpdate := model.GetMillis() - time.Sleep(100 * time.Millisecond) - - if _, err := Client.UpdateNotifyLevel(data); err != nil { - t.Fatal(err) - } - - rget := Client.Must(Client.GetChannels("")) - rdata := rget.Data.(*model.ChannelList) - if len(rdata.Members) == 0 || rdata.Members[channel1.Id].NotifyLevel != data["notify_level"] { - t.Fatal("NotifyLevel did not update properly") - } - - if rdata.Members[channel1.Id].LastUpdateAt <= timeBeforeUpdate { - t.Fatal("LastUpdateAt did not update") - } - - data["user_id"] = "junk" - if _, err := Client.UpdateNotifyLevel(data); err == nil { - t.Fatal("Should have errored - bad user id") - } - - data["user_id"] = "12345678901234567890123456" - if _, err := Client.UpdateNotifyLevel(data); err == nil { - t.Fatal("Should have errored - bad user id") - } - - data["user_id"] = user.Id - data["channel_id"] = "junk" - if _, err := Client.UpdateNotifyLevel(data); err == nil { - t.Fatal("Should have errored - bad channel id") - } - - data["channel_id"] = "12345678901234567890123456" - if _, err := Client.UpdateNotifyLevel(data); err == nil { - t.Fatal("Should have errored - bad channel id") - } - - data["channel_id"] = channel1.Id - data["notify_level"] = "" - if _, err := Client.UpdateNotifyLevel(data); err == nil { - t.Fatal("Should have errored - empty notify level") - } - - data["notify_level"] = "junk" - if _, err := Client.UpdateNotifyLevel(data); err == nil { - t.Fatal("Should have errored - bad notify level") - } - - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - - Client.LoginByEmail(team.Name, user2.Email, "pwd") - - data["channel_id"] = channel1.Id - data["user_id"] = user2.Id - data["notify_level"] = model.CHANNEL_NOTIFY_MENTION - if _, err := Client.UpdateNotifyLevel(data); err == nil { - t.Fatal("Should have errored - user not in channel") - } -} - func TestUpdateNotifyProps(t *testing.T) { Setup() @@ -1002,7 +920,7 @@ func TestUpdateNotifyProps(t *testing.T) { data["user_id"] = user2.Id data["desktop"] = model.CHANNEL_NOTIFY_MENTION data["mark_unread"] = model.CHANNEL_MARK_UNREAD_MENTION - if _, err := Client.UpdateNotifyLevel(data); err == nil { + if _, err := Client.UpdateNotifyProps(data); err == nil { t.Fatal("Should have errored - user not in channel") } } diff --git a/model/channel_member.go b/model/channel_member.go index f4a9a98363..3ae6127000 100644 --- a/model/channel_member.go +++ b/model/channel_member.go @@ -27,7 +27,6 @@ type ChannelMember struct { MsgCount int64 `json:"msg_count"` MentionCount int64 `json:"mention_count"` NotifyProps StringMap `json:"notify_props"` - NotifyLevel string `json:"notify_level"` LastUpdateAt int64 `json:"last_update_at"` } @@ -67,8 +66,9 @@ func (o *ChannelMember) IsValid() *AppError { } } - if len(o.NotifyLevel) > 20 || !IsChannelNotifyLevelValid(o.NotifyLevel) { - return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+o.NotifyLevel) + notifyLevel := o.NotifyProps["desktop"] + if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) { + return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+notifyLevel) } markUnreadLevel := o.NotifyProps["mark_unread"] diff --git a/model/channel_member_test.go b/model/channel_member_test.go index 5dc34ae566..edbb46e9bb 100644 --- a/model/channel_member_test.go +++ b/model/channel_member_test.go @@ -31,7 +31,6 @@ func TestChannelMemberIsValid(t *testing.T) { } o.Roles = "missing" - o.NotifyLevel = CHANNEL_NOTIFY_ALL o.NotifyProps = GetDefaultChannelNotifyProps() o.UserId = NewId() if err := o.IsValid(); err == nil { @@ -39,17 +38,17 @@ func TestChannelMemberIsValid(t *testing.T) { } o.Roles = CHANNEL_ROLE_ADMIN - o.NotifyLevel = "junk" + o.NotifyProps["desktop"] = "junk" if err := o.IsValid(); err == nil { t.Fatal("should be invalid") } - o.NotifyLevel = "123456789012345678901" + o.NotifyProps["desktop"] = "123456789012345678901" if err := o.IsValid(); err == nil { t.Fatal("should be invalid") } - o.NotifyLevel = CHANNEL_NOTIFY_ALL + o.NotifyProps["desktop"] = CHANNEL_NOTIFY_ALL if err := o.IsValid(); err != nil { t.Fatal(err) } diff --git a/model/client.go b/model/client.go index bba839809c..a291cc4f23 100644 --- a/model/client.go +++ b/model/client.go @@ -450,15 +450,6 @@ func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError) } } -func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/channels/update_notify_level", MapToJson(data)); err != nil { - return nil, err - } else { - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) { if r, err := c.DoApiPost("/channels/update_notify_props", MapToJson(data)); err != nil { return nil, err diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index f453c35897..3bbe7e716e 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -78,8 +78,7 @@ func (s SqlChannelStore) UpgradeSchemaIfNeeded() { l4g.Error(err.Error()) } - // TODO uncomment me - // s.RemoveColumnIfExists("ChannelMembers", "NotifyLevel") + s.RemoveColumnIfExists("ChannelMembers", "NotifyLevel") } } @@ -721,36 +720,6 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string) return storeChannel } -// TODO remove me -func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string) StoreChannel { - storeChannel := make(StoreChannel) - - go func() { - result := StoreResult{} - - updateAt := model.GetMillis() - - _, err := s.GetMaster().Exec( - `UPDATE - ChannelMembers - SET - NotifyLevel = :NotifyLevel, - LastUpdateAt = :LastUpdateAt - WHERE - UserId = :UserId - AND ChannelId = :ChannelId`, - map[string]interface{}{"ChannelId": channelId, "UserId": userId, "NotifyLevel": notifyLevel, "LastUpdateAt": updateAt}) - if err != nil { - result.Err = model.NewAppError("SqlChannelStore.UpdateNotifyLevel", "We couldn't update the notify level", "channel_id="+channelId+", user_id="+userId+", "+err.Error()) - } - - storeChannel <- result - close(storeChannel) - }() - - return storeChannel -} - func (s SqlChannelStore) GetForExport(teamId string) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go index 45c05645e4..b6d05684bc 100644 --- a/store/sql_channel_store_test.go +++ b/store/sql_channel_store_test.go @@ -135,14 +135,12 @@ func TestChannelStoreDelete(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = o1.Id m1.UserId = model.NewId() - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o2.Id m2.UserId = m1.UserId - m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) @@ -224,14 +222,12 @@ func TestChannelMemberStore(t *testing.T) { o1 := model.ChannelMember{} o1.ChannelId = c1.Id o1.UserId = u1.Id - o1.NotifyLevel = model.CHANNEL_NOTIFY_ALL o1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&o1)) o2 := model.ChannelMember{} o2.ChannelId = c1.Id o2.UserId = u2.Id - o2.NotifyLevel = model.CHANNEL_NOTIFY_ALL o2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&o2)) @@ -295,7 +291,6 @@ func TestChannelStorePermissionsTo(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = o1.Id m1.UserId = model.NewId() - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) @@ -376,21 +371,18 @@ func TestChannelStoreGetChannels(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = o1.Id m1.UserId = model.NewId() - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() - m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() - m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL m3.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m3)) @@ -422,21 +414,18 @@ func TestChannelStoreGetMoreChannels(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = o1.Id m1.UserId = model.NewId() - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() - m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() - m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL m3.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m3)) @@ -493,21 +482,18 @@ func TestChannelStoreGetChannelCounts(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = o1.Id m1.UserId = model.NewId() - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) m2 := model.ChannelMember{} m2.ChannelId = o1.Id m2.UserId = model.NewId() - m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL m2.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m2)) m3 := model.ChannelMember{} m3.ChannelId = o2.Id m3.UserId = model.NewId() - m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL m3.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m3)) @@ -537,7 +523,6 @@ func TestChannelStoreUpdateLastViewedAt(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = o1.Id m1.UserId = model.NewId() - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) @@ -566,7 +551,6 @@ func TestChannelStoreIncrementMentionCount(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = o1.Id m1.UserId = model.NewId() - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go index c8f7e75875..6a6364dc8b 100644 --- a/store/sql_post_store_test.go +++ b/store/sql_post_store_test.go @@ -484,7 +484,6 @@ func TestPostStoreSearch(t *testing.T) { m1 := model.ChannelMember{} m1.ChannelId = c1.Id m1.UserId = userId - m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL m1.NotifyProps = model.GetDefaultChannelNotifyProps() Must(store.Channel().SaveMember(&m1)) diff --git a/store/store.go b/store/store.go index 2cffe99dea..887913bc61 100644 --- a/store/store.go +++ b/store/store.go @@ -72,7 +72,6 @@ type ChannelStore interface { CheckPermissionsToByName(teamId string, channelName string, userId string) StoreChannel UpdateLastViewedAt(channelId string, userId string) StoreChannel IncrementMentionCount(channelId string, userId string) StoreChannel - UpdateNotifyLevel(channelId string, userId string, notifyLevel string) StoreChannel } type PostStore interface { diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 55c0b54387..45981b2951 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -41,7 +41,7 @@ export default class ChannelNotifications extends React.Component { var channelId = button.getAttribute('data-channelid'); const member = ChannelStore.getMember(channelId); - var notifyLevel = member.notify_level; + var notifyLevel = member.notify_props.desktop; var markUnreadLevel = member.notify_props.mark_unread; this.setState({ @@ -62,7 +62,7 @@ export default class ChannelNotifications extends React.Component { } const member = ChannelStore.getMember(this.state.channelId); - var notifyLevel = member.notify_level; + var notifyLevel = member.notify_props.desktop; var markUnreadLevel = member.notify_props.mark_unread; var newState = this.state; @@ -81,7 +81,7 @@ export default class ChannelNotifications extends React.Component { var channelId = this.state.channelId; var notifyLevel = this.state.notifyLevel; - if (ChannelStore.getMember(channelId).notify_level === notifyLevel) { + if (ChannelStore.getMember(channelId).notify_props.desktop === notifyLevel) { this.updateSection(''); return; } @@ -89,16 +89,12 @@ export default class ChannelNotifications extends React.Component { var data = {}; data.channel_id = channelId; data.user_id = UserStore.getCurrentId(); - data.notify_level = notifyLevel; + data.desktop = notifyLevel; - if (!data.notify_level || data.notify_level.length === 0) { - return; - } - - Client.updateNotifyLevel(data, + Client.updateNotifyProps(data, () => { var member = ChannelStore.getMember(channelId); - member.notify_level = notifyLevel; + member.notify_props.desktop = notifyLevel; ChannelStore.setChannelMember(member); this.updateSection(''); }, diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 821d7fd4aa..6d4b56b7b7 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -203,7 +203,7 @@ export default class Sidebar extends React.Component { const user = UserStore.getCurrentUser(); const member = ChannelStore.getMember(msg.channel_id); - var notifyLevel = member.notify_level; + var notifyLevel = member.notify_props.desktop; if (notifyLevel === 'default') { notifyLevel = user.notify_props.desktop; } diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 041c00516c..ea97b64214 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -568,22 +568,6 @@ export function updateChannelDesc(data, success, error) { track('api', 'api_channels_desc'); } -// TODO remove me -export function updateNotifyLevel(data, success, error) { - $.ajax({ - url: '/api/v1/channels/update_notify_level', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateNotifyLevel', xhr, status, err); - error(e); - } - }); -} - export function updateNotifyProps(data, success, error) { $.ajax({ url: '/api/v1/channels/update_notify_props', From 04916a5f9db011b44c624a6975f4b92e3abfb11c Mon Sep 17 00:00:00 2001 From: thomas9987 Date: Thu, 1 Oct 2015 12:30:47 -0700 Subject: [PATCH 10/22] Fixed formatting Removed empty bullet point --- doc/install/prod-ubuntu.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/install/prod-ubuntu.md b/doc/install/prod-ubuntu.md index 866b1bdbe9..2c7edcb6b1 100644 --- a/doc/install/prod-ubuntu.md +++ b/doc/install/prod-ubuntu.md @@ -90,8 +90,7 @@ exec bin/platform 1. Configure Nginx to proxy connections from the internet to the Mattermost Server * Create a configuration for Mattermost * ``` sudo touch /etc/nginx/sites-available/mattermost``` - * Below is a sample configuration with the minimum settings required to configure Mattermost. - * + * Below is a sample configuration with the minimum settings required to configure Mattermost ``` server { server_name mattermost.example.com; From 7ecca3bcadc8f911b68749e7317fe01dd6f8e77d Mon Sep 17 00:00:00 2001 From: drbaker Date: Thu, 1 Oct 2015 12:31:10 -0700 Subject: [PATCH 11/22] Grammar fix: "setup" -> "set up" in header lines --- doc/install/prod-ubuntu.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/install/prod-ubuntu.md b/doc/install/prod-ubuntu.md index 866b1bdbe9..20c4976d70 100644 --- a/doc/install/prod-ubuntu.md +++ b/doc/install/prod-ubuntu.md @@ -6,7 +6,7 @@ * ``` sudo apt-get update``` * ``` sudo apt-get upgrade``` -## Setup Database Server +## Set up Database Server 1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.1 1. Install PostgreSQL 9.3+ (or MySQL 5.2+) * ``` sudo apt-get install postgresql postgresql-contrib``` @@ -25,7 +25,7 @@ 1. You can exit the postgres account by typing: * ``` exit``` -## Setup Mattermost Server +## Set up Mattermost Server 1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2 1. Download the latest Mattermost Server by typing: * ``` wget https://github.com/mattermost/platform/releases/download/v1.0.0/mattermost.tar.gz``` @@ -70,7 +70,7 @@ exec bin/platform * You should see a page titles *Mattermost - Signup* * You can also stop the process by running the command ` sudo stop mattermost`, but we will skip this step for now. -## Setup Nginx Server +## Set up Nginx Server 1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.3 1. We use Nginx for proxying request to the Mattermost Server. The main benefits are: * SSL termination @@ -118,7 +118,7 @@ exec bin/platform * ``` curl http://localhost``` * You should see a page titles *Mattermost - Signup* -## Setup Nginx with SSL (Recommended) +## Set up Nginx with SSL (Recommended) 1. You will need a SSL cert from a certificate authority. 1. For simplicity we will generate a test certificate. * ``` mkdir ~/cert``` From 89bed8ddfcb37b3c3d574e432b39b0eb915dd2c9 Mon Sep 17 00:00:00 2001 From: chuck5 Date: Thu, 1 Oct 2015 12:35:42 -0700 Subject: [PATCH 12/22] Changed comma to period to correct grammar error. --- doc/install/prod-ubuntu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/prod-ubuntu.md b/doc/install/prod-ubuntu.md index 866b1bdbe9..8038a43d2b 100644 --- a/doc/install/prod-ubuntu.md +++ b/doc/install/prod-ubuntu.md @@ -31,7 +31,7 @@ * ``` wget https://github.com/mattermost/platform/releases/download/v1.0.0/mattermost.tar.gz``` 1. Unzip the Mattermost Server by typing: * ``` tar -xvzf mattermost.tar.gz``` -1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`, in the future we will give guidance for storing under `/opt`. +1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`. In the future we will give guidance for storing under `/opt`. 1. We have also elected to run the Mattermost Server as the `ubuntu` account for simplicity. We recommend settings up and running the service under a `mattermost` user account with limited permissions. 1. Create the storage directory for files. We assume you will have attached a large drive for storage of images and files. For this setup we will assume the directory is located at `/mattermost/data`. * Create the directory by typing: From ab8685a74d8554aae233a977ea4d9e70d540a43b Mon Sep 17 00:00:00 2001 From: tmuwandi Date: Thu, 1 Oct 2015 12:40:33 -0700 Subject: [PATCH 13/22] Update prod-ubuntu.md --- doc/install/prod-ubuntu.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/prod-ubuntu.md b/doc/install/prod-ubuntu.md index 20c4976d70..ea6bd4aa2b 100644 --- a/doc/install/prod-ubuntu.md +++ b/doc/install/prod-ubuntu.md @@ -177,9 +177,9 @@ exec bin/platform * Save the Settings 1. Update File Settings * Change *Local Directory Location* from `./data/` to `/mattermost/data` -1. Update Log Settings +1. Update Log Settings. * Set *Log to The Console* to false -1. Update Rate Limit Settings +1. Update Rate Limit Settings. * Set *Vary By Remote Address* to false * Set *Vary By HTTP Header* to X-Real-IP 1. Feel free to modify other settings. From 896c5f0b58ae59ed9897290b50ce5645133bb1c5 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 1 Oct 2015 16:37:15 -0400 Subject: [PATCH 14/22] Fixed typo in mattermost.go help text --- mattermost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mattermost.go b/mattermost.go index ff7b0f3f3a..94bbe344d3 100644 --- a/mattermost.go +++ b/mattermost.go @@ -355,7 +355,7 @@ Usage: -reset_password Resets the password for a user. It requires the -team_name, -email and -password flag. Example: - platform -reset_password -team_name="name" -email="user@example.com" -paossword="newpassword" + platform -reset_password -team_name="name" -email="user@example.com" -password="newpassword" ` From fc82f57102ae4b8f89d8ad65ae3097aba9891272 Mon Sep 17 00:00:00 2001 From: Teviot Systems Date: Fri, 2 Oct 2015 20:16:27 +1000 Subject: [PATCH 15/22] Pass-through post creation time when importing posts --- api/post.go | 2 ++ model/post.go | 5 ++++- model/post_test.go | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/api/post.go b/api/post.go index 2b683fb7d3..65dad0eb65 100644 --- a/api/post.go +++ b/api/post.go @@ -88,6 +88,8 @@ func CreatePost(c *Context, post *model.Post, doUpdateLastViewed bool) (*model.P } } + post.CreateAt = 0 + post.Hashtags, _ = model.ParseHashtags(post.Message) post.UserId = c.Session.UserId diff --git a/model/post.go b/model/post.go index e78469940e..1fc5963c36 100644 --- a/model/post.go +++ b/model/post.go @@ -120,7 +120,10 @@ func (o *Post) PreSave() { o.OriginalId = "" - o.CreateAt = GetMillis() + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + } + o.UpdateAt = o.CreateAt if o.Props == nil { diff --git a/model/post_test.go b/model/post_test.go index 38f4b4c983..a6b880fa0e 100644 --- a/model/post_test.go +++ b/model/post_test.go @@ -83,5 +83,18 @@ func TestPostIsValid(t *testing.T) { func TestPostPreSave(t *testing.T) { o := Post{Message: "test"} o.PreSave() + + if o.CreateAt == 0 { + t.Fatal("should be set") + } + + past := GetMillis() - 1 + o = Post{Message: "test", CreateAt: past} + o.PreSave() + + if o.CreateAt > past { + t.Fatal("should not be updated") + } + o.Etag() } From 36f0117afb3b795d782631205a1ae6b3bcda9f2b Mon Sep 17 00:00:00 2001 From: it33 Date: Fri, 2 Oct 2015 05:16:20 -0700 Subject: [PATCH 16/22] Create slack-import.md --- doc/import/slack-import.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 doc/import/slack-import.md diff --git a/doc/import/slack-import.md b/doc/import/slack-import.md new file mode 100644 index 0000000000..14528e4b90 --- /dev/null +++ b/doc/import/slack-import.md @@ -0,0 +1,15 @@ +#### Slack Import (Preview) + +The Slack Import feature in Mattermost is in "Preview" and focus is on supporting migration of teams of less than 100 registered users. The feature can be accessed from by Team Administrators and Team Owners via the `Team Settings -> Import` menu option. + +Mattermost currently supports the processing of an "Export" file from Slack containing account information and public channel archives from a Slack team. + +- In the feature preview, emails and usernames from Slack are used to create new Mattermost accounts, connected to messages history in imported Slack channels. Users can activate these accounts and by going to the Password Reset screen in Mattermost to set new credentials. +- Once logged in, users will have access to previous Slack messages shared in public channels, now imported to Mattermost. + +Limitations: + +- Slack does not export files or images your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI. +- Slack does not export any content from private groups or direct messages that your team has stored in Slack's database. +- The Preview release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. Also, mentions do not yet resolve as Mattermost usernames (still show Slack ID). +- Newly added markdown suppport in Slack's Posts 2.0 feature is not yet supported. From f0c8019da468895a3da89809f63d272eaf26bb59 Mon Sep 17 00:00:00 2001 From: it33 Date: Fri, 2 Oct 2015 05:16:58 -0700 Subject: [PATCH 17/22] Added link to Slack Import --- doc/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/README.md b/doc/README.md index c339f22792..dc15917051 100644 --- a/doc/README.md +++ b/doc/README.md @@ -28,3 +28,4 @@ ## End User Help - [Mattermost Markdown Formatting](help/enduser/markdown.md) +- [Slack Import](https://github.com/mattermost/platform/blob/master/doc/import/slack-import.md) From fbbee5bf614c1cbce5a4beb02d553b3261c8d749 Mon Sep 17 00:00:00 2001 From: it33 Date: Fri, 2 Oct 2015 05:40:34 -0700 Subject: [PATCH 18/22] Update slack-import.md --- doc/import/slack-import.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/import/slack-import.md b/doc/import/slack-import.md index 14528e4b90..c30de05670 100644 --- a/doc/import/slack-import.md +++ b/doc/import/slack-import.md @@ -1,5 +1,7 @@ #### Slack Import (Preview) +*Note: As a SaaS service, Slack is able to change its export format quickly. If you encounter issues not mentioned in the documentation below, please let us know by [filing an issue](https://github.com/mattermost/platform/issues).* + The Slack Import feature in Mattermost is in "Preview" and focus is on supporting migration of teams of less than 100 registered users. The feature can be accessed from by Team Administrators and Team Owners via the `Team Settings -> Import` menu option. Mattermost currently supports the processing of an "Export" file from Slack containing account information and public channel archives from a Slack team. @@ -9,7 +11,8 @@ Mattermost currently supports the processing of an "Export" file from Slack cont Limitations: +- Newly added markdown suppport in Slack's Posts 2.0 feature announced on September 28, 2015 is not yet supported. - Slack does not export files or images your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI. - Slack does not export any content from private groups or direct messages that your team has stored in Slack's database. - The Preview release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. Also, mentions do not yet resolve as Mattermost usernames (still show Slack ID). -- Newly added markdown suppport in Slack's Posts 2.0 feature is not yet supported. + From c4203d8cd907d090269ddc41406a9c2aecc062f6 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Fri, 2 Oct 2015 08:54:31 -0400 Subject: [PATCH 19/22] Removed $ from time tooltip --- web/react/components/post_info.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index 82e746dc06..dba75ac5f0 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -150,7 +150,7 @@ export default class PostInfo extends React.Component { var dropdown = this.createDropdown(); - let tooltip = {utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}; + let tooltip = {`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}; return (
    From 28e84f1d13a6e90c7373702728d4ba6b2da41d6e Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 2 Oct 2015 09:18:19 -0400 Subject: [PATCH 20/22] Added help text for incoming webhooks. --- web/react/components/user_settings/manage_incoming_hooks.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx index fa2e2e5e41..899dbcd05a 100644 --- a/web/react/components/user_settings/manage_incoming_hooks.jsx +++ b/web/react/components/user_settings/manage_incoming_hooks.jsx @@ -148,6 +148,8 @@ export default class ManageIncomingHooks extends React.Component { return (
    + {'Create webhook URLs for channels and private groups. These URLs can be used by outside applications to create posts in any channels or private groups you have access to. The specified channel will be used as the default.'} +