mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 21:16:26 +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>
		
			
				
	
	
		
			294 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package integration_test
 | 
						|
 | 
						|
import (
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/models/db"
 | 
						|
	"code.gitea.io/gitea/models/organization"
 | 
						|
	"code.gitea.io/gitea/models/unittest"
 | 
						|
 | 
						|
	"github.com/stretchr/testify/assert"
 | 
						|
	"github.com/stretchr/testify/require"
 | 
						|
)
 | 
						|
 | 
						|
// TestTimesByRepos tests TimesByRepos functionality
 | 
						|
func testTimesByRepos(t *testing.T) {
 | 
						|
	kases := []struct {
 | 
						|
		name     string
 | 
						|
		unixfrom int64
 | 
						|
		unixto   int64
 | 
						|
		orgname  int64
 | 
						|
		expected []organization.WorktimeSumByRepos
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:     "Full sum for org 1",
 | 
						|
			unixfrom: 0,
 | 
						|
			unixto:   9223372036854775807,
 | 
						|
			orgname:  1,
 | 
						|
			expected: []organization.WorktimeSumByRepos(nil),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Full sum for org 2",
 | 
						|
			unixfrom: 0,
 | 
						|
			unixto:   9223372036854775807,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByRepos{
 | 
						|
				{
 | 
						|
					RepoName: "repo1",
 | 
						|
					SumTime:  4083,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					RepoName: "repo2",
 | 
						|
					SumTime:  75,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Simple time bound",
 | 
						|
			unixfrom: 946684801,
 | 
						|
			unixto:   946684802,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByRepos{
 | 
						|
				{
 | 
						|
					RepoName: "repo1",
 | 
						|
					SumTime:  3662,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Both times inclusive",
 | 
						|
			unixfrom: 946684801,
 | 
						|
			unixto:   946684801,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByRepos{
 | 
						|
				{
 | 
						|
					RepoName: "repo1",
 | 
						|
					SumTime:  3661,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Should ignore deleted",
 | 
						|
			unixfrom: 947688814,
 | 
						|
			unixto:   947688815,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByRepos{
 | 
						|
				{
 | 
						|
					RepoName: "repo2",
 | 
						|
					SumTime:  71,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	// Run test kases
 | 
						|
	for _, kase := range kases {
 | 
						|
		t.Run(kase.name, func(t *testing.T) {
 | 
						|
			org, err := organization.GetOrgByID(db.DefaultContext, kase.orgname)
 | 
						|
			assert.NoError(t, err)
 | 
						|
			results, err := organization.GetWorktimeByRepos(org, kase.unixfrom, kase.unixto)
 | 
						|
			assert.NoError(t, err)
 | 
						|
			assert.Equal(t, kase.expected, results)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestTimesByMilestones tests TimesByMilestones functionality
 | 
						|
func testTimesByMilestones(t *testing.T) {
 | 
						|
	kases := []struct {
 | 
						|
		name     string
 | 
						|
		unixfrom int64
 | 
						|
		unixto   int64
 | 
						|
		orgname  int64
 | 
						|
		expected []organization.WorktimeSumByMilestones
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:     "Full sum for org 1",
 | 
						|
			unixfrom: 0,
 | 
						|
			unixto:   9223372036854775807,
 | 
						|
			orgname:  1,
 | 
						|
			expected: []organization.WorktimeSumByMilestones(nil),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Full sum for org 2",
 | 
						|
			unixfrom: 0,
 | 
						|
			unixto:   9223372036854775807,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMilestones{
 | 
						|
				{
 | 
						|
					RepoName:      "repo1",
 | 
						|
					MilestoneName: "",
 | 
						|
					MilestoneID:   0,
 | 
						|
					SumTime:       401,
 | 
						|
					HideRepoName:  false,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					RepoName:      "repo1",
 | 
						|
					MilestoneName: "milestone1",
 | 
						|
					MilestoneID:   1,
 | 
						|
					SumTime:       3682,
 | 
						|
					HideRepoName:  true,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					RepoName:      "repo2",
 | 
						|
					MilestoneName: "",
 | 
						|
					MilestoneID:   0,
 | 
						|
					SumTime:       75,
 | 
						|
					HideRepoName:  false,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Simple time bound",
 | 
						|
			unixfrom: 946684801,
 | 
						|
			unixto:   946684802,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMilestones{
 | 
						|
				{
 | 
						|
					RepoName:      "repo1",
 | 
						|
					MilestoneName: "milestone1",
 | 
						|
					MilestoneID:   1,
 | 
						|
					SumTime:       3662,
 | 
						|
					HideRepoName:  false,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Both times inclusive",
 | 
						|
			unixfrom: 946684801,
 | 
						|
			unixto:   946684801,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMilestones{
 | 
						|
				{
 | 
						|
					RepoName:      "repo1",
 | 
						|
					MilestoneName: "milestone1",
 | 
						|
					MilestoneID:   1,
 | 
						|
					SumTime:       3661,
 | 
						|
					HideRepoName:  false,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Should ignore deleted",
 | 
						|
			unixfrom: 947688814,
 | 
						|
			unixto:   947688815,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMilestones{
 | 
						|
				{
 | 
						|
					RepoName:      "repo2",
 | 
						|
					MilestoneName: "",
 | 
						|
					MilestoneID:   0,
 | 
						|
					SumTime:       71,
 | 
						|
					HideRepoName:  false,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	// Run test kases
 | 
						|
	for _, kase := range kases {
 | 
						|
		t.Run(kase.name, func(t *testing.T) {
 | 
						|
			org, err := organization.GetOrgByID(db.DefaultContext, kase.orgname)
 | 
						|
			require.NoError(t, err)
 | 
						|
			results, err := organization.GetWorktimeByMilestones(org, kase.unixfrom, kase.unixto)
 | 
						|
			if assert.NoError(t, err) {
 | 
						|
				assert.Equal(t, kase.expected, results)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestTimesByMembers tests TimesByMembers functionality
 | 
						|
func testTimesByMembers(t *testing.T) {
 | 
						|
	kases := []struct {
 | 
						|
		name     string
 | 
						|
		unixfrom int64
 | 
						|
		unixto   int64
 | 
						|
		orgname  int64
 | 
						|
		expected []organization.WorktimeSumByMembers
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:     "Full sum for org 1",
 | 
						|
			unixfrom: 0,
 | 
						|
			unixto:   9223372036854775807,
 | 
						|
			orgname:  1,
 | 
						|
			expected: []organization.WorktimeSumByMembers(nil),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			// Test case: Sum of times forever in org no. 2
 | 
						|
			name:     "Full sum for org 2",
 | 
						|
			unixfrom: 0,
 | 
						|
			unixto:   9223372036854775807,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMembers{
 | 
						|
				{
 | 
						|
					UserName: "user2",
 | 
						|
					SumTime:  3666,
 | 
						|
				},
 | 
						|
				{
 | 
						|
					UserName: "user1",
 | 
						|
					SumTime:  491,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Simple time bound",
 | 
						|
			unixfrom: 946684801,
 | 
						|
			unixto:   946684802,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMembers{
 | 
						|
				{
 | 
						|
					UserName: "user2",
 | 
						|
					SumTime:  3662,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Both times inclusive",
 | 
						|
			unixfrom: 946684801,
 | 
						|
			unixto:   946684801,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMembers{
 | 
						|
				{
 | 
						|
					UserName: "user2",
 | 
						|
					SumTime:  3661,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:     "Should ignore deleted",
 | 
						|
			unixfrom: 947688814,
 | 
						|
			unixto:   947688815,
 | 
						|
			orgname:  2,
 | 
						|
			expected: []organization.WorktimeSumByMembers{
 | 
						|
				{
 | 
						|
					UserName: "user1",
 | 
						|
					SumTime:  71,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	// Run test kases
 | 
						|
	for _, kase := range kases {
 | 
						|
		t.Run(kase.name, func(t *testing.T) {
 | 
						|
			org, err := organization.GetOrgByID(db.DefaultContext, kase.orgname)
 | 
						|
			assert.NoError(t, err)
 | 
						|
			results, err := organization.GetWorktimeByMembers(org, kase.unixfrom, kase.unixto)
 | 
						|
			assert.NoError(t, err)
 | 
						|
			assert.Equal(t, kase.expected, results)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestOrgWorktime(t *testing.T) {
 | 
						|
	// we need to run these tests in integration test because there are complex SQL queries
 | 
						|
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						|
	t.Run("ByRepos", testTimesByRepos)
 | 
						|
	t.Run("ByMilestones", testTimesByMilestones)
 | 
						|
	t.Run("ByMembers", testTimesByMembers)
 | 
						|
}
 |