ListIssues returns the list of issues by find query.
(ctx context.Context, find *FindIssueMessage)
| 276 | |
| 277 | // ListIssues returns the list of issues by find query. |
| 278 | func (s *Store) ListIssues(ctx context.Context, find *FindIssueMessage) ([]*IssueMessage, error) { |
| 279 | orderByClause := "ORDER BY issue.id DESC" |
| 280 | from := qb.Q().Space("issue") |
| 281 | where := qb.Q() |
| 282 | |
| 283 | // `ProjectIDs` is required: empty slice intentionally returns zero |
| 284 | // rows, which `SearchIssues` relies on as its IAM-derived "this user |
| 285 | // has access to no projects" guard. Skipping the filter on empty |
| 286 | // would leak cross-project issues. Cross-project runner scans must |
| 287 | // pre-collect the project ID set (e.g. via `ListProjects`) and pass |
| 288 | // the explicit list here. |
| 289 | if len(find.ProjectIDs) == 1 { |
| 290 | where.And("issue.project = ?", find.ProjectIDs[0]) |
| 291 | } else { |
| 292 | where.And("issue.project = ANY(?)", find.ProjectIDs) |
| 293 | } |
| 294 | if find.Workspace != "" { |
| 295 | from.Space("JOIN project ON issue.project = project.resource_id") |
| 296 | where.And("project.workspace = ?", find.Workspace) |
| 297 | } |
| 298 | |
| 299 | if v := find.UID; v != nil { |
| 300 | where.And("issue.id = ?", *v) |
| 301 | } |
| 302 | if v := find.PlanUID; v != nil { |
| 303 | where.And("issue.plan_id = ?", *v) |
| 304 | } |
| 305 | if v := find.PlanUIDs; v != nil { |
| 306 | where.And("issue.plan_id = ANY(?)", *v) |
| 307 | } |
| 308 | |
| 309 | if v := find.CreatorID; v != nil { |
| 310 | where.And("issue.creator = ?", *v) |
| 311 | } |
| 312 | if v := find.CreatedAtBefore; v != nil { |
| 313 | where.And("issue.created_at < ?", *v) |
| 314 | } |
| 315 | if v := find.CreatedAtAfter; v != nil { |
| 316 | where.And("issue.created_at > ?", *v) |
| 317 | } |
| 318 | if v := find.Types; v != nil { |
| 319 | typeStrings := make([]string, 0, len(*v)) |
| 320 | for _, t := range *v { |
| 321 | typeStrings = append(typeStrings, t.String()) |
| 322 | } |
| 323 | where.And("issue.type = ANY(?)", typeStrings) |
| 324 | } |
| 325 | if v := find.Query; v != nil && *v != "" { |
| 326 | searchCondition := qb.Q() |
| 327 | if tsQuery := getTSQuery(*v); tsQuery != "" { |
| 328 | from.Space("LEFT JOIN CAST(? AS tsquery) AS query ON TRUE", tsQuery) |
| 329 | searchCondition.Or("issue.ts_vector @@ query") |
| 330 | orderByClause = "ORDER BY ts_rank(issue.ts_vector, query) DESC, issue.id DESC" |
| 331 | } |
| 332 | searchCondition.Or("issue.name ILIKE ?", "%"+*v+"%") |
| 333 | where.And("(?)", searchCondition) |
| 334 | } |
| 335 | if len(find.StatusList) != 0 { |