From e49e4605ff6692d5abd1f4f757962973ed223d05 Mon Sep 17 00:00:00 2001 From: apCognixCh Date: Fri, 21 Jun 2024 15:57:05 +0300 Subject: [PATCH 1/3] adjust ms teams connector --- docs/connectors/connectors.md | 6 + docs/connectors/teams.md | 8 +- src/backend/connector/executor.go | 7 + .../core/connector/microsoft-core/drive.go | 5 +- src/backend/core/connector/ms-teams.go | 246 +++++++++--------- src/backend/core/connector/onedrive.go | 90 ------- 6 files changed, 142 insertions(+), 220 deletions(-) diff --git a/docs/connectors/connectors.md b/docs/connectors/connectors.md index 9c24af6d..306f1d74 100644 --- a/docs/connectors/connectors.md +++ b/docs/connectors/connectors.md @@ -135,3 +135,9 @@ copy token from response - Group.Read.All - Directory.Read.All + + +username +```html +sdsdfsdfs +``` \ No newline at end of file diff --git a/docs/connectors/teams.md b/docs/connectors/teams.md index 4c8316f1..3b9078d9 100644 --- a/docs/connectors/teams.md +++ b/docs/connectors/teams.md @@ -13,8 +13,7 @@ At step 2: ```json { - "channel": "", - "topics": [], + "team": "", "files": { "folder": "", "recursive": true @@ -28,9 +27,8 @@ At step 2: } ``` -- channel : name of channel for analyzing -- recursive : false - scan only given folder , true - scan nested folders -- token : OAuth token for access to ```one drive``` +- team : name of team for analyzing +- token : OAuth token for access to ```ms teams``` - files : - folder : optional, folder name for scanning - recursive : false - scan only given folder , true - scan nested folders diff --git a/src/backend/connector/executor.go b/src/backend/connector/executor.go index e04d10fb..3fa97414 100644 --- a/src/backend/connector/executor.go +++ b/src/backend/connector/executor.go @@ -78,12 +78,15 @@ func (e *Executor) runConnector(ctx context.Context, msg jetstream.Msg) error { // execute connector resultCh := connectorWF.Execute(ctx, trigger.Params) // read result from channel + hasSemanticMessage := false for result := range resultCh { var loopErr error // empty result when channel was closed. if result.SourceID == "" { break } + hasSemanticMessage = true + // save content in minio if result.Content != nil { if err = e.saveContent(ctx, result); err != nil { @@ -139,6 +142,10 @@ func (e *Executor) runConnector(ctx context.Context, msg jetstream.Msg) error { if err != nil { zap.S().Errorf("failed to update documents: %v", err) connectorModel.Status = model.ConnectorStatusUnableProcess + } else { + if !hasSemanticMessage { + connectorModel.Status = model.ConnectorStatusSuccess + } } connectorModel.LastUpdate = pg.NullTime{time.Now().UTC()} diff --git a/src/backend/core/connector/microsoft-core/drive.go b/src/backend/core/connector/microsoft-core/drive.go index 810fcb76..bbaae5a1 100644 --- a/src/backend/core/connector/microsoft-core/drive.go +++ b/src/backend/core/connector/microsoft-core/drive.go @@ -94,11 +94,10 @@ func (c *MSDrive) getFile(item *DriveChildBody) error { } else { // when file was stored in minio URL should be minio:bucket:filename minioFile := strings.Split(doc.URL, ":") - if len(minioFile) != 3 { - return fmt.Errorf("invalid file url: %s", doc.URL) + if len(minioFile) == 3 && minioFile[0] == "minio" { + fileName = minioFile[2] } // use previous file name for update file in minio - fileName = minioFile[2] } doc.IsExists = true diff --git a/src/backend/core/connector/ms-teams.go b/src/backend/core/connector/ms-teams.go index 4bda8c26..3b8dd0d7 100644 --- a/src/backend/core/connector/ms-teams.go +++ b/src/backend/core/connector/ms-teams.go @@ -14,16 +14,16 @@ import ( "github.com/google/uuid" "go.uber.org/zap" "golang.org/x/oauth2" - "jaytaylor.com/html2text" + "strings" "time" ) const ( msTeamsChannelsURL = "https://graph.microsoft.com/v1.0/teams/%s/channels" - //msTeamsMessagesURL = "https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages/microsoft.graph.delta()" - msTeamsMessagesURL = "https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages" - msTeamRepliesURL = "https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages/%s/replies" - msTeamsInfoURL = "https://graph.microsoft.com/v1.0/teams" + msTeamsMessagesURL = "https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages/microsoft.graph.delta()" + //msTeamsMessagesURL = "https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages" + msTeamRepliesURL = "https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages/%s/replies" + msTeamsInfoURL = "https://graph.microsoft.com/v1.0/teams" msTeamsFilesFolder = "https://graph.microsoft.com/v1.0/teams/%s/channels/%s/filesFolder" msTeamsFolderContent = "https://graph.microsoft.com/v1.0/groups/%s/drive/items/%s/children" @@ -31,8 +31,7 @@ const ( msTeamsChats = "https://graph.microsoft.com/v1.0/chats" msTeamsChatMessagesURL = "https://graph.microsoft.com/v1.0/chats/%s/messages" - msTeamsParamTeamID = "team_id" - msTeamsParamChannelID = "channel_id" + msTeamsParamTeamID = "team_id" ) type ( @@ -136,14 +135,16 @@ type ( sessionID uuid.NullUUID } MSTeamParameters struct { - Channel string `json:"channel"` - Topics model.StringSlice `json:"topics"` - Chat string `json:"chat"` - Token *oauth2.Token `json:"token"` - Files *microsoft_core.MSDriveParam `json:"files"` + Team string `json:"team"` + Token *oauth2.Token `json:"token"` + Files *microsoft_core.MSDriveParam `json:"files"` } // MSTeamState store ms team state after each execute MSTeamState struct { + Channels map[string]*MSTeamChannelState `json:"channels"` + } + + MSTeamChannelState struct { // Link for request changes after last execution DeltaLink string `json:"delta_link"` Topics map[string]*MSTeamMessageState `json:"topics"` @@ -154,11 +155,7 @@ type ( } MSTeamsResult struct { PrevLoadTime string - Messages []*MSTeamsResultMessages - } - MSTeamsResultMessages struct { - User string `json:"user"` - Message string `json:"message"` + Messages []byte } ) @@ -176,13 +173,6 @@ func (c *MSTeams) PrepareTask(ctx context.Context, task Task) error { } params[msTeamsParamTeamID] = teamID - channelID, err := c.getChannel(ctx, teamID) - if err != nil { - zap.S().Errorf(err.Error()) - return err - } - params[msTeamsParamChannelID] = channelID - zap.S().Infof("teamID %s channelID %s", teamID, channelID) return task.RunConnector(ctx, &proto.ConnectorRequest{ Id: c.model.ID.IntPart(), Params: params, @@ -212,11 +202,8 @@ func (c *MSTeams) execute(ctx context.Context, param map[string]string) error { if !ok { return fmt.Errorf("team_id is not configured") } - channelID, ok := param[msTeamsParamChannelID] - if !ok { - return fmt.Errorf("channel_id is not configured") - } - topics, err := c.getTopicsByChannel(ctx, teamID, channelID) + + channelIDs, err := c.getChannel(ctx, teamID) if err != nil { return err } @@ -224,57 +211,77 @@ func (c *MSTeams) execute(ctx context.Context, param map[string]string) error { UUID: uuid.New(), Valid: true, } - // load topics - for _, topic := range topics { - // create unique id for store new messages in new document - sourceID := fmt.Sprintf("%s-%s", topic.Id, uuid.New().String()) - replies, err := c.getReplies(ctx, teamID, channelID, topic) - if err != nil { - return err + // loop by channels + for _, channelID := range channelIDs { + // prepare state for channel + channelState, ok := c.state.Channels[channelID] + if !ok { + channelState = &MSTeamChannelState{ + DeltaLink: "", + Topics: make(map[string]*MSTeamMessageState), + } + c.state.Channels[channelID] = channelState } - body, err := json.Marshal(replies.Messages) + + topics, err := c.getTopicsByChannel(ctx, teamID, channelID) if err != nil { return err } - doc := &model.Document{ - SourceID: sourceID, - ConnectorID: c.model.ID, - URL: "", - ChunkingSession: c.sessionID, - Analyzed: false, - CreationDate: time.Now().UTC(), - LastUpdate: pg.NullTime{time.Now().UTC()}, - IsExists: true, - } - c.model.DocsMap[sourceID] = doc - fileName := topic.Id + "_" + topic.Subject - if replies.PrevLoadTime != "" { - fileName += "-" + replies.PrevLoadTime - } - fileName += ".json" - c.resultCh <- &Response{ - URL: doc.URL, - Name: fileName, - SourceID: doc.SourceID, - DocumentID: doc.ID.IntPart(), - MimeType: "plain/text", - FileType: proto.FileType_TXT, - Signature: "", - Content: &Content{ - Bucket: model.BucketName(c.model.User.EmbeddingModel.TenantID), - URL: "", - AppendContent: true, - Body: body, - }, - UpToData: false, - } - } - if c.param.Files != nil { - if err = c.loadFiles(ctx, param, teamID, channelID); err != nil { - return err + // load topics + for _, topic := range topics { + // create unique id for store new messages in new document + sourceID := fmt.Sprintf("%s-%s-%s", channelID, topic.Id, uuid.New().String()) + + replies, err := c.getReplies(ctx, teamID, channelID, topic) + if err != nil { + return err + } + if len(replies.Messages) == 0 { + continue + } + doc := &model.Document{ + SourceID: sourceID, + ConnectorID: c.model.ID, + URL: "", + ChunkingSession: c.sessionID, + Analyzed: false, + CreationDate: time.Now().UTC(), + LastUpdate: pg.NullTime{time.Now().UTC()}, + IsExists: true, + } + c.model.DocsMap[sourceID] = doc + + fileName := fmt.Sprintf("%s_%s.md", + strings.ReplaceAll(uuid.New().String(), "-", ""), + strings.ReplaceAll(topic.Subject, " ", "")) + c.resultCh <- &Response{ + URL: doc.URL, + Name: fileName, + SourceID: doc.SourceID, + DocumentID: doc.ID.IntPart(), + MimeType: "plain/text", + FileType: proto.FileType_MD, + Signature: "", + Content: &Content{ + Bucket: model.BucketName(c.model.User.EmbeddingModel.TenantID), + URL: "", + AppendContent: true, + Body: replies.Messages, + }, + UpToData: false, + } + + if c.param.Files != nil { + if err = c.loadFiles(ctx, param, teamID, channelID); err != nil { + return err + } + } + } + } + // save current state if err = c.model.State.FromStruct(c.state); err == nil { return c.connectorRepo.Update(ctx, c.model) @@ -282,6 +289,7 @@ func (c *MSTeams) execute(ctx context.Context, param map[string]string) error { return nil } +// loadFiles scrap channel files func (c *MSTeams) loadFiles(ctx context.Context, param map[string]string, teamID, channelID string) error { var folderInfo TeamFilesFolder if err := c.requestAndParse(ctx, fmt.Sprintf(msTeamsFilesFolder, teamID, channelID), &folderInfo); err != nil { @@ -299,17 +307,17 @@ func (c *MSTeams) loadFiles(ctx context.Context, param map[string]string, teamID } -func (c *MSTeams) getChannel(ctx context.Context, teamID string) (string, error) { +// getChannel get channels from team +func (c *MSTeams) getChannel(ctx context.Context, teamID string) ([]string, error) { var channelResp ChannelResponse if err := c.requestAndParse(ctx, fmt.Sprintf(msTeamsChannelsURL, teamID), &channelResp); err != nil { - return "", err + return nil, err } + var channels []string for _, channel := range channelResp.Value { - if channel.DisplayName == c.param.Channel { - return channel.Id, nil - } + channels = append(channels, channel.Id) } - return "", fmt.Errorf("channel not found") + return channels, nil } func (c *MSTeams) getReplies(ctx context.Context, teamID, channelID string, msg *MessageBody) (*MSTeamsResult, error) { @@ -319,10 +327,21 @@ func (c *MSTeams) getReplies(ctx context.Context, teamID, channelID string, msg return nil, err } var result MSTeamsResult - state, ok := c.state.Topics[msg.Id] + var messages []string + + state, ok := c.state.Channels[channelID].Topics[msg.Id] if !ok { state = &MSTeamMessageState{} - c.state.Topics[msg.Id] = state + c.state.Channels[channelID].Topics[msg.Id] = state + userName := msg.Subject + if msg.From != nil && msg.From.User != nil { + userName = msg.From.User.DisplayName + } + message := msg.Subject + if msg.Body != nil { + message = msg.Body.Content + } + messages = append(messages, fmt.Sprintf("%s\n```html\n%s\n```\n", userName, message)) } else { result.PrevLoadTime = state.LastCreatedDateTime.Format("2006-01-02-15-04-05") } @@ -339,17 +358,12 @@ func (c *MSTeams) getReplies(ctx context.Context, teamID, channelID string, msg lastTime = repl.CreatedDateTime } - message := repl.Body.Content - if repl.Body.ContentType == "html" { - message, err = html2text.FromString(message, html2text.Options{ - PrettyTables: true, - }) - } - result.Messages = append(result.Messages, &MSTeamsResultMessages{ - User: repl.From.User.DisplayName, - Message: message, - }) + message := fmt.Sprintf("%s\n```html\n%s\n```\n", repl.From.User.DisplayName, repl.Body.Content) + + messages = append(messages, message) + } + result.Messages = []byte(strings.Join(messages, "\n")) state.LastCreatedDateTime = lastTime return &result, nil } @@ -357,40 +371,26 @@ func (c *MSTeams) getReplies(ctx context.Context, teamID, channelID string, msg func (c *MSTeams) getTopicsByChannel(ctx context.Context, teamID, channelID string) ([]*MessageBody, error) { var messagesResp MessageResponse // Get url from state. Load changes from previous scan. + state := c.state.Channels[channelID] + + url := state.DeltaLink + if url == "" { + // Load all history if stored lin is empty + url = fmt.Sprintf(msTeamsMessagesURL, teamID, channelID) + } - //url := c.state.DeltaLink - //if url == "" { - // // Load all history if stored lin is empty - // url = fmt.Sprintf(msTeamsMessagesURL, teamID, channelID)incremental request - //} - url := fmt.Sprintf(msTeamsMessagesURL, teamID, channelID) if err := c.requestAndParse(ctx, url, &messagesResp); err != nil { return nil, err } - //if messagesResp.OdataNextLink != "" { - // c.state.DeltaLink = messagesResp.OdataNextLink - //} - //if messagesResp.OdataDeltaLink != "" { - // c.state.DeltaLink = messagesResp.OdataDeltaLink - //} - - messagesForScan := make([]*MessageBody, 0) - for _, msg := range messagesResp.Value { - if msg.Subject == "" { - // todo add validation on Subject == null - topic was deleted. - //for _, doc := range c.model.DocsMap { - // if strings.HasPrefix(doc.SourceID, msg.Id) { - // doc.IsExists = false - // } - //} - //delete(c.state.Topics, msg.Id) - continue + if len(messagesResp.Value) > 0 { + if messagesResp.OdataNextLink != "" { + state.DeltaLink = messagesResp.OdataNextLink } - if len(c.param.Topics) == 0 || c.param.Topics.InArray(msg.Subject) { - messagesForScan = append(messagesForScan, msg) + if messagesResp.OdataDeltaLink != "" { + state.DeltaLink = messagesResp.OdataDeltaLink } } - return messagesForScan, nil + return messagesResp.Value, nil } // getTeamID get team id for current user @@ -404,9 +404,11 @@ func (c *MSTeams) getTeamID(ctx context.Context) (string, error) { return "", fmt.Errorf("team not found") } for _, tm := range team.Value { - zap.S().Infof("team %s (%s) ", tm.Id, tm.DisplayName) + if tm.DisplayName == c.param.Team { + return tm.Id, nil + } } - return team.Value[0].Id, nil + return "", fmt.Errorf("team not found") } // requestAndParse request graph endpoint and parse result. @@ -466,8 +468,8 @@ func NewMSTeams(connector *model.Connector, if err = connector.State.ToStruct(conn.state); err != nil { zap.S().Infof("can not parse state %v", err) } - if conn.state.Topics == nil { - conn.state.Topics = make(map[string]*MSTeamMessageState) + if conn.state.Channels == nil { + conn.state.Channels = make(map[string]*MSTeamChannelState) } conn.client = resty.New(). diff --git a/src/backend/core/connector/onedrive.go b/src/backend/core/connector/onedrive.go index 3d08d235..d69f07ee 100644 --- a/src/backend/core/connector/onedrive.go +++ b/src/backend/core/connector/onedrive.go @@ -19,7 +19,6 @@ import ( "time" ) -// todo max file size 1G const ( authorizationHeader = "Authorization" apiBase = "https://graph.microsoft.com/v2.0" @@ -136,95 +135,6 @@ func (c *OneDrive) getFile(payload *microsoft_core.Response) { c.resultCh <- response } -// -//func (c *OneDrive) recognizeFiletype(item *DriveChildBody) (string, proto.FileType) { -// -// mimeTypeParts := strings.Split(item.File.MimeType, ";") -// -// if fileType, ok := supportedMimeTypes[mimeTypeParts[0]]; ok { -// return mimeTypeParts[0], fileType -// } -// // recognize fileType by filename extension -// fileNameParts := strings.Split(item.Name, ".") -// if len(fileNameParts) > 1 { -// if mimeType, ok := supportedExtensions[strings.ToUpper(fileNameParts[len(fileNameParts)-1])]; ok { -// return mimeType, supportedMimeTypes[mimeType] -// } -// } -// // recognize filetype by content -// response, err := c.client.R(). -// SetDoNotParseResponse(true). -// Get(item.MicrosoftGraphDownloadUrl) -// if err == nil && !response.IsError() { -// if mime, err := mimetype.DetectReader(response.RawBody()); err == nil { -// if fileType, ok := supportedMimeTypes[mime.String()]; ok { -// return mime.String(), fileType -// } -// } -// } -// response.RawBody().Close() -// return "", proto.FileType_UNKNOWN -//} -//func (c *OneDrive) getFolder(ctx context.Context, folder string, id string) error { -// body, err := c.request(ctx, fmt.Sprintf(getFolderChild, id)) -// if err != nil { -// return err -// } -// return c.handleItems(ctx, folder, body.Value) -//} -// -//func (c *OneDrive) handleItems(ctx context.Context, folder string, items []*DriveChildBody) error { -// for _, item := range items { -// // read files if user do not configure folder name -// // or current folder as a part of configured folder. -// if !c.isFolderAnalysing(folder) { -// continue -// } -// //if item.File != nil && (strings.Contains(folder, c.param.Folder) || c.param.Folder == "") { -// if item.File != nil && c.isFilesAnalysing(folder) { -// if err := c.getFile(item); err != nil { -// zap.S().Errorf("Failed to get file with id %s : %s ", item.Id, err.Error()) -// continue -// } -// } -// if item.Folder != nil { -// // do not scan nested folder if user wants to read dod from single folder -// if /*item.Name != c.param.Folder*/ strings.Contains(folder, c.param.Folder) && !c.param.Recursive { -// continue -// } -// nextFolder := folder -// if nextFolder != "" { -// nextFolder += "/" -// } -// if err := c.getFolder(ctx, nextFolder+item.Name, item.Id); err != nil { -// zap.S().Errorf("Failed to get folder with id %s : %s ", item.Id, err.Error()) -// continue -// } -// } -// -// } -// return nil -//} -// -//func (c *OneDrive) request(ctx context.Context, url string) (*GetDriveResponse, error) { -// response, err := c.client.R(). -// SetContext(ctx). -// SetHeader(authorizationHeader, fmt.Sprintf("%s %s", -// c.param.Token.TokenType, -// c.param.Token.AccessToken)). -// Get(url) -// if err = utils.WrapRestyError(response, err); err != nil { -// zap.S().Error(err.Error()) -// return nil, err -// } -// var body GetDriveResponse -// if err = json.Unmarshal(response.Body(), &body); err != nil { -// zap.S().Errorw("unmarshal failed", "error", err) -// return nil, err -// } -// return &body, nil -//} - // NewOneDrive creates new instance of OneDrive connector func NewOneDrive(connector *model.Connector, connectorRepo repository.ConnectorRepository, From 2fe4882ffad263eff2f6d54eee172de6a56797d3 Mon Sep 17 00:00:00 2001 From: apCognixCh Date: Fri, 21 Jun 2024 15:59:35 +0300 Subject: [PATCH 2/3] update docs --- docs/connectors/connectors.md | 38 ++-------------------- docs/microsoft/application-registration.md | 14 +++++++- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/docs/connectors/connectors.md b/docs/connectors/connectors.md index 306f1d74..ffaedcb9 100644 --- a/docs/connectors/connectors.md +++ b/docs/connectors/connectors.md @@ -96,48 +96,14 @@ copy token from response } } ``` - -#### Microsoft Teams - -```json -{ - "channel": "", - "topics": ["",""], - "files": { - "folder": "", - "recursive": false, - }, - "token": { - "access_token": "", - "expiry": "", - "refresh_token": "", - "token_type": "" - } -} -``` - -- channel : name of channel for analyzing -- topics : false - scan only given folder , true - scan nested folders -- token : OAuth token for access to ```one drive``` -- files : - - folder : optional, folder name for scanning - - recursive : false - scan only given folder , true - scan nested folders - - #### scope for oauth service - ChannelMessage.Read.All - - Chat.Read Chat.ReadBasic + - Chat.Read + - Chat.ReadBasic - Team.ReadBasic.All - TeamSettings.Read.All - ChannelSettings.Read.All - Channel.ReadBasic.All - Group.Read.All - Directory.Read.All - - - -username -```html -sdsdfsdfs -``` \ No newline at end of file diff --git a/docs/microsoft/application-registration.md b/docs/microsoft/application-registration.md index 40cec24f..375dd903 100644 --- a/docs/microsoft/application-registration.md +++ b/docs/microsoft/application-registration.md @@ -41,4 +41,16 @@ then choose Save. - offline_access - Authentication - Add platform - - choose web and define redirect URL https://rag.cognix.ch/api/oauth/microsoft/callback \ No newline at end of file + - choose web and define redirect URL https://rag.cognix.ch/api/oauth/microsoft/callback +#### Scope for MS TEAMS + +- ChannelMessage.Read.All +- Chat.Read +- Chat.ReadBasic +- Team.ReadBasic.All +- TeamSettings.Read.All +- ChannelSettings.Read.All +- Channel.ReadBasic.All +- Group.Read.All +- Directory.Read.All + From b116f5ac8f574633d50c6250bc003720b0305a2e Mon Sep 17 00:00:00 2001 From: apCognixCh Date: Fri, 21 Jun 2024 16:50:56 +0300 Subject: [PATCH 3/3] add channel name condition --- src/backend/core/connector/ms-teams.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/backend/core/connector/ms-teams.go b/src/backend/core/connector/ms-teams.go index 3b8dd0d7..bc5f3a6d 100644 --- a/src/backend/core/connector/ms-teams.go +++ b/src/backend/core/connector/ms-teams.go @@ -135,9 +135,10 @@ type ( sessionID uuid.NullUUID } MSTeamParameters struct { - Team string `json:"team"` - Token *oauth2.Token `json:"token"` - Files *microsoft_core.MSDriveParam `json:"files"` + Team string `json:"team"` + Channels model.StringSlice `json:"channels"` + Token *oauth2.Token `json:"token"` + Files *microsoft_core.MSDriveParam `json:"files"` } // MSTeamState store ms team state after each execute MSTeamState struct { @@ -168,7 +169,7 @@ func (c *MSTeams) PrepareTask(ctx context.Context, task Task) error { teamID, err := c.getTeamID(ctx) if err != nil { - zap.S().Errorf(err.Error()) + zap.S().Errorf("Prepare task get teamID : %s ", err.Error()) return err } params[msTeamsParamTeamID] = teamID @@ -190,7 +191,7 @@ func (c *MSTeams) Execute(ctx context.Context, param map[string]string) chan *Re go func() { defer close(c.resultCh) if err := c.execute(ctx, param); err != nil { - zap.S().Errorf(err.Error()) + zap.S().Errorf("execute %s ", err.Error()) } return }() @@ -283,6 +284,7 @@ func (c *MSTeams) execute(ctx context.Context, param map[string]string) error { } // save current state + zap.S().Infof("save connector state.") if err = c.model.State.FromStruct(c.state); err == nil { return c.connectorRepo.Update(ctx, c.model) } @@ -315,7 +317,13 @@ func (c *MSTeams) getChannel(ctx context.Context, teamID string) ([]string, erro } var channels []string for _, channel := range channelResp.Value { - channels = append(channels, channel.Id) + if len(c.param.Channels) == 0 || + c.param.Channels.InArray(channel.DisplayName) { + channels = append(channels, channel.Id) + } + } + if len(channels) == 0 { + return nil, fmt.Errorf("channel not found") } return channels, nil }