channels and messages API (#4068)
<!-- PR description--> Message handler implementation and other APIs to fetch channels and messages data #### Does this PR need a docs update or release note? - [ ] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [ ] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
518b0a41f0
commit
e1fe7f4b16
@ -31,3 +31,10 @@ type BackupHandler interface {
|
||||
teamID, channelID, messageID string,
|
||||
) (serialization.Parsable, error)
|
||||
}
|
||||
|
||||
type BackupMessagesHandler interface {
|
||||
GetMessage(ctx context.Context, teamID, channelID, itemID string) (models.ChatMessageable, error)
|
||||
NewMessagePager(teamID, channelID string) api.ChannelMessageDeltaEnumerator
|
||||
GetChannel(ctx context.Context, teamID, channelID string) (models.Channelable, error)
|
||||
GetReply(ctx context.Context, teamID, channelID, messageID string) (serialization.Parsable, error)
|
||||
}
|
||||
|
||||
@ -1 +1,194 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/teams"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// controller
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (c Client) Channels() Channels {
|
||||
return Channels{c}
|
||||
}
|
||||
|
||||
// Channels is an interface-compliant provider of the client.
|
||||
type Channels struct {
|
||||
Client
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// containers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (c Channels) GetChannel(
|
||||
ctx context.Context,
|
||||
teamID, containerID string,
|
||||
) (models.Channelable, error) {
|
||||
config := &teams.ItemChannelsChannelItemRequestBuilderGetRequestConfiguration{
|
||||
QueryParameters: &teams.ItemChannelsChannelItemRequestBuilderGetQueryParameters{
|
||||
Select: idAnd("displayName"),
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GetChannelByName fetches a channel by name
|
||||
func (c Channels) GetChannelByName(
|
||||
ctx context.Context,
|
||||
teamID, containerName string,
|
||||
) (models.Channelable, 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, options)
|
||||
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 channel with the provided name.
|
||||
// If we match multiples, we'll eagerly return the first one.
|
||||
logger.Ctx(ctx).Debugw("channels matched the name search")
|
||||
|
||||
// Sanity check ID and name
|
||||
cal := gv[0]
|
||||
|
||||
if err := CheckIDAndName(cal); err != nil {
|
||||
return nil, clues.Stack(err).WithClues(ctx)
|
||||
}
|
||||
|
||||
return cal, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// message
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GetMessage retrieves a ChannelMessage item.
|
||||
func (c Channels) GetMessage(
|
||||
ctx context.Context,
|
||||
teamID, channelID, itemID string,
|
||||
errs *fault.Bus,
|
||||
) (serialization.Parsable, *details.GroupsInfo, error) {
|
||||
var size int64
|
||||
|
||||
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, ChannelMessageInfo(message, size), nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// replies
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GetReplies retrieves all replies to a Channel Message.
|
||||
func (c Channels) GetReplies(
|
||||
ctx context.Context,
|
||||
teamID, channelID, messageID string,
|
||||
) (serialization.Parsable, error) {
|
||||
replies, err := c.Stable.
|
||||
Client().
|
||||
Teams().
|
||||
ByTeamId(teamID).
|
||||
Channels().
|
||||
ByChannelId(channelID).
|
||||
Messages().
|
||||
ByChatMessageId(messageID).
|
||||
Replies().
|
||||
Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return replies, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func ChannelMessageInfo(msg models.ChatMessageable, size int64) *details.GroupsInfo {
|
||||
created := ptr.Val(msg.GetCreatedDateTime())
|
||||
|
||||
return &details.GroupsInfo{
|
||||
ItemType: details.TeamsChannelMessage,
|
||||
Size: size,
|
||||
Created: created,
|
||||
Modified: ptr.OrNow(msg.GetLastModifiedDateTime()),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// helper funcs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// CheckIDAndName is a validator that ensures the ID
|
||||
// and name are populated and not zero valued.
|
||||
func CheckIDAndName(c models.Channelable) error {
|
||||
if c == nil {
|
||||
return clues.New("nil container")
|
||||
}
|
||||
|
||||
id := ptr.Val(c.GetId())
|
||||
if len(id) == 0 {
|
||||
return clues.New("container missing ID")
|
||||
}
|
||||
|
||||
dn := ptr.Val(c.GetDisplayName())
|
||||
if len(dn) == 0 {
|
||||
return clues.New("container missing display name").With("container_id", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
67
src/pkg/services/m365/api/channels_pager_test.go
Normal file
67
src/pkg/services/m365/api/channels_pager_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
"github.com/alcionai/corso/src/internal/tester"
|
||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||
)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
// This will be added once 'pager' is implemented
|
||||
// func (suite *ChannelPagerIntgSuite) TestChannels_GetPage() {
|
||||
// t := suite.T()
|
||||
|
||||
// ctx, flush := tester.NewContext(t)
|
||||
// defer flush()
|
||||
|
||||
// teamID := tconfig.M365TeamID(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_Get() {
|
||||
t := suite.T()
|
||||
ctx, flush := tester.NewContext(t)
|
||||
|
||||
defer flush()
|
||||
|
||||
var (
|
||||
containerName = "General"
|
||||
teamID = tconfig.M365TeamID(t)
|
||||
chanClient = suite.its.ac.Channels()
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
// GET channel -should be found
|
||||
_, err = chanClient.GetChannel(ctx, teamID, ptr.Val(channel.GetId()))
|
||||
assert.NoError(t, err, clues.ToCore(err))
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user