Merge pull request #142 from influxdata/feature/exploration-store
Implement exploration storepull/10616/head
commit
3903784728
2
Godeps
2
Godeps
|
@ -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
|
||||
|
|
3
Makefile
3
Makefile
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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.
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
36
mock/mock.go
36
mock/mock.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
41
stores.go
41
stores.go
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue