Allow users to hide all "Add more units..." hints
Repositories displaying an "Add more..." tab on the header is a neat way to let people discover they can enable more units. However, displaying it all the time for repository owners, even when they deliberately do not want to enable more units gets noisy very fast. As such, this patch introduces a new setting which lets people disable this hint under the appearance settings. Fixes #2378. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
This commit is contained in:
parent
96d2f2b8cd
commit
36147f580c
15 changed files with 233 additions and 24 deletions
|
@ -50,6 +50,8 @@ var migrations = []*Migration{
|
|||
NewMigration("create the forgejo_repo_flag table", forgejo_v1_22.CreateRepoFlagTable),
|
||||
// v5 -> v6
|
||||
NewMigration("Add wiki_branch to repository", forgejo_v1_22.AddWikiBranchToRepository),
|
||||
// v6 -> v7
|
||||
NewMigration("Add enable_repo_unit_hints to the user table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
|
|
17
models/forgejo_migrations/v1_22/v7.go
Normal file
17
models/forgejo_migrations/v1_22/v7.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddUserRepoUnitHintsSetting(x *xorm.Engine) error {
|
||||
type User struct {
|
||||
ID int64
|
||||
EnableRepoUnitHints bool `xorm:"NOT NULL DEFAULT true"`
|
||||
}
|
||||
|
||||
return x.Sync(&User{})
|
||||
}
|
|
@ -146,6 +146,7 @@ type User struct {
|
|||
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
||||
Theme string `xorm:"NOT NULL DEFAULT ''"`
|
||||
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
EnableRepoUnitHints bool `xorm:"NOT NULL DEFAULT true"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -67,13 +67,14 @@ func (u User) MarshalJSON() ([]byte, error) {
|
|||
// UserSettings represents user settings
|
||||
// swagger:model
|
||||
type UserSettings struct {
|
||||
FullName string `json:"full_name"`
|
||||
Website string `json:"website"`
|
||||
Description string `json:"description"`
|
||||
Location string `json:"location"`
|
||||
Language string `json:"language"`
|
||||
Theme string `json:"theme"`
|
||||
DiffViewStyle string `json:"diff_view_style"`
|
||||
FullName string `json:"full_name"`
|
||||
Website string `json:"website"`
|
||||
Description string `json:"description"`
|
||||
Location string `json:"location"`
|
||||
Language string `json:"language"`
|
||||
Theme string `json:"theme"`
|
||||
DiffViewStyle string `json:"diff_view_style"`
|
||||
EnableRepoUnitHints bool `json:"enable_repo_unit_hints"`
|
||||
// Privacy
|
||||
HideEmail bool `json:"hide_email"`
|
||||
HideActivity bool `json:"hide_activity"`
|
||||
|
@ -82,13 +83,14 @@ type UserSettings struct {
|
|||
// UserSettingsOptions represents options to change user settings
|
||||
// swagger:model
|
||||
type UserSettingsOptions struct {
|
||||
FullName *string `json:"full_name" binding:"MaxSize(100)"`
|
||||
Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"`
|
||||
Description *string `json:"description" binding:"MaxSize(255)"`
|
||||
Location *string `json:"location" binding:"MaxSize(50)"`
|
||||
Language *string `json:"language"`
|
||||
Theme *string `json:"theme"`
|
||||
DiffViewStyle *string `json:"diff_view_style"`
|
||||
FullName *string `json:"full_name" binding:"MaxSize(100)"`
|
||||
Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"`
|
||||
Description *string `json:"description" binding:"MaxSize(255)"`
|
||||
Location *string `json:"location" binding:"MaxSize(50)"`
|
||||
Language *string `json:"language"`
|
||||
Theme *string `json:"theme"`
|
||||
DiffViewStyle *string `json:"diff_view_style"`
|
||||
EnableRepoUnitHints *bool `json:"enable_repo_unit_hints"`
|
||||
// Privacy
|
||||
HideEmail *bool `json:"hide_email"`
|
||||
HideActivity *bool `json:"hide_activity"`
|
||||
|
|
|
@ -703,6 +703,11 @@ continue = Continue
|
|||
cancel = Cancel
|
||||
language = Language
|
||||
ui = Theme
|
||||
hints = Hints
|
||||
additional_repo_units_hint_description = Display an "Add more units..." button for repositories that do not have all available units enabled.
|
||||
additional_repo_units_hint = Encourage enabling additional repository units
|
||||
update_hints = Update hints
|
||||
update_hints_success = Hints have been updated.
|
||||
hidden_comment_types = Hidden comment types
|
||||
hidden_comment_types_description = Comment types checked here will not be shown inside issue pages. Checking "Label" for example removes all "<user> added/removed <label>" comments.
|
||||
hidden_comment_types.ref_tooltip = Comments where this issue was referenced from another issue/commit/…
|
||||
|
|
|
@ -55,6 +55,7 @@ func UpdateUserSettings(ctx *context.APIContext) {
|
|||
DiffViewStyle: optional.FromPtr(form.DiffViewStyle),
|
||||
KeepEmailPrivate: optional.FromPtr(form.HideEmail),
|
||||
KeepActivityPrivate: optional.FromPtr(form.HideActivity),
|
||||
EnableRepoUnitHints: optional.FromPtr(form.EnableRepoUnitHints),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
|
|
|
@ -393,6 +393,25 @@ func UpdateUserLang(ctx *context.Context) {
|
|||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||
}
|
||||
|
||||
// UpdateUserHints updates a user's hints settings
|
||||
func UpdateUserHints(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.UpdateHintsForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsAppearance"] = true
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
EnableRepoUnitHints: optional.Some(form.EnableRepoUnitHints),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("User settings updated: %s", ctx.Doer.Name)
|
||||
ctx.Flash.Success(translation.NewLocale(ctx.Doer.Language).TrString("settings.update_hints_success"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||
}
|
||||
|
||||
// UpdateUserHiddenComments update a user's shown comment types
|
||||
func UpdateUserHiddenComments(ctx *context.Context) {
|
||||
err := user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes, forms.UserHiddenCommentTypesFromRequest(ctx).String())
|
||||
|
|
|
@ -568,6 +568,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Group("/appearance", func() {
|
||||
m.Get("", user_setting.Appearance)
|
||||
m.Post("/language", web.Bind(forms.UpdateLanguageForm{}), user_setting.UpdateUserLang)
|
||||
m.Post("/hints", web.Bind(forms.UpdateHintsForm{}), user_setting.UpdateUserHints)
|
||||
m.Post("/hidden_comments", user_setting.UpdateUserHiddenComments)
|
||||
m.Post("/theme", web.Bind(forms.UpdateThemeForm{}), user_setting.UpdateUIThemePost)
|
||||
})
|
||||
|
|
|
@ -86,15 +86,16 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
|
|||
// User2UserSettings return UserSettings based on a user
|
||||
func User2UserSettings(user *user_model.User) api.UserSettings {
|
||||
return api.UserSettings{
|
||||
FullName: user.FullName,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
Language: user.Language,
|
||||
Description: user.Description,
|
||||
Theme: user.Theme,
|
||||
HideEmail: user.KeepEmailPrivate,
|
||||
HideActivity: user.KeepActivityPrivate,
|
||||
DiffViewStyle: user.DiffViewStyle,
|
||||
FullName: user.FullName,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
Language: user.Language,
|
||||
Description: user.Description,
|
||||
Theme: user.Theme,
|
||||
HideEmail: user.KeepEmailPrivate,
|
||||
HideActivity: user.KeepActivityPrivate,
|
||||
DiffViewStyle: user.DiffViewStyle,
|
||||
EnableRepoUnitHints: user.EnableRepoUnitHints,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -234,6 +234,11 @@ type UpdateLanguageForm struct {
|
|||
Language string
|
||||
}
|
||||
|
||||
// UpdateHintsForm form for updating user hint settings
|
||||
type UpdateHintsForm struct {
|
||||
EnableRepoUnitHints bool
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetValidateContext(req)
|
||||
|
|
|
@ -37,6 +37,7 @@ type UpdateOptions struct {
|
|||
EmailNotificationsPreference optional.Option[string]
|
||||
SetLastLogin bool
|
||||
RepoAdminChangeTeamAccess optional.Option[bool]
|
||||
EnableRepoUnitHints optional.Option[bool]
|
||||
}
|
||||
|
||||
func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) error {
|
||||
|
@ -83,6 +84,11 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
|
|||
|
||||
cols = append(cols, "diff_view_style")
|
||||
}
|
||||
if opts.EnableRepoUnitHints.Has() {
|
||||
u.EnableRepoUnitHints = opts.EnableRepoUnitHints.Value()
|
||||
|
||||
cols = append(cols, "enable_repo_unit_hints")
|
||||
}
|
||||
|
||||
if opts.AllowGitHook.Has() {
|
||||
u.AllowGitHook = opts.AllowGitHook.Value()
|
||||
|
|
|
@ -172,7 +172,7 @@
|
|||
{{end}}
|
||||
|
||||
{{if .Permission.IsAdmin}}
|
||||
{{if not (.Repository.AllUnitsEnabled ctx)}}
|
||||
{{if and .SignedUser.EnableRepoUnitHints (not (.Repository.AllUnitsEnabled ctx))}}
|
||||
<a class="{{if .PageIsRepoSettingsUnits}}active {{end}}item" href="{{.RepoLink}}/settings/units">
|
||||
{{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
|
||||
</a>
|
||||
|
|
8
templates/swagger/v1_json.tmpl
generated
8
templates/swagger/v1_json.tmpl
generated
|
@ -23853,6 +23853,10 @@
|
|||
"type": "string",
|
||||
"x-go-name": "DiffViewStyle"
|
||||
},
|
||||
"enable_repo_unit_hints": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "EnableRepoUnitHints"
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"x-go-name": "FullName"
|
||||
|
@ -23897,6 +23901,10 @@
|
|||
"type": "string",
|
||||
"x-go-name": "DiffViewStyle"
|
||||
},
|
||||
"enable_repo_unit_hints": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "EnableRepoUnitHints"
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"x-go-name": "FullName"
|
||||
|
|
|
@ -66,6 +66,25 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Hints -->
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "settings.hints"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form" action="{{.Link}}/hints" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox" data-tooltip-content="{{ctx.Locale.Tr "settings.additional_repo_units_hint_description"}}">
|
||||
<input name="enable_repo_unit_hints" type="checkbox" {{if $.SignedUser.EnableRepoUnitHints}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "settings.additional_repo_units_hint"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_hints"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Shown comment event types -->
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "settings.hidden_comment_types"}}
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -306,3 +308,123 @@ func TestUserLocationMapLink(t *testing.T) {
|
|||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
htmlDoc.AssertElement(t, `a[href="https://example/foo/A%2Fb"]`, true)
|
||||
}
|
||||
|
||||
func TestUserHints(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
// Create a known-good repo, with only one unit enabled
|
||||
repo, _, f := CreateDeclarativeRepo(t, user, "", []unit_model.Type{
|
||||
unit_model.TypeCode,
|
||||
}, []unit_model.Type{
|
||||
unit_model.TypePullRequests,
|
||||
unit_model.TypeProjects,
|
||||
unit_model.TypePackages,
|
||||
unit_model.TypeActions,
|
||||
unit_model.TypeIssues,
|
||||
unit_model.TypeWiki,
|
||||
}, nil)
|
||||
defer f()
|
||||
|
||||
ensureRepoUnitHints := func(t *testing.T, hints bool) {
|
||||
t.Helper()
|
||||
|
||||
req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
|
||||
EnableRepoUnitHints: &hints,
|
||||
}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var userSettings api.UserSettings
|
||||
DecodeJSON(t, resp, &userSettings)
|
||||
assert.Equal(t, hints, userSettings.EnableRepoUnitHints)
|
||||
}
|
||||
|
||||
t.Run("API", func(t *testing.T) {
|
||||
t.Run("setting hints on and off", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
ensureRepoUnitHints(t, true)
|
||||
ensureRepoUnitHints(t, false)
|
||||
})
|
||||
|
||||
t.Run("retrieving settings", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
for _, v := range []bool{true, false} {
|
||||
ensureRepoUnitHints(t, v)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var userSettings api.UserSettings
|
||||
DecodeJSON(t, resp, &userSettings)
|
||||
assert.Equal(t, v, userSettings.EnableRepoUnitHints)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("user settings", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
// Set a known-good state, that isn't the default
|
||||
ensureRepoUnitHints(t, false)
|
||||
|
||||
assertHintState := func(t *testing.T, enabled bool) {
|
||||
t.Helper()
|
||||
|
||||
req := NewRequest(t, "GET", "/user/settings/appearance")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
_, hintChecked := htmlDoc.Find(`input[name="enable_repo_unit_hints"]`).Attr("checked")
|
||||
assert.Equal(t, enabled, hintChecked)
|
||||
}
|
||||
|
||||
t.Run("view", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
assertHintState(t, false)
|
||||
})
|
||||
|
||||
t.Run("change", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings/appearance/hints", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user/settings/appearance"),
|
||||
"enable_repo_unit_hints": "true",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
assertHintState(t, true)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("repo view", func(t *testing.T) {
|
||||
assertAddMore := func(t *testing.T, present bool) {
|
||||
t.Helper()
|
||||
|
||||
req := NewRequest(t, "GET", repo.Link())
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
htmlDoc.AssertElement(t, fmt.Sprintf("a[href='%s/settings/units']", repo.Link()), present)
|
||||
}
|
||||
|
||||
t.Run("hints enabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
ensureRepoUnitHints(t, true)
|
||||
assertAddMore(t, true)
|
||||
})
|
||||
|
||||
t.Run("hints disabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
ensureRepoUnitHints(t, false)
|
||||
assertAddMore(t, false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue