From f179e89c0c18dce172813c275734a888acc3af57 Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:29:31 +0200 Subject: [PATCH] [web] add analytics for @mentions (#36144) GitOrigin-RevId: 9fb1447166eb5ee82867466abc9af2207c886f8b --- .../review-panel/context/threads-context.tsx | 12 ++++++-- .../js/shared/utils/parse-mentions.ts | 10 +++++++ .../shared/utils/parse-mentions.test.ts | 28 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/features/review-panel/context/threads-context.tsx b/services/web/frontend/js/features/review-panel/context/threads-context.tsx index b3a5d784df..a12f740110 100644 --- a/services/web/frontend/js/features/review-panel/context/threads-context.tsx +++ b/services/web/frontend/js/features/review-panel/context/threads-context.tsx @@ -35,6 +35,7 @@ import { useCodeMirrorViewContext } from '@/features/source-editor/components/co import { rangesUpdatedEffect } from '@/features/source-editor/extensions/history-ot' import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' import { useReviewPanelViewContext } from './review-panel-view-context' +import { countMentionedUsers } from '@/shared/utils/parse-mentions' export type Threads = Record @@ -279,7 +280,10 @@ export const ThreadsProvider: FC = ({ children }) => { body: { content }, }) - sendEvent('rp-new-comment', { size: content.length }) + sendEvent('rp-new-comment', { + size: content.length, + 'mentions-count': countMentionedUsers(content), + }) const op: CommentOperation = { c: text, @@ -317,6 +321,7 @@ export const ThreadsProvider: FC = ({ children }) => { view: reviewPanelView, size: content.length, thread: threadId, + 'mentions-count': countMentionedUsers(content), }) }, async editMessage( @@ -351,7 +356,10 @@ export const ThreadsProvider: FC = ({ children }) => { body: { content }, }) - sendEvent('rp-new-comment', { size: content.length }) + sendEvent('rp-new-comment', { + size: content.length, + 'mentions-count': countMentionedUsers(content), + }) const trackedDeletes = trackedDeletesFromState(view.state) pos = trackedDeletes.toSnapshot(pos) diff --git a/services/web/frontend/js/shared/utils/parse-mentions.ts b/services/web/frontend/js/shared/utils/parse-mentions.ts index 85249cbcab..70df4df833 100644 --- a/services/web/frontend/js/shared/utils/parse-mentions.ts +++ b/services/web/frontend/js/shared/utils/parse-mentions.ts @@ -29,6 +29,16 @@ export function parseMentions(content: string): MentionSegment[] { return segments } +export function countMentionedUsers(content: string): number { + const userIds = new Set() + + for (const match of content.matchAll(MENTION_REGEX)) { + userIds.add(match[1]) + } + + return userIds.size +} + const MENTION_RAW_LENGTH = 27 // @[ + 24 hex chars + ] export function sliceMentionSegments( diff --git a/services/web/test/frontend/shared/utils/parse-mentions.test.ts b/services/web/test/frontend/shared/utils/parse-mentions.test.ts index 1f63cefcb1..a2fc9f3fce 100644 --- a/services/web/test/frontend/shared/utils/parse-mentions.test.ts +++ b/services/web/test/frontend/shared/utils/parse-mentions.test.ts @@ -1,5 +1,6 @@ import { expect } from 'chai' import { + countMentionedUsers, parseMentions, sliceMentionSegments, } from '@/shared/utils/parse-mentions' @@ -76,6 +77,33 @@ describe('parseMentions', function () { }) }) +describe('countMentionedUsers', function () { + const userId1 = 'aabbccddeeff00112233aabb' as UserId + const userId2 = '112233445566778899aabbcc' as UserId + + it('should return 0 for content without mentions', function () { + expect(countMentionedUsers('hello world')).to.equal(0) + }) + + it('should count a single mention', function () { + expect(countMentionedUsers(`hey @[${userId1}]`)).to.equal(1) + }) + + it('should count distinct mentioned users', function () { + expect(countMentionedUsers(`@[${userId1}] and @[${userId2}]`)).to.equal(2) + }) + + it('should count a repeated user only once', function () { + expect(countMentionedUsers(`@[${userId1}] @[${userId1}]`)).to.equal(1) + }) + + it('should ignore malformed mentions', function () { + expect(countMentionedUsers('@[aabbcc] @aabbccddeeff00112233aabb')).to.equal( + 0 + ) + }) +}) + describe('sliceMentionSegments', function () { const userId = 'aabbccddeeff00112233aabb' as UserId