From 9027a81b915de2fac99f76b4d8de0a56037f309b Mon Sep 17 00:00:00 2001 From: Christer Edvartsen Date: Thu, 10 Sep 2026 13:53:11 +0200 Subject: [PATCH] feat: log Kafka topic grant updates --- integration_tests/update_kafka_topic.lua | 110 ++++ .../graph/gengql/activitylog.generated.go | 7 + internal/graph/gengql/kafka.generated.go | 548 ++++++++++++++++++ internal/graph/gengql/root_.generated.go | 194 +++++++ internal/graph/gengql/schema.generated.go | 7 + internal/graph/schema/kafka.graphqls | 51 ++ .../persistence/kafkatopic/activitylog.go | 28 + internal/persistence/kafkatopic/queries.go | 27 + 8 files changed, 972 insertions(+) diff --git a/integration_tests/update_kafka_topic.lua b/integration_tests/update_kafka_topic.lua index 0f6079eea..14527b635 100644 --- a/integration_tests/update_kafka_topic.lua +++ b/integration_tests/update_kafka_topic.lua @@ -87,6 +87,59 @@ Test.k8s("Kafka topic contains added grants", function(t) }) end) +Test.gql("update Kafka topic creates activity log entry for added grants", function(t) + t.addHeader("x-user-email", user:email()) + + t.query(string.format([[ + { + team(slug: "%s") { + activityLog(first: 10, filter: { activityTypes: [KAFKA_TOPIC_UPDATED] }) { + nodes { + __typename + message + actor + resourceType + resourceName + environmentName + ... on KafkaTopicUpdatedActivityLogEntry { + data { + addedGrants { subject teamName access } + revokedGrants { subject teamName access } + } + } + } + } + } + } + ]], team:slug())) + + t.check { + data = { + team = { + activityLog = { + nodes = { + { + __typename = "KafkaTopicUpdatedActivityLogEntry", + message = "Updated Kafka topic", + actor = user:email(), + resourceType = "KAFKA_TOPIC", + resourceName = "orders", + environmentName = "dev", + data = { + addedGrants = { + { subject = "orders-api", teamName = "consumer-team", access = "READ" }, + { subject = "orders-writer", teamName = "consumer-team", access = "READWRITE" }, + }, + revokedGrants = {}, + }, + }, + }, + }, + }, + }, + } +end) + Test.gql("update Kafka topic ignores existing grants", function(t) t.addHeader("x-user-email", user:email()) @@ -261,6 +314,63 @@ Test.k8s("Kafka topic excludes revoked grant", function(t) }) end) +Test.gql("update Kafka topic creates activity log entry for revoked grants", function(t) + t.addHeader("x-user-email", user:email()) + + t.query(string.format([[ + { + team(slug: "%s") { + activityLog(first: 10, filter: { activityTypes: [KAFKA_TOPIC_UPDATED] }) { + nodes { + ... on KafkaTopicUpdatedActivityLogEntry { + data { + addedGrants { subject teamName access } + revokedGrants { subject teamName access } + } + } + } + } + } + } + ]], team:slug())) + + t.check { + data = { + team = { + activityLog = { + nodes = { + { + data = { + addedGrants = {}, + revokedGrants = { + { subject = "orders-writer", teamName = "consumer-team", access = "READWRITE" }, + }, + }, + }, + { + data = { + addedGrants = { + { subject = "orders-admin", teamName = "consumer-team", access = "READWRITE" }, + }, + revokedGrants = {}, + }, + }, + { + data = { + addedGrants = { + { subject = "orders-api", teamName = "consumer-team", access = "READ" }, + { subject = "orders-writer", teamName = "consumer-team", access = "READWRITE" }, + }, + revokedGrants = {}, + }, + }, + }, + }, + }, + }, + } +end) + Test.gql("update Kafka topic adds and revokes grants", function(t) t.addHeader("x-user-email", user:email()) diff --git a/internal/graph/gengql/activitylog.generated.go b/internal/graph/gengql/activitylog.generated.go index 048de627f..8152a69b2 100644 --- a/internal/graph/gengql/activitylog.generated.go +++ b/internal/graph/gengql/activitylog.generated.go @@ -831,6 +831,13 @@ func (ec *executionContext) _ActivityLogEntry(ctx context.Context, sel ast.Selec return graphql.Null } return ec._OpenSearchCreatedActivityLogEntry(ctx, sel, obj) + case kafkatopic.KafkaTopicUpdatedActivityLogEntry: + return ec._KafkaTopicUpdatedActivityLogEntry(ctx, sel, &obj) + case *kafkatopic.KafkaTopicUpdatedActivityLogEntry: + if obj == nil { + return graphql.Null + } + return ec._KafkaTopicUpdatedActivityLogEntry(ctx, sel, obj) case kafkatopic.KafkaCredentialsCreatedActivityLogEntry: return ec._KafkaCredentialsCreatedActivityLogEntry(ctx, sel, &obj) case *kafkatopic.KafkaCredentialsCreatedActivityLogEntry: diff --git a/internal/graph/gengql/kafka.generated.go b/internal/graph/gengql/kafka.generated.go index 5918e7d6d..57beca6fc 100644 --- a/internal/graph/gengql/kafka.generated.go +++ b/internal/graph/gengql/kafka.generated.go @@ -1533,6 +1533,355 @@ func (ec *executionContext) fieldContext_KafkaTopicFacets_labels(_ context.Conte return fc, nil } +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_id(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ID(), nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v ident.Ident) graphql.Marshaler { + return ec.marshalNID2githubᚗcomᚋnaisᚋapiᚋinternalᚋgraphᚋidentᚐIdent(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, true, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_actor(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_actor(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Actor, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_actor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_createdAt(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_createdAt(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.CreatedAt, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v time.Time) graphql.Marshaler { + return ec.marshalNTime2timeᚐTime(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, false, false, errors.New("field of type Time does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_message(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_message(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Message, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_resourceType(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_resourceType(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ResourceType, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v activitylog.ActivityLogEntryResourceType) graphql.Marshaler { + return ec.marshalNActivityLogEntryResourceType2githubᚗcomᚋnaisᚋapiᚋinternalᚋactivitylogᚐActivityLogEntryResourceType(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_resourceType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, false, false, errors.New("field of type ActivityLogEntryResourceType does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_resourceName(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_resourceName(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ResourceName, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_resourceName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_teamSlug(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_teamSlug(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.TeamSlug, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *slug.Slug) graphql.Marshaler { + return ec.marshalNSlug2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋslugᚐSlug(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_teamSlug(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, false, false, errors.New("field of type Slug does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_environmentName(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_environmentName(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.EnvironmentName, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *string) graphql.Marshaler { + return ec.marshalOString2ᚖstring(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_environmentName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntry", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry_data(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntry_data(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Data, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *kafkatopic.KafkaTopicUpdatedActivityLogEntryData) graphql.Marshaler { + return ec.marshalNKafkaTopicUpdatedActivityLogEntryData2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicUpdatedActivityLogEntryData(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntry_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "KafkaTopicUpdatedActivityLogEntry", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_KafkaTopicUpdatedActivityLogEntryData(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntryData_addedGrants(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntryData) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryData_addedGrants(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.AddedGrants, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v []kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) graphql.Marshaler { + return ec.marshalNKafkaTopicUpdatedActivityLogEntryDataGrant2ᚕgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicUpdatedActivityLogEntryDataGrantᚄ(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntryData_addedGrants(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "KafkaTopicUpdatedActivityLogEntryData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_KafkaTopicUpdatedActivityLogEntryDataGrant(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntryData_revokedGrants(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntryData) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryData_revokedGrants(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.RevokedGrants, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v []kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) graphql.Marshaler { + return ec.marshalNKafkaTopicUpdatedActivityLogEntryDataGrant2ᚕgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicUpdatedActivityLogEntryDataGrantᚄ(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntryData_revokedGrants(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "KafkaTopicUpdatedActivityLogEntryData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_KafkaTopicUpdatedActivityLogEntryDataGrant(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntryDataGrant_subject(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_subject(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Subject, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_subject(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntryDataGrant", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntryDataGrant_teamName(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_teamName(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.TeamName, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_teamName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntryDataGrant", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntryDataGrant_access(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_access(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Access, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v kafkatopic.KafkaTopicGrantAccess) graphql.Marshaler { + return ec.marshalNKafkaTopicGrantAccess2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicGrantAccess(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_access(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("KafkaTopicUpdatedActivityLogEntryDataGrant", field, false, false, errors.New("field of type KafkaTopicGrantAccess does not have child fields")) +} + func (ec *executionContext) _TeamInventoryCountKafkaTopics_total(ctx context.Context, field graphql.CollectedField, obj *kafkatopic.TeamInventoryCountKafkaTopics) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -2859,6 +3208,175 @@ func (ec *executionContext) _KafkaTopicFacets(ctx context.Context, sel ast.Selec return out } +var kafkaTopicUpdatedActivityLogEntryImplementors = []string{"KafkaTopicUpdatedActivityLogEntry", "ActivityLogEntry", "Node"} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntry(ctx context.Context, sel ast.SelectionSet, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntry) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, kafkaTopicUpdatedActivityLogEntryImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("KafkaTopicUpdatedActivityLogEntry") + case "id": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "actor": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_actor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createdAt": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "message": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_message(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "resourceType": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_resourceType(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "resourceName": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_resourceName(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "teamSlug": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_teamSlug(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "environmentName": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_environmentName(ctx, field, obj) + case "data": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntry_data(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var kafkaTopicUpdatedActivityLogEntryDataImplementors = []string{"KafkaTopicUpdatedActivityLogEntryData"} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntryData(ctx context.Context, sel ast.SelectionSet, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntryData) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, kafkaTopicUpdatedActivityLogEntryDataImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("KafkaTopicUpdatedActivityLogEntryData") + case "addedGrants": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntryData_addedGrants(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "revokedGrants": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntryData_revokedGrants(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var kafkaTopicUpdatedActivityLogEntryDataGrantImplementors = []string{"KafkaTopicUpdatedActivityLogEntryDataGrant"} + +func (ec *executionContext) _KafkaTopicUpdatedActivityLogEntryDataGrant(ctx context.Context, sel ast.SelectionSet, obj *kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, kafkaTopicUpdatedActivityLogEntryDataGrantImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("KafkaTopicUpdatedActivityLogEntryDataGrant") + case "subject": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntryDataGrant_subject(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "teamName": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntryDataGrant_teamName(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "access": + out.Values[i] = ec._KafkaTopicUpdatedActivityLogEntryDataGrant_access(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferred), math.MaxInt32))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var teamInventoryCountKafkaTopicsImplementors = []string{"TeamInventoryCountKafkaTopics"} func (ec *executionContext) _TeamInventoryCountKafkaTopics(ctx context.Context, sel ast.SelectionSet, obj *kafkatopic.TeamInventoryCountKafkaTopics) graphql.Marshaler { @@ -3139,6 +3657,36 @@ func (ec *executionContext) marshalNKafkaTopicOrderField2githubᚗcomᚋnaisᚋa return v } +func (ec *executionContext) marshalNKafkaTopicUpdatedActivityLogEntryData2ᚖgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicUpdatedActivityLogEntryData(ctx context.Context, sel ast.SelectionSet, v *kafkatopic.KafkaTopicUpdatedActivityLogEntryData) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._KafkaTopicUpdatedActivityLogEntryData(ctx, sel, v) +} + +func (ec *executionContext) marshalNKafkaTopicUpdatedActivityLogEntryDataGrant2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicUpdatedActivityLogEntryDataGrant(ctx context.Context, sel ast.SelectionSet, v kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) graphql.Marshaler { + return ec._KafkaTopicUpdatedActivityLogEntryDataGrant(ctx, sel, &v) +} + +func (ec *executionContext) marshalNKafkaTopicUpdatedActivityLogEntryDataGrant2ᚕgithubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicUpdatedActivityLogEntryDataGrantᚄ(ctx context.Context, sel ast.SelectionSet, v []kafkatopic.KafkaTopicUpdatedActivityLogEntryDataGrant) graphql.Marshaler { + ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler { + fc := graphql.GetFieldContext(ctx) + fc.Result = &v[i] + return ec.marshalNKafkaTopicUpdatedActivityLogEntryDataGrant2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐKafkaTopicUpdatedActivityLogEntryDataGrant(ctx, sel, v[i]) + }) + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalNTeamInventoryCountKafkaTopics2githubᚗcomᚋnaisᚋapiᚋinternalᚋpersistenceᚋkafkatopicᚐTeamInventoryCountKafkaTopics(ctx context.Context, sel ast.SelectionSet, v kafkatopic.TeamInventoryCountKafkaTopics) graphql.Marshaler { return ec._TeamInventoryCountKafkaTopics(ctx, sel, &v) } diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go index b44067a35..e6d5e46da 100644 --- a/internal/graph/gengql/root_.generated.go +++ b/internal/graph/gengql/root_.generated.go @@ -1475,6 +1475,29 @@ type ComplexityRoot struct { Pools func(childComplexity int) int } + KafkaTopicUpdatedActivityLogEntry struct { + Actor func(childComplexity int) int + CreatedAt func(childComplexity int) int + Data func(childComplexity int) int + EnvironmentName func(childComplexity int) int + ID func(childComplexity int) int + Message func(childComplexity int) int + ResourceName func(childComplexity int) int + ResourceType func(childComplexity int) int + TeamSlug func(childComplexity int) int + } + + KafkaTopicUpdatedActivityLogEntryData struct { + AddedGrants func(childComplexity int) int + RevokedGrants func(childComplexity int) int + } + + KafkaTopicUpdatedActivityLogEntryDataGrant struct { + Access func(childComplexity int) int + Subject func(childComplexity int) int + TeamName func(childComplexity int) int + } + LabelFacetItem struct { Count func(childComplexity int) int Key func(childComplexity int) int @@ -9183,6 +9206,104 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.KafkaTopicFacets.Pools(childComplexity), true + case "KafkaTopicUpdatedActivityLogEntry.actor": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.Actor == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.Actor(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.createdAt": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.CreatedAt == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.CreatedAt(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.data": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.Data == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.Data(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.environmentName": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.EnvironmentName == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.EnvironmentName(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.id": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.ID == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.ID(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.message": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.Message == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.Message(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.resourceName": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.ResourceName == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.ResourceName(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.resourceType": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.ResourceType == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.ResourceType(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntry.teamSlug": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.TeamSlug == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntry.TeamSlug(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntryData.addedGrants": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryData.AddedGrants == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryData.AddedGrants(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntryData.revokedGrants": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryData.RevokedGrants == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryData.RevokedGrants(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntryDataGrant.access": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryDataGrant.Access == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryDataGrant.Access(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntryDataGrant.subject": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryDataGrant.Subject == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryDataGrant.Subject(childComplexity), true + + case "KafkaTopicUpdatedActivityLogEntryDataGrant.teamName": + if e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryDataGrant.TeamName == nil { + break + } + + return e.ComplexityRoot.KafkaTopicUpdatedActivityLogEntryDataGrant.TeamName(childComplexity), true + case "LabelFacetItem.count": if e.ComplexityRoot.LabelFacetItem.Count == nil { break @@ -25174,9 +25295,60 @@ type KafkaCredentialsCreatedActivityLogEntryData { ttl: String! } +type KafkaTopicUpdatedActivityLogEntry implements ActivityLogEntry & Node { + "ID of the entry." + id: ID! + + "The identity of the actor who performed the action." + actor: String! + + "Creation time of the entry." + createdAt: Time! + + "Message that summarizes the entry." + message: String! + + "Type of the resource that was affected by the action." + resourceType: ActivityLogEntryResourceType! + + "Name of the resource that was affected by the action." + resourceName: String! + + "The team slug that the entry belongs to." + teamSlug: Slug! + + "The environment name that the entry belongs to." + environmentName: String + + "Data associated with the update." + data: KafkaTopicUpdatedActivityLogEntryData! +} + +type KafkaTopicUpdatedActivityLogEntryData { + "Grants added to the Kafka topic." + addedGrants: [KafkaTopicUpdatedActivityLogEntryDataGrant!]! + + "Grants revoked from the Kafka topic." + revokedGrants: [KafkaTopicUpdatedActivityLogEntryDataGrant!]! +} + +type KafkaTopicUpdatedActivityLogEntryDataGrant { + "Subject affected by the grant." + subject: String! + + "Team affected by the grant." + teamName: String! + + "Access level affected by the grant." + access: KafkaTopicGrantAccess! +} + extend enum ActivityLogActivityType { "Filter for Kafka credential creation events." KAFKA_CREDENTIALS_CREATED + + "Filter for Kafka topic update events." + KAFKA_TOPIC_UPDATED } extend enum ActivityLogEntryResourceType { @@ -35661,6 +35833,28 @@ func (ec *executionContext) childFields_KafkaTopicFacets(ctx context.Context, fi return nil, fmt.Errorf("no field named %q was found under type KafkaTopicFacets", field.Name) } +func (ec *executionContext) childFields_KafkaTopicUpdatedActivityLogEntryData(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "addedGrants": + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryData_addedGrants(ctx, field) + case "revokedGrants": + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryData_revokedGrants(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type KafkaTopicUpdatedActivityLogEntryData", field.Name) +} + +func (ec *executionContext) childFields_KafkaTopicUpdatedActivityLogEntryDataGrant(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "subject": + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_subject(ctx, field) + case "teamName": + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_teamName(ctx, field) + case "access": + return ec.fieldContext_KafkaTopicUpdatedActivityLogEntryDataGrant_access(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type KafkaTopicUpdatedActivityLogEntryDataGrant", field.Name) +} + func (ec *executionContext) childFields_LabelFacetItem(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "key": diff --git a/internal/graph/gengql/schema.generated.go b/internal/graph/gengql/schema.generated.go index 50ced59ca..b250f46cd 100644 --- a/internal/graph/gengql/schema.generated.go +++ b/internal/graph/gengql/schema.generated.go @@ -6623,6 +6623,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._LastRunFailedIssue(ctx, sel, obj) + case kafkatopic.KafkaTopicUpdatedActivityLogEntry: + return ec._KafkaTopicUpdatedActivityLogEntry(ctx, sel, &obj) + case *kafkatopic.KafkaTopicUpdatedActivityLogEntry: + if obj == nil { + return graphql.Null + } + return ec._KafkaTopicUpdatedActivityLogEntry(ctx, sel, obj) case kafkatopic.KafkaTopic: return ec._KafkaTopic(ctx, sel, &obj) case *kafkatopic.KafkaTopic: diff --git a/internal/graph/schema/kafka.graphqls b/internal/graph/schema/kafka.graphqls index 392b6b1d9..55acd2746 100644 --- a/internal/graph/schema/kafka.graphqls +++ b/internal/graph/schema/kafka.graphqls @@ -305,9 +305,60 @@ type KafkaCredentialsCreatedActivityLogEntryData { ttl: String! } +type KafkaTopicUpdatedActivityLogEntry implements ActivityLogEntry & Node { + "ID of the entry." + id: ID! + + "The identity of the actor who performed the action." + actor: String! + + "Creation time of the entry." + createdAt: Time! + + "Message that summarizes the entry." + message: String! + + "Type of the resource that was affected by the action." + resourceType: ActivityLogEntryResourceType! + + "Name of the resource that was affected by the action." + resourceName: String! + + "The team slug that the entry belongs to." + teamSlug: Slug! + + "The environment name that the entry belongs to." + environmentName: String + + "Data associated with the update." + data: KafkaTopicUpdatedActivityLogEntryData! +} + +type KafkaTopicUpdatedActivityLogEntryData { + "Grants added to the Kafka topic." + addedGrants: [KafkaTopicUpdatedActivityLogEntryDataGrant!]! + + "Grants revoked from the Kafka topic." + revokedGrants: [KafkaTopicUpdatedActivityLogEntryDataGrant!]! +} + +type KafkaTopicUpdatedActivityLogEntryDataGrant { + "Subject affected by the grant." + subject: String! + + "Team affected by the grant." + teamName: String! + + "Access level affected by the grant." + access: KafkaTopicGrantAccess! +} + extend enum ActivityLogActivityType { "Filter for Kafka credential creation events." KAFKA_CREDENTIALS_CREATED + + "Filter for Kafka topic update events." + KAFKA_TOPIC_UPDATED } extend enum ActivityLogEntryResourceType { diff --git a/internal/persistence/kafkatopic/activitylog.go b/internal/persistence/kafkatopic/activitylog.go index 6b0b9b807..4768d9961 100644 --- a/internal/persistence/kafkatopic/activitylog.go +++ b/internal/persistence/kafkatopic/activitylog.go @@ -13,6 +13,16 @@ const ( func init() { activitylog.RegisterTransformer(ActivityLogEntryResourceTypeKafkaTopic, func(entry activitylog.GenericActivityLogEntry) (activitylog.ActivityLogEntry, error) { switch entry.Action { + case activitylog.ActivityLogEntryActionUpdated: + data, err := activitylog.UnmarshalData[KafkaTopicUpdatedActivityLogEntryData](entry) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal Kafka topic updated activity log entry data: %w", err) + } + + return KafkaTopicUpdatedActivityLogEntry{ + GenericActivityLogEntry: entry.WithMessage("Updated Kafka topic"), + Data: data, + }, nil case activitylog.ActivityLogEntryActionCredentialsCreated: data, err := activitylog.UnmarshalData[KafkaCredentialsCreatedActivityLogEntryData](entry) if err != nil { @@ -32,6 +42,24 @@ func init() { }) activitylog.RegisterFilter("KAFKA_CREDENTIALS_CREATED", activitylog.ActivityLogEntryActionCredentialsCreated, ActivityLogEntryResourceTypeKafkaTopic) + activitylog.RegisterFilter("KAFKA_TOPIC_UPDATED", activitylog.ActivityLogEntryActionUpdated, ActivityLogEntryResourceTypeKafkaTopic) +} + +type KafkaTopicUpdatedActivityLogEntry struct { + activitylog.GenericActivityLogEntry + + Data *KafkaTopicUpdatedActivityLogEntryData `json:"data"` +} + +type KafkaTopicUpdatedActivityLogEntryData struct { + AddedGrants []KafkaTopicUpdatedActivityLogEntryDataGrant `json:"addedGrants"` + RevokedGrants []KafkaTopicUpdatedActivityLogEntryDataGrant `json:"revokedGrants"` +} + +type KafkaTopicUpdatedActivityLogEntryDataGrant struct { + Subject string `json:"subject"` + TeamName string `json:"teamName"` + Access KafkaTopicGrantAccess `json:"access"` } type KafkaCredentialsCreatedActivityLogEntry struct { diff --git a/internal/persistence/kafkatopic/queries.go b/internal/persistence/kafkatopic/queries.go index 489cb7f7f..85a19c97e 100644 --- a/internal/persistence/kafkatopic/queries.go +++ b/internal/persistence/kafkatopic/queries.go @@ -128,6 +128,7 @@ func Update(ctx context.Context, input UpdateKafkaTopicInput) (*UpdateKafkaTopic patch := []map[string]any{} indicesToRevoke := make([]int, 0, len(input.RevokeGrants)) + revokedGrants := make([]KafkaTopicUpdatedActivityLogEntryDataGrant, 0, len(input.RevokeGrants)) seen := make(map[string]struct{}) for _, grant := range input.RevokeGrants { key := grant.Access.AivenAccess() + "|" + grant.TeamName + "|" + grant.Subject @@ -141,6 +142,11 @@ func Update(ctx context.Context, input UpdateKafkaTopicInput) (*UpdateKafkaTopic }) if aclIndex != -1 { indicesToRevoke = append(indicesToRevoke, aclIndex) + revokedGrants = append(revokedGrants, KafkaTopicUpdatedActivityLogEntryDataGrant{ + Subject: grant.Subject, + TeamName: grant.TeamName, + Access: grant.Access, + }) } } @@ -153,6 +159,7 @@ func Update(ctx context.Context, input UpdateKafkaTopicInput) (*UpdateKafkaTopic } seen = make(map[string]struct{}) + addedGrants := make([]KafkaTopicUpdatedActivityLogEntryDataGrant, 0, len(input.AddGrants)) for _, grant := range input.AddGrants { key := grant.Access.AivenAccess() + "|" + grant.TeamName + "|" + grant.Subject if _, ok := seen[key]; ok { @@ -175,6 +182,11 @@ func Update(ctx context.Context, input UpdateKafkaTopicInput) (*UpdateKafkaTopic "access": grant.Access.AivenAccess(), }, }) + addedGrants = append(addedGrants, KafkaTopicUpdatedActivityLogEntryDataGrant{ + Subject: grant.Subject, + TeamName: grant.TeamName, + Access: grant.Access, + }) } if len(patch) == 0 { @@ -201,6 +213,21 @@ func Update(ctx context.Context, input UpdateKafkaTopicInput) (*UpdateKafkaTopic return nil, err } + if err = activitylog.Create(ctx, activitylog.CreateInput{ + Action: activitylog.ActivityLogEntryActionUpdated, + Actor: authz.ActorFromContext(ctx).User, + ResourceType: ActivityLogEntryResourceTypeKafkaTopic, + ResourceName: input.Name, + TeamSlug: &input.TeamSlug, + EnvironmentName: &input.EnvironmentName, + Data: KafkaTopicUpdatedActivityLogEntryData{ + AddedGrants: addedGrants, + RevokedGrants: revokedGrants, + }, + }); err != nil { + fromContext(ctx).log.WithError(err).Warn("failed to create activity log entry for kafka topic update") + } + return &UpdateKafkaTopicPayload{KafkaTopic: updatedTopic}, nil }