Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add member, collaborator, contributor, and first-time contributor roles and tooltips #26658

Merged
merged 24 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 20 additions & 27 deletions models/issues/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

func stringToRoleDescriptor(role string) RoleDescriptor {
switch role {
case "Poster":
return RoleDescriptorPoster
case "Writer":
return RoleDescriptorWriter
case "Owner":
return RoleDescriptorOwner
default:
return RoleDescriptorNone
}
// 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))
}

// 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.
Expand Down
13 changes: 13 additions & 0 deletions models/issues/pull_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
15 changes: 12 additions & 3 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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_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.poster = Poster
issues.collaborator = Collaborator
issues.owner = Owner
issues.author = Author
issues.author_helper = This user is the author.
Comment on lines +1483 to +1484
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we even assign author?
I haven't found it inside roleDescriptor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Gitea, it is called poster.

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
Expand Down
69 changes: 46 additions & 23 deletions routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
return roleDescriptor, err
}

// By default the poster has no roles on the comment.
roleDescriptor := issues_model.RoleDescriptorNone
// If the poster is the actual poster of the issue, enable Poster role.
roleDescriptor.IsPoster = issue.IsPoster(poster.ID)

// Check if the poster is owner of the repo.
if perm.IsOwner() {
// If the poster isn't a admin, enable the owner role.
// If the poster isn't an admin, enable the owner role.
if !poster.IsAdmin {
roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorOwner)
} else {
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 issues_model.RoleDescriptorNone, err
}
if ok {
roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorOwner)
}
// 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
}
}

// 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 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 actual poster of the issue, enable Poster role.
if issue.IsPoster(poster.ID) {
roleDescriptor = roleDescriptor.WithRole(issues_model.RoleDescriptorPoster)
// 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
Expand Down
17 changes: 6 additions & 11 deletions templates/repo/issue/view_content/show_role.tmpl
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
{{if and (.ShowRole.HasRole "Poster") (not .IgnorePoster)}}
<div class="ui basic label role-label">
{{ctx.Locale.Tr "repo.issues.poster"}}
{{if and .ShowRole.IsPoster (not .IgnorePoster)}}
<div class="ui basic label role-label" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.author_helper"}}">
{{ctx.Locale.Tr "repo.issues.author"}}
</div>
{{end}}
{{if (.ShowRole.HasRole "Writer")}}
<div class="ui basic label role-label">
{{ctx.Locale.Tr "repo.issues.collaborator"}}
</div>
{{end}}
{{if (.ShowRole.HasRole "Owner")}}
<div class="ui basic label role-label">
{{ctx.Locale.Tr "repo.issues.owner"}}
{{if .ShowRole.RoleInRepo}}
<div class="ui basic label role-label" data-tooltip-content="{{.ShowRole.RoleInRepo.LocaleHelper ctx.Locale}}">
{{.ShowRole.RoleInRepo.LocaleString ctx.Locale}}
</div>
{{end}}