// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package conversations import ( "context" "fmt" "code.gitea.io/gitea/models/db" "xorm.io/builder" "xorm.io/xorm" ) // ConversationStats represents conversation statistic information. type ConversationStats struct { OpenCount, LockedCount int64 YourRepositoriesCount int64 CreateCount int64 } // Filter modes. const ( FilterModeAll = iota FilterModeAssign FilterModeCreate FilterModeMention FilterModeYourRepositories ) const ( // MaxQueryParameters represents the max query parameters // When queries are broken down in parts because of the number // of parameters, attempt to break by this amount MaxQueryParameters = 300 ) // CountConversationsByRepo map from repoID to number of conversations matching the options func CountConversationsByRepo(ctx context.Context, opts *ConversationsOptions) (map[int64]int64, error) { sess := db.GetEngine(ctx). Join("INNER", "repository", "`conversation`.repo_id = `repository`.id") applyConditions(sess, opts) countsSlice := make([]*struct { RepoID int64 Count int64 }, 0, 10) if err := sess.GroupBy("conversation.repo_id"). Select("conversation.repo_id AS repo_id, COUNT(*) AS count"). Table("conversation"). Find(&countsSlice); err != nil { return nil, fmt.Errorf("unable to CountConversationsByRepo: %w", err) } countMap := make(map[int64]int64, len(countsSlice)) for _, c := range countsSlice { countMap[c.RepoID] = c.Count } return countMap, nil } // CountConversations number return of conversations by given conditions. func CountConversations(ctx context.Context, opts *ConversationsOptions, otherConds ...builder.Cond) (int64, error) { sess := db.GetEngine(ctx). Select("COUNT(conversation.id) AS count"). Table("conversation"). Join("INNER", "repository", "`conversation`.repo_id = `repository`.id") applyConditions(sess, opts) for _, cond := range otherConds { sess.And(cond) } return sess.Count() } // GetConversationStats returns conversation statistic information by given conditions. func GetConversationStats(ctx context.Context, opts *ConversationsOptions) (*ConversationStats, error) { if len(opts.ConversationIDs) <= MaxQueryParameters { return getConversationStatsChunk(ctx, opts, opts.ConversationIDs) } // If too long a list of IDs is provided, we get the statistics in // smaller chunks and get accumulates. Note: this could potentially // get us invalid results. The alternative is to insert the list of // ids in a temporary table and join from them. accum := &ConversationStats{} for i := 0; i < len(opts.ConversationIDs); { chunk := i + MaxQueryParameters if chunk > len(opts.ConversationIDs) { chunk = len(opts.ConversationIDs) } stats, err := getConversationStatsChunk(ctx, opts, opts.ConversationIDs[i:chunk]) if err != nil { return nil, err } accum.YourRepositoriesCount += stats.YourRepositoriesCount accum.CreateCount += stats.CreateCount accum.LockedCount += stats.LockedCount i = chunk } return accum, nil } func getConversationStatsChunk(ctx context.Context, opts *ConversationsOptions, conversationIDs []int64) (*ConversationStats, error) { stats := &ConversationStats{} sess := db.GetEngine(ctx). Join("INNER", "repository", "`conversation`.repo_id = `repository`.id") var err error stats.OpenCount, err = applyConversationsOptions(sess, opts, conversationIDs). And("conversation.is_locked = ?", false). Count(new(Conversation)) if err != nil { return stats, err } stats.LockedCount, err = applyConversationsOptions(sess, opts, conversationIDs). And("conversation.is_locked = ?", true). Count(new(Conversation)) return stats, err } func applyConversationsOptions(sess *xorm.Session, opts *ConversationsOptions, conversationIDs []int64) *xorm.Session { if len(opts.RepoIDs) > 1 { sess.In("conversation.repo_id", opts.RepoIDs) } else if len(opts.RepoIDs) == 1 { sess.And("conversation.repo_id = ?", opts.RepoIDs[0]) } if len(conversationIDs) > 0 { sess.In("conversation.id", conversationIDs) } return sess } // CountOrphanedConversations count conversations without a repo func CountOrphanedConversations(ctx context.Context) (int64, error) { return db.GetEngine(ctx). Table("conversation"). Join("LEFT", "repository", "conversation.repo_id=repository.id"). Where(builder.IsNull{"repository.id"}). Select("COUNT(`conversation`.`id`)"). Count() }