From 54d5e05950c534ac2755e671ca3f701ad9ce8230 Mon Sep 17 00:00:00 2001 From: neha-Gupta1 Date: Fri, 18 Aug 2023 18:27:03 +0530 Subject: [PATCH 1/4] channels and messages API --- .../m365/collection/groups/handler.go | 1 - src/pkg/backup/details/iteminfo.go | 3 + src/pkg/services/m365/api/channels.go | 375 ++++++++++++++++++ src/pkg/services/m365/api/channels_pager.go | 75 +++- 4 files changed, 431 insertions(+), 23 deletions(-) diff --git a/src/internal/m365/collection/groups/handler.go b/src/internal/m365/collection/groups/handler.go index 950f6c828..742c41a2f 100644 --- a/src/internal/m365/collection/groups/handler.go +++ b/src/internal/m365/collection/groups/handler.go @@ -13,6 +13,5 @@ type BackupMessagesHandler interface { GetMessage(ctx context.Context, teamID, channelID, itemID string) (models.ChatMessageable, error) NewMessagePager(teamID, channelID string) api.MessageItemDeltaEnumerator GetChannel(ctx context.Context, teamID, channelID string) (models.Channelable, error) - NewChannelPager(teamID, channelID string) api.ChannelItemDeltaEnumerator GetReply(ctx context.Context, teamID, channelID, messageID string) (serialization.Parsable, error) } diff --git a/src/pkg/backup/details/iteminfo.go b/src/pkg/backup/details/iteminfo.go index 9912fb6d2..c6e05e9fb 100644 --- a/src/pkg/backup/details/iteminfo.go +++ b/src/pkg/backup/details/iteminfo.go @@ -36,6 +36,9 @@ const ( // Folder Management(30x) FolderItem ItemType = 306 + + // GroupChannel(40x) + GroupChannel ItemType = 407 ) func UpdateItem(item *ItemInfo, newLocPath *path.Builder) { diff --git a/src/pkg/services/m365/api/channels.go b/src/pkg/services/m365/api/channels.go index 778f64ec1..a6e0e3072 100644 --- a/src/pkg/services/m365/api/channels.go +++ b/src/pkg/services/m365/api/channels.go @@ -1 +1,376 @@ package api + +import ( + "context" + + "github.com/alcionai/clues" + "github.com/alcionai/corso/src/internal/common/ptr" + "github.com/alcionai/corso/src/internal/m365/graph" + "github.com/alcionai/corso/src/pkg/backup/details" + "github.com/alcionai/corso/src/pkg/fault" + "github.com/alcionai/corso/src/pkg/logger" + "github.com/microsoft/kiota-abstractions-go/serialization" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/teams" +) + +// --------------------------------------------------------------------------- +// controller +// --------------------------------------------------------------------------- + +func (c Client) Channels() Channels { + return Channels{c} +} + +// Channels is an interface-compliant provider of the client. +type Channels struct { + Client +} + +// --------------------------------------------------------------------------- +// containers +// --------------------------------------------------------------------------- + +// CreateContainer makes an channels with the name in the team +func (c Channels) CreateChannel( + ctx context.Context, + teamID, _, containerName string, +) (graph.Container, error) { + body := models.NewChannel() + body.SetDisplayName(&containerName) + + container, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + Post(ctx, body, nil) + if err != nil { + return nil, graph.Wrap(ctx, err, "creating channel") + } + + return ChannelsDisplayable{Channelable: container}, nil +} + +// DeleteChannel removes a channel from user's M365 account +func (c Channels) DeleteChannel( + ctx context.Context, + teamID, containerID string, +) error { + // deletes require unique http clients + // https://github.com/alcionai/corso/issues/2707 + srv, err := NewService(c.Credentials) + if err != nil { + return graph.Stack(ctx, err) + } + + err = srv.Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Delete(ctx, nil) + if err != nil { + return graph.Stack(ctx, err) + } + + return nil +} + +// prefer GetChannelByID where possible. +// use this only in cases where the models.Channelable +// is required. +func (c Channels) GetChannel( + ctx context.Context, + teamID, containerID string, +) (models.Channelable, error) { + config := &teams.ItemChannelsChannelItemRequestBuilderGetRequestConfiguration{ + QueryParameters: &teams.ItemChannelsChannelItemRequestBuilderGetQueryParameters{ + Select: idAnd("name", "owner"), + }, + } + + resp, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Get(ctx, config) + if err != nil { + return nil, graph.Stack(ctx, err) + } + + return resp, nil +} + +func (c Channels) GetChannelByID( + ctx context.Context, + teamID, containerID string, +) (graph.Container, error) { + channel, err := c.GetChannel(ctx, teamID, containerID) + if err != nil { + return nil, err + } + + return ChannelsDisplayable{Channelable: channel}, nil +} + +// GetChannelByName fetches a calendar by name +func (c Channels) GetChannelByName( + ctx context.Context, + teamID, _, containerName string, +) (graph.Container, error) { + + ctx = clues.Add(ctx, "channel_name", containerName) + + resp, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + Get(ctx, nil) + + if err != nil { + return nil, graph.Stack(ctx, err).WithClues(ctx) + } + + gv := resp.GetValue() + + if len(gv) == 0 { + return nil, clues.New("channel not found").WithClues(ctx) + } + + // We only allow the api to match one calendar with the provided name. + // If we match multiples, we'll eagerly return the first one. + logger.Ctx(ctx).Debugw("calendars matched the name search", "calendar_count", len(gv)) + + // Sanity check ID and name + cal := gv[0] + container := ChannelsDisplayable{Channelable: cal} + + if err := graph.CheckIDAndName(container); err != nil { + return nil, clues.Stack(err).WithClues(ctx) + } + + return container, nil +} + +func (c Channels) PatchChannel( + ctx context.Context, + teamID, containerID string, + body models.Channelable, +) error { + _, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Patch(ctx, body, nil) + + if err != nil { + return graph.Wrap(ctx, err, "patching event calendar") + } + + return nil +} + +// --------------------------------------------------------------------------- +// message +// --------------------------------------------------------------------------- + +// GetItem retrieves a Messageable item. +func (c Channels) GetMessage( + ctx context.Context, + teamID, channelID, itemID string, + immutableIDs bool, + errs *fault.Bus, +) (serialization.Parsable, *details.GroupsInfo, error) { + var ( + size int64 + ) + + // is preferImmutableIDs headers required here + message, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(channelID). + Messages(). + ByChatMessageId(itemID). + Get(ctx, nil) + if err != nil { + return nil, nil, graph.Stack(ctx, err) + } + + return message, MessageInfo(message, size), nil +} + +func (c Channels) PostMessage( + ctx context.Context, + teamID, containerID string, + body models.ChatMessageable, +) (models.ChatMessageable, error) { + itm, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Messages(). + Post(ctx, body, nil) + if err != nil { + return nil, graph.Wrap(ctx, err, "creating mail message") + } + + if itm == nil { + return nil, clues.New("nil response mail message creation").WithClues(ctx) + } + + return itm, nil +} + +func (c Channels) DeleteMessage( + ctx context.Context, + teamID, itemID, containerID string, +) error { + // deletes require unique http clients + // https://github.com/alcionai/corso/issues/2707 + srv, err := NewService(c.Credentials) + if err != nil { + return graph.Stack(ctx, err) + } + + err = srv. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Messages(). + ByChatMessageId(itemID). + Delete(ctx, nil) + if err != nil { + return graph.Wrap(ctx, err, "deleting mail message") + } + + return nil +} + +// --------------------------------------------------------------------------- +// replies +// --------------------------------------------------------------------------- + +// GetReplies retrieves a Messageable item. +func (c Channels) GetReplies( + ctx context.Context, + teamID, channelID, itemID string, +) (serialization.Parsable, error) { + replies, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(channelID). + Messages(). + ByChatMessageId(itemID). + Replies(). + Get(ctx, nil) + if err != nil { + return nil, graph.Stack(ctx, err) + } + + return replies, nil +} + +func (c Channels) PostReply( + ctx context.Context, + teamID, containerID, messageID string, + body models.ChatMessageable, +) (models.ChatMessageable, error) { + itm, err := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Messages(). + ByChatMessageId(messageID). + Replies(). + Post(ctx, body, nil) + if err != nil { + return nil, graph.Wrap(ctx, err, "creating reply message") + } + + if itm == nil { + return nil, clues.New("nil response reply to message creation").WithClues(ctx) + } + + return itm, nil +} + +func (c Channels) DeleteReply( + ctx context.Context, + teamID, itemID, containerID, replyID string, +) error { + // deletes require unique http clients + // https://github.com/alcionai/corso/issues/2707 + srv, err := NewService(c.Credentials) + if err != nil { + return graph.Stack(ctx, err) + } + + err = srv. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Messages(). + ByChatMessageId(itemID). + Replies(). + ByChatMessageId1(replyID). + Delete(ctx, nil) + if err != nil { + return graph.Wrap(ctx, err, "deleting mail message") + } + + return nil +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +func MessageInfo(msg models.ChatMessageable, size int64) *details.GroupsInfo { + var ( + created = ptr.Val(msg.GetCreatedDateTime()) + ) + + return &details.GroupsInfo{ + ItemType: details.GroupChannel, + Size: size, + Created: created, + Modified: ptr.OrNow(msg.GetLastModifiedDateTime()), + } +} + +// --------------------------------------------------------------------------- +// helper funcs +// --------------------------------------------------------------------------- + +// ChannelsDisplayable is a wrapper that complies with the +// models.Channelable interface with the graph.Container +// interfaces. +type ChannelsDisplayable struct { + models.Channelable +} + +// GetParentFolderId returns the default channe name address + +//nolint:revive +func (c ChannelsDisplayable) GetParentFolderId() *string { + return nil +} diff --git a/src/pkg/services/m365/api/channels_pager.go b/src/pkg/services/m365/api/channels_pager.go index 599c09649..ee1bb5f5c 100644 --- a/src/pkg/services/m365/api/channels_pager.go +++ b/src/pkg/services/m365/api/channels_pager.go @@ -2,6 +2,9 @@ package api import ( "context" + + "github.com/alcionai/corso/src/internal/m365/graph" + "github.com/microsoftgraph/msgraph-sdk-go/teams" ) // --------------------------------------------------------------------------- @@ -9,31 +12,59 @@ import ( // --------------------------------------------------------------------------- type MessageItemDeltaEnumerator interface { - GetPage(context.Context) (DeltaPageLinker, error) + GetPage(context.Context) (PageLinker, error) + SetNext(nextLink string) } -// TODO: implement -// var _ MessageItemDeltaEnumerator = &messagePageCtrl{} +var _ MessageItemDeltaEnumerator = &messagePageCtrl{} -// type messagePageCtrl struct { -// gs graph.Servicer -// builder *teams.ItemChannelsItemMessagesRequestBuilder -// options *teams.ItemChannelsItemMessagesRequestBuilderGetRequestConfiguration -// } - -// --------------------------------------------------------------------------- -// channel pager -// --------------------------------------------------------------------------- - -type ChannelItemDeltaEnumerator interface { - GetPage(context.Context) (DeltaPageLinker, error) +type messagePageCtrl struct { + gs graph.Servicer + builder *teams.ItemChannelsItemMessagesDeltaRequestBuilder + options *teams.ItemChannelsItemMessagesDeltaRequestBuilderGetRequestConfiguration } -// TODO: implement -// var _ ChannelsItemDeltaEnumerator = &channelsPageCtrl{} +func (c Channels) NewMessagePager( + teamID, + channelID string, + fields []string, +) *messagePageCtrl { + requestConfig := &teams.ItemChannelsItemMessagesDeltaRequestBuilderGetRequestConfiguration{ + QueryParameters: &teams.ItemChannelsItemMessagesDeltaRequestBuilderGetQueryParameters{ + Select: fields, + }, + } -// type channelsPageCtrl struct { -// gs graph.Servicer -// builder *teams.ItemChannelsChannelItemRequestBuilder -// options *teams.ItemChannelsChannelItemRequestBuilderGetRequestConfiguration -// } + res := &messagePageCtrl{ + gs: c.Stable, + options: requestConfig, + builder: c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(channelID). + Messages(). + Delta(), + } + + return res +} + +func (p *messagePageCtrl) SetNext(nextLink string) { + p.builder = teams.NewItemChannelsItemMessagesDeltaRequestBuilder(nextLink, p.gs.Adapter()) +} + +func (p *messagePageCtrl) GetPage(ctx context.Context) (PageLinker, error) { + var ( + resp PageLinker + err error + ) + + resp, err = p.builder.Get(ctx, p.options) + if err != nil { + return nil, graph.Stack(ctx, err) + } + + return resp, nil +} From aef4c688ac969615ed945ffa239b7919add188c7 Mon Sep 17 00:00:00 2001 From: neha-Gupta1 Date: Fri, 18 Aug 2023 23:34:31 +0530 Subject: [PATCH 2/4] lint code --- src/internal/m365/collection/groups/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/m365/collection/groups/handler.go b/src/internal/m365/collection/groups/handler.go index 742c41a2f..d4e963d16 100644 --- a/src/internal/m365/collection/groups/handler.go +++ b/src/internal/m365/collection/groups/handler.go @@ -3,10 +3,10 @@ package groups import ( "context" + "github.com/microsoft/kiota-abstractions-go/serialization" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/alcionai/corso/src/pkg/services/m365/api" - "github.com/microsoft/kiota-abstractions-go/serialization" ) type BackupMessagesHandler interface { From 7de04b98ab3df4c0c14c91b4ff8f76b5b25a3c40 Mon Sep 17 00:00:00 2001 From: neha-Gupta1 Date: Tue, 22 Aug 2023 10:19:36 +0530 Subject: [PATCH 3/4] test cases channel api --- src/internal/tester/tconfig/config.go | 8 + .../tester/tconfig/protected_resources.go | 11 ++ src/pkg/services/m365/api/channels.go | 28 +-- .../services/m365/api/channels_pager_test.go | 162 ++++++++++++++++++ 4 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 src/pkg/services/m365/api/channels_pager_test.go diff --git a/src/internal/tester/tconfig/config.go b/src/internal/tester/tconfig/config.go index a900f26f2..dbaebaecb 100644 --- a/src/internal/tester/tconfig/config.go +++ b/src/internal/tester/tconfig/config.go @@ -28,6 +28,7 @@ const ( TestCfgSiteURL = "m365siteurl" TestCfgTeamID = "m365teamid" TestCfgGroupID = "m365groupid" + TestCfgChannelID = "m365channelid" TestCfgUserID = "m365userid" TestCfgSecondaryUserID = "secondarym365userid" TestCfgTertiaryUserID = "tertiarym365userid" @@ -45,6 +46,7 @@ const ( EnvCorsoM365TestSiteURL = "CORSO_M365_TEST_SITE_URL" EnvCorsoM365TestTeamID = "CORSO_M365_TEST_TEAM_ID" EnvCorsoM365TestGroupID = "CORSO_M365_TEST_GROUP_ID" + EnvCorsoM365TestChannelID = "CORSO_M365_TEST_CHANNEL_ID" EnvCorsoM365TestUserID = "CORSO_M365_TEST_USER_ID" EnvCorsoSecondaryM365TestSiteID = "CORSO_SECONDARY_M365_TEST_SITE_ID" EnvCorsoSecondaryM365TestUserID = "CORSO_SECONDARY_M365_TEST_USER_ID" @@ -166,6 +168,12 @@ func ReadTestConfig() (map[string]string, error) { os.Getenv(EnvCorsoM365TestGroupID), vpr.GetString(TestCfgGroupID), "6f24b40d-b13d-4752-980f-f5fb9fba7aa0") + fallbackTo( + testEnv, + TestCfgChannelID, + os.Getenv(EnvCorsoM365TestChannelID), + vpr.GetString(TestCfgChannelID), + "19:nY6QHZ3hnHJ6ylarBxPjCOLRJNvrL3oKI5iW15QxTPA1@thread.tacv2") fallbackTo( testEnv, TestCfgSiteURL, diff --git a/src/internal/tester/tconfig/protected_resources.go b/src/internal/tester/tconfig/protected_resources.go index caac0c586..6a8d6a579 100644 --- a/src/internal/tester/tconfig/protected_resources.go +++ b/src/internal/tester/tconfig/protected_resources.go @@ -246,3 +246,14 @@ func M365GroupID(t *testing.T) string { return strings.ToLower(cfg[TestCfgTeamID]) } + +// M365ChannelID returns a channelID string representing the m365TeamsID described +// by either the env var CORSO_M365_TEST_CHANNEL_ID, the corso_test.toml config +// file or the default value (in that order of priority). The default is a +// last-attempt fallback that will only work on alcion's testing org. +func M365ChannelID(t *testing.T) string { + cfg, err := ReadTestConfig() + require.NoError(t, err, "retrieving m365 channel id from test configuration: %+v", clues.ToCore(err)) + + return cfg[TestCfgChannelID] +} diff --git a/src/pkg/services/m365/api/channels.go b/src/pkg/services/m365/api/channels.go index a6e0e3072..52e18b24b 100644 --- a/src/pkg/services/m365/api/channels.go +++ b/src/pkg/services/m365/api/channels.go @@ -2,6 +2,7 @@ package api import ( "context" + "fmt" "github.com/alcionai/clues" "github.com/alcionai/corso/src/internal/common/ptr" @@ -34,7 +35,7 @@ type Channels struct { // CreateContainer makes an channels with the name in the team func (c Channels) CreateChannel( ctx context.Context, - teamID, _, containerName string, + teamID, containerName string, ) (graph.Container, error) { body := models.NewChannel() body.SetDisplayName(&containerName) @@ -77,16 +78,13 @@ func (c Channels) DeleteChannel( return nil } -// prefer GetChannelByID where possible. -// use this only in cases where the models.Channelable -// is required. func (c Channels) GetChannel( ctx context.Context, teamID, containerID string, ) (models.Channelable, error) { config := &teams.ItemChannelsChannelItemRequestBuilderGetRequestConfiguration{ QueryParameters: &teams.ItemChannelsChannelItemRequestBuilderGetQueryParameters{ - Select: idAnd("name", "owner"), + Select: idAnd("displayName"), }, } @@ -116,20 +114,26 @@ func (c Channels) GetChannelByID( return ChannelsDisplayable{Channelable: channel}, nil } -// GetChannelByName fetches a calendar by name +// GetChannelByName fetches a channel by name func (c Channels) GetChannelByName( ctx context.Context, - teamID, _, containerName string, + teamID, containerName string, ) (graph.Container, error) { - ctx = clues.Add(ctx, "channel_name", containerName) + filter := fmt.Sprintf("displayName eq '%s'", containerName) + options := &teams.ItemChannelsRequestBuilderGetRequestConfiguration{ + QueryParameters: &teams.ItemChannelsRequestBuilderGetQueryParameters{ + Filter: &filter, + }, + } + resp, err := c.Stable. Client(). Teams(). ByTeamId(teamID). Channels(). - Get(ctx, nil) + Get(ctx, options) if err != nil { return nil, graph.Stack(ctx, err).WithClues(ctx) @@ -141,9 +145,9 @@ func (c Channels) GetChannelByName( return nil, clues.New("channel not found").WithClues(ctx) } - // We only allow the api to match one calendar with the provided name. + // We only allow the api to match one channel with the provided name. // If we match multiples, we'll eagerly return the first one. - logger.Ctx(ctx).Debugw("calendars matched the name search", "calendar_count", len(gv)) + logger.Ctx(ctx).Debugw("channels matched the name search") // Sanity check ID and name cal := gv[0] @@ -170,7 +174,7 @@ func (c Channels) PatchChannel( Patch(ctx, body, nil) if err != nil { - return graph.Wrap(ctx, err, "patching event calendar") + return graph.Wrap(ctx, err, "patching channel") } return nil diff --git a/src/pkg/services/m365/api/channels_pager_test.go b/src/pkg/services/m365/api/channels_pager_test.go new file mode 100644 index 000000000..4c8bc46e8 --- /dev/null +++ b/src/pkg/services/m365/api/channels_pager_test.go @@ -0,0 +1,162 @@ +package api_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/alcionai/clues" + "github.com/alcionai/corso/src/internal/common/ptr" + "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/internal/tester/tconfig" + "github.com/alcionai/corso/src/pkg/account" + "github.com/alcionai/corso/src/pkg/services/m365/api" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type ChannelPagerIntgSuite struct { + tester.Suite + its intgTesterSetup +} + +func TestChannelPagerIntgSuite(t *testing.T) { + suite.Run(t, &ChannelPagerIntgSuite{ + Suite: tester.NewIntegrationSuite( + t, + [][]string{tconfig.M365AcctCredEnvs}), + }) +} + +func (suite *ChannelPagerIntgSuite) SetupSuite() { + suite.its = newIntegrationTesterSetup(suite.T()) +} + +func (suite *ChannelPagerIntgSuite) TestChannels_GetPage() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + defer flush() + + teamID := tconfig.M365TeamsID(t) + channelID := tconfig.M365ChannelID(t) + pager := suite.its.ac.Channels().NewMessagePager(teamID, channelID, []string{}) + a, err := pager.GetPage(ctx) + assert.NoError(t, err, clues.ToCore(err)) + assert.NotNil(t, a) +} + +func (suite *ChannelPagerIntgSuite) TestChannels_CreateGetAndDelete() { + t := suite.T() + ctx, flush := tester.NewContext(t) + defer flush() + + var ( + yy, mm, dd = time.Now().Date() + hh = time.Now().Hour() + min = time.Now().Minute() + ss = time.Now().Second() + containerName = fmt.Sprintf("testChannel%d%d%d%d%d%d", yy, mm, dd, hh, min, ss) + teamID = tconfig.M365TeamsID(t) + credentials = suite.its.ac.Credentials + chanClient = suite.its.ac.Channels() + ) + + // GET channel - should be not found + _, err := suite.its.ac.Channels().GetChannelByName(ctx, teamID, containerName) + assert.Error(t, err, clues.ToCore(err)) + + // POST channel + channelPost, err := suite.its.ac.Channels().CreateChannel(ctx, teamID, containerName) + assert.NoError(t, err, clues.ToCore(err)) + + postChannelID := ptr.Val(channelPost.GetId()) + + // DELETE channel + defer func() { + _, err := chanClient.GetChannelByID(ctx, teamID, postChannelID) + + if err != nil { + fmt.Println("could not find channel: ", err) + } else { + deleteChannel(ctx, credentials, teamID, postChannelID) + } + }() + + // GET channel -should be found + channel, err := chanClient.GetChannelByName(ctx, teamID, containerName) + assert.NoError(t, err, clues.ToCore(err)) + assert.Equal(t, ptr.Val(channel.GetDisplayName()), containerName) + + // PATCH channel + patchBody := models.NewChannel() + patchName := fmt.Sprintf("othername%d%d%d%d%d%d", yy, mm, dd, hh, min, ss) + patchBody.SetDisplayName(ptr.To(patchName)) + err = chanClient.PatchChannel(ctx, teamID, postChannelID, patchBody) + assert.NoError(t, err, clues.ToCore(err)) + assert.Equal(t, ptr.Val(channel.GetDisplayName()), containerName) + + // GET channel -should not be found with old name + _, err = chanClient.GetChannelByName(ctx, teamID, containerName) + assert.Error(t, err, clues.ToCore(err)) + + // GET channel -should be found with new name + channel, err = chanClient.GetChannelByName(ctx, teamID, patchName) + assert.NoError(t, err, clues.ToCore(err)) + assert.Equal(t, ptr.Val(channel.GetDisplayName()), patchName) + assert.Equal(t, ptr.Val(channel.GetId()), postChannelID) + + // GET channel -should not be found with old name + err = chanClient.DeleteChannel(ctx, teamID, postChannelID) + assert.NoError(t, err, clues.ToCore(err)) + + // GET channel -should not be found anymore + _, err = chanClient.GetChannel(ctx, teamID, postChannelID) + assert.Error(t, err, clues.ToCore(err)) +} + +func deleteChannel(ctx context.Context, credentials account.M365Config, teamID, postChannelID string) { + srv, err := api.NewService(credentials) + if err != nil { + fmt.Println("Error found in getting creds") + } + + if err != nil { + fmt.Println("Error found in getting creds") + } + + err = srv.Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(postChannelID). + Delete(ctx, nil) + if err != nil { + fmt.Println("channel could not be delete in defer") + } +} + +// func (suite *ChannelPagerIntgSuite) TestMessages_CreateGetAndDelete() { +// t := suite.T() +// ctx, flush := tester.NewContext(t) +// defer flush() + +// var ( +// teamID = tconfig.M365TeamsID(t) +// channelID = tconfig.M365ChannelID(t) +// credentials = suite.its.ac.Credentials +// chanClient = suite.its.ac.Channels() +// ) + +// POST channel +// patchBody := models.NewChatMessage() +// body := models.NewItemBody() +// content := "Hello World" +// body.SetContent(&content) +// patchBody.SetBody(body) + +// _, := suite.its.ac.Channels().PostMessage(ctx, teamID, channelID, patchBody) +// assert.NoError(t, err, clues.ToCore(err)) +// } From 27990e6174d8a29ec1b0b59c1247c189571c3058 Mon Sep 17 00:00:00 2001 From: neha-Gupta1 Date: Tue, 22 Aug 2023 12:48:11 +0530 Subject: [PATCH 4/4] item pager for channels --- src/pkg/services/m365/api/channels_pager.go | 74 +++++++++++++++++++ .../services/m365/api/channels_pager_test.go | 39 +++++----- 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/pkg/services/m365/api/channels_pager.go b/src/pkg/services/m365/api/channels_pager.go index ee1bb5f5c..55ed89f56 100644 --- a/src/pkg/services/m365/api/channels_pager.go +++ b/src/pkg/services/m365/api/channels_pager.go @@ -3,7 +3,10 @@ package api import ( "context" + "github.com/alcionai/clues" + "github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/m365/graph" + "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/teams" ) @@ -68,3 +71,74 @@ func (p *messagePageCtrl) GetPage(ctx context.Context) (PageLinker, error) { return resp, nil } + +type MessageItemIDType struct { + ItemID string +} + +type channelItemPageCtrl struct { + gs graph.Servicer + builder *teams.ItemChannelsItemMessagesRequestBuilder + options *teams.ItemChannelsItemMessagesRequestBuilderGetRequestConfiguration +} + +func (c Channels) GetItemIDsInContainer( + ctx context.Context, + teamID, channelID string, +) (map[string]MessageItemIDType, error) { + ctx = clues.Add(ctx, "channel_id", channelID) + pager := c.NewChannelItemPager(teamID, channelID) + + items, err := enumerateItems(ctx, pager) + if err != nil { + return nil, graph.Wrap(ctx, err, "enumerating contacts") + } + + m := map[string]MessageItemIDType{} + + for _, item := range items { + m[ptr.Val(item.GetId())] = MessageItemIDType{ + ItemID: ptr.Val(item.GetId()), + } + } + + return m, nil +} + +func (c Channels) NewChannelItemPager( + teamID, containerID string, + selectProps ...string, +) itemPager[models.ChatMessageable] { + options := &teams.ItemChannelsItemMessagesRequestBuilderGetRequestConfiguration{ + QueryParameters: &teams.ItemChannelsItemMessagesRequestBuilderGetQueryParameters{}, + } + + if len(selectProps) > 0 { + options.QueryParameters.Select = selectProps + } + + builder := c.Stable. + Client(). + Teams(). + ByTeamId(teamID). + Channels(). + ByChannelId(containerID). + Messages() + + return &channelItemPageCtrl{c.Stable, builder, options} +} + +//lint:ignore U1000 False Positive +func (p *channelItemPageCtrl) getPage(ctx context.Context) (PageLinkValuer[models.ChatMessageable], error) { + page, err := p.builder.Get(ctx, p.options) + if err != nil { + return nil, graph.Stack(ctx, err) + } + + return EmptyDeltaLinker[models.ChatMessageable]{PageLinkValuer: page}, nil +} + +//lint:ignore U1000 False Positive +func (p *channelItemPageCtrl) setNext(nextLink string) { + p.builder = teams.NewItemChannelsItemMessagesRequestBuilder(nextLink, p.gs.Adapter()) +} diff --git a/src/pkg/services/m365/api/channels_pager_test.go b/src/pkg/services/m365/api/channels_pager_test.go index 4c8bc46e8..1b498c87f 100644 --- a/src/pkg/services/m365/api/channels_pager_test.go +++ b/src/pkg/services/m365/api/channels_pager_test.go @@ -139,24 +139,29 @@ func deleteChannel(ctx context.Context, credentials account.M365Config, teamID, } // func (suite *ChannelPagerIntgSuite) TestMessages_CreateGetAndDelete() { -// t := suite.T() -// ctx, flush := tester.NewContext(t) -// defer flush() +// t := suite.T() +// ctx, flush := tester.NewContext(t) +// defer flush() -// var ( -// teamID = tconfig.M365TeamsID(t) -// channelID = tconfig.M365ChannelID(t) -// credentials = suite.its.ac.Credentials -// chanClient = suite.its.ac.Channels() -// ) +// var ( +// teamID = tconfig.M365TeamsID(t) +// channelID = tconfig.M365ChannelID(t) +// credentials = suite.its.ac.Credentials +// chanClient = suite.its.ac.Channels() +// ) -// POST channel -// patchBody := models.NewChatMessage() -// body := models.NewItemBody() -// content := "Hello World" -// body.SetContent(&content) -// patchBody.SetBody(body) +// // GET channel - should be not found +// message, _, err := chanClient.GetMessage(ctx, teamID, channelID, "", "") +// assert.Error(t, err, clues.ToCore(err)) + +// // POST channel +// // patchBody := models.NewChatMessage() +// // body := models.NewItemBody() +// // content := "Hello World" +// // body.SetContent(&content) +// // patchBody.SetBody(body) + +// // _, := suite.its.ac.Channels().PostMessage(ctx, teamID, channelID, patchBody) +// // assert.NoError(t, err, clues.ToCore(err)) -// _, := suite.its.ac.Channels().PostMessage(ctx, teamID, channelID, patchBody) -// assert.NoError(t, err, clues.ToCore(err)) // }