Skip to content

Commit 9813eb0

Browse files
authored
fix: precompute project group merge suggestion counts (CM-1230) (#4250)
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
1 parent e12ad28 commit 9813eb0

10 files changed

Lines changed: 610 additions & 154 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
create table "segmentMergeSuggestionCounts" (
2+
"segmentId" uuid primary key references "segments" ("id") on delete cascade,
3+
"memberMergeSuggestionsCount" integer not null default 0,
4+
"organizationMergeSuggestionsCount" integer not null default 0,
5+
"updatedAt" timestamp with time zone default now() not null
6+
);

backend/src/database/repositories/memberRepository.ts

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ import {
5151
} from '@crowd/data-access-layer/src/members/segments'
5252
import { IDbMemberData } from '@crowd/data-access-layer/src/members/types'
5353
import { optionsQx } from '@crowd/data-access-layer/src/queryExecutor'
54-
import { fetchManySegments, getSegmentSubprojectIds } from '@crowd/data-access-layer/src/segments'
54+
import {
55+
fetchManySegments,
56+
getSegmentMergeSuggestionCounts,
57+
getSegmentSubprojectIds,
58+
} from '@crowd/data-access-layer/src/segments'
5559
import { ActivityDisplayService } from '@crowd/integrations'
5660
import {
5761
ALL_PLATFORM_TYPES,
@@ -63,6 +67,8 @@ import {
6367
MemberIdentityType,
6468
MemberSegmentAffiliation,
6569
MemberSegmentAffiliationJoined,
70+
MergeActionState,
71+
MergeActionType,
6672
PlatformType,
6773
SegmentType,
6874
TemporalWorkflowId,
@@ -277,13 +283,27 @@ class MemberRepository {
277283
AND EXISTS (
278284
SELECT 1 FROM "memberSegmentsAgg" ms2
279285
WHERE ms2."memberId" = mtm."toMergeId" AND ms2."segmentId" IN (:segmentIds)
286+
)
287+
AND NOT EXISTS (
288+
SELECT 1
289+
FROM "mergeActions" ma
290+
WHERE ma.type = :mergeActionType
291+
AND ma.state <> :mergeActionState
292+
AND (
293+
(ma."primaryId" = mtm."memberId" AND ma."secondaryId" = mtm."toMergeId")
294+
OR (ma."primaryId" = mtm."toMergeId" AND ma."secondaryId" = mtm."memberId")
295+
)
280296
)
281297
${memberFilter}
282298
${similarityFilter}
283299
${displayNameFilter}
284300
`,
285301
{
286-
replacements,
302+
replacements: {
303+
...replacements,
304+
mergeActionType: MergeActionType.MEMBER,
305+
mergeActionState: MergeActionState.ERROR,
306+
},
287307
type: QueryTypes.SELECT,
288308
},
289309
)
@@ -299,18 +319,16 @@ class MemberRepository {
299319
const MEDIUM_CONFIDENCE_LOWER_BOUND = 0.7
300320

301321
// Member segments are aggregated at each hierarchy level (group -> project -> subproject).
302-
// Match the selected segment ID directly; do not expand to leaf subprojects.
303-
const segmentIds = SequelizeRepository.getSegmentIds(options)
304-
305-
if (segmentIds.length === 0) {
306-
return args.countOnly
307-
? { count: '0' }
308-
: {
309-
rows: [{ members: [], similarity: 0 }],
310-
count: 0,
311-
limit: args.limit,
312-
offset: args.offset,
313-
}
322+
const projectGroupSegment = SequelizeRepository.getStrictlySingleProjectGroupSegment(options)
323+
324+
let segmentIds: string[]
325+
326+
if (args.filter?.projectIds?.length) {
327+
segmentIds = args.filter.projectIds
328+
} else if (args.filter?.subprojectIds?.length) {
329+
segmentIds = args.filter.subprojectIds
330+
} else {
331+
segmentIds = [projectGroupSegment.id]
314332
}
315333

316334
let similarityFilter = ''
@@ -357,8 +375,25 @@ class MemberRepository {
357375
order += 'mtm."memberId", mtm."toMergeId"'
358376
}
359377

360-
if (args.countOnly) {
361-
const totalCount = await this.countMemberMergeSuggestions(
378+
const hasProjectFilter = Boolean(
379+
args.filter?.projectIds?.length || args.filter?.subprojectIds?.length,
380+
)
381+
382+
const hasCountFilters = Boolean(
383+
args.filter?.memberId || args.filter?.displayName || args.filter?.similarity?.length,
384+
)
385+
386+
const getTotalCount = async (): Promise<number> => {
387+
if (!hasCountFilters && !hasProjectFilter) {
388+
const counts = await getSegmentMergeSuggestionCounts(
389+
SequelizeRepository.getQueryExecutor(options),
390+
projectGroupSegment.id,
391+
)
392+
393+
return counts?.memberMergeSuggestionsCount ?? 0
394+
}
395+
396+
return this.countMemberMergeSuggestions(
362397
memberFilter,
363398
similarityFilter,
364399
displayNameFilter,
@@ -369,8 +404,10 @@ class MemberRepository {
369404
},
370405
options,
371406
)
407+
}
372408

373-
return { count: totalCount }
409+
if (args.countOnly) {
410+
return { count: await getTotalCount() }
374411
}
375412

376413
const mems = await options.database.sequelize.query(
@@ -395,7 +432,16 @@ class MemberRepository {
395432
SELECT 1 FROM "memberSegmentsAgg" ms2
396433
WHERE ms2."memberId" = mtm."toMergeId" AND ms2."segmentId" IN (:segmentIds)
397434
)
398-
AND mtm.similarity IS NOT NULL
435+
AND NOT EXISTS (
436+
SELECT 1
437+
FROM "mergeActions" ma
438+
WHERE ma.type = :mergeActionType
439+
AND ma.state <> :mergeActionState
440+
AND (
441+
(ma."primaryId" = mtm."memberId" AND ma."secondaryId" = mtm."toMergeId")
442+
OR (ma."primaryId" = mtm."toMergeId" AND ma."secondaryId" = mtm."memberId")
443+
)
444+
)
399445
${memberFilter}
400446
${similarityFilter}
401447
${displayNameFilter}
@@ -410,6 +456,8 @@ class MemberRepository {
410456
offset: args.offset,
411457
displayName: args?.filter?.displayName ? `${args.filter.displayName}%` : undefined,
412458
memberId: args?.filter?.memberId,
459+
mergeActionType: MergeActionType.MEMBER,
460+
mergeActionState: MergeActionState.ERROR,
413461
},
414462
type: QueryTypes.SELECT,
415463
},
@@ -506,24 +554,12 @@ class MemberRepository {
506554
}))
507555
}
508556

509-
const totalCount = await this.countMemberMergeSuggestions(
510-
memberFilter,
511-
similarityFilter,
512-
displayNameFilter,
513-
{
514-
segmentIds,
515-
memberId: args?.filter?.memberId,
516-
displayName: args?.filter?.displayName ? `${args.filter.displayName}%` : undefined,
517-
},
518-
options,
519-
)
520-
521-
return { rows: result, count: totalCount, limit: args.limit, offset: args.offset }
557+
return { rows: result, count: await getTotalCount(), limit: args.limit, offset: args.offset }
522558
}
523559

524560
return {
525561
rows: [{ members: [], similarity: 0 }],
526-
count: 0,
562+
count: await getTotalCount(),
527563
limit: args.limit,
528564
offset: args.offset,
529565
}

0 commit comments

Comments
 (0)