Merge pull request #159 from influxdata/feature/bolt-sources

Add persistence to sources and use bolt by default
pull/10616/head
Chris Goller 2016-09-30 18:27:53 -05:00 committed by GitHub
commit f5e81e0ee6
18 changed files with 597 additions and 142 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ node_modules
mrfusion
node_modules/
build/
chronograf.db

View File

@ -13,11 +13,13 @@ type Client struct {
Now func() time.Time
ExplorationStore *ExplorationStore
SourcesStore *SourcesStore
}
func NewClient() *Client {
c := &Client{Now: time.Now}
c.ExplorationStore = &ExplorationStore{client: c}
c.SourcesStore = &SourcesStore{client: c}
return c
}
@ -35,12 +37,18 @@ func (c *Client) Open() error {
if _, err := tx.CreateBucketIfNotExists(ExplorationBucket); err != nil {
return err
}
// Always create Sources bucket.
if _, err := tx.CreateBucketIfNotExists(SourcesBucket); err != nil {
return err
}
return nil
}); err != nil {
return err
}
c.ExplorationStore = &ExplorationStore{client: c}
c.SourcesStore = &SourcesStore{client: c}
return nil
}

40
bolt/client_test.go Normal file
View File

@ -0,0 +1,40 @@
package bolt_test
import (
"errors"
"io/ioutil"
"os"
"time"
"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()
}

View File

@ -1,47 +1,11 @@
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()

View File

@ -39,3 +39,33 @@ func UnmarshalExploration(data []byte, e *mrfusion.Exploration) error {
return nil
}
// MarshalSource encodes an source to binary protobuf format.
func MarshalSource(s mrfusion.Source) ([]byte, error) {
return proto.Marshal(&Source{
ID: int64(s.ID),
Name: s.Name,
Type: s.Type,
Username: s.Username,
Password: s.Password,
URLs: s.URL,
Default: s.Default,
})
}
// UnmarshalSource decodes an source from binary protobuf data.
func UnmarshalSource(data []byte, s *mrfusion.Source) error {
var pb Source
if err := proto.Unmarshal(data, &pb); err != nil {
return err
}
s.ID = int(pb.ID)
s.Name = pb.Name
s.Type = pb.Type
s.Username = pb.Username
s.Password = pb.Password
s.URL = pb.URLs
s.Default = pb.Default
return nil
}

View File

