Skip to content

Commit 34df4e5

Browse files
a1012112796silverwindlafriks6543
authored
Add mentionable teams to tributeValues and change team mention rules to gh's style (go-gitea#13198)
* Add mentionable teams to tributeValues Signed-off-by: a1012112796 <[email protected]> * Apply suggestions from code review Co-authored-by: silverwind <[email protected]> * Change team mention rules to gh's style * use org's avator as team avator in ui Signed-off-by: a1012112796 <[email protected]> * Update modules/markup/html.go * Update models/issue.go Co-authored-by: Lauris BH <[email protected]> * Update models/issue.go * fix a small nit and update test code Co-authored-by: silverwind <[email protected]> Co-authored-by: Lauris BH <[email protected]> Co-authored-by: 6543 <[email protected]>
1 parent 1b1adab commit 34df4e5

File tree

8 files changed

+112
-34
lines changed

8 files changed

+112
-34
lines changed

models/issue.go

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,30 +1847,43 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti
18471847
if err = issue.loadRepo(ctx.e); err != nil {
18481848
return
18491849
}
1850-
resolved := make(map[string]bool, 20)
1851-
names := make([]string, 0, 20)
1850+
1851+
resolved := make(map[string]bool, 10)
1852+
var mentionTeams []string
1853+
1854+
if err := issue.Repo.getOwner(ctx.e); err != nil {
1855+
return nil, err
1856+
}
1857+
1858+
repoOwnerIsOrg := issue.Repo.Owner.IsOrganization()
1859+
if repoOwnerIsOrg {
1860+
mentionTeams = make([]string, 0, 5)
1861+
}
1862+
18521863
resolved[doer.LowerName] = true
18531864
for _, name := range mentions {
18541865
name := strings.ToLower(name)
18551866
if _, ok := resolved[name]; ok {
18561867
continue
18571868
}
1858-
resolved[name] = false
1859-
names = append(names, name)
1860-
}
1861-
1862-
if err := issue.Repo.getOwner(ctx.e); err != nil {
1863-
return nil, err
1869+
if repoOwnerIsOrg && strings.Contains(name, "/") {
1870+
names := strings.Split(name, "/")
1871+
if len(names) < 2 || names[0] != issue.Repo.Owner.LowerName {
1872+
continue
1873+
}
1874+
mentionTeams = append(mentionTeams, names[1])
1875+
resolved[name] = true
1876+
} else {
1877+
resolved[name] = false
1878+
}
18641879
}
18651880

1866-
if issue.Repo.Owner.IsOrganization() {
1867-
// Since there can be users with names that match the name of a team,
1868-
// if the team exists and can read the issue, the team takes precedence.
1869-
teams := make([]*Team, 0, len(names))
1881+
if issue.Repo.Owner.IsOrganization() && len(mentionTeams) > 0 {
1882+
teams := make([]*Team, 0, len(mentionTeams))
18701883
if err := ctx.e.
18711884
Join("INNER", "team_repo", "team_repo.team_id = team.id").
18721885
Where("team_repo.repo_id=?", issue.Repo.ID).
1873-
In("team.lower_name", names).
1886+
In("team.lower_name", mentionTeams).
18741887
Find(&teams); err != nil {
18751888
return nil, fmt.Errorf("find mentioned teams: %v", err)
18761889
}
@@ -1883,7 +1896,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti
18831896
for _, team := range teams {
18841897
if team.Authorize >= AccessModeOwner {
18851898
checked = append(checked, team.ID)
1886-
resolved[team.LowerName] = true
1899+
resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true
18871900
continue
18881901
}
18891902
has, err := ctx.e.Get(&TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype})
@@ -1892,7 +1905,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti
18921905
}
18931906
if has {
18941907
checked = append(checked, team.ID)
1895-
resolved[team.LowerName] = true
1908+
resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true
18961909
}
18971910
}
18981911
if len(checked) != 0 {
@@ -1916,24 +1929,28 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, menti
19161929
}
19171930
}
19181931
}
1932+
}
19191933

