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 7 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
49 changes: 25 additions & 24 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,40 @@ func (t CommentType) HasAttachmentSupport() bool {
return false
}

type Role string

// RoleDescriptor defines comment tag type
type RoleDescriptor int
type RoleDescriptor struct {
IsPoster util.OptionalBool
yp05327 marked this conversation as resolved.
Show resolved Hide resolved
Role Role
}

// Enumerate all the role tags.
const (
RoleDescriptorNone RoleDescriptor = iota
RoleDescriptorPoster
RoleDescriptorWriter
RoleDescriptorOwner
RoleDescriptorOwner Role = "owner"
RoleDescriptorMember Role = "member"
RoleDescriptorCollaborator Role = "collaborator"
RoleDescriptorFirstTimeContributor Role = "first_time_contributor"
RoleDescriptorContributor Role = "contributor"
)

// WithRole enable a specific tag on the RoleDescriptor.
func (rd RoleDescriptor) WithRole(role RoleDescriptor) RoleDescriptor {
return rd | (1 << role)
// HasRole returns if a role is not none
func (r Role) HasRole() bool {
return r.String() != ""
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
}

func stringToRoleDescriptor(role string) RoleDescriptor {
switch role {
case "Poster":
return RoleDescriptorPoster
case "Writer":
return RoleDescriptorWriter
case "Owner":
return RoleDescriptorOwner
default:
return RoleDescriptorNone
}
func (r Role) String() string {
return string(r)
}

wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
// LocaleString returns the locale string name of the Status
func (r Role) LocaleString(lang translation.Locale) string {
return lang.Tr("repo.issues." + r.String())
yp05327 marked this conversation as resolved.
Show resolved Hide resolved
}

// 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 string name of the Status
func (r Role) LocaleHelper(lang translation.Locale) string {
return lang.Tr("repo.issues." + r.String() + "_helper")
}

// Comment represents a comment in commit and issue page.
Expand Down
13 changes: 11 additions & 2 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.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.owner = Owner
issues.owner_helper = This user is the owner of this repository.
issues.member = Member
issues.member_helper = This user is a member of the organization owning this repository.
issues.collaborator = Collaborator
issues.collaborator_helper = This user has been invited to collaborate on the repository.
issues.first_time_contributor = First-time contributor
issues.first_time_contributor_helper = This is the first contribution of this user to the repository.
issues.contributor = Contributor
issues.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
77 changes: 56 additions & 21 deletions routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1230,45 +1230,80 @@ func NewIssuePost(ctx *context.Context) {

// 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.
if issue.IsPoster(poster.ID) {
roleDescriptor.IsPoster = util.OptionalBoolTrue
}

// 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 {
roleDescriptor.Role = issues_model.RoleDescriptorOwner
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.Role = issues_model.RoleDescriptorOwner
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.Role = issues_model.RoleDescriptorMember
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.Role = issues_model.RoleDescriptorCollaborator
return roleDescriptor, nil
}

// If the poster is the contributor of the repo
searchOpt := &issue_indexer.SearchOptions{
Paginator: &db.ListOptions{
Page: 1,
PageSize: 1,
},
RepoIDs: []int64{repo.ID},
IsClosed: util.OptionalBoolTrue,
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
IsPull: util.OptionalBoolTrue,
PosterID: &poster.ID,
}
if _, total, err := issue_indexer.SearchIssues(ctx, searchOpt); err != nil {
return roleDescriptor, err
} else if total > 0 {
roleDescriptor.Role = issues_model.RoleDescriptorContributor
} else if total == 0 && issue.IsPull && !issue.IsClosed {
// only display first time contributor in the first opening pull request
roleDescriptor.Role = issues_model.RoleDescriptorFirstTimeContributor
}

return roleDescriptor, nil
Expand Down
18 changes: 7 additions & 11 deletions templates/repo/issue/view_content/show_role.tmpl
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
{{if and (.ShowRole.HasRole "Poster") (not .IgnorePoster)}}
<div class="ui basic label role-label">
{{ctx.Locale.Tr "repo.issues.poster"}}
{{if and .ShowRole.IsPoster.IsTrue (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.Role.HasRole)}}
<div class="ui basic label role-label" data-tooltip-content="{{.ShowRole.Role.LocaleHelper ctx.Locale}}">
{{.ShowRole.Role.LocaleString ctx.Locale}}
</div>
{{end}}