fleet/server/service/global_schedule.go
jacobshandling 2118616f64
21855 – Paginate and filter Queries on the server, update platform filtering from compatible to targeted platforms (#24446)
## Addresses #21855 and all of its subtasks

**Frontend:**
- Update list queries API call to include pagination and filter-related
query params, including new `platform` param for filtering queries by
platforms they've been set to target
- Convert all filtering, sorting, and pagination functionality of the
Manage queries page from client-side to server-side
- Remove unneeded variable declarations / logic
- Various typing and naming improvements

**Server:**
- Add new `platform` `ListQueryOption`
- Update service and datastore level list queries logic to handle
filtering queries by targeted platform
- Update service and datastore level list queries logic to include
`meta` and `count` fields in addition to filtered/paginated queries


- [x] Changes file added for user-visible changes in `changes/`, `
- [x] Added/updated tests
  - [x] update DB, integration
  - [x] add integration (pagination)
  - [x] add integration (platform filter)
  - [x] add DB (pagination)
  - [x] add DB (platform filter)
- [x] Manual QA for all new/changed functionality

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2024-12-11 10:50:28 -08:00

172 lines
5.6 KiB
Go

package service
import (
"context"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
)
////////////////////////////////////////////////////////////////////////////////
// Get Global Schedule
////////////////////////////////////////////////////////////////////////////////
type getGlobalScheduleRequest struct {
ListOptions fleet.ListOptions `url:"list_options"`
}
type getGlobalScheduleResponse struct {
GlobalSchedule []*fleet.ScheduledQuery `json:"global_schedule"`
Err error `json:"error,omitempty"`
}
func (r getGlobalScheduleResponse) error() error { return r.Err }
func getGlobalScheduleEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*getGlobalScheduleRequest)
gp, err := svc.GetGlobalScheduledQueries(ctx, req.ListOptions)
if err != nil {
return getGlobalScheduleResponse{Err: err}, nil
}
return getGlobalScheduleResponse{
GlobalSchedule: gp,
}, nil
}
func (svc *Service) GetGlobalScheduledQueries(ctx context.Context, opts fleet.ListOptions) ([]*fleet.ScheduledQuery, error) {
queries, _, _, err := svc.ListQueries(ctx, opts, nil, ptr.Bool(true), false, nil) // teamID == nil means global
if err != nil {
return nil, err
}
scheduledQueries := make([]*fleet.ScheduledQuery, 0, len(queries))
for _, query := range queries {
scheduledQueries = append(scheduledQueries, fleet.ScheduledQueryFromQuery(query))
}
return scheduledQueries, nil
}
////////////////////////////////////////////////////////////////////////////////
// Schedule a global query
////////////////////////////////////////////////////////////////////////////////
type globalScheduleQueryRequest struct {
QueryID uint `json:"query_id"`
Interval uint `json:"interval"`
Snapshot *bool `json:"snapshot"`
Removed *bool `json:"removed"`
Platform *string `json:"platform"`
Version *string `json:"version"`
Shard *uint `json:"shard"`
}
type globalScheduleQueryResponse struct {
Scheduled *fleet.ScheduledQuery `json:"scheduled,omitempty"`
Err error `json:"error,omitempty"`
}
func (r globalScheduleQueryResponse) error() error { return r.Err }
func globalScheduleQueryEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*globalScheduleQueryRequest)
scheduled, err := svc.GlobalScheduleQuery(ctx, &fleet.ScheduledQuery{
QueryID: req.QueryID,
Interval: req.Interval,
Snapshot: req.Snapshot,
Removed: req.Removed,
Platform: req.Platform,
Version: req.Version,
Shard: req.Shard,
})
if err != nil {
return globalScheduleQueryResponse{Err: err}, nil
}
return globalScheduleQueryResponse{Scheduled: scheduled}, nil
}
func (svc *Service) GlobalScheduleQuery(ctx context.Context, scheduledQuery *fleet.ScheduledQuery) (*fleet.ScheduledQuery, error) {
originalQuery, err := svc.ds.Query(ctx, scheduledQuery.QueryID)
if err != nil {
setAuthCheckedOnPreAuthErr(ctx)
return nil, ctxerr.Wrap(ctx, err, "get query")
}
if originalQuery.TeamID != nil {
setAuthCheckedOnPreAuthErr(ctx)
return nil, ctxerr.New(ctx, "cannot create a global schedule from a team query")
}
originalQuery.Name = nameForCopiedQuery(originalQuery.Name)
newQuery, err := svc.NewQuery(ctx, fleet.ScheduledQueryToQueryPayloadForNewQuery(originalQuery, scheduledQuery))
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "create new query")
}
return fleet.ScheduledQueryFromQuery(newQuery), nil
}
////////////////////////////////////////////////////////////////////////////////
// Modify Global Schedule
////////////////////////////////////////////////////////////////////////////////
type modifyGlobalScheduleRequest struct {
ID uint `json:"-" url:"id"`
fleet.ScheduledQueryPayload
}
type modifyGlobalScheduleResponse struct {
Scheduled *fleet.ScheduledQuery `json:"scheduled,omitempty"`
Err error `json:"error,omitempty"`
}
func (r modifyGlobalScheduleResponse) error() error { return r.Err }
func modifyGlobalScheduleEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*modifyGlobalScheduleRequest)
sq, err := svc.ModifyGlobalScheduledQueries(ctx, req.ID, req.ScheduledQueryPayload)
if err != nil {
return modifyGlobalScheduleResponse{Err: err}, nil
}
return modifyGlobalScheduleResponse{
Scheduled: sq,
}, nil
}
func (svc *Service) ModifyGlobalScheduledQueries(ctx context.Context, id uint, scheduledQueryPayload fleet.ScheduledQueryPayload) (*fleet.ScheduledQuery, error) {
query, err := svc.ModifyQuery(ctx, id, fleet.ScheduledQueryPayloadToQueryPayloadForModifyQuery(scheduledQueryPayload))
if err != nil {
return nil, err
}
return fleet.ScheduledQueryFromQuery(query), nil
}
////////////////////////////////////////////////////////////////////////////////
// Delete Global Schedule
////////////////////////////////////////////////////////////////////////////////
type deleteGlobalScheduleRequest struct {
ID uint `url:"id"`
}
type deleteGlobalScheduleResponse struct {
Err error `json:"error,omitempty"`
}
func (r deleteGlobalScheduleResponse) error() error { return r.Err }
func deleteGlobalScheduleEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*deleteGlobalScheduleRequest)
err := svc.DeleteGlobalScheduledQueries(ctx, req.ID)
if err != nil {
return deleteGlobalScheduleResponse{Err: err}, nil
}
return deleteGlobalScheduleResponse{}, nil
}
// TODO(lucas): Document new behavior.
func (svc *Service) DeleteGlobalScheduledQueries(ctx context.Context, id uint) error {
return svc.DeleteQueryByID(ctx, id)
}