From fa73375e139d159caf697897b88068a2c66a48eb Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Tue, 23 Jan 2024 10:46:34 +0100 Subject: [PATCH 1/3] Split out repository unit settings This splits out the repository unit settings (formerly "Advanced settings" under the repository settings page) into their own, separate page. The primary reason for this is that the settings page became long and complicated, with a structure that not always made sense. A secondary reason is that toggling units on and off should not necessarily be an "advanced" setting. We want to make doing that easier, and having the units on their own page helps with that. This is basically a refactor, there is no new functionality introduced, just an extra pair of routes for the new page, and the supporting code. Signed-off-by: Gergely Nagy --- options/locale/locale_en-US.ini | 3 + routers/web/repo/setting/setting.go | 378 ++++++++++---------- routers/web/web.go | 2 + services/forms/repo_form.go | 28 +- templates/repo/settings/navbar.tmpl | 17 + templates/repo/settings/options.tmpl | 319 ----------------- templates/repo/settings/units.tmpl | 13 + templates/repo/settings/units/issues.tmpl | 102 ++++++ templates/repo/settings/units/overview.tmpl | 62 ++++ templates/repo/settings/units/pulls.tmpl | 121 +++++++ templates/repo/settings/units/wiki.tmpl | 51 +++ 11 files changed, 586 insertions(+), 510 deletions(-) create mode 100644 templates/repo/settings/units.tmpl create mode 100644 templates/repo/settings/units/issues.tmpl create mode 100644 templates/repo/settings/units/overview.tmpl create mode 100644 templates/repo/settings/units/pulls.tmpl create mode 100644 templates/repo/settings/units/wiki.tmpl diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 95ce92e882..be2a06b38f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2066,6 +2066,9 @@ settings.mirror_settings.push_mirror.remote_url = Git Remote Repository URL settings.mirror_settings.push_mirror.add = Add Push Mirror settings.mirror_settings.push_mirror.edit_sync_time = Edit mirror sync interval +settings.units.units = Repository Units +settings.units.overview = Overview + settings.sync_mirror = Synchronize Now settings.pull_mirror_sync_in_progress = Pulling changes from the remote %s at the moment. settings.push_mirror_sync_in_progress = Pushing changes to the remote %s at the moment. diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 552507e57c..8a429c359c 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -41,6 +41,7 @@ import ( const ( tplSettingsOptions base.TplName = "repo/settings/options" + tplSettingsUnits base.TplName = "repo/settings/units" tplCollaboration base.TplName = "repo/settings/collaboration" tplBranches base.TplName = "repo/settings/branches" tplGithooks base.TplName = "repo/settings/githooks" @@ -89,6 +90,201 @@ func SettingsCtxData(ctx *context.Context) { ctx.Data["PushMirrors"] = pushMirrors } +// Units show a repositorys unit settings page +func Units(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.settings.units.units") + ctx.Data["PageIsRepoSettingsUnits"] = true + + ctx.HTML(http.StatusOK, tplSettingsUnits) +} + +func UnitsPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoUnitSettingForm) + + repo := ctx.Repo.Repository + + var repoChanged bool + var units []repo_model.RepoUnit + var deleteUnitTypes []unit_model.Type + + // This section doesn't require repo_name/RepoName to be set in the form, don't show it + // as an error on the UI for this action + ctx.Data["Err_RepoName"] = nil + + if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch { + repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch + repoChanged = true + } + + if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeCode, + }) + } else if !unit_model.TypeCode.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) + } + + if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { + if !validation.IsValidExternalURL(form.ExternalWikiURL) { + ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) + ctx.Redirect(repo.Link() + "/settings/units") + return + } + + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeExternalWiki, + Config: &repo_model.ExternalWikiConfig{ + ExternalWikiURL: form.ExternalWikiURL, + }, + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) + } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { + var wikiPermissions repo_model.UnitAccessMode + if form.GloballyWriteableWiki { + wikiPermissions = repo_model.UnitAccessModeWrite + } else { + wikiPermissions = repo_model.UnitAccessModeRead + } + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeWiki, + Config: new(repo_model.UnitConfig), + DefaultPermissions: wikiPermissions, + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) + } else { + if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) + } + if !unit_model.TypeWiki.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) + } + } + + if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { + if !validation.IsValidExternalURL(form.ExternalTrackerURL) { + ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) + ctx.Redirect(repo.Link() + "/settings/units") + return + } + if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { + ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) + ctx.Redirect(repo.Link() + "/settings/units") + return + } + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeExternalTracker, + Config: &repo_model.ExternalTrackerConfig{ + ExternalTrackerURL: form.ExternalTrackerURL, + ExternalTrackerFormat: form.TrackerURLFormat, + ExternalTrackerStyle: form.TrackerIssueStyle, + ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, + }, + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) + } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeIssues, + Config: &repo_model.IssuesConfig{ + EnableTimetracker: form.EnableTimetracker, + AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, + EnableDependencies: form.EnableIssueDependencies, + }, + }) + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) + } else { + if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) + } + if !unit_model.TypeIssues.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) + } + } + + if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeProjects, + }) + } else if !unit_model.TypeProjects.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) + } + + if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeReleases, + }) + } else if !unit_model.TypeReleases.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) + } + + if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypePackages, + }) + } else if !unit_model.TypePackages.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) + } + + if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeActions, + }) + } else if !unit_model.TypeActions.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) + } + + if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypePullRequests, + Config: &repo_model.PullRequestsConfig{ + IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, + AllowMerge: form.PullsAllowMerge, + AllowRebase: form.PullsAllowRebase, + AllowRebaseMerge: form.PullsAllowRebaseMerge, + AllowSquash: form.PullsAllowSquash, + AllowManualMerge: form.PullsAllowManualMerge, + AutodetectManualMerge: form.EnableAutodetectManualMerge, + AllowRebaseUpdate: form.PullsAllowRebaseUpdate, + DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, + DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), + DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, + }, + }) + } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { + deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) + } + + if len(units) == 0 { + ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/units") + return + } + + if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { + ctx.ServerError("UpdateRepositoryUnits", err) + return + } + if repoChanged { + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { + ctx.ServerError("UpdateRepository", err) + return + } + } + log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) + + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/units") +} + // Settings show a repository's settings page func Settings(ctx *context.Context) { ctx.HTML(http.StatusOK, tplSettingsOptions) @@ -435,188 +631,6 @@ func SettingsPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") - case "advanced": - var repoChanged bool - var units []repo_model.RepoUnit - var deleteUnitTypes []unit_model.Type - - // This section doesn't require repo_name/RepoName to be set in the form, don't show it - // as an error on the UI for this action - ctx.Data["Err_RepoName"] = nil - - if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch { - repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch - repoChanged = true - } - - if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeCode, - }) - } else if !unit_model.TypeCode.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) - } - - if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { - if !validation.IsValidExternalURL(form.ExternalWikiURL) { - ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) - ctx.Redirect(repo.Link() + "/settings") - return - } - - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalWiki, - Config: &repo_model.ExternalWikiConfig{ - ExternalWikiURL: form.ExternalWikiURL, - }, - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) - } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { - var wikiPermissions repo_model.UnitAccessMode - if form.GloballyWriteableWiki { - wikiPermissions = repo_model.UnitAccessModeWrite - } else { - wikiPermissions = repo_model.UnitAccessModeRead - } - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeWiki, - Config: new(repo_model.UnitConfig), - DefaultPermissions: wikiPermissions, - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) - } else { - if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) - } - if !unit_model.TypeWiki.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) - } - } - - if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { - if !validation.IsValidExternalURL(form.ExternalTrackerURL) { - ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) - ctx.Redirect(repo.Link() + "/settings") - return - } - if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { - ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) - ctx.Redirect(repo.Link() + "/settings") - return - } - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalTracker, - Config: &repo_model.ExternalTrackerConfig{ - ExternalTrackerURL: form.ExternalTrackerURL, - ExternalTrackerFormat: form.TrackerURLFormat, - ExternalTrackerStyle: form.TrackerIssueStyle, - ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, - }, - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) - } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeIssues, - Config: &repo_model.IssuesConfig{ - EnableTimetracker: form.EnableTimetracker, - AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, - EnableDependencies: form.EnableIssueDependencies, - }, - }) - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) - } else { - if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) - } - if !unit_model.TypeIssues.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) - } - } - - if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeProjects, - }) - } else if !unit_model.TypeProjects.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) - } - - if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeReleases, - }) - } else if !unit_model.TypeReleases.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) - } - - if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePackages, - }) - } else if !unit_model.TypePackages.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) - } - - if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeActions, - }) - } else if !unit_model.TypeActions.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) - } - - if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePullRequests, - Config: &repo_model.PullRequestsConfig{ - IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, - AllowMerge: form.PullsAllowMerge, - AllowRebase: form.PullsAllowRebase, - AllowRebaseMerge: form.PullsAllowRebaseMerge, - AllowSquash: form.PullsAllowSquash, - AllowManualMerge: form.PullsAllowManualMerge, - AutodetectManualMerge: form.EnableAutodetectManualMerge, - AllowRebaseUpdate: form.PullsAllowRebaseUpdate, - DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, - DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), - DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, - }, - }) - } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { - deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) - } - - if len(units) == 0 { - ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - return - } - - if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { - ctx.ServerError("UpdateRepositoryUnits", err) - return - } - if repoChanged { - if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { - ctx.ServerError("UpdateRepository", err) - return - } - } - log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) - - ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings") - case "signing": changed := false trustModel := repo_model.ToTrustModel(form.TrustModel) diff --git a/routers/web/web.go b/routers/web/web.go index 1744ddb83a..688fe48160 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1037,6 +1037,8 @@ func registerRoutes(m *web.Route) { m.Combo("").Get(repo_setting.Settings). Post(web.Bind(forms.RepoSettingForm{}), repo_setting.SettingsPost) }, repo_setting.SettingsCtxData) + m.Combo("/units").Get(repo_setting.Units). + Post(web.Bind(forms.RepoUnitSettingForm{}), repo_setting.UnitsPost) m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar) m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 7cc07532ef..9527916ae0 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -130,6 +130,24 @@ type RepoSettingForm struct { EnablePrune bool // Advanced settings + IsArchived bool + + // Signing Settings + TrustModel string + + // Admin settings + EnableHealthCheck bool + RequestReindexType string +} + +// Validate validates the fields +func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { + ctx := context.GetValidateContext(req) + return middleware.Validate(errs, ctx.Data, f, ctx.Locale) +} + +// RepoUnitSettingForm form for changing repository unit settings +type RepoUnitSettingForm struct { EnableCode bool EnableWiki bool GloballyWriteableWiki bool @@ -161,18 +179,10 @@ type RepoSettingForm struct { EnableTimetracker bool AllowOnlyContributorsToTrackTime bool EnableIssueDependencies bool - IsArchived bool - - // Signing Settings - TrustModel string - - // Admin settings - EnableHealthCheck bool - RequestReindexType string } // Validate validates the fields -func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { +func (f *RepoUnitSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/templates/repo/settings/navbar.tmpl b/templates/repo/settings/navbar.tmpl index 3bef0fa4c1..62f81a901e 100644 --- a/templates/repo/settings/navbar.tmpl +++ b/templates/repo/settings/navbar.tmpl @@ -4,6 +4,23 @@ {{ctx.Locale.Tr "repo.settings.options"}} +
+ {{ctx.Locale.Tr "repo.settings.units.units"}} + +
{{ctx.Locale.Tr "repo.settings.collaboration"}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 191ac53967..6cfef31060 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -299,325 +299,6 @@ {{end}} -

- {{ctx.Locale.Tr "repo.settings.advanced_settings"}} -

-
-
- {{.CsrfTokenHtml}} - - - {{$isCodeEnabled := .Repository.UnitEnabled $.Context $.UnitTypeCode}} - {{$isCodeGlobalDisabled := .UnitTypeCode.UnitGlobalDisabled}} -
- -
- - -
-
- - {{$isWikiEnabled := or (.Repository.UnitEnabled $.Context $.UnitTypeWiki) (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}} - {{$isWikiGlobalDisabled := .UnitTypeWiki.UnitGlobalDisabled}} - {{$isExternalWikiGlobalDisabled := .UnitTypeExternalWiki.UnitGlobalDisabled}} - {{$isBothWikiGlobalDisabled := and $isWikiGlobalDisabled $isExternalWikiGlobalDisabled}} -
- -
- - -
-
-
-
-
- - -
-
- {{if (not .Repository.IsPrivate)}} -
-
-
- - -
-
-
- {{end}} -
-
- - -
-
-
- - -

{{ctx.Locale.Tr "repo.settings.external_wiki_url_desc"}}

-
-
- -
- - {{$isIssuesEnabled := or (.Repository.UnitEnabled $.Context $.UnitTypeIssues) (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}} - {{$isIssuesGlobalDisabled := .UnitTypeIssues.UnitGlobalDisabled}} - {{$isExternalTrackerGlobalDisabled := .UnitTypeExternalTracker.UnitGlobalDisabled}} - {{$isIssuesAndExternalGlobalDisabled := and $isIssuesGlobalDisabled $isExternalTrackerGlobalDisabled}} -
- -
- - -
-
-
-
-
- - -
-
-
- {{if .Repository.CanEnableTimetracker}} -
-
- - -
-
-
-
- - -
-
- {{end}} -
-
- - -
-
-
- - -
-
-
-
- - -
-
-
-
- - -

{{ctx.Locale.Tr "repo.settings.external_tracker_url_desc"}}

-
-
- - -

{{ctx.Locale.Tr "repo.settings.tracker_url_format_desc" | Str2html}}

-
-
- -
-
- {{$externalTracker := (.Repository.MustGetUnit $.Context $.UnitTypeExternalTracker)}} - {{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}} - - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -

{{ctx.Locale.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | Str2html}}

-
-
-
- -
- - {{$isProjectsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeProjects}} - {{$isProjectsGlobalDisabled := .UnitTypeProjects.UnitGlobalDisabled}} -
- -
- - -
-
- - {{$isReleasesEnabled := .Repository.UnitEnabled $.Context $.UnitTypeReleases}} - {{$isReleasesGlobalDisabled := .UnitTypeReleases.UnitGlobalDisabled}} -
- -
- - -
-
- - {{$isPackagesEnabled := .Repository.UnitEnabled $.Context $.UnitTypePackages}} - {{$isPackagesGlobalDisabled := .UnitTypePackages.UnitGlobalDisabled}} -
- -
- - -
-
- - {{if .EnableActions}} - {{$isActionsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeActions}} - {{$isActionsGlobalDisabled := .UnitTypeActions.UnitGlobalDisabled}} -
- -
- - -
-
- {{end}} - - {{if not .IsMirror}} -
- {{$pullRequestEnabled := .Repository.UnitEnabled $.Context $.UnitTypePullRequests}} - {{$pullRequestGlobalDisabled := .UnitTypePullRequests.UnitGlobalDisabled}} - {{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} -
- -
- - -
-
-
-
-

- {{ctx.Locale.Tr "repo.settings.merge_style_desc"}} -

-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- -
-

- {{ctx.Locale.Tr "repo.settings.default_merge_style_desc"}} -

- -
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- {{end}} - -
-
- -
-
-
-

{{ctx.Locale.Tr "repo.settings.signing_settings"}}

diff --git a/templates/repo/settings/units.tmpl b/templates/repo/settings/units.tmpl new file mode 100644 index 0000000000..66ed035964 --- /dev/null +++ b/templates/repo/settings/units.tmpl @@ -0,0 +1,13 @@ +{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings options")}} +
+
+ {{.CsrfTokenHtml}} + {{template "repo/settings/units/overview" .}} + {{template "repo/settings/units/issues" .}} + {{if not .IsMirror}} + {{template "repo/settings/units/pulls" .}} + {{end}} + {{template "repo/settings/units/wiki" .}} +
+
+{{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/units/issues.tmpl b/templates/repo/settings/units/issues.tmpl new file mode 100644 index 0000000000..a09b8edb2e --- /dev/null +++ b/templates/repo/settings/units/issues.tmpl @@ -0,0 +1,102 @@ +

+ {{ctx.Locale.Tr "repo.issues"}} +

+
+ {{$isIssuesEnabled := or (.Repository.UnitEnabled $.Context $.UnitTypeIssues) (.Repository.UnitEnabled $.Context $.UnitTypeExternalTracker)}} + {{$isIssuesGlobalDisabled := .UnitTypeIssues.UnitGlobalDisabled}} + {{$isExternalTrackerGlobalDisabled := .UnitTypeExternalTracker.UnitGlobalDisabled}} + {{$isIssuesAndExternalGlobalDisabled := and $isIssuesGlobalDisabled $isExternalTrackerGlobalDisabled}} +
+ +
+ + +
+
+
+
+
+ + +
+
+
+ {{if .Repository.CanEnableTimetracker}} +
+
+ + +
+
+
+
+ + +
+
+ {{end}} +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +

{{ctx.Locale.Tr "repo.settings.external_tracker_url_desc"}}

+
+
+ + +

{{ctx.Locale.Tr "repo.settings.tracker_url_format_desc" | Str2html}}

+
+
+ +
+
+ {{$externalTracker := (.Repository.MustGetUnit $.Context $.UnitTypeExternalTracker)}} + {{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}} + + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +

{{ctx.Locale.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | Str2html}}

+
+
+
+ +
+ +
+ +
+
diff --git a/templates/repo/settings/units/overview.tmpl b/templates/repo/settings/units/overview.tmpl new file mode 100644 index 0000000000..816b45ce1d --- /dev/null +++ b/templates/repo/settings/units/overview.tmpl @@ -0,0 +1,62 @@ +

+ {{ctx.Locale.Tr "repo.settings.units.overview"}} +

+
+ {{$isCodeEnabled := .Repository.UnitEnabled $.Context $.UnitTypeCode}} + {{$isCodeGlobalDisabled := .UnitTypeCode.UnitGlobalDisabled}} +
+ +
+ + +
+
+ + {{$isProjectsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeProjects}} + {{$isProjectsGlobalDisabled := .UnitTypeProjects.UnitGlobalDisabled}} +
+ +
+ + +
+
+ + {{$isReleasesEnabled := .Repository.UnitEnabled $.Context $.UnitTypeReleases}} + {{$isReleasesGlobalDisabled := .UnitTypeReleases.UnitGlobalDisabled}} +
+ +
+ + +
+
+ + {{$isPackagesEnabled := .Repository.UnitEnabled $.Context $.UnitTypePackages}} + {{$isPackagesGlobalDisabled := .UnitTypePackages.UnitGlobalDisabled}} +
+ +
+ + +
+
+ + {{if .EnableActions}} + {{$isActionsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeActions}} + {{$isActionsGlobalDisabled := .UnitTypeActions.UnitGlobalDisabled}} +
+ +
+ + +
+
+ {{end}} + +
+ +
+ +
+
diff --git a/templates/repo/settings/units/pulls.tmpl b/templates/repo/settings/units/pulls.tmpl new file mode 100644 index 0000000000..e735fe974c --- /dev/null +++ b/templates/repo/settings/units/pulls.tmpl @@ -0,0 +1,121 @@ +

+ {{ctx.Locale.Tr "repo.pulls"}} +

+
+ {{$pullRequestEnabled := .Repository.UnitEnabled $.Context $.UnitTypePullRequests}} + {{$pullRequestGlobalDisabled := .UnitTypePullRequests.UnitGlobalDisabled}} + {{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} +
+ +
+ + +
+
+
+
+

+ {{ctx.Locale.Tr "repo.settings.merge_style_desc"}} +

+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ +
+

+ {{ctx.Locale.Tr "repo.settings.default_merge_style_desc"}} +

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+ +
+
diff --git a/templates/repo/settings/units/wiki.tmpl b/templates/repo/settings/units/wiki.tmpl new file mode 100644 index 0000000000..c3be39f1cc --- /dev/null +++ b/templates/repo/settings/units/wiki.tmpl @@ -0,0 +1,51 @@ +

+ {{ctx.Locale.Tr "repo.wiki"}} +

+
+ {{$isWikiEnabled := or (.Repository.UnitEnabled $.Context $.UnitTypeWiki) (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}} + {{$isWikiGlobalDisabled := .UnitTypeWiki.UnitGlobalDisabled}} + {{$isExternalWikiGlobalDisabled := .UnitTypeExternalWiki.UnitGlobalDisabled}} + {{$isBothWikiGlobalDisabled := and $isWikiGlobalDisabled $isExternalWikiGlobalDisabled}} +
+ +
+ + +
+
+
+
+
+ + +
+
+ {{if (not .Repository.IsPrivate)}} +
+
+
+ + +
+
+
+ {{end}} +
+
+ + +
+
+
+ + +

{{ctx.Locale.Tr "repo.settings.external_wiki_url_desc"}}

+
+
+ +
+ +
+ +
+
From e07b0e75ff013dbab2ab85a69273661b60a816e6 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Tue, 23 Jan 2024 10:57:49 +0100 Subject: [PATCH 2/3] Add a direct link from repo header to unit settings If a repository administrator is viewing a repository, and there are units that can be enabled, display an "Add more..." link that leads to the repository unit settings page. The goal here is to allow instances to configure a small set of repo units to be enabled by default, but also highlight for repo admins that they can add more. Signed-off-by: Gergely Nagy --- models/unit/unit.go | 43 +++++++++++++++++++++++++++++++++ modules/context/repo.go | 26 ++++++++++++++++++++ options/locale/locale_en-US.ini | 1 + templates/repo/header.tmpl | 5 ++++ 4 files changed, 75 insertions(+) diff --git a/models/unit/unit.go b/models/unit/unit.go index b216712d37..e37adf995e 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -108,6 +108,10 @@ var ( // DisabledRepoUnits contains the units that have been globally disabled DisabledRepoUnits = []Type{} + + // AllowedRepoUnitGroups contains the units that have been globally enabled, + // with mutually exclusive units grouped together. + AllowedRepoUnitGroups = [][]Type{} ) // Get valid set of default repository units from settings @@ -162,6 +166,45 @@ func LoadUnitConfig() error { if len(DefaultForkRepoUnits) == 0 { return errors.New("no default fork repository units found") } + + // Collect the allowed repo unit groups. Mutually exclusive units are + // grouped together. + AllowedRepoUnitGroups = [][]Type{} + for _, unit := range []Type{ + TypeCode, + TypePullRequests, + TypeProjects, + TypePackages, + TypeActions, + } { + // If unit is globally disabled, ignore it. + if unit.UnitGlobalDisabled() { + continue + } + + // If it is allowed, add it to the group list. + AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, []Type{unit}) + } + + addMutuallyExclusiveGroup := func(unit1, unit2 Type) { + var list []Type + + if !unit1.UnitGlobalDisabled() { + list = append(list, unit1) + } + + if !unit2.UnitGlobalDisabled() { + list = append(list, unit2) + } + + if len(list) > 0 { + AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, list) + } + } + + addMutuallyExclusiveGroup(TypeIssues, TypeExternalTracker) + addMutuallyExclusiveGroup(TypeWiki, TypeExternalWiki) + return nil } diff --git a/modules/context/repo.go b/modules/context/repo.go index b48f6ded26..727c18cad6 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -81,6 +81,31 @@ func (r *Repository) CanCreateBranch() bool { return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch() } +// AllUnitsEnabled returns true if all units are enabled for the repo. +func (r *Repository) AllUnitsEnabled(ctx context.Context) bool { + hasAnyUnitEnabled := func(unitGroup []unit_model.Type) bool { + // Loop over the group of units + for _, unit := range unitGroup { + // If *any* of them is enabled, return true. + if r.Repository.UnitEnabled(ctx, unit) { + return true + } + } + + // If none are enabled, return false. + return false + } + + for _, unitGroup := range unit_model.AllowedRepoUnitGroups { + // If any disabled unit is found, return false immediately. + if !hasAnyUnitEnabled(unitGroup) { + return false + } + } + + return true +} + // RepoMustNotBeArchived checks if a repo is archived func RepoMustNotBeArchived() func(ctx *Context) { return func(ctx *Context) { @@ -1053,6 +1078,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() + ctx.Data["AllUnitsEnabled"] = ctx.Repo.AllUnitsEnabled(ctx) ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() if err != nil { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index be2a06b38f..9c8b3fc541 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2068,6 +2068,7 @@ settings.mirror_settings.push_mirror.edit_sync_time = Edit mirror sync interval settings.units.units = Repository Units settings.units.overview = Overview +settings.units.add_more = Add more... settings.sync_mirror = Synchronize Now settings.pull_mirror_sync_in_progress = Pulling changes from the remote %s at the moment. diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index ef3e40eea8..681cba9ef4 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -219,6 +219,11 @@ {{end}} {{if .Permission.IsAdmin}} + {{if not .AllUnitsEnabled}} + + {{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}} + + {{end}} {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} From 44424bfe60d1038101b7797bd85ab963fe982a84 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sun, 11 Feb 2024 23:11:57 +0100 Subject: [PATCH 3/3] Add tests for the previous two commits Signed-off-by: Gergely Nagy --- tests/integration/repo_settings_test.go | 130 ++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 tests/integration/repo_settings_test.go diff --git a/tests/integration/repo_settings_test.go b/tests/integration/repo_settings_test.go new file mode 100644 index 0000000000..16e0bb3d7d --- /dev/null +++ b/tests/integration/repo_settings_test.go @@ -0,0 +1,130 @@ +// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models/db" + 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" + repo_service "code.gitea.io/gitea/services/repository" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestRepoSettingsUnits(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: "repo1"}) + session := loginUser(t, user.Name) + + req := NewRequest(t, "GET", fmt.Sprintf("%s/settings/units", repo.Link())) + session.MakeRequest(t, req, http.StatusOK) +} + +func TestRepoAddMoreUnits(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + session := loginUser(t, user.Name) + + // Make sure there are no disabled repos in the settings! + setting.Repository.DisabledRepoUnits = []string{} + unit_model.LoadUnitConfig() + + // Create a known-good repo, with all units enabled. + repo, _, f := CreateDeclarativeRepo(t, user, "", []unit_model.Type{ + unit_model.TypeCode, + unit_model.TypePullRequests, + unit_model.TypeProjects, + unit_model.TypePackages, + unit_model.TypeActions, + unit_model.TypeIssues, + unit_model.TypeWiki, + }, nil, nil) + defer f() + + 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("no add more with all units enabled", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + assertAddMore(t, false) + }) + + t.Run("add more if units can be enabled", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer func() { + repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{ + RepoID: repo.ID, + Type: unit_model.TypePackages, + }}, nil) + }() + + // Disable the Packages unit + err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, nil, []unit_model.Type{unit_model.TypePackages}) + assert.NoError(t, err) + + assertAddMore(t, true) + }) + + t.Run("no add more if unit is globally disabled", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer func() { + repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{ + RepoID: repo.ID, + Type: unit_model.TypePackages, + }}, nil) + setting.Repository.DisabledRepoUnits = []string{} + unit_model.LoadUnitConfig() + }() + + // Disable the Packages unit globally + setting.Repository.DisabledRepoUnits = []string{"repo.packages"} + unit_model.LoadUnitConfig() + + // Disable the Packages unit + err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, nil, []unit_model.Type{unit_model.TypePackages}) + assert.NoError(t, err) + + // The "Add more" link appears no more + assertAddMore(t, false) + }) + + t.Run("issues & ext tracker globally disabled", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer func() { + repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{ + RepoID: repo.ID, + Type: unit_model.TypeIssues, + }}, nil) + setting.Repository.DisabledRepoUnits = []string{} + unit_model.LoadUnitConfig() + }() + + // Disable both Issues and ExternalTracker units globally + setting.Repository.DisabledRepoUnits = []string{"repo.issues", "repo.ext_issues"} + unit_model.LoadUnitConfig() + + // Disable the Issues unit + err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, nil, []unit_model.Type{unit_model.TypeIssues}) + assert.NoError(t, err) + + // The "Add more" link appears no more + assertAddMore(t, false) + }) +}