mirror of https://github.com/grafana/grafana.git
EntityStore: Move slug+folder to summary metadata (#59620)
This commit is contained in:
parent
6dbe3b555f
commit
fb98a97efa
|
@ -88,6 +88,12 @@ type EntitySummary struct {
|
|||
// Key value pairs. Tags are are represented as keys with empty values
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
// Parent folder UID
|
||||
Folder string `json:"folder,omitempty"`
|
||||
|
||||
// URL safe version of the name. It will be unique within the folder
|
||||
Slug string `json:"slug,omitempty"`
|
||||
|
||||
// URL should only be set if the value is not derived directly from kind+uid
|
||||
// NOTE: this may go away with a more robust GRN solution /!\
|
||||
URL string `json:"URL,omitempty"`
|
|
@ -152,13 +152,13 @@ func (s *entityStoreImpl) Get(ctx context.Context, q *playlist.GetPlaylistByUidQ
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rsp.Entity == nil || rsp.Entity.Body == nil {
|
||||
if rsp == nil || rsp.Body == nil {
|
||||
return nil, fmt.Errorf("missing object")
|
||||
}
|
||||
|
||||
// Get the object from payload
|
||||
found := &playlist.PlaylistDTO{}
|
||||
err = json.Unmarshal(rsp.Entity.Body, found)
|
||||
err = json.Unmarshal(rsp.Body, found)
|
||||
return found, err
|
||||
}
|
||||
|
||||
|
|
|
@ -1,398 +0,0 @@
|
|||
package dummy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/x/persistentcollection"
|
||||
"github.com/grafana/grafana/pkg/services/grpcserver"
|
||||
"github.com/grafana/grafana/pkg/services/store"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
"github.com/grafana/grafana/pkg/services/store/kind"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type EntityVersionWithBody struct {
|
||||
*entity.EntityVersionInfo `json:"info,omitempty"`
|
||||
|
||||
Body []byte `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
type EntityWithHistory struct {
|
||||
Entity *entity.Entity `json:"entity,omitempty"`
|
||||
History []*EntityVersionWithBody `json:"history,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
// increment when Entity changes
|
||||
rawEntityVersion = 10
|
||||
)
|
||||
|
||||
// Make sure we implement both store + admin
|
||||
var _ entity.EntityStoreServer = &dummyEntityServer{}
|
||||
var _ entity.EntityStoreAdminServer = &dummyEntityServer{}
|
||||
|
||||
func ProvideDummyEntityServer(cfg *setting.Cfg, grpcServerProvider grpcserver.Provider, kinds kind.KindRegistry) entity.EntityStoreServer {
|
||||
objectServer := &dummyEntityServer{
|
||||
collection: persistentcollection.NewLocalFSPersistentCollection[*EntityWithHistory]("raw-object", cfg.DataPath, rawEntityVersion),
|
||||
log: log.New("in-memory-object-server"),
|
||||
kinds: kinds,
|
||||
}
|
||||
entity.RegisterEntityStoreServer(grpcServerProvider.GetServer(), objectServer)
|
||||
return objectServer
|
||||
}
|
||||
|
||||
type dummyEntityServer struct {
|
||||
log log.Logger
|
||||
collection persistentcollection.PersistentCollection[*EntityWithHistory]
|
||||
kinds kind.KindRegistry
|
||||
}
|
||||
|
||||
func namespaceFromUID(grn *entity.GRN) string {
|
||||
// TODO
|
||||
return "orgId-1"
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) findEntity(ctx context.Context, grn *entity.GRN, version string) (*EntityWithHistory, *entity.Entity, error) {
|
||||
if grn == nil {
|
||||
return nil, nil, errors.New("GRN must not be nil")
|
||||
}
|
||||
|
||||
obj, err := i.collection.FindFirst(ctx, namespaceFromUID(grn), func(i *EntityWithHistory) (bool, error) {
|
||||
return grn.Equals(i.Entity.GRN), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if obj == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
getLatestVersion := version == ""
|
||||
if getLatestVersion {
|
||||
return obj, obj.Entity, nil
|
||||
}
|
||||
|
||||
for _, objVersion := range obj.History {
|
||||
if objVersion.Version == version {
|
||||
copy := &entity.Entity{
|
||||
GRN: obj.Entity.GRN,
|
||||
CreatedAt: obj.Entity.CreatedAt,
|
||||
CreatedBy: obj.Entity.CreatedBy,
|
||||
UpdatedAt: objVersion.UpdatedAt,
|
||||
UpdatedBy: objVersion.UpdatedBy,
|
||||
ETag: objVersion.ETag,
|
||||
Version: objVersion.Version,
|
||||
|
||||
// Body is added from the dummy server cache (it does not exist in EntityVersionInfo)
|
||||
Body: objVersion.Body,
|
||||
}
|
||||
|
||||
return obj, copy, nil
|
||||
}
|
||||
}
|
||||
|
||||
return obj, nil, nil
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) Read(ctx context.Context, r *entity.ReadEntityRequest) (*entity.ReadEntityResponse, error) {
|
||||
grn := getFullGRN(ctx, r.GRN)
|
||||
_, objVersion, err := i.findEntity(ctx, grn, r.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if objVersion == nil {
|
||||
return &entity.ReadEntityResponse{
|
||||
Entity: nil,
|
||||
SummaryJson: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
rsp := &entity.ReadEntityResponse{
|
||||
Entity: objVersion,
|
||||
}
|
||||
if r.WithSummary {
|
||||
// Since we do not store the summary, we can just recreate on demand
|
||||
builder := i.kinds.GetSummaryBuilder(r.GRN.Kind)
|
||||
if builder != nil {
|
||||
summary, _, e2 := builder(ctx, r.GRN.UID, objVersion.Body)
|
||||
if e2 != nil {
|
||||
return nil, e2
|
||||
}
|
||||
rsp.SummaryJson, err = json.Marshal(summary)
|
||||
}
|
||||
}
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) BatchRead(ctx context.Context, batchR *entity.BatchReadEntityRequest) (*entity.BatchReadEntityResponse, error) {
|
||||
results := make([]*entity.ReadEntityResponse, 0)
|
||||
for _, r := range batchR.Batch {
|
||||
resp, err := i.Read(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, resp)
|
||||
}
|
||||
|
||||
return &entity.BatchReadEntityResponse{Results: results}, nil
|
||||
}
|
||||
|
||||
func createContentsHash(contents []byte) string {
|
||||
hash := md5.Sum(contents)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) update(ctx context.Context, r *entity.AdminWriteEntityRequest, namespace string) (*entity.WriteEntityResponse, error) {
|
||||
builder := i.kinds.GetSummaryBuilder(r.GRN.Kind)
|
||||
if builder == nil {
|
||||
return nil, fmt.Errorf("unsupported kind: " + r.GRN.Kind)
|
||||
}
|
||||
rsp := &entity.WriteEntityResponse{}
|
||||
|
||||
updatedCount, err := i.collection.Update(ctx, namespace, func(i *EntityWithHistory) (bool, *EntityWithHistory, error) {
|
||||
if !r.GRN.Equals(i.Entity.GRN) {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
if r.PreviousVersion != "" && i.Entity.Version != r.PreviousVersion {
|
||||
return false, nil, fmt.Errorf("expected the previous version to be %s, but was %s", r.PreviousVersion, i.Entity.Version)
|
||||
}
|
||||
|
||||
prevVersion, err := strconv.Atoi(i.Entity.Version)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
modifier := store.UserFromContext(ctx)
|
||||
|
||||
updated := &entity.Entity{
|
||||
GRN: r.GRN,
|
||||
CreatedAt: i.Entity.CreatedAt,
|
||||
CreatedBy: i.Entity.CreatedBy,
|
||||
UpdatedAt: time.Now().UnixMilli(),
|
||||
UpdatedBy: store.GetUserIDString(modifier),
|
||||
Size: int64(len(r.Body)),
|
||||
ETag: createContentsHash(r.Body),
|
||||
Body: r.Body,
|
||||
Version: fmt.Sprintf("%d", prevVersion+1),
|
||||
}
|
||||
|
||||
versionInfo := &EntityVersionWithBody{
|
||||
Body: r.Body,
|
||||
EntityVersionInfo: &entity.EntityVersionInfo{
|
||||
Version: updated.Version,
|
||||
UpdatedAt: updated.UpdatedAt,
|
||||
UpdatedBy: updated.UpdatedBy,
|
||||
Size: updated.Size,
|
||||
ETag: updated.ETag,
|
||||
Comment: r.Comment,
|
||||
},
|
||||
}
|
||||
rsp.Entity = versionInfo.EntityVersionInfo
|
||||
rsp.Status = entity.WriteEntityResponse_UPDATED
|
||||
|
||||
// When saving, it must be different than the head version
|
||||
if i.Entity.ETag == updated.ETag {
|
||||
versionInfo.EntityVersionInfo.Version = i.Entity.Version
|
||||
rsp.Status = entity.WriteEntityResponse_UNCHANGED
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
return true, &EntityWithHistory{
|
||||
Entity: updated,
|
||||
History: append(i.History, versionInfo),
|
||||
}, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if updatedCount == 0 && rsp.Entity == nil {
|
||||
return nil, fmt.Errorf("could not find object: %v", r.GRN)
|
||||
}
|
||||
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) insert(ctx context.Context, r *entity.AdminWriteEntityRequest, namespace string) (*entity.WriteEntityResponse, error) {
|
||||
modifier := store.GetUserIDString(store.UserFromContext(ctx))
|
||||
rawObj := &entity.Entity{
|
||||
GRN: r.GRN,
|
||||
UpdatedAt: time.Now().UnixMilli(),
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
CreatedBy: modifier,
|
||||
UpdatedBy: modifier,
|
||||
Size: int64(len(r.Body)),
|
||||
ETag: createContentsHash(r.Body),
|
||||
Body: r.Body,
|
||||
Version: fmt.Sprintf("%d", 1),
|
||||
}
|
||||
|
||||
info := &entity.EntityVersionInfo{
|
||||
Version: rawObj.Version,
|
||||
UpdatedAt: rawObj.UpdatedAt,
|
||||
UpdatedBy: rawObj.UpdatedBy,
|
||||
Size: rawObj.Size,
|
||||
ETag: rawObj.ETag,
|
||||
Comment: r.Comment,
|
||||
}
|
||||
|
||||
newObj := &EntityWithHistory{
|
||||
Entity: rawObj,
|
||||
History: []*EntityVersionWithBody{{
|
||||
EntityVersionInfo: info,
|
||||
Body: r.Body,
|
||||
}},
|
||||
}
|
||||
|
||||
err := i.collection.Insert(ctx, namespace, newObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.WriteEntityResponse{
|
||||
Error: nil,
|
||||
Entity: info,
|
||||
Status: entity.WriteEntityResponse_CREATED,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) Write(ctx context.Context, r *entity.WriteEntityRequest) (*entity.WriteEntityResponse, error) {
|
||||
return i.doWrite(ctx, entity.ToAdminWriteEntityRequest(r))
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) AdminWrite(ctx context.Context, r *entity.AdminWriteEntityRequest) (*entity.WriteEntityResponse, error) {
|
||||
// Check permissions?
|
||||
return i.doWrite(ctx, r)
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) doWrite(ctx context.Context, r *entity.AdminWriteEntityRequest) (*entity.WriteEntityResponse, error) {
|
||||
grn := getFullGRN(ctx, r.GRN)
|
||||
namespace := namespaceFromUID(grn)
|
||||
obj, err := i.collection.FindFirst(ctx, namespace, func(i *EntityWithHistory) (bool, error) {
|
||||
if i == nil || r == nil {
|
||||
return false, nil
|
||||
}
|
||||
return grn.Equals(i.Entity.GRN), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if obj == nil {
|
||||
return i.insert(ctx, r, namespace)
|
||||
}
|
||||
|
||||
return i.update(ctx, r, namespace)
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequest) (*entity.DeleteEntityResponse, error) {
|
||||
grn := getFullGRN(ctx, r.GRN)
|
||||
_, err := i.collection.Delete(ctx, namespaceFromUID(grn), func(i *EntityWithHistory) (bool, error) {
|
||||
if grn.Equals(i.Entity.GRN) {
|
||||
if r.PreviousVersion != "" && i.Entity.Version != r.PreviousVersion {
|
||||
return false, fmt.Errorf("expected the previous version to be %s, but was %s", r.PreviousVersion, i.Entity.Version)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.DeleteEntityResponse{
|
||||
OK: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) History(ctx context.Context, r *entity.EntityHistoryRequest) (*entity.EntityHistoryResponse, error) {
|
||||
grn := getFullGRN(ctx, r.GRN)
|
||||
obj, _, err := i.findEntity(ctx, grn, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rsp := &entity.EntityHistoryResponse{}
|
||||
if obj != nil {
|
||||
// Return the most recent versions first
|
||||
// Better? save them in this order?
|
||||
for i := len(obj.History) - 1; i >= 0; i-- {
|
||||
rsp.Versions = append(rsp.Versions, obj.History[i].EntityVersionInfo)
|
||||
}
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
func (i *dummyEntityServer) Search(ctx context.Context, r *entity.EntitySearchRequest) (*entity.EntitySearchResponse, error) {
|
||||
var kindMap map[string]bool
|
||||
if len(r.Kind) != 0 {
|
||||
kindMap = make(map[string]bool)
|
||||
for _, k := range r.Kind {
|
||||
kindMap[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO more filters
|
||||
objects, err := i.collection.Find(ctx, namespaceFromUID(&entity.GRN{}), func(i *EntityWithHistory) (bool, error) {
|
||||
if len(r.Kind) != 0 {
|
||||
if _, ok := kindMap[i.Entity.GRN.Kind]; !ok {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
searchResults := make([]*entity.EntitySearchResult, 0)
|
||||
for _, o := range objects {
|
||||
builder := i.kinds.GetSummaryBuilder(o.Entity.GRN.Kind)
|
||||
if builder == nil {
|
||||
continue
|
||||
}
|
||||
summary, clean, e2 := builder(ctx, o.Entity.GRN.UID, o.Entity.Body)
|
||||
if e2 != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
searchResults = append(searchResults, &entity.EntitySearchResult{
|
||||
GRN: o.Entity.GRN,
|
||||
Version: o.Entity.Version,
|
||||
UpdatedAt: o.Entity.UpdatedAt,
|
||||
UpdatedBy: o.Entity.UpdatedBy,
|
||||
Name: summary.Name,
|
||||
Description: summary.Description,
|
||||
Body: clean,
|
||||
})
|
||||
}
|
||||
|
||||
return &entity.EntitySearchResponse{
|
||||
Results: searchResults,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// This sets the TenantId on the request GRN
|
||||
func getFullGRN(ctx context.Context, grn *entity.GRN) *entity.GRN {
|
||||
if grn.TenantId == 0 {
|
||||
modifier := store.UserFromContext(ctx)
|
||||
grn.TenantId = modifier.OrgID
|
||||
}
|
||||
return grn
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package dummy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRawEncoders(t *testing.T) {
|
||||
body, err := json.Marshal(map[string]interface{}{
|
||||
"hello": "world",
|
||||
"field": 1.23,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
raw := &EntityVersionWithBody{
|
||||
&entity.EntityVersionInfo{
|
||||
Version: "A",
|
||||
},
|
||||
body,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(raw)
|
||||
require.NoError(t, err)
|
||||
|
||||
str := string(b)
|
||||
fmt.Printf("expect: %s", str)
|
||||
require.JSONEq(t, `{"info":{"version":"A"},"body":"eyJmaWVsZCI6MS4yMywiaGVsbG8iOiJ3b3JsZCJ9"}`, str)
|
||||
|
||||
copy := &EntityVersionWithBody{}
|
||||
err = json.Unmarshal(b, copy)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRawEntityWithHistory(t *testing.T) {
|
||||
body, err := json.Marshal(map[string]interface{}{
|
||||
"hello": "world",
|
||||
"field": 1.23,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
raw := &EntityWithHistory{
|
||||
Entity: &entity.Entity{
|
||||
GRN: &entity.GRN{UID: "x"},
|
||||
Version: "A",
|
||||
Body: body,
|
||||
},
|
||||
History: make([]*EntityVersionWithBody, 0),
|
||||
}
|
||||
raw.History = append(raw.History, &EntityVersionWithBody{
|
||||
&entity.EntityVersionInfo{
|
||||
Version: "B",
|
||||
},
|
||||
body,
|
||||
})
|
||||
|
||||
b, err := json.MarshalIndent(raw, "", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
str := string(b)
|
||||
//fmt.Printf("expect: %s", str)
|
||||
require.JSONEq(t, `{
|
||||
"entity": {
|
||||
"GRN": {
|
||||
"UID": "x"
|
||||
},
|
||||
"version": "A",
|
||||
"body": {
|
||||
"field": 1.23,
|
||||
"hello": "world"
|
||||
}
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"info": {
|
||||
"version": "B"
|
||||
},
|
||||
"body": "eyJmaWVsZCI6MS4yMywiaGVsbG8iOiJ3b3JsZCJ9"
|
||||
}
|
||||
]
|
||||
}`, str)
|
||||
|
||||
copy := &EntityVersionWithBody{}
|
||||
err = json.Unmarshal(b, copy)
|
||||
require.NoError(t, err)
|
||||
}
|
|
@ -25,7 +25,7 @@ func (i fakeEntityStore) Write(ctx context.Context, r *entity.WriteEntityRequest
|
|||
return nil, fmt.Errorf("unimplemented")
|
||||
}
|
||||
|
||||
func (i fakeEntityStore) Read(ctx context.Context, r *entity.ReadEntityRequest) (*entity.ReadEntityResponse, error) {
|
||||
func (i fakeEntityStore) Read(ctx context.Context, r *entity.ReadEntityRequest) (*entity.Entity, error) {
|
||||
return nil, fmt.Errorf("unimplemented")
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,47 +21,43 @@ message Entity {
|
|||
// Entity identifier
|
||||
GRN GRN = 1;
|
||||
|
||||
// The version will change when the entity is saved. It is not necessarily sortable
|
||||
string version = 2;
|
||||
|
||||
// Time in epoch milliseconds that the entity was created
|
||||
int64 created_at = 2;
|
||||
int64 created_at = 3;
|
||||
|
||||
// Time in epoch milliseconds that the entity was updated
|
||||
int64 updated_at = 3;
|
||||
int64 updated_at = 4;
|
||||
|
||||
// Who created the entity
|
||||
string created_by = 4;
|
||||
string created_by = 5;
|
||||
|
||||
// Who updated the entity
|
||||
string updated_by = 5;
|
||||
string updated_by = 6;
|
||||
|
||||
// Content Length
|
||||
int64 size = 6;
|
||||
int64 size = 7;
|
||||
|
||||
// MD5 digest of the body
|
||||
string ETag = 7;
|
||||
string ETag = 8;
|
||||
|
||||
// Raw bytes of the storage entity. The kind will determine what is a valid payload
|
||||
bytes body = 8;
|
||||
bytes body = 9;
|
||||
|
||||
// Folder UID
|
||||
string folder = 9;
|
||||
|
||||
// Unique slug within folder (may be UID)
|
||||
string slug = 10;
|
||||
|
||||
// The version will change when the entity is saved. It is not necessarily sortable
|
||||
//
|
||||
// NOTE: currently managed by the dashboard+dashboard_version tables
|
||||
string version = 11;
|
||||
// Entity summary as JSON
|
||||
bytes summary_json = 10;
|
||||
|
||||
// External location info
|
||||
EntityOriginInfo origin = 12;
|
||||
EntityOriginInfo origin = 11;
|
||||
}
|
||||
|
||||
// This stores additional metadata for items entities that were synced from external systmes
|
||||
message EntityOriginInfo {
|
||||
// NOTE: currently managed by the dashboard_provisioning table
|
||||
// identify the external source (plugin, git instance, etc)
|
||||
string source = 1;
|
||||
|
||||
// Key in the upstream system
|
||||
// Key in the upstream system (git hash, file path, etc)
|
||||
string key = 2;
|
||||
|
||||
// Time in epoch milliseconds that the entity was last synced with an external system (provisioning/git)
|
||||
|
@ -122,14 +118,6 @@ message ReadEntityRequest {
|
|||
bool with_summary = 4;
|
||||
}
|
||||
|
||||
message ReadEntityResponse {
|
||||
// Entity details with the body removed
|
||||
Entity entity = 1;
|
||||
|
||||
// Entity summary as JSON
|
||||
bytes summary_json = 2;
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
// Make many read requests at once (by Kind+ID+version)
|
||||
//------------------------------------------------------
|
||||
|
@ -139,7 +127,7 @@ message BatchReadEntityRequest {
|
|||
}
|
||||
|
||||
message BatchReadEntityResponse {
|
||||
repeated ReadEntityResponse results = 1;
|
||||
repeated Entity results = 1;
|
||||
}
|
||||
|
||||
//-----------------------------------------------
|
||||
|
@ -375,7 +363,7 @@ message EntitySearchResponse {
|
|||
|
||||
// The entity store provides a basic CRUD (+watch eventually) interface for generic entitys
|
||||
service EntityStore {
|
||||
rpc Read(ReadEntityRequest) returns (ReadEntityResponse);
|
||||
rpc Read(ReadEntityRequest) returns (Entity);
|
||||
rpc BatchRead(BatchReadEntityRequest) returns (BatchReadEntityResponse);
|
||||
rpc Write(WriteEntityRequest) returns (WriteEntityResponse);
|
||||
rpc Delete(DeleteEntityRequest) returns (DeleteEntityResponse);
|
||||
|
|
|
@ -22,7 +22,7 @@ const _ = grpc.SupportPackageIsVersion7
|
|||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type EntityStoreClient interface {
|
||||
Read(ctx context.Context, in *ReadEntityRequest, opts ...grpc.CallOption) (*ReadEntityResponse, error)
|
||||
Read(ctx context.Context, in *ReadEntityRequest, opts ...grpc.CallOption) (*Entity, error)
|
||||
BatchRead(ctx context.Context, in *BatchReadEntityRequest, opts ...grpc.CallOption) (*BatchReadEntityResponse, error)
|
||||
Write(ctx context.Context, in *WriteEntityRequest, opts ...grpc.CallOption) (*WriteEntityResponse, error)
|
||||
Delete(ctx context.Context, in *DeleteEntityRequest, opts ...grpc.CallOption) (*DeleteEntityResponse, error)
|
||||
|
@ -40,8 +40,8 @@ func NewEntityStoreClient(cc grpc.ClientConnInterface) EntityStoreClient {
|
|||
return &entityStoreClient{cc}
|
||||
}
|
||||
|
||||
func (c *entityStoreClient) Read(ctx context.Context, in *ReadEntityRequest, opts ...grpc.CallOption) (*ReadEntityResponse, error) {
|
||||
out := new(ReadEntityResponse)
|
||||
func (c *entityStoreClient) Read(ctx context.Context, in *ReadEntityRequest, opts ...grpc.CallOption) (*Entity, error) {
|
||||
out := new(Entity)
|
||||
err := c.cc.Invoke(ctx, "/entity.EntityStore/Read", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -107,7 +107,7 @@ func (c *entityStoreClient) AdminWrite(ctx context.Context, in *AdminWriteEntity
|
|||
// All implementations should embed UnimplementedEntityStoreServer
|
||||
// for forward compatibility
|
||||
type EntityStoreServer interface {
|
||||
Read(context.Context, *ReadEntityRequest) (*ReadEntityResponse, error)
|
||||
Read(context.Context, *ReadEntityRequest) (*Entity, error)
|
||||
BatchRead(context.Context, *BatchReadEntityRequest) (*BatchReadEntityResponse, error)
|
||||
Write(context.Context, *WriteEntityRequest) (*WriteEntityResponse, error)
|
||||
Delete(context.Context, *DeleteEntityRequest) (*DeleteEntityResponse, error)
|
||||
|
@ -121,7 +121,7 @@ type EntityStoreServer interface {
|
|||
type UnimplementedEntityStoreServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedEntityStoreServer) Read(context.Context, *ReadEntityRequest) (*ReadEntityResponse, error) {
|
||||
func (UnimplementedEntityStoreServer) Read(context.Context, *ReadEntityRequest) (*Entity, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Read not implemented")
|
||||
}
|
||||
func (UnimplementedEntityStoreServer) BatchRead(context.Context, *BatchReadEntityRequest) (*BatchReadEntityResponse, error) {
|
||||
|
|
|
@ -90,17 +90,17 @@ func (s *httpEntityStore) doGetEntity(c *models.ReqContext) response.Response {
|
|||
if err != nil {
|
||||
return response.Error(500, "error fetching entity", err)
|
||||
}
|
||||
if rsp.Entity == nil {
|
||||
if rsp == nil {
|
||||
return response.Error(404, "not found", nil)
|
||||
}
|
||||
|
||||
// Configure etag support
|
||||
currentEtag := rsp.Entity.ETag
|
||||
currentEtag := rsp.ETag
|
||||
previousEtag := c.Req.Header.Get("If-None-Match")
|
||||
if previousEtag == currentEtag {
|
||||
return response.CreateNormalResponse(
|
||||
http.Header{
|
||||
"ETag": []string{rsp.Entity.ETag},
|
||||
"ETag": []string{rsp.ETag},
|
||||
},
|
||||
[]byte{}, // nothing
|
||||
http.StatusNotModified, // 304
|
||||
|
@ -130,14 +130,14 @@ func (s *httpEntityStore) doGetRawEntity(c *models.ReqContext) response.Response
|
|||
return response.Error(400, "Unsupported kind", err)
|
||||
}
|
||||
|
||||
if rsp.Entity != nil && rsp.Entity.Body != nil {
|
||||
if rsp != nil && rsp.Body != nil {
|
||||
// Configure etag support
|
||||
currentEtag := rsp.Entity.ETag
|
||||
currentEtag := rsp.ETag
|
||||
previousEtag := c.Req.Header.Get("If-None-Match")
|
||||
if previousEtag == currentEtag {
|
||||
return response.CreateNormalResponse(
|
||||
http.Header{
|
||||
"ETag": []string{rsp.Entity.ETag},
|
||||
"ETag": []string{rsp.ETag},
|
||||
},
|
||||
[]byte{}, // nothing
|
||||
http.StatusNotModified, // 304
|
||||
|
@ -152,7 +152,7 @@ func (s *httpEntityStore) doGetRawEntity(c *models.ReqContext) response.Response
|
|||
"Content-Type": []string{mime},
|
||||
"ETag": []string{currentEtag},
|
||||
},
|
||||
rsp.Entity.Body,
|
||||
rsp.Body,
|
||||
200,
|
||||
)
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ func (s *httpEntityStore) doUpload(c *models.ReqContext) response.Response {
|
|||
if err != nil {
|
||||
return response.Error(500, "Internal Server Error", err)
|
||||
}
|
||||
if result.Entity != nil {
|
||||
if result.GRN != nil {
|
||||
return response.Error(400, "File name already in use", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
func init() { //nolint:gochecknoinits
|
||||
jsoniter.RegisterTypeEncoder("entity.EntitySearchResult", &searchResultCodec{})
|
||||
jsoniter.RegisterTypeEncoder("entity.WriteEntityResponse", &writeResponseCodec{})
|
||||
jsoniter.RegisterTypeEncoder("entity.ReadEntityResponse", &readResponseCodec{})
|
||||
|
||||
jsoniter.RegisterTypeEncoder("entity.Entity", &rawEntityCodec{})
|
||||
jsoniter.RegisterTypeDecoder("entity.Entity", &rawEntityCodec{})
|
||||
|
@ -91,33 +90,26 @@ func (codec *rawEntityCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream)
|
|||
stream.WriteString(sEnc) // works for strings
|
||||
}
|
||||
}
|
||||
if len(obj.SummaryJson) > 0 {
|
||||
stream.WriteMore()
|
||||
stream.WriteObjectField("summary")
|
||||
writeRawJson(stream, obj.SummaryJson)
|
||||
}
|
||||
if obj.ETag != "" {
|
||||
stream.WriteMore()
|
||||
stream.WriteObjectField("etag")
|
||||
stream.WriteString(obj.ETag)
|
||||
}
|
||||
if obj.Folder != "" {
|
||||
stream.WriteMore()
|
||||
stream.WriteObjectField("folder")
|
||||
stream.WriteString(obj.Folder)
|
||||
}
|
||||
if obj.Slug != "" {
|
||||
stream.WriteMore()
|
||||
stream.WriteObjectField("slug")
|
||||
stream.WriteString(obj.Slug)
|
||||
}
|
||||
if obj.Size > 0 {
|
||||
stream.WriteMore()
|
||||
stream.WriteObjectField("size")
|
||||
stream.WriteInt64(obj.Size)
|
||||
}
|
||||
|
||||
if obj.Origin != nil {
|
||||
stream.WriteMore()
|
||||
stream.WriteObjectField("origin")
|
||||
stream.WriteVal(obj.Origin)
|
||||
}
|
||||
|
||||
stream.WriteObjectEnd()
|
||||
}
|
||||
|
||||
|
@ -145,15 +137,20 @@ func readEntity(iter *jsoniter.Iterator, raw *Entity) {
|
|||
raw.Size = iter.ReadInt64()
|
||||
case "etag":
|
||||
raw.ETag = iter.ReadString()
|
||||
case "folder":
|
||||
raw.Folder = iter.ReadString()
|
||||
case "slug":
|
||||
raw.Slug = iter.ReadString()
|
||||
case "version":
|
||||
raw.Version = iter.ReadString()
|
||||
case "origin":
|
||||
raw.Origin = &EntityOriginInfo{}
|
||||
iter.ReadVal(raw.Origin)
|
||||
case "summary":
|
||||
var val interface{}
|
||||
iter.ReadVal(&val) // ??? is there a smarter way to just keep the underlying bytes without read+marshal
|
||||
body, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
iter.ReportError("raw entity", "error reading summary body")
|
||||
return
|
||||
}
|
||||
raw.SummaryJson = body
|
||||
|
||||
case "body":
|
||||
var val interface{}
|
||||
|
@ -181,34 +178,6 @@ func readEntity(iter *jsoniter.Iterator, raw *Entity) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unlike the standard JSON marshal, this will write bytes as JSON when it can
|
||||
type readResponseCodec struct{}
|
||||
|
||||
func (obj *ReadEntityResponse) MarshalJSON() ([]byte, error) {
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
return json.Marshal(obj)
|
||||
}
|
||||
|
||||
func (codec *readResponseCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||
f := (*ReadEntityResponse)(ptr)
|
||||
return f == nil
|
||||
}
|
||||
|
||||
func (codec *readResponseCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||
obj := (*ReadEntityResponse)(ptr)
|
||||
stream.WriteObjectStart()
|
||||
stream.WriteObjectField("entity")
|
||||
stream.WriteVal(obj.Entity)
|
||||
|
||||
if len(obj.SummaryJson) > 0 {
|
||||
stream.WriteMore()
|
||||
stream.WriteObjectField("summary")
|
||||
writeRawJson(stream, obj.SummaryJson)
|
||||
}
|
||||
|
||||
stream.WriteObjectEnd()
|
||||
}
|
||||
|
||||
// Unlike the standard JSON marshal, this will write bytes as JSON when it can
|
||||
type searchResultCodec struct{}
|
||||
|
||||
|
|
|
@ -46,8 +46,7 @@ type sqlEntityServer struct {
|
|||
func getReadSelect(r *entity.ReadEntityRequest) string {
|
||||
fields := []string{
|
||||
"tenant_id", "kind", "uid", // The PK
|
||||
"version", "slug", "folder",
|
||||
"size", "etag", "errors", // errors are always returned
|
||||
"version", "size", "etag", "errors", // errors are always returned
|
||||
"created_at", "created_by",
|
||||
"updated_at", "updated_by",
|
||||
"origin", "origin_key", "origin_ts"}
|
||||
|
@ -56,23 +55,21 @@ func getReadSelect(r *entity.ReadEntityRequest) string {
|
|||
fields = append(fields, `body`)
|
||||
}
|
||||
if r.WithSummary {
|
||||
fields = append(fields, `name`, `description`, `labels`, `fields`)
|
||||
fields = append(fields, "name", "slug", "folder", "description", "labels", "fields")
|
||||
}
|
||||
return "SELECT " + strings.Join(fields, ",") + " FROM entity WHERE "
|
||||
}
|
||||
|
||||
func (s *sqlEntityServer) rowToReadEntityResponse(ctx context.Context, rows *sql.Rows, r *entity.ReadEntityRequest) (*entity.ReadEntityResponse, error) {
|
||||
func (s *sqlEntityServer) rowToReadEntityResponse(ctx context.Context, rows *sql.Rows, r *entity.ReadEntityRequest) (*entity.Entity, error) {
|
||||
raw := &entity.Entity{
|
||||
GRN: &entity.GRN{},
|
||||
Origin: &entity.EntityOriginInfo{},
|
||||
}
|
||||
slug := ""
|
||||
|
||||
summaryjson := &summarySupport{}
|
||||
args := []interface{}{
|
||||
&raw.GRN.TenantId, &raw.GRN.Kind, &raw.GRN.UID,
|
||||
&raw.Version, &slug, &raw.Folder,
|
||||
&raw.Size, &raw.ETag, &summaryjson.errors,
|
||||
&raw.Version, &raw.Size, &raw.ETag, &summaryjson.errors,
|
||||
&raw.CreatedAt, &raw.CreatedBy,
|
||||
&raw.UpdatedAt, &raw.UpdatedBy,
|
||||
&raw.Origin.Source, &raw.Origin.Key, &raw.Origin.Time,
|
||||
|
@ -81,7 +78,7 @@ func (s *sqlEntityServer) rowToReadEntityResponse(ctx context.Context, rows *sql
|
|||
args = append(args, &raw.Body)
|
||||
}
|
||||
if r.WithSummary {
|
||||
args = append(args, &summaryjson.name, &summaryjson.description, &summaryjson.labels, &summaryjson.fields)
|
||||
args = append(args, &summaryjson.name, &summaryjson.slug, &summaryjson.folder, &summaryjson.description, &summaryjson.labels, &summaryjson.fields)
|
||||
}
|
||||
|
||||
err := rows.Scan(args...)
|
||||
|
@ -93,10 +90,6 @@ func (s *sqlEntityServer) rowToReadEntityResponse(ctx context.Context, rows *sql
|
|||
raw.Origin = nil
|
||||
}
|
||||
|
||||
rsp := &entity.ReadEntityResponse{
|
||||
Entity: raw,
|
||||
}
|
||||
|
||||
if r.WithSummary || summaryjson.errors != nil {
|
||||
summary, err := summaryjson.toEntitySummary()
|
||||
if err != nil {
|
||||
|
@ -107,9 +100,9 @@ func (s *sqlEntityServer) rowToReadEntityResponse(ctx context.Context, rows *sql
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsp.SummaryJson = js
|
||||
raw.SummaryJson = js
|
||||
}
|
||||
return rsp, nil
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (s *sqlEntityServer) validateGRN(ctx context.Context, grn *entity.GRN) (*entity.GRN, error) {
|
||||
|
@ -138,7 +131,7 @@ func (s *sqlEntityServer) validateGRN(ctx context.Context, grn *entity.GRN) (*en
|
|||
return grn, nil
|
||||
}
|
||||
|
||||
func (s *sqlEntityServer) Read(ctx context.Context, r *entity.ReadEntityRequest) (*entity.ReadEntityResponse, error) {
|
||||
func (s *sqlEntityServer) Read(ctx context.Context, r *entity.ReadEntityRequest) (*entity.Entity, error) {
|
||||
if r.Version != "" {
|
||||
return s.readFromHistory(ctx, r)
|
||||
}
|
||||
|
@ -157,13 +150,13 @@ func (s *sqlEntityServer) Read(ctx context.Context, r *entity.ReadEntityRequest)
|
|||
defer func() { _ = rows.Close() }()
|
||||
|
||||
if !rows.Next() {
|
||||
return &entity.ReadEntityResponse{}, nil
|
||||
return &entity.Entity{}, nil
|
||||
}
|
||||
|
||||
return s.rowToReadEntityResponse(ctx, rows, r)
|
||||
}
|
||||
|
||||
func (s *sqlEntityServer) readFromHistory(ctx context.Context, r *entity.ReadEntityRequest) (*entity.ReadEntityResponse, error) {
|
||||
func (s *sqlEntityServer) readFromHistory(ctx context.Context, r *entity.ReadEntityRequest) (*entity.Entity, error) {
|
||||
grn, err := s.validateGRN(ctx, r.GRN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -185,15 +178,12 @@ func (s *sqlEntityServer) readFromHistory(ctx context.Context, r *entity.ReadEnt
|
|||
|
||||
// Version or key not found
|
||||
if !rows.Next() {
|
||||
return &entity.ReadEntityResponse{}, nil
|
||||
return &entity.Entity{}, nil
|
||||
}
|
||||
|
||||
raw := &entity.Entity{
|
||||
GRN: r.GRN,
|
||||
}
|
||||
rsp := &entity.ReadEntityResponse{
|
||||
Entity: raw,
|
||||
}
|
||||
err = rows.Scan(&raw.Body, &raw.Size, &raw.ETag, &raw.UpdatedAt, &raw.UpdatedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -210,7 +200,7 @@ func (s *sqlEntityServer) readFromHistory(ctx context.Context, r *entity.ReadEnt
|
|||
val, out, err := builder(ctx, r.GRN.UID, raw.Body)
|
||||
if err == nil {
|
||||
raw.Body = out // cleaned up
|
||||
rsp.SummaryJson, err = json.Marshal(val)
|
||||
raw.SummaryJson, err = json.Marshal(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -220,10 +210,10 @@ func (s *sqlEntityServer) readFromHistory(ctx context.Context, r *entity.ReadEnt
|
|||
|
||||
// Clear the body if not requested
|
||||
if !r.WithBody {
|
||||
rsp.Entity.Body = nil
|
||||
raw.Body = nil
|
||||
}
|
||||
|
||||
return rsp, err
|
||||
return raw, err
|
||||
}
|
||||
|
||||
func (s *sqlEntityServer) BatchRead(ctx context.Context, b *entity.BatchReadEntityRequest) (*entity.BatchReadEntityResponse, error) {
|
||||
|
@ -306,12 +296,6 @@ func (s *sqlEntityServer) AdminWrite(ctx context.Context, r *entity.AdminWriteEn
|
|||
return nil, err
|
||||
}
|
||||
|
||||
t := summary.name
|
||||
if t == "" {
|
||||
t = r.GRN.UID
|
||||
}
|
||||
|
||||
slug := slugify.Slugify(t)
|
||||
etag := createContentsHash(body)
|
||||
rsp := &entity.WriteEntityResponse{
|
||||
GRN: grn,
|
||||
|
@ -479,8 +463,8 @@ func (s *sqlEntityServer) AdminWrite(ctx context.Context, r *entity.AdminWriteEn
|
|||
" ?, ?, ?)",
|
||||
oid, grn.TenantId, grn.Kind, grn.UID, r.Folder,
|
||||
versionInfo.Size, body, etag, versionInfo.Version,
|
||||
updatedAt, createdBy, createdAt, createdBy, // created + updated are the same
|
||||
summary.model.Name, summary.model.Description, slug,
|
||||
updatedAt, createdBy, createdAt, createdBy,
|
||||
summary.model.Name, summary.model.Description, summary.model.Slug,
|
||||
summary.labels, summary.fields, summary.errors,
|
||||
origin.Source, origin.Key, origin.Time,
|
||||
)
|
||||
|
@ -550,6 +534,16 @@ func (s *sqlEntityServer) prepare(ctx context.Context, r *entity.AdminWriteEntit
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Update a summary based on the name (unless the root suggested one)
|
||||
if summary.Slug == "" {
|
||||
t := summary.Name
|
||||
if t == "" {
|
||||
t = r.GRN.UID
|
||||
}
|
||||
summary.Slug = slugify.Slugify(t)
|
||||
}
|
||||
|
||||
return summaryjson, body, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ type summarySupport struct {
|
|||
model *models.EntitySummary
|
||||
name string
|
||||
description *string // null or empty
|
||||
slug *string // null or empty
|
||||
folder *string // null or empty
|
||||
labels *string
|
||||
fields *string
|
||||
errors *string // should not allow saving with this!
|
||||
|
@ -32,7 +34,12 @@ func newSummarySupport(summary *models.EntitySummary) (*summarySupport, error) {
|
|||
if summary.Description != "" {
|
||||
s.description = &summary.Description
|
||||
}
|
||||
|
||||
if summary.Slug != "" {
|
||||
s.slug = &summary.Slug
|
||||
}
|
||||
if summary.Folder != "" {
|
||||
s.folder = &summary.Folder
|
||||
}
|
||||
if len(summary.Labels) > 0 {
|
||||
js, err = json.Marshal(summary.Labels)
|
||||
if err != nil {
|
||||
|
@ -71,6 +78,12 @@ func (s summarySupport) toEntitySummary() (*models.EntitySummary, error) {
|
|||
if s.description != nil {
|
||||
summary.Description = *s.description
|
||||
}
|
||||
if s.slug != nil {
|
||||
summary.Slug = *s.slug
|
||||
}
|
||||
if s.folder != nil {
|
||||
summary.Folder = *s.folder
|
||||
}
|
||||
if s.labels != nil {
|
||||
b := []byte(*s.labels)
|
||||
err = json.Unmarshal(b, &summary.Labels)
|
||||
|
|
|
@ -134,7 +134,7 @@ func TestIntegrationEntityServer(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, resp)
|
||||
require.Nil(t, resp.Entity)
|
||||
require.Nil(t, resp.GRN)
|
||||
})
|
||||
|
||||
t.Run("should be able to read persisted objects", func(t *testing.T) {
|
||||
|
@ -162,9 +162,9 @@ func TestIntegrationEntityServer(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readResp.SummaryJson)
|
||||
require.NotNil(t, readResp.Entity)
|
||||
require.NotNil(t, readResp)
|
||||
|
||||
foundGRN := readResp.Entity.GRN
|
||||
foundGRN := readResp.GRN
|
||||
require.NotNil(t, foundGRN)
|
||||
require.Equal(t, testCtx.user.OrgID, foundGRN.TenantId) // orgId becomes the tenant id when not set
|
||||
require.Equal(t, grn.Kind, foundGRN.Kind)
|
||||
|
@ -179,7 +179,7 @@ func TestIntegrationEntityServer(t *testing.T) {
|
|||
body: body,
|
||||
version: &firstVersion,
|
||||
}
|
||||
requireEntityMatch(t, readResp.Entity, objectMatcher)
|
||||
requireEntityMatch(t, readResp, objectMatcher)
|
||||
|
||||
deleteResp, err := testCtx.client.Delete(ctx, &entity.DeleteEntityRequest{
|
||||
GRN: grn,
|
||||
|
@ -194,7 +194,7 @@ func TestIntegrationEntityServer(t *testing.T) {
|
|||
WithBody: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readRespAfterDelete.Entity)
|
||||
require.Nil(t, readRespAfterDelete.GRN)
|
||||
})
|
||||
|
||||
t.Run("should be able to update an object", func(t *testing.T) {
|
||||
|
@ -258,7 +258,7 @@ func TestIntegrationEntityServer(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readRespLatest.SummaryJson)
|
||||
requireEntityMatch(t, readRespLatest.Entity, latestMatcher)
|
||||
requireEntityMatch(t, readRespLatest, latestMatcher)
|
||||
|
||||
readRespFirstVer, err := testCtx.client.Read(ctx, &entity.ReadEntityRequest{
|
||||
GRN: grn,
|
||||
|
@ -268,8 +268,8 @@ func TestIntegrationEntityServer(t *testing.T) {
|
|||
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readRespFirstVer.SummaryJson)
|
||||
require.NotNil(t, readRespFirstVer.Entity)
|
||||
requireEntityMatch(t, readRespFirstVer.Entity, rawEntityMatcher{
|
||||
require.NotNil(t, readRespFirstVer)
|
||||
requireEntityMatch(t, readRespFirstVer, rawEntityMatcher{
|
||||
grn: grn,
|
||||
createdRange: []time.Time{before, time.Now()},
|
||||
updatedRange: []time.Time{before, time.Now()},
|
||||
|
|
Loading…
Reference in New Issue