loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement Issue Config (#20956)

Closes #20955

This PR adds the possibility to disable blank Issues, when the Repo has
templates. This can be done by creating the file
`.gitea/issue_config.yaml` with the content `blank_issues_enabled` in
the Repo.

authored by

JakobDev and committed by
GitHub
f384b13f 5cd1d6c9

+463 -13
+38
docs/content/doc/usage/issue-pull-request-templates.en-us.md
··· 50 50 - `.github/issue_template.yaml` 51 51 - `.github/issue_template.yml` 52 52 53 + Possible file names for issue config: 54 + 55 + - `.gitea/ISSUE_TEMPLATE/config.yaml` 56 + - `.gitea/ISSUE_TEMPLATE/config.yml` 57 + - `.gitea/issue_template/config.yaml` 58 + - `.gitea/issue_template/config.yml` 59 + - `.github/ISSUE_TEMPLATE/config.yaml` 60 + - `.github/ISSUE_TEMPLATE/config.yml` 61 + - `.github/issue_template/config.yaml` 62 + - `.github/issue_template/config.yml` 63 + 53 64 Possible file names for PR templates: 54 65 55 66 - `PULL_REQUEST_TEMPLATE.md` ··· 267 278 |----------|------------------------------------------------------------------------------------------------------------------------------------------|----------|---------|---------|---------| 268 279 | label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - | 269 280 | required | Prevents form submission until element is completed. | Optional | Boolean | false | - | 281 + 282 + ## Syntax for issue config 283 + 284 + This is a example for a issue config file 285 + 286 + ```yaml 287 + blank_issues_enabled: true 288 + contact_links: 289 + - name: Gitea 290 + url: https://gitea.io 291 + about: Visit the Gitea Website 292 + ``` 293 + 294 + ### Possible Options 295 + 296 + | Key | Description | Type | Default | 297 + |----------------------|-------------------------------------------------------------------------------------------------------|--------------------|----------------| 298 + | blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true | 299 + | contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array | 300 + 301 + ### Contact Link 302 + 303 + | Key | Description | Type | Required | 304 + |----------------------|-------------------------------------------------------------------------------------------------------|---------|----------| 305 + | name | the name of your link | String | true | 306 + | url | The URL of your Link | String | true | 307 + | about | A short description of your Link | String | true |
+114
modules/context/repo.go
··· 8 8 "context" 9 9 "fmt" 10 10 "html" 11 + "io" 11 12 "net/http" 12 13 "net/url" 13 14 "path" ··· 33 34 asymkey_service "code.gitea.io/gitea/services/asymkey" 34 35 35 36 "github.com/editorconfig/editorconfig-core-go/v2" 37 + "gopkg.in/yaml.v3" 36 38 ) 37 39 38 40 // IssueTemplateDirCandidates issue templates directory ··· 45 47 ".github/issue_template", 46 48 ".gitlab/ISSUE_TEMPLATE", 47 49 ".gitlab/issue_template", 50 + } 51 + 52 + var IssueConfigCandidates = []string{ 53 + ".gitea/ISSUE_TEMPLATE/config", 54 + ".gitea/issue_template/config", 55 + ".github/ISSUE_TEMPLATE/config", 56 + ".github/issue_template/config", 48 57 } 49 58 50 59 // PullRequest contains information to make a pull request ··· 1088 1097 } 1089 1098 return issueTemplates, invalidFiles 1090 1099 } 1100 + 1101 + func GetDefaultIssueConfig() api.IssueConfig { 1102 + return api.IssueConfig{ 1103 + BlankIssuesEnabled: true, 1104 + ContactLinks: make([]api.IssueConfigContactLink, 0), 1105 + } 1106 + } 1107 + 1108 + // GetIssueConfig loads the given issue config file. 1109 + // It never returns a nil config. 1110 + func (r *Repository) GetIssueConfig(path string, commit *git.Commit) (api.IssueConfig, error) { 1111 + if r.GitRepo == nil { 1112 + return GetDefaultIssueConfig(), nil 1113 + } 1114 + 1115 + var err error 1116 + 1117 + treeEntry, err := commit.GetTreeEntryByPath(path) 1118 + if err != nil { 1119 + return GetDefaultIssueConfig(), err 1120 + } 1121 + 1122 + reader, err := treeEntry.Blob().DataAsync() 1123 + if err != nil { 1124 + log.Debug("DataAsync: %v", err) 1125 + return GetDefaultIssueConfig(), nil 1126 + } 1127 + 1128 + defer reader.Close() 1129 + 1130 + configContent, err := io.ReadAll(reader) 1131 + if err != nil { 1132 + return GetDefaultIssueConfig(), err 1133 + } 1134 + 1135 + issueConfig := api.IssueConfig{} 1136 + if err := yaml.Unmarshal(configContent, &issueConfig); err != nil { 1137 + return GetDefaultIssueConfig(), err 1138 + } 1139 + 1140 + for pos, link := range issueConfig.ContactLinks { 1141 + if link.Name == "" { 1142 + return GetDefaultIssueConfig(), fmt.Errorf("contact_link at position %d is missing name key", pos+1) 1143 + } 1144 + 1145 + if link.URL == "" { 1146 + return GetDefaultIssueConfig(), fmt.Errorf("contact_link at position %d is missing url key", pos+1) 1147 + } 1148 + 1149 + if link.About == "" { 1150 + return GetDefaultIssueConfig(), fmt.Errorf("contact_link at position %d is missing about key", pos+1) 1151 + } 1152 + 1153 + _, err = url.ParseRequestURI(link.URL) 1154 + if err != nil { 1155 + return GetDefaultIssueConfig(), fmt.Errorf("%s is not a valid URL", link.URL) 1156 + } 1157 + } 1158 + 1159 + return issueConfig, nil 1160 + } 1161 + 1162 + // IssueConfigFromDefaultBranch returns the issue config for this repo. 1163 + // It never returns a nil config. 1164 + func (ctx *Context) IssueConfigFromDefaultBranch() (api.IssueConfig, error) { 1165 + if ctx.Repo.Repository.IsEmpty { 1166 + return GetDefaultIssueConfig(), nil 1167 + } 1168 + 1169 + commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) 1170 + if err != nil { 1171 + return GetDefaultIssueConfig(), err 1172 + } 1173 + 1174 + for _, configName := range IssueConfigCandidates { 1175 + if _, err := commit.GetTreeEntryByPath(configName + ".yaml"); err == nil { 1176 + return ctx.Repo.GetIssueConfig(configName+".yaml", commit) 1177 + } 1178 + 1179 + if _, err := commit.GetTreeEntryByPath(configName + ".yml"); err == nil { 1180 + return ctx.Repo.GetIssueConfig(configName+".yml", commit) 1181 + } 1182 + } 1183 + 1184 + return GetDefaultIssueConfig(), nil 1185 + } 1186 + 1187 + // IsIssueConfig returns if the given path is a issue config file. 1188 + func (r *Repository) IsIssueConfig(path string) bool { 1189 + for _, configName := range IssueConfigCandidates { 1190 + if path == configName+".yaml" || path == configName+".yml" { 1191 + return true 1192 + } 1193 + } 1194 + return false 1195 + } 1196 + 1197 + func (ctx *Context) HasIssueTemplatesOrContactLinks() bool { 1198 + if len(ctx.IssueTemplatesFromDefaultBranch()) > 0 { 1199 + return true 1200 + } 1201 + 1202 + issueConfig, _ := ctx.IssueConfigFromDefaultBranch() 1203 + return len(issueConfig.ContactLinks) > 0 1204 + }
+16
modules/structs/issue.go
··· 190 190 return fmt.Errorf("line %d: cannot unmarshal %s into IssueTemplateLabels", value.Line, value.ShortTag()) 191 191 } 192 192 193 + type IssueConfigContactLink struct { 194 + Name string `json:"name" yaml:"name"` 195 + URL string `json:"url" yaml:"url"` 196 + About string `json:"about" yaml:"about"` 197 + } 198 + 199 + type IssueConfig struct { 200 + BlankIssuesEnabled bool `json:"blank_issues_enabled" yaml:"blank_issues_enabled"` 201 + ContactLinks []IssueConfigContactLink `json:"contact_links" yaml:"contact_links"` 202 + } 203 + 204 + type IssueConfigValidation struct { 205 + Valid bool `json:"valid"` 206 + Message string `json:"message"` 207 + } 208 + 193 209 // IssueTemplateType defines issue template type 194 210 type IssueTemplateType string 195 211
+2
options/locale/locale_en-US.ini
··· 1272 1272 issues.new.no_reviewers = No reviewers 1273 1273 issues.new.add_reviewer_title = Request review 1274 1274 issues.choose.get_started = Get Started 1275 + issues.choose.open_external_link = Open 1275 1276 issues.choose.blank = Default 1276 1277 issues.choose.blank_about = Create an issue from default template. 1277 1278 issues.choose.ignore_invalid_templates = Invalid templates have been ignored 1278 1279 issues.choose.invalid_templates = %v invalid template(s) found 1280 + issues.choose.invalid_config = The issue config contains errors: 1279 1281 issues.no_ref = No Branch/Tag Specified 1280 1282 issues.create = Create Issue 1281 1283 issues.new_label = New Label
+2
routers/api/v1/api.go
··· 1169 1169 }, reqAdmin()) 1170 1170 }, reqAnyRepoReader()) 1171 1171 m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates) 1172 + m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig) 1173 + m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig) 1172 1174 m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages) 1173 1175 }, repoAssignment()) 1174 1176 })
+55
routers/api/v1/repo/repo.go
··· 1144 1144 1145 1145 ctx.JSON(http.StatusOK, ctx.IssueTemplatesFromDefaultBranch()) 1146 1146 } 1147 + 1148 + // GetIssueConfig returns the issue config for a repo 1149 + func GetIssueConfig(ctx *context.APIContext) { 1150 + // swagger:operation GET /repos/{owner}/{repo}/issue_config repository repoGetIssueConfig 1151 + // --- 1152 + // summary: Returns the issue config for a repo 1153 + // produces: 1154 + // - application/json 1155 + // parameters: 1156 + // - name: owner 1157 + // in: path 1158 + // description: owner of the repo 1159 + // type: string 1160 + // required: true 1161 + // - name: repo 1162 + // in: path 1163 + // description: name of the repo 1164 + // type: string 1165 + // required: true 1166 + // responses: 1167 + // "200": 1168 + // "$ref": "#/responses/RepoIssueConfig" 1169 + issueConfig, _ := ctx.IssueConfigFromDefaultBranch() 1170 + ctx.JSON(http.StatusOK, issueConfig) 1171 + } 1172 + 1173 + // ValidateIssueConfig returns validation errors for the issue config 1174 + func ValidateIssueConfig(ctx *context.APIContext) { 1175 + // swagger:operation GET /repos/{owner}/{repo}/issue_config/validate repository repoValidateIssueConfig 1176 + // --- 1177 + // summary: Returns the validation information for a issue config 1178 + // produces: 1179 + // - application/json 1180 + // parameters: 1181 + // - name: owner 1182 + // in: path 1183 + // description: owner of the repo 1184 + // type: string 1185 + // required: true 1186 + // - name: repo 1187 + // in: path 1188 + // description: name of the repo 1189 + // type: string 1190 + // required: true 1191 + // responses: 1192 + // "200": 1193 + // "$ref": "#/responses/RepoIssueConfigValidation" 1194 + _, err := ctx.IssueConfigFromDefaultBranch() 1195 + 1196 + if err == nil { 1197 + ctx.JSON(http.StatusOK, api.IssueConfigValidation{Valid: true, Message: ""}) 1198 + } else { 1199 + ctx.JSON(http.StatusOK, api.IssueConfigValidation{Valid: false, Message: err.Error()}) 1200 + } 1201 + }
+14
routers/api/v1/swagger/repo.go
··· 386 386 // in:body 387 387 Body api.RepoCollaboratorPermission `json:"body"` 388 388 } 389 + 390 + // RepoIssueConfig 391 + // swagger:response RepoIssueConfig 392 + type swaggerRepoIssueConfig struct { 393 + // in:body 394 + Body api.IssueConfig `json:"body"` 395 + } 396 + 397 + // RepoIssueConfigValidation 398 + // swagger:response RepoIssueConfigValidation 399 + type swaggerRepoIssueConfigValidation struct { 400 + // in:body 401 + Body api.IssueConfigValidation `json:"body"` 402 + }
+9 -5
routers/web/repo/issue.go
··· 435 435 } 436 436 ctx.Data["Title"] = ctx.Tr("repo.issues") 437 437 ctx.Data["PageIsIssueList"] = true 438 - ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 438 + ctx.Data["NewIssueChooseTemplate"] = ctx.HasIssueTemplatesOrContactLinks() 439 439 } 440 440 441 441 issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), util.OptionalBoolOf(isPullList)) ··· 848 848 func NewIssue(ctx *context.Context) { 849 849 ctx.Data["Title"] = ctx.Tr("repo.issues.new") 850 850 ctx.Data["PageIsIssueList"] = true 851 - ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 851 + ctx.Data["NewIssueChooseTemplate"] = ctx.HasIssueTemplatesOrContactLinks() 852 852 ctx.Data["RequireTribute"] = true 853 853 ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes 854 854 title := ctx.FormString("title") ··· 946 946 ctx.Flash.Warning(renderErrorOfTemplates(ctx, errs), true) 947 947 } 948 948 949 - if len(issueTemplates) == 0 { 949 + if !ctx.HasIssueTemplatesOrContactLinks() { 950 950 // The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if no template here, just redirect to the "issues/new" page with these parameters. 951 951 ctx.Redirect(fmt.Sprintf("%s/issues/new?%s", ctx.Repo.Repository.Link(), ctx.Req.URL.RawQuery), http.StatusSeeOther) 952 952 return 953 953 } 954 954 955 + issueConfig, err := ctx.IssueConfigFromDefaultBranch() 956 + ctx.Data["IssueConfig"] = issueConfig 957 + ctx.Data["IssueConfigError"] = err // ctx.Flash.Err makes problems here 958 + 955 959 ctx.Data["milestone"] = ctx.FormInt64("milestone") 956 960 ctx.Data["project"] = ctx.FormInt64("project") 957 961 ··· 1086 1090 form := web.GetForm(ctx).(*forms.CreateIssueForm) 1087 1091 ctx.Data["Title"] = ctx.Tr("repo.issues.new") 1088 1092 ctx.Data["PageIsIssueList"] = true 1089 - ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 1093 + ctx.Data["NewIssueChooseTemplate"] = ctx.HasIssueTemplatesOrContactLinks() 1090 1094 ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes 1091 1095 ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled 1092 1096 upload.AddUploadContext(ctx, "comment") ··· 1280 1284 return 1281 1285 } 1282 1286 ctx.Data["PageIsIssueList"] = true 1283 - ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0 1287 + ctx.Data["NewIssueChooseTemplate"] = ctx.HasIssueTemplatesOrContactLinks() 1284 1288 } 1285 1289 1286 1290 if issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) {
+3
routers/web/repo/view.go
··· 348 348 if ctx.Repo.TreePath == ".editorconfig" { 349 349 _, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) 350 350 ctx.Data["FileError"] = editorconfigErr 351 + } else if ctx.Repo.IsIssueConfig(ctx.Repo.TreePath) { 352 + _, issueConfigErr := ctx.Repo.GetIssueConfig(ctx.Repo.TreePath, ctx.Repo.Commit) 353 + ctx.Data["FileError"] = issueConfigErr 351 354 } 352 355 353 356 isDisplayingSource := ctx.FormString("display") == "source"
+31 -8
templates/repo/issue/choose.tmpl
··· 20 20 </div> 21 21 </div> 22 22 {{end}} 23 - <div class="ui attached segment"> 24 - <div class="ui two column grid"> 25 - <div class="column left aligned"> 26 - <strong>{{.locale.Tr "repo.issues.choose.blank"}}</strong> 27 - <br>{{.locale.Tr "repo.issues.choose.blank_about"}} 23 + {{range .IssueConfig.ContactLinks}} 24 + <div class="ui attached segment"> 25 + <div class="ui two column grid"> 26 + <div class="column left aligned"> 27 + <strong>{{.Name | RenderEmojiPlain}}</strong> 28 + <br>{{.About | RenderEmojiPlain}} 29 + </div> 30 + <div class="column right aligned"> 31 + <a href="{{.URL}}" class="ui green button">{{svg "octicon-link-external"}} {{$.locale.Tr "repo.issues.choose.open_external_link"}}</a> 32 + </div> 33 + </div> 34 + </div> 35 + {{end}} 36 + {{if .IssueConfig.BlankIssuesEnabled}} 37 + <div class="ui attached segment"> 38 + <div class="ui two column grid"> 39 + <div class="column left aligned"> 40 + <strong>{{.locale.Tr "repo.issues.choose.blank"}}</strong> 41 + <br/>{{.locale.Tr "repo.issues.choose.blank_about"}} 42 + </div> 43 + <div class="column right aligned"> 44 + <a href="{{.RepoLink}}/issues/new?{{if .milestone}}&milestone={{.milestone}}{{end}}{{if $.project}}&project={{$.project}}{{end}}" class="ui green button">{{$.locale.Tr "repo.issues.choose.get_started"}}</a> 45 + </div> 28 46 </div> 29 - <div class="column right aligned"> 30 - <a href="{{.RepoLink}}/issues/new?{{if .milestone}}&milestone={{.milestone}}{{end}}{{if $.project}}&project={{$.project}}{{end}}" class="ui green button">{{$.locale.Tr "repo.issues.choose.get_started"}}</a> 47 + </div> 48 + {{end}} 49 + {{- if .IssueConfigError}}{{/* normal warning flash makes problems here*/}} 50 + <div class="ui warning message"> 51 + <div class="text left"> 52 + <div>{{.locale.Tr "repo.issues.choose.invalid_config"}}</div> 53 + <diy>{{.IssueConfigError}}</div> 31 54 </div> 32 55 </div> 33 - </div> 56 + {{end}} 34 57 </div> 35 58 </div> 36 59 {{template "base/footer" .}}
+127
templates/swagger/v1_json.tmpl
··· 5013 5013 } 5014 5014 } 5015 5015 }, 5016 + "/repos/{owner}/{repo}/issue_config": { 5017 + "get": { 5018 + "produces": [ 5019 + "application/json" 5020 + ], 5021 + "tags": [ 5022 + "repository" 5023 + ], 5024 + "summary": "Returns the issue config for a repo", 5025 + "operationId": "repoGetIssueConfig", 5026 + "parameters": [ 5027 + { 5028 + "type": "string", 5029 + "description": "owner of the repo", 5030 + "name": "owner", 5031 + "in": "path", 5032 + "required": true 5033 + }, 5034 + { 5035 + "type": "string", 5036 + "description": "name of the repo", 5037 + "name": "repo", 5038 + "in": "path", 5039 + "required": true 5040 + } 5041 + ], 5042 + "responses": { 5043 + "200": { 5044 + "$ref": "#/responses/RepoIssueConfig" 5045 + } 5046 + } 5047 + } 5048 + }, 5049 + "/repos/{owner}/{repo}/issue_config/validate": { 5050 + "get": { 5051 + "produces": [ 5052 + "application/json" 5053 + ], 5054 + "tags": [ 5055 + "repository" 5056 + ], 5057 + "summary": "Returns the validation information for a issue config", 5058 + "operationId": "repoValidateIssueConfig", 5059 + "parameters": [ 5060 + { 5061 + "type": "string", 5062 + "description": "owner of the repo", 5063 + "name": "owner", 5064 + "in": "path", 5065 + "required": true 5066 + }, 5067 + { 5068 + "type": "string", 5069 + "description": "name of the repo", 5070 + "name": "repo", 5071 + "in": "path", 5072 + "required": true 5073 + } 5074 + ], 5075 + "responses": { 5076 + "200": { 5077 + "$ref": "#/responses/RepoIssueConfigValidation" 5078 + } 5079 + } 5080 + } 5081 + }, 5016 5082 "/repos/{owner}/{repo}/issue_templates": { 5017 5083 "get": { 5018 5084 "produces": [ ··· 18165 18231 }, 18166 18232 "x-go-package": "code.gitea.io/gitea/modules/structs" 18167 18233 }, 18234 + "IssueConfig": { 18235 + "type": "object", 18236 + "properties": { 18237 + "blank_issues_enabled": { 18238 + "type": "boolean", 18239 + "x-go-name": "BlankIssuesEnabled" 18240 + }, 18241 + "contact_links": { 18242 + "type": "array", 18243 + "items": { 18244 + "$ref": "#/definitions/IssueConfigContactLink" 18245 + }, 18246 + "x-go-name": "ContactLinks" 18247 + } 18248 + }, 18249 + "x-go-package": "code.gitea.io/gitea/modules/structs" 18250 + }, 18251 + "IssueConfigContactLink": { 18252 + "type": "object", 18253 + "properties": { 18254 + "about": { 18255 + "type": "string", 18256 + "x-go-name": "About" 18257 + }, 18258 + "name": { 18259 + "type": "string", 18260 + "x-go-name": "Name" 18261 + }, 18262 + "url": { 18263 + "type": "string", 18264 + "x-go-name": "URL" 18265 + } 18266 + }, 18267 + "x-go-package": "code.gitea.io/gitea/modules/structs" 18268 + }, 18269 + "IssueConfigValidation": { 18270 + "type": "object", 18271 + "properties": { 18272 + "message": { 18273 + "type": "string", 18274 + "x-go-name": "Message" 18275 + }, 18276 + "valid": { 18277 + "type": "boolean", 18278 + "x-go-name": "Valid" 18279 + } 18280 + }, 18281 + "x-go-package": "code.gitea.io/gitea/modules/structs" 18282 + }, 18168 18283 "IssueDeadline": { 18169 18284 "description": "IssueDeadline represents an issue deadline", 18170 18285 "type": "object", ··· 21442 21557 "description": "RepoCollaboratorPermission", 21443 21558 "schema": { 21444 21559 "$ref": "#/definitions/RepoCollaboratorPermission" 21560 + } 21561 + }, 21562 + "RepoIssueConfig": { 21563 + "description": "RepoIssueConfig", 21564 + "schema": { 21565 + "$ref": "#/definitions/IssueConfig" 21566 + } 21567 + }, 21568 + "RepoIssueConfigValidation": { 21569 + "description": "RepoIssueConfigValidation", 21570 + "schema": { 21571 + "$ref": "#/definitions/IssueConfigValidation" 21445 21572 } 21446 21573 }, 21447 21574 "Repository": {
+52
tests/integration/api_issue_config_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "fmt" 8 + "net/http" 9 + "testing" 10 + 11 + repo_model "code.gitea.io/gitea/models/repo" 12 + "code.gitea.io/gitea/models/unittest" 13 + user_model "code.gitea.io/gitea/models/user" 14 + api "code.gitea.io/gitea/modules/structs" 15 + "code.gitea.io/gitea/tests" 16 + 17 + "github.com/stretchr/testify/assert" 18 + ) 19 + 20 + func TestAPIReposGetDefaultIssueConfig(t *testing.T) { 21 + defer tests.PrepareTestEnv(t)() 22 + 23 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 24 + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 25 + 26 + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config", owner.Name, repo.Name) 27 + req := NewRequest(t, "GET", urlStr) 28 + resp := MakeRequest(t, req, http.StatusOK) 29 + 30 + var issueConfig api.IssueConfig 31 + DecodeJSON(t, resp, &issueConfig) 32 + 33 + assert.True(t, issueConfig.BlankIssuesEnabled) 34 + assert.Equal(t, issueConfig.ContactLinks, make([]api.IssueConfigContactLink, 0)) 35 + } 36 + 37 + func TestAPIReposValidateDefaultIssueConfig(t *testing.T) { 38 + defer tests.PrepareTestEnv(t)() 39 + 40 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 41 + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 42 + 43 + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) 44 + req := NewRequest(t, "GET", urlStr) 45 + resp := MakeRequest(t, req, http.StatusOK) 46 + 47 + var issueConfigValidation api.IssueConfigValidation 48 + DecodeJSON(t, resp, &issueConfigValidation) 49 + 50 + assert.True(t, issueConfigValidation.Valid) 51 + assert.Equal(t, issueConfigValidation.Message, "") 52 + }