get details working (and other cleanup) (#5116)

details wasn't properly listing backed up items. This fixes the details
display, and contains some code clean-up that occurred along the way.
This commit is contained in:
Keepers 2024-02-15 14:32:25 -07:00 committed by GitHub
parent 316e5f0195
commit 0efe25bb71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 291 additions and 164 deletions

View File

@ -213,7 +213,6 @@ func runDetailsTeamsChatsCmd(cmd *cobra.Command) error {
opts := utils.MakeTeamsChatsOpts(cmd)
sel := utils.IncludeTeamsChatsRestoreDataSelectors(ctx, opts)
sel.Configure(selectors.Config{OnlyMatchItemNames: true})
utils.FilterTeamsChatsRestoreInfoSelectors(sel, opts)
ds, err := genericDetailsCommand(cmd, flags.BackupIDFV, sel.Selector)

View File

@ -89,7 +89,10 @@ func IncludeTeamsChatsRestoreDataSelectors(ctx context.Context, opts TeamsChatsO
users = selectors.Any()
}
return selectors.NewTeamsChatsRestore(users)
sel := selectors.NewTeamsChatsRestore(users)
sel.Include(sel.Chats(selectors.Any()))
return sel
}
// FilterTeamsChatsRestoreInfoSelectors builds the common info-selector filters.

View File

@ -115,11 +115,8 @@ func populateCollection[I chatsItemer](
)
ctx = clues.AddLabelCounter(ctx, cl.PlainAdder())
cc := api.CallConfig{
CanMakeDeltaQueries: false,
}
items, err := bh.getItemIDs(ctx, cc)
items, err := bh.getItemIDs(ctx)
if err != nil {
errs.AddRecoverable(ctx, clues.Stack(err))
return collection, clues.Stack(errs.Failure()).OrNil()

View File

@ -49,14 +49,6 @@ type mockBackupHandler struct {
doNotInclude bool
}
//lint:ignore U1000 false linter issue due to generics
func (bh mockBackupHandler) augmentItemInfo(
*details.TeamsChatsInfo,
models.Chatable,
) {
// no-op
}
func (bh mockBackupHandler) container() container[models.Chatable] {
return chatContainer()
}
@ -71,7 +63,6 @@ func (bh mockBackupHandler) getContainer(
func (bh mockBackupHandler) getItemIDs(
_ context.Context,
_ api.CallConfig,
) ([]models.Chatable, error) {
return bh.chats, bh.chatsErr
}

View File

@ -46,8 +46,11 @@ func (bh usersChatsBackupHandler) getContainer(
//lint:ignore U1000 required for interface compliance
func (bh usersChatsBackupHandler) getItemIDs(
ctx context.Context,
cc api.CallConfig,
) ([]models.Chatable, error) {
cc := api.CallConfig{
Expand: []string{"lastMessagePreview"},
}
return bh.ac.GetChats(
ctx,
bh.protectedResourceID,
@ -89,26 +92,21 @@ func (bh usersChatsBackupHandler) getItem(
chatID := ptr.Val(chat.GetId())
cc := api.CallConfig{
Expand: []string{"lastMessagePreview"},
}
msgs, err := bh.ac.GetChatMessages(ctx, chatID, cc)
msgs, err := bh.ac.GetChatMessages(ctx, chatID, api.CallConfig{})
if err != nil {
return nil, nil, clues.Stack(err)
}
chat.SetMessages(msgs)
return chat, api.TeamsChatInfo(chat), nil
}
members, err := bh.ac.GetChatMembers(ctx, chatID, api.CallConfig{})
if err != nil {
return nil, nil, clues.Stack(err)
}
//lint:ignore U1000 false linter issue due to generics
func (bh usersChatsBackupHandler) augmentItemInfo(
dgi *details.TeamsChatsInfo,
c models.Chatable,
) {
// no-op
chat.SetMembers(members)
return chat, api.TeamsChatInfo(chat), nil
}
func chatContainer() container[models.Chatable] {

View File

@ -66,7 +66,7 @@ func updateStatus(
// or notMoved (if they match).
func NewCollection[I chatsItemer](
baseCol data.BaseCollection,
getAndAugment getItemAndAugmentInfoer[I],
getter getItemer[I],
protectedResource string,
items []I,
contains container[I],
@ -76,7 +76,7 @@ func NewCollection[I chatsItemer](
BaseCollection: baseCol,
items: items,
contains: contains,
getAndAugment: getAndAugment,
getter: getter,
statusUpdater: statusUpdater,
stream: make(chan data.Item, collectionChannelBufferSize),
protectedResource: protectedResource,
@ -96,7 +96,7 @@ type lazyFetchCollection[I chatsItemer] struct {
items []I
getAndAugment getItemAndAugmentInfoer[I]
getter getItemer[I]
statusUpdater support.StatusUpdater
}
@ -167,13 +167,13 @@ func (col *lazyFetchCollection[I]) streamItems(ctx context.Context, errs *fault.
col.stream <- data.NewLazyItemWithInfo(
ictx,
&lazyItemGetter[I]{
modTime: modTime,
getAndAugment: col.getAndAugment,
resourceID: col.protectedResource,
item: item,
containerIDs: col.FullPath().Folders(),
contains: col.contains,
parentPath: col.LocationPath().String(),
modTime: modTime,
getter: col.getter,
resourceID: col.protectedResource,
item: item,
containerIDs: col.FullPath().Folders(),
contains: col.contains,
parentPath: col.LocationPath().String(),
},
itemID,
modTime,
@ -192,13 +192,13 @@ func (col *lazyFetchCollection[I]) streamItems(ctx context.Context, errs *fault.
}
type lazyItemGetter[I chatsItemer] struct {
getAndAugment getItemAndAugmentInfoer[I]
resourceID string
item I
parentPath string
containerIDs path.Elements
modTime time.Time
contains container[I]
getter getItemer[I]
resourceID string
item I
parentPath string
containerIDs path.Elements
modTime time.Time
contains container[I]
}
func (lig *lazyItemGetter[I]) GetData(
@ -208,7 +208,7 @@ func (lig *lazyItemGetter[I]) GetData(
writer := kjson.NewJsonSerializationWriter()
defer writer.Close()
item, info, err := lig.getAndAugment.getItem(
item, info, err := lig.getter.getItem(
ctx,
lig.resourceID,
lig.item)
@ -229,8 +229,6 @@ func (lig *lazyItemGetter[I]) GetData(
return nil, nil, false, err
}
lig.getAndAugment.augmentItemInfo(info, lig.contains.container)
if err := writer.WriteObjectValue("", item); err != nil {
err = clues.WrapWC(ctx, err, "writing item to serializer").Label(fault.LabelForceNoBackupCreation)
errs.AddRecoverable(ctx, err)

View File

@ -163,11 +163,6 @@ func (m getAndAugmentChat) getItem(
return chat, &details.TeamsChatsInfo{}, m.err
}
//lint:ignore U1000 false linter issue due to generics
func (getAndAugmentChat) augmentItemInfo(*details.TeamsChatsInfo, models.Chatable) {
// no-op
}
func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
var (
t = suite.T()
@ -225,7 +220,7 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
count.New()),
items: test.items,
contains: container[models.Chatable]{},
getAndAugment: getterAugmenter,
getter: getterAugmenter,
stream: make(chan data.Item),
statusUpdater: statusUpdater,
}
@ -285,11 +280,11 @@ func (suite *CollectionUnitSuite) TestLazyItem_GetDataErrors() {
li := data.NewLazyItemWithInfo(
ctx,
&lazyItemGetter[models.Chatable]{
resourceID: "resourceID",
item: chat,
getAndAugment: &m,
modTime: now,
parentPath: parentPath,
resourceID: "resourceID",
item: chat,
getter: &m,
modTime: now,
parentPath: parentPath,
},
ptr.Val(chat.GetId()),
now,
@ -329,11 +324,11 @@ func (suite *CollectionUnitSuite) TestLazyItem_ReturnsEmptyReaderOnDeletedInFlig
li := data.NewLazyItemWithInfo(
ctx,
&lazyItemGetter[models.Chatable]{
resourceID: "resourceID",
item: chat,
getAndAugment: &m,
modTime: now,
parentPath: parentPath,
resourceID: "resourceID",
item: chat,
getter: &m,
modTime: now,
parentPath: parentPath,
},
ptr.Val(chat.GetId()),
now,
@ -368,11 +363,11 @@ func (suite *CollectionUnitSuite) TestLazyItem() {
li := data.NewLazyItemWithInfo(
ctx,
&lazyItemGetter[models.Chatable]{
resourceID: "resourceID",
item: chat,
getAndAugment: &m,
modTime: now,
parentPath: parentPath,
resourceID: "resourceID",
item: chat,
getter: &m,
modTime: now,
parentPath: parentPath,
},
ptr.Val(chat.GetId()),
now,

View File

@ -22,7 +22,7 @@ type chatsItemer interface {
type backupHandler[I chatsItemer] interface {
getContainerer[I]
getItemAndAugmentInfoer[I]
getItemer[I]
getItemer[I]
getItemIDser[I]
includeItemer[I]
@ -39,22 +39,10 @@ type getContainerer[I chatsItemer] interface {
) (container[I], error)
}
type getItemAndAugmentInfoer[I chatsItemer] interface {
getItemer[I]
augmentItemInfoer[I]
}
type augmentItemInfoer[I chatsItemer] interface {
// augmentItemInfo completes the teamChatsInfo population with any data
// owned by the container and not accessible to the item.
augmentItemInfo(*details.TeamsChatsInfo, I)
}
// gets all item IDs in the container
type getItemIDser[I chatsItemer] interface {
getItemIDs(
ctx context.Context,
cc api.CallConfig,
) ([]I, error)
}

View File

@ -103,6 +103,12 @@ func (de Entry) ToLocationIDer(backupVersion int) (LocationIDer, error) {
}
baseLoc = path.Builder{}.Append(p.Root).Append(p.Folders...)
case TeamsChat:
baseLoc = &path.Builder{}
default:
return nil, clues.New("undentified item type").With("item_type", de.ItemInfo.infoType())
}
if baseLoc == nil {
@ -141,26 +147,23 @@ func (de Entry) MinimumPrintable() any {
// Headers returns the human-readable names of properties in a DetailsEntry
// for printing out to a terminal in a columnar display.
func (de Entry) Headers(skipID bool) []string {
hs := []string{}
var hs []string
if de.ItemInfo.Folder != nil {
switch {
case de.ItemInfo.Folder != nil:
hs = de.ItemInfo.Folder.Headers()
}
if de.ItemInfo.Exchange != nil {
case de.ItemInfo.Exchange != nil:
hs = de.ItemInfo.Exchange.Headers()
}
if de.ItemInfo.SharePoint != nil {
case de.ItemInfo.SharePoint != nil:
hs = de.ItemInfo.SharePoint.Headers()
}
if de.ItemInfo.OneDrive != nil {
case de.ItemInfo.OneDrive != nil:
hs = de.ItemInfo.OneDrive.Headers()
}
if de.ItemInfo.Groups != nil {
case de.ItemInfo.Groups != nil:
hs = de.ItemInfo.Groups.Headers()
case de.ItemInfo.TeamsChats != nil:
hs = de.ItemInfo.TeamsChats.Headers()
default:
hs = []string{"ERROR - Service not recognized"}
}
if skipID {
@ -172,26 +175,23 @@ func (de Entry) Headers(skipID bool) []string {
// Values returns the values matching the Headers list.
func (de Entry) Values(skipID bool) []string {
vs := []string{}
var vs []string
if de.ItemInfo.Folder != nil {
switch {
case de.ItemInfo.Folder != nil:
vs = de.ItemInfo.Folder.Values()
}
if de.ItemInfo.Exchange != nil {
case de.ItemInfo.Exchange != nil:
vs = de.ItemInfo.Exchange.Values()
}
if de.ItemInfo.SharePoint != nil {
case de.ItemInfo.SharePoint != nil:
vs = de.ItemInfo.SharePoint.Values()
}
if de.ItemInfo.OneDrive != nil {
case de.ItemInfo.OneDrive != nil:
vs = de.ItemInfo.OneDrive.Values()
}
if de.ItemInfo.Groups != nil {
case de.ItemInfo.Groups != nil:
vs = de.ItemInfo.Groups.Values()
case de.ItemInfo.TeamsChats != nil:
vs = de.ItemInfo.TeamsChats.Values()
default:
vs = []string{"ERROR - Service not recognized"}
}
if skipID {

View File

@ -91,19 +91,14 @@ func (i ItemInfo) infoType() ItemType {
switch {
case i.Folder != nil:
return i.Folder.ItemType
case i.Exchange != nil:
return i.Exchange.ItemType
case i.SharePoint != nil:
return i.SharePoint.ItemType
case i.OneDrive != nil:
return i.OneDrive.ItemType
case i.Groups != nil:
return i.Groups.ItemType
case i.TeamsChats != nil:
return i.TeamsChats.ItemType
}
@ -115,19 +110,14 @@ func (i ItemInfo) size() int64 {
switch {
case i.Exchange != nil:
return i.Exchange.Size
case i.OneDrive != nil:
return i.OneDrive.Size
case i.SharePoint != nil:
return i.SharePoint.Size
case i.Groups != nil:
return i.Groups.Size
case i.Folder != nil:
return i.Folder.Size
case i.TeamsChats != nil:
return int64(i.TeamsChats.Chat.MessageCount)
}
@ -139,19 +129,14 @@ func (i ItemInfo) Modified() time.Time {
switch {
case i.Exchange != nil:
return i.Exchange.Modified
case i.OneDrive != nil:
return i.OneDrive.Modified
case i.SharePoint != nil:
return i.SharePoint.Modified
case i.Groups != nil:
return i.Groups.Modified
case i.Folder != nil:
return i.Folder.Modified
case i.TeamsChats != nil:
return i.TeamsChats.Modified
}
@ -163,19 +148,14 @@ func (i ItemInfo) uniqueLocation(baseLoc *path.Builder) (*uniqueLoc, error) {
switch {
case i.Exchange != nil:
return i.Exchange.uniqueLocation(baseLoc)
case i.OneDrive != nil:
return i.OneDrive.uniqueLocation(baseLoc)
case i.SharePoint != nil:
return i.SharePoint.uniqueLocation(baseLoc)
case i.Groups != nil:
return i.Groups.uniqueLocation(baseLoc)
case i.TeamsChats != nil:
return i.TeamsChats.uniqueLocation(baseLoc)
default:
return nil, clues.New("unsupported type")
}
@ -185,19 +165,14 @@ func (i ItemInfo) updateFolder(f *FolderInfo) error {
switch {
case i.Exchange != nil:
return i.Exchange.updateFolder(f)
case i.OneDrive != nil:
return i.OneDrive.updateFolder(f)
case i.SharePoint != nil:
return i.SharePoint.updateFolder(f)
case i.Groups != nil:
return i.Groups.updateFolder(f)
case i.TeamsChats != nil:
return i.TeamsChats.updateFolder(f)
default:
return clues.New("unsupported type")
}

View File

@ -44,7 +44,7 @@ type ChatInfo struct {
LastMessagePreview string `json:"preview,omitempty"`
Members []string `json:"members,omitempty"`
MessageCount int `json:"messageCount,omitempty"`
Name string `json:"name,omitempty"`
Topic string `json:"topic,omitempty"`
}
// Headers returns the human-readable names of properties in a ChatsInfo
@ -52,7 +52,7 @@ type ChatInfo struct {
func (i TeamsChatsInfo) Headers() []string {
switch i.ItemType {
case TeamsChat:
return []string{"Name", "Last message", "Last message at", "Message count", "Created", "Members"}
return []string{"Topic", "Last message", "Last message at", "Message count", "Created", "Members"}
}
return []string{}
@ -75,7 +75,7 @@ func (i TeamsChatsInfo) Values() []string {
}
return []string{
i.Chat.Name,
i.Chat.Topic,
i.Chat.LastMessagePreview,
dttm.FormatToTabularDisplay(i.Chat.LastMessageAt),
strconv.Itoa(i.Chat.MessageCount),

View File

@ -42,7 +42,7 @@ func (suite *ChatsUnitSuite) TestChatsPrintable() {
LastMessagePreview: "last message preview",
Members: []string{"foo@bar.baz", "fnords@smarf.zoomba"},
MessageCount: 42,
Name: "chat name",
Topic: "chat name",
},
},
expectHs: []string{"Name", "Last message", "Last message at", "Message count", "Created", "Members"},

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/backup/identity"
"github.com/alcionai/corso/src/pkg/dttm"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
@ -254,21 +255,81 @@ func (sr *TeamsChatsRestore) ChatMember(memberID string) []TeamsChatsScope {
}
}
// ChatName produces one or more teamsChats chat name info scopes.
// ChatTopic produces one or more teamsChats chat name info scopes.
// Matches any chat whose name contains the provided string.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *TeamsChatsRestore) ChatName(memberID string) []TeamsChatsScope {
func (sr *TeamsChatsRestore) ChatTopic(topic string) []TeamsChatsScope {
return []TeamsChatsScope{
makeInfoScope[TeamsChatsScope](
TeamsChatsChat,
TeamsChatsInfoChatName,
[]string{memberID},
TeamsChatsInfoChatTopic,
[]string{topic},
filters.In),
}
}
// ChatCreatedBefore produces one or more teamsChats chat name info scopes.
// Matches any chat whose creation datetime is before the given datetime.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *TeamsChatsRestore) ChatCreatedBefore(datetime string) []TeamsChatsScope {
return []TeamsChatsScope{
makeInfoScope[TeamsChatsScope](
TeamsChatsChat,
TeamsChatsInfoChatCreatedBefore,
[]string{datetime},
filters.Greater),
}
}
// ChatCreatedBefore produces one or more teamsChats chat name info scopes.
// Matches any chat whose creation datetime is after the given datetime.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *TeamsChatsRestore) ChatCreatedAfter(datetime string) []TeamsChatsScope {
return []TeamsChatsScope{
makeInfoScope[TeamsChatsScope](
TeamsChatsChat,
TeamsChatsInfoChatCreatedAfter,
[]string{datetime},
filters.Less),
}
}
// ChatLastMessageBefore produces one or more teamsChats chat name info scopes.
// Matches any chat whose most recent message (if it has messages) is before the given datetime.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *TeamsChatsRestore) ChatLastMessageBefore(datetime string) []TeamsChatsScope {
return []TeamsChatsScope{
makeInfoScope[TeamsChatsScope](
TeamsChatsChat,
TeamsChatsInfoChatLastMessageBefore,
[]string{datetime},
filters.Greater),
}
}
// ChatCreatedBefore produces one or more teamsChats chat name info scopes.
// Matches any chat whose most recent message (if it has messages) is after the given datetime.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (sr *TeamsChatsRestore) ChatLastMessageAfter(datetime string) []TeamsChatsScope {
return []TeamsChatsScope{
makeInfoScope[TeamsChatsScope](
TeamsChatsChat,
TeamsChatsInfoChatLastMesasgeAfter,
[]string{datetime},
filters.Less),
}
}
// ---------------------------------------------------------------------------
// Categories
// ---------------------------------------------------------------------------
@ -288,8 +349,12 @@ const (
TeamsChatsChat teamsChatsCategory = "TeamsChatsChat"
// data contained within details.ItemInfo
TeamsChatsInfoChatMember teamsChatsCategory = "TeamsChatsInfoChatMember"
TeamsChatsInfoChatName teamsChatsCategory = "TeamsChatsInfoChatName"
TeamsChatsInfoChatCreatedBefore teamsChatsCategory = "TeamsChatsInfoChatCreatedBefore"
TeamsChatsInfoChatCreatedAfter teamsChatsCategory = "TeamsChatsInfoChatCreatedAfter"
TeamsChatsInfoChatLastMessageBefore teamsChatsCategory = "TeamsChatsInfoChatLastMessageBefore"
TeamsChatsInfoChatLastMesasgeAfter teamsChatsCategory = "TeamsChatsInfoChatLastMesasgeAfter"
TeamsChatsInfoChatMember teamsChatsCategory = "TeamsChatsInfoChatMember"
TeamsChatsInfoChatTopic teamsChatsCategory = "TeamsChatsInfoChatName"
)
// teamsChatsLeafProperties describes common metadata of the leaf categories
@ -317,7 +382,9 @@ func (ec teamsChatsCategory) String() string {
// Ex: TeamsChatsUser.leafCat() => TeamsChatsUser
func (ec teamsChatsCategory) leafCat() categorizer {
switch ec {
case TeamsChatsChat, TeamsChatsInfoChatMember, TeamsChatsInfoChatName:
case TeamsChatsChat, TeamsChatsInfoChatMember, TeamsChatsInfoChatTopic,
TeamsChatsInfoChatCreatedBefore, TeamsChatsInfoChatCreatedAfter,
TeamsChatsInfoChatLastMessageBefore, TeamsChatsInfoChatLastMesasgeAfter:
return TeamsChatsChat
}
@ -505,8 +572,16 @@ func (s TeamsChatsScope) matchesInfo(dii details.ItemInfo) bool {
switch infoCat {
case TeamsChatsInfoChatMember:
i = strings.Join(info.Chat.Members, ",")
case TeamsChatsInfoChatName:
i = info.Chat.Name
case TeamsChatsInfoChatTopic:
i = info.Chat.Topic
case TeamsChatsInfoChatCreatedBefore, TeamsChatsInfoChatCreatedAfter:
i = dttm.Format(info.Chat.CreatedAt)
case TeamsChatsInfoChatLastMessageBefore, TeamsChatsInfoChatLastMesasgeAfter:
if info.Chat.MessageCount < 1 {
return false
}
i = dttm.Format(info.Chat.LastMessageAt)
}
return s.Matches(infoCat, i)

View File

@ -12,6 +12,7 @@ import (
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/dttm"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
@ -252,14 +253,16 @@ func (suite *TeamsChatsSelectorSuite) TestTeamsChatsScope_MatchesInfo() {
cs := NewTeamsChatsRestore(Any())
const (
name = "smarf mcfnords"
topic = "smarf mcfnords"
member = "cooks@2many.smarf"
subject = "I have seen the fnords!"
dtype = details.TeamsChat
)
var (
now = time.Now()
future = now.Add(1 * time.Minute)
past = dttm.Format(now.Add(-1 * time.Minute))
future = dttm.Format(now.Add(1 * time.Minute))
)
infoWith := func(itype details.ItemType) details.ItemInfo {
@ -269,11 +272,11 @@ func (suite *TeamsChatsSelectorSuite) TestTeamsChatsScope_MatchesInfo() {
Chat: details.ChatInfo{
CreatedAt: now,
HasExternalMembers: false,
LastMessageAt: future,
LastMessageAt: now,
LastMessagePreview: "preview",
Members: []string{member},
MessageCount: 1,
Name: name,
Topic: topic,
},
},
}
@ -285,12 +288,20 @@ func (suite *TeamsChatsSelectorSuite) TestTeamsChatsScope_MatchesInfo() {
scope []TeamsChatsScope
expect assert.BoolAssertionFunc
}{
{"chat with a different member", details.TeamsChat, cs.ChatMember("blarps"), assert.False},
{"chat with the same member", details.TeamsChat, cs.ChatMember(member), assert.True},
{"chat with a member submatch search", details.TeamsChat, cs.ChatMember(member[2:5]), assert.True},
{"chat with a different name", details.TeamsChat, cs.ChatName("blarps"), assert.False},
{"chat with the same name", details.TeamsChat, cs.ChatName(name), assert.True},
{"chat with a subname search", details.TeamsChat, cs.ChatName(name[2:5]), assert.True},
{"chat with a different member", dtype, cs.ChatMember("blarps"), assert.False},
{"chat with the same member", dtype, cs.ChatMember(member), assert.True},
{"chat with a member submatch search", dtype, cs.ChatMember(member[2:5]), assert.True},
{"chat with a different topic", dtype, cs.ChatTopic("blarps"), assert.False},
{"chat with the same topic", dtype, cs.ChatTopic(topic), assert.True},
{"chat with a subtopic search", dtype, cs.ChatTopic(topic[2:5]), assert.True},
{"chat created after", dtype, cs.ChatCreatedAfter(past), assert.True},
{"chat not created after", dtype, cs.ChatCreatedAfter(future), assert.False},
{"chat created before", dtype, cs.ChatCreatedBefore(future), assert.True},
{"chat not created before", dtype, cs.ChatCreatedBefore(past), assert.False},
{"chat last message after", dtype, cs.ChatLastMessageAfter(past), assert.True},
{"chat last message not after", dtype, cs.ChatLastMessageAfter(future), assert.False},
{"chat last message before", dtype, cs.ChatLastMessageBefore(future), assert.True},
{"chat last message not before", dtype, cs.ChatLastMessageBefore(past), assert.False},
}
for _, test := range table {
suite.Run(test.name, func() {

View File

@ -103,7 +103,7 @@ func TeamsChatInfo(chat models.Chatable) *details.TeamsChatsInfo {
LastMessagePreview: preview,
Members: memberNames,
MessageCount: len(msgs),
Name: ptr.Val(chat.GetTopic()),
Topic: ptr.Val(chat.GetTopic()),
},
}
}

View File

@ -12,6 +12,79 @@ import (
"github.com/alcionai/corso/src/pkg/services/m365/api/pagers"
)
// ---------------------------------------------------------------------------
// chat members pager
// ---------------------------------------------------------------------------
// delta queries are not supported
var _ pagers.NonDeltaHandler[models.ConversationMemberable] = &chatMembersPageCtrl{}
type chatMembersPageCtrl struct {
chatID string
gs graph.Servicer
builder *chats.ItemMembersRequestBuilder
options *chats.ItemMembersRequestBuilderGetRequestConfiguration
}
func (p *chatMembersPageCtrl) SetNextLink(nextLink string) {
p.builder = chats.NewItemMembersRequestBuilder(nextLink, p.gs.Adapter())
}
func (p *chatMembersPageCtrl) GetPage(
ctx context.Context,
) (pagers.NextLinkValuer[models.ConversationMemberable], error) {
resp, err := p.builder.Get(ctx, p.options)
return resp, graph.Stack(ctx, err).OrNil()
}
func (p *chatMembersPageCtrl) ValidModTimes() bool {
return true
}
func (c Chats) NewChatMembersPager(
chatID string,
cc CallConfig,
) *chatMembersPageCtrl {
builder := c.Stable.
Client().
Chats().
ByChatId(chatID).
Members()
options := &chats.ItemMembersRequestBuilderGetRequestConfiguration{
QueryParameters: &chats.ItemMembersRequestBuilderGetQueryParameters{},
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize)),
}
if len(cc.Select) > 0 {
options.QueryParameters.Select = cc.Select
}
if len(cc.Expand) > 0 {
options.QueryParameters.Expand = cc.Expand
}
return &chatMembersPageCtrl{
chatID: chatID,
builder: builder,
gs: c.Stable,
options: options,
}
}
// GetChatMembers fetches a delta of all members in the chat.
func (c Chats) GetChatMembers(
ctx context.Context,
chatID string,
cc CallConfig,
) ([]models.ConversationMemberable, error) {
ctx = clues.Add(ctx, "chat_id", chatID)
pager := c.NewChatMembersPager(chatID, cc)
items, err := pagers.BatchEnumerateItems[models.ConversationMemberable](ctx, pager)
return items, graph.Stack(ctx, err).OrNil()
}
// ---------------------------------------------------------------------------
// chat message pager
// ---------------------------------------------------------------------------

View File

@ -61,6 +61,11 @@ func (suite *ChatsPagerIntgSuite) TestEnumerateChats() {
ac,
chatID,
chat.GetLastMessagePreview())
testEnumerateChatMembers(
suite.T(),
ac,
chatID)
})
}
}
@ -123,3 +128,22 @@ func testEnumerateChatMessages(
}
}
}
func testEnumerateChatMembers(
t *testing.T,
ac Chats,
chatID string,
) {
ctx, flush := tester.NewContext(t)
defer flush()
cc := CallConfig{}
members, err := ac.GetChatMembers(ctx, chatID, cc)
require.NoError(t, err, clues.ToCore(err))
// no good way to test members right now. Even though
// the graph api response contains the `userID` and `email`
// properties, we can't access them in the sdk model
assert.NotEmpty(t, members)
}

View File

@ -64,7 +64,7 @@ func (suite *ChatsAPIUnitSuite) TestChatsInfo() {
ItemType: details.TeamsChat,
Modified: then,
Chat: details.ChatInfo{
Name: "Hello world",
Topic: "Hello world",
LastMessageAt: then,
LastMessagePreview: id,
Members: []string{},
@ -92,7 +92,7 @@ func (suite *ChatsAPIUnitSuite) TestChatsInfo() {
ItemType: details.TeamsChat,
Modified: then,
Chat: details.ChatInfo{
Name: "Hello world",
Topic: "Hello world",
LastMessageAt: then,
LastMessagePreview: id,
Members: []string{},
@ -120,7 +120,7 @@ func (suite *ChatsAPIUnitSuite) TestChatsInfo() {
ItemType: details.TeamsChat,
Modified: then,
Chat: details.ChatInfo{
Name: "Hello world",
Topic: "Hello world",
LastMessageAt: time.Time{},
LastMessagePreview: "",
Members: []string{},
@ -138,7 +138,7 @@ func (suite *ChatsAPIUnitSuite) TestChatsInfo() {
chat, expected := test.expected()
result := TeamsChatInfo(chat)
assert.Equal(t, expected.Chat.Name, result.Chat.Name)
assert.Equal(t, expected.Chat.Topic, result.Chat.Topic)
expectCreated := chat.GetCreatedDateTime()
if expectCreated != nil {