1920-
// Remove names already in the list to avoid querying the database if pending names remain
1921-
names = make([]string, 0, len(resolved))
1922-
for name, already := range resolved {
1923-
if !already {
1924-
names = append(names, name)
1925-
}
1926-
}
1927-
if len(names) == 0 {
1928-
return
1934+
// Remove names already in the list to avoid querying the database if pending names remain
1935+
mentionUsers := make([]string, 0, len(resolved))
1936+
for name, already := range resolved {
1937+
if !already {
1938+
mentionUsers = append(mentionUsers, name)
19291939
}
19301940
}
1941+
if len(mentionUsers) == 0 {
1942+
return
1943+
}
1944+
1945+
if users == nil {
1946+
users = make([]*User, 0, len(mentionUsers))
1947+
}
19311948

1932-
unchecked := make([]*User, 0, len(names))
1949+
unchecked := make([]*User, 0, len(mentionUsers))
19331950
if err := ctx.e.
19341951
Where("`user`.is_active = ?", true).
19351952
And("`user`.prohibit_login = ?", false).
1936-
In("`user`.lower_name", names).
1953+
In("`user`.lower_name", mentionUsers).
19371954
Find(&unchecked); err != nil {
19381955
return nil, fmt.Errorf("find mentioned users: %v", err)
19391956
}

models/issue_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,5 +400,5 @@ func TestIssue_ResolveMentions(t *testing.T) {
400400
// Private repo, not a team member
401401
testSuccess("user17", "big_test_private_4", "user20", []string{"user5"}, []int64{})
402402
// Private repo, whole team
403-
testSuccess("user17", "big_test_private_4", "user15", []string{"owners"}, []int64{18})
403+
testSuccess("user17", "big_test_private_4", "user15", []string{"user17/owners"}, []int64{18})
404404
}

modules/markup/html.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -596,11 +596,15 @@ func mentionProcessor(ctx *postProcessCtx, node *html.Node) {
596596
mention := node.Data[loc.Start:loc.End]
597597
var teams string
598598
teams, ok := ctx.metas["teams"]
599-
if ok && strings.Contains(teams, ","+strings.ToLower(mention[1:])+",") {
600-
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, "org", ctx.metas["org"], "teams", mention[1:]), mention, "mention"))
601-
} else {
602-
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention"))
599+
// team mention should follow @orgName/teamName style
600+
if ok && strings.Contains(mention, "/") {
601+
mentionOrgAndTeam := strings.Split(mention, "/")
602+
if mentionOrgAndTeam[0][1:] == ctx.metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
603+
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, "org", ctx.metas["org"], "teams", mentionOrgAndTeam[1]), mention, "mention"))
604+
}
605+
return
603606
}
607+
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention"))
604608
}
605609

