You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

issue_label.go 14KB

8 jaren geleden
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
4 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
7 jaren geleden
Allow cross-repository dependencies on issues (#7901) * in progress changes for #7405, added ability to add cross-repo dependencies * removed unused repolink var * fixed query that was breaking ci tests; fixed check in issue dependency add so that the id of the issue and dependency is checked rather than the indexes * reverted removal of string in local files becasue these are done via crowdin, not updated manually * removed 'Select("issue.*")' from getBlockedByDependencies and getBlockingDependencies based on comments in PR review * changed getBlockedByDependencies and getBlockingDependencies to use a more xorm-like query, also updated the sidebar as a result * simplified the getBlockingDependencies and getBlockedByDependencies methods; changed the sidebar to show the dependencies in a different format where you can see the name of the repository * made some changes to the issue view in the dependencies (issue name on top, repo full name on separate line). Change view of issue in the dependency search results (also showing the full repo name on separate line) * replace call to FindUserAccessibleRepoIDs with SearchRepositoryByName. The former was hardcoded to use isPrivate = false on the repo search, but this code needed it to be true. The SearchRepositoryByName method is used more in the code including on the user's dashboard * some more tweaks to the layout of the issues when showing dependencies and in the search box when you add new dependencies * added Name to the RepositoryMeta struct * updated swagger doc * fixed total count for link header on SearchIssues * fixed indentation * fixed aligment of remove icon on dependencies in issue sidebar * removed unnecessary nil check (unnecessary because issue.loadRepo is called prior to this block) * reverting .css change, somehow missed or forgot that less is used * updated less file and generated css; updated sidebar template with styles to line up delete and issue index * added ordering to the blocked by/depends on queries * fixed sorting in issue dependency search and the depends on/blocks views to show issues from the current repo first, then by created date descending; added a "all cross repository dependencies" setting to allow this feature to be turned off, if turned off, the issue dependency search will work the way it did before (restricted to the current repository) * re-applied my swagger changes after merge * fixed split string condition in issue search * changed ALLOW_CROSS_REPOSITORY_DEPENDENCIES description to sound more global than just the issue dependency search; returning 400 in the cross repo issue search api method if not enabled; fixed bug where the issue count did not respect the state parameter * when adding a dependency to an issue, added a check to make sure the issue and dependency are in the same repo if cross repo dependencies is not enabled * updated sortIssuesSession call in PullRequests, another commit moved this method from pull.go to pull_list.go so I had to re-apply my change here * fixed incorrect setting of user id parameter in search repos call
4 jaren geleden
7 jaren geleden
7 jaren geleden
8 jaren geleden
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
4 jaren geleden
8 jaren geleden
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
4 jaren geleden
8 jaren geleden
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
4 jaren geleden
8 jaren geleden
7 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
7 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
8 jaren geleden
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package models
  6. import (
  7. "fmt"
  8. "html/template"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. "xorm.io/builder"
  13. "xorm.io/xorm"
  14. )
  15. // LabelColorPattern is a regexp witch can validate LabelColor
  16. var LabelColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
  17. // Label represents a label of repository for issues.
  18. type Label struct {
  19. ID int64 `xorm:"pk autoincr"`
  20. RepoID int64 `xorm:"INDEX"`
  21. Name string
  22. Description string
  23. Color string `xorm:"VARCHAR(7)"`
  24. NumIssues int
  25. NumClosedIssues int
  26. NumOpenIssues int `xorm:"-"`
  27. IsChecked bool `xorm:"-"`
  28. QueryString string `xorm:"-"`
  29. IsSelected bool `xorm:"-"`
  30. IsExcluded bool `xorm:"-"`
  31. }
  32. // GetLabelTemplateFile loads the label template file by given name,
  33. // then parses and returns a list of name-color pairs and optionally description.
  34. func GetLabelTemplateFile(name string) ([][3]string, error) {
  35. data, err := GetRepoInitFile("label", name)
  36. if err != nil {
  37. return nil, fmt.Errorf("GetRepoInitFile: %v", err)
  38. }
  39. lines := strings.Split(string(data), "\n")
  40. list := make([][3]string, 0, len(lines))
  41. for i := 0; i < len(lines); i++ {
  42. line := strings.TrimSpace(lines[i])
  43. if len(line) == 0 {
  44. continue
  45. }
  46. parts := strings.SplitN(line, ";", 2)
  47. fields := strings.SplitN(parts[0], " ", 2)
  48. if len(fields) != 2 {
  49. return nil, fmt.Errorf("line is malformed: %s", line)
  50. }
  51. color := strings.Trim(fields[0], " ")
  52. if len(color) == 6 {
  53. color = "#" + color
  54. }
  55. if !LabelColorPattern.MatchString(color) {
  56. return nil, fmt.Errorf("bad HTML color code in line: %s", line)
  57. }
  58. var description string
  59. if len(parts) > 1 {
  60. description = strings.TrimSpace(parts[1])
  61. }
  62. fields[1] = strings.TrimSpace(fields[1])
  63. list = append(list, [3]string{fields[1], color, description})
  64. }
  65. return list, nil
  66. }
  67. // CalOpenIssues calculates the open issues of label.
  68. func (label *Label) CalOpenIssues() {
  69. label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
  70. }
  71. // LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
  72. func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64) {
  73. var labelQuerySlice []string
  74. labelSelected := false
  75. labelID := strconv.FormatInt(label.ID, 10)
  76. for _, s := range currentSelectedLabels {
  77. if s == label.ID {
  78. labelSelected = true
  79. } else if -s == label.ID {
  80. labelSelected = true
  81. label.IsExcluded = true
  82. } else if s != 0 {
  83. labelQuerySlice = append(labelQuerySlice, strconv.FormatInt(s, 10))
  84. }
  85. }
  86. if !labelSelected {
  87. labelQuerySlice = append(labelQuerySlice, labelID)
  88. }
  89. label.IsSelected = labelSelected
  90. label.QueryString = strings.Join(labelQuerySlice, ",")
  91. }
  92. // ForegroundColor calculates the text color for labels based
  93. // on their background color.
  94. func (label *Label) ForegroundColor() template.CSS {
  95. if strings.HasPrefix(label.Color, "#") {
  96. if color, err := strconv.ParseUint(label.Color[1:], 16, 64); err == nil {
  97. r := float32(0xFF & (color >> 16))
  98. g := float32(0xFF & (color >> 8))
  99. b := float32(0xFF & color)
  100. luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255
  101. if luminance < 0.66 {
  102. return template.CSS("#fff")
  103. }
  104. }
  105. }
  106. // default to black
  107. return template.CSS("#000")
  108. }
  109. func loadLabels(labelTemplate string) ([]string, error) {
  110. list, err := GetLabelTemplateFile(labelTemplate)
  111. if err != nil {
  112. return nil, ErrIssueLabelTemplateLoad{labelTemplate, err}
  113. }
  114. labels := make([]string, len(list))
  115. for i := 0; i < len(list); i++ {
  116. labels[i] = list[i][0]
  117. }
  118. return labels, nil
  119. }
  120. // LoadLabelsFormatted loads the labels' list of a template file as a string separated by comma
  121. func LoadLabelsFormatted(labelTemplate string) (string, error) {
  122. labels, err := loadLabels(labelTemplate)
  123. return strings.Join(labels, ", "), err
  124. }
  125. func initializeLabels(e Engine, repoID int64, labelTemplate string) error {
  126. list, err := GetLabelTemplateFile(labelTemplate)
  127. if err != nil {
  128. return ErrIssueLabelTemplateLoad{labelTemplate, err}
  129. }
  130. labels := make([]*Label, len(list))
  131. for i := 0; i < len(list); i++ {
  132. labels[i] = &Label{
  133. RepoID: repoID,
  134. Name: list[i][0],
  135. Description: list[i][2],
  136. Color: list[i][1],
  137. }
  138. }
  139. for _, label := range labels {
  140. if err = newLabel(e, label); err != nil {
  141. return err
  142. }
  143. }
  144. return nil
  145. }
  146. // InitializeLabels adds a label set to a repository using a template
  147. func InitializeLabels(ctx DBContext, repoID int64, labelTemplate string) error {
  148. return initializeLabels(ctx.e, repoID, labelTemplate)
  149. }
  150. func newLabel(e Engine, label *Label) error {
  151. _, err := e.Insert(label)
  152. return err
  153. }
  154. // NewLabel creates a new label for a repository
  155. func NewLabel(label *Label) error {
  156. if !LabelColorPattern.MatchString(label.Color) {
  157. return fmt.Errorf("bad color code: %s", label.Color)
  158. }
  159. return newLabel(x, label)
  160. }
  161. // NewLabels creates new labels for a repository.
  162. func NewLabels(labels ...*Label) error {
  163. sess := x.NewSession()
  164. defer sess.Close()
  165. if err := sess.Begin(); err != nil {
  166. return err
  167. }
  168. for _, label := range labels {
  169. if !LabelColorPattern.MatchString(label.Color) {
  170. return fmt.Errorf("bad color code: %s", label.Color)
  171. }
  172. if err := newLabel(sess, label); err != nil {
  173. return err
  174. }
  175. }
  176. return sess.Commit()
  177. }
  178. // getLabelInRepoByName returns a label by Name in given repository.
  179. // If pass repoID as 0, then ORM will ignore limitation of repository
  180. // and can return arbitrary label with any valid ID.
  181. func getLabelInRepoByName(e Engine, repoID int64, labelName string) (*Label, error) {
  182. if len(labelName) == 0 {
  183. return nil, ErrLabelNotExist{0, repoID}
  184. }
  185. l := &Label{
  186. Name: labelName,
  187. RepoID: repoID,
  188. }
  189. has, err := e.Get(l)
  190. if err != nil {
  191. return nil, err
  192. } else if !has {
  193. return nil, ErrLabelNotExist{0, l.RepoID}
  194. }
  195. return l, nil
  196. }
  197. // getLabelInRepoByID returns a label by ID in given repository.
  198. // If pass repoID as 0, then ORM will ignore limitation of repository
  199. // and can return arbitrary label with any valid ID.
  200. func getLabelInRepoByID(e Engine, repoID, labelID int64) (*Label, error) {
  201. if labelID <= 0 {
  202. return nil, ErrLabelNotExist{labelID, repoID}
  203. }
  204. l := &Label{
  205. ID: labelID,
  206. RepoID: repoID,
  207. }
  208. has, err := e.Get(l)
  209. if err != nil {
  210. return nil, err
  211. } else if !has {
  212. return nil, ErrLabelNotExist{l.ID, l.RepoID}
  213. }
  214. return l, nil
  215. }
  216. // GetLabelByID returns a label by given ID.
  217. func GetLabelByID(id int64) (*Label, error) {
  218. return getLabelInRepoByID(x, 0, id)
  219. }
  220. // GetLabelInRepoByName returns a label by name in given repository.
  221. func GetLabelInRepoByName(repoID int64, labelName string) (*Label, error) {
  222. return getLabelInRepoByName(x, repoID, labelName)
  223. }
  224. // GetLabelIDsInRepoByNames returns a list of labelIDs by names in a given
  225. // repository.
  226. // it silently ignores label names that do not belong to the repository.
  227. func GetLabelIDsInRepoByNames(repoID int64, labelNames []string) ([]int64, error) {
  228. labelIDs := make([]int64, 0, len(labelNames))
  229. return labelIDs, x.Table("label").
  230. Where("repo_id = ?", repoID).
  231. In("name", labelNames).
  232. Asc("name").
  233. Cols("id").
  234. Find(&labelIDs)
  235. }
  236. // GetLabelIDsInReposByNames returns a list of labelIDs by names in one of the given
  237. // repositories.
  238. // it silently ignores label names that do not belong to the repository.
  239. func GetLabelIDsInReposByNames(repoIDs []int64, labelNames []string) ([]int64, error) {
  240. labelIDs := make([]int64, 0, len(labelNames))
  241. return labelIDs, x.Table("label").
  242. In("repo_id", repoIDs).
  243. In("name", labelNames).
  244. Asc("name").
  245. Cols("id").
  246. Find(&labelIDs)
  247. }
  248. // GetLabelInRepoByID returns a label by ID in given repository.
  249. func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) {
  250. return getLabelInRepoByID(x, repoID, labelID)
  251. }
  252. // GetLabelsInRepoByIDs returns a list of labels by IDs in given repository,
  253. // it silently ignores label IDs that do not belong to the repository.
  254. func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) {
  255. labels := make([]*Label, 0, len(labelIDs))
  256. return labels, x.
  257. Where("repo_id = ?", repoID).
  258. In("id", labelIDs).
  259. Asc("name").
  260. Find(&labels)
  261. }
  262. func getLabelsByRepoID(e Engine, repoID int64, sortType string, listOptions ListOptions) ([]*Label, error) {
  263. labels := make([]*Label, 0, 10)
  264. sess := e.Where("repo_id = ?", repoID)
  265. switch sortType {
  266. case "reversealphabetically":
  267. sess.Desc("name")
  268. case "leastissues":
  269. sess.Asc("num_issues")
  270. case "mostissues":
  271. sess.Desc("num_issues")
  272. default:
  273. sess.Asc("name")
  274. }
  275. if listOptions.Page != 0 {
  276. sess = listOptions.setSessionPagination(sess)
  277. }
  278. return labels, sess.Find(&labels)
  279. }
  280. // GetLabelsByRepoID returns all labels that belong to given repository by ID.
  281. func GetLabelsByRepoID(repoID int64, sortType string, listOptions ListOptions) ([]*Label, error) {
  282. return getLabelsByRepoID(x, repoID, sortType, listOptions)
  283. }
  284. func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
  285. var labels []*Label
  286. return labels, e.Where("issue_label.issue_id = ?", issueID).
  287. Join("LEFT", "issue_label", "issue_label.label_id = label.id").
  288. Asc("label.name").
  289. Find(&labels)
  290. }
  291. // GetLabelsByIssueID returns all labels that belong to given issue by ID.
  292. func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
  293. return getLabelsByIssueID(x, issueID)
  294. }
  295. func updateLabel(e Engine, l *Label) error {
  296. _, err := e.ID(l.ID).
  297. SetExpr("num_issues",
  298. builder.Select("count(*)").From("issue_label").
  299. Where(builder.Eq{"label_id": l.ID}),
  300. ).
  301. SetExpr("num_closed_issues",
  302. builder.Select("count(*)").From("issue_label").
  303. InnerJoin("issue", "issue_label.issue_id = issue.id").
  304. Where(builder.Eq{
  305. "issue_label.label_id": l.ID,
  306. "issue.is_closed": true,
  307. }),
  308. ).
  309. AllCols().Update(l)
  310. return err
  311. }
  312. // UpdateLabel updates label information.
  313. func UpdateLabel(l *Label) error {
  314. if !LabelColorPattern.MatchString(l.Color) {
  315. return fmt.Errorf("bad color code: %s", l.Color)
  316. }
  317. return updateLabel(x, l)
  318. }
  319. // DeleteLabel delete a label of given repository.
  320. func DeleteLabel(repoID, labelID int64) error {
  321. _, err := GetLabelInRepoByID(repoID, labelID)
  322. if err != nil {
  323. if IsErrLabelNotExist(err) {
  324. return nil
  325. }
  326. return err
  327. }
  328. sess := x.NewSession()
  329. defer sess.Close()
  330. if err = sess.Begin(); err != nil {
  331. return err
  332. }
  333. if _, err = sess.ID(labelID).Delete(new(Label)); err != nil {
  334. return err
  335. } else if _, err = sess.
  336. Where("label_id = ?", labelID).
  337. Delete(new(IssueLabel)); err != nil {
  338. return err
  339. }
  340. // Clear label id in comment table
  341. if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Update(&Comment{}); err != nil {
  342. return err
  343. }
  344. return sess.Commit()
  345. }
  346. // .___ .____ ___. .__
  347. // | | ______ ________ __ ____ | | _____ \_ |__ ____ | |
  348. // | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| |
  349. // | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__
  350. // |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
  351. // \/ \/ \/ \/ \/ \/ \/
  352. // IssueLabel represents an issue-label relation.
  353. type IssueLabel struct {
  354. ID int64 `xorm:"pk autoincr"`
  355. IssueID int64 `xorm:"UNIQUE(s)"`
  356. LabelID int64 `xorm:"UNIQUE(s)"`
  357. }
  358. func hasIssueLabel(e Engine, issueID, labelID int64) bool {
  359. has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel))
  360. return has
  361. }
  362. // HasIssueLabel returns true if issue has been labeled.
  363. func HasIssueLabel(issueID, labelID int64) bool {
  364. return hasIssueLabel(x, issueID, labelID)
  365. }
  366. func newIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) {
  367. if _, err = e.Insert(&IssueLabel{
  368. IssueID: issue.ID,
  369. LabelID: label.ID,
  370. }); err != nil {
  371. return err
  372. }
  373. if err = issue.loadRepo(e); err != nil {
  374. return
  375. }
  376. var opts = &CreateCommentOptions{
  377. Type: CommentTypeLabel,
  378. Doer: doer,
  379. Repo: issue.Repo,
  380. Issue: issue,
  381. Label: label,
  382. Content: "1",
  383. }
  384. if _, err = createComment(e, opts); err != nil {
  385. return err
  386. }
  387. return updateLabel(e, label)
  388. }
  389. // NewIssueLabel creates a new issue-label relation.
  390. func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
  391. if HasIssueLabel(issue.ID, label.ID) {
  392. return nil
  393. }
  394. sess := x.NewSession()
  395. defer sess.Close()
  396. if err = sess.Begin(); err != nil {
  397. return err
  398. }
  399. if err = newIssueLabel(sess, issue, label, doer); err != nil {
  400. return err
  401. }
  402. return sess.Commit()
  403. }
  404. func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label, doer *User) (err error) {
  405. for i := range labels {
  406. if hasIssueLabel(e, issue.ID, labels[i].ID) {
  407. continue
  408. }
  409. if err = newIssueLabel(e, issue, labels[i], doer); err != nil {
  410. return fmt.Errorf("newIssueLabel: %v", err)
  411. }
  412. }
  413. return nil
  414. }
  415. // NewIssueLabels creates a list of issue-label relations.
  416. func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) {
  417. sess := x.NewSession()
  418. defer sess.Close()
  419. if err = sess.Begin(); err != nil {
  420. return err
  421. }
  422. if err = newIssueLabels(sess, issue, labels, doer); err != nil {
  423. return err
  424. }
  425. return sess.Commit()
  426. }
  427. func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) {
  428. if count, err := e.Delete(&IssueLabel{
  429. IssueID: issue.ID,
  430. LabelID: label.ID,
  431. }); err != nil {
  432. return err
  433. } else if count == 0 {
  434. return nil
  435. }
  436. if err = issue.loadRepo(e); err != nil {
  437. return
  438. }
  439. var opts = &CreateCommentOptions{
  440. Type: CommentTypeLabel,
  441. Doer: doer,
  442. Repo: issue.Repo,
  443. Issue: issue,
  444. Label: label,
  445. }
  446. if _, err = createComment(e, opts); err != nil {
  447. return err
  448. }
  449. return updateLabel(e, label)
  450. }
  451. // DeleteIssueLabel deletes issue-label relation.
  452. func DeleteIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
  453. sess := x.NewSession()
  454. defer sess.Close()
  455. if err = sess.Begin(); err != nil {
  456. return err
  457. }
  458. if err = deleteIssueLabel(sess, issue, label, doer); err != nil {
  459. return err
  460. }
  461. return sess.Commit()
  462. }