mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-08 05:47:07 +09:00
Merge branch 'feature/spaces' into go-1777-subscription-and-query-for-typetypekey-filter
# Conflicts: # pkg/lib/localstore/objectstore/objects_test.go
This commit is contained in:
commit
7c501b61cd
32 changed files with 860 additions and 707 deletions
|
@ -5,15 +5,16 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock/smarttest"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/template"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore/mock_objectstore"
|
||||
"github.com/anyproto/anytype-heart/core/relation/mock_relation"
|
||||
"github.com/anyproto/anytype-heart/core/session"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore/mock_objectstore"
|
||||
)
|
||||
|
||||
type testPicker struct {
|
||||
|
@ -38,8 +39,13 @@ func newFixture(t *testing.T) *fixture {
|
|||
a := &app.App{}
|
||||
objectStore := mock_objectstore.NewMockObjectStore(t)
|
||||
objectStore.EXPECT().Name().Return("objectStore")
|
||||
|
||||
relationService := mock_relation.NewMockService(t)
|
||||
relationService.EXPECT().Name().Return("relationService")
|
||||
|
||||
a.Register(picker)
|
||||
a.Register(objectStore)
|
||||
a.Register(relationService)
|
||||
s := New()
|
||||
|
||||
err := s.Init(a)
|
||||
|
|
|
@ -18,7 +18,7 @@ func NewArchiveTest(ctrl *gomock.Controller) (*Archive, error) {
|
|||
sb := smarttest.New("root")
|
||||
objectStore := testMock.NewMockObjectStore(ctrl)
|
||||
objectStore.EXPECT().GetDetails(gomock.Any()).AnyTimes()
|
||||
objectStore.EXPECT().Query(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
objectStore.EXPECT().Query(gomock.Any()).AnyTimes()
|
||||
dm := mockDetailsModifier.NewMockDetailsModifier(ctrl)
|
||||
dm.EXPECT().ModifyLocalDetails(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
a := &Archive{
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
@ -844,7 +846,7 @@ func TestClipboard_PasteToCodeBock(t *testing.T) {
|
|||
func Test_PasteText(t *testing.T) {
|
||||
|
||||
t.Run("paste", func(t *testing.T) {
|
||||
//given
|
||||
// given
|
||||
sb := smarttest.New("text")
|
||||
require.NoError(t, smartblock.ObjectApplyTemplate(sb, nil, template.WithEmpty))
|
||||
s := sb.NewState()
|
||||
|
@ -870,20 +872,20 @@ func Test_PasteText(t *testing.T) {
|
|||
s.InsertTo("", model.Block_Inner, b2.Model().Id)
|
||||
require.NoError(t, sb.Apply(s))
|
||||
|
||||
//when
|
||||
// when
|
||||
cb := NewClipboard(sb, nil, nil, nil, nil)
|
||||
_, _, _, _, err := cb.Paste(nil, &pb.RpcBlockPasteRequest{
|
||||
SelectedBlockIds: []string{"1", "2"},
|
||||
TextSlot: "One string",
|
||||
}, "")
|
||||
|
||||
//then
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "One string", sb.NewState().Snippet())
|
||||
})
|
||||
|
||||
t.Run("paste - when asterisks", func(t *testing.T) {
|
||||
//given
|
||||
// given
|
||||
sb := smarttest.New("text")
|
||||
require.NoError(t, smartblock.ObjectApplyTemplate(sb, nil, template.WithEmpty))
|
||||
s := sb.NewState()
|
||||
|
@ -899,7 +901,7 @@ func Test_PasteText(t *testing.T) {
|
|||
s.InsertTo("", model.Block_Inner, b1.Model().Id)
|
||||
require.NoError(t, sb.Apply(s))
|
||||
|
||||
//when
|
||||
// when
|
||||
cb := NewClipboard(sb, nil, nil, nil, nil)
|
||||
_, _, _, _, err := cb.Paste(nil, &pb.RpcBlockPasteRequest{
|
||||
SelectedBlockIds: []string{"1"},
|
||||
|
@ -907,7 +909,7 @@ func Test_PasteText(t *testing.T) {
|
|||
HtmlSlot: "<meta charset='utf-8'><p data-pm-slice=\"1 1 []\">a *<em> b</em> * c</p>",
|
||||
}, "")
|
||||
|
||||
//then
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "a * b * c", sb.NewState().Snippet())
|
||||
})
|
||||
|
|
|
@ -12,9 +12,12 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/core/session"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/mock_core"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/threads"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func Test_calculateEntriesDiff(t *testing.T) {
|
||||
|
@ -86,7 +89,11 @@ func TestDataviewCollectionImpl_SetViewPosition(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}}))
|
||||
return NewDataview(sb, nil, nil, nil, nil), sb
|
||||
|
||||
coreService := mock_core.NewMockService(t)
|
||||
coreService.EXPECT().PredefinedObjects(mock.Anything).Return(threads.DerivedSmartblockIds{})
|
||||
|
||||
return NewDataview(sb, coreService, nil, nil, nil), sb
|
||||
}
|
||||
assertViewPositions := func(viewId string, pos uint32, exp []string) {
|
||||
dv, sb := newTestDv()
|
||||
|
|
|
@ -176,6 +176,7 @@ func (p *Page) CreationStateMigration(ctx *smartblock.InitContext) migration.Mig
|
|||
template.WithDescription,
|
||||
template.WithAddedFeaturedRelation(bundle.RelationKeyType),
|
||||
)
|
||||
// TODO case for relationOption?
|
||||
default:
|
||||
templates = append(templates,
|
||||
template.WithTitle,
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/template"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/objectlink"
|
||||
"github.com/anyproto/anytype-heart/core/block/restriction"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/core/block/source"
|
||||
|
@ -496,7 +497,7 @@ func (sb *smartBlock) onMetaChange(details *types.Struct) {
|
|||
|
||||
// dependentSmartIds returns list of dependent objects in this order: Simple blocks(Link, mentions in Text), Relations. Both of them are returned in the order of original blocks/relations
|
||||
func (sb *smartBlock) dependentSmartIds(includeRelations, includeObjTypes, includeCreatorModifier, _ bool) (ids []string) {
|
||||
return sb.Doc.(*state.State).DepSmartIds(true, true, includeRelations, includeObjTypes, includeCreatorModifier)
|
||||
return objectlink.DependentObjectIDs(sb.Doc.(*state.State), sb.relationService, true, true, includeRelations, includeObjTypes, includeCreatorModifier)
|
||||
}
|
||||
|
||||
func (sb *smartBlock) navigationalLinks(s *state.State) []string {
|
||||
|
|
|
@ -7,14 +7,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/uniquekey"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/core/block/undo"
|
||||
"github.com/anyproto/anytype-heart/core/block/uniquekey"
|
||||
"github.com/anyproto/anytype-heart/core/session"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
|
@ -24,6 +19,8 @@ import (
|
|||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/anyproto/anytype-heart/util/slice"
|
||||
textutil "github.com/anyproto/anytype-heart/util/text"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
var log = logging.Logger("anytype-mw-state")
|
||||
|
@ -942,6 +939,7 @@ func (s *State) SetObjectType(objectType string) *State {
|
|||
return s.SetObjectTypes([]string{objectType})
|
||||
}
|
||||
|
||||
// TODO What objectTypes means here? ID? Key?
|
||||
func (s *State) SetObjectTypes(objectTypes []string) *State {
|
||||
for _, ot := range objectTypes {
|
||||
if strings.HasPrefix(ot, addr.ObjectTypeKeyToIdPrefix) || strings.HasPrefix(ot, addr.BundledObjectTypeURLPrefix) {
|
||||
|
@ -1134,106 +1132,6 @@ func (s *State) SetParent(parent *State) {
|
|||
s.parent = parent
|
||||
}
|
||||
|
||||
func (s *State) DepSmartIds(blocks, details, relations, objTypes, creatorModifierWorkspace bool) (ids []string) {
|
||||
if blocks {
|
||||
err := s.Iterate(func(b simple.Block) (isContinue bool) {
|
||||
if ls, ok := b.(linkSource); ok {
|
||||
ids = ls.FillSmartIds(ids)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
log.With("objectID", s.RootId()).Errorf("failed to iterate over simple blocks: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if objTypes {
|
||||
for _, ot := range pbtypes.GetStringList(s.LocalDetails(), bundle.RelationKeyType.String()) {
|
||||
if ot == "" { // TODO is it possible?
|
||||
log.Errorf("sb %s has empty ot", s.RootId())
|
||||
continue
|
||||
}
|
||||
ids = append(ids, ot)
|
||||
}
|
||||
}
|
||||
|
||||
var det *types.Struct
|
||||
if details {
|
||||
det = s.CombinedDetails()
|
||||
}
|
||||
|
||||
for _, rel := range s.GetRelationLinks() {
|
||||
// do not index local dates such as lastOpened/lastModified
|
||||
if relations {
|
||||
// todo: add the relation ids somewhere else
|
||||
// ids = append(ids, addr.RelationKeyToIdPrefix+rel.Key)
|
||||
}
|
||||
|
||||
if !details {
|
||||
continue
|
||||
}
|
||||
|
||||
// handle corner cases first for specific formats
|
||||
if rel.Format == model.RelationFormat_date &&
|
||||
!slices.Contains(bundle.LocalRelationsKeys, rel.Key) &&
|
||||
!slices.Contains(bundle.DerivedRelationsKeys, rel.Key) {
|
||||
relInt := pbtypes.GetInt64(det, rel.Key)
|
||||
if relInt > 0 {
|
||||
t := time.Unix(relInt, 0)
|
||||
t = t.In(time.UTC)
|
||||
ids = append(ids, addr.TimeToID(t))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if rel.Key == bundle.RelationKeyCreator.String() ||
|
||||
rel.Key == bundle.RelationKeyLastModifiedBy.String() ||
|
||||
rel.Key == bundle.RelationKeyWorkspaceId.String() {
|
||||
if creatorModifierWorkspace {
|
||||
v := pbtypes.GetString(det, rel.Key)
|
||||
ids = append(ids, v)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if rel.Key == bundle.RelationKeyId.String() ||
|
||||
rel.Key == bundle.RelationKeyLinks.String() ||
|
||||
rel.Key == bundle.RelationKeyType.String() || // always skip type because it was proceed above
|
||||
rel.Key == bundle.RelationKeyFeaturedRelations.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
if rel.Key == bundle.RelationKeyCoverId.String() {
|
||||
v := pbtypes.GetString(det, rel.Key)
|
||||
_, err := cid.Decode(v)
|
||||
if err != nil {
|
||||
// this is an exception cause coverId can contains not a file hash but color
|
||||
continue
|
||||
}
|
||||
ids = append(ids, v)
|
||||
}
|
||||
|
||||
if rel.Format != model.RelationFormat_object &&
|
||||
rel.Format != model.RelationFormat_file &&
|
||||
rel.Format != model.RelationFormat_status &&
|
||||
rel.Format != model.RelationFormat_tag {
|
||||
continue
|
||||
}
|
||||
|
||||
// add all object relation values as dependents
|
||||
for _, targetID := range pbtypes.GetStringList(det, rel.Key) {
|
||||
if targetID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ids = append(ids, targetID)
|
||||
}
|
||||
}
|
||||
|
||||
ids = lo.Uniq(ids)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *State) Validate() (err error) {
|
||||
var (
|
||||
err2 error
|
||||
|
@ -1865,8 +1763,3 @@ func (s *State) AddBundledRelations(keys ...bundle.RelationKey) {
|
|||
func (s *State) UniqueKeyInternal() string {
|
||||
return s.uniqueKeyInternal
|
||||
}
|
||||
|
||||
type linkSource interface {
|
||||
FillSmartIds(ids []string) []string
|
||||
HasSmartIds() bool
|
||||
}
|
||||
|
|
|
@ -3,15 +3,12 @@ package state
|
|||
import (
|
||||
"errors"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple/base"
|
||||
|
@ -660,7 +657,7 @@ func TestState_ContainsInStore(t *testing.T) {
|
|||
// then
|
||||
assert.True(t, st.ContainsInStore([]string{collectionName, "subobject1"}))
|
||||
assert.True(t, st.ContainsInStore([]string{collectionName, "subobject2"}))
|
||||
//nested
|
||||
// nested
|
||||
assert.False(t, st.ContainsInStore([]string{collectionName, "subobject3"}))
|
||||
assert.False(t, st.ContainsInStore([]string{collectionName, "subobject1", "subobject3"}))
|
||||
assert.True(t, st.ContainsInStore([]string{collectionName, "subobject2", "subobject3"}))
|
||||
|
@ -703,7 +700,7 @@ func TestState_HasInStore(t *testing.T) {
|
|||
// then
|
||||
assert.True(t, st.HasInStore([]string{collectionName, "subobject1"}))
|
||||
assert.True(t, st.HasInStore([]string{collectionName, "subobject2"}))
|
||||
//nested
|
||||
// nested
|
||||
assert.False(t, st.HasInStore([]string{collectionName, "subobject3"}))
|
||||
assert.False(t, st.HasInStore([]string{collectionName, "subobject1", "subobject3"}))
|
||||
assert.True(t, st.HasInStore([]string{collectionName, "subobject2", "subobject3"}))
|
||||
|
@ -774,282 +771,6 @@ func TestState_Validate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinks(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := NewDoc("root", map[string]simple.Block{
|
||||
"root": simple.New(&model.Block{
|
||||
Id: "root",
|
||||
ChildrenIds: []string{"childBlock", "childBlock2", "childBlock3"},
|
||||
}),
|
||||
"childBlock": simple.New(&model.Block{Id: "childBlock",
|
||||
Content: &model.BlockContentOfText{
|
||||
Text: &model.BlockContentText{Marks: &model.BlockContentTextMarks{
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 8,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Object,
|
||||
Param: "objectID",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 9,
|
||||
To: 19,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: "objectID2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}),
|
||||
"childBlock2": simple.New(&model.Block{Id: "childBlock2",
|
||||
Content: &model.BlockContentOfBookmark{
|
||||
Bookmark: &model.BlockContentBookmark{
|
||||
TargetObjectId: "objectID3",
|
||||
},
|
||||
}}),
|
||||
"childBlock3": simple.New(&model.Block{Id: "childBlock3",
|
||||
Content: &model.BlockContentOfLink{
|
||||
Link: &model.BlockContentLink{
|
||||
TargetBlockId: "objectID4",
|
||||
},
|
||||
}}),
|
||||
}).(*State)
|
||||
|
||||
t.Run("all options are turned off", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(true, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 4)
|
||||
})
|
||||
|
||||
t.Run("block option is turned on: get ids from blocks", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(false, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinksAndRelations(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := NewDoc("root", map[string]simple.Block{
|
||||
"root": simple.New(&model.Block{
|
||||
Id: "root",
|
||||
ChildrenIds: []string{"childBlock", "childBlock2", "childBlock3"},
|
||||
}),
|
||||
"childBlock": simple.New(&model.Block{Id: "childBlock",
|
||||
Content: &model.BlockContentOfText{
|
||||
Text: &model.BlockContentText{Marks: &model.BlockContentTextMarks{
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 8,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Object,
|
||||
Param: "objectID",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 9,
|
||||
To: 19,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: "objectID2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}),
|
||||
"childBlock2": simple.New(&model.Block{Id: "childBlock2",
|
||||
Content: &model.BlockContentOfBookmark{
|
||||
Bookmark: &model.BlockContentBookmark{
|
||||
TargetObjectId: "objectID3",
|
||||
},
|
||||
}}),
|
||||
"childBlock3": simple.New(&model.Block{Id: "childBlock3",
|
||||
Content: &model.BlockContentOfLink{
|
||||
Link: &model.BlockContentLink{
|
||||
TargetBlockId: "objectID4",
|
||||
},
|
||||
}}),
|
||||
}).(*State)
|
||||
|
||||
relations := []*model.RelationLink{
|
||||
{
|
||||
Key: "relation1",
|
||||
Format: model.RelationFormat_file,
|
||||
},
|
||||
{
|
||||
Key: "relation2",
|
||||
Format: model.RelationFormat_tag,
|
||||
},
|
||||
{
|
||||
Key: "relation3",
|
||||
Format: model.RelationFormat_status,
|
||||
},
|
||||
{
|
||||
Key: "relation4",
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
}
|
||||
stateWithLinks.AddRelationLinks(relations...)
|
||||
|
||||
t.Run("blocks option is turned on: get ids from blocks", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(true, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 4)
|
||||
})
|
||||
|
||||
t.Run("blocks option and relations options are turned on: get ids from blocks and relations", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(true, false, true, false, false)
|
||||
assert.Len(t, objectIDs, 10) // 4 links + 4 relations + 2 derived relations
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinksDetailsAndRelations(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := NewDoc("root", map[string]simple.Block{
|
||||
"root": simple.New(&model.Block{
|
||||
Id: "root",
|
||||
ChildrenIds: []string{"childBlock", "childBlock2", "childBlock3"},
|
||||
}),
|
||||
"childBlock": simple.New(&model.Block{Id: "childBlock",
|
||||
Content: &model.BlockContentOfText{
|
||||
Text: &model.BlockContentText{Marks: &model.BlockContentTextMarks{
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 8,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Object,
|
||||
Param: "objectID",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 9,
|
||||
To: 19,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: "objectID2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}),
|
||||
"childBlock2": simple.New(&model.Block{Id: "childBlock2",
|
||||
Content: &model.BlockContentOfBookmark{
|
||||
Bookmark: &model.BlockContentBookmark{
|
||||
TargetObjectId: "objectID3",
|
||||
},
|
||||
}}),
|
||||
"childBlock3": simple.New(&model.Block{Id: "childBlock3",
|
||||
Content: &model.BlockContentOfLink{
|
||||
Link: &model.BlockContentLink{
|
||||
TargetBlockId: "objectID4",
|
||||
},
|
||||
}}),
|
||||
}).(*State)
|
||||
|
||||
relations := []*model.RelationLink{
|
||||
{
|
||||
Key: "relation1",
|
||||
Format: model.RelationFormat_file,
|
||||
},
|
||||
{
|
||||
Key: "relation2",
|
||||
Format: model.RelationFormat_tag,
|
||||
},
|
||||
{
|
||||
Key: "relation3",
|
||||
Format: model.RelationFormat_status,
|
||||
},
|
||||
{
|
||||
Key: "relation4",
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
{
|
||||
Key: "relation5",
|
||||
Format: model.RelationFormat_date,
|
||||
},
|
||||
}
|
||||
stateWithLinks.AddRelationLinks(relations...)
|
||||
stateWithLinks.SetDetail("relation1", pbtypes.String("file"))
|
||||
stateWithLinks.SetDetail("relation2", pbtypes.String("option1"))
|
||||
stateWithLinks.SetDetail("relation3", pbtypes.String("option2"))
|
||||
stateWithLinks.SetDetail("relation4", pbtypes.String("option3"))
|
||||
stateWithLinks.SetDetail("relation5", pbtypes.Int64(time.Now().Unix()))
|
||||
|
||||
t.Run("blocks option is turned on: get ids from blocks", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(true, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 4) // links
|
||||
})
|
||||
t.Run("blocks option and relations option are turned on: get ids from blocks and relations", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(true, false, true, false, false)
|
||||
assert.Len(t, objectIDs, 11) // 4 links + 5 relations + 2 derived relations
|
||||
})
|
||||
t.Run("blocks, relations and details option are turned on: get ids from blocks, relations and details", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(true, true, true, false, false)
|
||||
assert.Len(t, objectIDs, 16) // 4 links + 5 relations + 2 derived relations + 3 options + 1 fileID + 1 date
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinksCreatorModifierWorkspace(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := NewDoc("root", nil).(*State)
|
||||
relations := []*model.RelationLink{
|
||||
{
|
||||
Key: "relation1",
|
||||
Format: model.RelationFormat_date,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyCreatedDate.String(),
|
||||
Format: model.RelationFormat_date,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyCreator.String(),
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyWorkspaceId.String(),
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyLastModifiedBy.String(),
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
}
|
||||
stateWithLinks.AddRelationLinks(relations...)
|
||||
stateWithLinks.SetDetail("relation1", pbtypes.Int64(time.Now().Unix()))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyCreatedDate.String(), pbtypes.Int64(time.Now().Unix()))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyCreator.String(), pbtypes.String("creator"))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyWorkspaceId.String(), pbtypes.String("workspaceID"))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyLastModifiedBy.String(), pbtypes.String("lastModifiedBy"))
|
||||
|
||||
t.Run("details option is turned on: get ids only from details", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(false, true, false, false, true)
|
||||
assert.Len(t, objectIDs, 4) // creator + workspaceID + lastModifiedBy + 1 date
|
||||
})
|
||||
|
||||
t.Run("details and relations options are turned on: get ids from details and relations", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(false, true, true, false, true)
|
||||
assert.Len(t, objectIDs, 11) // 5 relations + creator + workspaceID + lastModifiedBy + 1 date + 2 derived relations
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsObjectTypes(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := NewDoc("root", nil).(*State)
|
||||
stateWithLinks.SetObjectType(bundle.TypeKeyPage.URL())
|
||||
|
||||
t.Run("all options are turned off", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(false, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 0)
|
||||
})
|
||||
t.Run("objTypes option is turned on, get only object types id", func(t *testing.T) {
|
||||
objectIDs := stateWithLinks.DepSmartIds(false, false, false, true, false)
|
||||
assert.Len(t, objectIDs, 1)
|
||||
assert.Equal(t, objectIDs[0], bundle.TypeKeyPage.URL())
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_CheckRestrictions(t *testing.T) {
|
||||
t.Run("state doesn't have parent state", func(t *testing.T) {
|
||||
// given
|
||||
|
@ -1662,7 +1383,7 @@ func TestState_ApplyChangeIgnoreErrBlockUpdateTextParams(t *testing.T) {
|
|||
// when
|
||||
st.ApplyChangeIgnoreErr(change)
|
||||
|
||||
//then
|
||||
// then
|
||||
b := st.Get("textBlock")
|
||||
assert.NotNil(t, b)
|
||||
assert.Equal(t, "updated text", b.Model().GetText().Text)
|
||||
|
@ -2456,7 +2177,7 @@ func TestState_ApplyChangeIgnoreErrObjectTypeAdd(t *testing.T) {
|
|||
st.ApplyChangeIgnoreErr(change)
|
||||
|
||||
// then
|
||||
assert.Equal(t, "ot-page", st.ObjectType())
|
||||
assert.Equal(t, "page", st.ObjectType())
|
||||
})
|
||||
|
||||
t.Run("apply ObjectTypeAdd change: add another object type", func(t *testing.T) {
|
||||
|
@ -2471,7 +2192,7 @@ func TestState_ApplyChangeIgnoreErrObjectTypeAdd(t *testing.T) {
|
|||
st.ApplyChangeIgnoreErr(change)
|
||||
|
||||
// apply
|
||||
assert.Equal(t, []string{"ot-page", "ot-note"}, st.ObjectTypes())
|
||||
assert.Equal(t, []string{"page", "note"}, st.ObjectTypes())
|
||||
})
|
||||
|
||||
t.Run("apply ObjectTypeAdd change: add existing object type - no changes", func(t *testing.T) {
|
||||
|
@ -2486,7 +2207,7 @@ func TestState_ApplyChangeIgnoreErrObjectTypeAdd(t *testing.T) {
|
|||
st.ApplyChangeIgnoreErr(change)
|
||||
|
||||
// then
|
||||
assert.Equal(t, []string{"ot-page", "ot-note"}, st.ObjectTypes())
|
||||
assert.Equal(t, []string{"page", "note"}, st.ObjectTypes())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2496,7 +2217,7 @@ func TestState_ApplyChangeIgnoreErrObjectTypeRemove(t *testing.T) {
|
|||
Id: "root",
|
||||
}),
|
||||
}).(*State)
|
||||
st.objectTypes = append(st.objectTypes, "ot-page")
|
||||
st.objectTypes = append(st.objectTypes, "page")
|
||||
|
||||
t.Run("apply ObjectTypeRemove change: remove existing object type", func(t *testing.T) {
|
||||
// given
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/simple/link"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple/text"
|
||||
"github.com/anyproto/anytype-heart/core/event/mock_event"
|
||||
"github.com/anyproto/anytype-heart/core/session"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
|
@ -154,7 +153,7 @@ func TestTextImpl_Split(t *testing.T) {
|
|||
assert.Equal(t, "two", r.Pick(newId).Model().GetText().Text)
|
||||
})
|
||||
t.Run("split - when code block", func(t *testing.T) {
|
||||
//given
|
||||
// given
|
||||
sb := smarttest.New("test")
|
||||
sb.AddBlock(
|
||||
simple.New(
|
||||
|
@ -164,9 +163,10 @@ func TestTextImpl_Split(t *testing.T) {
|
|||
},
|
||||
),
|
||||
).AddBlock(newCodeBlock("1", "onetwo"))
|
||||
tb := NewText(sb, nil)
|
||||
sender := mock_event.NewMockSender(t)
|
||||
tb := NewText(sb, nil, sender)
|
||||
|
||||
//when
|
||||
// when
|
||||
newId, err := tb.Split(nil, pb.RpcBlockSplitRequest{
|
||||
BlockId: "1",
|
||||
Range: &model.Range{From: 3, To: 3},
|
||||
|
@ -174,7 +174,7 @@ func TestTextImpl_Split(t *testing.T) {
|
|||
Mode: pb.RpcBlockSplitRequest_BOTTOM,
|
||||
})
|
||||
|
||||
//then
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, newId)
|
||||
r := sb.NewState()
|
||||
|
|
|
@ -3,20 +3,23 @@ package editor
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock/smarttest"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/template"
|
||||
"github.com/anyproto/anytype-heart/core/block/migration"
|
||||
"github.com/anyproto/anytype-heart/core/block/uniquekey"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/anyproto/anytype-heart/util/testMock"
|
||||
"github.com/gogo/protobuf/types"
|
||||
)
|
||||
|
||||
func NewTemplateTest(ctrl *gomock.Controller, templateName string) (*Template, error) {
|
||||
func NewTemplateTest(t *testing.T, ctrl *gomock.Controller, templateName string) (*Template, error) {
|
||||
sb := smarttest.New("root")
|
||||
_ = sb.SetDetails(nil, []*pb.RpcObjectSetDetailsDetail{&pb.RpcObjectSetDetailsDetail{
|
||||
Key: bundle.RelationKeyName.String(),
|
||||
|
@ -24,21 +27,31 @@ func NewTemplateTest(ctrl *gomock.Controller, templateName string) (*Template, e
|
|||
}}, false)
|
||||
objectStore := testMock.NewMockObjectStore(ctrl)
|
||||
objectStore.EXPECT().GetObjectTypes(gomock.Any()).AnyTimes()
|
||||
t := &Template{
|
||||
templ := &Template{
|
||||
Page: &Page{
|
||||
SmartBlock: sb,
|
||||
objectStore: objectStore,
|
||||
},
|
||||
}
|
||||
uniqueKey, err := uniquekey.New(model.SmartBlockType_STType, bundle.TypeKeyPage.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
objectStore.EXPECT().GetObjectByUniqueKey(gomock.Any(), uniqueKey.Marshal()).Return(&model.ObjectDetails{
|
||||
Details: &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRecommendedLayout.String(): pbtypes.Int64(int64(model.ObjectType_basic)),
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
initCtx := &smartblock.InitContext{IsNewObject: true}
|
||||
if err := t.Init(initCtx); err != nil {
|
||||
if err := templ.Init(initCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
migration.RunMigrations(t, initCtx)
|
||||
if err := t.Apply(initCtx.State); err != nil {
|
||||
migration.RunMigrations(templ, initCtx)
|
||||
if err := templ.Apply(initCtx.State); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
return templ, nil
|
||||
}
|
||||
|
||||
func TestTemplate_GetNewPageState(t *testing.T) {
|
||||
|
@ -48,7 +61,7 @@ func TestTemplate_GetNewPageState(t *testing.T) {
|
|||
templateName := "template"
|
||||
|
||||
t.Run("empty page name", func(t *testing.T) {
|
||||
tmpl, err := NewTemplateTest(ctrl, templateName)
|
||||
tmpl, err := NewTemplateTest(t, ctrl, templateName)
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := tmpl.GetNewPageState("")
|
||||
|
@ -58,7 +71,7 @@ func TestTemplate_GetNewPageState(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("custom page name", func(t *testing.T) {
|
||||
tmpl, err := NewTemplateTest(ctrl, templateName)
|
||||
tmpl, err := NewTemplateTest(t, ctrl, templateName)
|
||||
require.NoError(t, err)
|
||||
|
||||
customName := "some name"
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/converter/pbjson"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/core/files"
|
||||
"github.com/anyproto/anytype-heart/core/relation"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core"
|
||||
|
@ -56,12 +57,13 @@ type Export interface {
|
|||
}
|
||||
|
||||
type export struct {
|
||||
blockService *block.Service
|
||||
picker getblock.Picker
|
||||
objectStore objectstore.ObjectStore
|
||||
coreService core.Service
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider
|
||||
fileService files.Service
|
||||
blockService *block.Service
|
||||
picker getblock.Picker
|
||||
objectStore objectstore.ObjectStore
|
||||
coreService core.Service
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider
|
||||
fileService files.Service
|
||||
relationService relation.Service
|
||||
}
|
||||
|
||||
func New() Export {
|
||||
|
@ -75,6 +77,7 @@ func (e *export) Init(a *app.App) (err error) {
|
|||
e.fileService = app.MustComponent[files.Service](a)
|
||||
e.picker = app.MustComponent[getblock.Picker](a)
|
||||
e.sbtProvider = app.MustComponent[typeprovider.SmartBlockTypeProvider](a)
|
||||
e.relationService = app.MustComponent[relation.Service](a)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -119,14 +122,14 @@ func (e *export) Export(ctx context.Context, req pb.RpcObjectListExportRequest)
|
|||
if req.Format == pb.RpcObjectListExport_SVG {
|
||||
format = dot.ExportFormatSVG
|
||||
}
|
||||
mc := dot.NewMultiConverter(format, e.sbtProvider)
|
||||
mc := dot.NewMultiConverter(format, e.sbtProvider, e.relationService)
|
||||
mc.SetKnownDocs(docs)
|
||||
var werr error
|
||||
if succeed, werr = e.writeMultiDoc(ctx, mc, wr, docs, queue, req.IncludeFiles); werr != nil {
|
||||
log.Warnf("can't export docs: %v", werr)
|
||||
}
|
||||
} else if req.Format == pb.RpcObjectListExport_GRAPH_JSON {
|
||||
mc := graphjson.NewMultiConverter(e.sbtProvider)
|
||||
mc := graphjson.NewMultiConverter(e.sbtProvider, e.relationService)
|
||||
mc.SetKnownDocs(docs)
|
||||
var werr error
|
||||
if succeed, werr = e.writeMultiDoc(ctx, mc, wr, docs, queue, req.IncludeFiles); werr != nil {
|
||||
|
|
|
@ -120,7 +120,7 @@ func getDetailsFromCSVTable(csvTable [][]string, useFirstRowForRelations bool) (
|
|||
})
|
||||
relationsSnapshots = append(relationsSnapshots, &converter.Snapshot{
|
||||
Id: addr.RelationKeyToIdPrefix + id,
|
||||
SbType: smartblock.SmartBlockTypeSubObject,
|
||||
SbType: smartblock.SmartBlockTypeRelation,
|
||||
Snapshot: &pb.ChangeSnapshot{Data: &model.SmartBlockSnapshotBase{
|
||||
Details: getRelationDetails(relationName, id, float64(model.RelationFormat_longtext)),
|
||||
ObjectTypes: []string{bundle.TypeKeyRelation.String()},
|
||||
|
|
|
@ -188,7 +188,7 @@ func TestCsv_GetSnapshotsTranspose(t *testing.T) {
|
|||
assert.Len(t, sn.Snapshots, 4) // 2 object + root collection + transpose collection + 1 relations
|
||||
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
if snapshot.SbType == sb.SmartBlockTypeSubObject {
|
||||
if snapshot.SbType == sb.SmartBlockTypeRelation {
|
||||
name := pbtypes.GetString(snapshot.Snapshot.GetData().GetDetails(), bundle.RelationKeyName.String())
|
||||
assert.True(t, name == "name" || name == "price")
|
||||
}
|
||||
|
@ -197,8 +197,8 @@ func TestCsv_GetSnapshotsTranspose(t *testing.T) {
|
|||
var collection *converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.URL()) &&
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) &&
|
||||
pbtypes.GetString(snapshot.Snapshot.Data.Details, bundle.RelationKeyName.String()) == "transpose Transpose" {
|
||||
collection = snapshot
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ func TestCsv_GetSnapshotsTransposeUseFirstRowForRelationsOff(t *testing.T) {
|
|||
assert.Len(t, sn.Snapshots, 5) // 2 object + root collection + transpose collection + 1 relations
|
||||
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
if snapshot.SbType == sb.SmartBlockTypeSubObject {
|
||||
if snapshot.SbType == sb.SmartBlockTypeRelation {
|
||||
name := pbtypes.GetString(snapshot.Snapshot.GetData().GetDetails(), bundle.RelationKeyName.String())
|
||||
assert.True(t, name == "Field 1" || name == "Field 2")
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ func TestCsv_GetSnapshotsUseFirstColumnForRelationsOn(t *testing.T) {
|
|||
var rowsObjects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
rowsObjects = append(rowsObjects, snapshot)
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ func TestCsv_GetSnapshotsUseFirstColumnForRelationsOff(t *testing.T) {
|
|||
var objects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ func TestCsv_GetSnapshotsUseFirstColumnForRelationsOff(t *testing.T) {
|
|||
var subObjects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType == sb.SmartBlockTypeSubObject {
|
||||
if snapshot.SbType == sb.SmartBlockTypeRelation {
|
||||
subObjects = append(subObjects, snapshot)
|
||||
}
|
||||
}
|
||||
|
@ -375,21 +375,7 @@ func TestCsv_GetSnapshotsBigFile(t *testing.T) {
|
|||
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, errors.Is(err.GetResultError(pb.RpcObjectImportRequest_Csv), converter.ErrLimitExceeded))
|
||||
assert.NotNil(t, sn)
|
||||
|
||||
var objects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Len(t, objects, limitForRows)
|
||||
for _, object := range objects {
|
||||
assert.Len(t, object.Snapshot.Data.Details.Fields, limitForColumns)
|
||||
}
|
||||
assert.Nil(t, sn)
|
||||
}
|
||||
|
||||
func TestCsv_GetSnapshotsEmptyFirstLineUseFirstColumnForRelationsOn(t *testing.T) {
|
||||
|
@ -413,7 +399,7 @@ func TestCsv_GetSnapshotsEmptyFirstLineUseFirstColumnForRelationsOn(t *testing.T
|
|||
|
||||
var subObjects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
if snapshot.SbType == sb.SmartBlockTypeSubObject {
|
||||
if snapshot.SbType == sb.SmartBlockTypeRelation {
|
||||
subObjects = append(subObjects, snapshot)
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +427,7 @@ func TestCsv_GetSnapshotsEmptyFirstLineUseFirstColumnForRelationsOff(t *testing.
|
|||
|
||||
var subObjects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
if snapshot.SbType == sb.SmartBlockTypeSubObject {
|
||||
if snapshot.SbType == sb.SmartBlockTypeRelation {
|
||||
subObjects = append(subObjects, snapshot)
|
||||
}
|
||||
}
|
||||
|
@ -488,7 +474,7 @@ func TestCsv_GetSnapshots1000RowsFile(t *testing.T) {
|
|||
var objects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
|
@ -514,7 +500,7 @@ func TestCsv_GetSnapshots1000RowsFile(t *testing.T) {
|
|||
objects = []*converter.Snapshot{}
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
|
@ -593,7 +579,7 @@ func Test_findUniqueRelationWithSpaces(t *testing.T) {
|
|||
|
||||
var subObjects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
if snapshot.SbType == sb.SmartBlockTypeSubObject {
|
||||
if snapshot.SbType == sb.SmartBlockTypeRelation {
|
||||
subObjects = append(subObjects, snapshot)
|
||||
}
|
||||
}
|
||||
|
@ -637,8 +623,8 @@ func TestCsv_GetSnapshots10Relations(t *testing.T) {
|
|||
var objects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.URL()) {
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
}
|
||||
|
@ -668,8 +654,8 @@ func TestCsv_GetSnapshots10Relations(t *testing.T) {
|
|||
objects = []*converter.Snapshot{}
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.URL()) {
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
}
|
||||
|
@ -708,8 +694,8 @@ func TestCsv_GetSnapshotsTableModeDifferentColumnsNumber(t *testing.T) {
|
|||
var objects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.URL()) {
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
}
|
||||
|
@ -746,8 +732,8 @@ func TestCsv_GetSnapshotsTableModeDifferentColumnsNumber(t *testing.T) {
|
|||
var objects []*converter.Snapshot
|
||||
for _, snapshot := range sn.Snapshots {
|
||||
// only objects created from rows
|
||||
if snapshot.SbType != sb.SmartBlockTypeSubObject &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.URL()) {
|
||||
if snapshot.SbType != sb.SmartBlockTypeRelation &&
|
||||
!lo.Contains(snapshot.Snapshot.Data.ObjectTypes, bundle.TypeKeyCollection.String()) {
|
||||
objects = append(objects, snapshot)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -511,7 +511,7 @@ func (w *Creator) createObjectType(ctx context.Context, spaceID string, details
|
|||
createState := state.NewDoc("", nil).(*state.State)
|
||||
createState.SetObjectType(bundle.TypeKeyObjectType.String())
|
||||
createState.SetDetails(object)
|
||||
return w.CreateSmartBlockFromState(ctx, spaceID, coresb.SmartBlockTypeRelation, nil, createState)
|
||||
return w.CreateSmartBlockFromState(ctx, spaceID, coresb.SmartBlockTypeObjectType, nil, createState)
|
||||
}
|
||||
|
||||
func getUniqueKeyOrGenerate(sbType coresb.SmartBlockType, details *types.Struct) (uniquekey.UniqueKey, error) {
|
||||
|
|
137
core/block/object/objectlink/dependent_objects.go
Normal file
137
core/block/object/objectlink/dependent_objects.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package objectlink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/logging"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/samber/lo"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logging.Logger("objectlink")
|
||||
|
||||
type KeyToIDConverter interface {
|
||||
GetRelationIdByKey(ctx context.Context, spaceId string, key bundle.RelationKey) (id string, err error)
|
||||
GetTypeIdByKey(ctx context.Context, spaceId string, key bundle.TypeKey) (id string, err error)
|
||||
}
|
||||
|
||||
type linkSource interface {
|
||||
FillSmartIds(ids []string) []string
|
||||
HasSmartIds() bool
|
||||
}
|
||||
|
||||
func DependentObjectIDs(s *state.State, converter KeyToIDConverter, blocks, details, relations, objTypes, creatorModifierWorkspace bool) (ids []string) {
|
||||
if blocks {
|
||||
err := s.Iterate(func(b simple.Block) (isContinue bool) {
|
||||
if ls, ok := b.(linkSource); ok {
|
||||
ids = ls.FillSmartIds(ids)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
log.With("objectID", s.RootId()).Errorf("failed to iterate over simple blocks: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if objTypes {
|
||||
for _, objectTypeKey := range s.ObjectTypes() {
|
||||
if objectTypeKey == "" { // TODO is it possible?
|
||||
log.Errorf("sb %s has empty ot", s.RootId())
|
||||
continue
|
||||
}
|
||||
id, err := converter.GetTypeIdByKey(context.Background(), s.SpaceID(), bundle.TypeKey(objectTypeKey))
|
||||
if err != nil {
|
||||
log.With("objectID", s.RootId()).Errorf("failed to get object type id by key %s: %s", objectTypeKey, err)
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
|
||||
var det *types.Struct
|
||||
if details {
|
||||
det = s.CombinedDetails()
|
||||
}
|
||||
|
||||
for _, rel := range s.GetRelationLinks() {
|
||||
// do not index local dates such as lastOpened/lastModified
|
||||
if relations {
|
||||
id, err := converter.GetRelationIdByKey(context.Background(), s.SpaceID(), bundle.RelationKey(rel.Key))
|
||||
if err != nil {
|
||||
log.With("objectID", s.RootId()).Errorf("failed to get relation id by key %s: %s", rel.Key, err)
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
if !details {
|
||||
continue
|
||||
}
|
||||
|
||||
// handle corner cases first for specific formats
|
||||
if rel.Format == model.RelationFormat_date &&
|
||||
!lo.Contains(bundle.LocalRelationsKeys, rel.Key) &&
|
||||
!lo.Contains(bundle.DerivedRelationsKeys, rel.Key) {
|
||||
relInt := pbtypes.GetInt64(det, rel.Key)
|
||||
if relInt > 0 {
|
||||
t := time.Unix(relInt, 0)
|
||||
t = t.In(time.UTC)
|
||||
ids = append(ids, addr.TimeToID(t))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if rel.Key == bundle.RelationKeyCreator.String() ||
|
||||
rel.Key == bundle.RelationKeyLastModifiedBy.String() ||
|
||||
rel.Key == bundle.RelationKeyWorkspaceId.String() {
|
||||
if creatorModifierWorkspace {
|
||||
v := pbtypes.GetString(det, rel.Key)
|
||||
ids = append(ids, v)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if rel.Key == bundle.RelationKeyId.String() ||
|
||||
rel.Key == bundle.RelationKeyLinks.String() ||
|
||||
rel.Key == bundle.RelationKeyType.String() || // always skip type because it was proceed above
|
||||
rel.Key == bundle.RelationKeyFeaturedRelations.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
if rel.Key == bundle.RelationKeyCoverId.String() {
|
||||
v := pbtypes.GetString(det, rel.Key)
|
||||
_, err := cid.Decode(v)
|
||||
if err != nil {
|
||||
// this is an exception cause coverId can contains not a file hash but color
|
||||
continue
|
||||
}
|
||||
ids = append(ids, v)
|
||||
}
|
||||
|
||||
if rel.Format != model.RelationFormat_object &&
|
||||
rel.Format != model.RelationFormat_file &&
|
||||
rel.Format != model.RelationFormat_status &&
|
||||
rel.Format != model.RelationFormat_tag {
|
||||
continue
|
||||
}
|
||||
|
||||
// add all object relation values as dependents
|
||||
for _, targetID := range pbtypes.GetStringList(det, rel.Key) {
|
||||
if targetID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ids = append(ids, targetID)
|
||||
}
|
||||
}
|
||||
|
||||
ids = lo.Uniq(ids)
|
||||
return
|
||||
}
|
313
core/block/object/objectlink/dependent_objects_test.go
Normal file
313
core/block/object/objectlink/dependent_objects_test.go
Normal file
|
@ -0,0 +1,313 @@
|
|||
package objectlink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/core/relation/mock_relation"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newTestConverter(t *testing.T) KeyToIDConverter {
|
||||
converter := mock_relation.NewMockService(t)
|
||||
converter.EXPECT().GetRelationIdByKey(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, spaceId string, key bundle.RelationKey) (string, error) {
|
||||
return fakeDerivedID(key.String()), nil
|
||||
}).Maybe()
|
||||
converter.EXPECT().GetTypeIdByKey(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, spaceId string, key bundle.TypeKey) (string, error) {
|
||||
return fakeDerivedID(key.String()), nil
|
||||
}).Maybe()
|
||||
return converter
|
||||
}
|
||||
|
||||
func fakeDerivedID(key string) string {
|
||||
return fmt.Sprintf("derivedFrom(%s)", key)
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinks(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := state.NewDoc("root", map[string]simple.Block{
|
||||
"root": simple.New(&model.Block{
|
||||
Id: "root",
|
||||
ChildrenIds: []string{"childBlock", "childBlock2", "childBlock3"},
|
||||
}),
|
||||
"childBlock": simple.New(&model.Block{Id: "childBlock",
|
||||
Content: &model.BlockContentOfText{
|
||||
Text: &model.BlockContentText{Marks: &model.BlockContentTextMarks{
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 8,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Object,
|
||||
Param: "objectID",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 9,
|
||||
To: 19,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: "objectID2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}),
|
||||
"childBlock2": simple.New(&model.Block{Id: "childBlock2",
|
||||
Content: &model.BlockContentOfBookmark{
|
||||
Bookmark: &model.BlockContentBookmark{
|
||||
TargetObjectId: "objectID3",
|
||||
},
|
||||
}}),
|
||||
"childBlock3": simple.New(&model.Block{Id: "childBlock3",
|
||||
Content: &model.BlockContentOfLink{
|
||||
Link: &model.BlockContentLink{
|
||||
TargetBlockId: "objectID4",
|
||||
},
|
||||
}}),
|
||||
}).(*state.State)
|
||||
converter := newTestConverter(t)
|
||||
|
||||
t.Run("all options are turned off", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, true, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 4)
|
||||
})
|
||||
|
||||
t.Run("block option is turned on: get ids from blocks", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, false, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinksAndRelations(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := state.NewDoc("root", map[string]simple.Block{
|
||||
"root": simple.New(&model.Block{
|
||||
Id: "root",
|
||||
ChildrenIds: []string{"childBlock", "childBlock2", "childBlock3"},
|
||||
}),
|
||||
"childBlock": simple.New(&model.Block{Id: "childBlock",
|
||||
Content: &model.BlockContentOfText{
|
||||
Text: &model.BlockContentText{Marks: &model.BlockContentTextMarks{
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 8,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Object,
|
||||
Param: "objectID",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 9,
|
||||
To: 19,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: "objectID2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}),
|
||||
"childBlock2": simple.New(&model.Block{Id: "childBlock2",
|
||||
Content: &model.BlockContentOfBookmark{
|
||||
Bookmark: &model.BlockContentBookmark{
|
||||
TargetObjectId: "objectID3",
|
||||
},
|
||||
}}),
|
||||
"childBlock3": simple.New(&model.Block{Id: "childBlock3",
|
||||
Content: &model.BlockContentOfLink{
|
||||
Link: &model.BlockContentLink{
|
||||
TargetBlockId: "objectID4",
|
||||
},
|
||||
}}),
|
||||
}).(*state.State)
|
||||
converter := newTestConverter(t)
|
||||
|
||||
relations := []*model.RelationLink{
|
||||
{
|
||||
Key: "relation1",
|
||||
Format: model.RelationFormat_file,
|
||||
},
|
||||
{
|
||||
Key: "relation2",
|
||||
Format: model.RelationFormat_tag,
|
||||
},
|
||||
{
|
||||
Key: "relation3",
|
||||
Format: model.RelationFormat_status,
|
||||
},
|
||||
{
|
||||
Key: "relation4",
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
}
|
||||
stateWithLinks.AddRelationLinks(relations...)
|
||||
|
||||
t.Run("blocks option is turned on: get ids from blocks", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, true, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 4)
|
||||
})
|
||||
|
||||
t.Run("blocks option and relations options are turned on: get ids from blocks and relations", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, true, false, true, false, false)
|
||||
assert.Len(t, objectIDs, 8) // 4 links + 4 relations
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinksDetailsAndRelations(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := state.NewDoc("root", map[string]simple.Block{
|
||||
"root": simple.New(&model.Block{
|
||||
Id: "root",
|
||||
ChildrenIds: []string{"childBlock", "childBlock2", "childBlock3"},
|
||||
}),
|
||||
"childBlock": simple.New(&model.Block{Id: "childBlock",
|
||||
Content: &model.BlockContentOfText{
|
||||
Text: &model.BlockContentText{Marks: &model.BlockContentTextMarks{
|
||||
Marks: []*model.BlockContentTextMark{
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 0,
|
||||
To: 8,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Object,
|
||||
Param: "objectID",
|
||||
},
|
||||
{
|
||||
Range: &model.Range{
|
||||
From: 9,
|
||||
To: 19,
|
||||
},
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: "objectID2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}),
|
||||
"childBlock2": simple.New(&model.Block{Id: "childBlock2",
|
||||
Content: &model.BlockContentOfBookmark{
|
||||
Bookmark: &model.BlockContentBookmark{
|
||||
TargetObjectId: "objectID3",
|
||||
},
|
||||
}}),
|
||||
"childBlock3": simple.New(&model.Block{Id: "childBlock3",
|
||||
Content: &model.BlockContentOfLink{
|
||||
Link: &model.BlockContentLink{
|
||||
TargetBlockId: "objectID4",
|
||||
},
|
||||
}}),
|
||||
}).(*state.State)
|
||||
converter := newTestConverter(t)
|
||||
|
||||
relations := []*model.RelationLink{
|
||||
{
|
||||
Key: "relation1",
|
||||
Format: model.RelationFormat_file,
|
||||
},
|
||||
{
|
||||
Key: "relation2",
|
||||
Format: model.RelationFormat_tag,
|
||||
},
|
||||
{
|
||||
Key: "relation3",
|
||||
Format: model.RelationFormat_status,
|
||||
},
|
||||
{
|
||||
Key: "relation4",
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
{
|
||||
Key: "relation5",
|
||||
Format: model.RelationFormat_date,
|
||||
},
|
||||
}
|
||||
stateWithLinks.AddRelationLinks(relations...)
|
||||
stateWithLinks.SetDetail("relation1", pbtypes.String("file"))
|
||||
stateWithLinks.SetDetail("relation2", pbtypes.String("option1"))
|
||||
stateWithLinks.SetDetail("relation3", pbtypes.String("option2"))
|
||||
stateWithLinks.SetDetail("relation4", pbtypes.String("option3"))
|
||||
stateWithLinks.SetDetail("relation5", pbtypes.Int64(time.Now().Unix()))
|
||||
|
||||
t.Run("blocks option is turned on: get ids from blocks", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, true, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 4) // links
|
||||
})
|
||||
t.Run("blocks option and relations option are turned on: get ids from blocks and relations", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, true, false, true, false, false)
|
||||
assert.Len(t, objectIDs, 9) // 4 links + 5 relations
|
||||
})
|
||||
t.Run("blocks, relations and details option are turned on: get ids from blocks, relations and details", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, true, true, true, false, false)
|
||||
assert.Len(t, objectIDs, 14) // 4 links + 5 relations + 3 options + 1 fileID + 1 date
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsLinksCreatorModifierWorkspace(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := state.NewDoc("root", nil).(*state.State)
|
||||
relations := []*model.RelationLink{
|
||||
{
|
||||
Key: "relation1",
|
||||
Format: model.RelationFormat_date,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyCreatedDate.String(),
|
||||
Format: model.RelationFormat_date,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyCreator.String(),
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyWorkspaceId.String(),
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyLastModifiedBy.String(),
|
||||
Format: model.RelationFormat_object,
|
||||
},
|
||||
}
|
||||
stateWithLinks.AddRelationLinks(relations...)
|
||||
stateWithLinks.SetDetail("relation1", pbtypes.Int64(time.Now().Unix()))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyCreatedDate.String(), pbtypes.Int64(time.Now().Unix()))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyCreator.String(), pbtypes.String("creator"))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyWorkspaceId.String(), pbtypes.String("workspaceID"))
|
||||
stateWithLinks.SetDetail(bundle.RelationKeyLastModifiedBy.String(), pbtypes.String("lastModifiedBy"))
|
||||
converter := newTestConverter(t)
|
||||
|
||||
t.Run("details option is turned on: get ids only from details", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, false, true, false, false, true)
|
||||
assert.Len(t, objectIDs, 4) // creator + workspaceID + lastModifiedBy + 1 date
|
||||
})
|
||||
|
||||
t.Run("details and relations options are turned on: get ids from details and relations", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, false, true, true, false, true)
|
||||
assert.Len(t, objectIDs, 9) // 5 relations + creator + workspaceID + lastModifiedBy + 1 date
|
||||
})
|
||||
}
|
||||
|
||||
func TestState_DepSmartIdsObjectTypes(t *testing.T) {
|
||||
// given
|
||||
stateWithLinks := state.NewDoc("root", nil).(*state.State)
|
||||
stateWithLinks.SetObjectType(bundle.TypeKeyPage.String())
|
||||
converter := newTestConverter(t)
|
||||
|
||||
t.Run("all options are turned off", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, false, false, false, false, false)
|
||||
assert.Len(t, objectIDs, 0)
|
||||
})
|
||||
t.Run("objTypes option is turned on, get only object types id", func(t *testing.T) {
|
||||
objectIDs := DependentObjectIDs(stateWithLinks, converter, false, false, false, true, false)
|
||||
assert.Equal(t, []string{
|
||||
fakeDerivedID(bundle.TypeKeyPage.String()),
|
||||
}, objectIDs)
|
||||
})
|
||||
}
|
|
@ -2,39 +2,50 @@ package restriction
|
|||
|
||||
import (
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestService_DataviewRestrictions(t *testing.T) {
|
||||
rest := New()
|
||||
assert.True(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.TypeKeyAudio.URL(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_objectType,
|
||||
}).Dataview.Equal(DataviewRestrictions{
|
||||
model.RestrictionsDataviewRestrictions{
|
||||
BlockId: DataviewBlockId,
|
||||
Restrictions: []model.RestrictionsDataviewRestriction{model.Restrictions_DVCreateObject},
|
||||
},
|
||||
}))
|
||||
s := newFixture(t)
|
||||
|
||||
assert.Nil(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.TypeKeyContact.URL(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_objectType,
|
||||
}).Dataview)
|
||||
t.Run("internal types have restrictions", func(t *testing.T) {
|
||||
for _, typeKey := range bundle.InternalTypes {
|
||||
restrictions := s.GetRestrictions(givenObjectType(typeKey))
|
||||
assert.Equal(t,
|
||||
DataviewRestrictions{
|
||||
model.RestrictionsDataviewRestrictions{
|
||||
BlockId: DataviewBlockId,
|
||||
Restrictions: []model.RestrictionsDataviewRestriction{model.Restrictions_DVCreateObject},
|
||||
},
|
||||
},
|
||||
restrictions.Dataview)
|
||||
}
|
||||
})
|
||||
|
||||
assert.Nil(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.RelationKeyType.URL(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_relation,
|
||||
}).Dataview)
|
||||
t.Run("non-internal types have no restrictions", func(t *testing.T) {
|
||||
restrictions := s.GetRestrictions(givenObjectType(bundle.TypeKeyContact))
|
||||
assert.Nil(t, restrictions.Dataview)
|
||||
})
|
||||
|
||||
assert.Nil(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.RelationKeySizeInBytes.URL(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_relation,
|
||||
}).Dataview)
|
||||
t.Run("relations don't have restrictions", func(t *testing.T) {
|
||||
restrictions := s.GetRestrictions(givenRelation(bundle.RelationKeyId))
|
||||
assert.Nil(t, restrictions.Dataview)
|
||||
})
|
||||
|
||||
t.Run("ordinary objects don't have restrictions", func(t *testing.T) {
|
||||
objectTypeID := "derivedFrom(page)"
|
||||
s.objectStoreMock.EXPECT().HasObjectType(objectTypeID).Return(true, nil)
|
||||
restrictions := s.GetRestrictions(
|
||||
newRestrictionHolder(
|
||||
smartblock.SmartBlockTypePage,
|
||||
model.ObjectType_basic,
|
||||
nil,
|
||||
objectTypeID,
|
||||
),
|
||||
)
|
||||
assert.Equal(t, dvRestrictNo, restrictions.Dataview)
|
||||
})
|
||||
}
|
||||
|
|
60
core/block/restriction/fixture_test.go
Normal file
60
core/block/restriction/fixture_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package restriction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/anytype-heart/core/block/uniquekey"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore/mock_objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/typeprovider/mock_typeprovider"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
Service
|
||||
objectStoreMock *mock_objectstore.MockObjectStore
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
objectStore := mock_objectstore.NewMockObjectStore(t)
|
||||
objectStore.EXPECT().Name().Return("objectstore")
|
||||
|
||||
sbtProvider := mock_typeprovider.NewMockSmartBlockTypeProvider(t)
|
||||
sbtProvider.EXPECT().Name().Return("sbtProvider")
|
||||
|
||||
a := &app.App{}
|
||||
a.Register(objectStore)
|
||||
a.Register(sbtProvider)
|
||||
s := New()
|
||||
err := s.Init(a)
|
||||
require.NoError(t, err)
|
||||
return &fixture{
|
||||
Service: s,
|
||||
objectStoreMock: objectStore,
|
||||
}
|
||||
}
|
||||
|
||||
func fakeDerivedID(key string) string {
|
||||
return fmt.Sprintf("derivedFrom(%s)", key)
|
||||
}
|
||||
|
||||
func givenObjectType(typeKey bundle.TypeKey) RestrictionHolder {
|
||||
return newRestrictionHolder(
|
||||
smartblock.SmartBlockTypeObjectType,
|
||||
model.ObjectType_objectType,
|
||||
uniquekey.MustUniqueKey(model.SmartBlockType_STType, typeKey.String()),
|
||||
fakeDerivedID(typeKey.String()),
|
||||
)
|
||||
}
|
||||
|
||||
func givenRelation(relationKey bundle.RelationKey) RestrictionHolder {
|
||||
return newRestrictionHolder(
|
||||
smartblock.SmartBlockTypeRelation,
|
||||
model.ObjectType_relation,
|
||||
uniquekey.MustUniqueKey(model.SmartBlockType_STRelation, relationKey.String()),
|
||||
fakeDerivedID(relationKey.String()),
|
||||
)
|
||||
}
|
|
@ -3,48 +3,20 @@ package restriction
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore/mock_objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/typeprovider/mock_typeprovider"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
Service
|
||||
objectStoreMock *mock_objectstore.MockObjectStore
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
objectStore := mock_objectstore.NewMockObjectStore(t)
|
||||
objectStore.EXPECT().Name().Return("objectstore")
|
||||
|
||||
sbtProvider := mock_typeprovider.NewMockSmartBlockTypeProvider(t)
|
||||
sbtProvider.EXPECT().Name().Return("sbtProvider")
|
||||
|
||||
a := &app.App{}
|
||||
a.Register(objectStore)
|
||||
a.Register(sbtProvider)
|
||||
s := New()
|
||||
err := s.Init(a)
|
||||
require.NoError(t, err)
|
||||
return &fixture{
|
||||
Service: s,
|
||||
objectStoreMock: objectStore,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Use constructors instead for initializing restrictionHolder structures by hand. See givenObjectType and givenRelation
|
||||
func TestService_ObjectRestrictionsById(t *testing.T) {
|
||||
rest := newFixture(t)
|
||||
rest.objectStoreMock.EXPECT().HasObjectType(mock.Anything).Return(false, nil)
|
||||
|
||||
assert.ErrorIs(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: "",
|
||||
tp: model.SmartBlockType_AnytypeProfile,
|
||||
objectType: "",
|
||||
tp: model.SmartBlockType_AnytypeProfile,
|
||||
objectTypeID: "",
|
||||
}).Object.Check(
|
||||
model.Restrictions_Blocks,
|
||||
model.Restrictions_LayoutChange,
|
||||
|
@ -56,128 +28,113 @@ func TestService_ObjectRestrictionsById(t *testing.T) {
|
|||
)
|
||||
|
||||
assert.ErrorIs(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: "",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_collection,
|
||||
objectType: bundle.TypeKeyCollection.URL(),
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_collection,
|
||||
objectTypeID: bundle.TypeKeyCollection.URL(),
|
||||
}).Object.Check(model.Restrictions_Blocks),
|
||||
ErrRestricted,
|
||||
)
|
||||
|
||||
assert.NoError(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: "",
|
||||
tp: model.SmartBlockType_Page,
|
||||
objectType: bundle.TypeKeyPage.URL(),
|
||||
tp: model.SmartBlockType_Page,
|
||||
objectTypeID: bundle.TypeKeyPage.URL(),
|
||||
}).Object.Check(model.Restrictions_Blocks))
|
||||
|
||||
assert.NoError(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.TypeKeyDailyPlan.URL(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_objectType,
|
||||
objectType: bundle.TypeKeyObjectType.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Details,
|
||||
model.Restrictions_Delete,
|
||||
))
|
||||
t.Run("system type", func(t *testing.T) {
|
||||
assert.ErrorIs(t, rest.GetRestrictions(givenObjectType(bundle.TypeKeyObjectType)).Object.Check(
|
||||
model.Restrictions_Details,
|
||||
model.Restrictions_Delete,
|
||||
), ErrRestricted)
|
||||
})
|
||||
|
||||
t.Run("ordinary type", func(t *testing.T) {
|
||||
assert.NoError(t, rest.GetRestrictions(givenObjectType(bundle.TypeKeyDailyPlan)).Object.Check(
|
||||
model.Restrictions_Details,
|
||||
model.Restrictions_Delete,
|
||||
))
|
||||
})
|
||||
|
||||
assert.ErrorIs(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.TypeKeyPage.URL(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_objectType,
|
||||
objectType: bundle.TypeKeyObjectType.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Details,
|
||||
model.Restrictions_Delete,
|
||||
), ErrRestricted)
|
||||
|
||||
assert.ErrorIs(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.TypeKeyBookmark.BundledURL(),
|
||||
tp: model.SmartBlockType_BundledObjectType,
|
||||
layout: model.ObjectType_objectType,
|
||||
objectType: bundle.TypeKeyObjectType.URL(),
|
||||
tp: model.SmartBlockType_BundledObjectType,
|
||||
layout: model.ObjectType_objectType,
|
||||
objectTypeID: bundle.TypeKeyObjectType.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Duplicate,
|
||||
model.Restrictions_Relations,
|
||||
), ErrRestricted)
|
||||
|
||||
assert.NoError(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.RelationKeyImdbRating.String(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_relation,
|
||||
objectType: bundle.TypeKeyRelation.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Delete,
|
||||
model.Restrictions_Relations,
|
||||
model.Restrictions_Details,
|
||||
))
|
||||
t.Run("ordinary relation", func(t *testing.T) {
|
||||
assert.NoError(t, rest.GetRestrictions(givenRelation(bundle.RelationKeyImdbRating)).Object.Check(
|
||||
model.Restrictions_Delete,
|
||||
model.Restrictions_Relations,
|
||||
model.Restrictions_Details,
|
||||
))
|
||||
})
|
||||
|
||||
t.Run("system relation", func(t *testing.T) {
|
||||
assert.ErrorIs(t, rest.GetRestrictions(givenRelation(bundle.RelationKeyId)).Object.Check(
|
||||
model.Restrictions_Delete,
|
||||
model.Restrictions_Relations,
|
||||
model.Restrictions_Details,
|
||||
), ErrRestricted)
|
||||
})
|
||||
|
||||
assert.ErrorIs(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.RelationKeyName.URL(),
|
||||
tp: model.SmartBlockType_SubObject,
|
||||
layout: model.ObjectType_relation,
|
||||
objectType: bundle.TypeKeyRelation.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Delete,
|
||||
model.Restrictions_Relations,
|
||||
model.Restrictions_Details,
|
||||
), ErrRestricted)
|
||||
|
||||
assert.ErrorIs(t, rest.GetRestrictions(&restrictionHolder{
|
||||
id: bundle.RelationKeyId.BundledURL(),
|
||||
tp: model.SmartBlockType_BundledRelation,
|
||||
layout: model.ObjectType_relation,
|
||||
objectType: bundle.TypeKeyRelation.URL(),
|
||||
tp: model.SmartBlockType_BundledRelation,
|
||||
layout: model.ObjectType_relation,
|
||||
objectTypeID: bundle.TypeKeyRelation.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Duplicate,
|
||||
), ErrRestricted)
|
||||
}
|
||||
|
||||
// TODO Use constructors instead for initializing restrictionHolder structures by hand. See givenObjectType and givenRelation
|
||||
func TestTemplateRestriction(t *testing.T) {
|
||||
rs := newFixture(t)
|
||||
rs.objectStoreMock.EXPECT().HasObjectType(bundle.TypeKeyPage.URL()).Return(false, nil)
|
||||
rs.objectStoreMock.EXPECT().HasObjectType(bundle.TypeKeyContact.URL()).Return(true, nil)
|
||||
|
||||
assert.ErrorIs(t, rs.GetRestrictions(&restrictionHolder{
|
||||
id: "cannot make template from Template smartblock type",
|
||||
tp: model.SmartBlockType_Template,
|
||||
layout: model.ObjectType_basic,
|
||||
objectType: bundle.TypeKeyTemplate.URL(),
|
||||
// id: "cannot make template from Template smartblock type",
|
||||
tp: model.SmartBlockType_Template,
|
||||
layout: model.ObjectType_basic,
|
||||
objectTypeID: bundle.TypeKeyTemplate.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Template,
|
||||
), ErrRestricted)
|
||||
|
||||
assert.ErrorIs(t, rs.GetRestrictions(&restrictionHolder{
|
||||
id: "cannot make template from set or collection layout",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_collection,
|
||||
objectType: bundle.TypeKeyCollection.URL(),
|
||||
// id: "cannot make template from set or collection layout",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_collection,
|
||||
objectTypeID: bundle.TypeKeyCollection.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Template,
|
||||
), ErrRestricted)
|
||||
|
||||
assert.ErrorIs(t, rs.GetRestrictions(&restrictionHolder{
|
||||
id: "cannot make template from space layout",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_space,
|
||||
objectType: bundle.TypeKeySpace.URL(),
|
||||
// id: "cannot make template from space layout",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_space,
|
||||
objectTypeID: bundle.TypeKeySpace.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Template,
|
||||
), ErrRestricted)
|
||||
|
||||
assert.ErrorIs(t, rs.GetRestrictions(&restrictionHolder{
|
||||
id: "cannot make template from object with objectType not added to space",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_basic,
|
||||
objectType: bundle.TypeKeyPage.URL(),
|
||||
// id: "cannot make template from object with objectType not added to space",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_basic,
|
||||
objectTypeID: bundle.TypeKeyPage.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Template,
|
||||
), ErrRestricted)
|
||||
|
||||
assert.NoError(t, rs.GetRestrictions(&restrictionHolder{
|
||||
id: "make template from object with objectType added to space",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_basic,
|
||||
objectType: bundle.TypeKeyContact.URL(),
|
||||
// id: "make template from object with objectType added to space",
|
||||
tp: model.SmartBlockType_Page,
|
||||
layout: model.ObjectType_basic,
|
||||
objectTypeID: bundle.TypeKeyContact.URL(),
|
||||
}).Object.Check(
|
||||
model.Restrictions_Template,
|
||||
))
|
||||
|
|
|
@ -7,35 +7,29 @@ import (
|
|||
)
|
||||
|
||||
type RestrictionHolder interface {
|
||||
Id() string
|
||||
Type() model.SmartBlockType
|
||||
Layout() (model.ObjectTypeLayout, bool)
|
||||
// ObjectType is id of object type
|
||||
ObjectType() string
|
||||
UniqueKey() uniquekey.UniqueKey
|
||||
}
|
||||
|
||||
type restrictionHolder struct {
|
||||
id string
|
||||
tp model.SmartBlockType
|
||||
uk uniquekey.UniqueKey
|
||||
layout model.ObjectTypeLayout
|
||||
objectType string
|
||||
tp model.SmartBlockType
|
||||
uk uniquekey.UniqueKey
|
||||
layout model.ObjectTypeLayout
|
||||
objectTypeID string
|
||||
}
|
||||
|
||||
func newRestrictionHolder(id string, sbType smartblock.SmartBlockType, layout model.ObjectTypeLayout, uk uniquekey.UniqueKey, ot string) RestrictionHolder {
|
||||
func newRestrictionHolder(sbType smartblock.SmartBlockType, layout model.ObjectTypeLayout, uk uniquekey.UniqueKey, objectTypeID string) RestrictionHolder {
|
||||
return &restrictionHolder{
|
||||
id: id,
|
||||
tp: sbType.ToProto(),
|
||||
layout: layout,
|
||||
uk: uk,
|
||||
objectType: ot,
|
||||
tp: sbType.ToProto(),
|
||||
layout: layout,
|
||||
uk: uk,
|
||||
objectTypeID: objectTypeID,
|
||||
}
|
||||
}
|
||||
|
||||
func (rh *restrictionHolder) Id() string {
|
||||
return rh.id
|
||||
}
|
||||
|
||||
func (rh *restrictionHolder) Type() model.SmartBlockType {
|
||||
return rh.tp
|
||||
}
|
||||
|
@ -45,7 +39,7 @@ func (rh *restrictionHolder) Layout() (model.ObjectTypeLayout, bool) {
|
|||
}
|
||||
|
||||
func (rh *restrictionHolder) ObjectType() string {
|
||||
return rh.objectType
|
||||
return rh.objectTypeID
|
||||
}
|
||||
|
||||
func (s *restrictionHolder) UniqueKey() uniquekey.UniqueKey {
|
||||
|
|
|
@ -93,7 +93,7 @@ func (s *service) getRestrictionsById(spaceID string, id string) (r Restrictions
|
|||
log.Errorf("failed to parse unique key %s: %v", u, err)
|
||||
}
|
||||
}
|
||||
obj := newRestrictionHolder(id, sbType, layout, uk, ot)
|
||||
obj := newRestrictionHolder(sbType, layout, uk, ot)
|
||||
if err != nil {
|
||||
return Restrictions{}, err
|
||||
}
|
||||
|
|
|
@ -41,6 +41,14 @@ func New(sbt model.SmartBlockType, key string) (UniqueKey, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func MustUniqueKey(sbt model.SmartBlockType, key string) UniqueKey {
|
||||
uk, err := New(sbt, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uk
|
||||
}
|
||||
|
||||
func UnmarshalFromString(raw string) (UniqueKey, error) {
|
||||
parts := strings.Split(raw, separator)
|
||||
if raw == "" || len(parts) > 2 {
|
||||
|
|
|
@ -13,7 +13,9 @@ import (
|
|||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/objectlink"
|
||||
"github.com/anyproto/anytype-heart/core/converter"
|
||||
"github.com/anyproto/anytype-heart/core/relation"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
|
@ -21,23 +23,6 @@ import (
|
|||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func NewMultiConverter(format graphviz.Format, sbtProvider typeprovider.SmartBlockTypeProvider) converter.MultiConverter {
|
||||
g := graphviz.New()
|
||||
graph, err := g.Graph()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &dot{
|
||||
graph: graph,
|
||||
graphviz: g,
|
||||
exportFormat: format,
|
||||
linksByNode: map[string][]linkInfo{},
|
||||
nodes: map[string]*cgraph.Node{},
|
||||
sbtProvider: sbtProvider,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
ExportFormatDOT = graphviz.XDOT
|
||||
ExportFormatSVG = graphviz.SVG
|
||||
|
@ -58,15 +43,38 @@ type linkInfo struct {
|
|||
}
|
||||
|
||||
type dot struct {
|
||||
graph *cgraph.Graph
|
||||
graphviz *graphviz.Graphviz
|
||||
knownDocs map[string]*types.Struct
|
||||
fileHashes []string
|
||||
imageHashes []string
|
||||
exportFormat graphviz.Format
|
||||
nodes map[string]*cgraph.Node
|
||||
linksByNode map[string][]linkInfo
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider
|
||||
graph *cgraph.Graph
|
||||
graphviz *graphviz.Graphviz
|
||||
knownDocs map[string]*types.Struct
|
||||
fileHashes []string
|
||||
imageHashes []string
|
||||
exportFormat graphviz.Format
|
||||
nodes map[string]*cgraph.Node
|
||||
linksByNode map[string][]linkInfo
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider
|
||||
relationService relation.Service
|
||||
}
|
||||
|
||||
func NewMultiConverter(
|
||||
format graphviz.Format,
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider,
|
||||
relationService relation.Service,
|
||||
) converter.MultiConverter {
|
||||
g := graphviz.New()
|
||||
graph, err := g.Graph()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &dot{
|
||||
graph: graph,
|
||||
graphviz: g,
|
||||
exportFormat: format,
|
||||
linksByNode: map[string][]linkInfo{},
|
||||
nodes: map[string]*cgraph.Node{},
|
||||
sbtProvider: sbtProvider,
|
||||
relationService: relationService,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dot) SetKnownDocs(docs map[string]*types.Struct) converter.Converter {
|
||||
|
@ -112,7 +120,8 @@ func (d *dot) Add(st *state.State) error {
|
|||
|
||||
// TODO: add relations
|
||||
|
||||
for _, depID := range st.DepSmartIds(true, true, false, false, false) {
|
||||
dependentObjectIDs := objectlink.DependentObjectIDs(st, d.relationService, true, true, false, false, false)
|
||||
for _, depID := range dependentObjectIDs {
|
||||
t, err := d.sbtProvider.Type(st.SpaceID(), depID)
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
|
@ -8,11 +8,12 @@ import (
|
|||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/converter"
|
||||
"github.com/anyproto/anytype-heart/core/relation"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/typeprovider"
|
||||
)
|
||||
|
||||
func NewMultiConverter(format int, _ typeprovider.SmartBlockTypeProvider) converter.MultiConverter {
|
||||
func NewMultiConverter(format int, _ typeprovider.SmartBlockTypeProvider, _ relation.Service) converter.MultiConverter {
|
||||
return &dot{}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@ import (
|
|||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/objectlink"
|
||||
"github.com/anyproto/anytype-heart/core/converter"
|
||||
"github.com/anyproto/anytype-heart/core/relation"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
|
@ -14,14 +16,6 @@ import (
|
|||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func NewMultiConverter(sbtProvider typeprovider.SmartBlockTypeProvider) converter.MultiConverter {
|
||||
return &graphjson{
|
||||
linksByNode: map[string][]*Edge{},
|
||||
nodes: map[string]*Node{},
|
||||
sbtProvider: sbtProvider,
|
||||
}
|
||||
}
|
||||
|
||||
type edgeType int
|
||||
|
||||
const (
|
||||
|
@ -57,7 +51,21 @@ type graphjson struct {
|
|||
imageHashes []string
|
||||
nodes map[string]*Node
|
||||
linksByNode map[string][]*Edge
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider
|
||||
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider
|
||||
relationService relation.Service
|
||||
}
|
||||
|
||||
func NewMultiConverter(
|
||||
sbtProvider typeprovider.SmartBlockTypeProvider,
|
||||
relationService relation.Service,
|
||||
) converter.MultiConverter {
|
||||
return &graphjson{
|
||||
linksByNode: map[string][]*Edge{},
|
||||
nodes: map[string]*Node{},
|
||||
sbtProvider: sbtProvider,
|
||||
relationService: relationService,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *graphjson) SetKnownDocs(docs map[string]*types.Struct) converter.Converter {
|
||||
|
@ -87,7 +95,8 @@ func (g *graphjson) Add(st *state.State) error {
|
|||
g.nodes[st.RootId()] = &n
|
||||
// TODO: add relations
|
||||
|
||||
for _, depID := range st.DepSmartIds(true, true, false, false, false) {
|
||||
dependentObjectIDs := objectlink.DependentObjectIDs(st, g.relationService, true, true, false, false, false)
|
||||
for _, depID := range dependentObjectIDs {
|
||||
t, err := g.sbtProvider.Type(st.SpaceID(), depID)
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
smartblock2 "github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
history2 "github.com/anyproto/anytype-heart/core/block/history"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/objectlink"
|
||||
"github.com/anyproto/anytype-heart/core/block/source"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/core/relation"
|
||||
|
@ -72,8 +73,9 @@ func (h *history) Show(id domain.FullID, versionID string) (bs *model.ObjectView
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
dependentObjectIDs := objectlink.DependentObjectIDs(s, h.relationService, true, true, false, true, false)
|
||||
// nolint:errcheck
|
||||
metaD, _ := h.objectStore.QueryByID(s.DepSmartIds(true, true, false, true, false))
|
||||
metaD, _ := h.objectStore.QueryByID(dependentObjectIDs)
|
||||
details := make([]*model.ObjectViewDetailsSet, 0, len(metaD))
|
||||
var uniqueObjTypes []string
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/typeprovider/mock_typeprovider"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func Test_GrouperTags(t *testing.T) {
|
||||
|
@ -29,10 +30,10 @@ func Test_GrouperTags(t *testing.T) {
|
|||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
a := new(app.App)
|
||||
defer a.Close(context.Background())
|
||||
tp := mock_typeprovider.NewMockSmartBlockTypeProvider(t)
|
||||
tp.EXPECT().Name().Return("typeprovider")
|
||||
tp.EXPECT().Init(a).Return(nil)
|
||||
|
||||
ds := objectstore.New()
|
||||
kanbanSrv := New()
|
||||
err := a.Register(&config.DefaultConfig).
|
||||
|
@ -45,13 +46,16 @@ func Test_GrouperTags(t *testing.T) {
|
|||
Start(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
tp.EXPECT().Type("", "rel-tag").Return(smartblock.SmartBlockTypeSubObject, nil)
|
||||
// Just catch-all
|
||||
tp.EXPECT().Type("", mock.Anything).Return(smartblock.SmartBlockTypePage, nil)
|
||||
|
||||
require.NoError(t, ds.UpdateObjectDetails("rel-tag", &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
"id": pbtypes.String("rel-tag"),
|
||||
"relationKey": pbtypes.String("tag"),
|
||||
"relationFormat": pbtypes.Int64(int64(model.RelationFormat_tag)),
|
||||
"type": pbtypes.String(bundle.TypeKeyRelation.URL()),
|
||||
"layout": pbtypes.Int64(int64(model.ObjectType_relation)),
|
||||
},
|
||||
}))
|
||||
|
||||
|
@ -64,6 +68,7 @@ func Test_GrouperTags(t *testing.T) {
|
|||
"id": pbtypes.String(idTag1),
|
||||
"relationKey": pbtypes.String("tag"),
|
||||
"type": pbtypes.String(bundle.TypeKeyRelationOption.URL()),
|
||||
"layout": pbtypes.Int64(int64(model.ObjectType_relationOption)),
|
||||
},
|
||||
}))
|
||||
|
||||
|
@ -72,6 +77,7 @@ func Test_GrouperTags(t *testing.T) {
|
|||
"id": pbtypes.String(idTag2),
|
||||
"relationKey": pbtypes.String("tag"),
|
||||
"type": pbtypes.String(bundle.TypeKeyRelationOption.URL()),
|
||||
"layout": pbtypes.Int64(int64(model.ObjectType_relationOption)),
|
||||
},
|
||||
}))
|
||||
require.NoError(t, ds.UpdateObjectDetails(idTag3, &types.Struct{
|
||||
|
@ -79,6 +85,7 @@ func Test_GrouperTags(t *testing.T) {
|
|||
"id": pbtypes.String(idTag3),
|
||||
"relationKey": pbtypes.String("tag"),
|
||||
"type": pbtypes.String(bundle.TypeKeyRelationOption.URL()),
|
||||
"layout": pbtypes.Int64(int64(model.ObjectType_relationOption)),
|
||||
},
|
||||
}))
|
||||
|
||||
|
|
|
@ -97,8 +97,8 @@ func (s *service) GetRelationIdByKey(ctx context.Context, spaceId string, key bu
|
|||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
s.objectStore = a.MustComponent(objectstore.CName).(objectstore.ObjectStore)
|
||||
s.core = a.MustComponent(core.CName).(core.Service)
|
||||
s.objectStore = app.MustComponent[objectstore.ObjectStore](a)
|
||||
s.core = app.MustComponent[core.Service](a)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -255,7 +255,7 @@ func (s *dsObjectStore) GetAggregatedOptions(relationKey string) (options []*mod
|
|||
{
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
Value: pbtypes.Float64(float64(model.ObjectType_relationOption)),
|
||||
Value: pbtypes.Int64(int64(model.ObjectType_relationOption)),
|
||||
// todo: revert check by objectType
|
||||
},
|
||||
},
|
||||
|
@ -322,6 +322,11 @@ func (s *dsObjectStore) GetRelationByKey(key string) (*model.Relation, error) {
|
|||
RelationKey: bundle.RelationKeyRelationKey.String(),
|
||||
Value: pbtypes.String(key),
|
||||
},
|
||||
{
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
Value: pbtypes.Int64(int64(model.ObjectType_relation)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -712,8 +717,7 @@ func (s *dsObjectStore) GetObjectType(id string) (*model.ObjectType, error) {
|
|||
return nil, fmt.Errorf("object %s is not an object type", id)
|
||||
}
|
||||
|
||||
ot := s.extractObjectTypeFromDetails(details.Details, id)
|
||||
ot.Key = uk.InternalKey()
|
||||
ot := s.extractObjectTypeFromDetails(details.Details, id, uk.InternalKey())
|
||||
return ot, nil
|
||||
}
|
||||
|
||||
|
@ -749,40 +753,33 @@ func (s *dsObjectStore) GetObjectByUniqueKey(spaceId string, uniqueKey string) (
|
|||
return &model.ObjectDetails{Details: records[0].Details}, nil
|
||||
}
|
||||
|
||||
func (s *dsObjectStore) extractObjectTypeFromDetails(details *types.Struct, url string) *model.ObjectType {
|
||||
objectType := &model.ObjectType{}
|
||||
// s.fillObjectTypeWithRecommendedRelations(details, objectType)
|
||||
objectType.Name = pbtypes.GetString(details, bundle.RelationKeyName.String())
|
||||
objectType.Layout = model.ObjectTypeLayout(int(pbtypes.GetFloat64(details, bundle.RelationKeyRecommendedLayout.String())))
|
||||
objectType.IconEmoji = pbtypes.GetString(details, bundle.RelationKeyIconEmoji.String())
|
||||
objectType.Url = url
|
||||
objectType.IsArchived = pbtypes.GetBool(details, bundle.RelationKeyIsArchived.String())
|
||||
|
||||
// we use Page for all custom object types
|
||||
objectType.Types = []model.SmartBlockType{model.SmartBlockType_Page}
|
||||
return objectType
|
||||
func (s *dsObjectStore) extractObjectTypeFromDetails(details *types.Struct, url string, objectTypeKey string) *model.ObjectType {
|
||||
return &model.ObjectType{
|
||||
Url: url,
|
||||
Key: objectTypeKey,
|
||||
Name: pbtypes.GetString(details, bundle.RelationKeyName.String()),
|
||||
Layout: model.ObjectTypeLayout(int(pbtypes.GetInt64(details, bundle.RelationKeyRecommendedLayout.String()))),
|
||||
IconEmoji: pbtypes.GetString(details, bundle.RelationKeyIconEmoji.String()),
|
||||
IsArchived: pbtypes.GetBool(details, bundle.RelationKeyIsArchived.String()),
|
||||
// we use Page for all custom object types
|
||||
Types: []model.SmartBlockType{model.SmartBlockType_Page},
|
||||
RelationLinks: s.getRelationLinksForRecommendedRelations(details),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *dsObjectStore) fillObjectTypeWithRecommendedRelations(details *types.Struct, objectType *model.ObjectType) {
|
||||
// relationKeys := objectInfos[0].RelationKeys
|
||||
for _, relationID := range pbtypes.GetStringList(details, bundle.RelationKeyRecommendedRelations.String()) {
|
||||
// todo: fix or remove
|
||||
relationKey, err := pbtypes.BundledRelationIdToKey(relationID)
|
||||
if err == nil {
|
||||
//nolint:govet
|
||||
relation, err := s.GetRelationByKey(relationKey)
|
||||
if err == nil {
|
||||
objectType.RelationLinks = append(
|
||||
objectType.RelationLinks,
|
||||
(&relationutils.Relation{Relation: relation}).RelationLink(),
|
||||
)
|
||||
} else {
|
||||
log.Errorf("GetObjectType failed to get relation key from id: %s (%s)", err.Error(), relationID)
|
||||
}
|
||||
func (s *dsObjectStore) getRelationLinksForRecommendedRelations(details *types.Struct) []*model.RelationLink {
|
||||
recommendedRelationIDs := pbtypes.GetStringList(details, bundle.RelationKeyRecommendedRelations.String())
|
||||
relationLinks := make([]*model.RelationLink, 0, len(recommendedRelationIDs))
|
||||
for _, relationID := range recommendedRelationIDs {
|
||||
relation, err := s.GetRelationByID(relationID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get relation %s: %s", relationID, err)
|
||||
} else {
|
||||
log.Errorf("GetObjectType failed to get relation key from id: %s (%s)", err.Error(), relationID)
|
||||
relationModel := &relationutils.Relation{Relation: relation}
|
||||
relationLinks = append(relationLinks, relationModel.RelationLink())
|
||||
}
|
||||
}
|
||||
return relationLinks
|
||||
}
|
||||
|
||||
func (s *dsObjectStore) GetObjectTypes(ids []string) (ots []*model.ObjectType, err error) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/uniquekey"
|
||||
"github.com/anyproto/anytype-heart/core/relation/relationutils"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
|
@ -200,17 +201,21 @@ func TestGetObjectType(t *testing.T) {
|
|||
s := NewStoreFixture(t)
|
||||
|
||||
id := "id1"
|
||||
relationID := "derivedFrom(assignee)"
|
||||
uniqueKey, err := uniquekey.New(model.SmartBlockType_STType, "note")
|
||||
require.NoError(t, err)
|
||||
obj := TestObject{
|
||||
bundle.RelationKeyId: pbtypes.String(id),
|
||||
bundle.RelationKeyType: pbtypes.String(bundle.TypeKeyObjectType.URL()),
|
||||
bundle.RelationKeyName: pbtypes.String("my note"),
|
||||
bundle.RelationKeyRecommendedRelations: pbtypes.StringList([]string{bundle.RelationKeyAssignee.URL()}),
|
||||
bundle.RelationKeyRecommendedRelations: pbtypes.StringList([]string{relationID}),
|
||||
bundle.RelationKeyRecommendedLayout: pbtypes.Int64(int64(model.ObjectType_note)),
|
||||
bundle.RelationKeyIconEmoji: pbtypes.String("📝"),
|
||||
bundle.RelationKeyIsArchived: pbtypes.Bool(true),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String(uniqueKey.Marshal()),
|
||||
}
|
||||
relObj := TestObject{
|
||||
bundle.RelationKeyId: pbtypes.String("id2"),
|
||||
bundle.RelationKeyId: pbtypes.String(relationID),
|
||||
bundle.RelationKeyRelationKey: pbtypes.String(bundle.RelationKeyAssignee.String()),
|
||||
bundle.RelationKeyType: pbtypes.String(bundle.TypeKeyRelation.URL()),
|
||||
}
|
||||
|
@ -228,6 +233,7 @@ func TestGetObjectType(t *testing.T) {
|
|||
IconEmoji: "📝",
|
||||
IsArchived: true,
|
||||
Types: []model.SmartBlockType{model.SmartBlockType_Page},
|
||||
Key: "note",
|
||||
RelationLinks: []*model.RelationLink{
|
||||
{
|
||||
Key: bundle.RelationKeyAssignee.String(),
|
||||
|
@ -286,6 +292,7 @@ func makeRelationOptionObject(id, name, color, relationKey string) TestObject {
|
|||
bundle.RelationKeyName: pbtypes.String(name),
|
||||
bundle.RelationKeyRelationOptionColor: pbtypes.String(color),
|
||||
bundle.RelationKeyRelationKey: pbtypes.String(relationKey),
|
||||
bundle.RelationKeyLayout: pbtypes.Int64(int64(model.ObjectType_relationOption)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,7 +300,7 @@ func TestGetRelationById(t *testing.T) {
|
|||
t.Run("relation is not found", func(t *testing.T) {
|
||||
s := NewStoreFixture(t)
|
||||
|
||||
_, err := s.GetRelationByID(bundle.RelationKeyTag.URL())
|
||||
_, err := s.GetRelationByID("relationID")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
|
@ -309,13 +316,14 @@ func TestGetRelationById(t *testing.T) {
|
|||
t.Run("relation is found", func(t *testing.T) {
|
||||
s := NewStoreFixture(t)
|
||||
|
||||
rel := &relationutils.Relation{Relation: bundle.MustGetRelation(bundle.RelationKeyName)}
|
||||
rel.Id = bundle.RelationKeyName.URL()
|
||||
relObject := rel.ToStruct()
|
||||
err := s.UpdateObjectDetails(rel.Id, relObject)
|
||||
relation := &relationutils.Relation{Relation: bundle.MustGetRelation(bundle.RelationKeyName)}
|
||||
relationID := "derivedFrom(name)"
|
||||
relation.Id = relationID
|
||||
relObject := relation.ToStruct()
|
||||
err := s.UpdateObjectDetails(relation.Id, relObject)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := s.GetRelationByID(bundle.RelationKeyName.URL())
|
||||
got, err := s.GetRelationByID(relationID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, relationutils.RelationFromStruct(relObject).Relation, got)
|
||||
})
|
||||
|
|
|
@ -48,8 +48,8 @@ type builtinTemplate struct {
|
|||
}
|
||||
|
||||
func (b *builtinTemplate) Init(a *app.App) (err error) {
|
||||
b.source = a.MustComponent(source.CName).(source.Service)
|
||||
b.relationService = a.MustComponent(relation2.CName).(relation2.Service)
|
||||
b.source = app.MustComponent[source.Service](a)
|
||||
b.relationService = app.MustComponent[relation2.Service](a)
|
||||
|
||||
b.makeGenHash(4)
|
||||
return
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/anyproto/anytype-heart/core/anytype/config"
|
||||
"github.com/anyproto/anytype-heart/core/block/source"
|
||||
"github.com/anyproto/anytype-heart/core/relation/mock_relation"
|
||||
"github.com/anyproto/anytype-heart/util/testMock/mockSource"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
@ -22,13 +23,19 @@ func Test_registerBuiltin(t *testing.T) {
|
|||
s.EXPECT().NewStaticSource(gomock.Any(), gomock.Any(), gomock.Any(), nil).AnyTimes()
|
||||
s.EXPECT().RegisterStaticSource(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
|
||||
relationService := mock_relation.NewMockService(t)
|
||||
relationService.EXPECT().Name().Return("relation")
|
||||
|
||||
builtInTemplates := New()
|
||||
a := new(app.App)
|
||||
a.Register(s).Register(builtInTemplates).Register(config.New())
|
||||
a.Register(s).
|
||||
Register(builtInTemplates).
|
||||
Register(config.New()).
|
||||
Register(relationService)
|
||||
err := builtInTemplates.Init(a)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
err = builtInTemplates.Run(context.Background())
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer a.Close(context.Background())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue