Remove insights (#23952)
* removed server side * Updated store layer * unused import * Updated autogenerated code template * Updated tests * lint fix * unused translations * webapp side * Updated i18n * lint fix: * type fix * Updated snapshots * Removed insights from API specs * updated e2e * Updated e2e tests * Updated e2e tests * Removed insights tests * Removed Insights as possible channel to load in sidebar from test * Removed more insights tests * More e2e fixed * More cleanup * Lint * More cleanup in client4 and boards api * More cleanup * Fixes * lint fix --------- Co-authored-by: maria.nunez <maria.nunez@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e37459cd00
Коммит
26617fcbdc
@@ -4342,414 +4342,3 @@ func (s SqlChannelStore) GetTeamForChannel(channelID string) (*model.Team, error
|
||||
}
|
||||
return &team, nil
|
||||
}
|
||||
|
||||
// GetTopChannelsForTeamSince returns the filtered post counts of the following Channels sets:
|
||||
// a) those that are private channels in the given user's membership graph on the given team, and
|
||||
// b) those that are public channels in the given team.
|
||||
func (s SqlChannelStore) GetTopChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
|
||||
channels := make([]*model.TopChannel, 0)
|
||||
var args []any
|
||||
postgresPropQuery := `AND (Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false') AND (Posts.Props ->> 'from_webhook' IS NULL OR Posts.Props ->> 'from_webhook' = 'false') AND (Posts.Props ->> 'from_oauth_app' IS NULL OR Posts.Props ->> 'from_oauth_app' = 'false') AND (Posts.Props ->> 'from_plugin' IS NULL OR Posts.Props ->> 'from_plugin' = 'false')`
|
||||
mySqlPropsQuery := `AND (JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_webhook') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_webhook') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_plugin') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_plugin') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_oauth_app') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_oauth_app') = 'false')`
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
ID,
|
||||
Type,
|
||||
DisplayName,
|
||||
Name,
|
||||
TeamID,
|
||||
MessageCount
|
||||
FROM
|
||||
((SELECT
|
||||
Posts.ChannelId AS ID,
|
||||
'O' AS Type,
|
||||
PublicChannels.DisplayName AS DisplayName,
|
||||
PublicChannels.Name AS Name,
|
||||
PublicChannels.TeamId AS TeamID,
|
||||
count(Posts.Id) AS MessageCount,
|
||||
PublicChannels.DeleteAt AS DeleteAt
|
||||
FROM
|
||||
Posts
|
||||
LEFT JOIN PublicChannels on Posts.ChannelId = PublicChannels.Id
|
||||
WHERE
|
||||
Posts.DeleteAt = 0
|
||||
AND Posts.CreateAt > ?
|
||||
AND Posts.Type = ''`
|
||||
args = []any{since}
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query += mySqlPropsQuery
|
||||
} else if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
query += postgresPropQuery
|
||||
}
|
||||
|
||||
query += `
|
||||
AND PublicChannels.TeamId = ?
|
||||
GROUP BY
|
||||
Posts.ChannelId,
|
||||
PublicChannels.DisplayName,
|
||||
PublicChannels.Name,
|
||||
PublicChannels.TeamId,
|
||||
PublicChannels.DeleteAt)
|
||||
UNION ALL
|
||||
(SELECT
|
||||
Posts.ChannelId AS ID,
|
||||
Channels.Type AS Type,
|
||||
Channels.DisplayName AS DisplayName,
|
||||
Channels.Name AS Name,
|
||||
Channels.TeamId AS TeamID,
|
||||
count(Posts.Id) AS MessageCount,
|
||||
Channels.DeleteAt AS DeleteAt
|
||||
FROM
|
||||
Posts
|
||||
LEFT JOIN Channels on Posts.ChannelId = Channels.Id
|
||||
LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId
|
||||
WHERE
|
||||
Posts.DeleteAt = 0
|
||||
AND Posts.CreateAt > ?
|
||||
AND Posts.Type = ''`
|
||||
args = append(args, teamID, since)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query += mySqlPropsQuery
|
||||
} else if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
query += postgresPropQuery
|
||||
}
|
||||
|
||||
query += `
|
||||
AND Channels.TeamId = ?
|
||||
AND Channels.Type = 'P'
|
||||
AND ChannelMembers.UserId = ?
|
||||
GROUP BY
|
||||
Posts.ChannelId,
|
||||
Channels.Type,
|
||||
Channels.DisplayName,
|
||||
Channels.Name,
|
||||
Channels.TeamId,
|
||||
Channels.DeleteAt)) AS A
|
||||
WHERE
|
||||
DeleteAt = 0
|
||||
ORDER BY
|
||||
MessageCount DESC,
|
||||
Name ASC
|
||||
LIMIT ?
|
||||
OFFSET ?`
|
||||
args = append(args, teamID, userID, limit+1, offset)
|
||||
|
||||
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get top Channels")
|
||||
}
|
||||
|
||||
return model.GetTopChannelListWithPagination(channels, limit), nil
|
||||
}
|
||||
|
||||
// GetTopChannelsForUserSince returns the filtered post counts of channels with with posts created by the user
|
||||
// after the given timestamp within the given team (or across the workspace if no team is given). Excludes DM and GM channels.
|
||||
func (s SqlChannelStore) GetTopChannelsForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopChannelList, error) {
|
||||
channels := make([]*model.TopChannel, 0)
|
||||
var args []any
|
||||
var query string
|
||||
|
||||
var propsQuery string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
propsQuery = `AND (JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_webhook') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_webhook') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_plugin') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_plugin') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_oauth_app') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_oauth_app') = 'false')`
|
||||
} else if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
propsQuery = `AND (Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false') AND (Posts.Props ->> 'from_webhook' IS NULL OR Posts.Props ->> 'from_webhook' = 'false') AND (Posts.Props ->> 'from_oauth_app' IS NULL OR Posts.Props ->> 'from_oauth_app' = 'false') AND (Posts.Props ->> 'from_plugin' IS NULL OR Posts.Props ->> 'from_plugin' = 'false')`
|
||||
}
|
||||
|
||||
query = `
|
||||
SELECT
|
||||
Posts.ChannelId AS ID,
|
||||
Channels.Type AS Type,
|
||||
Channels.DisplayName AS DisplayName,
|
||||
Channels.Name AS Name,
|
||||
Channels.TeamId AS TeamID,
|
||||
count(Posts.Id) AS MessageCount
|
||||
FROM
|
||||
Posts
|
||||
LEFT JOIN Channels on Posts.ChannelId = Channels.Id
|
||||
LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId
|
||||
WHERE
|
||||
Posts.DeleteAt = 0
|
||||
AND Posts.CreateAt > ?
|
||||
AND Posts.Type = ''
|
||||
AND Posts.UserID = ?
|
||||
AND Channels.DeleteAt = 0
|
||||
AND (Channels.Type = 'O' OR Channels.Type = 'P')
|
||||
AND ChannelMembers.UserId = ? `
|
||||
|
||||
query += propsQuery
|
||||
|
||||
args = []any{since, userID, userID}
|
||||
|
||||
if teamID != "" {
|
||||
query += `
|
||||
AND Channels.TeamID = ?`
|
||||
args = append(args, teamID)
|
||||
}
|
||||
|
||||
query += `
|
||||
Group By
|
||||
Posts.ChannelId,
|
||||
Channels.Type,
|
||||
Channels.DisplayName,
|
||||
Channels.Name,
|
||||
Channels.TeamId
|
||||
ORDER BY
|
||||
MessageCount DESC,
|
||||
Name ASC
|
||||
LIMIT ?
|
||||
OFFSET ?`
|
||||
args = append(args, limit+1, offset)
|
||||
|
||||
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get top Channels")
|
||||
}
|
||||
|
||||
return model.GetTopChannelListWithPagination(channels, limit), nil
|
||||
}
|
||||
|
||||
// GetTopInactiveChannelsForTeamSince returns the filtered post counts of the following Channels sets:
|
||||
// a) those that are private channels in the given user's membership graph on the given team, and
|
||||
// b) those that are public channels in the given team.
|
||||
func (s SqlChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) {
|
||||
channels := make([]*model.TopInactiveChannel, 0)
|
||||
var args []any
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
ID,
|
||||
Type,
|
||||
DisplayName,
|
||||
Name,
|
||||
MessageCount,
|
||||
LastActivityAt
|
||||
FROM
|
||||
((SELECT
|
||||
PublicChannels.Id AS ID,
|
||||
'O' AS Type,
|
||||
PublicChannels.DisplayName AS DisplayName,
|
||||
PublicChannels.Name AS Name,
|
||||
COALESCE(count(Posts.Id), 0) AS MessageCount,
|
||||
COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt
|
||||
FROM
|
||||
PublicChannels
|
||||
LEFT JOIN Posts on Posts.ChannelId = PublicChannels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0
|
||||
LEFT JOIN Channels on Channels.Id = PublicChannels.Id
|
||||
WHERE
|
||||
PublicChannels.TeamId = ?
|
||||
AND PublicChannels.DeleteAt = 0
|
||||
AND Channels.CreateAt < ?
|
||||
GROUP BY
|
||||
PublicChannels.Id,
|
||||
PublicChannels.DisplayName,
|
||||
PublicChannels.Name,
|
||||
PublicChannels.TeamId)
|
||||
UNION ALL
|
||||
(SELECT
|
||||
Channels.Id AS ID,
|
||||
Channels.Type AS Type,
|
||||
Channels.DisplayName AS DisplayName,
|
||||
Channels.Name AS Name,
|
||||
COALESCE(count(Posts.Id), 0) AS MessageCount,
|
||||
COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt
|
||||
FROM
|
||||
Channels
|
||||
LEFT JOIN Posts on Posts.ChannelId = Channels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0
|
||||
LEFT JOIN ChannelMembers on Channels.Id = ChannelMembers.ChannelId
|
||||
WHERE
|
||||
Channels.TeamId = ?
|
||||
AND Channels.CreateAt < ?
|
||||
AND Channels.Type = 'P'
|
||||
AND Channels.DeleteAt = 0
|
||||
AND ChannelMembers.UserId = ?
|
||||
GROUP BY
|
||||
Channels.Id,
|
||||
Channels.Type,
|
||||
Channels.DisplayName,
|
||||
Channels.Name)) AS A
|
||||
ORDER BY
|
||||
MessageCount ASC,
|
||||
Name ASC
|
||||
LIMIT ?
|
||||
OFFSET ?`
|
||||
args = append(args, since, teamID, since, since, teamID, since, userID, limit+1, offset)
|
||||
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get top Channels")
|
||||
}
|
||||
|
||||
channels, err := postProcessTopInactiveChannels(s, channels)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return model.GetTopInactiveChannelListWithPagination(channels, limit), nil
|
||||
}
|
||||
|
||||
// GetTopInactiveChannelsForUserSince returns the filtered post counts of channels with with posts created by the user
|
||||
// after the given timestamp within the given team (or across the workspace if no team is given). Excludes DM and GM channels.
|
||||
func (s SqlChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopInactiveChannelList, error) {
|
||||
channels := make([]*model.TopInactiveChannel, 0)
|
||||
var args []any
|
||||
var query string
|
||||
|
||||
query = `
|
||||
SELECT
|
||||
Channels.Id AS ID,
|
||||
Channels.Type AS Type,
|
||||
Channels.DisplayName AS DisplayName,
|
||||
Channels.Name AS Name,
|
||||
COALESCE(count(Posts.Id), 0) AS MessageCount,
|
||||
COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt
|
||||
FROM
|
||||
Channels
|
||||
LEFT JOIN Posts on Posts.ChannelId = Channels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0
|
||||
LEFT JOIN ChannelMembers on Channels.Id = ChannelMembers.ChannelId
|
||||
WHERE
|
||||
Channels.DeleteAt = 0
|
||||
AND Channels.CreateAt < ?
|
||||
AND (Channels.Type = 'O' OR Channels.Type = 'P')
|
||||
AND ChannelMembers.UserId = ? `
|
||||
|
||||
args = []any{since, since, userID}
|
||||
|
||||
if teamID != "" {
|
||||
query += `
|
||||
AND Channels.TeamID = ?`
|
||||
args = append(args, teamID)
|
||||
}
|
||||
|
||||
query += `
|
||||
Group By
|
||||
Channels.Id,
|
||||
Channels.Type,
|
||||
Channels.DisplayName,
|
||||
Channels.Name
|
||||
ORDER BY
|
||||
MessageCount ASC,
|
||||
Name ASC
|
||||
LIMIT ?
|
||||
OFFSET ?`
|
||||
args = append(args, limit+1, offset)
|
||||
|
||||
if err := s.GetReplicaX().Select(&channels, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get top Inactive Channels")
|
||||
}
|
||||
|
||||
channels, err := postProcessTopInactiveChannels(s, channels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return model.GetTopInactiveChannelListWithPagination(channels, limit), nil
|
||||
}
|
||||
|
||||
func postProcessTopInactiveChannels(s SqlChannelStore, channels []*model.TopInactiveChannel) ([]*model.TopInactiveChannel, error) {
|
||||
// query channel members for Ids
|
||||
var conditionalAggrSelector string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
conditionalAggrSelector = "GROUP_CONCAT(UserId SEPARATOR ',') as UserIds"
|
||||
} else if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
conditionalAggrSelector = "string_agg(UserId, ',') as UserIds"
|
||||
}
|
||||
|
||||
var channelIds []string
|
||||
for _, channel := range channels {
|
||||
channelIds = append(channelIds, channel.ID)
|
||||
}
|
||||
q := s.getQueryBuilder().Select("ChannelId", conditionalAggrSelector).From("ChannelMembers").
|
||||
Where(sq.Eq{
|
||||
"ChannelId": channelIds,
|
||||
}).GroupBy("ChannelId")
|
||||
|
||||
channelsUserIdsMap := make(map[string]string, len(channels))
|
||||
type ChannelUserIdsResult struct {
|
||||
ChannelId string
|
||||
UserIds string
|
||||
}
|
||||
|
||||
channelsUserIdsResultList := make([]ChannelUserIdsResult, len(channels))
|
||||
sql, args, err := q.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to stringify squirrel query")
|
||||
}
|
||||
if err := s.GetReplicaX().Select(&channelsUserIdsResultList, sql, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get top Inactive Channels users")
|
||||
}
|
||||
|
||||
for _, channelUserIds := range channelsUserIdsResultList {
|
||||
channelsUserIdsMap[channelUserIds.ChannelId] = channelUserIds.UserIds
|
||||
}
|
||||
for index, channel := range channels {
|
||||
userIds := channelsUserIdsMap[channel.ID]
|
||||
userIdsSlice := strings.Split(userIds, ",")
|
||||
|
||||
channels[index].Participants = userIdsSlice
|
||||
|
||||
// handle channels with 0 participants
|
||||
if len(userIdsSlice) == 1 && userIdsSlice[0] == "" {
|
||||
channels[index].Participants = make([]string, 0)
|
||||
}
|
||||
}
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) PostCountsByDuration(channelIDs []string, sinceUnixMillis int64, userID *string, duration model.PostCountGrouping, atLocation *time.Location) ([]*model.DurationPostCount, error) {
|
||||
var unixSelect string
|
||||
var propsQuery string
|
||||
loc := atLocation.String()
|
||||
if loc == "Local" {
|
||||
loc = "UTC"
|
||||
}
|
||||
var format string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
if duration == model.PostsByDay {
|
||||
format = `%Y-%m-%d`
|
||||
} else {
|
||||
format = `%Y-%m-%dT%H`
|
||||
}
|
||||
unixSelect = fmt.Sprintf(`DATE_FORMAT(
|
||||
COALESCE(
|
||||
CONVERT_TZ(FROM_UNIXTIME(Posts.CreateAt / 1000), 'GMT', '%s'),
|
||||
FROM_UNIXTIME(Posts.CreateAt / 1000)
|
||||
),
|
||||
'%s') AS duration`, loc, format)
|
||||
propsQuery = `(JSON_EXTRACT(Posts.Props, '$.from_bot') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_bot') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_webhook') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_webhook') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_plugin') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_plugin') = 'false') AND (JSON_EXTRACT(Posts.Props, '$.from_oauth_app') IS NULL OR JSON_EXTRACT(Posts.Props, '$.from_oauth_app') = 'false')`
|
||||
} else if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
if duration == model.PostsByDay {
|
||||
format = "YYYY-MM-DD"
|
||||
} else {
|
||||
format = `YYYY-MM-DD"T"HH24`
|
||||
}
|
||||
unixSelect = fmt.Sprintf(`TO_CHAR(TO_TIMESTAMP(Posts.CreateAt / 1000) AT TIME ZONE '%s', '%s') AS duration`, loc, format)
|
||||
propsQuery = `(Posts.Props ->> 'from_bot' IS NULL OR Posts.Props ->> 'from_bot' = 'false') AND (Posts.Props ->> 'from_webhook' IS NULL OR Posts.Props ->> 'from_webhook' = 'false') AND (Posts.Props ->> 'from_oauth_app' IS NULL OR Posts.Props ->> 'from_oauth_app' = 'false') AND (Posts.Props ->> 'from_plugin' IS NULL OR Posts.Props ->> 'from_plugin' = 'false')`
|
||||
}
|
||||
query := sq.
|
||||
Select("Posts.ChannelId AS channelid", unixSelect, "count(Posts.Id) AS postcount").
|
||||
From("Posts").
|
||||
LeftJoin("Channels ON Posts.ChannelId = Channels.Id").
|
||||
Where(sq.And{
|
||||
sq.Eq{"Posts.DeleteAt": 0},
|
||||
sq.Gt{"Posts.CreateAt": sinceUnixMillis},
|
||||
sq.Eq{"Posts.Type": ""},
|
||||
sq.Eq{"Channels.Id": channelIDs},
|
||||
}).
|
||||
Where(propsQuery).
|
||||
GroupBy("channelid", "duration").
|
||||
OrderBy("channelid", "duration")
|
||||
if userID != nil && model.IsValidId(*userID) {
|
||||
query = query.Where(sq.And{sq.Eq{"Posts.UserId": *userID}})
|
||||
}
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse query")
|
||||
}
|
||||
dailyPostCounts := make([]*model.DurationPostCount, 0)
|
||||
if err := s.GetReplicaX().Select(&dailyPostCounts, queryString, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get post counts by duration")
|
||||
}
|
||||
|
||||
return dailyPostCounts, nil
|
||||
}
|
||||
|
||||
@@ -3059,163 +3059,6 @@ func (s *SqlPostStore) updateThreadsFromPosts(transaction *sqlxTxWrapper, posts
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) {
|
||||
var botsFilterExpr string
|
||||
/*
|
||||
Channel.Name is of the format userId1__userId2.
|
||||
Using this, self dms, and bot dms can be filtered.
|
||||
*/
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
botsFilterExpr = `SPLIT_PART(Channels.Name, '__', 1) NOT IN (SELECT UserId FROM Bots)
|
||||
AND SPLIT_PART(Channels.Name, '__', 2) NOT IN (SELECT UserId FROM Bots)
|
||||
`
|
||||
} else if s.DriverName() == model.DatabaseDriverMysql {
|
||||
botsFilterExpr = `SUBSTRING_INDEX(Channels.Name, '__', 1) NOT IN (SELECT UserId FROM Bots)
|
||||
AND SUBSTRING_INDEX(Channels.Name, '__', -1) NOT IN (SELECT UserId FROM Bots)
|
||||
`
|
||||
}
|
||||
|
||||
channelSelector := s.getQueryBuilder().Select("Id", "TotalMsgCount").From("Channels").Join("ChannelMembers as cm on cm.ChannelId = Channels.Id").
|
||||
Where(sq.And{
|
||||
sq.Expr("Channels.Type = 'D'"),
|
||||
sq.Eq{"cm.UserId": userID},
|
||||
sq.NotEq{"Channels.Name": fmt.Sprintf("%s__%s", userID, userID)},
|
||||
sq.Expr(botsFilterExpr),
|
||||
})
|
||||
var aggregator string
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
aggregator = "group_concat(distinct cm.UserId) as Participants"
|
||||
} else {
|
||||
aggregator = "string_agg(distinct cm.UserId, ',') as Participants"
|
||||
}
|
||||
|
||||
topDMsBuilder := s.getQueryBuilder().Select("count(p.Id) as MessageCount", aggregator, "vch.Id as ChannelId").FromSelect(channelSelector, "vch").
|
||||
Join("ChannelMembers as cm on cm.ChannelId = vch.Id").
|
||||
Join("Posts as p on p.ChannelId = vch.Id").
|
||||
Where(sq.And{
|
||||
sq.Gt{
|
||||
"p.UpdateAt": since,
|
||||
},
|
||||
sq.Eq{
|
||||
"p.DeleteAt": 0,
|
||||
},
|
||||
}).GroupBy("vch.id")
|
||||
|
||||
// following where clause filters out all archived DMs with "deleted" users, that has only 1 user-id in Participants column.
|
||||
archivedDMsFilter := s.getQueryBuilder().Select("MessageCount", "Participants", "ChannelId").FromSelect(topDMsBuilder, "top_dms").
|
||||
Where(sq.Expr("POSITION(',' IN Participants) > 0"))
|
||||
|
||||
archivedDMsFilter = archivedDMsFilter.OrderBy("MessageCount DESC").Limit(uint64(limit + 1)).Offset(uint64(offset))
|
||||
|
||||
topDMs := make([]*model.TopDM, 0)
|
||||
sql, args, err := archivedDMsFilter.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetTopDMsForUserSince_ToSql")
|
||||
}
|
||||
err = s.GetReplicaX().Select(&topDMs, sql, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find top DMs for user-id: %s", userID)
|
||||
}
|
||||
|
||||
// fill SecondParticipant column
|
||||
topDMs, err = postProcessTopDMs(s, userID, topDMs, since)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.GetTopDMListWithPagination(topDMs, limit), nil
|
||||
}
|
||||
|
||||
func postProcessTopDMs(s *SqlPostStore, userID string, topDMs []*model.TopDM, since int64) ([]*model.TopDM, error) {
|
||||
var topDMsFiltered = []*model.TopDM{}
|
||||
var secondParticipantIds []string
|
||||
var channelIds []string
|
||||
|
||||
// identify second participant in a list of participants
|
||||
for _, topDM := range topDMs {
|
||||
participants := strings.Split(topDM.Participants, ",")
|
||||
var secondParticipantId string
|
||||
// divide message count by 2, because it's counted twice due to channel memberships being 2 for dms.
|
||||
topDM.MessageCount = topDM.MessageCount / 2
|
||||
if participants[0] == userID {
|
||||
secondParticipantId = participants[1]
|
||||
} else {
|
||||
secondParticipantId = participants[0]
|
||||
}
|
||||
secondParticipantIds = append(secondParticipantIds, secondParticipantId)
|
||||
channelIds = append(channelIds, topDM.ChannelId)
|
||||
}
|
||||
|
||||
// get user profiles
|
||||
users, err := s.User().GetProfileByIds(context.Background(), secondParticipantIds, &store.UserGetByIdsOpts{}, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get second participants' information")
|
||||
}
|
||||
|
||||
// get outgoing message count for userId
|
||||
outgoingMessagesQuery := s.getQueryBuilder().Select("ch.Id as ChannelId, count(p.Id) as MessageCount").From("Channels as ch").
|
||||
Join("Posts as p on p.ChannelId=ch.Id").Where(
|
||||
sq.And{
|
||||
sq.Gt{
|
||||
"p.UpdateAt": since,
|
||||
},
|
||||
sq.Eq{
|
||||
"p.DeleteAt": 0,
|
||||
},
|
||||
sq.Eq{
|
||||
"ch.Id": channelIds,
|
||||
},
|
||||
sq.Eq{
|
||||
"p.UserId": userID,
|
||||
},
|
||||
}).GroupBy("ch.Id")
|
||||
|
||||
outgoingMessages := make([]*model.OutgoingMessageQueryResult, 0)
|
||||
sql, args, err := outgoingMessagesQuery.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetTopDMsForUserSince_outgoingMessagesQuery_ToSql")
|
||||
}
|
||||
err = s.GetReplicaX().Select(&outgoingMessages, sql, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find top DMs for user-id: %s", userID)
|
||||
}
|
||||
|
||||
// create map of channelId -> MessageCount
|
||||
outgoingMessagesMap := make(map[string]int)
|
||||
for _, outgoingMessage := range outgoingMessages {
|
||||
outgoingMessagesMap[outgoingMessage.ChannelId] = outgoingMessage.MessageCount
|
||||
}
|
||||
|
||||
// create map of userId -> User
|
||||
usersMap := make(map[string]*model.User)
|
||||
for _, user := range users {
|
||||
usersMap[user.Id] = user
|
||||
}
|
||||
|
||||
for index, topDM := range topDMs {
|
||||
if secondParticipantIds[index] == "-1" {
|
||||
return nil, errors.Wrapf(err, "failed to find second user for topDM: %s", userID)
|
||||
}
|
||||
user := usersMap[secondParticipantIds[index]]
|
||||
topDM.SecondParticipant = &model.TopDMInsightUserInformation{
|
||||
InsightUserInformation: model.InsightUserInformation{
|
||||
Id: user.Id,
|
||||
LastPictureUpdate: user.LastPictureUpdate,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Username: user.Username,
|
||||
NickName: user.Nickname,
|
||||
},
|
||||
Position: user.Position,
|
||||
}
|
||||
|
||||
topDM.OutgoingMessageCount = int64(outgoingMessagesMap[topDM.ChannelId])
|
||||
topDMsFiltered = append(topDMsFiltered, topDM)
|
||||
}
|
||||
|
||||
return topDMsFiltered, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) SetPostReminder(reminder *model.PostReminder) error {
|
||||
transaction, err := s.GetMasterX().Beginx()
|
||||
if err != nil {
|
||||
|
||||
@@ -245,124 +245,6 @@ func (s *SqlReactionStore) PermanentDeleteBatch(endTime int64, limit int64) (int
|
||||
return rowsAffected, nil
|
||||
}
|
||||
|
||||
// GetTopForTeamSince returns the instance counts of the following Reactions sets:
|
||||
// a) those created by anyone in private channels in the given user's membership graph on the given team, and
|
||||
// b) those created by anyone in public channels on the given team.
|
||||
func (s *SqlReactionStore) GetTopForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||
reactions := make([]*model.TopReaction, 0)
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
EmojiName,
|
||||
sum(EmojiCount) AS Count
|
||||
FROM ((
|
||||
SELECT
|
||||
EmojiName,
|
||||
count(EmojiName) AS EmojiCount,
|
||||
Reactions.DeleteAt AS DeleteAt,
|
||||
Reactions.CreateAt AS CreateAt
|
||||
FROM
|
||||
ChannelMembers
|
||||
INNER JOIN Channels ON ChannelMembers.ChannelId = Channels.Id
|
||||
INNER JOIN Reactions ON Channels.Id = Reactions.ChannelId
|
||||
WHERE
|
||||
ChannelMembers.UserId = ?
|
||||
AND Channels.Type = 'P'
|
||||
AND Channels.TeamId = ?
|
||||
GROUP BY
|
||||
Reactions.EmojiName,
|
||||
Reactions.DeleteAt,
|
||||
Reactions.CreateAt)
|
||||
UNION ALL (
|
||||
SELECT
|
||||
EmojiName,
|
||||
count(EmojiName) AS EmojiCount,
|
||||
Reactions.DeleteAt AS DeleteAt,
|
||||
Reactions.CreateAt AS CreateAt
|
||||
FROM
|
||||
Reactions
|
||||
INNER JOIN PublicChannels ON Reactions.ChannelId = PublicChannels.Id
|
||||
WHERE
|
||||
PublicChannels.TeamId = ?
|
||||
GROUP BY
|
||||
Reactions.EmojiName,
|
||||
Reactions.DeleteAt,
|
||||
Reactions.CreateAt)) AS A
|
||||
WHERE
|
||||
DeleteAt = 0
|
||||
AND CreateAt > ?
|
||||
GROUP BY
|
||||
EmojiName
|
||||
ORDER BY
|
||||
Count DESC,
|
||||
EmojiName ASC
|
||||
LIMIT ?
|
||||
OFFSET ?`
|
||||
|
||||
if err := s.GetReplicaX().Select(&reactions, query, userID, teamID, teamID, since, limit+1, offset); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get top Reactions")
|
||||
}
|
||||
|
||||
return model.GetTopReactionListWithPagination(reactions, limit), nil
|
||||
}
|
||||
|
||||
// GetTopForUserSince returns the instance counts of the following Reactions sets:
|
||||
// a) those created by the given user in any channel type on the given team (across the workspace if no team is given), and
|
||||
// b) those created by the given user in DM or group channels.
|
||||
func (s *SqlReactionStore) GetTopForUserSince(userID string, teamID string, since int64, offset int, limit int) (*model.TopReactionList, error) {
|
||||
reactions := make([]*model.TopReaction, 0)
|
||||
var args []any
|
||||
var query string
|
||||
|
||||
if teamID != "" {
|
||||
query = `
|
||||
SELECT
|
||||
EmojiName,
|
||||
count(EmojiName) AS Count
|
||||
FROM
|
||||
Reactions
|
||||
INNER JOIN Channels ON Channels.Id = Reactions.ChannelId
|
||||
WHERE
|
||||
Reactions.DeleteAt = 0
|
||||
AND Reactions.UserId = ?
|
||||
AND (Channels.TeamId = ? OR Channels.Type = 'D' OR Channels.Type = 'G')
|
||||
AND Reactions.CreateAt > ?
|
||||
GROUP BY
|
||||
EmojiName
|
||||
ORDER BY
|
||||
Count DESC,
|
||||
EmojiName ASC
|
||||
LIMIT ?
|
||||
OFFSET ?`
|
||||
args = []any{userID, teamID, since, limit + 1, offset}
|
||||
} else {
|
||||
query = `
|
||||
SELECT
|
||||
EmojiName,
|
||||
count(EmojiName) AS Count
|
||||
FROM
|
||||
Reactions
|
||||
WHERE
|
||||
Reactions.DeleteAt = 0
|
||||
AND Reactions.UserId = ?
|
||||
AND Reactions.CreateAt > ?
|
||||
GROUP BY
|
||||
Reactions.EmojiName
|
||||
ORDER BY
|
||||
Count DESC,
|
||||
EmojiName ASC
|
||||
LIMIT ?
|
||||
OFFSET ?`
|
||||
args = []any{userID, since, limit + 1, offset}
|
||||
}
|
||||
|
||||
if err := s.GetReplicaX().Select(&reactions, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get top Reactions")
|
||||
}
|
||||
|
||||
return model.GetTopReactionListWithPagination(reactions, limit), nil
|
||||
}
|
||||
|
||||
func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *sqlxTxWrapper, reaction *model.Reaction) error {
|
||||
reaction.DeleteAt = 0
|
||||
|
||||
|
||||
@@ -1652,46 +1652,3 @@ func (s SqlTeamStore) GroupSyncedTeamCount() (int64, error) {
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetNewTeamMembersSince(teamID string, since int64, offset int, limit int, showFullName bool) (*model.NewTeamMembersList, int64, error) {
|
||||
builderF := func(selectClause string) sq.SelectBuilder {
|
||||
return s.getQueryBuilder().
|
||||
Select(selectClause).
|
||||
From("TeamMembers").
|
||||
Join("Users ON Users.id = TeamMembers.userid").
|
||||
LeftJoin("Bots ON Bots.userid = Users.id").
|
||||
Where(sq.GtOrEq{"TeamMembers.createat": since}).
|
||||
Where(sq.Eq{"TeamMembers.deleteat": 0, "teamid": teamID, "Users.deleteat": 0, "Bots.userid": nil})
|
||||
}
|
||||
|
||||
countBuilder := builderF("count(*)")
|
||||
query, args, err := countBuilder.ToSql()
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "team_tosql")
|
||||
}
|
||||
var totalCount int64
|
||||
err = s.GetReplicaX().Get(&totalCount, query, args...)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to count team members since")
|
||||
}
|
||||
|
||||
selectClause := "Users.Id, Users.Username, Users.Position, Users.LastPictureUpdate, TeamMembers.CreateAt, Users.Nickname"
|
||||
if showFullName {
|
||||
selectClause += ", Users.FirstName, Users.LastName"
|
||||
}
|
||||
|
||||
newTeamMembersBuilder := builderF(selectClause).
|
||||
Limit(uint64(limit + 1)).
|
||||
Offset(uint64(offset))
|
||||
query, args, err = newTeamMembersBuilder.ToSql()
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "team_tosql")
|
||||
}
|
||||
var ntms []*model.NewTeamMember
|
||||
err = s.GetReplicaX().Select(&ntms, query, args...)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to get team members since")
|
||||
}
|
||||
|
||||
return model.GetNewTeamMembersListWithPagination(ntms, limit), totalCount, nil
|
||||
}
|
||||
|
||||
@@ -1021,209 +1021,3 @@ func (s *SqlThreadStore) GetThreadUnreadReplyCount(threadMembership *model.Threa
|
||||
|
||||
return unreadReplies, nil
|
||||
}
|
||||
|
||||
// Top threads in all public channels and private channels userID is a member of. Returns a list of threads ranked by interactions.
|
||||
func (s *SqlThreadStore) GetTopThreadsForTeamSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) {
|
||||
var args []any
|
||||
query := `select
|
||||
threads_list.PostId,
|
||||
threads_list.ReplyCount,
|
||||
threads_list.ChannelId,
|
||||
threads_list.DisplayName,
|
||||
threads_list.Name,
|
||||
threads_list.Participants,
|
||||
p.UserId
|
||||
from((
|
||||
SELECT
|
||||
t.PostId,
|
||||
t.ReplyCount,
|
||||
t.ChannelId,
|
||||
t.Participants,
|
||||
c.DisplayName,
|
||||
c.Name
|
||||
FROM
|
||||
Threads t
|
||||
LEFT JOIN PublicChannels c ON t.ChannelId = c.Id
|
||||
WHERE
|
||||
t.threaddeleteat IS NULL
|
||||
AND t.LastReplyAt > ?
|
||||
AND c.TeamId = ?
|
||||
GROUP BY
|
||||
t.PostId,
|
||||
c.DisplayName,
|
||||
c.Name,
|
||||
t.Participants
|
||||
)
|
||||
UNION
|
||||
ALL (
|
||||
SELECT
|
||||
t.PostId,
|
||||
t.ReplyCount,
|
||||
t.ChannelId,
|
||||
t.Participants,
|
||||
c.DisplayName,
|
||||
c.Name
|
||||
FROM
|
||||
Threads t
|
||||
LEFT JOIN ChannelMembers cm ON t.ChannelId = cm.ChannelId
|
||||
LEFT JOIN Channels c ON t.ChannelId = c.Id
|
||||
WHERE
|
||||
t.threaddeleteat IS NULL
|
||||
AND cm.UserId = ?
|
||||
AND c.Type = 'P'
|
||||
AND c.TeamId = ?
|
||||
AND t.LastReplyAt > ?
|
||||
GROUP BY
|
||||
t.PostId,
|
||||
c.DisplayName,
|
||||
c.Name,
|
||||
t.Participants
|
||||
)) as threads_list
|
||||
LEFT JOIN Posts as p on p.Id = threads_list.PostId
|
||||
ORDER BY ReplyCount DESC
|
||||
limit ? offset ?`
|
||||
|
||||
args = append(args, since, teamID, userID, teamID, since, limit+1, offset)
|
||||
|
||||
topThreads := make([]*model.TopThread, 0)
|
||||
err := s.GetReplicaX().Select(&topThreads, query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get top threads=%s", teamID)
|
||||
}
|
||||
topThreads, err = postProcessTopThreads(topThreads, s, teamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.GetTopThreadListWithPagination(topThreads, limit), nil
|
||||
}
|
||||
|
||||
func (s *SqlThreadStore) GetTopThreadsForUserSince(teamID string, userID string, since int64, offset int, limit int) (*model.TopThreadList, error) {
|
||||
var args []any
|
||||
|
||||
// gets all threads within the team which user follows.
|
||||
query := `select
|
||||
threads_list.PostId,
|
||||
threads_list.ReplyCount,
|
||||
threads_list.ChannelId,
|
||||
threads_list.DisplayName,
|
||||
threads_list.Name,
|
||||
threads_list.Participants,
|
||||
p.UserId
|
||||
from((
|
||||
SELECT
|
||||
t.PostId,
|
||||
t.ReplyCount,
|
||||
t.ChannelId,
|
||||
t.Participants,
|
||||
c.DisplayName,
|
||||
c.Name
|
||||
FROM
|
||||
Threads t
|
||||
LEFT JOIN PublicChannels c ON t.ChannelId = c.Id
|
||||
LEFT JOIN ThreadMemberships as tm on t.PostId = tm.PostId
|
||||
WHERE
|
||||
t.threaddeleteat IS NULL
|
||||
AND t.LastReplyAt > ?
|
||||
AND c.TeamId = ?
|
||||
AND tm.UserId = ?
|
||||
AND tm.Following = TRUE
|
||||
GROUP BY
|
||||
t.PostId,
|
||||
c.DisplayName,
|
||||
c.Name,
|
||||
t.Participants
|
||||
)
|
||||
UNION
|
||||
ALL (
|
||||
SELECT
|
||||
t.PostId,
|
||||
t.ReplyCount,
|
||||
t.ChannelId,
|
||||
t.Participants,
|
||||
c.DisplayName,
|
||||
c.Name
|
||||
FROM
|
||||
Threads t
|
||||
LEFT JOIN ChannelMembers cm ON t.ChannelId = cm.ChannelId
|
||||
LEFT JOIN Channels c ON t.ChannelId = c.Id
|
||||
LEFT JOIN ThreadMemberships as tm on t.PostId = tm.PostId
|
||||
WHERE
|
||||
cm.UserId = ?
|
||||
AND c.Type = 'P'
|
||||
AND c.TeamId = ?
|
||||
AND t.threaddeleteat IS NULL
|
||||
AND t.LastReplyAt > ?
|
||||
AND tm.UserId = ?
|
||||
AND tm.Following = TRUE
|
||||
GROUP BY
|
||||
t.PostId,
|
||||
c.DisplayName,
|
||||
c.Name,
|
||||
t.Participants
|
||||
)) as threads_list
|
||||
LEFT JOIN Posts as p on p.Id = threads_list.PostId
|
||||
ORDER BY ReplyCount DESC
|
||||
limit ? offset ?`
|
||||
|
||||
args = append(args, since, teamID, userID, userID, teamID, since, userID, limit+1, offset)
|
||||
|
||||
topThreads := make([]*model.TopThread, 0)
|
||||
err := s.GetReplicaX().Select(&topThreads, query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get top threads=%s", teamID)
|
||||
}
|
||||
topThreads, err = postProcessTopThreads(topThreads, s, teamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.GetTopThreadListWithPagination(topThreads, limit), nil
|
||||
}
|
||||
|
||||
func userContains(userIDs []string, searchedUserID string) bool {
|
||||
for _, userID := range userIDs {
|
||||
if userID == searchedUserID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func postProcessTopThreads(topThreads []*model.TopThread, s *SqlThreadStore, teamID string) ([]*model.TopThread, error) {
|
||||
// create list of userIDs
|
||||
var userIDs []string
|
||||
for _, topThread := range topThreads {
|
||||
userID := topThread.UserId
|
||||
if !userContains(userIDs, userID) {
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
}
|
||||
|
||||
usersMap := map[string]*model.User{}
|
||||
|
||||
users, err := s.User().GetProfileByIds(context.Background(), userIDs, &store.UserGetByIdsOpts{}, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get users for top threads in team=%s", teamID)
|
||||
}
|
||||
for _, user := range users {
|
||||
usersMap[user.Id] = user
|
||||
}
|
||||
|
||||
// resolve user, root post for each top thread
|
||||
for _, topThread := range topThreads {
|
||||
postCreator := usersMap[topThread.UserId]
|
||||
topThread.UserInformation = &model.InsightUserInformation{
|
||||
Id: postCreator.Id,
|
||||
LastPictureUpdate: postCreator.LastPictureUpdate,
|
||||
FirstName: postCreator.FirstName,
|
||||
LastName: postCreator.LastName,
|
||||
Username: postCreator.Username,
|
||||
NickName: postCreator.Nickname,
|
||||
}
|
||||
post, err := s.Post().GetSingle(topThread.PostId, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get extended post for post id=%s", topThread.PostId)
|
||||
}
|
||||
topThread.Post = post
|
||||
}
|
||||
return topThreads, nil
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user