feat: facets for issues and alerts#483
Conversation
There was a problem hiding this comment.
Pull request overview
Adds facet distributions to the Issues GraphQL connection so clients can build filter UIs (counts per environment/severity/resourceType/issueType) without additional round-trips, using a SQL aggregate query and lazy computation when facets is requested.
Changes:
- Extends
IssueConnectionin the GraphQL schema with an optionalfacetsfield and introduces new facet item types. - Adds
FacetsForIssuesSQL aggregate query +issue.ComputeFacets/buildFacetsto compute per-dimension counts. - Refactors the issues connection return type from
pagination.Connection[Issue]to*issue.IssueConnectionacross resolvers and regenerates gqlgen/sqlc outputs.
Reviewed changes
Copilot reviewed 13 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/issue/queries/issue.sql | Adds FacetsForIssues aggregate query for grouped total/filtered counts. |
| internal/issue/queries.go | Changes ListIssues to return *IssueConnection carrying teamSlug/filter. |
| internal/issue/model.go | Replaces IssueConnection type alias with a struct; adds facet DTO types. |
| internal/issue/issuesql/querier.go | Adds FacetsForIssues to the sqlc querier interface. |
| internal/issue/issuesql/issue.sql.go | sqlc-generated implementation/types for FacetsForIssues. |
| internal/issue/facets.go | Implements ComputeFacets and buildFacets assembly/sorting logic. |
| internal/issue/facets_test.go | Unit tests for buildFacets behavior (seeded zeros, empty input). |
| internal/graph/schema/issues.graphqls | Adds IssueConnection.facets and new facet GraphQL types with descriptions. |
| internal/graph/issues.resolvers.go | Adds IssueConnection resolver for facets; updates Team issues return type. |
| internal/graph/applications.resolvers.go | Updates Application.issues resolver to return *issue.IssueConnection. |
| internal/graph/jobs.resolvers.go | Updates Job.issues resolver to return *issue.IssueConnection. |
| internal/graph/opensearch.resolvers.go | Updates OpenSearch.issues resolver to return *issue.IssueConnection. |
| internal/graph/sqlinstance.resolvers.go | Updates SQLInstance.issues resolver to return *issue.IssueConnection. |
| internal/graph/valkey.resolvers.go | Updates Valkey.issues resolver to return *issue.IssueConnection. |
| internal/graph/gengql/applications.generated.go | gqlgen updates for Application.issues returning IssueConnection. |
| internal/graph/gengql/jobs.generated.go | gqlgen updates for Job.issues returning IssueConnection. |
| internal/graph/gengql/opensearch.generated.go | gqlgen updates for OpenSearch.issues returning IssueConnection. |
| internal/graph/gengql/sqlinstance.generated.go | gqlgen updates for SQLInstance.issues returning IssueConnection. |
| internal/graph/gengql/teams.generated.go | gqlgen updates for Team.issues returning IssueConnection. |
| internal/graph/gengql/valkey.generated.go | gqlgen updates for Valkey.issues returning IssueConnection. |
| internal/graph/gengql/issues.generated.go | gqlgen adds IssueConnectionResolver.Facets and marshaling for new facet types. |
| internal/graph/gengql/root_.generated.go | gqlgen registers IssueConnection resolver root + complexity entries. |
Files not reviewed (9)
- internal/graph/gengql/applications.generated.go: Generated file
- internal/graph/gengql/issues.generated.go: Generated file
- internal/graph/gengql/jobs.generated.go: Generated file
- internal/graph/gengql/opensearch.generated.go: Generated file
- internal/graph/gengql/sqlinstance.generated.go: Generated file
- internal/graph/gengql/teams.generated.go: Generated file
- internal/graph/gengql/valkey.generated.go: Generated file
- internal/issue/issuesql/issue.sql.go: Generated file
- internal/issue/issuesql/querier.go: Generated file
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 22 changed files in this pull request and generated no new comments.
Files not reviewed (9)
- internal/graph/gengql/applications.generated.go: Generated file
- internal/graph/gengql/issues.generated.go: Generated file
- internal/graph/gengql/jobs.generated.go: Generated file
- internal/graph/gengql/opensearch.generated.go: Generated file
- internal/graph/gengql/sqlinstance.generated.go: Generated file
- internal/graph/gengql/teams.generated.go: Generated file
- internal/graph/gengql/valkey.generated.go: Generated file
- internal/issue/issuesql/issue.sql.go: Generated file
- internal/issue/issuesql/querier.go: Generated file
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 28 changed files in this pull request and generated 1 comment.
Files not reviewed (10)
- internal/graph/gengql/alerts.generated.go: Generated file
- internal/graph/gengql/applications.generated.go: Generated file
- internal/graph/gengql/issues.generated.go: Generated file
- internal/graph/gengql/jobs.generated.go: Generated file
- internal/graph/gengql/opensearch.generated.go: Generated file
- internal/graph/gengql/sqlinstance.generated.go: Generated file
- internal/graph/gengql/teams.generated.go: Generated file
- internal/graph/gengql/valkey.generated.go: Generated file
- internal/issue/issuesql/issue.sql.go: Generated file
- internal/issue/issuesql/querier.go: Generated file
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 28 changed files in this pull request and generated no new comments.
Files not reviewed (10)
- internal/graph/gengql/alerts.generated.go: Generated file
- internal/graph/gengql/applications.generated.go: Generated file
- internal/graph/gengql/issues.generated.go: Generated file
- internal/graph/gengql/jobs.generated.go: Generated file
- internal/graph/gengql/opensearch.generated.go: Generated file
- internal/graph/gengql/sqlinstance.generated.go: Generated file
- internal/graph/gengql/teams.generated.go: Generated file
- internal/graph/gengql/valkey.generated.go: Generated file
- internal/issue/issuesql/issue.sql.go: Generated file
- internal/issue/issuesql/querier.go: Generated file
Introduces facets for the team issues query, following the SQL-aggregate pattern used by the activity log. Facets provide distribution counts across four dimensions to help narrow down results: - environments - severities - resourceTypes - issueTypes A new FacetsForIssues SQL query computes counts grouped by (severity, resource_type, env, issue_type) using a FILTER WHERE clause so that all possible values are seeded (with count 0 if unmatched) and the filtered counts reflect the current active filter.
Adds IssueFacets type to IssueConnection with four fields: environments, severities, resourceTypes, issueTypes Each field returns a list of facet items with count of matching issues. Counts respect the current filter but are computed across the full (unpaginated) result set. New GraphQL types: IssueFacets, SeverityFacetItem, ResourceTypeFacetItem, IssueTypeFacetItem All Issues resolvers (team + resource-level) now return *issue.IssueConnection so the connection carries the team slug and filter needed for lazy facet computation.
Address Copilot review feedback:
- FacetsForIssues SQL: split scope params (resource_type, resource_name, env)
into the outer WHERE clause so total_count reflects the actual connection
scope, matching the activitylog FacetsForActivityTypes pattern. User filter
params remain in COUNT(*) FILTER (...).
- IssueConnection now carries a separate IssueScope for resource-level
queries (application, job, opensearch, sqlinstance, valkey). Resource
resolvers build the scope from the resource object instead of embedding
it in IssueFilter.
- Normalize empty environments slice to nil in ListIssues and ComputeFacets
so that an empty []string{} does not produce an always-false ANY predicate.
…o IssueSeverityFacetItem and IssueResourceTypeFacetItem
…ations in issues.generated.go
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 28 changed files in this pull request and generated 2 comments.
Files not reviewed (10)
- internal/graph/gengql/alerts.generated.go: Generated file
- internal/graph/gengql/applications.generated.go: Generated file
- internal/graph/gengql/issues.generated.go: Generated file
- internal/graph/gengql/jobs.generated.go: Generated file
- internal/graph/gengql/opensearch.generated.go: Generated file
- internal/graph/gengql/sqlinstance.generated.go: Generated file
- internal/graph/gengql/teams.generated.go: Generated file
- internal/graph/gengql/valkey.generated.go: Generated file
- internal/issue/issuesql/issue.sql.go: Generated file
- internal/issue/issuesql/querier.go: Generated file
…ssue constraints" This reverts commit 10724c3.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 28 changed files in this pull request and generated 1 comment.
Files not reviewed (10)
- internal/graph/gengql/alerts.generated.go: Generated file
- internal/graph/gengql/applications.generated.go: Generated file
- internal/graph/gengql/issues.generated.go: Generated file
- internal/graph/gengql/jobs.generated.go: Generated file
- internal/graph/gengql/opensearch.generated.go: Generated file
- internal/graph/gengql/sqlinstance.generated.go: Generated file
- internal/graph/gengql/teams.generated.go: Generated file
- internal/graph/gengql/valkey.generated.go: Generated file
- internal/issue/issuesql/issue.sql.go: Generated file
- internal/issue/issuesql/querier.go: Generated file
Summary
Adds facets to
IssueConnectionandAlertConnection, giving clients distribution counts to build filter UIs without extra round-trips.Changes
Issues — SQL-aggregate pattern
IssueConnectiongains an optionalfacetsfield:New supporting types:
IssueSeverityFacetItem,IssueResourceTypeFacetItem,IssueTypeFacetItem.FacetsForIssuesaggregate query groups by(severity, resource_type, env, issue_type)and returnsfiltered_countper group. The scope params (scope_resource_type,scope_resource_name,scope_env) are applied in the outerWHEREso only groups that exist within the connection scope appear in the result; user filter params (env[],severity,issue_type,filter_resource_type,filter_resource_name) go insideCOUNT(*) FILTER (...). Single extra DB query, executed only whenfacetsis requested.IssueConnection– changed from a type alias to a struct (likeActivityLogEntryConnection) carryingteamSlug,scope, andfilterfor lazy facet computation in the resolver.IssueScope– separates the fixed resource context (set by the caller, e.g.application.issuesfixesresourceType,resourceName,env) from the user-controlledIssueFilter. Scope params narrow the base dataset; filter params narrow the counted set.ComputeFacets– sumsfilteredCountper dimension; dimension values withfilteredCount = 0are still present because the scope WHERE ensures every group that exists in the scope seeds the map.Issuesresolvers (team + five resource-level) updated.Alerts — in-memory pattern
AlertConnectiongains an optionalfacetsfield:New supporting type:
AlertStateFacetItem.AlertConnection– changed frompagination.Connectiontopagination.FacetableConnection, following the same pattern as applications, OpenSearch, and Valkey.AlertFacets– computes environment and state distribution counts in-memory over all alerts in the connection. Usessync.Onceto avoid recomputing the filtered set.Alertsresolvers (teamResolver,teamEnvironmentResolver) now useSortFilter.PaginatedList.Tests
Issues —
TestBuildFacetscovers three cases:0Alerts —
TestAlertFacets_EnvironmentsandTestAlertFacets_Statescover:0Design choices
activitylogpattern and ensures facet items are never missing due to the current filter.resourceNameis not a facet dimension — it is a free-text search field, not a categorical one.