Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
271 lines
5.0 KiB
Go
271 lines
5.0 KiB
Go
package revisions
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/cortezaproject/corteza/server/pkg/cast2"
|
|
"github.com/cortezaproject/corteza/server/pkg/dal"
|
|
"github.com/cortezaproject/corteza/server/pkg/filter"
|
|
"github.com/cortezaproject/corteza/server/pkg/id"
|
|
"github.com/cortezaproject/corteza/server/pkg/j7s"
|
|
)
|
|
|
|
type (
|
|
// generic revision struct
|
|
Revision struct {
|
|
ID uint64 `json:"changeID,string"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
|
|
ResourceID uint64 `json:"resourceID,string"`
|
|
|
|
Revision int `json:"revision"`
|
|
Operation Operation `json:"operation"`
|
|
|
|
UserID uint64 `json:"userID,string"`
|
|
|
|
Changes []*Change `json:"changes"`
|
|
|
|
Comment string `json:"comment"`
|
|
}
|
|
|
|
Change struct {
|
|
// changed field
|
|
Key string `json:"key"`
|
|
|
|
Old []any `json:"old,omitempty"`
|
|
New []any `json:"new,omitempty"`
|
|
}
|
|
|
|
Filter struct {
|
|
ResourceID uint64 `json:"resourceID,string"`
|
|
}
|
|
)
|
|
|
|
var (
|
|
now = func() time.Time {
|
|
return time.Now().Round(time.Second)
|
|
}
|
|
)
|
|
|
|
func Make(op Operation, revision int, resourceID, userID uint64) (rev *Revision) {
|
|
return &Revision{
|
|
ID: id.Next(),
|
|
Timestamp: now(),
|
|
ResourceID: resourceID,
|
|
Revision: revision,
|
|
UserID: userID,
|
|
Operation: op,
|
|
}
|
|
}
|
|
|
|
// untested
|
|
func (r *Revision) CollectChanges(new, old dal.ValueGetter, skip ...string) (err error) {
|
|
var (
|
|
cc = make([]*Change, 0)
|
|
ch *Change
|
|
|
|
// old values count
|
|
ovc map[string]uint
|
|
|
|
val any
|
|
)
|
|
|
|
if new == nil {
|
|
return
|
|
}
|
|
|
|
if old != nil {
|
|
// old values count
|
|
ovc = old.CountValues()
|
|
|
|
}
|
|
|
|
keys:
|
|
for key, count := range new.CountValues() {
|
|
if count == 0 && old == nil {
|
|
continue keys
|
|
}
|
|
|
|
for _, s := range skip {
|
|
if key == s {
|
|
continue keys
|
|
}
|
|
}
|
|
|
|
ch = &Change{
|
|
Key: key,
|
|
New: make([]any, 0, count),
|
|
Old: make([]any, 0, count),
|
|
}
|
|
|
|
for pos := uint(0); pos < count; pos++ {
|
|
val, err = new.GetValue(key, pos)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ch.New = append(ch.New, val)
|
|
}
|
|
|
|
if old == nil {
|
|
cc = append(cc, ch)
|
|
continue
|
|
}
|
|
|
|
for pos := uint(0); pos < ovc[key]; pos++ {
|
|
val, err = old.GetValue(key, pos)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ch.Old = append(ch.Old, val)
|
|
}
|
|
|
|
if len(ch.New) != len(ch.Old) {
|
|
// different sizes, means that something has changed
|
|
cc = append(cc, ch)
|
|
continue
|
|
}
|
|
|
|
for i := range ch.New {
|
|
// go over all and append on first found difference
|
|
if ch.New[i] != ch.Old[i] {
|
|
cc = append(cc, ch)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
r.Changes = cc
|
|
return
|
|
}
|
|
|
|
// CountValues satisfies dal.ValueGetter interface
|
|
func (r *Revision) CountValues() map[string]uint {
|
|
// signaling DAL that each attribute has exactly one value!
|
|
return nil
|
|
}
|
|
|
|
// GetValue satisfies dal.ValueGetter interface
|
|
func (r *Revision) GetValue(ident string, _ uint) (any, error) {
|
|
switch ident {
|
|
case "id":
|
|
return r.ID, nil
|
|
|
|
case "ts":
|
|
return r.Timestamp, nil
|
|
|
|
case "rel_resource":
|
|
return r.ResourceID, nil
|
|
|
|
case "revision":
|
|
return r.Revision, nil
|
|
|
|
case "operation":
|
|
return r.Operation, nil
|
|
|
|
case "rel_user":
|
|
return r.UserID, nil
|
|
|
|
case "delta":
|
|
return r.Changes, nil
|
|
|
|
case "comment":
|
|
return r.Comment, nil
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *Revision) SetValue(name string, _ uint, value any) error {
|
|
switch name {
|
|
case "id":
|
|
return cast2.Uint64(value, &r.ID)
|
|
|
|
case "ts":
|
|
return cast2.Time(value, &r.Timestamp)
|
|
|
|
case "rel_resource":
|
|
return cast2.Uint64(value, &r.ResourceID)
|
|
|
|
case "revision":
|
|
return cast2.Int(value, &r.Revision)
|
|
|
|
case "operation":
|
|
return cast2.String(value, &r.Operation)
|
|
|
|
case "rel_user":
|
|
return cast2.Uint64(value, &r.UserID)
|
|
|
|
case "delta":
|
|
if bb, is := value.([]byte); is {
|
|
decoder := json.NewDecoder(bytes.NewReader(bb))
|
|
decoder.UseNumber()
|
|
return decoder.Decode(&r.Changes)
|
|
}
|
|
|
|
return fmt.Errorf("unexpected type for delta: %T", value)
|
|
|
|
case "comment":
|
|
return cast2.String(value, &r.Comment)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f Filter) Constraints() map[string][]any {
|
|
return map[string][]any{
|
|
"rel_resource": {f.ResourceID},
|
|
}
|
|
}
|
|
|
|
func (f Filter) Expression() string { return "" }
|
|
func (f Filter) OrderBy() filter.SortExprSet { return nil }
|
|
func (f Filter) Limit() uint { return 0 }
|
|
func (f Filter) Cursor() *filter.PagingCursor { return nil }
|
|
func (f Filter) StateConstraints() map[string]filter.State { return nil }
|
|
|
|
func (c *Change) MarshalJSON() ([]byte, error) {
|
|
row, err := j7s.MakeMap("key", c.Key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if old := normalizeChangeValues(c.Old); len(old) > 0 {
|
|
row, err = j7s.AddMap(row, "old", old)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if newVals := normalizeChangeValues(c.New); len(newVals) > 0 {
|
|
row, err = j7s.AddMap(row, "new", newVals)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return json.Marshal(row)
|
|
}
|
|
|
|
func normalizeChangeValues(values []any) []any {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := make([]any, 0, len(values))
|
|
for _, v := range values {
|
|
if jn, ok := v.(json.Number); ok {
|
|
out = append(out, jn.String())
|
|
} else {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|