From 3d653521d2bf4a81a05de4455b61d4f5e5122cd8 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Tue, 16 Oct 2018 09:52:52 -0400 Subject: [PATCH] feat(platform): add meta field to dashboards feat(testing): test for meta field on dashboard conformance tests feat(http): ensure that dashboard json is encoded correctly feat(bolt): update dashboard meta appropriately fix(testing): use equate empty in dashboard test for empty cells list feat(inmem): add meta to dashboards for conformance tests feat(http): add support for dashboard conformance tests feat(platform): add find options to FindDashboards method on dashboard svc feat(testing): use find options in dashboard conformance tests feat(mock): add find options to dashboard service feat(bolt): add find options to dashboard service feat(inmem): add find options to dashboard service feat(http): add find options to dashboard service feat(http): update dashboard swagger specification fix(platform): fix mistakes after rebase review(http): use default find options for dashboards --- bolt/bbolt.go | 8 ++ bolt/dashboard.go | 25 ++++-- bolt/dashboard_test.go | 1 + dashboard.go | 49 +++++++++-- http/dashboard_service.go | 26 +++--- http/dashboard_test.go | 57 ++++++++++-- http/swagger.yml | 22 ++++- inmem/dashboard.go | 27 ++++-- inmem/dashboard_test.go | 1 + inmem/service.go | 9 ++ mock/dashboard_service.go | 6 +- testing/dashboards.go | 179 +++++++++++++++++++++++++++++++++----- 12 files changed, 345 insertions(+), 65 deletions(-) diff --git a/bolt/bbolt.go b/bolt/bbolt.go index 2eacbd4819..ef06dfe0c8 100644 --- a/bolt/bbolt.go +++ b/bolt/bbolt.go @@ -32,6 +32,7 @@ type Client struct { IDGenerator platform.IDGenerator TokenGenerator platform.TokenGenerator + time func() time.Time } // NewClient returns an instance of a Client. @@ -40,6 +41,7 @@ func NewClient() *Client { Logger: zap.NewNop(), IDGenerator: snowflake.NewIDGenerator(), TokenGenerator: rand.NewTokenGenerator(64), + time: time.Now, } } @@ -54,6 +56,12 @@ func (c *Client) WithLogger(l *zap.Logger) { c.Logger = l } +// WithTime sets the function for computing the current time. Used for updating meta data +// about objects stored. Should only be used in tests for mocking. +func (c *Client) WithTime(fn func() time.Time) { + c.time = fn +} + // Open / create boltDB file. func (c *Client) Open(ctx context.Context) error { if _, err := os.Stat(c.Path); err != nil && !os.IsNotExist(err) { diff --git a/bolt/dashboard.go b/bolt/dashboard.go index 2cbe912a9d..27f8dfcdf4 100644 --- a/bolt/dashboard.go +++ b/bolt/dashboard.go @@ -107,7 +107,7 @@ func filterDashboardsFn(filter platform.DashboardFilter) func(d *platform.Dashbo } // FindDashboards retrives all dashboards that match an arbitrary dashboard filter. -func (c *Client) FindDashboards(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) { +func (c *Client) FindDashboards(ctx context.Context, filter platform.DashboardFilter, opts platform.FindOptions) ([]*platform.Dashboard, int, error) { if len(filter.IDs) == 1 { d, err := c.FindDashboardByID(ctx, *filter.IDs[0]) if err != nil { @@ -131,6 +131,8 @@ func (c *Client) FindDashboards(ctx context.Context, filter platform.DashboardFi return nil, 0, err } + platform.SortDashboards(opts.SortBy, ds) + return ds, len(ds), nil } @@ -165,7 +167,9 @@ func (c *Client) CreateDashboard(ctx context.Context, d *platform.Dashboard) err } } - return c.putDashboard(ctx, tx, d) + d.Meta.CreatedAt = c.time() + + return c.putDashboardWithMeta(ctx, tx, d) }) } @@ -231,7 +235,7 @@ func (c *Client) ReplaceDashboardCells(ctx context.Context, id platform.ID, cs [ d.Cells = cs - return c.putDashboard(ctx, tx, d) + return c.putDashboardWithMeta(ctx, tx, d) }) } @@ -248,7 +252,8 @@ func (c *Client) AddDashboardCell(ctx context.Context, id platform.ID, cell *pla } d.Cells = append(d.Cells, cell) - return c.putDashboard(ctx, tx, d) + + return c.putDashboardWithMeta(ctx, tx, d) }) } @@ -276,7 +281,8 @@ func (c *Client) RemoveDashboardCell(ctx context.Context, dashboardID, cellID pl } d.Cells = append(d.Cells[:idx], d.Cells[idx+1:]...) - return c.putDashboard(ctx, tx, d) + + return c.putDashboardWithMeta(ctx, tx, d) }) } @@ -306,7 +312,7 @@ func (c *Client) UpdateDashboardCell(ctx context.Context, dashboardID, cellID pl cell = d.Cells[idx] - return c.putDashboard(ctx, tx, d) + return c.putDashboardWithMeta(ctx, tx, d) }) if err != nil { @@ -338,6 +344,11 @@ func (c *Client) putDashboard(ctx context.Context, tx *bolt.Tx, d *platform.Dash return nil } +func (c *Client) putDashboardWithMeta(ctx context.Context, tx *bolt.Tx, d *platform.Dashboard) error { + d.Meta.UpdatedAt = c.time() + return c.putDashboard(ctx, tx, d) +} + // forEachDashboard will iterate through all dashboards while fn returns true. func (c *Client) forEachDashboard(ctx context.Context, tx *bolt.Tx, fn func(*platform.Dashboard) bool) error { cur := tx.Bucket(dashboardBucket).Cursor() @@ -383,7 +394,7 @@ func (c *Client) updateDashboard(ctx context.Context, tx *bolt.Tx, id platform.I return nil, err } - if err := c.putDashboard(ctx, tx, d); err != nil { + if err := c.putDashboardWithMeta(ctx, tx, d); err != nil { return nil, err } diff --git a/bolt/dashboard_test.go b/bolt/dashboard_test.go index bb215a6c3d..1a3e9e1829 100644 --- a/bolt/dashboard_test.go +++ b/bolt/dashboard_test.go @@ -14,6 +14,7 @@ func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (plat t.Fatalf("failed to create new bolt client: %v", err) } c.IDGenerator = f.IDGenerator + c.WithTime(f.NowFn) ctx := context.TODO() for _, b := range f.Dashboards { if err := c.PutDashboard(ctx, b); err != nil { diff --git a/dashboard.go b/dashboard.go index c09b540a9c..45df0d50fc 100644 --- a/dashboard.go +++ b/dashboard.go @@ -3,6 +3,8 @@ package platform import ( "context" "fmt" + "sort" + "time" ) // ErrDashboardNotFound is the error for a missing dashboard. @@ -18,7 +20,7 @@ type DashboardService interface { // FindDashboards returns a list of dashboards that match filter and the total count of matching dashboards. // Additional options provide pagination & sorting. - FindDashboards(ctx context.Context, filter DashboardFilter) ([]*Dashboard, int, error) + FindDashboards(ctx context.Context, filter DashboardFilter, opts FindOptions) ([]*Dashboard, int, error) // CreateDashboard creates a new dashboard and sets b.ID with the new identifier. CreateDashboard(ctx context.Context, b *Dashboard) error @@ -45,10 +47,47 @@ type DashboardService interface { // Dashboard represents all visual and query data for a dashboard type Dashboard struct { - ID ID `json:"id,omitempty"` - Name string `json:"name"` - Description string `json:"description"` - Cells []*Cell `json:"cells"` + ID ID `json:"id,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Cells []*Cell `json:"cells"` + Meta DashboardMeta `json:"meta"` +} + +// Dashboard meta contains meta information about dashboards +type DashboardMeta struct { + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// DefaultDashboardFindOptions are the default find options for dashboards +var DefaultDashboardFindOptions = FindOptions{ + SortBy: "ID", +} + +// SortDashboards sorts a slice of dashboards by a field. +func SortDashboards(by string, ds []*Dashboard) { + var sorter func(i, j int) bool + switch by { + case "CreatedAt": + sorter = func(i, j int) bool { + return ds[j].Meta.CreatedAt.After(ds[i].Meta.CreatedAt) + } + case "UpdatedAt": + sorter = func(i, j int) bool { + return ds[j].Meta.UpdatedAt.After(ds[i].Meta.UpdatedAt) + } + case "Name": + sorter = func(i, j int) bool { + return ds[i].Name < ds[j].Name + } + default: + sorter = func(i, j int) bool { + return ds[i].ID < ds[j].ID + } + } + + sort.Slice(ds, sorter) } // Cell holds positional information about a cell on dashboard and a reference to a cell. diff --git a/http/dashboard_service.go b/http/dashboard_service.go index 7a4d4d203f..c5715323f1 100644 --- a/http/dashboard_service.go +++ b/http/dashboard_service.go @@ -73,13 +73,6 @@ type dashboardResponse struct { } func (d dashboardResponse) toPlatform() *platform.Dashboard { - if len(d.Cells) == 0 { - return &platform.Dashboard{ - ID: d.ID, - Name: d.Name, - } - } - cells := make([]*platform.Cell, 0, len(d.Cells)) for i := range d.Cells { cells = append(cells, d.Cells[i].toPlatform()) @@ -87,6 +80,7 @@ func (d dashboardResponse) toPlatform() *platform.Dashboard { return &platform.Dashboard{ ID: d.ID, Name: d.Name, + Meta: d.Meta, Cells: cells, } } @@ -174,7 +168,7 @@ func (h *DashboardHandler) handleGetDashboards(w http.ResponseWriter, r *http.Re } } - dashboards, _, err := h.DashboardService.FindDashboards(ctx, req.filter) + dashboards, _, err := h.DashboardService.FindDashboards(ctx, req.filter, req.opts) if err != nil { EncodeError(ctx, errors.InternalErrorf("Error loading dashboards: %v", err), w) return @@ -188,6 +182,7 @@ func (h *DashboardHandler) handleGetDashboards(w http.ResponseWriter, r *http.Re type getDashboardsRequest struct { filter platform.DashboardFilter + opts platform.FindOptions ownerID *platform.ID } @@ -211,6 +206,12 @@ func decodeGetDashboardsRequest(ctx context.Context, r *http.Request) (*getDashb } } + req.opts = platform.DefaultDashboardFindOptions + + if sortBy := qp.Get("sortBy"); sortBy != "" { + req.opts.SortBy = sortBy + } + return req, nil } @@ -685,13 +686,14 @@ func (s *DashboardService) FindDashboardByID(ctx context.Context, id platform.ID // FindDashboards returns a list of dashboards that match filter and the total count of matching dashboards. // Additional options provide pagination & sorting. -func (s *DashboardService) FindDashboards(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) { +func (s *DashboardService) FindDashboards(ctx context.Context, filter platform.DashboardFilter, opts platform.FindOptions) ([]*platform.Dashboard, int, error) { url, err := newURL(s.Addr, dashboardsPath) if err != nil { return nil, 0, err } qp := url.Query() + qp.Add("sortBy", opts.SortBy) for _, id := range filter.IDs { qp.Add("id", id.String()) } @@ -754,7 +756,11 @@ func (s *DashboardService) CreateDashboard(ctx context.Context, d *platform.Dash return err } - return json.NewDecoder(resp.Body).Decode(d) + if err := json.NewDecoder(resp.Body).Decode(d); err != nil { + return err + } + + return nil } // UpdateDashboard updates a single dashboard with changeset. diff --git a/http/dashboard_test.go b/http/dashboard_test.go index 1dc5633a1b..5c27772752 100644 --- a/http/dashboard_test.go +++ b/http/dashboard_test.go @@ -9,6 +9,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/influxdata/platform" "github.com/influxdata/platform/inmem" @@ -40,12 +41,16 @@ func TestService_handleGetDashboards(t *testing.T) { name: "get all dashboards", fields: fields{ &mock.DashboardService{ - FindDashboardsF: func(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) { + FindDashboardsF: func(ctx context.Context, filter platform.DashboardFilter, opts platform.FindOptions) ([]*platform.Dashboard, int, error) { return []*platform.Dashboard{ { ID: platformtesting.MustIDBase16("da7aba5e5d81e550"), Name: "hello", Description: "oh hello there!", + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Cells: []*platform.Cell{ { ID: platformtesting.MustIDBase16("da7aba5e5d81e550"), @@ -58,7 +63,11 @@ func TestService_handleGetDashboards(t *testing.T) { }, }, { - ID: platformtesting.MustIDBase16("0ca2204eca2204e0"), + ID: platformtesting.MustIDBase16("0ca2204eca2204e0"), + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Name: "example", }, }, 2, nil @@ -79,6 +88,10 @@ func TestService_handleGetDashboards(t *testing.T) { "id": "da7aba5e5d81e550", "name": "hello", "description": "oh hello there!", + "meta": { + "createdAt": "2009-11-10T23:00:00Z", + "updatedAt": "2009-11-11T00:00:00Z" + }, "cells": [ { "id": "da7aba5e5d81e550", @@ -102,6 +115,10 @@ func TestService_handleGetDashboards(t *testing.T) { "id": "0ca2204eca2204e0", "name": "example", "description": "", + "meta": { + "createdAt": "2012-11-10T23:00:00Z", + "updatedAt": "2012-11-11T00:00:00Z" + }, "cells": [], "links": { "self": "/api/v2/dashboards/0ca2204eca2204e0", @@ -117,7 +134,7 @@ func TestService_handleGetDashboards(t *testing.T) { name: "get all dashboards when there are none", fields: fields{ &mock.DashboardService{ - FindDashboardsF: func(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) { + FindDashboardsF: func(ctx context.Context, filter platform.DashboardFilter, opts platform.FindOptions) ([]*platform.Dashboard, int, error) { return []*platform.Dashboard{}, 0, nil }, }, @@ -201,7 +218,11 @@ func TestService_handleGetDashboard(t *testing.T) { FindDashboardByIDF: func(ctx context.Context, id platform.ID) (*platform.Dashboard, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { return &platform.Dashboard{ - ID: platformtesting.MustIDBase16("020f755c3c082000"), + ID: platformtesting.MustIDBase16("020f755c3c082000"), + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Name: "hello", Cells: []*platform.Cell{ { @@ -231,6 +252,10 @@ func TestService_handleGetDashboard(t *testing.T) { "id": "020f755c3c082000", "name": "hello", "description": "", + "meta": { + "createdAt": "2012-11-10T23:00:00Z", + "updatedAt": "2012-11-11T00:00:00Z" + }, "cells": [ { "id": "da7aba5e5d81e550", @@ -335,6 +360,10 @@ func TestService_handlePostDashboard(t *testing.T) { &mock.DashboardService{ CreateDashboardF: func(ctx context.Context, c *platform.Dashboard) error { c.ID = platformtesting.MustIDBase16("020f755c3c082000") + c.Meta = platform.DashboardMeta{ + CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2012, time.November, 10, 24, 0, 0, 0, time.UTC), + } return nil }, }, @@ -364,6 +393,10 @@ func TestService_handlePostDashboard(t *testing.T) { "id": "020f755c3c082000", "name": "hello", "description": "howdy there", + "meta": { + "createdAt": "2012-11-10T23:00:00Z", + "updatedAt": "2012-11-11T00:00:00Z" + }, "cells": [ { "id": "da7aba5e5d81e550", @@ -547,6 +580,10 @@ func TestService_handlePatchDashboard(t *testing.T) { d := &platform.Dashboard{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2012, time.November, 10, 23, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2012, time.November, 10, 25, 0, 0, 0, time.UTC), + }, Cells: []*platform.Cell{ { ID: platformtesting.MustIDBase16("da7aba5e5d81e550"), @@ -582,6 +619,10 @@ func TestService_handlePatchDashboard(t *testing.T) { "id": "020f755c3c082000", "name": "example", "description": "", + "meta": { + "createdAt": "2012-11-10T23:00:00Z", + "updatedAt": "2012-11-11T01:00:00Z" + }, "cells": [ { "id": "da7aba5e5d81e550", @@ -1032,11 +1073,11 @@ func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (plat t.Helper() svc := inmem.NewService() svc.IDGenerator = f.IDGenerator - + svc.WithTime(f.NowFn) ctx := context.Background() - for _, o := range f.Dashboards { - if err := svc.PutDashboard(ctx, o); err != nil { - t.Fatalf("failed to populate organizations") + for _, d := range f.Dashboards { + if err := svc.PutDashboard(ctx, d); err != nil { + t.Fatalf("failed to populate dashboard") } } for _, b := range f.Views { diff --git a/http/swagger.yml b/http/swagger.yml index 9b197dafad..44d3e703e9 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -1014,6 +1014,15 @@ paths: description: specifies the owner id to return resources for schema: type: string + - in: query + name: sortBy + description: specifies the owner id to return resources for + schema: + type: string + enum: + - "ID" + - "CreatedAt" + - "UpdatedAt" - in: query name: id description: ID list of dashboards to return. If both this and owner are specified, only ids is used. @@ -4397,6 +4406,15 @@ components: description: type: string description: user-facing description of the dashboard + meta: + type: object + properties: + createdAt: + type: string + format: date + updatedAt: + type: string + format: date cells: $ref: "#/components/schemas/Cells" Dashboards: @@ -4493,7 +4511,7 @@ components: type: type: string enum: [input, output, processor, aggregator] - comment: + comment: type: string config: oneOf: @@ -4649,7 +4667,7 @@ components: bucket: type: string retentionPeriodHrs: - type: integer + type: integer required: - username - password diff --git a/inmem/dashboard.go b/inmem/dashboard.go index 8d0652948d..61214748ff 100644 --- a/inmem/dashboard.go +++ b/inmem/dashboard.go @@ -42,7 +42,7 @@ func filterDashboardFn(filter platform.DashboardFilter) func(d *platform.Dashboa } // FindDashboards implements platform.DashboardService interface. -func (s *Service) FindDashboards(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) { +func (s *Service) FindDashboards(ctx context.Context, filter platform.DashboardFilter, opts platform.FindOptions) ([]*platform.Dashboard, int, error) { if len(filter.IDs) == 1 { d, err := s.FindDashboardByID(ctx, *filter.IDs[0]) if err != nil { @@ -66,13 +66,17 @@ func (s *Service) FindDashboards(ctx context.Context, filter platform.DashboardF } return true }) + + platform.SortDashboards(opts.SortBy, ds) + return ds, len(ds), err } // CreateDashboard implements platform.DashboardService interface. func (s *Service) CreateDashboard(ctx context.Context, d *platform.Dashboard) error { d.ID = s.IDGenerator.ID() - return s.PutDashboard(ctx, d) + d.Meta.CreatedAt = s.time() + return s.PutDashboardWithMeta(ctx, d) } // PutDashboard implements platform.DashboardService interface. @@ -81,6 +85,12 @@ func (s *Service) PutDashboard(ctx context.Context, o *platform.Dashboard) error return nil } +// PutDashboardWithMeta sets a dashboard while updating the meta field of a dashboard. +func (s *Service) PutDashboardWithMeta(ctx context.Context, d *platform.Dashboard) error { + d.Meta.UpdatedAt = s.time() + return s.PutDashboard(ctx, d) +} + // UpdateDashboard implements platform.DashboardService interface. func (s *Service) UpdateDashboard(ctx context.Context, id platform.ID, upd platform.DashboardUpdate) (*platform.Dashboard, error) { if err := upd.Valid(); err != nil { @@ -96,7 +106,10 @@ func (s *Service) UpdateDashboard(ctx context.Context, id platform.ID, upd platf return nil, err } - s.dashboardKV.Store(d.ID.String(), d) + if err := s.PutDashboardWithMeta(ctx, d); err != nil { + return nil, err + } + return d, nil } @@ -121,7 +134,7 @@ func (s *Service) AddDashboardCell(ctx context.Context, id platform.ID, cell *pl } d.Cells = append(d.Cells, cell) - return s.PutDashboard(ctx, d) + return s.PutDashboardWithMeta(ctx, d) } // PutDashboardCell replaces a dashboad cell with the cell contents. @@ -163,7 +176,7 @@ func (s *Service) RemoveDashboardCell(ctx context.Context, dashboardID platform. } d.Cells = append(d.Cells[:idx], d.Cells[idx+1:]...) - return s.PutDashboard(ctx, d) + return s.PutDashboardWithMeta(ctx, d) } @@ -191,7 +204,7 @@ func (s *Service) UpdateDashboardCell(ctx context.Context, dashboardID platform. cell := d.Cells[idx] - if err := s.PutDashboard(ctx, d); err != nil { + if err := s.PutDashboardWithMeta(ctx, d); err != nil { return nil, err } @@ -227,5 +240,5 @@ func (s *Service) ReplaceDashboardCells(ctx context.Context, id platform.ID, cs d.Cells = cs - return s.PutDashboard(ctx, d) + return s.PutDashboardWithMeta(ctx, d) } diff --git a/inmem/dashboard_test.go b/inmem/dashboard_test.go index 3641b9a665..443113e911 100644 --- a/inmem/dashboard_test.go +++ b/inmem/dashboard_test.go @@ -12,6 +12,7 @@ func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (plat s := NewService() s.IDGenerator = f.IDGenerator ctx := context.Background() + s.WithTime(f.NowFn) for _, b := range f.Dashboards { if err := s.PutDashboard(ctx, b); err != nil { t.Fatalf("failed to populate Dashboards") diff --git a/inmem/service.go b/inmem/service.go index 0a7dabe0b7..e787dbe94b 100644 --- a/inmem/service.go +++ b/inmem/service.go @@ -2,6 +2,7 @@ package inmem import ( "sync" + "time" "github.com/influxdata/platform" "github.com/influxdata/platform/rand" @@ -24,6 +25,7 @@ type Service struct { TokenGenerator platform.TokenGenerator IDGenerator platform.IDGenerator + time func() time.Time } // NewService creates an instance of a Service. @@ -31,5 +33,12 @@ func NewService() *Service { return &Service{ TokenGenerator: rand.NewTokenGenerator(64), IDGenerator: snowflake.NewIDGenerator(), + time: time.Now, } } + +// WithTime sets the function for computing the current time. Used for updating meta data +// about objects stored. Should only be used in tests for mocking. +func (s *Service) WithTime(fn func() time.Time) { + s.time = fn +} diff --git a/mock/dashboard_service.go b/mock/dashboard_service.go index bcbfe92f99..ebf3140983 100644 --- a/mock/dashboard_service.go +++ b/mock/dashboard_service.go @@ -11,7 +11,7 @@ var _ platform.DashboardService = &DashboardService{} type DashboardService struct { CreateDashboardF func(context.Context, *platform.Dashboard) error FindDashboardByIDF func(context.Context, platform.ID) (*platform.Dashboard, error) - FindDashboardsF func(context.Context, platform.DashboardFilter) ([]*platform.Dashboard, int, error) + FindDashboardsF func(context.Context, platform.DashboardFilter, platform.FindOptions) ([]*platform.Dashboard, int, error) UpdateDashboardF func(context.Context, platform.ID, platform.DashboardUpdate) (*platform.Dashboard, error) DeleteDashboardF func(context.Context, platform.ID) error @@ -26,8 +26,8 @@ func (s *DashboardService) FindDashboardByID(ctx context.Context, id platform.ID return s.FindDashboardByIDF(ctx, id) } -func (s *DashboardService) FindDashboards(ctx context.Context, filter platform.DashboardFilter) ([]*platform.Dashboard, int, error) { - return s.FindDashboardsF(ctx, filter) +func (s *DashboardService) FindDashboards(ctx context.Context, filter platform.DashboardFilter, opts platform.FindOptions) ([]*platform.Dashboard, int, error) { + return s.FindDashboardsF(ctx, filter, opts) } func (s *DashboardService) CreateDashboard(ctx context.Context, b *platform.Dashboard) error { diff --git a/testing/dashboards.go b/testing/dashboards.go index b8e6b96227..ef0d83159e 100644 --- a/testing/dashboards.go +++ b/testing/dashboards.go @@ -4,10 +4,11 @@ import ( "bytes" "context" "fmt" - "sort" "testing" + "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/influxdata/platform" "github.com/influxdata/platform/mock" ) @@ -26,18 +27,13 @@ var dashboardCmpOptions = cmp.Options{ cmp.Comparer(func(x, y []byte) bool { return bytes.Equal(x, y) }), - cmp.Transformer("Sort", func(in []*platform.Dashboard) []*platform.Dashboard { - out := append([]*platform.Dashboard(nil), in...) // Copy input to avoid mutating it - sort.Slice(out, func(i, j int) bool { - return out[i].ID.String() > out[j].ID.String() - }) - return out - }), + cmpopts.EquateEmpty(), } // DashboardFields will include the IDGenerator, and dashboards type DashboardFields struct { IDGenerator platform.IDGenerator + NowFn func() time.Time Dashboards []*platform.Dashboard Views []*platform.View } @@ -118,6 +114,7 @@ func CreateDashboard( return MustIDBase16(dashTwoID) }, }, + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, Dashboards: []*platform.Dashboard{ { ID: MustIDBase16(dashOneID), @@ -140,6 +137,10 @@ func CreateDashboard( { ID: MustIDBase16(dashTwoID), Name: "dashboard2", + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, }, }, }, @@ -152,6 +153,7 @@ func CreateDashboard( return MustIDBase16(dashTwoID) }, }, + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, Dashboards: []*platform.Dashboard{ { ID: MustIDBase16(dashOneID), @@ -173,6 +175,10 @@ func CreateDashboard( { ID: MustIDBase16(dashTwoID), Name: "dashboard2", + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, }, }, }, @@ -196,7 +202,7 @@ func CreateDashboard( } defer s.DeleteDashboard(ctx, tt.args.dashboard.ID) - dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}) + dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}, platform.DefaultDashboardFindOptions) if err != nil { t.Fatalf("failed to retrieve dashboards: %v", err) } @@ -235,6 +241,7 @@ func AddDashboardCell( return MustIDBase16(dashTwoID) }, }, + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, Dashboards: []*platform.Dashboard{ { ID: MustIDBase16(dashOneID), @@ -259,7 +266,10 @@ func AddDashboardCell( wants: wants{ dashboards: []*platform.Dashboard{ { - ID: MustIDBase16(dashOneID), + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Name: "dashboard1", Cells: []*platform.Cell{ { @@ -274,6 +284,7 @@ func AddDashboardCell( { name: "add cell with no id", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, IDGenerator: &mock.IDGenerator{ IDFn: func() platform.ID { return MustIDBase16(dashTwoID) @@ -302,7 +313,10 @@ func AddDashboardCell( wants: wants{ dashboards: []*platform.Dashboard{ { - ID: MustIDBase16(dashOneID), + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Name: "dashboard1", Cells: []*platform.Cell{ { @@ -333,7 +347,7 @@ func AddDashboardCell( } defer s.DeleteDashboard(ctx, tt.args.dashboardID) - dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}) + dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}, platform.DefaultDashboardFindOptions) if err != nil { t.Fatalf("failed to retrieve dashboards: %v", err) } @@ -419,8 +433,9 @@ func FindDashboards( t *testing.T, ) { type args struct { - IDs []*platform.ID - name string + IDs []*platform.ID + name string + findOptions platform.FindOptions } type wants struct { @@ -447,7 +462,9 @@ func FindDashboards( }, }, }, - args: args{}, + args: args{ + findOptions: platform.DefaultDashboardFindOptions, + }, wants: wants{ dashboards: []*platform.Dashboard{ { @@ -461,6 +478,94 @@ func FindDashboards( }, }, }, + { + name: "find all dashboards sorted by created at", + fields: DashboardFields{ + Dashboards: []*platform.Dashboard{ + { + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + Name: "abc", + }, + { + ID: MustIDBase16(dashTwoID), + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2004, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + Name: "xyz", + }, + }, + }, + args: args{ + findOptions: platform.FindOptions{ + SortBy: "CreatedAt", + }, + }, + wants: wants{ + dashboards: []*platform.Dashboard{ + { + ID: MustIDBase16(dashTwoID), + Name: "xyz", + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2004, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + }, + { + ID: MustIDBase16(dashOneID), + Name: "abc", + Meta: platform.DashboardMeta{ + CreatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + { + name: "find all dashboards sorted by updated at", + fields: DashboardFields{ + Dashboards: []*platform.Dashboard{ + { + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + Name: "abc", + }, + { + ID: MustIDBase16(dashTwoID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2010, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + Name: "xyz", + }, + }, + }, + args: args{ + findOptions: platform.FindOptions{ + SortBy: "UpdatedAt", + }, + }, + wants: wants{ + dashboards: []*platform.Dashboard{ + { + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + Name: "abc", + }, + { + ID: MustIDBase16(dashTwoID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2010, time.November, 10, 24, 0, 0, 0, time.UTC), + }, + Name: "xyz", + }, + }, + }, + }, { name: "find dashboard by id", fields: DashboardFields{ @@ -479,6 +584,7 @@ func FindDashboards( IDs: []*platform.ID{ idPtr(MustIDBase16(dashTwoID)), }, + findOptions: platform.DefaultDashboardFindOptions, }, wants: wants{ dashboards: []*platform.Dashboard{ @@ -508,6 +614,7 @@ func FindDashboards( idPtr(MustIDBase16(dashOneID)), idPtr(MustIDBase16(dashTwoID)), }, + findOptions: platform.DefaultDashboardFindOptions, }, wants: wants{ dashboards: []*platform.Dashboard{ @@ -535,7 +642,7 @@ func FindDashboards( filter.IDs = tt.args.IDs } - dashboards, _, err := s.FindDashboards(ctx, filter) + dashboards, _, err := s.FindDashboards(ctx, filter, tt.args.findOptions) if (err != nil) != (tt.wants.err != nil) { t.Fatalf("expected errors to be equal '%v' got '%v'", tt.wants.err, err) } @@ -648,7 +755,7 @@ func DeleteDashboard( } filter := platform.DashboardFilter{} - dashboards, _, err := s.FindDashboards(ctx, filter) + dashboards, _, err := s.FindDashboards(ctx, filter, platform.DefaultDashboardFindOptions) if err != nil { t.Fatalf("failed to retrieve dashboards: %v", err) } @@ -683,6 +790,7 @@ func UpdateDashboard( { name: "update name", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, Dashboards: []*platform.Dashboard{ { ID: MustIDBase16(dashOneID), @@ -700,7 +808,10 @@ func UpdateDashboard( }, wants: wants{ dashboard: &platform.Dashboard{ - ID: MustIDBase16(dashOneID), + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Name: "changed", }, }, @@ -708,6 +819,7 @@ func UpdateDashboard( { name: "update description", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, Dashboards: []*platform.Dashboard{ { ID: MustIDBase16(dashOneID), @@ -728,12 +840,16 @@ func UpdateDashboard( ID: MustIDBase16(dashOneID), Name: "dashboard1", Description: "changed", + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, }, }, }, { name: "update description and name", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, Dashboards: []*platform.Dashboard{ { ID: MustIDBase16(dashOneID), @@ -755,6 +871,9 @@ func UpdateDashboard( ID: MustIDBase16(dashOneID), Name: "changed", Description: "changed", + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, }, }, }, @@ -815,6 +934,7 @@ func RemoveDashboardCell( { name: "basic remove cell", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, IDGenerator: &mock.IDGenerator{ IDFn: func() platform.ID { return MustIDBase16(dashTwoID) @@ -851,7 +971,10 @@ func RemoveDashboardCell( wants: wants{ dashboards: []*platform.Dashboard{ { - ID: MustIDBase16(dashOneID), + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Name: "dashboard1", Cells: []*platform.Cell{ { @@ -882,7 +1005,7 @@ func RemoveDashboardCell( } defer s.DeleteDashboard(ctx, tt.args.dashboardID) - dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}) + dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}, platform.DefaultDashboardFindOptions) if err != nil { t.Fatalf("failed to retrieve dashboards: %v", err) } @@ -917,6 +1040,7 @@ func UpdateDashboardCell( { name: "basic remove cell", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, IDGenerator: &mock.IDGenerator{ IDFn: func() platform.ID { return MustIDBase16(dashTwoID) @@ -950,7 +1074,10 @@ func UpdateDashboardCell( wants: wants{ dashboards: []*platform.Dashboard{ { - ID: MustIDBase16(dashOneID), + ID: MustIDBase16(dashOneID), + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Name: "dashboard1", Cells: []*platform.Cell{ { @@ -986,7 +1113,7 @@ func UpdateDashboardCell( } defer s.DeleteDashboard(ctx, tt.args.dashboardID) - dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}) + dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}, platform.DefaultDashboardFindOptions) if err != nil { t.Fatalf("failed to retrieve dashboards: %v", err) } @@ -1020,6 +1147,7 @@ func ReplaceDashboardCells( { name: "basic replace cells", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, IDGenerator: &mock.IDGenerator{ IDFn: func() platform.ID { return MustIDBase16(dashTwoID) @@ -1074,6 +1202,9 @@ func ReplaceDashboardCells( { ID: MustIDBase16(dashOneID), Name: "dashboard1", + Meta: platform.DashboardMeta{ + UpdatedAt: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC), + }, Cells: []*platform.Cell{ { ID: MustIDBase16(dashTwoID), @@ -1093,6 +1224,7 @@ func ReplaceDashboardCells( { name: "try to add a cell that didn't previously exist", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, IDGenerator: &mock.IDGenerator{ IDFn: func() platform.ID { return MustIDBase16(dashTwoID) @@ -1152,6 +1284,7 @@ func ReplaceDashboardCells( { name: "try to update a view during a replace", fields: DashboardFields{ + NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, IDGenerator: &mock.IDGenerator{ IDFn: func() platform.ID { return MustIDBase16(dashTwoID) @@ -1227,7 +1360,7 @@ func ReplaceDashboardCells( } defer s.DeleteDashboard(ctx, tt.args.dashboardID) - dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}) + dashboards, _, err := s.FindDashboards(ctx, platform.DashboardFilter{}, platform.DefaultDashboardFindOptions) if err != nil { t.Fatalf("failed to retrieve dashboards: %v", err) }