diff --git a/apps/web/features/issues/store.ts b/apps/web/features/issues/store.ts index 1e47b7d73e..f439f8bd63 100644 --- a/apps/web/features/issues/store.ts +++ b/apps/web/features/issues/store.ts @@ -30,9 +30,18 @@ export const useIssueStore = create((set, get) => ({ const isInitialLoad = get().issues.length === 0; if (isInitialLoad) set({ loading: true }); try { - const res = await api.listIssues({ limit: 200 }); - logger.info("fetched", res.issues.length, "issues"); - set({ issues: res.issues, loading: false }); + const pageSize = 200; + let allIssues: Issue[] = []; + let offset = 0; + let total = 0; + do { + const res = await api.listIssues({ limit: pageSize, offset }); + allIssues = allIssues.concat(res.issues); + total = res.total; + offset += res.issues.length; + } while (allIssues.length < total); + logger.info("fetched", allIssues.length, "issues"); + set({ issues: allIssues, loading: false }); } catch (err) { logger.error("fetch failed", err); toast.error("Failed to load issues"); diff --git a/server/internal/handler/issue.go b/server/internal/handler/issue.go index 418252e24c..c320d454c6 100644 --- a/server/internal/handler/issue.go +++ b/server/internal/handler/issue.go @@ -124,6 +124,17 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { return } + total, err := h.Queries.CountIssues(ctx, db.CountIssuesParams{ + WorkspaceID: parseUUID(workspaceID), + Status: statusFilter, + Priority: priorityFilter, + AssigneeID: assigneeFilter, + }) + if err != nil { + writeError(w, http.StatusInternalServerError, "failed to count issues") + return + } + prefix := h.getIssuePrefix(ctx, parseUUID(workspaceID)) resp := make([]IssueResponse, len(issues)) for i, issue := range issues { @@ -132,7 +143,7 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{ "issues": resp, - "total": len(resp), + "total": total, }) } diff --git a/server/pkg/db/generated/issue.sql.go b/server/pkg/db/generated/issue.sql.go index f899eb6ebe..915d93485e 100644 --- a/server/pkg/db/generated/issue.sql.go +++ b/server/pkg/db/generated/issue.sql.go @@ -189,6 +189,33 @@ func (q *Queries) GetIssueInWorkspace(ctx context.Context, arg GetIssueInWorkspa return i, err } +const countIssues = `-- name: CountIssues :one +SELECT count(*) FROM issue +WHERE workspace_id = $1 + AND ($2::text IS NULL OR status = $2) + AND ($3::text IS NULL OR priority = $3) + AND ($4::uuid IS NULL OR assignee_id = $4) +` + +type CountIssuesParams struct { + WorkspaceID pgtype.UUID `json:"workspace_id"` + Status pgtype.Text `json:"status"` + Priority pgtype.Text `json:"priority"` + AssigneeID pgtype.UUID `json:"assignee_id"` +} + +func (q *Queries) CountIssues(ctx context.Context, arg CountIssuesParams) (int64, error) { + row := q.db.QueryRow(ctx, countIssues, + arg.WorkspaceID, + arg.Status, + arg.Priority, + arg.AssigneeID, + ) + var count int64 + err := row.Scan(&count) + return count, err +} + const listIssues = `-- name: ListIssues :many SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number FROM issue WHERE workspace_id = $1 diff --git a/server/pkg/db/queries/issue.sql b/server/pkg/db/queries/issue.sql index edc229c31c..ce89cce73f 100644 --- a/server/pkg/db/queries/issue.sql +++ b/server/pkg/db/queries/issue.sql @@ -7,6 +7,13 @@ WHERE workspace_id = $1 ORDER BY position ASC, created_at DESC LIMIT $2 OFFSET $3; +-- name: CountIssues :one +SELECT count(*) FROM issue +WHERE workspace_id = $1 + AND (sqlc.narg('status')::text IS NULL OR status = sqlc.narg('status')) + AND (sqlc.narg('priority')::text IS NULL OR priority = sqlc.narg('priority')) + AND (sqlc.narg('assignee_id')::uuid IS NULL OR assignee_id = sqlc.narg('assignee_id')); + -- name: GetIssue :one SELECT * FROM issue WHERE id = $1;