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 <dev.lh@web.de> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
parent
0d55f64e6c
commit
d2e4039def
|
@ -23,6 +23,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
@ -181,40 +182,32 @@ func (t CommentType) HasAttachmentSupport() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoleDescriptor defines comment tag type
|
// RoleInRepo presents the user's participation in the repo
|
||||||
type RoleDescriptor int
|
type RoleInRepo string
|
||||||
|
|
||||||
|
// RoleDescriptor defines comment "role" tags
|
||||||
|
type RoleDescriptor struct {
|
||||||
|
IsPoster bool
|
||||||
|
RoleInRepo RoleInRepo
|
||||||
|
}
|
||||||
|
|
||||||
// Enumerate all the role tags.
|
// Enumerate all the role tags.
|
||||||
const (
|
const (
|
||||||
RoleDescriptorNone RoleDescriptor = iota
|
RoleRepoOwner RoleInRepo = "owner"
|
||||||
RoleDescriptorPoster
|
RoleRepoMember RoleInRepo = "member"
|
||||||
RoleDescriptorWriter
|
RoleRepoCollaborator RoleInRepo = "collaborator"
|
||||||
RoleDescriptorOwner
|
RoleRepoFirstTimeContributor RoleInRepo = "first_time_contributor"
|
||||||
|
RoleRepoContributor RoleInRepo = "contributor"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithRole enable a specific tag on the RoleDescriptor.
|
// LocaleString returns the locale string name of the role
|
||||||
func (rd RoleDescriptor) WithRole(role RoleDescriptor) RoleDescriptor {
|
func (r RoleInRepo) LocaleString(lang translation.Locale) string {
|
||||||
return rd | (1 << role)
|
return lang.Tr("repo.issues.role." + string(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringToRoleDescriptor(role string) RoleDescriptor {
|
// LocaleHelper returns the locale tooltip of the role
|
||||||
switch role {
|
func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
|
||||||
case "Poster":
|
return lang.Tr("repo.issues.role." + string(r) + "_helper")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment represents a comment in commit and issue page.
|
// Comment represents a comment in commit and issue page.
|
||||||
|
|
|
@ -199,3 +199,16 @@ func (prs PullRequestList) GetIssueIDs() []int64 {
|
||||||
}
|
}
|
||||||
return issueIDs
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -1480,9 +1480,18 @@ issues.ref_reopening_from = `<a href="%[3]s">referenced a pull request %[4]s tha
|
||||||
issues.ref_closed_from = `<a href="%[3]s">closed this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_closed_from = `<a href="%[3]s">closed this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_reopened_from = `<a href="%[3]s">reopened this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_reopened_from = `<a href="%[3]s">reopened this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_from = `from %[1]s`
|
issues.ref_from = `from %[1]s`
|
||||||
issues.poster = Poster
|
issues.author = Author
|
||||||
issues.collaborator = Collaborator
|
issues.author_helper = This user is the author.
|
||||||
issues.owner = Owner
|
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.re_request_review=Re-request review
|
||||||
issues.is_stale = There have been changes to this PR since this review
|
issues.is_stale = There have been changes to this PR since this review
|
||||||
issues.remove_request_review=Remove review request
|
issues.remove_request_review=Remove review request
|
||||||
|
|
|
@ -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) {
|
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 {
|
if hasOriginalAuthor {
|
||||||
return issues_model.RoleDescriptorNone, nil
|
return roleDescriptor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, poster)
|
perm, err := access_model.GetUserRepoPermission(ctx, repo, poster)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return issues_model.RoleDescriptorNone, err
|
return roleDescriptor, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the poster is the actual poster of the issue, enable Poster role.
|
// If the poster is the actual poster of the issue, enable Poster role.
|
||||||
if issue.IsPoster(poster.ID) {
|
roleDescriptor.IsPoster = issue.IsPoster(poster.ID)
|
||||||
roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorPoster)
|
|
||||||
|
// 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
|
return roleDescriptor, nil
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
{{if and (.ShowRole.HasRole "Poster") (not .IgnorePoster)}}
|
{{if and .ShowRole.IsPoster (not .IgnorePoster)}}
|
||||||
<div class="ui basic label role-label">
|
<div class="ui basic label role-label" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.author_helper"}}">
|
||||||
{{ctx.Locale.Tr "repo.issues.poster"}}
|
{{ctx.Locale.Tr "repo.issues.author"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if (.ShowRole.HasRole "Writer")}}
|
{{if .ShowRole.RoleInRepo}}
|
||||||
<div class="ui basic label role-label">
|
<div class="ui basic label role-label" data-tooltip-content="{{.ShowRole.RoleInRepo.LocaleHelper ctx.Locale}}">
|
||||||
{{ctx.Locale.Tr "repo.issues.collaborator"}}
|
{{.ShowRole.RoleInRepo.LocaleString ctx.Locale}}
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{if (.ShowRole.HasRole "Owner")}}
|
|
||||||
<div class="ui basic label role-label">
|
|
||||||
{{ctx.Locale.Tr "repo.issues.owner"}}
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
Loading…
Reference in New Issue