mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Add count to host upcoming activities API response (#16511)
This commit is contained in:
parent
379ab87805
commit
424ffef185
2
changes/16426-add-upcoming-activity-count
Normal file
2
changes/16426-add-upcoming-activity-count
Normal file
@ -0,0 +1,2 @@
|
||||
- Updated `GET /api/v1/fleet/hosts/:id/activities/upcoming` response to include the count of all
|
||||
upcoming activities for the host.
|
@ -186,6 +186,16 @@ func (ds *Datastore) MarkActivitiesAsStreamed(ctx context.Context, activityIDs [
|
||||
}
|
||||
|
||||
func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint, opt fleet.ListOptions) ([]*fleet.Activity, *fleet.PaginationMetadata, error) {
|
||||
const countStmt = `SELECT COUNT(*) FROM host_script_results WHERE host_id = ? AND exit_code IS NULL`
|
||||
var count uint
|
||||
if err := sqlx.GetContext(ctx, ds.reader(ctx), &count, countStmt, hostID); err != nil {
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "count upcoming activities")
|
||||
}
|
||||
if count == 0 {
|
||||
return []*fleet.Activity{}, &fleet.PaginationMetadata{}, nil
|
||||
}
|
||||
|
||||
// NOTE: Be sure to update both the count and list statements if the list query is modified
|
||||
const listStmt = `
|
||||
SELECT
|
||||
hsr.execution_id as uuid,
|
||||
@ -229,12 +239,10 @@ func (ds *Datastore) ListHostUpcomingActivities(ctx context.Context, hostID uint
|
||||
}
|
||||
|
||||
var metaData *fleet.PaginationMetadata
|
||||
if opt.IncludeMetadata {
|
||||
metaData = &fleet.PaginationMetadata{HasPreviousResults: opt.Page > 0}
|
||||
if len(activities) > int(opt.PerPage) {
|
||||
metaData.HasNextResults = true
|
||||
activities = activities[:len(activities)-1]
|
||||
}
|
||||
metaData = &fleet.PaginationMetadata{HasPreviousResults: opt.Page > 0, TotalResults: count}
|
||||
if len(activities) > int(opt.PerPage) {
|
||||
metaData.HasNextResults = true
|
||||
activities = activities[:len(activities)-1]
|
||||
}
|
||||
|
||||
return activities, metaData, nil
|
||||
|
@ -375,49 +375,49 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
|
||||
opts: fleet.ListOptions{PerPage: 2},
|
||||
hostID: h1.ID,
|
||||
wantExecs: []string{h1A, h1B},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: false},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: false, TotalResults: 5},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{Page: 1, PerPage: 2},
|
||||
hostID: h1.ID,
|
||||
wantExecs: []string{h1C, h1D},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: true},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: true, TotalResults: 5},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{Page: 2, PerPage: 2},
|
||||
hostID: h1.ID,
|
||||
wantExecs: []string{h1E},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 5},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{PerPage: 3},
|
||||
hostID: h1.ID,
|
||||
wantExecs: []string{h1A, h1B, h1C},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: false},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: true, HasPreviousResults: false, TotalResults: 5},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{Page: 1, PerPage: 3},
|
||||
hostID: h1.ID,
|
||||
wantExecs: []string{h1D, h1E},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 5},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{Page: 2, PerPage: 3},
|
||||
hostID: h1.ID,
|
||||
wantExecs: []string{},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: true, TotalResults: 5},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{PerPage: 3},
|
||||
hostID: h2.ID,
|
||||
wantExecs: []string{h2A},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false, TotalResults: 1},
|
||||
},
|
||||
{
|
||||
opts: fleet.ListOptions{},
|
||||
hostID: h3.ID,
|
||||
wantExecs: []string{},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false},
|
||||
wantMeta: &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false, TotalResults: 0},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
|
@ -8,4 +8,8 @@ type ObjectMetadata struct {
|
||||
type PaginationMetadata struct {
|
||||
HasNextResults bool `json:"has_next_results"`
|
||||
HasPreviousResults bool `json:"has_previous_results"`
|
||||
// TotalResults is the total number of results found for the query (as opposed to the number
|
||||
// of results returned in the current paginated response). This field is not always set so callers
|
||||
// must take care to confirm whether a non-zero value should be expected in their specific use cases.
|
||||
TotalResults uint `json:"-"`
|
||||
}
|
||||
|
@ -538,7 +538,8 @@ type Service interface {
|
||||
|
||||
// ListHostUpcomingActivities lists the upcoming activities for the specified
|
||||
// host. Those are activities that are queued or scheduled to run on the host
|
||||
// but haven't run yet.
|
||||
// but haven't run yet. It also returns the total (unpaginated) count of upcoming
|
||||
// activities.
|
||||
ListHostUpcomingActivities(ctx context.Context, hostID uint, opt ListOptions) ([]*Activity, *PaginationMetadata, error)
|
||||
|
||||
// ListHostPastActivities lists the activities that have already happened for the specified host.
|
||||
|
@ -56,14 +56,23 @@ type listHostUpcomingActivitiesRequest struct {
|
||||
ListOptions fleet.ListOptions `url:"list_options"`
|
||||
}
|
||||
|
||||
type listHostUpcomingActivitiesResponse struct {
|
||||
Meta *fleet.PaginationMetadata `json:"meta"`
|
||||
Activities []*fleet.Activity `json:"activities"`
|
||||
Count uint `json:"count"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r listHostUpcomingActivitiesResponse) error() error { return r.Err }
|
||||
|
||||
func listHostUpcomingActivitiesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||
req := request.(*listHostUpcomingActivitiesRequest)
|
||||
acts, meta, err := svc.ListHostUpcomingActivities(ctx, req.HostID, req.ListOptions)
|
||||
if err != nil {
|
||||
return listActivitiesResponse{Err: err}, nil
|
||||
return listHostUpcomingActivitiesResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return listActivitiesResponse{Meta: meta, Activities: acts}, nil
|
||||
return listHostUpcomingActivitiesResponse{Meta: meta, Activities: acts, Count: meta.TotalResults}, nil
|
||||
}
|
||||
|
||||
// ListHostUpcomingActivities returns a slice of upcoming activities for the
|
||||
|
@ -9849,10 +9849,11 @@ func (s *integrationTestSuite) TestListHostUpcomingActivities() {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("%#v", c.queries), func(t *testing.T) {
|
||||
var listResp listActivitiesResponse
|
||||
var listResp listHostUpcomingActivitiesResponse
|
||||
queryArgs := c.queries
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/activities/upcoming", host1.ID), nil, http.StatusOK, &listResp, queryArgs...)
|
||||
|
||||
require.Equal(t, uint(5), listResp.Count)
|
||||
require.Equal(t, len(c.wantExecs), len(listResp.Activities))
|
||||
require.Equal(t, c.wantMeta, listResp.Meta)
|
||||
|
||||
@ -9873,4 +9874,24 @@ func (s *integrationTestSuite) TestListHostUpcomingActivities() {
|
||||
require.Equal(t, c.wantExecs, gotExecs)
|
||||
})
|
||||
}
|
||||
|
||||
// Test with a host that has no upcoming activities
|
||||
host2, err := s.ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now().Add(-1 * time.Minute),
|
||||
OsqueryHostID: ptr.String(t.Name() + "2"),
|
||||
NodeKey: ptr.String(t.Name() + "2"),
|
||||
UUID: uuid.New().String(),
|
||||
Hostname: fmt.Sprintf("%sfoo2.local", t.Name()),
|
||||
Platform: "darwin",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var listResp listHostUpcomingActivitiesResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/activities/upcoming", host2.ID), nil, http.StatusOK, &listResp)
|
||||
require.Equal(t, uint(0), listResp.Count)
|
||||
require.Empty(t, listResp.Activities)
|
||||
require.Equal(t, &fleet.PaginationMetadata{HasNextResults: false, HasPreviousResults: false}, listResp.Meta)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user