@ -10,6 +10,7 @@ It is generated from these files:
It has these top-level messages:
Exploration
Source
*/
package internal
@ -29,13 +30,13 @@ var _ = math.Inf
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"`
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
UserID int64 `protobuf:"varint,3,opt,name=UserID,json=userID,proto3" json:"UserID,omitempty"`
Data string `protobuf:"bytes,4,opt,name=Data,json=data,proto3" json:"Data,omitempty"`
CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,json=createdAt,proto3" json:"CreatedAt,omitempty"`
UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,json=updatedAt,proto3" json:"UpdatedAt,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,json=default,proto3" json:"Default,omitempty"`
}
func (m *Exploration) Reset() { *m = Exploration{} }
@ -43,23 +44,45 @@ func (m *Exploration) String() string { return proto.CompactTextStrin
func (*Exploration) ProtoMessage() {}
func (*Exploration) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
type Source struct {
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
Type string `protobuf:"bytes,3,opt,name=Type,json=type,proto3" json:"Type,omitempty"`
Username string `protobuf:"bytes,4,opt,name=Username,json=username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,5,opt,name=Password,json=password,proto3" json:"Password,omitempty"`
URLs []string `protobuf:"bytes,6,rep,name=URLs,json=uRLs" json:"URLs,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,json=default,proto3" json:"Default,omitempty"`
}
func (m *Source) Reset() { *m = Source{} }
func (m *Source) String() string { return proto.CompactTextString(m) }
func (*Source) ProtoMessage() {}
func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} }
func init() {
proto.RegisterType((*Exploration)(nil), "internal.Exploration")
proto.RegisterType((*Source)(nil), "internal.Source")
}
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,
// 257 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x90, 0xcf, 0x4a, 0xf4, 0x30,
0x14, 0xc5, 0x49, 0xdb, 0x2f, 0x6d, 0xef, 0x07, 0xb3, 0xc8, 0x42, 0x82, 0xb8, 0x28, 0xb3, 0xea,
0xca, 0x8d, 0x4f, 0x20, 0xd6, 0xc5, 0xc0, 0x20, 0x12, 0xed, 0x03, 0x5c, 0xa7, 0x57, 0x28, 0x74,
0x9a, 0x90, 0x3f, 0xe8, 0xbc, 0x90, 0x2f, 0xe0, 0x0b, 0x4a, 0xd2, 0x76, 0x29, 0xb8, 0x3c, 0xf7,
0x77, 0x38, 0xfc, 0x12, 0xd8, 0x8d, 0xb3, 0x27, 0x3b, 0xe3, 0x74, 0x6b, 0xac, 0xf6, 0x5a, 0x54,
0x5b, 0xde, 0x7f, 0x33, 0xf8, 0xff, 0xf8, 0x69, 0x26, 0x6d, 0xd1, 0x8f, 0x7a, 0x16, 0x3b, 0xc8,
0x0e, 0x9d, 0x64, 0x0d, 0x6b, 0x73, 0x95, 0x8d, 0x9d, 0x10, 0x50, 0x3c, 0xe1, 0x99, 0x64, 0xd6,
0xb0, 0xb6, 0x56, 0xc5, 0x8c, 0x67, 0x12, 0x57, 0xc0, 0x7b, 0x47, 0xf6, 0xd0, 0xc9, 0x3c, 0xf5,
0x78, 0x48, 0x29, 0x76, 0x3b, 0xf4, 0x28, 0x8b, 0xa5, 0x3b, 0xa0, 0x47, 0x71, 0x03, 0xf5, 0x83,
0x25, 0xf4, 0x34, 0xdc, 0x7b, 0xf9, 0x2f, 0xd5, 0xeb, 0xd3, 0x76, 0x88, 0xb4, 0x37, 0xc3, 0x4a,
0xf9, 0x42, 0xc3, 0x76, 0x10, 0x12, 0xca, 0x8e, 0xde, 0x31, 0x4c, 0x5e, 0x96, 0x0d, 0x6b, 0x2b,
0x55, 0x0e, 0x4b, 0xdc, 0x7f, 0x31, 0xe0, 0x2f, 0x3a, 0xd8, 0x13, 0xfd, 0x49, 0x58, 0x40, 0xf1,
0x7a, 0x31, 0x94, 0x74, 0x6b, 0x55, 0xf8, 0x8b, 0x21, 0x71, 0x0d, 0x55, 0x7c, 0x44, 0xe4, 0xab,
0x70, 0x15, 0xd6, 0x1c, 0xd9, 0x33, 0x3a, 0xf7, 0xa1, 0xed, 0x90, 0x9c, 0x6b, 0x55, 0x99, 0x35,
0xc7, 0xad, 0x5e, 0x1d, 0x9d, 0xe4, 0x4d, 0x1e, 0xb7, 0x82, 0x3a, 0xba, 0xdf, 0x45, 0xdf, 0x78,
0xfa, 0xef, 0xbb, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x58, 0x30, 0x04, 0x81, 0x01, 0x00,
0x00,
}

View File

@ -2,11 +2,21 @@ 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 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.
bool Default = 7; // Flags an exploration as the default.
}
message Source {
int64 ID = 1; // ID is the unique ID of the source
string Name = 2; // Name is the user-defined name for the source
string Type = 3; // Type specifies which kinds of source (enterprise vs oss)
string Username = 4; // Username is the username to connect to the source
string Password = 5;
repeated string URLs = 6; // URL are the connections to the source
bool Default = 7; // Flags an exploration as the default.
}

View File

@ -29,3 +29,24 @@ func TestMarshalExploration(t *testing.T) {
t.Fatalf("exploration protobuf copy error: got %#v, expected %#v", vv, v)
}
}
func TestMarshalSource(t *testing.T) {
v := mrfusion.Source{
ID: 12,
Name: "Fountain of Truth",
Type: "influx",
Username: "docbrown",
Password: "1 point twenty-one g1g@w@tts",
URL: []string{"http://twin-pines.mall.io:8086", "https://lonepine.mall.io:8086"},
Default: true,
}
var vv mrfusion.Source
if buf, err := internal.MarshalSource(v); err != nil {
t.Fatal(err)
} else if err := internal.UnmarshalSource(buf, &vv); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(v, vv) {
t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, v)
}
}

116
bolt/sources.go Normal file
View File

