mirror of https://github.com/harness/drone.git
Add comment mentions metadata and return id to principal info mapping in list activity response (#2024)
parent
423801d51a
commit
e31f33adde
|
@ -47,6 +47,16 @@ func (c *Controller) ActivityList(
|
|||
return nil, fmt.Errorf("failed to list pull requests activities: %w", err)
|
||||
}
|
||||
|
||||
for _, act := range list {
|
||||
if act.Metadata != nil && act.Metadata.Mentions != nil {
|
||||
mentions, err := c.principalInfoCache.Map(ctx, act.Metadata.Mentions.IDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch activity mentions from principalInfoView: %w", err)
|
||||
}
|
||||
act.Mentions = mentions
|
||||
}
|
||||
}
|
||||
|
||||
list = removeDeletedComments(list)
|
||||
|
||||
return list, nil
|
||||
|
|
|
@ -127,6 +127,12 @@ func (c *Controller) CommentCreate(
|
|||
// generate all metadata updates
|
||||
var metadataUpdates []types.PullReqActivityMetadataUpdate
|
||||
|
||||
metadataUpdates, principalInfos, err := c.appendMetadataUpdateForMentions(
|
||||
ctx, metadataUpdates, in.Text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update metadata for mentions: %w", err)
|
||||
}
|
||||
|
||||
// suggestion metadata in case of code comments or code comment replies (don't restrict to either side for now).
|
||||
if in.IsCodeComment() || (in.IsReply() && parentAct.IsValidCodeComment()) {
|
||||
metadataUpdates = appendMetadataUpdateForSuggestions(metadataUpdates, in.Text)
|
||||
|
@ -192,6 +198,9 @@ func (c *Controller) CommentCreate(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Populate activity mentions (used only for response purposes).
|
||||
act.Mentions = principalInfos
|
||||
|
||||
if in.IsCodeComment() {
|
||||
// Migrate the comment if necessary... Note: we still need to return the code comment as is.
|
||||
c.migrateCodeComment(ctx, repo, pr, in, act.AsCodeComment(), cut)
|
||||
|
@ -428,3 +437,29 @@ func appendMetadataUpdateForSuggestions(
|
|||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Controller) appendMetadataUpdateForMentions(
|
||||
ctx context.Context,
|
||||
updates []types.PullReqActivityMetadataUpdate,
|
||||
comment string,
|
||||
) ([]types.PullReqActivityMetadataUpdate, map[int64]*types.PrincipalInfo, error) {
|
||||
principalInfos, err := c.processMentions(ctx, comment)
|
||||
if err != nil {
|
||||
return nil, map[int64]*types.PrincipalInfo{}, err
|
||||
}
|
||||
|
||||
ids := make([]int64, len(principalInfos))
|
||||
i := 0
|
||||
for id := range principalInfos {
|
||||
ids[i] = id
|
||||
i++
|
||||
}
|
||||
|
||||
return append(
|
||||
updates,
|
||||
types.WithPullReqActivityMentionsMetadataUpdate(
|
||||
func(m *types.PullReqActivityMentionsMetadata) {
|
||||
m.IDs = ids
|
||||
}),
|
||||
), principalInfos, nil
|
||||
}
|
||||
|
|
|
@ -83,6 +83,13 @@ func (c *Controller) CommentUpdate(
|
|||
// generate all metadata updates
|
||||
var metadataUpdates []types.PullReqActivityMetadataUpdate
|
||||
|
||||
metadataUpdates, principalInfos, err := c.appendMetadataUpdateForMentions(
|
||||
ctx, metadataUpdates, in.Text,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update metadata for mentions: %w", err)
|
||||
}
|
||||
|
||||
// suggestion metadata in case of code comments or code comment replies (don't restrict to either side for now).
|
||||
if act.IsValidCodeComment() || (act.IsReply() && parentAct.IsValidCodeComment()) {
|
||||
metadataUpdates = appendMetadataUpdateForSuggestions(metadataUpdates, in.Text)
|
||||
|
@ -99,6 +106,9 @@ func (c *Controller) CommentUpdate(
|
|||
return nil, fmt.Errorf("failed to update comment: %w", err)
|
||||
}
|
||||
|
||||
// Populate activity mentions (used only for response purposes).
|
||||
act.Mentions = principalInfos
|
||||
|
||||
if err = c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypePullRequestUpdated, pr); err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msg("failed to publish PR changed event")
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ type Controller struct {
|
|||
reviewerStore store.PullReqReviewerStore
|
||||
repoStore store.RepoStore
|
||||
principalStore store.PrincipalStore
|
||||
principalInfoCache store.PrincipalInfoCache
|
||||
fileViewStore store.PullReqFileViewStore
|
||||
membershipStore store.MembershipStore
|
||||
checkStore store.CheckStore
|
||||
|
@ -74,6 +75,7 @@ func NewController(
|
|||
pullreqReviewerStore store.PullReqReviewerStore,
|
||||
repoStore store.RepoStore,
|
||||
principalStore store.PrincipalStore,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
fileViewStore store.PullReqFileViewStore,
|
||||
membershipStore store.MembershipStore,
|
||||
checkStore store.CheckStore,
|
||||
|
@ -97,6 +99,7 @@ func NewController(
|
|||
reviewerStore: pullreqReviewerStore,
|
||||
repoStore: repoStore,
|
||||
principalStore: principalStore,
|
||||
principalInfoCache: principalInfoCache,
|
||||
fileViewStore: fileViewStore,
|
||||
membershipStore: membershipStore,
|
||||
checkStore: checkStore,
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pullreq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (c *Controller) processMentions(
|
||||
ctx context.Context,
|
||||
text string,
|
||||
) (map[int64]*types.PrincipalInfo, error) {
|
||||
mentions := parseMentions(ctx, text)
|
||||
if len(mentions) == 0 {
|
||||
return map[int64]*types.PrincipalInfo{}, nil
|
||||
}
|
||||
|
||||
infos, err := c.principalInfoCache.Map(ctx, mentions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch info from principalInfoCache: %w", err)
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
var mentionRegex = regexp.MustCompile(`@\[(\d+)\]`)
|
||||
|
||||
func parseMentions(ctx context.Context, text string) []int64 {
|
||||
matches := mentionRegex.FindAllStringSubmatch(text, -1)
|
||||
|
||||
var mentions []int64
|
||||
for _, match := range matches {
|
||||
if len(match) < 2 {
|
||||
continue
|
||||
}
|
||||
if mention, err := strconv.ParseInt(match[1], 10, 64); err == nil {
|
||||
mentions = append(mentions, mention)
|
||||
} else {
|
||||
log.Ctx(ctx).Warn().Err(err).Msgf("failed to parse mention %q", match[1])
|
||||
}
|
||||
}
|
||||
|
||||
return mentions
|
||||
}
|
|
@ -40,7 +40,7 @@ func ProvideController(tx dbtx.Transactor, urlProvider url.Provider, authorizer
|
|||
pullReqStore store.PullReqStore, pullReqActivityStore store.PullReqActivityStore,
|
||||
codeCommentsView store.CodeCommentView,
|
||||
pullReqReviewStore store.PullReqReviewStore, pullReqReviewerStore store.PullReqReviewerStore,
|
||||
repoStore store.RepoStore, principalStore store.PrincipalStore,
|
||||
repoStore store.RepoStore, principalStore store.PrincipalStore, principalInfoCache store.PrincipalInfoCache,
|
||||
fileViewStore store.PullReqFileViewStore, membershipStore store.MembershipStore,
|
||||
checkStore store.CheckStore,
|
||||
rpcClient git.Interface, eventReporter *pullreqevents.Reporter, codeCommentMigrator *codecomments.Migrator,
|
||||
|
@ -51,7 +51,7 @@ func ProvideController(tx dbtx.Transactor, urlProvider url.Provider, authorizer
|
|||
pullReqStore, pullReqActivityStore,
|
||||
codeCommentsView,
|
||||
pullReqReviewStore, pullReqReviewerStore,
|
||||
repoStore, principalStore,
|
||||
repoStore, principalStore, principalInfoCache,
|
||||
fileViewStore, membershipStore,
|
||||
checkStore,
|
||||
rpcClient, eventReporter,
|
||||
|
|
|
@ -17,14 +17,10 @@ package notification
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||
"github.com/harness/gitness/events"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type CommentPayload struct {
|
||||
|
@ -125,7 +121,7 @@ func (s *Service) processCommentCreatedEvent(
|
|||
seen[commenter.ID] = true
|
||||
|
||||
// process mentions
|
||||
mentions, err = s.processMentions(ctx, activity.Text, seen)
|
||||
mentions, err = s.processMentions(ctx, activity.Metadata, seen)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
@ -147,29 +143,27 @@ func (s *Service) processCommentCreatedEvent(
|
|||
|
||||
func (s *Service) processMentions(
|
||||
ctx context.Context,
|
||||
text string,
|
||||
metadata *types.PullReqActivityMetadata,
|
||||
seen map[int64]bool,
|
||||
) ([]*types.PrincipalInfo, error) {
|
||||
var mentions []*types.PrincipalInfo
|
||||
|
||||
commentMentions := parseMentions(ctx, text)
|
||||
if len(commentMentions) == 0 {
|
||||
if metadata == nil || metadata.Mentions == nil {
|
||||
return []*types.PrincipalInfo{}, nil
|
||||
}
|
||||
|
||||
var mentionIDs []int64
|
||||
for _, mentionID := range commentMentions {
|
||||
if !seen[mentionID] {
|
||||
mentionIDs = append(mentionIDs, mentionID)
|
||||
seen[mentionID] = true
|
||||
var ids []int64
|
||||
for _, id := range metadata.Mentions.IDs {
|
||||
if !seen[id] {
|
||||
ids = append(ids, id)
|
||||
seen[id] = true
|
||||
}
|
||||
}
|
||||
if len(mentionIDs) > 0 {
|
||||
var err error
|
||||
mentions, err = s.principalInfoView.FindMany(ctx, mentionIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch thread mentions from principalInfoView: %w", err)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return []*types.PrincipalInfo{}, nil
|
||||
}
|
||||
|
||||
mentions, err := s.principalInfoView.FindMany(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch thread mentions from principalInfoView: %w", err)
|
||||
}
|
||||
|
||||
return mentions, nil
|
||||
|
@ -213,23 +207,3 @@ func (s *Service) processParticipants(
|
|||
|
||||
return participants, nil
|
||||
}
|
||||
|
||||
var mentionRegex = regexp.MustCompile(`@\[(\d+)\]`)
|
||||
|
||||
func parseMentions(ctx context.Context, text string) []int64 {
|
||||
matches := mentionRegex.FindAllStringSubmatch(text, -1)
|
||||
|
||||
var mentions []int64
|
||||
for _, match := range matches {
|
||||
if len(match) < 2 {
|
||||
continue
|
||||
}
|
||||
if mention, err := strconv.ParseInt(match[1], 10, 64); err == nil {
|
||||
mentions = append(mentions, mention)
|
||||
} else {
|
||||
log.Ctx(ctx).Warn().Err(err).Msgf("failed to parse mention %q", match[1])
|
||||
}
|
||||
}
|
||||
|
||||
return mentions
|
||||
}
|
||||
|
|
|
@ -257,7 +257,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, pullReqFileViewStore, membershipStore, checkStore, gitInterface, eventsReporter, migrator, pullreqService, protectionManager, streamer, codeownersService, lockerLocker)
|
||||
pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, principalInfoCache, pullReqFileViewStore, membershipStore, checkStore, gitInterface, eventsReporter, migrator, pullreqService, protectionManager, streamer, codeownersService, lockerLocker)
|
||||
webhookConfig := server.ProvideWebhookConfig(config)
|
||||
webhookStore := database.ProvideWebhookStore(db)
|
||||
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
|
||||
|
|
|
@ -55,6 +55,8 @@ type PullReqActivity struct {
|
|||
Resolver *PrincipalInfo `json:"resolver,omitempty"`
|
||||
|
||||
CodeComment *CodeCommentFields `json:"code_comment,omitempty"`
|
||||
|
||||
Mentions map[int64]*PrincipalInfo `json:"mentions,omitempty"` // used only in response
|
||||
}
|
||||
|
||||
func (a *PullReqActivity) IsValidCodeComment() bool {
|
||||
|
|
|
@ -17,6 +17,7 @@ package types
|
|||
// PullReqActivityMetadata contains metadata related to pull request activity.
|
||||
type PullReqActivityMetadata struct {
|
||||
Suggestions *PullReqActivitySuggestionsMetadata `json:"suggestions,omitempty"`
|
||||
Mentions *PullReqActivityMentionsMetadata `json:"mentions,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PullReqActivityMetadata) IsEmpty() bool {
|
||||
|
@ -64,3 +65,28 @@ func WithPullReqActivitySuggestionsMetadataUpdate(
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PullReqActivityMentionsMetadata contains metadata for code comment mentions.
|
||||
type PullReqActivityMentionsMetadata struct {
|
||||
IDs []int64 `json:"ids,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PullReqActivityMentionsMetadata) IsEmpty() bool {
|
||||
return len(m.IDs) == 0
|
||||
}
|
||||
|
||||
func WithPullReqActivityMentionsMetadataUpdate(
|
||||
f func(m *PullReqActivityMentionsMetadata),
|
||||
) PullReqActivityMetadataUpdate {
|
||||
return pullReqActivityMetadataUpdateFunc(func(m *PullReqActivityMetadata) {
|
||||
if m.Mentions == nil {
|
||||
m.Mentions = &PullReqActivityMentionsMetadata{}
|
||||
}
|
||||
|
||||
f(m.Mentions)
|
||||
|
||||
if m.Mentions.IsEmpty() {
|
||||
m.Mentions = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue