Update channel member notify props to use native JSON (#18114)

* Update channel member notify props to use native JSON

Created a new store method that patches the notify props field.

https://community-daily.mattermost.com/plugins/focalboard/workspace/zyoahc9uapdn3xdptac6jb69ic?id=285b80a3-257d-41f6-8cf4-ed80ca9d92e5&v=495cdb4d-c13a-4992-8eb9-80cfee2819a4&c=91d08676-4a0e-4f02-8dce-d24d4fc56449

```release-note
NONE
```

* cleanup

```release-note
NONE
```

* forgot to commit

```release-note
NONE
```

* Fix edge case

```release-note
NONE
```

* address review comments

```release-note
NONE
```

* fix incorrect order

```release-note
NONE
```

* Address review comments

```release-note
NONE
```

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Agniva De Sarker
2021-08-23 10:04:30 +05:30
коммит произвёл GitHub
родитель c4c1fda128
Коммит 7bbcf86531
10 изменённых файлов: 249 добавлений и 12 удалений

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

@@ -1632,6 +1632,52 @@ func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) (*model.Chann
return updatedMembers[0], nil
}
func (s SqlChannelStore) UpdateMemberNotifyProps(channelID, userID string, props map[string]string) (*model.ChannelMember, error) {
tx, err := s.GetMaster().Begin()
if err != nil {
return nil, errors.Wrap(err, "begin_transaction")
}
defer finalizeTransaction(tx)
if s.DriverName() == model.DatabaseDriverPostgres {
_, err = tx.Exec(`UPDATE channelmembers
SET notifyprops = notifyprops || $1::jsonb
WHERE userid=$2 AND channelid=$3`, model.MapToJson(props), userID, channelID)
} else {
// It's difficult to construct a SQL query for MySQL
// to handle a case of empty map. So we just ignore it.
if len(props) > 0 {
// unpack the keys and values to pass to MySQL.
args, argString := constructMySQLJSONArgs(props)
args = append(args, userID, channelID)
// Example: UPDATE ChannelMembers
// SET NotifyProps = JSON_SET(NotifyProps, '$.mark_unread', '"yes"' [, ...])
// WHERE ...
_, err = tx.Exec(`UPDATE ChannelMembers
SET NotifyProps = JSON_SET(NotifyProps, `+argString+`)
WHERE UserId=? AND ChannelId=?`, args...)
}
}
if err != nil {
return nil, errors.Wrapf(err, "failed to update ChannelMember with channelID=%s and userID=%s", channelID, userID)
}
var dbMember channelMemberWithSchemeRoles
if err2 := tx.SelectOne(&dbMember, ChannelMembersWithSchemeSelectQuery+"WHERE ChannelMembers.ChannelId = :ChannelId AND ChannelMembers.UserId = :UserId", map[string]interface{}{"ChannelId": channelID, "UserId": userID}); err2 != nil {
if err2 == sql.ErrNoRows {
return nil, store.NewErrNotFound("ChannelMember", fmt.Sprintf("channelId=%s, userId=%s", channelID, userID))
}
return nil, errors.Wrapf(err2, "failed to get ChannelMember with channelId=%s and userId=%s", channelID, userID)
}
if err2 := tx.Commit(); err2 != nil {
return nil, errors.Wrap(err2, "commit_transaction")
}
return dbMember.ToModel(), err
}
func (s SqlChannelStore) GetMembers(channelId string, offset, limit int) (model.ChannelMembers, error) {
var dbMembers channelMemberWithSchemeRolesList
_, err := s.GetReplica().Select(&dbMembers, ChannelMembersWithSchemeSelectQuery+"WHERE ChannelId = :ChannelId LIMIT :Limit OFFSET :Offset", map[string]interface{}{"ChannelId": channelId, "Limit": limit, "Offset": offset})

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

@@ -89,3 +89,30 @@ func isQuotedWord(s string) bool {
return s[0] == '"' && s[len(s)-1] == '"'
}
// constructMySQLJSONArgs returns the arg list to pass to a query along with
// the string of placeholders which is needed to be to the JSON_SET function.
// Use this function in this way:
// UPDATE Table
// SET Col = JSON_SET(Col, `+argString+`)
// WHERE Id=?`, args...)
// after appending the Id param to the args slice.
func constructMySQLJSONArgs(props map[string]string) ([]interface{}, string) {
if len(props) == 0 {
return nil, ""
}
// Unpack the keys and values to pass to MySQL.
args := make([]interface{}, 0, len(props))
for k, v := range props {
args = append(args, "$."+k)
args = append(args, v)
}
// We calculate the number of ? to set in the query string.
argString := strings.Repeat("?, ", len(props)*2)
// Strip off the trailing comma.
argString = strings.TrimSuffix(strings.TrimSpace(argString), ",")
return args, argString
}

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

@@ -6,6 +6,7 @@ package sqlstore
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -103,3 +104,32 @@ func TestRemoveNonAlphaNumericUnquotedTerms(t *testing.T) {
})
}
}
func TestMySQLJSONArgs(t *testing.T) {
tests := []struct {
props map[string]string
args []interface{}
argString string
}{
{
props: map[string]string{
"desktop": "linux",
"mobile": "android",
"notify": "always",
},
args: []interface{}{"$.desktop", "linux", "$.mobile", "android", "$.notify", "always"},
argString: "?, ?, ?, ?, ?, ?",
},
{
props: map[string]string{},
args: nil,
argString: "",
},
}
for _, test := range tests {
args, argString := constructMySQLJSONArgs(test.props)
assert.ElementsMatch(t, test.args, args)
assert.Equal(t, test.argString, argString)
}
}