diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 6377ebf9d2..d819b55d28 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1339,6 +1339,9 @@ LEVEL = Info ;; Number of repos that are displayed on one page ;REPO_PAGING_NUM = 15 +;; Number of orgs that are displayed on profile page +;ORG_PAGING_NUM = 15 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[ui.meta] diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index 2242b90dcd..acfac74968 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -96,3 +96,14 @@ num_issues: 0 num_closed_issues: 0 archived_unix: 0 + +- + id: 10 + repo_id: 3 + org_id: 0 + name: repo3label1 + color: '#112233' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + archived_unix: 0 diff --git a/models/issues/label.go b/models/issues/label.go index d80578193e..b9d24bbe99 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -349,6 +349,17 @@ func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []st Find(&labelIDs) } +// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given org. +func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) { + labelIDs := make([]int64, 0, len(labelNames)) + return labelIDs, db.GetEngine(ctx).Table("label"). + Where("org_id = ?", orgID). + In("name", labelNames). + Asc("name"). + Cols("id"). + Find(&labelIDs) +} + // BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder { return builder.Select("issue_label.issue_id"). diff --git a/modules/setting/ui.go b/modules/setting/ui.go index db0fe9ef79..20fc612b43 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -63,6 +63,7 @@ var UI = struct { } `ini:"ui.admin"` User struct { RepoPagingNum int + OrgPagingNum int } `ini:"ui.user"` Meta struct { Author string @@ -127,8 +128,10 @@ var UI = struct { }, User: struct { RepoPagingNum int + OrgPagingNum int }{ RepoPagingNum: 15, + OrgPagingNum: 15, }, Meta: struct { Author string diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ea4d36fb1c..16d894aa26 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -649,6 +649,7 @@ joined_on = Joined on %s repositories = Repositories activity = Public Activity followers = Followers +show_more = Show More starred = Starred Repositories watched = Watched Repositories code = Code diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go index a2c81ceb16..ee1a842bc6 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -335,6 +335,9 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) labelIDs = append(labelIDs, int64(rv.Float())) case reflect.String: labelNames = append(labelNames, rv.String()) + default: + ctx.Error(http.StatusBadRequest, "InvalidLabel", "a label must be an integer or a string") + return nil, nil, fmt.Errorf("invalid label") } } if len(labelIDs) > 0 && len(labelNames) > 0 { @@ -342,11 +345,20 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) return nil, nil, fmt.Errorf("invalid labels") } if len(labelNames) > 0 { - labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames) + repoLabelIDs, err := issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames) if err != nil { ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err) return nil, nil, err } + labelIDs = append(labelIDs, repoLabelIDs...) + if ctx.Repo.Owner.IsOrganization() { + orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(ctx, ctx.Repo.Owner.ID, labelNames) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetLabelIDsInOrgByNames", err) + return nil, nil, err + } + labelIDs = append(labelIDs, orgLabelIDs...) + } } labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive") diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 4cb0592b4b..d388d2b5d9 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -61,11 +61,20 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) { orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{ UserID: ctx.ContextUser.ID, IncludePrivate: showPrivate, + ListOptions: db.ListOptions{ + Page: 1, + // query one more results (without a separate counting) to see whether we need to add the "show more orgs" link + PageSize: setting.UI.User.OrgPagingNum + 1, + }, }) if err != nil { ctx.ServerError("FindOrgs", err) return } + if len(orgs) > setting.UI.User.OrgPagingNum { + orgs = orgs[:setting.UI.User.OrgPagingNum] + ctx.Data["ShowMoreOrgs"] = true + } ctx.Data["Orgs"] = orgs ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(ctx, orgs, ctx.Doer) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 44824de752..9c014bffdb 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -12,6 +12,7 @@ import ( activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -256,6 +257,21 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb ctx.Data["ProfileReadme"] = profileContent } } + case "organizations": + orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{ + UserID: ctx.ContextUser.ID, + IncludePrivate: showPrivate, + ListOptions: db.ListOptions{ + Page: page, + PageSize: pagingNum, + }, + }) + if err != nil { + ctx.ServerError("GetUserOrganizations", err) + return + } + ctx.Data["Cards"] = orgs + total = int(count) default: // default to "repositories" repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ @@ -294,31 +310,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb } pager := context.NewPagination(total, pagingNum, page, 5) - pager.SetDefaultParams(ctx) - pager.AddParamString("tab", tab) - if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" { - pager.AddParamString("language", language) - } - if tab == "activity" { - if ctx.Data["Date"] != nil { - pager.AddParamString("date", fmt.Sprint(ctx.Data["Date"])) - } - } - if archived.Has() { - pager.AddParamString("archived", fmt.Sprint(archived.Value())) - } - if fork.Has() { - pager.AddParamString("fork", fmt.Sprint(fork.Value())) - } - if mirror.Has() { - pager.AddParamString("mirror", fmt.Sprint(mirror.Value())) - } - if template.Has() { - pager.AddParamString("template", fmt.Sprint(template.Value())) - } - if private.Has() { - pager.AddParamString("private", fmt.Sprint(private.Value())) - } + pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager } diff --git a/templates/shared/user/profile_big_avatar.tmpl b/templates/shared/user/profile_big_avatar.tmpl index f04f1ef6c4..91f04e0b53 100644 --- a/templates/shared/user/profile_big_avatar.tmpl +++ b/templates/shared/user/profile_big_avatar.tmpl @@ -92,6 +92,9 @@ {{end}} {{end}} + {{if .ShowMoreOrgs}} +
  • {{svg "octicon-kebab-horizontal" 28 "icon tw-p-1"}}
  • + {{end}} {{end}} diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index cf61bb906a..2c83ce97cd 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -27,6 +27,8 @@ {{template "repo/user_cards" .}} {{else if eq .TabName "overview"}}
    {{.ProfileReadme}}
    + {{else if eq .TabName "organizations"}} + {{template "repo/user_cards" .}} {{else}} {{template "shared/repo_search" .}} {{template "explore/repo_list" .}} diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index 0e4cd8243b..c9cdd46b9a 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -117,27 +117,33 @@ func TestAPIAddIssueLabels(t *testing.T) { func TestAPIAddIssueLabelsWithLabelNames(t *testing.T) { assert.NoError(t, unittest.LoadFixtures()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6, RepoID: repo.ID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + repoLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10, RepoID: repo.ID}) + orgLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4, OrgID: owner.ID}) - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", - repo.OwnerName, repo.Name, issue.Index) + user1Session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteIssue) + + // add the org label and the repo label to the issue + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner.Name, repo.Name, issue.Index) req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ - Labels: []any{"label1", "label2"}, + Labels: []any{repoLabel.Name, orgLabel.Name}, }).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var apiLabels []*api.Label DecodeJSON(t, resp, &apiLabels) assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID})) - var apiLabelNames []string for _, label := range apiLabels { apiLabelNames = append(apiLabelNames, label.Name) } - assert.ElementsMatch(t, apiLabelNames, []string{"label1", "label2"}) + assert.ElementsMatch(t, apiLabelNames, []string{repoLabel.Name, orgLabel.Name}) + + // delete labels + req = NewRequest(t, "DELETE", urlStr).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) } func TestAPIReplaceIssueLabels(t *testing.T) {