mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 02:04:11 +01:00 
			
		
		
		
	Dear Gitea team, first of all, thanks for the great work you're doing with this project. I'm planning to introduce Gitea at a client site, and noticed that while there is time recording, there are no project-manager-friendly reports to actually make use of that data, as were also mentioned by others in #4870 #8684 and #13531. Since I had a little time last weekend, I had put together something that I hope to be a useful contribution to this great project (while of course useful for me too). This PR adds a new "Worktime" tab to the Organisation level. There is a date range selector (by default set to the current month), and there are three possible views: - by repository, - by milestone, and - by team member. Happy to receive any feedback! There are several possible future improvements of course (predefined date ranges, charts, a member time sheet, matrix of repos/members, etc) but I hope that even in this relatively simple state this would be useful to lots of people. <img width="1161" alt="Screen Shot 2022-05-25 at 22 12 58" src="https://user-images.githubusercontent.com/118010/170366976-af00c7af-c4f3-4117-86d7-00356d6797a5.png"> Keep up the good work! Kristof --------- Co-authored-by: user <user@kk-git1> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			104 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			104 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package organization
 | 
						|
 | 
						|
import (
 | 
						|
	"sort"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/models/db"
 | 
						|
 | 
						|
	"xorm.io/builder"
 | 
						|
)
 | 
						|
 | 
						|
type WorktimeSumByRepos struct {
 | 
						|
	RepoName string
 | 
						|
	SumTime  int64
 | 
						|
}
 | 
						|
 | 
						|
func GetWorktimeByRepos(org *Organization, unitFrom, unixTo int64) (results []WorktimeSumByRepos, err error) {
 | 
						|
	err = db.GetEngine(db.DefaultContext).
 | 
						|
		Select("repository.name AS repo_name, SUM(tracked_time.time) AS sum_time").
 | 
						|
		Table("tracked_time").
 | 
						|
		Join("INNER", "issue", "tracked_time.issue_id = issue.id").
 | 
						|
		Join("INNER", "repository", "issue.repo_id = repository.id").
 | 
						|
		Where(builder.Eq{"repository.owner_id": org.ID}).
 | 
						|
		And(builder.Eq{"tracked_time.deleted": false}).
 | 
						|
		And(builder.Gte{"tracked_time.created_unix": unitFrom}).
 | 
						|
		And(builder.Lte{"tracked_time.created_unix": unixTo}).
 | 
						|
		GroupBy("repository.name").
 | 
						|
		OrderBy("repository.name").
 | 
						|
		Find(&results)
 | 
						|
	return results, err
 | 
						|
}
 | 
						|
 | 
						|
type WorktimeSumByMilestones struct {
 | 
						|
	RepoName          string
 | 
						|
	MilestoneName     string
 | 
						|
	MilestoneID       int64
 | 
						|
	MilestoneDeadline int64
 | 
						|
	SumTime           int64
 | 
						|
	HideRepoName      bool
 | 
						|
}
 | 
						|
 | 
						|
func GetWorktimeByMilestones(org *Organization, unitFrom, unixTo int64) (results []WorktimeSumByMilestones, err error) {
 | 
						|
	err = db.GetEngine(db.DefaultContext).
 | 
						|
		Select("repository.name AS repo_name, milestone.name AS milestone_name, milestone.id AS milestone_id, milestone.deadline_unix as milestone_deadline, SUM(tracked_time.time) AS sum_time").
 | 
						|
		Table("tracked_time").
 | 
						|
		Join("INNER", "issue", "tracked_time.issue_id = issue.id").
 | 
						|
		Join("INNER", "repository", "issue.repo_id = repository.id").
 | 
						|
		Join("LEFT", "milestone", "issue.milestone_id = milestone.id").
 | 
						|
		Where(builder.Eq{"repository.owner_id": org.ID}).
 | 
						|
		And(builder.Eq{"tracked_time.deleted": false}).
 | 
						|
		And(builder.Gte{"tracked_time.created_unix": unitFrom}).
 | 
						|
		And(builder.Lte{"tracked_time.created_unix": unixTo}).
 | 
						|
		GroupBy("repository.name, milestone.name, milestone.deadline_unix, milestone.id").
 | 
						|
		OrderBy("repository.name, milestone.deadline_unix, milestone.id").
 | 
						|
		Find(&results)
 | 
						|
 | 
						|
	// TODO: pgsql: NULL values are sorted last in default ascending order, so we need to sort them manually again.
 | 
						|
	sort.Slice(results, func(i, j int) bool {
 | 
						|
		if results[i].RepoName != results[j].RepoName {
 | 
						|
			return results[i].RepoName < results[j].RepoName
 | 
						|
		}
 | 
						|
		if results[i].MilestoneDeadline != results[j].MilestoneDeadline {
 | 
						|
			return results[i].MilestoneDeadline < results[j].MilestoneDeadline
 | 
						|
		}
 | 
						|
		return results[i].MilestoneID < results[j].MilestoneID
 | 
						|
	})
 | 
						|
 | 
						|
	// Show only the first RepoName, for nicer output.
 | 
						|
	prevRepoName := ""
 | 
						|
	for i := 0; i < len(results); i++ {
 | 
						|
		res := &results[i]
 | 
						|
		res.MilestoneDeadline = 0 // clear the deadline because we do not really need it
 | 
						|
		if prevRepoName == res.RepoName {
 | 
						|
			res.HideRepoName = true
 | 
						|
		}
 | 
						|
		prevRepoName = res.RepoName
 | 
						|
	}
 | 
						|
	return results, err
 | 
						|
}
 | 
						|
 | 
						|
type WorktimeSumByMembers struct {
 | 
						|
	UserName string
 | 
						|
	SumTime  int64
 | 
						|
}
 | 
						|
 | 
						|
func GetWorktimeByMembers(org *Organization, unitFrom, unixTo int64) (results []WorktimeSumByMembers, err error) {
 | 
						|
	err = db.GetEngine(db.DefaultContext).
 | 
						|
		Select("`user`.name AS user_name, SUM(tracked_time.time) AS sum_time").
 | 
						|
		Table("tracked_time").
 | 
						|
		Join("INNER", "issue", "tracked_time.issue_id = issue.id").
 | 
						|
		Join("INNER", "repository", "issue.repo_id = repository.id").
 | 
						|
		Join("INNER", "`user`", "tracked_time.user_id = `user`.id").
 | 
						|
		Where(builder.Eq{"repository.owner_id": org.ID}).
 | 
						|
		And(builder.Eq{"tracked_time.deleted": false}).
 | 
						|
		And(builder.Gte{"tracked_time.created_unix": unitFrom}).
 | 
						|
		And(builder.Lte{"tracked_time.created_unix": unixTo}).
 | 
						|
		GroupBy("`user`.name").
 | 
						|
		OrderBy("sum_time DESC").
 | 
						|
		Find(&results)
 | 
						|
	return results, err
 | 
						|
}
 |