From d2e4039def61d9cc9952be462216001125327270 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Thu, 24 Aug 2023 14:06:17 +0900 Subject: [PATCH] Add `member`, `collaborator`, `contributor`, and `first-time contributor` roles and tooltips (#26658) GitHub like role descriptor ![image](https://github.com/go-gitea/gitea/assets/18380374/ceaed92c-6749-47b3-89e8-0e0e7ae65321) ![image](https://github.com/go-gitea/gitea/assets/18380374/8193ec34-cbf0-47f9-b0de-10dbddd66970) ![image](https://github.com/go-gitea/gitea/assets/18380374/56c7ed85-6177-425e-9f2f-926e99770782) --------- Co-authored-by: delvh Co-authored-by: wxiaoguang Co-authored-by: Lunny Xiao --- models/issues/comment.go | 47 +++++----- models/issues/pull_list.go | 13 +++ options/locale/locale_en-US.ini | 15 +++- routers/web/repo/issue.go | 87 ++++++++++++------- .../repo/issue/view_content/show_role.tmpl | 17 ++-- 5 files changed, 106 insertions(+), 73 deletions(-) diff --git a/models/issues/comment.go b/models/issues/comment.go index e781931261..17e579b455 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" "xorm.io/builder" @@ -181,40 +182,32 @@ func (t CommentType) HasAttachmentSupport() bool { return false } -// RoleDescriptor defines comment tag type -type RoleDescriptor int +// RoleInRepo presents the user's participation in the repo +type RoleInRepo string + +// RoleDescriptor defines comment "role" tags +type RoleDescriptor struct { + IsPoster bool + RoleInRepo RoleInRepo +} // Enumerate all the role tags. const ( - RoleDescriptorNone RoleDescriptor = iota - RoleDescriptorPoster - RoleDescriptorWriter - RoleDescriptorOwner + RoleRepoOwner RoleInRepo = "owner" + RoleRepoMember RoleInRepo = "member" + RoleRepoCollaborator RoleInRepo = "collaborator" + RoleRepoFirstTimeContributor RoleInRepo = "first_time_contributor" + RoleRepoContributor RoleInRepo = "contributor" ) -// WithRole enable a specific tag on the RoleDescriptor. -func (rd RoleDescriptor) WithRole(role RoleDescriptor) RoleDescriptor { - return rd | (1 << role) +// LocaleString returns the locale string name of the role +func (r RoleInRepo) LocaleString(lang translation.Locale) string { + return lang.Tr("repo.issues.role." + string(r)) } -func stringToRoleDescriptor(role string) RoleDescriptor { - switch role { - case "Poster": - return RoleDescriptorPoster - case "Writer": - return RoleDescriptorWriter - case "Owner": - return RoleDescriptorOwner - default: - return RoleDescriptorNone - } -} - -// HasRole returns if a certain role is enabled on the RoleDescriptor. -func (rd RoleDescriptor) HasRole(role string) bool { - roleDescriptor := stringToRoleDescriptor(role) - bitValue := rd & (1 << roleDescriptor) - return (bitValue > 0) +// LocaleHelper returns the locale tooltip of the role +func (r RoleInRepo) LocaleHelper(lang translation.Locale) string { + return lang.Tr("repo.issues.role." + string(r) + "_helper") } // Comment represents a comment in commit and issue page. diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index 3b2416900b..c4506ef150 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -199,3 +199,16 @@ func (prs PullRequestList) GetIssueIDs() []int64 { } return issueIDs } + +// HasMergedPullRequestInRepo returns whether the user(poster) has merged pull-request in the repo +func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bool, error) { + return db.GetEngine(ctx). + Join("INNER", "pull_request", "pull_request.issue_id = issue.id"). + Where("repo_id=?", repoID). + And("poster_id=?", posterID). + And("is_pull=?", true). + And("pull_request.has_merged=?", true). + Select("issue.id"). + Limit(1). + Get(new(Issue)) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f08d2b7eae..e32399dd89 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1480,9 +1480,18 @@ issues.ref_reopening_from = `referenced a pull request %[4]s tha issues.ref_closed_from = `closed this issue %[4]s %[2]s` issues.ref_reopened_from = `reopened this issue %[4]s %[2]s` issues.ref_from = `from %[1]s` -issues.poster = Poster -issues.collaborator = Collaborator -issues.owner = Owner +issues.author = Author +issues.author_helper = This user is the author. +issues.role.owner = Owner +issues.role.owner_helper = This user is the owner of this repository. +issues.role.member = Member +issues.role.member_helper = This user is a member of the organization owning this repository. +issues.role.collaborator = Collaborator +issues.role.collaborator_helper = This user has been invited to collaborate on the repository. +issues.role.first_time_contributor = First-time contributor +issues.role.first_time_contributor_helper = This is the first contribution of this user to the repository. +issues.role.contributor = Contributor +issues.role.contributor_helper = This user has previously committed to the repository. issues.re_request_review=Re-request review issues.is_stale = There have been changes to this PR since this review issues.remove_request_review=Remove review request diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index b04802e452..9a2add1452 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1228,47 +1228,70 @@ func NewIssuePost(ctx *context.Context) { } } -// roleDescriptor returns the Role Descriptor for a comment in/with the given repo, poster and issue +// roleDescriptor returns the role descriptor for a comment in/with the given repo, poster and issue func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *user_model.User, issue *issues_model.Issue, hasOriginalAuthor bool) (issues_model.RoleDescriptor, error) { + roleDescriptor := issues_model.RoleDescriptor{} + if hasOriginalAuthor { - return issues_model.RoleDescriptorNone, nil + return roleDescriptor, nil } perm, err := access_model.GetUserRepoPermission(ctx, repo, poster) if err != nil { - return issues_model.RoleDescriptorNone, err - } - - // By default the poster has no roles on the comment. - roleDescriptor := issues_model.RoleDescriptorNone - - // Check if the poster is owner of the repo. - if perm.IsOwner() { - // If the poster isn't a admin, enable the owner role. - if !poster.IsAdmin { - roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorOwner) - } else { - - // Otherwise check if poster is the real repo admin. - ok, err := access_model.IsUserRealRepoAdmin(repo, poster) - if err != nil { - return issues_model.RoleDescriptorNone, err - } - if ok { - roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorOwner) - } - } - } - - // Is the poster can write issues or pulls to the repo, enable the Writer role. - // Only enable this if the poster doesn't have the owner role already. - if !roleDescriptor.HasRole("Owner") && perm.CanWriteIssuesOrPulls(issue.IsPull) { - roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorWriter) + return roleDescriptor, err } // If the poster is the actual poster of the issue, enable Poster role. - if issue.IsPoster(poster.ID) { - roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorPoster) + roleDescriptor.IsPoster = issue.IsPoster(poster.ID) + + // Check if the poster is owner of the repo. + if perm.IsOwner() { + // If the poster isn't an admin, enable the owner role. + if !poster.IsAdmin { + roleDescriptor.RoleInRepo = issues_model.RoleRepoOwner + return roleDescriptor, nil + } + + // Otherwise check if poster is the real repo admin. + ok, err := access_model.IsUserRealRepoAdmin(repo, poster) + if err != nil { + return roleDescriptor, err + } + if ok { + roleDescriptor.RoleInRepo = issues_model.RoleRepoOwner + return roleDescriptor, nil + } + } + + // If repo is organization, check Member role + if err := repo.LoadOwner(ctx); err != nil { + return roleDescriptor, err + } + if repo.Owner.IsOrganization() { + if isMember, err := organization.IsOrganizationMember(ctx, repo.Owner.ID, poster.ID); err != nil { + return roleDescriptor, err + } else if isMember { + roleDescriptor.RoleInRepo = issues_model.RoleRepoMember + return roleDescriptor, nil + } + } + + // If the poster is the collaborator of the repo + if isCollaborator, err := repo_model.IsCollaborator(ctx, repo.ID, poster.ID); err != nil { + return roleDescriptor, err + } else if isCollaborator { + roleDescriptor.RoleInRepo = issues_model.RoleRepoCollaborator + return roleDescriptor, nil + } + + hasMergedPR, err := issues_model.HasMergedPullRequestInRepo(ctx, repo.ID, poster.ID) + if err != nil { + return roleDescriptor, err + } else if hasMergedPR { + roleDescriptor.RoleInRepo = issues_model.RoleRepoContributor + } else { + // only display first time contributor in the first opening pull request + roleDescriptor.RoleInRepo = issues_model.RoleRepoFirstTimeContributor } return roleDescriptor, nil diff --git a/templates/repo/issue/view_content/show_role.tmpl b/templates/repo/issue/view_content/show_role.tmpl index f85f43bd66..40c8b67fa9 100644 --- a/templates/repo/issue/view_content/show_role.tmpl +++ b/templates/repo/issue/view_content/show_role.tmpl @@ -1,15 +1,10 @@ -{{if and (.ShowRole.HasRole "Poster") (not .IgnorePoster)}} -
- {{ctx.Locale.Tr "repo.issues.poster"}} +{{if and .ShowRole.IsPoster (not .IgnorePoster)}} +
+ {{ctx.Locale.Tr "repo.issues.author"}}
{{end}} -{{if (.ShowRole.HasRole "Writer")}} -
- {{ctx.Locale.Tr "repo.issues.collaborator"}} -
-{{end}} -{{if (.ShowRole.HasRole "Owner")}} -
- {{ctx.Locale.Tr "repo.issues.owner"}} +{{if .ShowRole.RoleInRepo}} +
+ {{.ShowRole.RoleInRepo.LocaleString ctx.Locale}}
{{end}}