mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Some performance optimization on dashboard and issues page (#29010)
This PR do some loading speed optimization for feeds user interface pages. - Load action users batchly but not one by one. - Load action repositories batchly but not one by one. - Load action's Repo Owners batchly but not one by one. - Load action's possible issues batchly but not one by one. - Load action's possible comments batchly but not one by one.
This commit is contained in:
		| @@ -148,6 +148,7 @@ type Action struct { | |||||||
| 	Repo        *repo_model.Repository `xorm:"-"` | 	Repo        *repo_model.Repository `xorm:"-"` | ||||||
| 	CommentID   int64                  `xorm:"INDEX"` | 	CommentID   int64                  `xorm:"INDEX"` | ||||||
| 	Comment     *issues_model.Comment  `xorm:"-"` | 	Comment     *issues_model.Comment  `xorm:"-"` | ||||||
|  | 	Issue       *issues_model.Issue    `xorm:"-"` // get the issue id from content | ||||||
| 	IsDeleted   bool                   `xorm:"NOT NULL DEFAULT false"` | 	IsDeleted   bool                   `xorm:"NOT NULL DEFAULT false"` | ||||||
| 	RefName     string | 	RefName     string | ||||||
| 	IsPrivate   bool               `xorm:"NOT NULL DEFAULT false"` | 	IsPrivate   bool               `xorm:"NOT NULL DEFAULT false"` | ||||||
| @@ -290,11 +291,6 @@ func (a *Action) GetRepoAbsoluteLink(ctx context.Context) string { | |||||||
| 	return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx)) | 	return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx)) | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetCommentHTMLURL returns link to action comment. |  | ||||||
| func (a *Action) GetCommentHTMLURL(ctx context.Context) string { |  | ||||||
| 	return a.getCommentHTMLURL(ctx) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *Action) loadComment(ctx context.Context) (err error) { | func (a *Action) loadComment(ctx context.Context) (err error) { | ||||||
| 	if a.CommentID == 0 || a.Comment != nil { | 	if a.CommentID == 0 || a.Comment != nil { | ||||||
| 		return nil | 		return nil | ||||||
| @@ -303,7 +299,8 @@ func (a *Action) loadComment(ctx context.Context) (err error) { | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *Action) getCommentHTMLURL(ctx context.Context) string { | // GetCommentHTMLURL returns link to action comment. | ||||||
|  | func (a *Action) GetCommentHTMLURL(ctx context.Context) string { | ||||||
| 	if a == nil { | 	if a == nil { | ||||||
| 		return "#" | 		return "#" | ||||||
| 	} | 	} | ||||||
| @@ -311,34 +308,19 @@ func (a *Action) getCommentHTMLURL(ctx context.Context) string { | |||||||
| 	if a.Comment != nil { | 	if a.Comment != nil { | ||||||
| 		return a.Comment.HTMLURL(ctx) | 		return a.Comment.HTMLURL(ctx) | ||||||
| 	} | 	} | ||||||
| 	if len(a.GetIssueInfos()) == 0 { |  | ||||||
|  | 	if err := a.LoadIssue(ctx); err != nil || a.Issue == nil { | ||||||
| 		return "#" | 		return "#" | ||||||
| 	} | 	} | ||||||
| 	// Return link to issue | 	if err := a.Issue.LoadRepo(ctx); err != nil { | ||||||
| 	issueIDString := a.GetIssueInfos()[0] |  | ||||||
| 	issueID, err := strconv.ParseInt(issueIDString, 10, 64) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "#" | 		return "#" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	issue, err := issues_model.GetIssueByID(ctx, issueID) | 	return a.Issue.HTMLURL() | ||||||
| 	if err != nil { |  | ||||||
| 		return "#" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err = issue.LoadRepo(ctx); err != nil { |  | ||||||
| 		return "#" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return issue.HTMLURL() |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetCommentLink returns link to action comment. | // GetCommentLink returns link to action comment. | ||||||
| func (a *Action) GetCommentLink(ctx context.Context) string { | func (a *Action) GetCommentLink(ctx context.Context) string { | ||||||
| 	return a.getCommentLink(ctx) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *Action) getCommentLink(ctx context.Context) string { |  | ||||||
| 	if a == nil { | 	if a == nil { | ||||||
| 		return "#" | 		return "#" | ||||||
| 	} | 	} | ||||||
| @@ -346,26 +328,15 @@ func (a *Action) getCommentLink(ctx context.Context) string { | |||||||
| 	if a.Comment != nil { | 	if a.Comment != nil { | ||||||
| 		return a.Comment.Link(ctx) | 		return a.Comment.Link(ctx) | ||||||
| 	} | 	} | ||||||
| 	if len(a.GetIssueInfos()) == 0 { |  | ||||||
|  | 	if err := a.LoadIssue(ctx); err != nil || a.Issue == nil { | ||||||
| 		return "#" | 		return "#" | ||||||
| 	} | 	} | ||||||
| 	// Return link to issue | 	if err := a.Issue.LoadRepo(ctx); err != nil { | ||||||
| 	issueIDString := a.GetIssueInfos()[0] |  | ||||||
| 	issueID, err := strconv.ParseInt(issueIDString, 10, 64) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "#" | 		return "#" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	issue, err := issues_model.GetIssueByID(ctx, issueID) | 	return a.Issue.Link() | ||||||
| 	if err != nil { |  | ||||||
| 		return "#" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err = issue.LoadRepo(ctx); err != nil { |  | ||||||
| 		return "#" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return issue.Link() |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetBranch returns the action's repository branch. | // GetBranch returns the action's repository branch. | ||||||
| @@ -393,6 +364,10 @@ func (a *Action) GetCreate() time.Time { | |||||||
| 	return a.CreatedUnix.AsTime() | 	return a.CreatedUnix.AsTime() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a *Action) IsIssueEvent() bool { | ||||||
|  | 	return a.OpType.InActions("comment_issue", "approve_pull_request", "reject_pull_request", "comment_pull", "merge_pull_request") | ||||||
|  | } | ||||||
|  |  | ||||||
| // GetIssueInfos returns a list of associated information with the action. | // GetIssueInfos returns a list of associated information with the action. | ||||||
| func (a *Action) GetIssueInfos() []string { | func (a *Action) GetIssueInfos() []string { | ||||||
| 	// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length | 	// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length | ||||||
| @@ -403,27 +378,52 @@ func (a *Action) GetIssueInfos() []string { | |||||||
| 	return ret | 	return ret | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetIssueTitle returns the title of first issue associated with the action. | func (a *Action) getIssueIndex() int64 { | ||||||
| func (a *Action) GetIssueTitle(ctx context.Context) string { | 	infos := a.GetIssueInfos() | ||||||
| 	index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64) | 	if len(infos) == 0 { | ||||||
| 	issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index) | 		return 0 | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error("GetIssueByIndex: %v", err) |  | ||||||
| 		return "500 when get issue" |  | ||||||
| 	} | 	} | ||||||
| 	return issue.Title | 	index, _ := strconv.ParseInt(infos[0], 10, 64) | ||||||
|  | 	return index | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetIssueContent returns the content of first issue associated with | func (a *Action) LoadIssue(ctx context.Context) error { | ||||||
| // this action. | 	if a.Issue != nil { | ||||||
| func (a *Action) GetIssueContent(ctx context.Context) string { | 		return nil | ||||||
| 	index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64) | 	} | ||||||
|  | 	if index := a.getIssueIndex(); index > 0 { | ||||||
| 		issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index) | 		issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 		log.Error("GetIssueByIndex: %v", err) | 			return err | ||||||
| 		return "500 when get issue" |  | ||||||
| 		} | 		} | ||||||
| 	return issue.Content | 		a.Issue = issue | ||||||
|  | 		a.Issue.Repo = a.Repo | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetIssueTitle returns the title of first issue associated with the action. | ||||||
|  | func (a *Action) GetIssueTitle(ctx context.Context) string { | ||||||
|  | 	if err := a.LoadIssue(ctx); err != nil { | ||||||
|  | 		log.Error("LoadIssue: %v", err) | ||||||
|  | 		return "<500 when get issue>" | ||||||
|  | 	} | ||||||
|  | 	if a.Issue == nil { | ||||||
|  | 		return "<Issue not found>" | ||||||
|  | 	} | ||||||
|  | 	return a.Issue.Title | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetIssueContent returns the content of first issue associated with this action. | ||||||
|  | func (a *Action) GetIssueContent(ctx context.Context) string { | ||||||
|  | 	if err := a.LoadIssue(ctx); err != nil { | ||||||
|  | 		log.Error("LoadIssue: %v", err) | ||||||
|  | 		return "<500 when get issue>" | ||||||
|  | 	} | ||||||
|  | 	if a.Issue == nil { | ||||||
|  | 		return "<Content not found>" | ||||||
|  | 	} | ||||||
|  | 	return a.Issue.Content | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetFeedsOptions options for retrieving feeds | // GetFeedsOptions options for retrieving feeds | ||||||
| @@ -463,7 +463,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err | |||||||
| 		return nil, 0, fmt.Errorf("FindAndCount: %w", err) | 		return nil, 0, fmt.Errorf("FindAndCount: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := ActionList(actions).loadAttributes(ctx); err != nil { | 	if err := ActionList(actions).LoadAttributes(ctx); err != nil { | ||||||
| 		return nil, 0, fmt.Errorf("LoadAttributes: %w", err) | 		return nil, 0, fmt.Errorf("LoadAttributes: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,11 +6,16 @@ package activities | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"strconv" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
|  | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/container" | 	"code.gitea.io/gitea/modules/container" | ||||||
|  | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
|  | 	"xorm.io/builder" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ActionList defines a list of actions | // ActionList defines a list of actions | ||||||
| @@ -24,7 +29,7 @@ func (actions ActionList) getUserIDs() []int64 { | |||||||
| 	return userIDs.Values() | 	return userIDs.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) { | func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) { | ||||||
| 	if len(actions) == 0 { | 	if len(actions) == 0 { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| @@ -52,7 +57,7 @@ func (actions ActionList) getRepoIDs() []int64 { | |||||||
| 	return repoIDs.Values() | 	return repoIDs.Values() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (actions ActionList) loadRepositories(ctx context.Context) error { | func (actions ActionList) LoadRepositories(ctx context.Context) error { | ||||||
| 	if len(actions) == 0 { | 	if len(actions) == 0 { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| @@ -63,11 +68,11 @@ func (actions ActionList) loadRepositories(ctx context.Context) error { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("find repository: %w", err) | 		return fmt.Errorf("find repository: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, action := range actions { | 	for _, action := range actions { | ||||||
| 		action.Repo = repoMaps[action.RepoID] | 		action.Repo = repoMaps[action.RepoID] | ||||||
| 	} | 	} | ||||||
| 	return nil | 	repos := repo_model.RepositoryList(util.ValuesOfMap(repoMaps)) | ||||||
|  | 	return repos.LoadUnits(ctx) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*user_model.User) (err error) { | func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*user_model.User) (err error) { | ||||||
| @@ -75,37 +80,124 @@ func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]* | |||||||
| 		userMap = make(map[int64]*user_model.User) | 		userMap = make(map[int64]*user_model.User) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	userSet := make(container.Set[int64], len(actions)) | ||||||
| 	for _, action := range actions { | 	for _, action := range actions { | ||||||
| 		if action.Repo == nil { | 		if action.Repo == nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		repoOwner, ok := userMap[action.Repo.OwnerID] | 		if _, ok := userMap[action.Repo.OwnerID]; !ok { | ||||||
| 		if !ok { | 			userSet.Add(action.Repo.OwnerID) | ||||||
| 			repoOwner, err = user_model.GetUserByID(ctx, action.Repo.OwnerID) |  | ||||||
| 			if err != nil { |  | ||||||
| 				if user_model.IsErrUserNotExist(err) { |  | ||||||
| 					continue |  | ||||||
| 		} | 		} | ||||||
| 				return err |  | ||||||
| 	} | 	} | ||||||
| 			userMap[repoOwner.ID] = repoOwner |  | ||||||
|  | 	if err := db.GetEngine(ctx). | ||||||
|  | 		In("id", userSet.Values()). | ||||||
|  | 		Find(&userMap); err != nil { | ||||||
|  | 		return fmt.Errorf("find user: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, action := range actions { | ||||||
|  | 		if action.Repo != nil { | ||||||
|  | 			action.Repo.Owner = userMap[action.Repo.OwnerID] | ||||||
| 		} | 		} | ||||||
| 		action.Repo.Owner = repoOwner |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // loadAttributes loads all attributes | // LoadAttributes loads all attributes | ||||||
| func (actions ActionList) loadAttributes(ctx context.Context) error { | func (actions ActionList) LoadAttributes(ctx context.Context) error { | ||||||
| 	userMap, err := actions.loadUsers(ctx) | 	// the load sequence cannot be changed because of the dependencies | ||||||
|  | 	userMap, err := actions.LoadActUsers(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	if err := actions.LoadRepositories(ctx); err != nil { | ||||||
| 	if err := actions.loadRepositories(ctx); err != nil { |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	if err := actions.loadRepoOwner(ctx, userMap); err != nil { | ||||||
| 	return actions.loadRepoOwner(ctx, userMap) | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := actions.LoadIssues(ctx); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return actions.LoadComments(ctx) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (actions ActionList) LoadComments(ctx context.Context) error { | ||||||
|  | 	if len(actions) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	commentIDs := make([]int64, 0, len(actions)) | ||||||
|  | 	for _, action := range actions { | ||||||
|  | 		if action.CommentID > 0 { | ||||||
|  | 			commentIDs = append(commentIDs, action.CommentID) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs)) | ||||||
|  | 	if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil { | ||||||
|  | 		return fmt.Errorf("find comment: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, action := range actions { | ||||||
|  | 		if action.CommentID > 0 { | ||||||
|  | 			action.Comment = commentsMap[action.CommentID] | ||||||
|  | 			if action.Comment != nil { | ||||||
|  | 				action.Comment.Issue = action.Issue | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (actions ActionList) LoadIssues(ctx context.Context) error { | ||||||
|  | 	if len(actions) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	conditions := builder.NewCond() | ||||||
|  | 	issueNum := 0 | ||||||
|  | 	for _, action := range actions { | ||||||
|  | 		if action.IsIssueEvent() { | ||||||
|  | 			infos := action.GetIssueInfos() | ||||||
|  | 			if len(infos) == 0 { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			index, _ := strconv.ParseInt(infos[0], 10, 64) | ||||||
|  | 			if index > 0 { | ||||||
|  | 				conditions = conditions.Or(builder.Eq{ | ||||||
|  | 					"repo_id": action.RepoID, | ||||||
|  | 					"`index`": index, | ||||||
|  | 				}) | ||||||
|  | 				issueNum++ | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if !conditions.IsValid() { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	issuesMap := make(map[string]*issues_model.Issue, issueNum) | ||||||
|  | 	issues := make([]*issues_model.Issue, 0, issueNum) | ||||||
|  | 	if err := db.GetEngine(ctx).Where(conditions).Find(&issues); err != nil { | ||||||
|  | 		return fmt.Errorf("find issue: %w", err) | ||||||
|  | 	} | ||||||
|  | 	for _, issue := range issues { | ||||||
|  | 		issuesMap[fmt.Sprintf("%d-%d", issue.RepoID, issue.Index)] = issue | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, action := range actions { | ||||||
|  | 		if !action.IsIssueEvent() { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if index := action.getIssueIndex(); index > 0 { | ||||||
|  | 			if issue, ok := issuesMap[fmt.Sprintf("%d-%d", action.RepoID, index)]; ok { | ||||||
|  | 				action.Issue = issue | ||||||
|  | 				action.Issue.Repo = action.Repo | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -476,6 +476,16 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) { | |||||||
| 	} | 	} | ||||||
| 	trackedTimes := make(map[int64]int64, len(issues)) | 	trackedTimes := make(map[int64]int64, len(issues)) | ||||||
|  |  | ||||||
|  | 	reposMap := make(map[int64]*repo_model.Repository, len(issues)) | ||||||
|  | 	for _, issue := range issues { | ||||||
|  | 		reposMap[issue.RepoID] = issue.Repo | ||||||
|  | 	} | ||||||
|  | 	repos := repo_model.RepositoryListOfMap(reposMap) | ||||||
|  |  | ||||||
|  | 	if err := repos.LoadUnits(ctx); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	ids := make([]int64, 0, len(issues)) | 	ids := make([]int64, 0, len(issues)) | ||||||
| 	for _, issue := range issues { | 	for _, issue := range issues { | ||||||
| 		if issue.Repo.IsTimetrackerEnabled(ctx) { | 		if issue.Repo.IsTimetrackerEnabled(ctx) { | ||||||
|   | |||||||
| @@ -531,6 +531,9 @@ func (repo *Repository) GetBaseRepo(ctx context.Context) (err error) { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if repo.BaseRepo != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| 	repo.BaseRepo, err = GetRepositoryByID(ctx, repo.ForkID) | 	repo.BaseRepo, err = GetRepositoryByID(ctx, repo.ForkID) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|   | |||||||
| @@ -63,6 +63,41 @@ func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList { | |||||||
| 	return RepositoryList(ValuesRepository(repoMap)) | 	return RepositoryList(ValuesRepository(repoMap)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (repos RepositoryList) LoadUnits(ctx context.Context) error { | ||||||
|  | 	if len(repos) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Load units. | ||||||
|  | 	units := make([]*RepoUnit, 0, len(repos)*6) | ||||||
|  | 	if err := db.GetEngine(ctx). | ||||||
|  | 		In("repo_id", repos.IDs()). | ||||||
|  | 		Find(&units); err != nil { | ||||||
|  | 		return fmt.Errorf("find units: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	unitsMap := make(map[int64][]*RepoUnit, len(repos)) | ||||||
|  | 	for _, unit := range units { | ||||||
|  | 		if !unit.Type.UnitGlobalDisabled() { | ||||||
|  | 			unitsMap[unit.RepoID] = append(unitsMap[unit.RepoID], unit) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, repo := range repos { | ||||||
|  | 		repo.Units = unitsMap[repo.ID] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (repos RepositoryList) IDs() []int64 { | ||||||
|  | 	repoIDs := make([]int64, len(repos)) | ||||||
|  | 	for i := range repos { | ||||||
|  | 		repoIDs[i] = repos[i].ID | ||||||
|  | 	} | ||||||
|  | 	return repoIDs | ||||||
|  | } | ||||||
|  |  | ||||||
| // LoadAttributes loads the attributes for the given RepositoryList | // LoadAttributes loads the attributes for the given RepositoryList | ||||||
| func (repos RepositoryList) LoadAttributes(ctx context.Context) error { | func (repos RepositoryList) LoadAttributes(ctx context.Context) error { | ||||||
| 	if len(repos) == 0 { | 	if len(repos) == 0 { | ||||||
|   | |||||||
| @@ -53,3 +53,12 @@ func Sorted[S ~[]E, E cmp.Ordered](values S) S { | |||||||
| 	slices.Sort(values) | 	slices.Sort(values) | ||||||
| 	return values | 	return values | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: Replace with "maps.Values" once available | ||||||
|  | func ValuesOfMap[K comparable, V any](m map[K]V) []V { | ||||||
|  | 	values := make([]V, 0, len(m)) | ||||||
|  | 	for _, v := range m { | ||||||
|  | 		values = append(values, v) | ||||||
|  | 	} | ||||||
|  | 	return values | ||||||
|  | } | ||||||
|   | |||||||
| @@ -547,9 +547,13 @@ func InitiateDownload(ctx *context.Context) { | |||||||
|  |  | ||||||
| // SearchRepo repositories via options | // SearchRepo repositories via options | ||||||
| func SearchRepo(ctx *context.Context) { | func SearchRepo(ctx *context.Context) { | ||||||
|  | 	page := ctx.FormInt("page") | ||||||
|  | 	if page <= 0 { | ||||||
|  | 		page = 1 | ||||||
|  | 	} | ||||||
| 	opts := &repo_model.SearchRepoOptions{ | 	opts := &repo_model.SearchRepoOptions{ | ||||||
| 		ListOptions: db.ListOptions{ | 		ListOptions: db.ListOptions{ | ||||||
| 			Page:     ctx.FormInt("page"), | 			Page:     page, | ||||||
| 			PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), | 			PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), | ||||||
| 		}, | 		}, | ||||||
| 		Actor:              ctx.Doer, | 		Actor:              ctx.Doer, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user