Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue templates directory #11450

Merged
merged 20 commits into from
Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion docs/content/doc/usage/issue-pull-request-templates.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,39 @@ Possible file names for PR templates:
* .github/pull_request_template.md


Additionally, the New Issue page URL can be suffixed with `?body=Issue+Text` and the form will be populated with that string. This string will be used instead of the template if there is one.
Additionally, the New Issue page URL can be suffixed with `?title=Issue+Title&body=Issue+Text` and the form will be populated with those strings. Those strings will be used instead of the template if there is one.

# Issue Template Directory

Alternatively, users can create multiple issue templates inside a special directory and allow users to choose one that more specifically
addresses their problem.

Possible directory names for issue templates:

* ISSUE_TEMPLATE
* issue_template
* .gitea/ISSUE_TEMPLATE
* .gitea/issue_template
* .github/ISSUE_TEMPLATE
* .github/issue_template
* .gitlab/ISSUE_TEMPLATE
* .gitlab/issue_template

Inside the directory can be multiple issue templates with the form

```markdown
-----
name: "Template Name"
about: "This template is for testing!"
title: "[TEST] "
labels:
- bug
- "help needed"
-----
This is the template!
```

In the above example, when a user is presented with the list of issues they can submit, this would show as `Template Name` with the description
`This template is for testing!`. When submitting an issue with the above example, the issue title would be pre-populated with
`[TEST] ` while the issue body would be pre-populated with `This is the template!`. The issue would also be assigned two labels,
`bug` and `help needed`.
71 changes: 71 additions & 0 deletions modules/context/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,27 @@ import (
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"

"gitea.com/macaron/macaron"
"github.com/editorconfig/editorconfig-core-go/v2"
"github.com/unknwon/com"
)

// IssueTemplateDirCandidates issue templates directory
var IssueTemplateDirCandidates = []string{
"ISSUE_TEMPLATE",
"issue_template",
".gitea/ISSUE_TEMPLATE",
".gitea/issue_template",
".github/ISSUE_TEMPLATE",
".github/issue_template",
jolheiser marked this conversation as resolved.
Show resolved Hide resolved
".gitlab/ISSUE_TEMPLATE",
".gitlab/issue_template",
}

// PullRequest contains informations to make a pull request
type PullRequest struct {
BaseRepo *models.Repository
Expand Down Expand Up @@ -821,3 +835,60 @@ func UnitTypes() macaron.Handler {
ctx.Data["UnitTypeProjects"] = models.UnitTypeProjects
}
}

// IssueTemplatesFromDefaultBranch checks for issue templates in the repo's default branch
func (ctx *Context) IssueTemplatesFromDefaultBranch() []api.IssueTemplate {
var issueTemplates []api.IssueTemplate
if ctx.Repo.Commit == nil {
var err error
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
return issueTemplates
jolheiser marked this conversation as resolved.
Show resolved Hide resolved
}
}

for _, dirName := range IssueTemplateDirCandidates {
tree, err := ctx.Repo.Commit.SubTree(dirName)
if err != nil {
continue
}
entries, err := tree.ListEntries()
if err != nil {
return issueTemplates
}
for _, entry := range entries {
if strings.HasSuffix(entry.Name(), ".md") {
if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
log.Debug("Issue template is too large: %s", entry.Name())
continue
}
r, err := entry.Blob().DataAsync()
zeripath marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
log.Debug("DataAsync: %v", err)
zeripath marked this conversation as resolved.
Show resolved Hide resolved
continue
}
defer r.Close()
data, err := ioutil.ReadAll(r)
zeripath marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
log.Debug("ReadAll: %v", err)
continue
}
var it api.IssueTemplate
content, err := markdown.ExtractMetadata(string(data), &it)
if err != nil {
log.Debug("ExtractMetadata: %v", err)
continue
}
it.Content = content
it.FileName = entry.Name()
if it.Valid() {
issueTemplates = append(issueTemplates, it)
}
}
}
if len(issueTemplates) > 0 {
return issueTemplates
}
}
return issueTemplates
}
49 changes: 49 additions & 0 deletions modules/markup/markdown/meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package markdown

