Merge pull request #142 from influxdata/feature/exploration-store

Implement exploration store
pull/10616/head
Gunnar 2016-09-29 10:31:39 -04:00 committed by GitHub
commit 3903784728
14 changed files with 572 additions and 44 deletions

2
Godeps
View File

@ -1,6 +1,7 @@
github.com/PuerkitoBio/purell 8a290539e2e8629dbc4e6bad948158f790ec31f4
github.com/PuerkitoBio/urlesc 5bd2802263f21d8788851d5305584c82a5c75d7e
github.com/asaskevich/govalidator 593d64559f7600f29581a3ee42177f5dbded27a9
github.com/boltdb/bolt 5cc10bbbc5c141029940133bb33c9e969512a698
github.com/elazarl/go-bindata-assetfs 9a6736ed45b44bf3835afeebb3034b57ed329f3e
github.com/go-openapi/analysis b44dc874b601d9e4e2f6e19140e794ba24bead3b
github.com/go-openapi/errors 4178436c9f2430cdd945c50301cfb61563b56573
@ -12,6 +13,7 @@ github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff
github.com/go-openapi/strfmt d65c7fdb29eca313476e529628176fe17e58c488
github.com/go-openapi/swag 0e04f5e499b19bf51031c01a00f098f25067d8dc
github.com/go-openapi/validate deaf2c9013bc1a7f4c774662259a506ba874d80f
github.com/gogo/protobuf 6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b
github.com/gorilla/context 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
github.com/jessevdk/go-flags 4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa
github.com/mailru/easyjson e978125a7e335d8f4db746a9ac5b44643f27416b

View File

@ -50,6 +50,9 @@ godep:
jsdep:
cd ui && npm install
gen: bolt/internal/internal.proto
go generate github.com/influxdata/mrfusion/bolt/internal
test: jstest gotest
gotest:

53
bolt/client.go Normal file
View File

@ -0,0 +1,53 @@
package bolt
import (
"time"
"github.com/boltdb/bolt"
)
// Client is a client for the boltDB data store.
type Client struct {
Path string
db *bolt.DB
Now func() time.Time
ExplorationStore *ExplorationStore
}
func NewClient() *Client {
c := &Client{Now: time.Now}
c.ExplorationStore = &ExplorationStore{client: c}
return c
}
// Open and initialize boltDB. Initial buckets are created if they do not exist.
func (c *Client) Open() error {
// Open database file.
db, err := bolt.Open(c.Path, 0644, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
return err
}
c.db = db
if err := c.db.Update(func(tx *bolt.Tx) error {
// Always create explorations bucket.
if _, err := tx.CreateBucketIfNotExists(ExplorationBucket); err != nil {
return err
}
return nil
}); err != nil {
return err
}
c.ExplorationStore = &ExplorationStore{client: c}
return nil
}
func (c *Client) Close() error {
if c.db != nil {
return c.db.Close()
}
return nil
}

126
bolt/exploration.go Normal file
View File

