corso/src/pkg/services/m365/api/channels.go
Keepers d2e35a9ea8
filter out system messages (#4481)
adds a filter that removes system messages from
channel message backups.

Also extends the preview size from 16 to 128 characters.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #3988

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
2023-10-13 02:48:08 +00:00

215 lines
5.3 KiB
Go

package api
import (
"context"
"fmt"
"time"
"github.com/alcionai/clues"
"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/common/str"
"github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/pkg/backup/details"
"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
// ---------------------------------------------------------------------------
func (c Channels) GetChannelMessage(
ctx context.Context,
teamID, channelID, messageID string,
) (models.ChatMessageable, *details.GroupsInfo, error) {
message, err := c.Stable.
Client().
Teams().
ByTeamId(teamID).
Channels().
ByChannelId(channelID).
Messages().
ByChatMessageId(messageID).
Get(ctx, nil)
if err != nil {
return nil, nil, graph.Stack(ctx, err)
}
replies, err := c.GetChannelMessageReplies(ctx, teamID, channelID, messageID)
if err != nil {
return nil, nil, graph.Wrap(ctx, err, "retrieving message replies")
}
message.SetReplies(replies)
info := ChannelMessageInfo(message)
return message, info, nil
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
func ChannelMessageInfo(
msg models.ChatMessageable,
) *details.GroupsInfo {
var (
lastReply time.Time
modTime = ptr.OrNow(msg.GetLastModifiedDateTime())
content string
)
for _, r := range msg.GetReplies() {
cdt := ptr.Val(r.GetCreatedDateTime())
if cdt.After(lastReply) {
lastReply = cdt
}
}
// if the message hasn't been modified since before the most recent
// reply, set the modified time to the most recent reply. This ensures
// we update the message contents to match changes in replies.
if modTime.Before(lastReply) {
modTime = lastReply
}
if msg.GetBody() != nil {
content = ptr.Val(msg.GetBody().GetContent())
}
return &details.GroupsInfo{
ItemType: details.GroupsChannelMessage,
Created: ptr.Val(msg.GetCreatedDateTime()),
LastReplyAt: lastReply,
Modified: modTime,
MessageCreator: GetChatMessageFrom(msg),
MessagePreview: str.Preview(content, 128),
ReplyCount: len(msg.GetReplies()),
Size: int64(len(content)),
}
}
// 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
}
func GetChatMessageFrom(msg models.ChatMessageable) string {
from := msg.GetFrom()
switch true {
case from == nil:
return ""
case from.GetApplication() != nil:
return ptr.Val(from.GetApplication().GetDisplayName())
case from.GetDevice() != nil:
return ptr.Val(from.GetDevice().GetDisplayName())
case from.GetUser() != nil:
return ptr.Val(from.GetUser().GetDisplayName())
}
return ""
}