// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package model import ( "net/http" "time" ) type PostCountGrouping string const ( TimeRangeToday string = "today" TimeRange7Day string = "7_day" TimeRange28Day string = "28_day" PostsByHour PostCountGrouping = "hour" PostsByDay PostCountGrouping = "day" ) type InsightsOpts struct { StartUnixMilli int64 Page int PerPage int } type InsightsListData struct { HasNext bool `json:"has_next"` } // Top Reactions type TopReactionList struct { InsightsListData Items []*TopReaction `json:"items"` } type TopReaction struct { EmojiName string `json:"emoji_name"` Count int64 `json:"count"` } // Top Channels type TopChannelList struct { InsightsListData Items []*TopChannel `json:"items"` PostCountByDuration ChannelPostCountByDuration `json:"channel_post_counts_by_duration"` } func (t *TopChannelList) ChannelIDs() []string { var ids []string for _, item := range t.Items { ids = append(ids, item.ID) } return ids } type TopChannel struct { ID string `json:"id"` Type ChannelType `json:"type"` DisplayName string `json:"display_name"` Name string `json:"name"` TeamID string `json:"team_id"` MessageCount int64 `json:"message_count"` } // Top Channels type TopInactiveChannelList struct { InsightsListData Items []*TopInactiveChannel `json:"items"` } type TopInactiveChannel struct { ID string `json:"id"` Type ChannelType `json:"type"` DisplayName string `json:"display_name"` Name string `json:"name"` LastActivityAt int64 `json:"last_activity_at"` Participants StringArray `json:"participants"` MessageCount int64 `json:"-"` } // Top Threads type TopThreadList struct { InsightsListData Items []*TopThread `json:"items"` } type TopThread struct { PostId string `json:"-"` ReplyCount int64 `json:"-"` ChannelId string `json:"channel_id"` DisplayName string `json:"channel_display_name"` Name string `json:"channel_name"` Participants StringArray `json:"participants"` UserId string `json:"-"` UserInformation *InsightUserInformation `json:"user_information"` Post *Post `json:"post"` } type InsightUserInformation struct { Id string `json:"id"` LastPictureUpdate int64 `json:"last_picture_update"` FirstName string `json:"first_name"` LastName string `json:"last_name"` NickName string `json:"nickname"` Username string `json:"username"` } type TopDMInsightUserInformation struct { InsightUserInformation Position string `json:"position"` } type NewTeamMembersList struct { InsightsListData Items []*NewTeamMember `json:"items"` TotalCount int64 `json:"total_count"` } type NewTeamMember struct { Id string `json:"id"` Username string `json:"username"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Position string `json:"position"` Nickname string `json:"nickname"` LastPictureUpdate int64 `json:"last_picture_update,omitempty"` CreateAt int64 `json:"create_at"` } type DurationPostCount struct { ChannelID string `db:"channelid"` // Duration is an ISO8601 date string. Duration string `db:"duration"` PostCount int `db:"postcount"` } // Top DMs type TopDM struct { MessageCount int64 `json:"post_count"` OutgoingMessageCount int64 `json:"outgoing_message_count"` Participants string `json:"-"` ChannelId string `json:"-"` SecondParticipant *TopDMInsightUserInformation `json:"second_participant"` } type OutgoingMessageQueryResult struct { ChannelId string MessageCount int } type TopDMList struct { InsightsListData Items []*TopDM `json:"items"` } func TimeRangeToNumberDays(timeRange string) int { var n int switch timeRange { case TimeRangeToday: n = 1 case TimeRange7Day: n = 7 case TimeRange28Day: n = 28 } return n } // ChannelPostCountByDuration contains a count of posts by channel id, grouped by ISO8601 date string. // Example 1 (grouped by day): // // cpc := model.ChannelPostCountByDuration{ // "2009-11-11": { // "ezbp7nqxzjgdir8riodyafr9ww": 90, // "p949c1xdojfgzffxma3p3s3ikr": 201, // }, // "2009-11-12": { // "ezbp7nqxzjgdir8riodyafr9ww": 45, // "p949c1xdojfgzffxma3p3s3ikr": 68, // }, // } // // Example 2 (grouped by hour): // // cpc := model.ChannelPostCountByDuration{ // "2009-11-11T01": { // "ezbp7nqxzjgdir8riodyafr9ww": 90, // "p949c1xdojfgzffxma3p3s3ikr": 201, // }, // "2009-11-11T02": { // "ezbp7nqxzjgdir8riodyafr9ww": 45, // "p949c1xdojfgzffxma3p3s3ikr": 68, // }, // } type ChannelPostCountByDuration map[string]map[string]int func blankChannelCountsMap(channelIDs []string) map[string]int { blankChannelCounts := map[string]int{} for _, id := range channelIDs { blankChannelCounts[id] = 0 } return blankChannelCounts } func ToDailyPostCountViewModel(dpc []*DurationPostCount, startTime *time.Time, numDays int, channelIDs []string) ChannelPostCountByDuration { viewModel := ChannelPostCountByDuration{} keyTime := *startTime nowAtLocation := time.Now().In(startTime.Location()) if numDays == 1 { for keyTime.Before(nowAtLocation) { dateTimeKey := keyTime.Format(time.RFC3339) viewModel[dateTimeKey] = blankChannelCountsMap(channelIDs) keyTime = keyTime.Add(time.Hour) } } else { for keyTime.Before(nowAtLocation) { dateTimeKey := keyTime.Format("2006-01-02") viewModel[dateTimeKey] = blankChannelCountsMap(channelIDs) keyTime = keyTime.Add(24 * time.Hour) } } for _, item := range dpc { var parseFormat string var keyFormat string if numDays == 1 { parseFormat = "2006-01-02T15 " keyFormat = time.RFC3339 } else { parseFormat = "2006-01-02" keyFormat = parseFormat } durTime, err := time.ParseInLocation(parseFormat, item.Duration, startTime.Location()) if err != nil { continue } localizedKey := durTime.Format(keyFormat) _, hasKey := viewModel[localizedKey] if !hasKey { viewModel[localizedKey] = map[string]int{} } viewModel[localizedKey][item.ChannelID] = item.PostCount } return viewModel } // Deprecated: This method doesn't perform error checking. // Use GetStartOfDayForTimeRange instead. // // StartOfDayForTimeRange gets the unix start time in milliseconds from the given time range. // Time range can be one of: "today", "7_day", or "28_day". func StartOfDayForTimeRange(timeRange string, location *time.Location) *time.Time { now := time.Now().In(location) resultTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location) switch timeRange { case TimeRange7Day: resultTime = resultTime.Add(time.Hour * time.Duration(-144)) case TimeRange28Day: resultTime = resultTime.Add(time.Hour * time.Duration(-648)) } return &resultTime } // GetStartOfDayForTimeRange gets the unix start time in milliseconds from the given time range. // Time range can be one of: "today", "7_day", or "28_day". func GetStartOfDayForTimeRange(timeRange string, location *time.Location) (*time.Time, *AppError) { now := time.Now().In(location) resultTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location) switch timeRange { case TimeRangeToday: case TimeRange7Day: resultTime = resultTime.Add(time.Hour * time.Duration(-144)) case TimeRange28Day: resultTime = resultTime.Add(time.Hour * time.Duration(-648)) default: return nil, NewAppError("GetStartOfDayForTimeRange", "model.insights.get_start_of_day_for_time_range.time_range.app_error", nil, "", http.StatusBadRequest) } return &resultTime, nil } // GetTopReactionListWithPagination adds a rank to each item in the given list of TopReaction and checks if there is // another page that can be fetched based on the given limit and offset. The given list of TopReaction is assumed to be // sorted by Count. Returns a TopReactionList. func GetTopReactionListWithPagination(reactions []*TopReaction, limit int) *TopReactionList { // Add pagination support var hasNext bool if (limit != 0) && (len(reactions) == limit+1) { hasNext = true reactions = reactions[:len(reactions)-1] } return &TopReactionList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: reactions} } // GetTopChannelListWithPagination adds a rank to each item in the given list of TopChannel and checks if there is // another page that can be fetched based on the given limit and offset. The given list of TopChannel is assumed to be // sorted by Score. Returns a TopChannelList. func GetTopChannelListWithPagination(channels []*TopChannel, limit int) *TopChannelList { // Add pagination support var hasNext bool if (limit != 0) && (len(channels) == limit+1) { hasNext = true channels = channels[:len(channels)-1] } return &TopChannelList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: channels} } // GetTopThreadListWithPagination adds a rank to each item in the given list of TopThread and checks if there is // another page that can be fetched based on the given limit and offset. The given list of TopThread is assumed to be // sorted by ReplyCount(score). Returns a TopThreadList. func GetTopThreadListWithPagination(threads []*TopThread, limit int) *TopThreadList { // Add pagination support var hasNext bool if (limit != 0) && (len(threads) == limit+1) { hasNext = true threads = threads[:len(threads)-1] } return &TopThreadList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: threads} } // GetTopInactiveChannelListWithPagination adds a rank to each item in the given list of TopInactiveChannel and checks if there is // another page that can be fetched based on the given limit and offset. The given list of TopInactiveChannel is assumed to be // sorted by Score. Returns a TopInactiveChannelList. func GetTopInactiveChannelListWithPagination(channels []*TopInactiveChannel, limit int) *TopInactiveChannelList { // Add pagination support var hasNext bool if (limit != 0) && (len(channels) == limit+1) { hasNext = true channels = channels[:len(channels)-1] } return &TopInactiveChannelList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: channels} } // GetTopDMListWithPagination adds a rank to each item in the given list of TopDM and checks if there is // another page that can be fetched based on the given limit and offset. The given list of TopDM is assumed to be // sorted by MessageCount(score). Returns a TopDMList. func GetTopDMListWithPagination(dms []*TopDM, limit int) *TopDMList { // Add pagination support var hasNext bool if (limit != 0) && (len(dms) == limit+1) { hasNext = true dms = dms[:len(dms)-1] } return &TopDMList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: dms} } func GetNewTeamMembersListWithPagination(teamMembers []*NewTeamMember, limit int) *NewTeamMembersList { var hasNext bool if (limit != 0) && (len(teamMembers) == limit+1) { hasNext = true teamMembers = teamMembers[:len(teamMembers)-1] } return &NewTeamMembersList{InsightsListData: InsightsListData{HasNext: hasNext}, Items: teamMembers} }