606610
func shortLinkProcessor(ctx *postProcessCtx, node *html.Node) {

modules/references/references.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ var (
2626
// While fast, this is also incorrect and lead to false positives.
2727
// TODO: fix invalid linking issue
2828

29-
// mentionPattern matches all mentions in the form of "@user"
30-
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`)
29+
// mentionPattern matches all mentions in the form of "@user" or "@org/team"
30+
mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`)
3131
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
3232
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
3333
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234

modules/references/references_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ func TestRegExp_mentionPattern(t *testing.T) {
325325
{"@gitea.", "@gitea"},
326326
{"@gitea,", "@gitea"},
327327
{"@gitea;", "@gitea"},
328+
{"@gitea/team1;", "@gitea/team1"},
328329
}
329330
falseTestCases := []string{
330331
"@ 0",
@@ -340,6 +341,7 @@ func TestRegExp_mentionPattern(t *testing.T) {
340341
"@gitea?this",
341342
"@gitea,this",
342343
"@gitea;this",
344+
"@gitea/team1/more",
343345
}
344346

345347
for _, testCase := range trueTestCases {

routers/repo/issue.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
274274
return
275275
}
276276

277+
handleTeamMentions(ctx)
278+
if ctx.Written() {
279+
return
280+
}
281+
277282
labels, err := models.GetLabelsByRepoID(repo.ID, "", models.ListOptions{})
278283
if err != nil {
279284
ctx.ServerError("GetLabelsByRepoID", err)
@@ -410,6 +415,11 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos
410415
ctx.ServerError("GetAssignees", err)
411416
return
412417
}
418+
419+
handleTeamMentions(ctx)
420+
if ctx.Written() {
421+
return
422+
}
413423
}
414424

415425
func retrieveProjects(ctx *context.Context, repo *models.Repository) {
@@ -2445,3 +2455,40 @@ func combineLabelComments(issue *models.Issue) {
24452455
i--
24462456
}
24472457
}
2458+
2459+
// get all teams that current user can mention
2460+
func handleTeamMentions(ctx *context.Context) {
2461+
if ctx.User == nil || !ctx.Repo.Owner.IsOrganization() {
2462+
return
2463+
}
2464+
2465+
isAdmin := false
2466+
var err error
2467+
// Admin has super access.
2468+
if ctx.User.IsAdmin {
2469+
isAdmin = true
2470+
} else {
2471+
isAdmin, err = ctx.Repo.Owner.IsOwnedBy(ctx.User.ID)
2472+
if err != nil {
2473+
ctx.ServerError("IsOwnedBy", err)
2474+
return
2475+
}
2476+
}
2477+
2478+
if isAdmin {
2479+
if err := ctx.Repo.Owner.GetTeams(&models.SearchTeamOptions{}); err != nil {
2480+
ctx.ServerError("GetTeams", err)
2481+
return
2482+
}
2483+
} else {
2484+
ctx.Repo.Owner.Teams, err = ctx.Repo.Owner.GetUserTeams(ctx.User.ID)
2485+
if err != nil {
2486+
ctx.ServerError("GetUserTeams", err)
2487+
return
2488+
}
2489+
}
2490+
2491+
ctx.Data["MentionableTeams"] = ctx.Repo.Owner.Teams
2492+
ctx.Data["MentionableTeamsOrg"] = ctx.Repo.Owner.Name
2493+
ctx.Data["MentionableTeamsOrgAvator"] = ctx.Repo.Owner.RelAvatarLink()
2494+
}

routers/repo/pull.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,10 @@ func ViewPullFiles(ctx *context.Context) {
684684
ctx.ServerError("GetAssignees", err)
685685
return
686686
}
687+
handleTeamMentions(ctx)
688+
if ctx.Written() {
689+
return
690+
}
687691
ctx.Data["CurrentReview"], err = models.GetCurrentReview(ctx.User, issue)
688692
if err != nil && !models.IsErrReviewNotExist(err) {
689693
ctx.ServerError("GetCurrentReview", err)

templates/base/head.tmpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
EventSourceUpdateTime: {{NotificationSettings.EventSourceUpdateTime}},
4545
},
4646
PageIsProjects: {{if .PageIsProjects }}true{{else}}false{{end}},
47-
{{if .RequireTribute}}
47+
{{if .RequireTribute}}
4848
tributeValues: Array.from(new Map([
4949
{{ range .Participants }}
5050
['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
@@ -54,6 +54,10 @@
5454
['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
5555
name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.RelAvatarLink}}'}],
5656
{{ end }}
57+
{{ range .MentionableTeams }}
58+
['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}',
59+
name: '{{$.MentionableTeamsOrg}}/{{.Name}}', avatar: '{{$.MentionableTeamsOrgAvator}}'}],
60+
{{ end }}
5761
]).values()),
5862
{{end}}
5963
};

0 commit comments

Comments
 (0)