PLT-8297: Message Export Should Still Produce Valid Exports if ChannelMemberHistory Data is Missing (#7951)
* Added a less accurate ChannelMembers fallback that is used if export period occurs before MessageExport was enabled * Fixed test names * Improved testing * Made hasDataFromBefore() a little less strict * Fixed the test to cleanly truncate the ChannelMemberHistory table without exposing the db via the interface * Renamed a function for better clarity * Binary logic is hard
Этот коммит содержится в:
@@ -6,6 +6,8 @@ package sqlstore
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"database/sql"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
@@ -65,7 +67,47 @@ func (s SqlChannelMemberHistoryStore) LogLeaveEvent(userId string, channelId str
|
||||
|
||||
func (s SqlChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelId string) store.StoreChannel {
|
||||
return store.Do(func(result *store.StoreResult) {
|
||||
query := `
|
||||
if useChannelMemberHistory, err := s.hasDataAtOrBefore(startTime); err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else if useChannelMemberHistory {
|
||||
// the export period starts after the ChannelMemberHistory table was first introduced, so we can use the
|
||||
// data from it for our export
|
||||
if channelMemberHistories, err := s.getFromChannelMemberHistoryTable(startTime, endTime, channelId); err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = channelMemberHistories
|
||||
}
|
||||
} else {
|
||||
// the export period starts before the ChannelMemberHistory table was introduced, so we need to fake the
|
||||
// data by assuming that anybody who has ever joined the channel in question was present during the export period.
|
||||
// this may not always be true, but it's better than saying that somebody wasn't there when they were
|
||||
if channelMemberHistories, err := s.getFromChannelMembersTable(startTime, endTime, channelId); err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = channelMemberHistories
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s SqlChannelMemberHistoryStore) hasDataAtOrBefore(time int64) (bool, error) {
|
||||
type NullableCountResult struct {
|
||||
Min sql.NullInt64
|
||||
}
|
||||
var result NullableCountResult
|
||||
query := "SELECT MIN(JoinTime) AS Min FROM ChannelMemberHistory"
|
||||
if err := s.GetReplica().SelectOne(&result, query); err != nil {
|
||||
return false, err
|
||||
} else if result.Min.Valid {
|
||||
return result.Min.Int64 <= time, nil
|
||||
} else {
|
||||
// if the result was null, there are no rows in the table, so there is no data from before
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistory, error) {
|
||||
query := `
|
||||
SELECT
|
||||
cmh.*,
|
||||
u.Email
|
||||
@@ -76,14 +118,37 @@ func (s SqlChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, e
|
||||
AND (cmh.LeaveTime IS NULL OR cmh.LeaveTime >= :StartTime)
|
||||
ORDER BY cmh.JoinTime ASC`
|
||||
|
||||
params := map[string]interface{}{"ChannelId": channelId, "StartTime": startTime, "EndTime": endTime}
|
||||
var histories []*model.ChannelMemberHistory
|
||||
if _, err := s.GetReplica().Select(&histories, query, params); err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelMemberHistoryStore.GetUsersInChannelAt", "store.sql_channel_member_history.get_users_in_channel_during.app_error", params, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
result.Data = histories
|
||||
params := map[string]interface{}{"ChannelId": channelId, "StartTime": startTime, "EndTime": endTime}
|
||||
var histories []*model.ChannelMemberHistory
|
||||
if _, err := s.GetReplica().Select(&histories, query, params); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return histories, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistory, error) {
|
||||
query := `
|
||||
SELECT DISTINCT
|
||||
ch.ChannelId,
|
||||
ch.UserId,
|
||||
u.email
|
||||
FROM ChannelMembers AS ch
|
||||
INNER JOIN Users AS u ON ch.UserId = u.id
|
||||
WHERE ch.ChannelId = :ChannelId`
|
||||
|
||||
params := map[string]interface{}{"ChannelId": channelId}
|
||||
var histories []*model.ChannelMemberHistory
|
||||
if _, err := s.GetReplica().Select(&histories, query, params); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
// we have to fill in the join/leave times, because that data doesn't exist in the channel members table
|
||||
for _, channelMemberHistory := range histories {
|
||||
channelMemberHistory.JoinTime = startTime
|
||||
channelMemberHistory.LeaveTime = model.NewInt64(endTime)
|
||||
}
|
||||
})
|
||||
return histories, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit int64) store.StoreChannel {
|
||||
@@ -112,7 +177,6 @@ func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit
|
||||
} else {
|
||||
if rowsAffected, err1 := sqlResult.RowsAffected(); err1 != nil {
|
||||
result.Err = model.NewAppError("SqlChannelMemberHistoryStore.PermanentDeleteBatchForChannel", "store.sql_channel_member_history.permanent_delete_batch.app_error", params, err.Error(), http.StatusInternalServerError)
|
||||
result.Data = int64(0)
|
||||
} else {
|
||||
result.Data = rowsAffected
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user