import (
"errors"
"strings"

"gopkg.in/yaml.v2"
)

func isYAMLSeparator(line string) bool {
line = strings.TrimSpace(line)
for i := 0; i < len(line); i++ {
if line[i] != '-' {
return false
}
}
return len(line) > 2
}

// ExtractMetadata consumes a markdown file, parses YAML frontmatter,
// and returns the frontmatter metadata separated from the markdown content
func ExtractMetadata(contents string, out interface{}) (string, error) {
var front, body []string
var seps int
lines := strings.Split(contents, "\n")
for idx, line := range lines {
if seps == 2 {
front, body = lines[:idx], lines[idx:]
break
}
if isYAMLSeparator(line) {
seps++
continue
}
}

if len(front) == 0 && len(body) == 0 {
return "", errors.New("could not determine metadata")
}

if err := yaml.Unmarshal([]byte(strings.Join(front, "\n")), out); err != nil {
return "", err
}
return strings.Join(body, "\n"), nil
}
17 changes: 17 additions & 0 deletions modules/structs/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package structs

import (
"strings"
"time"
)

Expand Down Expand Up @@ -119,3 +120,19 @@ type IssueDeadline struct {
// swagger:strfmt date-time
Deadline *time.Time `json:"due_date"`
}

// IssueTemplate represents an issue template for a repository
// swagger:model
type IssueTemplate struct {
Name string `json:"name" yaml:"name"`
Title string `json:"title" yaml:"title"`
About string `json:"about" yaml:"about"`
Labels []string `json:"labels" yaml:"labels"`
Content string `json:"content" yaml:"-"`
FileName string `json:"file_name" yaml:"-"`
}

// Valid checks whether an IssueTemplate is considered valid, e.g. at least name and about
func (it IssueTemplate) Valid() bool {
return strings.TrimSpace(it.Name) != "" && strings.TrimSpace(it.About) != ""
}
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,8 @@ issues.new.clear_assignees = Clear assignees
issues.new.no_assignees = No Assignees
issues.new.no_reviewers = No reviewers
issues.new.add_reviewer_title = Request review
issues.choose.get_started = Get Started
issues.choose.blank = Open a blank issue
issues.no_ref = No Branch/Tag Specified
issues.create = Create Issue
issues.new_label = New Label
Expand Down
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin())
}, reqAnyRepoReader())
m.Get("/issue_templates", context.ReferencesGitRepo(false), repo.GetIssueTemplates)
m.Get("/languages", reqRepoReader(models.UnitTypeCode), repo.GetLanguages)
}, repoAssignment())
})
Expand Down
25 changes: 25 additions & 0 deletions routers/api/v1/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -812,3 +812,28 @@ func Delete(ctx *context.APIContext) {
log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
ctx.Status(http.StatusNoContent)
}

// GetIssueTemplates returns the issue templates for a repository
func GetIssueTemplates(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issue_templates repository repoGetIssueTemplates
// ---
// summary: Get available issue templates for a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/IssueTemplates"

ctx.JSON(http.StatusOK, ctx.IssueTemplatesFromDefaultBranch())
}
7 changes: 7 additions & 0 deletions routers/api/v1/swagger/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ type swaggerIssueDeadline struct {
Body api.IssueDeadline `json:"body"`
}

// IssueTemplates
// swagger:response IssueTemplates
type swaggerIssueTemplates struct {
// in:body
Body []api.IssueTemplate `json:"body"`
}

// StopWatch
// swagger:response StopWatch
type swaggerResponseStopWatch struct {
Expand Down
2 changes: 1 addition & 1 deletion routers/repo/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ func CompareDiff(ctx *context.Context) {
ctx.Data["RequireTribute"] = true
ctx.Data["RequireSimpleMDE"] = true
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
setTemplateIfExists(ctx, pullRequestTemplateKey, nil, pullRequestTemplateCandidates)
renderAttachmentSettings(ctx)

ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
Expand Down
Loading