; Default value for EnableDependencies
; Repositories will use dependencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true
+; Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting.
+ALLOW_CROSS_REPOSITORY_DEPENDENCIES = true
; Enable heatmap on users profiles.
ENABLE_USER_HEATMAP = true
; Enable Timetracking
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha.
- `RECAPTCHA_URL`: **https://www.google.com/recaptcha/**: Set the recaptcha url - allows the use of recaptcha net.
- `DEFAULT_ENABLE_DEPENDENCIES`: **true**: Enable this to have dependencies enabled by default.
+- `ALLOW_CROSS_REPOSITORY_DEPENDENCIES` : **true** Enable this to allow dependencies on issues from any repository where the user is granted access.
- `ENABLE_USER_HEATMAP`: **true**: Enable this to display the heatmap on users profiles.
- `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
on this instance.
"path"
"regexp"
"sort"
+ "strconv"
"strings"
"code.gitea.io/gitea/modules/base"
Updated: issue.UpdatedUnix.AsTime(),
}
+ apiIssue.Repo = &api.RepositoryMeta{
+ ID: issue.Repo.ID,
+ Name: issue.Repo.Name,
+ FullName: issue.Repo.FullName(),
+ }
+
if issue.ClosedUnix != 0 {
apiIssue.Closed = issue.ClosedUnix.AsTimePtr()
}
LabelIDs []int64
SortType string
IssueIDs []int64
+ // prioritize issues from this repo
+ PriorityRepoID int64
}
// sortIssuesSession sort an issues-related session based on the provided
// sortType string
-func sortIssuesSession(sess *xorm.Session, sortType string) {
+func sortIssuesSession(sess *xorm.Session, sortType string, priorityRepoID int64) {
switch sortType {
case "oldest":
sess.Asc("issue.created_unix")
sess.Asc("issue.deadline_unix")
case "farduedate":
sess.Desc("issue.deadline_unix")
+ case "priorityrepo":
+ sess.OrderBy("CASE WHEN issue.repo_id = " + strconv.FormatInt(priorityRepoID, 10) + " THEN 1 ELSE 2 END, issue.created_unix DESC")
default:
sess.Desc("issue.created_unix")
}
defer sess.Close()
opts.setupSession(sess)
- sortIssuesSession(sess, opts.SortType)
+ sortIssuesSession(sess, opts.SortType, opts.PriorityRepoID)
issues := make([]*Issue, 0, setting.UI.IssuePagingNum)
if err := sess.Find(&issues); err != nil {
}
// SearchIssueIDsByKeyword search issues on database
-func SearchIssueIDsByKeyword(kw string, repoID int64, limit, start int) (int64, []int64, error) {
- var repoCond = builder.Eq{"repo_id": repoID}
+func SearchIssueIDsByKeyword(kw string, repoIDs []int64, limit, start int) (int64, []int64, error) {
+ var repoCond = builder.In("repo_id", repoIDs)
var subQuery = builder.Select("id").From("issue").Where(repoCond)
var cond = builder.And(
repoCond,
return sess.Commit()
}
+// DependencyInfo represents high level information about an issue which is a dependency of another issue.
+type DependencyInfo struct {
+ Issue `xorm:"extends"`
+ Repository `xorm:"extends"`
+}
+
// Get Blocked By Dependencies, aka all issues this issue is blocked by.
-func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*Issue, err error) {
+func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*DependencyInfo, err error) {
return issueDeps, e.
- Table("issue_dependency").
- Select("issue.*").
- Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
+ Table("issue").
+ Join("INNER", "repository", "repository.id = issue.repo_id").
+ Join("INNER", "issue_dependency", "issue_dependency.dependency_id = issue.id").
Where("issue_id = ?", issue.ID).
+ //sort by repo id then created date, with the issues of the same repo at the beginning of the list
+ OrderBy("CASE WHEN issue.repo_id = " + strconv.FormatInt(issue.RepoID, 10) + " THEN 0 ELSE issue.repo_id END, issue.created_unix DESC").
Find(&issueDeps)
}
// Get Blocking Dependencies, aka all issues this issue blocks.
-func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*Issue, err error) {
+func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*DependencyInfo, err error) {
return issueDeps, e.
- Table("issue_dependency").
- Select("issue.*").
- Join("INNER", "issue", "issue.id = issue_dependency.issue_id").
+ Table("issue").
+ Join("INNER", "repository", "repository.id = issue.repo_id").
+ Join("INNER", "issue_dependency", "issue_dependency.issue_id = issue.id").
Where("dependency_id = ?", issue.ID).
+ //sort by repo id then created date, with the issues of the same repo at the beginning of the list
+ OrderBy("CASE WHEN issue.repo_id = " + strconv.FormatInt(issue.RepoID, 10) + " THEN 0 ELSE issue.repo_id END, issue.created_unix DESC").
Find(&issueDeps)
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
-func (issue *Issue) BlockedByDependencies() ([]*Issue, error) {
+func (issue *Issue) BlockedByDependencies() ([]*DependencyInfo, error) {
return issue.getBlockedByDependencies(x)
}
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks
-func (issue *Issue) BlockingDependencies() ([]*Issue, error) {
+func (issue *Issue) BlockingDependencies() ([]*DependencyInfo, error) {
return issue.getBlockingDependencies(x)
}
Find(&labelIDs)
}
+// GetLabelIDsInReposByNames returns a list of labelIDs by names in one of the given
+// repositories.
+// it silently ignores label names that do not belong to the repository.
+func GetLabelIDsInReposByNames(repoIDs []int64, labelNames []string) ([]int64, error) {
+ labelIDs := make([]int64, 0, len(labelNames))
+ return labelIDs, x.Table("label").
+ In("repo_id", repoIDs).
+ In("name", labelNames).
+ Asc("name").
+ Cols("id").
+ Find(&labelIDs)
+}
+
// GetLabelInRepoByID returns a label by ID in given repository.
func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) {
return getLabelInRepoByID(x, repoID, labelID)
func TestIssue_SearchIssueIDsByKeyword(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
-
- total, ids, err := SearchIssueIDsByKeyword("issue2", 1, 10, 0)
+ total, ids, err := SearchIssueIDsByKeyword("issue2", []int64{1}, 10, 0)
assert.NoError(t, err)
assert.EqualValues(t, 1, total)
assert.EqualValues(t, []int64{2}, ids)
- total, ids, err = SearchIssueIDsByKeyword("first", 1, 10, 0)
+ total, ids, err = SearchIssueIDsByKeyword("first", []int64{1}, 10, 0)
assert.NoError(t, err)
assert.EqualValues(t, 1, total)
assert.EqualValues(t, []int64{1}, ids)
- total, ids, err = SearchIssueIDsByKeyword("for", 1, 10, 0)
+ total, ids, err = SearchIssueIDsByKeyword("for", []int64{1}, 10, 0)
assert.NoError(t, err)
assert.EqualValues(t, 4, total)
assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
// issue1's comment id 2
- total, ids, err = SearchIssueIDsByKeyword("good", 1, 10, 0)
+ total, ids, err = SearchIssueIDsByKeyword("good", []int64{1}, 10, 0)
assert.NoError(t, err)
assert.EqualValues(t, 1, total)
assert.EqualValues(t, []int64{1}, ids)
prs := make([]*PullRequest, 0, ItemsPerPage)
findSession, err := listPullRequestStatement(baseRepoID, opts)
- sortIssuesSession(findSession, opts.SortType)
+ sortIssuesSession(findSession, opts.SortType, 0)
if err != nil {
log.Error("listPullRequestStatement: %v", err)
return nil, maxResults, err
// Search searches for issues by given conditions.
// Returns the matching issue IDs
-func (b *BleveIndexer) Search(keyword string, repoID int64, limit, start int) (*SearchResult, error) {
+func (b *BleveIndexer) Search(keyword string, repoIDs []int64, limit, start int) (*SearchResult, error) {
+ var repoQueriesP []*query.NumericRangeQuery
+ for _, repoID := range repoIDs {
+ repoQueriesP = append(repoQueriesP, numericEqualityQuery(repoID, "RepoID"))
+ }
+ repoQueries := make([]query.Query, len(repoQueriesP))
+ for i, v := range repoQueriesP {
+ repoQueries[i] = query.Query(v)
+ }
+
indexerQuery := bleve.NewConjunctionQuery(
- numericEqualityQuery(repoID, "RepoID"),
+ bleve.NewDisjunctionQuery(repoQueries...),
bleve.NewDisjunctionQuery(
newMatchPhraseQuery(keyword, "Title", issueIndexerAnalyzer),
newMatchPhraseQuery(keyword, "Content", issueIndexerAnalyzer),
return nil, err
}
ret.Hits = append(ret.Hits, Match{
- ID: id,
- RepoID: repoID,
+ ID: id,
})
}
return &ret, nil
)
for _, kw := range keywords {
- res, err := indexer.Search(kw.Keyword, 2, 10, 0)
+ res, err := indexer.Search(kw.Keyword, []int64{2}, 10, 0)
assert.NoError(t, err)
var ids = make([]int64, 0, len(res.Hits))
}
// Search dummy function
-func (db *DBIndexer) Search(kw string, repoID int64, limit, start int) (*SearchResult, error) {
- total, ids, err := models.SearchIssueIDsByKeyword(kw, repoID, limit, start)
+func (db *DBIndexer) Search(kw string, repoIDs []int64, limit, start int) (*SearchResult, error) {
+ total, ids, err := models.SearchIssueIDsByKeyword(kw, repoIDs, limit, start)
if err != nil {
return nil, err
}
}
for _, id := range ids {
result.Hits = append(result.Hits, Match{
- ID: id,
- RepoID: repoID,
+ ID: id,
})
}
return &result, nil
// Match represents on search result
type Match struct {
- ID int64 `json:"id"`
- RepoID int64 `json:"repo_id"`
- Score float64 `json:"score"`
+ ID int64 `json:"id"`
+ Score float64 `json:"score"`
}
// SearchResult represents search results
Init() (bool, error)
Index(issue []*IndexerData) error
Delete(ids ...int64) error
- Search(kw string, repoID int64, limit, start int) (*SearchResult, error)
+ Search(kw string, repoIDs []int64, limit, start int) (*SearchResult, error)
}
type indexerHolder struct {
}
// SearchIssuesByKeyword search issue ids by keywords and repo id
-func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) {
+func SearchIssuesByKeyword(repoIDs []int64, keyword string) ([]int64, error) {
var issueIDs []int64
- res, err := holder.get().Search(keyword, repoID, 1000, 0)
+ res, err := holder.get().Search(keyword, repoIDs, 1000, 0)
if err != nil {
return nil, err
}
time.Sleep(5 * time.Second)
- ids, err := SearchIssuesByKeyword(1, "issue2")
+ ids, err := SearchIssuesByKeyword([]int64{1}, "issue2")
assert.NoError(t, err)
assert.EqualValues(t, []int64{2}, ids)
- ids, err = SearchIssuesByKeyword(1, "first")
+ ids, err = SearchIssuesByKeyword([]int64{1}, "first")
assert.NoError(t, err)
assert.EqualValues(t, []int64{1}, ids)
- ids, err = SearchIssuesByKeyword(1, "for")
+ ids, err = SearchIssuesByKeyword([]int64{1}, "for")
assert.NoError(t, err)
assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
- ids, err = SearchIssuesByKeyword(1, "good")
+ ids, err = SearchIssuesByKeyword([]int64{1}, "good")
assert.NoError(t, err)
assert.EqualValues(t, []int64{1}, ids)
}
setting.Indexer.IssueType = "db"
InitIssueIndexer(true)
- ids, err := SearchIssuesByKeyword(1, "issue2")
+ ids, err := SearchIssuesByKeyword([]int64{1}, "issue2")
assert.NoError(t, err)
assert.EqualValues(t, []int64{2}, ids)
- ids, err = SearchIssuesByKeyword(1, "first")
+ ids, err = SearchIssuesByKeyword([]int64{1}, "first")
assert.NoError(t, err)
assert.EqualValues(t, []int64{1}, ids)
- ids, err = SearchIssuesByKeyword(1, "for")
+ ids, err = SearchIssuesByKeyword([]int64{1}, "for")
assert.NoError(t, err)
assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
- ids, err = SearchIssuesByKeyword(1, "good")
+ ids, err = SearchIssuesByKeyword([]int64{1}, "good")
assert.NoError(t, err)
assert.EqualValues(t, []int64{1}, ids)
}
EnableTimetracking bool
DefaultEnableTimetracking bool
DefaultEnableDependencies bool
+ AllowCrossRepositoryDependencies bool
DefaultAllowOnlyContributorsToTrackTime bool
NoReplyAddress string
EnableUserHeatmap bool
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
}
Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
+ Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true)
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")
Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)
Merged *time.Time `json:"merged_at"`
}
+// RepositoryMeta basic repository information
+type RepositoryMeta struct {
+ ID int64 `json:"id"`
+ Name string `json:"name"`
+ FullName string `json:"full_name"`
+}
+
// Issue represents an issue in a repository
// swagger:model
type Issue struct {
Deadline *time.Time `json:"due_date"`
PullRequest *PullRequestMeta `json:"pull_request"`
+ Repo *RepositoryMeta `json:"repository"`
}
// ListIssueOption list issue options
.ui.form .ui.button{font-weight:400}
.ui.floating.label{z-index:10}
.ui.transparent.label{background-color:transparent}
+.ui.nopadding{padding:0}
.ui.menu,.ui.segment,.ui.vertical.menu{box-shadow:none}
.ui .menu:not(.vertical) .item>.button.compact{padding:.58928571em 1.125em}
.ui .menu:not(.vertical) .item>.button.small{font-size:.92857143rem}
.ui .text.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block}
.ui .text.thin{font-weight:400}
.ui .text.middle{vertical-align:middle}
+.ui .text.nopadding{padding:0}
+.ui .text.nomargin{margin:0}
.ui .message{text-align:center}
.ui.bottom.attached.message{font-weight:700;text-align:left;color:#000}
.ui.bottom.attached.message .pull-right{color:#000}
function initIssueList() {
const repolink = $('#repolink').val();
+ const repoId = $('#repoId').val();
+ const crossRepoSearch = $('#crossRepoSearch').val();
+ let issueSearchUrl = suburl + '/api/v1/repos/' + repolink + '/issues?q={query}';
+ if (crossRepoSearch === 'true') {
+ issueSearchUrl = suburl + '/api/v1/repos/issues/search?q={query}&priority_repo_id=' + repoId;
+ }
$('#new-dependency-drop-list')
.dropdown({
apiSettings: {
- url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}',
+ url: issueSearchUrl,
onResponse: function(response) {
const filteredResponse = {'success': true, 'results': []};
const currIssueId = $('#new-dependency-drop-list').data('issue-id');
return;
}
filteredResponse.results.push({
- 'name' : '#' + issue.number + ' ' + htmlEncode(issue.title),
+ 'name' : '#' + issue.number + ' ' + htmlEncode(issue.title) +
+ '<div class="text small dont-break-out">' + htmlEncode(issue.repository.full_name) + '</div>',
'value' : issue.id
});
});
background-color: transparent;
}
+ &.nopadding {
+ padding: 0;
+ }
+
&.menu,
&.vertical.menu,
&.segment {
&.middle {
vertical-align: middle;
}
+
+ &.nopadding {
+ padding: 0;
+ }
+
+ &.nomargin {
+ margin: 0;
+ }
}
.message {
m.Get("/search", repo.Search)
})
+ m.Get("/repos/issues/search", repo.SearchIssues)
+
m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)
m.Group("/repos", func() {
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
milestone_service "code.gitea.io/gitea/services/milestone"
)
+// SearchIssues searches for issues across the repositories that the user has access to
+func SearchIssues(ctx *context.APIContext) {
+ // swagger:operation GET /repos/issues/search issue issueSearchIssues
+ // ---
+ // summary: Search for issues across the repositories that the user has access to
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: state
+ // in: query
+ // description: whether issue is open or closed
+ // type: string
+ // - name: labels
+ // in: query
+ // description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
+ // type: string
+ // - name: page
+ // in: query
+ // description: page number of requested issues
+ // type: integer
+ // - name: q
+ // in: query
+ // description: search string
+ // type: string
+ // - name: priority_repo_id
+ // in: query
+ // description: repository to prioritize in the results
+ // type: integer
+ // format: int64
+ // responses:
+ // "200":
+ // "$ref": "#/responses/IssueList"
+ var isClosed util.OptionalBool
+ switch ctx.Query("state") {
+ case "closed":
+ isClosed = util.OptionalBoolTrue
+ case "all":
+ isClosed = util.OptionalBoolNone
+ default:
+ isClosed = util.OptionalBoolFalse
+ }
+
+ // find repos user can access (for issue search)
+ repoIDs := make([]int64, 0)
+ issueCount := 0
+ for page := 1; ; page++ {
+ repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
+ Page: page,
+ PageSize: 15,
+ Private: true,
+ Keyword: "",
+ OwnerID: ctx.User.ID,
+ TopicOnly: false,
+ Collaborate: util.OptionalBoolNone,
+ UserIsAdmin: ctx.IsUserSiteAdmin(),
+ UserID: ctx.User.ID,
+ OrderBy: models.SearchOrderByRecentUpdated,
+ })
+ if err != nil {
+ ctx.Error(500, "SearchRepositoryByName", err)
+ return
+ }
+
+ if len(repos) == 0 {
+ break
+ }
+ log.Trace("Processing next %d repos of %d", len(repos), count)
+ for _, repo := range repos {
+ switch isClosed {
+ case util.OptionalBoolTrue:
+ issueCount += repo.NumClosedIssues
+ case util.OptionalBoolFalse:
+ issueCount += repo.NumOpenIssues
+ case util.OptionalBoolNone:
+ issueCount += repo.NumIssues
+ }
+ repoIDs = append(repoIDs, repo.ID)
+ }
+ }
+
+ var issues []*models.Issue
+
+ keyword := strings.Trim(ctx.Query("q"), " ")
+ if strings.IndexByte(keyword, 0) >= 0 {
+ keyword = ""
+ }
+ var issueIDs []int64
+ var labelIDs []int64
+ var err error
+ if len(keyword) > 0 && len(repoIDs) > 0 {
+ issueIDs, err = issue_indexer.SearchIssuesByKeyword(repoIDs, keyword)
+ }
+
+ labels := ctx.Query("labels")
+ if splitted := strings.Split(labels, ","); labels != "" && len(splitted) > 0 {
+ labelIDs, err = models.GetLabelIDsInReposByNames(repoIDs, splitted)
+ if err != nil {
+ ctx.Error(500, "GetLabelIDsInRepoByNames", err)
+ return
+ }
+ }
+
+ // Only fetch the issues if we either don't have a keyword or the search returned issues
+ // This would otherwise return all issues if no issues were found by the search.
+ if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
+ issues, err = models.Issues(&models.IssuesOptions{
+ RepoIDs: repoIDs,
+ Page: ctx.QueryInt("page"),
+ PageSize: setting.UI.IssuePagingNum,
+ IsClosed: isClosed,
+ IssueIDs: issueIDs,
+ LabelIDs: labelIDs,
+ SortType: "priorityrepo",
+ PriorityRepoID: ctx.QueryInt64("priority_repo_id"),
+ })
+ }
+
+ if err != nil {
+ ctx.Error(500, "Issues", err)
+ return
+ }
+
+ apiIssues := make([]*api.Issue, len(issues))
+ for i := range issues {
+ apiIssues[i] = issues[i].APIFormat()
+ }
+
+ ctx.SetLinkHeader(issueCount, setting.UI.IssuePagingNum)
+ ctx.JSON(200, &apiIssues)
+}
+
// ListIssues list the issues of a repository
func ListIssues(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issues issue issueListIssues
var labelIDs []int64
var err error
if len(keyword) > 0 {
- issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx.Repo.Repository.ID, keyword)
+ issueIDs, err = issue_indexer.SearchIssuesByKeyword([]int64{ctx.Repo.Repository.ID}, keyword)
}
if splitted := strings.Split(ctx.Query("labels"), ","); len(splitted) > 0 {
var issueIDs []int64
if len(keyword) > 0 {
- issueIDs, err = issue_indexer.SearchIssuesByKeyword(repo.ID, keyword)
+ issueIDs, err = issue_indexer.SearchIssuesByKeyword([]int64{repo.ID}, keyword)
if err != nil {
ctx.ServerError("issueIndexer.Search", err)
return
// Check if the user can use the dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
+ // check if dependencies can be created across repositories
+ ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies
+
// Render comments and and fetch participants.
participants[0] = issue.Poster
for _, comment = range issue.Comments {
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/setting"
)
// AddDependency adds new dependencies
return
}
- // Check if both issues are in the same repo
- if issue.RepoID != dep.RepoID {
+ // Check if both issues are in the same repo if cross repository dependencies is not enabled
+ if issue.RepoID != dep.RepoID && !setting.Service.AllowCrossRepositoryDependencies {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_dep_not_same_repo"))
return
}
// Check if issue and dependency is the same
- if dep.Index == issueIndex {
+ if dep.ID == issue.ID {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.add_error_same_issue"))
return
}
</span>
<div class="ui relaxed divided list">
{{range .BlockingDependencies}}
- <div class="item{{if .IsClosed}} is-closed{{end}}">
- <div class="ui black label">#{{.Index}}</div>
- <a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a>
- <div class="ui transparent label right floated">
+ <div class="item{{if .Issue.IsClosed}} is-closed{{end}}">
+ <span class="text grey right floated">#{{.Issue.Index}}</span>
+ <a class="title has-emoji" href="{{.Repository.Link}}/issues/{{.Issue.Index}}">{{.Issue.Title}}</a>
+ <div class="text small">{{.Repository.OwnerName}}/{{.Repository.Name}}</div>
+ <div class="ui transparent label right floated nopadding">
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
- <a class="delete-dependency-button" onclick="deleteDependencyModal({{.ID}}, 'blocking');"
- data-tooltip="{{$.i18n.Tr "repo.issues.dependency.remove_info"}}" data-inverted="">
- <i class="delete icon text red"></i>
+ <a class="delete-dependency-button" onclick="deleteDependencyModal({{.Issue.ID}}, 'blocking');"
+ data-tooltip="{{$.i18n.Tr "repo.issues.dependency.remove_info"}}" data-inverted="">
+ <i class="delete icon text red nopadding nomargin"></i>
</a>
{{end}}
</div>
</span>
<div class="ui relaxed divided list">
{{range .BlockedByDependencies}}
- <div class="item{{if .IsClosed}} is-closed{{end}}">
- <div class="ui black label">#{{.Index}}</div>
- <a class="title has-emoji" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title}}</a>
- <div class="ui transparent label right floated">
- {{if and $.CanCreateIssueDependencies (not $.IsArchived)}}
- <a class="delete-dependency-button" onclick="deleteDependencyModal({{.ID}}, 'blockedBy');"
- data-tooltip="{{$.i18n.Tr "repo.issues.dependency.remove_info"}}" data-inverted="">
- <i class="delete icon text red"></i>
+ <div class="item{{if .Issue.IsClosed}} is-closed{{end}}">
+ <span class="text grey right floated">#{{.Issue.Index}}</span>
+ <a class="title has-emoji" href="{{.Repository.Link}}/issues/{{.Issue.Index}}">{{.Issue.Title}}</a>
+ <div class="text small">{{.Repository.OwnerName}}/{{.Repository.Name}}</div>
+ <div class="ui transparent label right floated nopadding">
+ {{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
+ <a class="delete-dependency-button" onclick="deleteDependencyModal({{.Issue.ID}}, 'blockedBy');"
+ data-tooltip="{{$.i18n.Tr "repo.issues.dependency.remove_info"}}" data-inverted="">
+ <i class="delete icon text red nopadding nomargin"></i>
</a>
{{end}}
</div>
</div>
{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
<input type="hidden" id="repolink" value="{{$.RepoRelPath}}">
+ <input type="hidden" id="repoId" value="{{.Repository.ID}}">
+ <input type="hidden" id="crossRepoSearch" value="{{.AllowCrossRepositoryDependencies}}">
<!-- I know, there is probably a better way to do this -->
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/>
}
}
},
+ "/repos/issues/search": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "issue"
+ ],
+ "summary": "Search for issues across the repositories that the user has access to",
+ "operationId": "issueSearchIssues",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "whether issue is open or closed",
+ "name": "state",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded",
+ "name": "labels",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "description": "page number of requested issues",
+ "name": "page",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "search string",
+ "name": "q",
+ "in": "query"
+ },
+ {
+ "type": "integer",
+ "format": "int64",
+ "description": "repository to prioritize in the results",
+ "name": "priority_repo_id",
+ "in": "query"
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/IssueList"
+ }
+ }
+ }
+ },
"/repos/migrate": {
"post": {
"consumes": [
"pull_request": {
"$ref": "#/definitions/PullRequestMeta"
},
+ "repository": {
+ "$ref": "#/definitions/RepositoryMeta"
+ },
"state": {
"$ref": "#/definitions/StateType"
},
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "RepositoryMeta": {
+ "description": "RepositoryMeta basic repository information",
+ "type": "object",
+ "properties": {
+ "full_name": {
+ "type": "string",
+ "x-go-name": "FullName"
+ },
+ "id": {
+ "type": "integer",
+ "format": "int64",
+ "x-go-name": "ID"
+ },
+ "name": {
+ "type": "string",
+ "x-go-name": "Name"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"SearchResults": {
"description": "SearchResults results of a successful search",
"type": "object",