@ -0,0 +1,126 @@
package bolt
import (
"github.com/boltdb/bolt"
"github.com/influxdata/mrfusion"
"github.com/influxdata/mrfusion/bolt/internal"
"golang.org/x/net/context"
)
// Ensure ExplorationStore implements mrfusion.ExplorationStore.
var _ mrfusion.ExplorationStore = &ExplorationStore{}
var ExplorationBucket = []byte("Explorations")
type ExplorationStore struct {
client *Client
}
// Search the ExplorationStore for all explorations owned by userID.
func (s *ExplorationStore) Query(ctx context.Context, uid mrfusion.UserID) ([]*mrfusion.Exploration, error) {
var explorations []*mrfusion.Exploration
if err := s.client.db.View(func(tx *bolt.Tx) error {
if err := tx.Bucket(ExplorationBucket).ForEach(func(k, v []byte) error {
var e mrfusion.Exploration
if err := internal.UnmarshalExploration(v, &e); err != nil {
return err
} else if e.UserID != uid {
return nil
}
explorations = append(explorations, &e)
return nil
}); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return explorations, nil
}
// Create a new Exploration in the ExplorationStore.
func (s *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) (*mrfusion.Exploration, error) {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(ExplorationBucket)
seq, err := b.NextSequence()
if err != nil {
return err
}
e.ID = mrfusion.ExplorationID(seq)
e.CreatedAt = s.client.Now()
if v, err := internal.MarshalExploration(e); err != nil {
return err
} else if err := b.Put(itob(int(e.ID)), v); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return e, nil
}
// Delete the exploration from the ExplorationStore
func (s *ExplorationStore) Delete(ctx context.Context, e *mrfusion.Exploration) error {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
if err := tx.Bucket(ExplorationBucket).Delete(itob(int(e.ID))); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// Retrieve an exploration for an id exists.
func (s *ExplorationStore) Get(ctx context.Context, id mrfusion.ExplorationID) (*mrfusion.Exploration, error) {
var e mrfusion.Exploration
if err := s.client.db.View(func(tx *bolt.Tx) error {
if v := tx.Bucket(ExplorationBucket).Get(itob(int(id))); v == nil {
return mrfusion.ErrExplorationNotFound
} else if err := internal.UnmarshalExploration(v, &e); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return &e, nil
}
// Update an exploration; will also update the `UpdatedAt` time.
func (s *ExplorationStore) Update(ctx context.Context, e *mrfusion.Exploration) error {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
// Retreive an existing exploration with the same exploration ID.
var ee mrfusion.Exploration
b := tx.Bucket(ExplorationBucket)
if v := b.Get(itob(int(e.ID))); v == nil {
return mrfusion.ErrExplorationNotFound
} else if err := internal.UnmarshalExploration(v, &ee); err != nil {
return err
}
ee.Name = e.Name
ee.UserID = e.UserID
ee.Data = e.Data
ee.UpdatedAt = s.client.Now()
if v, err := internal.MarshalExploration(&ee); err != nil {
return err
} else if err := b.Put(itob(int(ee.ID)), v); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}

175
bolt/exploration_test.go Normal file
View File

@ -0,0 +1,175 @@
package bolt_test
import (
"errors"
// "fmt"
"io/ioutil"
"os"
"testing"
"time"
"github.com/influxdata/mrfusion"
"github.com/influxdata/mrfusion/bolt"
)
// TestNow is a set time for testing.
var TestNow = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
// TestClient wraps *bolt.Client.
type TestClient struct {
*bolt.Client
}
// NewTestClient creates new *bolt.Client with a set time and temp path.
func NewTestClient() (*TestClient, error) {
f, err := ioutil.TempFile("", "mrfusion-bolt-")
if err != nil {
return nil, errors.New("unable to open temporary boltdb file")
}
f.Close()
c := &TestClient{
Client: bolt.NewClient(),
}
c.Path = f.Name()
c.Now = func() time.Time { return TestNow }
return c, nil
}
func (c *TestClient) Close() error {
defer os.Remove(c.Path)
return c.Client.Close()
}
// Ensure an ExplorationStore can store, retrieve, update, and delete explorations.
func TestExplorationStore_CRUD(t *testing.T) {
c, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
if err := c.Open(); err != nil {
t.Fatal(err)
}
defer c.Close()
s := c.ExplorationStore
explorations := []*mrfusion.Exploration{
&mrfusion.Exploration{
Name: "Ferdinand Magellan",
UserID: 2,
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
},
&mrfusion.Exploration{
Name: "Marco Polo",
UserID: 3,
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
},
&mrfusion.Exploration{
Name: "Leif Ericson",
UserID: 3,
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
},
}
// Add new explorations.
for i := range explorations {
if _, err := s.Add(nil, explorations[i]); err != nil {
t.Fatal(err)
}
}
// Confirm first exploration in the store is the same as the original.
if e, err := s.Get(nil, explorations[0].ID); err != nil {
t.Fatal(err)
} else if e.ID != explorations[0].ID {
t.Fatalf("exploration ID error: got %v, expected %v", e.ID, explorations[1].ID)
} else if e.Name != explorations[0].Name {
t.Fatalf("exploration Name error: got %v, expected %v", e.Name, explorations[1].Name)
} else if e.UserID != explorations[0].UserID {
t.Fatalf("exploration UserID error: got %v, expected %v", e.UserID, explorations[1].UserID)
} else if e.Data != explorations[0].Data {
t.Fatalf("exploration Data error: got %v, expected %v", e.Data, explorations[1].Data)
}
// Update explorations.
explorations[1].Name = "Francis Drake"
explorations[2].UserID = 4
if err := s.Update(nil, explorations[1]); err != nil {
t.Fatal(err)
} else if err := s.Update(nil, explorations[2]); err != nil {
t.Fatal(err)
}
// Confirm explorations are updated.
if e, err := s.Get(nil, explorations[1].ID); err != nil {
t.Fatal(err)
} else if e.Name != "Francis Drake" {
t.Fatalf("exploration 1 update error: got %v, expected %v", e.Name, "Francis Drake")
}
if e, err := s.Get(nil, explorations[2].ID); err != nil {
t.Fatal(err)
} else if e.UserID != 4 {
t.Fatalf("exploration 2 update error: got %v, expected %v", e.UserID, 4)
}
// Delete an exploration.
if err := s.Delete(nil, explorations[2]); err != nil {
t.Fatal(err)
}
// Confirm exploration has been deleted.
if e, err := s.Get(nil, explorations[2].ID); err != mrfusion.ErrExplorationNotFound {
t.Fatalf("exploration delete error: got %v, expected %v", e, mrfusion.ErrExplorationNotFound)
}
}
// Ensure Explorations can be queried by UserID.
func TestExplorationStore_Query(t *testing.T) {
c, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
if err := c.Open(); err != nil {
t.Fatal(err)
}
defer c.Close()
s := c.ExplorationStore
explorations := []*mrfusion.Exploration{
&mrfusion.Exploration{
Name: "Ferdinand Magellan",
UserID: 2,
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
},
&mrfusion.Exploration{
Name: "Marco Polo",
UserID: 3,
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
},
&mrfusion.Exploration{
Name: "Leif Ericson",
UserID: 3,
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
},
}
// Add new explorations.
for i := range explorations {
if _, err := s.Add(nil, explorations[i]); err != nil {
t.Fatal(err)
}
}
// Query for explorations.
if e, err := s.Query(nil, 3); err != nil {
t.Fatal(err)
} else if len(e) != 2 {
t.Fatalf("exploration query length error: got %v, expected %v", len(explorations), len(e))
} else if e[0].Name != explorations[1].Name {
t.Fatalf("exploration query error: got %v, expected %v", explorations[0].Name, "Marco Polo")
} else if e[1].Name != explorations[2].Name {
t.Fatalf("exploration query error: got %v, expected %v", explorations[1].Name, "Leif Ericson")
}
}

41
bolt/internal/internal.go Normal file
View File

@ -0,0 +1,41 @@
package internal
import (
"time"
"github.com/gogo/protobuf/proto"
"github.com/influxdata/mrfusion"
)
//go:generate protoc --gogo_out=. internal.proto
// MarshalExploration encodes an exploration to binary protobuf format.
func MarshalExploration(e *mrfusion.Exploration) ([]byte, error) {
return proto.Marshal(&Exploration{
ID: int64(e.ID),
Name: e.Name,
UserID: int64(e.UserID),
Data: e.Data,
CreatedAt: e.CreatedAt.UnixNano(),
UpdatedAt: e.UpdatedAt.UnixNano(),
Default: e.Default,
})
}
// UnmarshalExploration decodes an exploration from binary protobuf data.
func UnmarshalExploration(data []byte, e *mrfusion.Exploration) error {
var pb Exploration
if err := proto.Unmarshal(data, &pb); err != nil {
return err
}
e.ID = mrfusion.ExplorationID(pb.ID)
e.Name = pb.Name
e.UserID = mrfusion.UserID(pb.UserID)
e.Data = pb.Data
e.CreatedAt = time.Unix(0, pb.CreatedAt).UTC()
e.UpdatedAt = time.Unix(0, pb.UpdatedAt).UTC()
e.Default = pb.Default
return nil
}

View File

@ -0,0 +1,65 @@
// Code generated by protoc-gen-gogo.
// source: internal.proto
// DO NOT EDIT!
/*
Package internal is a generated protocol buffer package.
It is generated from these files:
internal.proto
It has these top-level messages:
Exploration
*/
package internal
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type Exploration struct {
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
UserID int64 `protobuf:"varint,3,opt,name=UserID,proto3" json:"UserID,omitempty"`
Data string `protobuf:"bytes,4,opt,name=Data,proto3" json:"Data,omitempty"`
CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"`
UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"`
}
func (m *Exploration) Reset() { *m = Exploration{} }
func (m *Exploration) String() string { return proto.CompactTextString(m) }
func (*Exploration) ProtoMessage() {}
func (*Exploration) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
func init() {
proto.RegisterType((*Exploration)(nil), "internal.Exploration")
}
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 176 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0xcc, 0x2b, 0x49,
0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x80, 0xf1, 0x95, 0x36,
0x33, 0x72, 0x71, 0xbb, 0x56, 0x14, 0xe4, 0xe4, 0x17, 0x25, 0x96, 0x64, 0xe6, 0xe7, 0x09, 0xf1,
0x71, 0x31, 0x79, 0xba, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0x31, 0x79, 0xba, 0x08, 0x09,
0x71, 0xb1, 0xf8, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x06, 0x81, 0xd9, 0x42,
0x62, 0x5c, 0x6c, 0xa1, 0xc5, 0xa9, 0x45, 0x9e, 0x2e, 0x12, 0xcc, 0x60, 0x75, 0x50, 0x1e, 0x48,
0xad, 0x4b, 0x62, 0x49, 0xa2, 0x04, 0x0b, 0x44, 0x2d, 0x88, 0x2d, 0x24, 0xc3, 0xc5, 0xe9, 0x5c,
0x94, 0x9a, 0x58, 0x92, 0x9a, 0xe2, 0x58, 0x22, 0xc1, 0x0a, 0x56, 0x8e, 0x10, 0x00, 0xc9, 0x86,
0x16, 0xa4, 0x40, 0x65, 0xd9, 0x20, 0xb2, 0x70, 0x01, 0x21, 0x09, 0x2e, 0x76, 0x97, 0xd4, 0xb4,
0xc4, 0xd2, 0x9c, 0x12, 0x09, 0x76, 0x05, 0x46, 0x0d, 0x8e, 0x20, 0x18, 0x37, 0x89, 0x0d, 0xec,
0x0d, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x69, 0xb2, 0xe7, 0xd8, 0x00, 0x00, 0x00,
}

View File

@ -0,0 +1,12 @@
syntax = "proto3";
package internal;
message Exploration {
int64 ID = 1; // ExplorationID is a unique ID for an Exploration.
string Name = 2; // User provided name of the Exploration.
int64 UserID = 3; // UserID is the owner of this Exploration.
string Data = 4; // Opaque blob of JSON data.
int64 CreatedAt = 5; // Time the exploration was first created.
int64 UpdatedAt = 6; // Latest time the exploration was updated.
bool Default = 7; // Flags an exploration as the default.
}

View File

@ -0,0 +1,31 @@
package internal_test
import (
"reflect"
"testing"
"time"
"github.com/influxdata/mrfusion"
"github.com/influxdata/mrfusion/bolt/internal"
)
// Ensure an exploration can be marshaled and unmarshaled.
func TestMarshalExploration(t *testing.T) {
v := mrfusion.Exploration{
ID: 12,
Name: "Some Exploration",
UserID: 34,
Data: "{\"data\":\"something\"}",
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
}
var vv mrfusion.Exploration
if buf, err := internal.MarshalExploration(&v); err != nil {
t.Fatal(err)
} else if err := internal.UnmarshalExploration(buf, &vv); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(v, vv) {
t.Fatalf("exploration protobuf copy error: got %#v, expected %#v", vv, v)
}
}

12
bolt/util.go Normal file
View File

@ -0,0 +1,12 @@
package bolt
import (
"encoding/binary"
)
// itob returns an 8-byte big endian representation of v.
func itob(v int) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(v))
return b
}

View File

@ -2,7 +2,8 @@ package mrfusion
// General errors.
const (
ErrUpstreamTimeout = Error("request to backend timed out")
ErrUpstreamTimeout = Error("request to backend timed out")
ErrExplorationNotFound = Error("exploration not found")
)
// Error is a domain error encountered while processing mrfusion requests

View File

@ -103,7 +103,7 @@ func (m *Handler) Explorations(ctx context.Context, params op.GetSourcesIDUsersU
if err != nil {
return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500)
}
exs, err := m.Store.Query(ctx, id)
exs, err := m.Store.Query(ctx, mrfusion.UserID(id))
if err != nil {
return op.NewGetSourcesIDUsersUserIDExplorationsNotFound()
}
@ -139,7 +139,7 @@ func (m *Handler) Exploration(ctx context.Context, params op.GetSourcesIDUsersUs
return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
}
e, err := m.Store.Get(ctx, eID)
e, err := m.Store.Get(ctx, mrfusion.ExplorationID(eID))
if err != nil {
log.Printf("Error unknown exploration id: %d: %v", eID, err)
errMsg := &models.Error{Code: 404, Message: "Error unknown exploration id"}
@ -167,14 +167,14 @@ func (m *Handler) UpdateExploration(ctx context.Context, params op.PatchSourcesI
return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500)
}
e, err := m.Store.Get(ctx, eID)
e, err := m.Store.Get(ctx, mrfusion.ExplorationID(eID))
if err != nil {
log.Printf("Error unknown exploration id: %d: %v", eID, err)
errMsg := &models.Error{Code: 404, Message: "Error unknown exploration id"}
return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
}
if params.Exploration != nil {
e.ID = eID
e.ID = mrfusion.ExplorationID(eID)
e.Data = params.Exploration.Data.(string)
e.Name = params.Exploration.Name
m.Store.Update(ctx, e)
@ -188,7 +188,7 @@ func (m *Handler) NewExploration(ctx context.Context, params op.PostSourcesIDUse
return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500)
}
exs, err := m.Store.Query(ctx, id)
exs, err := m.Store.Query(ctx, mrfusion.UserID(id))
if err != nil {
log.Printf("Error unknown user id: %d: %v", id, err)
errMsg := &models.Error{Code: 404, Message: "Error unknown user id"}
@ -200,9 +200,9 @@ func (m *Handler) NewExploration(ctx context.Context, params op.PostSourcesIDUse
e := mrfusion.Exploration{
Data: params.Exploration.Data.(string),
Name: params.Exploration.Name,
ID: eID,
ID: mrfusion.ExplorationID(eID),
}
m.Store.Add(ctx, e)
m.Store.Add(ctx, &e)
}
params.Exploration.UpdatedAt = strfmt.DateTime(time.Now())
params.Exploration.CreatedAt = strfmt.DateTime(time.Now())
@ -225,7 +225,7 @@ func (m *Handler) DeleteExploration(ctx context.Context, params op.DeleteSources
return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500)
}
if err := m.Store.Delete(ctx, mrfusion.Exploration{ID: ID}); err != nil {
if err := m.Store.Delete(ctx, &mrfusion.Exploration{ID: mrfusion.ExplorationID(ID)}); err != nil {
log.Printf("Error unknown explorations id: %d: %v", ID, err)
errMsg := &models.Error{Code: 404, Message: "Error unknown user id"}
return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)

View File

@ -9,22 +9,22 @@ import (
)
type ExplorationStore struct {
db map[int]mrfusion.Exploration
db map[int]*mrfusion.Exploration
NowFunc func() time.Time
}
func NewExplorationStore(nowFunc func() time.Time) mrfusion.ExplorationStore {
e := ExplorationStore{
NowFunc: nowFunc,
db: map[int]mrfusion.Exploration{},
db: map[int]*mrfusion.Exploration{},
}
e.db[0] = mrfusion.Exploration{
e.db[0] = &mrfusion.Exploration{
Name: "Ferdinand Magellan",
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
CreatedAt: nowFunc(),
UpdatedAt: nowFunc(),
}
e.db[1] = mrfusion.Exploration{
e.db[1] = &mrfusion.Exploration{
Name: "Ferdinand Magellan",
Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}",
CreatedAt: nowFunc(),
@ -36,41 +36,41 @@ func NewExplorationStore(nowFunc func() time.Time) mrfusion.ExplorationStore {
var DefaultExplorationStore mrfusion.ExplorationStore = NewExplorationStore(time.Now)
func (m *ExplorationStore) Query(ctx context.Context, userID int) ([]mrfusion.Exploration, error) {
res := []mrfusion.Exploration{}
func (m *ExplorationStore) Query(ctx context.Context, userID mrfusion.UserID) ([]*mrfusion.Exploration, error) {
res := []*mrfusion.Exploration{}
for _, v := range m.db {
res = append(res, v)
}
return res, nil
}
func (m *ExplorationStore) Add(ctx context.Context, e mrfusion.Exploration) error {
func (m *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) (*mrfusion.Exploration, error) {
e.CreatedAt = m.NowFunc()
e.UpdatedAt = m.NowFunc()
m.db[len(m.db)] = e
return e, nil
}
func (m *ExplorationStore) Delete(ctx context.Context, e *mrfusion.Exploration) error {
delete(m.db, int(e.ID))
return nil
}
func (m *ExplorationStore) Delete(ctx context.Context, e mrfusion.Exploration) error {
delete(m.db, e.ID)
return nil
}
func (m *ExplorationStore) Get(ctx context.Context, ID int) (mrfusion.Exploration, error) {
e, ok := m.db[ID]
func (m *ExplorationStore) Get(ctx context.Context, ID mrfusion.ExplorationID) (*mrfusion.Exploration, error) {
e, ok := m.db[int(ID)]
if !ok {
return mrfusion.Exploration{}, fmt.Errorf("Unknown ID %d", ID)
return nil, fmt.Errorf("Unknown ID %d", ID)
}
return e, nil
}
func (m *ExplorationStore) Update(ctx context.Context, e mrfusion.Exploration) error {
_, ok := m.db[e.ID]
func (m *ExplorationStore) Update(ctx context.Context, e *mrfusion.Exploration) error {
_, ok := m.db[int(e.ID)]
if !ok {
return fmt.Errorf("Unknown ID %d", e.ID)
}
e.UpdatedAt = m.NowFunc()
m.db[e.ID] = e
m.db[int(e.ID)] = e
return nil
}

View File

@ -10,9 +10,12 @@ import (
type Permission string
type Permissions []Permission
// UserID is a unique ID for a source user.
type UserID int
// Represents an authenticated user.
type User struct {
ID int
ID UserID
Name string
Permissions Permissions
Roles []Role
@ -57,28 +60,32 @@ type AuthStore struct {
}
}
// ExplorationID is a unique ID for an Exploration.
type ExplorationID int
// Exploration is a serialization of front-end Data Explorer.
type Exploration struct {
ID int
Name string // User provided name of the exploration.
UserID int // UserID is the owner of this exploration.
Data string // Opaque blob of JSON data
CreatedAt time.Time // Time the exploration was first created
ID ExplorationID
Name string // User provided name of the Exploration.
UserID UserID // UserID is the owner of this Exploration.
Data string // Opaque blob of JSON data.
CreatedAt time.Time // Time the exploration was first created.
UpdatedAt time.Time // Latest time the exploration was updated.
Default bool // Flags an exploration as the default.
}
// ExplorationStore stores front-end serializations of explorater sessions
// ExplorationStore stores front-end serializations of data explorer sessions.
type ExplorationStore interface {
// Search the ExplorationStore for all explorations owned by userID.
Query(ctx context.Context, userID int) ([]Exploration, error)
// Create a new Exploration in the ExplorationStore
Add(context.Context, Exploration) error
// Delete the exploration from the ExplorationStore
Delete(context.Context, Exploration) error
// Retrieve an exploration if `ID` exists.
Get(ctx context.Context, ID int) (Exploration, error)
// Update the exploration; will update `UpdatedAt`.
Update(context.Context, Exploration) error
// Search the ExplorationStore for each Exploration owned by `UserID`.
Query(ctx context.Context, userID UserID) ([]*Exploration, error)
// Create a new Exploration in the ExplorationStore.
Add(context.Context, *Exploration) (*Exploration, error)
// Delete the Exploration from the ExplorationStore.
Delete(context.Context, *Exploration) error
// Retrieve an Exploration if `ID` exists.
Get(ctx context.Context, ID ExplorationID) (*Exploration, error)
// Update the Exploration; will also update the `UpdatedAt` time.
Update(context.Context, *Exploration) error
}
// Cell is a rectangle and multiple time series queries to visualize.