Skip to content

Commit

Permalink
feat(ChangeRequest): add support for change requests to query reposit…
Browse files Browse the repository at this point in the history
…ories

In order go give users the capacity to either reject or allow a change based on shifts to a specified url, we need the concept of a "request" that can be either accepted or rejected. Accepting a request means adding the change to a requested history. Rejecting it should mark the change rejected in some way.
  • Loading branch information
b5 committed Nov 7, 2017
1 parent f9a3938 commit eba76ea
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 26 deletions.
1 change: 0 additions & 1 deletion api/handlers/datasets.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ func (h *DatasetHandlers) deleteDatasetHandler(w http.ResponseWriter, r *http.Re

func (h *DatasetHandlers) getStructuredDataHandler(w http.ResponseWriter, r *http.Request) {
listParams := core.ListParamsFromRequest(r)
page := listParams.Page()
all, err := util.ReqParamBool("all", r)
if err != nil {
all = false
Expand Down
108 changes: 108 additions & 0 deletions repo/change_requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package repo

import (
"time"

"github.com/ipfs/go-datastore"
"github.com/qri-io/dataset"
)

// ChangeRequestStore is the interface for storying & manipulating Change Requests
// Qri repos should embed a ChangeRequestStore
type ChangeRequestStore interface {
// Put a change request into the store
PutChangeRequest(path datastore.Key, cr *ChangeRequest) error
// Get a change request by it's path
GetChangeRequest(path datastore.Key) (*ChangeRequest, error)
// accept an open change request
// AcceptChangeRequest(path datastore.Key) error
// decline an open change request
// DeclineChangeRequest(path datastore.Key) error
// get change requests for a given target
ChangeRequestsForTarget(target datastore.Key, limit, offset int) ([]*ChangeRequest, error)
// list change requests in this store
ListChangeRequests(limit, offset int) ([]*ChangeRequest, error)
}

const (
// open change requests haven't been addressed yet
ChangeRequestStatusOpen = "open"
// accepted change requests have been merged into
// the target history
ChangeRequestStatusAccepted = "accepted"
// declined change requests will not be merged into
// the target history
ChangeRequestStatusDeclined = "declined"
)

// ChangeRequests are proposed additions to the history of a given dataset
type ChangeRequest struct {
// status of this request. one of: open,accepted,declined
Status string `json:"status"`
// created marks the time this change request was created
Created time.Time `json:"created"`
// the dataset this change is aimed at. The
// history of target must match the history
// of change up until new history entries
// TODO - should changes be targeting a mutable history?
Target datastore.Key `json:"target"`
// path to HEAD of the change history
Path datastore.Key `json:"path"`
// The actual change history. All relevant details must be stored
// in the dataset itself. Title & description of the change goes
// into this dataset's commit.
Change *dataset.Dataset `json:"change"`
}

// accept an open change request, advancing the name of the dataset
// that refer to the target path to the newly-added history
func AcceptChangeRequest(r Repo, path datastore.Key) (err error) {
cr, err := r.GetChangeRequest(path)
if err != nil {
return err
}

cr.Status = ChangeRequestStatusAccepted
if err := r.PutChangeRequest(path, cr); err != nil {
return err
}

// TODO - place all datasets related to this history chain in the store
ds := &dataset.Dataset{Previous: path}
for {
if ds.Previous.Equal(cr.Target) {
break
}
// datasets can sometimes resolve over the netowork, so this get / put
// combination is required
ds, err = r.GetDataset(ds.Previous)
if err != nil {
return
}

if err = r.PutDataset(ds.Previous, ds); err != nil {
return
}
}

name, err := r.GetName(cr.Target)
if err != nil {
return err
}

if err := r.PutName(name, cr.Path); err != nil {
return err
}

return nil
}

// decline an open change request
func DeclineChangeRequest(r Repo, path datastore.Key) error {
cr, err := r.GetChangeRequest(path)
if err != nil {
return err
}
cr.Status = ChangeRequestStatusDeclined
return r.PutChangeRequest(path, cr)
}
128 changes: 128 additions & 0 deletions repo/fs/change_requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package fs_repo

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/ipfs/go-datastore"
"github.com/qri-io/qri/repo"
)

type ChangeRequests struct {
basepath
file File
}

func NewChangeRequests(base string, file File) ChangeRequests {
return ChangeRequests{basepath: basepath(base), file: file}
}

// Put a change request into the store
func (r ChangeRequests) PutChangeRequest(path datastore.Key, cr *repo.ChangeRequest) error {
crs, err := r.changeRequests()
if err != nil {
return err
}
crs[path.String()] = cr
return r.saveFile(cr, r.file)
}

func (r ChangeRequests) DeleteChangeRequest(path datastore.Key) error {
cr, err := r.changeRequests()
if err != nil {
return err
}
delete(cr, path.String())
return r.saveFile(cr, r.file)
}

// Get a change request by it's path
func (r ChangeRequests) GetChangeRequest(path datastore.Key) (*repo.ChangeRequest, error) {
crs, err := r.changeRequests()
if err != nil {
return nil, err
}

cr := crs[path.String()]
if cr == nil {
return nil, datastore.ErrNotFound
}
return cr, nil
}

// get change requests for a given target
func (r ChangeRequests) ChangeRequestsForTarget(target datastore.Key, limit, offset int) ([]*repo.ChangeRequest, error) {
crs, err := r.changeRequests()
if err != nil {
return nil, err
}

results := []*repo.ChangeRequest{}
skipped := 0
for _, cr := range crs {
if cr.Target == target {
if skipped < offset {
skipped++
continue
}
results = append(results, cr)
}
if len(results) >= limit {
break
}
}

return results, nil
}

// list change requests in this store
func (r ChangeRequests) ListChangeRequests(limit, offset int) ([]*repo.ChangeRequest, error) {
crs, err := r.changeRequests()
if err != nil {
return nil, err
}

if limit == -1 && len(crs) <= 0 {
// default to limit of 100 entries
limit = 100
} else if limit == -1 {
limit = len(crs)
}

i := 0
added := 0
res := make([]*repo.ChangeRequest, limit)
for _, cr := range crs {
if i < offset {
continue
}

if limit > 0 && added < limit {
res[i] = cr
added++
} else if added == limit {
break
}

i++
}
return res[:added], nil
}

func (r ChangeRequests) changeRequests() (map[string]*repo.ChangeRequest, error) {
ds := map[string]*repo.ChangeRequest{}
data, err := ioutil.ReadFile(r.filepath(r.file))
if err != nil {
if os.IsNotExist(err) {
return ds, nil
}
return ds, fmt.Errorf("error loading changeRequests: %s", err.Error())
}

if err := json.Unmarshal(data, &ds); err != nil {
return ds, fmt.Errorf("error unmarshaling changeRequests: %s", err.Error())
}
return ds, nil
}
2 changes: 1 addition & 1 deletion repo/fs/datasets.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package fs_repo
import (
"encoding/json"
"fmt"
"github.com/qri-io/dataset/dsfs"
"io/ioutil"
"os"

"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/query"
"github.com/qri-io/cafs"
"github.com/qri-io/dataset"
"github.com/qri-io/dataset/dsfs"
"github.com/qri-io/qri/repo"
)

Expand Down
27 changes: 15 additions & 12 deletions repo/fs/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,24 @@ const (
FileAnalytics
// SearchIndex
FileSearchIndex
// ChangeRequests
FileChangeRequests
)

var paths = map[File]string{
FileUnknown: "",
FileLockfile: "/repo.lock",
FileInfo: "/info.json",
FileProfile: "/profile.json",
FileConfig: "/config.json",
FileDatasets: "/datasets.json",
FileQueryLogs: "/queries.json",
FileNamestore: "/namespace.json",
FilePeers: "/peers.json",
FileCache: "/cache.json",
FileAnalytics: "/analytics.json",
FileSearchIndex: "/index.bleve",
FileUnknown: "",
FileLockfile: "/repo.lock",
FileInfo: "/info.json",
FileProfile: "/profile.json",
FileConfig: "/config.json",
FileDatasets: "/datasets.json",
FileQueryLogs: "/queries.json",
FileNamestore: "/namespace.json",
FilePeers: "/peers.json",
FileCache: "/cache.json",
FileAnalytics: "/analytics.json",
FileSearchIndex: "/index.bleve",
FileChangeRequests: "/change_requests.json",
}

// Filepath gives the relative filepath to a repofile
Expand Down
12 changes: 8 additions & 4 deletions repo/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Repo struct {
Datasets
Namestore
QueryLog
ChangeRequests

analytics Analytics
peers PeerStore
Expand All @@ -42,10 +43,13 @@ func NewRepo(store cafs.Filestore, base, id string) (repo.Repo, error) {
}

return &Repo{
basepath: bp,
Datasets: NewDatasets(base, FileDatasets, store),
Namestore: Namestore{bp, index, store},
QueryLog: NewQueryLog(base, FileQueryLogs, store),
basepath: bp,

Datasets: NewDatasets(base, FileDatasets, store),
Namestore: Namestore{bp, index, store},
QueryLog: NewQueryLog(base, FileQueryLogs, store),
ChangeRequests: NewChangeRequests(base, FileChangeRequests),

analytics: NewAnalytics(base),
peers: PeerStore{bp},
cache: NewDatasets(base, FileCache, nil),
Expand Down
Loading

0 comments on commit eba76ea

Please sign in to comment.