Tenant activity log with facets and pagination#477
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a tenant-scoped activity log GraphQL query that supports cursor pagination, facet aggregation (including teams), and time-range filtering, backed by new SQL queries and regenerated gqlgen/sqlc outputs. The PR also tightens some service account creation behavior and updates generated/activity log transformation code.
Changes:
- Introduce
tenantActivityLogGraphQL query with cursor pagination andActivityLogFiltertime range (from/to). - Add tenant-scoped activity log SQL query and extend facet computation to include team distribution.
- Add new authorization-check SQL helpers and adjust service account creation validation + integration tests.
Reviewed changes
Copilot reviewed 16 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/workload/config/queries.go | Simplifies map merge logic using maps.Copy for updated-fields comparison. |
| internal/vulnerability/activitylog.go | Handles empty entry.Data for vulnerability update log entries. |
| internal/serviceaccount/queries.go | Validates TeamSlug existence on create; replaces TODOs with function doc comments. |
| internal/graph/schema/activitylog.graphqls | Adds tenantActivityLog query, from/to filters, and teams facet. |
| internal/graph/gengql/schema.generated.go | Regenerated gqlgen schema bindings for new query/filter/facet. |
| internal/graph/gengql/root_.generated.go | Regenerated gqlgen complexity root and embedded schema source updates. |
| internal/graph/gengql/complexity.go | Adds complexity calculation for tenantActivityLog. |
| internal/graph/gengql/activitylog.generated.go | Regenerated gqlgen resolvers/unmarshal for filter + facets. |
| internal/graph/activitylog.resolvers.go | Adds query resolver that calls into tenant activity log listing. |
| internal/auth/authz/queries/roles.sql | Adds HasAnyAuthorization and ServiceAccountHasAnyAuthorization queries. |
| internal/auth/authz/queries.go | Adds CanReadActivityLogs helper using the new SQL queries. |
| internal/auth/authz/authzsql/roles.sql.go | Regenerated sqlc code for the new authz queries. |
| internal/auth/authz/authzsql/querier.go | Extends authz sqlc querier interface with new methods. |
| internal/activitylog/queries/activitylog.sql | Adds tenant listing query; adds time filtering; adds team slug to facets query. |
| internal/activitylog/queries.go | Adds ListForTenant and extends existing list queries with from/to. |
| internal/activitylog/model.go | Extends models for tenant scope, team facets, and time filter fields. |
| internal/activitylog/filter.go | Adds pgtype conversion helpers for from/to. |
| internal/activitylog/facets.go | Adds teams facet aggregation and applies time filtering to facets query. |
| internal/activitylog/activitylogsql/querier.go | Extends activitylog sqlc querier interface with ListForTenant. |
| internal/activitylog/activitylogsql/activitylog.sql.go | Regenerated sqlc code for tenant listing, facets team slug, and time filters. |
| integration_tests/tenant_activitylog.lua | Adds integration coverage for tenant activity log pagination/facets/time filtering. |
| integration_tests/serviceaccounts_queries.lua | Updates expected error message for non-existent team on service account create. |
| go.sum | Updates module sums following dependency/version changes. |
| go.mod | Bumps indirect go.etcd.io/bbolt dependency. |
Files not reviewed (7)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/auth/authz/authzsql/querier.go: Generated file
- internal/auth/authz/authzsql/roles.sql.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
dc4e835 to
3557905
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 24 changed files in this pull request and generated 1 comment.
Files not reviewed (7)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/auth/authz/authzsql/querier.go: Generated file
- internal/auth/authz/authzsql/roles.sql.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
Comments suppressed due to low confidence (1)
internal/activitylog/model.go:52
ActivityLogScopegained aTenantflag, but it is never read anywhere (and tenant-wide queries would work the same with a nil/empty scope becauseComputeFacetsalready treats nil scope as “no additional constraints”). Keeping an unused field makes it unclear whether tenant scoping is supposed to influence facets/queries.
Consider either removing the Tenant field, or using it in ComputeFacets/SQL to enforce tenant-specific behavior (if that’s the intent).
// ActivityLogScope defines the base scope for an activity log query.
type ActivityLogScope struct {
Tenant bool
TeamSlug *slug.Slug
ResourceType *string
ResourceName *string
EnvironmentName *string
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 20 changed files in this pull request and generated no new comments.
Files not reviewed (5)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
Comments suppressed due to low confidence (2)
internal/serviceaccount/queries.go:89
- The team existence check happens before starting the DB transaction. If the team is deleted between this check and the INSERT, the INSERT can still fail with a FK violation and surface as a generic database error to GraphQL clients (undoing the intent of returning a friendly “team not found” message). Move the validation into the transaction so it runs on the same connection/context as the INSERT and reduces the race window.
func Create(ctx context.Context, input CreateServiceAccountInput) (*ServiceAccount, error) {
if err := authz.CanCreateServiceAccounts(ctx, input.TeamSlug); err != nil {
return nil, err
}
if input.TeamSlug != nil {
if _, err := team.Get(ctx, *input.TeamSlug); err != nil {
return nil, err
}
}
var sa *serviceaccountsql.ServiceAccount
err := database.Transaction(ctx, func(ctx context.Context) error {
var err error
sa, err = db(ctx).Create(ctx, serviceaccountsql.CreateParams{
Name: input.Name,
Description: input.Description,
TeamSlug: input.TeamSlug,
})
if err != nil {
internal/activitylog/model.go:52
ActivityLogScope.Tenantis set when building a tenant-scoped connection, but it is not referenced anywhere (facets and list queries key off the other scope fields only). Keeping an unused scope flag makes the scoping rules harder to understand and can drift from actual behavior. Either remove it (and the assignment in ListForTenant) or start using it where scope is interpreted (e.g., facets/list selection) so it has an observable effect.
// ActivityLogScope defines the base scope for an activity log query.
type ActivityLogScope struct {
Tenant bool
TeamSlug *slug.Slug
ResourceType *string
ResourceName *string
EnvironmentName *string
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 20 changed files in this pull request and generated 1 comment.
Files not reviewed (5)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 19 changed files in this pull request and generated no new comments.
Files not reviewed (5)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
40e88b9 to
371008a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 20 changed files in this pull request and generated 2 comments.
Files not reviewed (5)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 19 changed files in this pull request and generated no new comments.
Files not reviewed (5)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
Deleted 554 corrupt activity_log_entries rows where resource_type='VULNERABILITY' and data is null. Now the schema constraint (data: VulnerabilityActivityLogEntryData!) is enforced genuinely instead of being paper-over-ed with empty structs.
…e unused member variable
Previously GROUP BY (resource_type, action, team_slug, environment) produced O(teams × resource_types × actions × environments) rows. With 200+ teams this could easily be 100k+ rows per request. Now uses two separate queries: - FacetsForActivityTypes: groups by (resource_type, action, environment) - FacetsForTeams: groups by (team_slug) only, skipped for non-tenant scope Worst case is now O(resource_types × actions × environments) + O(teams).
…om migrations 0011 and 0037
Upgrade semconv from v1.40.0 to v1.41.0 in http.go, metrics.go and pubsub.go to match otel/sdk which uses schema v1.41.0, resolving startup error: 'conflicting Schema URL: https://opentelemetry.io/schemas/1.41.0 and 1.40.0'
77911b6 to
208c3bf
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 19 changed files in this pull request and generated 2 comments.
Files not reviewed (5)
- internal/activitylog/activitylogsql/activitylog.sql.go: Generated file
- internal/activitylog/activitylogsql/querier.go: Generated file
- internal/graph/gengql/activitylog.generated.go: Generated file
- internal/graph/gengql/complexity.go: Generated file
- internal/graph/gengql/schema.generated.go: Generated file
Replace team.Get() check before INSERT with FK violation detection after INSERT. team.Get() uses the team dataloader which requires the loader to be set up in the context (panics otherwise) and also runs against the connection pool rather than the transaction. Detecting the FK violation (SQLSTATE 23503) after the INSERT is both safer and idiomatic.
Adds a tenant-level activity log query with pagination, facets, and time filtering.
What's included
Tenant activity log
activityLogroot query (mirrors the existing team-level activity log)PageInfofrom/toparametersPerformance
FacetsForActivityTypesquery grouping by(resource_type, action, environment)— avoids the previous cross-product cardinality explosion that would have been O(teams × resource_types × actions × environments) with 200+ teamsData integrity
activity_log_entriesrows whereresource_type = 'VULNERABILITY'anddata IS NULLOther fixes
CanReadActivityLogs,HasAnyAuthorization)semconvimport fromv1.40.0→v1.41.0to resolve conflicting OTel schema URL at startupgodirective to1.26.4govulncheckset tocontinue-on-errorin CI due to GO-2026-5662 (no upstream fix available)Validation
mise run checkpasses (staticcheck, gosec, helm lint)mise run generateis clean)