@ -0,0 +1,116 @@
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.SourcesStore = &SourcesStore{}
var SourcesBucket = []byte("Sources")
type SourcesStore struct {
client *Client
}
// All returns all known sources
func (s *SourcesStore) All(ctx context.Context) ([]mrfusion.Source, error) {
var srcs []mrfusion.Source
if err := s.client.db.View(func(tx *bolt.Tx) error {
if err := tx.Bucket(SourcesBucket).ForEach(func(k, v []byte) error {
var src mrfusion.Source
if err := internal.UnmarshalSource(v, &src); err != nil {
return err
}
srcs = append(srcs, src)
return nil
}); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return srcs, nil
}
// Add creates a new Source in the SourceStore.
func (s *SourcesStore) Add(ctx context.Context, src mrfusion.Source) (mrfusion.Source, error) {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(SourcesBucket)
seq, err := b.NextSequence()
if err != nil {
return err
}
src.ID = int(seq)
if v, err := internal.MarshalSource(src); err != nil {
return err
} else if err := b.Put(itob(src.ID), v); err != nil {
return err
}
return nil
}); err != nil {
return mrfusion.Source{}, err
}
return src, nil
}
// Delete removes the Source from the SourcesStore
func (s *SourcesStore) Delete(ctx context.Context, src mrfusion.Source) error {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
if err := tx.Bucket(SourcesBucket).Delete(itob(src.ID)); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// Get returns a Source if the id exists.
func (s *SourcesStore) Get(ctx context.Context, id int) (mrfusion.Source, error) {
var src mrfusion.Source
if err := s.client.db.View(func(tx *bolt.Tx) error {
if v := tx.Bucket(SourcesBucket).Get(itob(id)); v == nil {
return mrfusion.ErrSourceNotFound
} else if err := internal.UnmarshalSource(v, &src); err != nil {
return err
}
return nil
}); err != nil {
return mrfusion.Source{}, err
}
return src, nil
}
// Update a Source
func (s *SourcesStore) Update(ctx context.Context, src mrfusion.Source) error {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
// Get an existing soource with the same ID.
b := tx.Bucket(SourcesBucket)
if v := b.Get(itob(src.ID)); v == nil {
return mrfusion.ErrSourceNotFound
}
if v, err := internal.MarshalSource(src); err != nil {
return err
} else if err := b.Put(itob(src.ID), v); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}

95
bolt/sources_test.go Normal file
View File

@ -0,0 +1,95 @@
package bolt_test
import (
// "fmt"
"reflect"
"testing"
"github.com/influxdata/mrfusion"
)
// Ensure an SourceStore can store, retrieve, update, and delete explorations.
func TestSourceStore(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.SourcesStore
srcs := []mrfusion.Source{
mrfusion.Source{
Name: "Of Truth",
Type: "influx",
Username: "marty",
Password: "I❤ jennifer parker",
URL: []string{"toyota-hilux.lyon-estates.local", "lake.hilldale.local"},
Default: true,
},
mrfusion.Source{
Name: "HipToBeSquare",
Type: "influx",
Username: "calvinklein",
Password: "chuck b3rry",
URL: []string{"toyota-hilux.lyon-estates.local", "lake.hilldale.local"},
Default: true,
},
}
// Add new srcs.
for i, src := range srcs {
if srcs[i], err = s.Add(nil, src); err != nil {
t.Fatal(err)
}
// Confirm first src in the store is the same as the original.
if actual, err := s.Get(nil, srcs[i].ID); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(actual, srcs[i]) {
t.Fatal("source loaded is different then source saved; actual: %v, expected %v", actual, srcs[i])
}
}
// Update source.
srcs[0].Username = "calvinklein"
srcs[1].Name = "Enchantment Under the Sea Dance"
if err := s.Update(nil, srcs[0]); err != nil {
t.Fatal(err)
} else if err := s.Update(nil, srcs[1]); err != nil {
t.Fatal(err)
}
// Confirm sources have updated.
if src, err := s.Get(nil, srcs[0].ID); err != nil {
t.Fatal(err)
} else if src.Username != "calvinklein" {
t.Fatalf("source 0 update error: got %v, expected %v", src.Username, "calvinklein")
}
if src, err := s.Get(nil, srcs[1].ID); err != nil {
t.Fatal(err)
} else if src.Name != "Enchantment Under the Sea Dance" {
t.Fatalf("source 1 update error: got %v, expected %v", src.Name, "Enchantment Under the Sea Dance")
}
// Delete an source.
if err := s.Delete(nil, srcs[0]); err != nil {
t.Fatal(err)
}
// Confirm source has been deleted.
if _, err := s.Get(nil, srcs[0].ID); err != mrfusion.ErrSourceNotFound {
t.Fatalf("source delete error: got %v, expected %v", err, mrfusion.ErrSourceNotFound)
}
if bsrcs, err := s.All(nil); err != nil {
t.Fatal(err)
} else if len(bsrcs) != 1 {
t.Fatalf("After delete All returned incorrect number of srcs; got %d, expected %d", len(bsrcs), 1)
} else if !reflect.DeepEqual(bsrcs[0], srcs[1]) {
t.Fatalf("After delete All returned incorrect source; got %v, expected %v", bsrcs[0], srcs[1])
}
}

View File

@ -4,6 +4,7 @@ package mrfusion
const (
ErrUpstreamTimeout = Error("request to backend timed out")
ErrExplorationNotFound = Error("exploration not found")
ErrSourceNotFound = Error("source not found")
)
// Error is a domain error encountered while processing mrfusion requests

View File

@ -14,11 +14,7 @@ import (
op "github.com/influxdata/mrfusion/restapi/operations"
)
type ExplorationStore struct {
ExplorationStore mrfusion.ExplorationStore
}
func (h *ExplorationStore) Explorations(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
func (h *Store) Explorations(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
uID, err := strconv.Atoi(params.UserID)
if err != nil {
log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
@ -26,18 +22,18 @@ func (h *ExplorationStore) Explorations(ctx context.Context, params op.GetSource
return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
}
exs, err := h.ExplorationStore.Query(ctx, mrfusion.UserID(uID))
mrExs, err := h.ExplorationStore.Query(ctx, mrfusion.UserID(uID))
if err != nil {
log.Printf("Error: Unknown response from store while querying UserID: %s: %v", params.UserID, err)
errMsg := &models.Error{Code: 500, Message: "Error: Unknown response from store while querying UserID"}
return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
}
res := &models.Explorations{}
for _, e := range exs {
exs := make([]*models.Exploration, len(mrExs))
for _, e := range mrExs {
rel := "self"
href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, e.ID)
res.Explorations = append(res.Explorations, &models.Exploration{
exs = append(exs, &models.Exploration{
Data: e.Data,
Name: e.Name,
UpdatedAt: strfmt.DateTime(e.UpdatedAt),
@ -49,10 +45,13 @@ func (h *ExplorationStore) Explorations(ctx context.Context, params op.GetSource
},
)
}
res := &models.Explorations{
Explorations: exs,
}
return op.NewGetSourcesIDUsersUserIDExplorationsOK().WithPayload(res)
}
func (h *ExplorationStore) Exploration(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
func (h *Store) Exploration(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
eID, err := strconv.Atoi(params.ExplorationID)
if err != nil {
log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)
@ -95,7 +94,7 @@ func (h *ExplorationStore) Exploration(ctx context.Context, params op.GetSources
return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDOK().WithPayload(res)
}
func (h *ExplorationStore) UpdateExploration(ctx context.Context, params op.PatchSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
func (h *Store) UpdateExploration(ctx context.Context, params op.PatchSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
if params.Exploration == nil {
log.Printf("Error: Exploration is nil")
errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"}
@ -140,7 +139,7 @@ func (h *ExplorationStore) UpdateExploration(ctx context.Context, params op.Patc
return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNoContent()
}
func (h *ExplorationStore) NewExploration(ctx context.Context, params op.PostSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
func (h *Store) NewExploration(ctx context.Context, params op.PostSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
if params.Exploration == nil {
log.Printf("Error: Exploration is nil")
errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"}
@ -185,7 +184,7 @@ func (h *ExplorationStore) NewExploration(ctx context.Context, params op.PostSou
}
func (h *ExplorationStore) DeleteExploration(ctx context.Context, params op.DeleteSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
func (h *Store) DeleteExploration(ctx context.Context, params op.DeleteSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
eID, err := strconv.Atoi(params.ExplorationID)
if err != nil {
log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)

141
handlers/sources.go Normal file
View File

@ -0,0 +1,141 @@
package handlers
import (
"fmt"
"strconv"
"github.com/go-openapi/runtime/middleware"
"github.com/influxdata/mrfusion"
"github.com/influxdata/mrfusion/models"
op "github.com/influxdata/mrfusion/restapi/operations"
"golang.org/x/net/context"
)
func (h *Store) NewSource(ctx context.Context, params op.PostSourcesParams) middleware.Responder {
src := mrfusion.Source{
Name: *params.Source.Name,
Type: params.Source.Type,
Username: params.Source.Username,
Password: params.Source.Password,
URL: []string{*params.Source.URL},
Default: params.Source.Default,
}
var err error
if src, err = h.SourcesStore.Add(ctx, src); err != nil {
errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error storing source %v: %v", params.Source, err)}
return op.NewPostSourcesDefault(500).WithPayload(errMsg)
}
mSrc := mrToModel(src)
return op.NewPostSourcesCreated().WithPayload(mSrc).WithLocation(mSrc.Links.Self)
}
func srcLinks(id int) *models.SourceLinks {
return &models.SourceLinks{
Self: fmt.Sprintf("/chronograf/v1/sources/%d", id),
Proxy: fmt.Sprintf("/chronograf/v1/sources/%d/proxy", id),
Users: fmt.Sprintf("/chronograf/v1/sources/%d/users", id),
Roles: fmt.Sprintf("/chronograf/v1/sources/%d/roles", id),
Permissions: fmt.Sprintf("/chronograf/v1/sources/%d/permissions", id),
}
}
func mrToModel(src mrfusion.Source) *models.Source {
return &models.Source{
ID: strconv.Itoa(src.ID),
Links: srcLinks(src.ID),
Name: &src.Name,
Type: src.Type,
Username: src.Username,
Password: src.Password,
URL: &src.URL[0],
Default: src.Default,
}
}
func (h *Store) Sources(ctx context.Context, params op.GetSourcesParams) middleware.Responder {
mrSrcs, err := h.SourcesStore.All(ctx)
if err != nil {
errMsg := &models.Error{Code: 500, Message: "Error loading sources"}
return op.NewGetSourcesDefault(500).WithPayload(errMsg)
}
srcs := make([]*models.Source, len(mrSrcs))
for i, src := range mrSrcs {
srcs[i] = mrToModel(src)
}
res := &models.Sources{
Sources: srcs,
}
return op.NewGetSourcesOK().WithPayload(res)
}
func (h *Store) SourcesID(ctx context.Context, params op.GetSourcesIDParams) middleware.Responder {
id, err := strconv.Atoi(params.ID)
if err != nil {
errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)}
return op.NewGetSourcesIDDefault(500).WithPayload(errMsg)
}
src, err := h.SourcesStore.Get(ctx, id)
if err != nil {
errMsg := &models.Error{Code: 404, Message: fmt.Sprintf("Unknown ID %s", params.ID)}
return op.NewGetSourcesIDNotFound().WithPayload(errMsg)
}
return op.NewGetSourcesIDOK().WithPayload(mrToModel(src))
}
func (h *Store) RemoveSource(ctx context.Context, params op.DeleteSourcesIDParams) middleware.Responder {
id, err := strconv.Atoi(params.ID)
if err != nil {
errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)}
return op.NewDeleteSourcesIDDefault(500).WithPayload(errMsg)
}
src := mrfusion.Source{
ID: id,
}
if err = h.SourcesStore.Delete(ctx, src); err != nil {
errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Unknown error deleting source %s", params.ID)}
return op.NewDeleteSourcesIDDefault(500).WithPayload(errMsg)
}
return op.NewDeleteSourcesIDNoContent()
}
func (h *Store) UpdateSource(ctx context.Context, params op.PatchSourcesIDParams) middleware.Responder {
id, err := strconv.Atoi(params.ID)
if err != nil {
errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)}
return op.NewPatchSourcesIDDefault(500).WithPayload(errMsg)
}
src, err := h.SourcesStore.Get(ctx, id)
if err != nil {
errMsg := &models.Error{Code: 404, Message: fmt.Sprintf("Unknown ID %s", params.ID)}
return op.NewPatchSourcesIDNotFound().WithPayload(errMsg)
}
src.Default = params.Config.Default
if params.Config.Name != nil {
src.Name = *params.Config.Name
}
if params.Config.Password != "" {
src.Password = params.Config.Password
}
if params.Config.Username != "" {
// TODO: Change to bolt when finished
src.Username = params.Config.Username
}
if params.Config.URL != nil {
src.URL = []string{*params.Config.URL}
}
if params.Config.Type != "" {
src.Type = params.Config.Type
}
if err := h.SourcesStore.Update(ctx, src); err != nil {
errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error updating source ID %s", params.ID)}
return op.NewPatchSourcesIDDefault(500).WithPayload(errMsg)
}
return op.NewPatchSourcesIDNoContent()
}

9
handlers/store.go Normal file
View File

@ -0,0 +1,9 @@
package handlers
import "github.com/influxdata/mrfusion"
// Store handles REST calls to the persistence
type Store struct {
ExplorationStore mrfusion.ExplorationStore
SourcesStore mrfusion.SourcesStore
}

View File

@ -8,6 +8,7 @@ import (
"github.com/go-openapi/swag"
"github.com/go-openapi/errors"
"github.com/go-openapi/validate"
)
/*Explorations explorations
@ -17,8 +18,10 @@ swagger:model Explorations
type Explorations struct {
/* explorations
*/
Explorations []*Exploration `json:"explorations,omitempty"`
Required: true
*/
Explorations []*Exploration `json:"explorations"`
}
// Validate validates this explorations
@ -38,8 +41,8 @@ func (m *Explorations) Validate(formats strfmt.Registry) error {
func (m *Explorations) validateExplorations(formats strfmt.Registry) error {
if swag.IsZero(m.Explorations) { // not required
return nil
if err := validate.Required("explorations", "body", m.Explorations); err != nil {
return err
}
for i := 0; i < len(m.Explorations); i++ {

View File

@ -18,7 +18,7 @@ import (
"github.com/influxdata/mrfusion/handlers"
"github.com/influxdata/mrfusion/influx"
"github.com/influxdata/mrfusion/mock"
"github.com/influxdata/mrfusion/restapi/operations"
op "github.com/influxdata/mrfusion/restapi/operations"
)
// This file is safe to edit. Once it exists it will not be overwritten
@ -29,26 +29,17 @@ var devFlags = struct {
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
}{}
var influxFlags = struct {
Server string `short:"s" long:"server" description:"Full URL of InfluxDB server (http://localhost:8086)" env:"INFLUX_HOST"`
}{}
var storeFlags = struct {
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (/Users/somebody/mrfusion.db)" env:"BOLT_PATH"`
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (/Users/somebody/mrfusion.db)" env:"BOLT_PATH" default:"chronograf.db"`
}{}
func configureFlags(api *operations.MrFusionAPI) {
func configureFlags(api *op.MrFusionAPI) {
api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{
swag.CommandLineOptionsGroup{
ShortDescription: "Develop Mode server",
LongDescription: "Server will use the ui/build directory directly.",
Options: &devFlags,
},
swag.CommandLineOptionsGroup{
ShortDescription: "Default Time Series Backend",
LongDescription: "Specify the url of an InfluxDB server",
Options: &influxFlags,
},
swag.CommandLineOptionsGroup{
ShortDescription: "Default Store Backend",
LongDescription: "Specify the path to a BoltDB file",
@ -70,7 +61,7 @@ func assets() mrfusion.Assets {
}
}
func configureAPI(api *operations.MrFusionAPI) http.Handler {
func configureAPI(api *op.MrFusionAPI) http.Handler {
// configure the api here
api.ServeError = errors.ServeError
@ -86,7 +77,7 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler {
mockHandler := mock.NewHandler()
api.GetHandler = operations.GetHandlerFunc(mockHandler.AllRoutes)
api.GetHandler = op.GetHandlerFunc(mockHandler.AllRoutes)
if len(storeFlags.BoltPath) > 0 {
c := bolt.NewClient()
@ -94,102 +85,103 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler {
if err := c.Open(); err != nil {
panic(err)
}
h := handlers.ExplorationStore{
h := handlers.Store{
ExplorationStore: c.ExplorationStore,
SourcesStore: c.SourcesStore,
}
api.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.DeleteExploration)
api.GetSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.GetSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.Exploration)
api.GetSourcesIDUsersUserIDExplorationsHandler = operations.GetSourcesIDUsersUserIDExplorationsHandlerFunc(h.Explorations)
api.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.UpdateExploration)
api.PostSourcesIDUsersUserIDExplorationsHandler = operations.PostSourcesIDUsersUserIDExplorationsHandlerFunc(h.NewExploration)
api.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandler = op.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.DeleteExploration)
api.GetSourcesIDUsersUserIDExplorationsExplorationIDHandler = op.GetSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.Exploration)
api.GetSourcesIDUsersUserIDExplorationsHandler = op.GetSourcesIDUsersUserIDExplorationsHandlerFunc(h.Explorations)
api.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandler = op.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(h.UpdateExploration)
api.PostSourcesIDUsersUserIDExplorationsHandler = op.PostSourcesIDUsersUserIDExplorationsHandlerFunc(h.NewExploration)
api.DeleteSourcesIDHandler = op.DeleteSourcesIDHandlerFunc(h.RemoveSource)
api.PatchSourcesIDHandler = op.PatchSourcesIDHandlerFunc(h.UpdateSource)
api.GetSourcesHandler = op.GetSourcesHandlerFunc(h.Sources)
api.GetSourcesIDHandler = op.GetSourcesIDHandlerFunc(h.SourcesID)
api.PostSourcesHandler = op.PostSourcesHandlerFunc(h.NewSource)
ts := influx.Client{}
p := handlers.InfluxProxy{
Srcs: c.SourcesStore,
TimeSeries: &ts,
}
api.PostSourcesIDProxyHandler = op.PostSourcesIDProxyHandlerFunc(p.Proxy)
} else {
api.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.DeleteExploration)
api.GetSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.GetSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.Exploration)
api.GetSourcesIDUsersUserIDExplorationsHandler = operations.GetSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.Explorations)
api.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandler = operations.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.UpdateExploration)
api.PostSourcesIDUsersUserIDExplorationsHandler = operations.PostSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.NewExploration)
api.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandler = op.DeleteSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.DeleteExploration)
api.GetSourcesIDUsersUserIDExplorationsExplorationIDHandler = op.GetSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.Exploration)
api.GetSourcesIDUsersUserIDExplorationsHandler = op.GetSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.Explorations)
api.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandler = op.PatchSourcesIDUsersUserIDExplorationsExplorationIDHandlerFunc(mockHandler.UpdateExploration)
api.PostSourcesIDUsersUserIDExplorationsHandler = op.PostSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.NewExploration)
api.DeleteSourcesIDHandler = op.DeleteSourcesIDHandlerFunc(mockHandler.RemoveSource)
api.PatchSourcesIDHandler = op.PatchSourcesIDHandlerFunc(mockHandler.UpdateSource)
api.GetSourcesHandler = op.GetSourcesHandlerFunc(mockHandler.Sources)
api.GetSourcesIDHandler = op.GetSourcesIDHandlerFunc(mockHandler.SourcesID)
api.PostSourcesHandler = op.PostSourcesHandlerFunc(mockHandler.NewSource)
api.PostSourcesIDProxyHandler = op.PostSourcesIDProxyHandlerFunc(mockHandler.Proxy)
}
api.DeleteSourcesIDHandler = operations.DeleteSourcesIDHandlerFunc(mockHandler.RemoveSource)
api.PatchSourcesIDHandler = operations.PatchSourcesIDHandlerFunc(mockHandler.UpdateSource)
api.GetSourcesHandler = operations.GetSourcesHandlerFunc(mockHandler.Sources)
api.GetSourcesIDHandler = operations.GetSourcesIDHandlerFunc(mockHandler.SourcesID)
api.PostSourcesHandler = operations.PostSourcesHandlerFunc(mockHandler.NewSource)
if len(influxFlags.Server) > 0 {
c, err := influx.NewClient(influxFlags.Server)
if err != nil {
panic(err)
}
// TODO: Change to bolt when finished
h := handlers.InfluxProxy{
Srcs: mock.DefaultSourcesStore,
TimeSeries: c,
}
api.PostSourcesIDProxyHandler = operations.PostSourcesIDProxyHandlerFunc(h.Proxy)
} else {
api.PostSourcesIDProxyHandler = operations.PostSourcesIDProxyHandlerFunc(mockHandler.Proxy)
}
api.DeleteSourcesIDRolesRoleIDHandler = operations.DeleteSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params operations.DeleteSourcesIDRolesRoleIDParams) middleware.Responder {
api.DeleteSourcesIDRolesRoleIDHandler = op.DeleteSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params op.DeleteSourcesIDRolesRoleIDParams) middleware.Responder {
return middleware.NotImplemented("operation .DeleteSourcesIDRolesRoleID has not yet been implemented")
})
api.DeleteSourcesIDUsersUserIDHandler = operations.DeleteSourcesIDUsersUserIDHandlerFunc(func(ctx context.Context, params operations.DeleteSourcesIDUsersUserIDParams) middleware.Responder {
api.DeleteSourcesIDUsersUserIDHandler = op.DeleteSourcesIDUsersUserIDHandlerFunc(func(ctx context.Context, params op.DeleteSourcesIDUsersUserIDParams) middleware.Responder {
return middleware.NotImplemented("operation .DeleteSourcesIDUsersUserID has not yet been implemented")
})
api.DeleteDashboardsIDHandler = operations.DeleteDashboardsIDHandlerFunc(func(ctx context.Context, params operations.DeleteDashboardsIDParams) middleware.Responder {
api.DeleteDashboardsIDHandler = op.DeleteDashboardsIDHandlerFunc(func(ctx context.Context, params op.DeleteDashboardsIDParams) middleware.Responder {
return middleware.NotImplemented("operation .DeleteDashboardsID has not yet been implemented")
})
api.GetDashboardsHandler = operations.GetDashboardsHandlerFunc(func(ctx context.Context, params operations.GetDashboardsParams) middleware.Responder {
api.GetDashboardsHandler = op.GetDashboardsHandlerFunc(func(ctx context.Context, params op.GetDashboardsParams) middleware.Responder {
return middleware.NotImplemented("operation .GetDashboards has not yet been implemented")
})
api.GetDashboardsIDHandler = operations.GetDashboardsIDHandlerFunc(func(ctx context.Context, params operations.GetDashboardsIDParams) middleware.Responder {
api.GetDashboardsIDHandler = op.GetDashboardsIDHandlerFunc(func(ctx context.Context, params op.GetDashboardsIDParams) middleware.Responder {
return middleware.NotImplemented("operation .GetDashboardsID has not yet been implemented")
})
api.GetSourcesIDPermissionsHandler = operations.GetSourcesIDPermissionsHandlerFunc(func(ctx context.Context, params operations.GetSourcesIDPermissionsParams) middleware.Responder {
api.GetSourcesIDPermissionsHandler = op.GetSourcesIDPermissionsHandlerFunc(func(ctx context.Context, params op.GetSourcesIDPermissionsParams) middleware.Responder {
return middleware.NotImplemented("operation .GetSourcesIDPermissions has not yet been implemented")
})
api.GetSourcesIDRolesHandler = operations.GetSourcesIDRolesHandlerFunc(func(ctx context.Context, params operations.GetSourcesIDRolesParams) middleware.Responder {
api.GetSourcesIDRolesHandler = op.GetSourcesIDRolesHandlerFunc(func(ctx context.Context, params op.GetSourcesIDRolesParams) middleware.Responder {
return middleware.NotImplemented("operation .GetSourcesIDRoles has not yet been implemented")
})
api.GetSourcesIDRolesRoleIDHandler = operations.GetSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params operations.GetSourcesIDRolesRoleIDParams) middleware.Responder {
api.GetSourcesIDRolesRoleIDHandler = op.GetSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params op.GetSourcesIDRolesRoleIDParams) middleware.Responder {
return middleware.NotImplemented("operation .GetSourcesIDRolesRoleID has not yet been implemented")
})
api.GetSourcesIDUsersHandler = operations.GetSourcesIDUsersHandlerFunc(func(ctx context.Context, params operations.GetSourcesIDUsersParams) middleware.Responder {
api.GetSourcesIDUsersHandler = op.GetSourcesIDUsersHandlerFunc(func(ctx context.Context, params op.GetSourcesIDUsersParams) middleware.Responder {
return middleware.NotImplemented("operation .GetSourcesIDUsers has not yet been implemented")
})
api.GetSourcesIDUsersUserIDHandler = operations.GetSourcesIDUsersUserIDHandlerFunc(func(ctx context.Context, params operations.GetSourcesIDUsersUserIDParams) middleware.Responder {
api.GetSourcesIDUsersUserIDHandler = op.GetSourcesIDUsersUserIDHandlerFunc(func(ctx context.Context, params op.GetSourcesIDUsersUserIDParams) middleware.Responder {
return middleware.NotImplemented("operation .GetSourcesIDUsersUserID has not yet been implemented")
})
api.PatchSourcesIDRolesRoleIDHandler = operations.PatchSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params operations.PatchSourcesIDRolesRoleIDParams) middleware.Responder {
api.PatchSourcesIDRolesRoleIDHandler = op.PatchSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params op.PatchSourcesIDRolesRoleIDParams) middleware.Responder {
return middleware.NotImplemented("operation .PatchSourcesIDRolesRoleID has not yet been implemented")
})
api.PatchSourcesIDUsersUserIDHandler = operations.PatchSourcesIDUsersUserIDHandlerFunc(func(ctx context.Context, params operations.PatchSourcesIDUsersUserIDParams) middleware.Responder {
api.PatchSourcesIDUsersUserIDHandler = op.PatchSourcesIDUsersUserIDHandlerFunc(func(ctx context.Context, params op.PatchSourcesIDUsersUserIDParams) middleware.Responder {
return middleware.NotImplemented("operation .PatchSourcesIDUsersUserID has not yet been implemented")
})
api.PostDashboardsHandler = operations.PostDashboardsHandlerFunc(func(ctx context.Context, params operations.PostDashboardsParams) middleware.Responder {
api.PostDashboardsHandler = op.PostDashboardsHandlerFunc(func(ctx context.Context, params op.PostDashboardsParams) middleware.Responder {
return middleware.NotImplemented("operation .PostDashboards has not yet been implemented")
})
api.PostSourcesIDRolesHandler = operations.PostSourcesIDRolesHandlerFunc(func(ctx context.Context, params operations.PostSourcesIDRolesParams) middleware.Responder {
api.PostSourcesIDRolesHandler = op.PostSourcesIDRolesHandlerFunc(func(ctx context.Context, params op.PostSourcesIDRolesParams) middleware.Responder {
return middleware.NotImplemented("operation .PostSourcesIDRoles has not yet been implemented")
})
api.PostSourcesIDUsersHandler = operations.PostSourcesIDUsersHandlerFunc(func(ctx context.Context, params operations.PostSourcesIDUsersParams) middleware.Responder {
api.PostSourcesIDUsersHandler = op.PostSourcesIDUsersHandlerFunc(func(ctx context.Context, params op.PostSourcesIDUsersParams) middleware.Responder {
return middleware.NotImplemented("operation .PostSourcesIDUsers has not yet been implemented")
})
api.PutDashboardsIDHandler = operations.PutDashboardsIDHandlerFunc(func(ctx context.Context, params operations.PutDashboardsIDParams) middleware.Responder {
api.PutDashboardsIDHandler = op.PutDashboardsIDHandlerFunc(func(ctx context.Context, params op.PutDashboardsIDParams) middleware.Responder {
return middleware.NotImplemented("operation .PutDashboardsID has not yet been implemented")
})
api.GetSourcesIDMonitoredHandler = operations.GetSourcesIDMonitoredHandlerFunc(mockHandler.MonitoredServices)
api.GetSourcesIDMonitoredHandler = op.GetSourcesIDMonitoredHandlerFunc(mockHandler.MonitoredServices)
api.ServerShutdown = func() {}

View File

@ -820,6 +820,8 @@ definitions:
type: object
Explorations:
type: object
required:
- explorations
properties:
explorations:
type: array

View File

@ -238,7 +238,7 @@ export function fetchExplorers({source, userID, explorerURI, push}) {
// Create a new explorer session for a user if they don't have any
// saved (e.g. when they visit for the first time).
if (!explorers.length) {
dispatch(createExploration(push));
dispatch(createExploration(source, push));
return;
}