1
0
Fork 0
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:
Sergey 2023-08-17 10:26:07 +05:00
commit 7c501b61cd
No known key found for this signature in database
GPG key ID: 3B6BEF79160221C6
32 changed files with 860 additions and 707 deletions

View file

@ -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)

View file

@ -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{

View file

@ -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())
})

View file

@ -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()

View file

@ -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,

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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()

View file

@ -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"

View file

@ -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 {

View file

@ -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()},

View file

@ -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)
}
}

View file

@ -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) {

View 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
}

View 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)
})
}

View file

@ -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)
})
}

View 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()),
)
}

View file

@ -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,
))

View file

@ -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 {

View file

@ -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
}

View file

@ -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 {

View file

@ -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

View file

@ -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{}
}

View file

@ -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

View file

@ -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

View file

@ -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)),
},
}))

View file

@ -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
}

View file

@ -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) {

View file

@ -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)
})

View file

@ -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

View file

@ -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())
}