diff --git a/changelog/18452.txt b/changelog/18452.txt new file mode 100644 index 000000000..6d4566667 --- /dev/null +++ b/changelog/18452.txt @@ -0,0 +1,3 @@ +```release-note:bug +core/activity: de-duplicate namespaces when historical and current month data are mixed +``` diff --git a/vault/activity_log.go b/vault/activity_log.go index 970cef996..24df0213e 100644 --- a/vault/activity_log.go +++ b/vault/activity_log.go @@ -1590,8 +1590,27 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T return nil, err } - // Add the current month's namespace data the precomputed query namespaces - byNamespaceResponse = append(byNamespaceResponse, byNamespaceResponseCurrent...) + // Create a mapping of namespace id to slice index, so that we can efficiently update our results without + // having to traverse the entire namespace response slice every time. + nsrMap := make(map[string]int) + for i, nr := range byNamespaceResponse { + nsrMap[nr.NamespaceID] = i + } + + // Rather than blindly appending, which will create duplicates, check our existing counts against the current + // month counts, and append or update as necessary. + for _, nrc := range byNamespaceResponseCurrent { + if ndx, ok := nsrMap[nrc.NamespaceID]; ok { + existingRecord := byNamespaceResponse[ndx] + existingRecord.Counts.EntityClients += nrc.Counts.EntityClients + existingRecord.Counts.Clients += nrc.Counts.Clients + existingRecord.Counts.DistinctEntities += nrc.Counts.DistinctEntities + existingRecord.Counts.NonEntityClients += nrc.Counts.NonEntityClients + existingRecord.Counts.NonEntityTokens += nrc.Counts.NonEntityTokens + } else { + byNamespaceResponse = append(byNamespaceResponse, nrc) + } + } } // Sort clients within each namespace diff --git a/vault/logical_system_activity.go b/vault/logical_system_activity.go index 7b8d67513..4d743379d 100644 --- a/vault/logical_system_activity.go +++ b/vault/logical_system_activity.go @@ -161,6 +161,7 @@ func parseStartEndTimes(a *ActivityLog, d *framework.FieldData) (time.Time, time return startTime, endTime, nil } +// This endpoint is not used by the UI. The UI's "export" feature is entirely client-side. func (b *SystemBackend) handleClientExport(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { a := b.Core.activityLog if a == nil {