code.gitea.io/gitea@v1.22.3/modules/indexer/issues/internal/model.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package internal
     5  
     6  import (
     7  	"code.gitea.io/gitea/models/db"
     8  	"code.gitea.io/gitea/modules/optional"
     9  	"code.gitea.io/gitea/modules/timeutil"
    10  )
    11  
    12  // IndexerData data stored in the issue indexer
    13  type IndexerData struct {
    14  	ID       int64 `json:"id"`
    15  	RepoID   int64 `json:"repo_id"`
    16  	IsPublic bool  `json:"is_public"` // If the repo is public
    17  
    18  	// Fields used for keyword searching
    19  	Title    string   `json:"title"`
    20  	Content  string   `json:"content"`
    21  	Comments []string `json:"comments"`
    22  
    23  	// Fields used for filtering
    24  	IsPull             bool               `json:"is_pull"`
    25  	IsClosed           bool               `json:"is_closed"`
    26  	LabelIDs           []int64            `json:"label_ids"`
    27  	NoLabel            bool               `json:"no_label"` // True if LabelIDs is empty
    28  	MilestoneID        int64              `json:"milestone_id"`
    29  	ProjectID          int64              `json:"project_id"`
    30  	ProjectBoardID     int64              `json:"project_board_id"`
    31  	PosterID           int64              `json:"poster_id"`
    32  	AssigneeID         int64              `json:"assignee_id"`
    33  	MentionIDs         []int64            `json:"mention_ids"`
    34  	ReviewedIDs        []int64            `json:"reviewed_ids"`
    35  	ReviewRequestedIDs []int64            `json:"review_requested_ids"`
    36  	SubscriberIDs      []int64            `json:"subscriber_ids"`
    37  	UpdatedUnix        timeutil.TimeStamp `json:"updated_unix"`
    38  
    39  	// Fields used for sorting
    40  	// UpdatedUnix is both used for filtering and sorting.
    41  	// ID is used for sorting too, to make the sorting stable.
    42  	CreatedUnix  timeutil.TimeStamp `json:"created_unix"`
    43  	DeadlineUnix timeutil.TimeStamp `json:"deadline_unix"`
    44  	CommentCount int64              `json:"comment_count"`
    45  }
    46  
    47  // Match represents on search result
    48  type Match struct {
    49  	ID    int64   `json:"id"`
    50  	Score float64 `json:"score"`
    51  }
    52  
    53  // SearchResult represents search results
    54  type SearchResult struct {
    55  	Total int64
    56  	Hits  []Match
    57  }
    58  
    59  // SearchOptions represents search options.
    60  //
    61  // It has a slightly different design from database query options.
    62  // In database query options, a field is never a pointer, so it could be confusing when it's zero value:
    63  // Do you want to find data with a field value of 0, or do you not specify the field in the options?
    64  // To avoid this confusion, db introduced db.NoConditionID(-1).
    65  // So zero value means the field is not specified in the search options, and db.NoConditionID means "== 0" or "id NOT IN (SELECT id FROM ...)"
    66  // It's still not ideal, it trapped developers many times.
    67  // And sometimes -1 could be a valid value, like issue ID, negative numbers indicate exclusion.
    68  // Since db.NoConditionID is for "db" (the package name is db), it makes sense not to use it in the indexer:
    69  // Why do bleve/elasticsearch/meilisearch indexers need to know about db.NoConditionID?
    70  // So in SearchOptions, we use pointer for fields which could be not specified,
    71  // and always use the value to filter if it's not nil, even if it's zero or negative.
    72  // It can handle almost all cases, if there is an exception, we can add a new field, like NoLabelOnly.
    73  // Unfortunately, we still use db for the indexer and have to convert between db.NoConditionID and nil for legacy reasons.
    74  type SearchOptions struct {
    75  	Keyword string // keyword to search
    76  
    77  	IsFuzzyKeyword bool // if false the levenshtein distance is 0
    78  
    79  	RepoIDs   []int64 // repository IDs which the issues belong to
    80  	AllPublic bool    // if include all public repositories
    81  
    82  	IsPull   optional.Option[bool] // if the issues is a pull request
    83  	IsClosed optional.Option[bool] // if the issues is closed
    84  
    85  	IncludedLabelIDs    []int64 // labels the issues have
    86  	ExcludedLabelIDs    []int64 // labels the issues don't have
    87  	IncludedAnyLabelIDs []int64 // labels the issues have at least one. It will be ignored if IncludedLabelIDs is not empty. It's an uncommon filter, but it has been supported accidentally by issues.IssuesOptions.IncludedLabelNames.
    88  	NoLabelOnly         bool    // if the issues have no label, if true, IncludedLabelIDs and ExcludedLabelIDs, IncludedAnyLabelIDs will be ignored
    89  
    90  	MilestoneIDs []int64 // milestones the issues have
    91  
    92  	ProjectID      optional.Option[int64] // project the issues belong to
    93  	ProjectBoardID optional.Option[int64] // project board the issues belong to
    94  
    95  	PosterID optional.Option[int64] // poster of the issues
    96  
    97  	AssigneeID optional.Option[int64] // assignee of the issues, zero means no assignee
    98  
    99  	MentionID optional.Option[int64] // mentioned user of the issues
   100  
   101  	ReviewedID        optional.Option[int64] // reviewer of the issues
   102  	ReviewRequestedID optional.Option[int64] // requested reviewer of the issues
   103  
   104  	SubscriberID optional.Option[int64] // subscriber of the issues
   105  
   106  	UpdatedAfterUnix  optional.Option[int64]
   107  	UpdatedBeforeUnix optional.Option[int64]
   108  
   109  	Paginator *db.ListOptions
   110  
   111  	SortBy SortBy // sort by field
   112  }
   113  
   114  // Copy returns a copy of the options.
   115  // Be careful, it's not a deep copy, so `SearchOptions.RepoIDs = {...}` is OK while `SearchOptions.RepoIDs[0] = ...` is not.
   116  func (o *SearchOptions) Copy(edit ...func(options *SearchOptions)) *SearchOptions {
   117  	if o == nil {
   118  		return nil
   119  	}
   120  	v := *o
   121  	for _, e := range edit {
   122  		e(&v)
   123  	}
   124  	return &v
   125  }
   126  
   127  type SortBy string
   128  
   129  const (
   130  	SortByCreatedDesc  SortBy = "-created_unix"
   131  	SortByUpdatedDesc  SortBy = "-updated_unix"
   132  	SortByCommentsDesc SortBy = "-comment_count"
   133  	SortByDeadlineDesc SortBy = "-deadline_unix"
   134  	SortByCreatedAsc   SortBy = "created_unix"
   135  	SortByUpdatedAsc   SortBy = "updated_unix"
   136  	SortByCommentsAsc  SortBy = "comment_count"
   137  	SortByDeadlineAsc  SortBy = "deadline_unix"
   138  	// Unsupported sort types which are supported by issues.IssuesOptions.SortType:
   139  	//
   140  	//  - "priorityrepo":
   141  	//                    It's impossible to support it in the indexer.
   142  	//                    It is based on the specified repository in the request, so we cannot add static field to the indexer.
   143  	//                    If we do something like that query the issues in the specified repository first then append other issues,
   144  	//                    it will break the pagination.
   145  	//
   146  	// - "project-column-sorting":
   147  	//                    Although it's possible to support it by adding project.ProjectIssue.Sorting to the indexer,
   148  	//                    but what if the issue belongs to multiple projects?
   149  	//                    Since it's unsupported to search issues with keyword in project page, we don't need to support it.
   150  )