From 454eb76098176941d2faa251ddffbe37ea7cffaf Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Wed, 17 Jul 2024 16:28:59 +0530
Subject: [PATCH 01/16] todo
---
services/libs/data-access-layer/src/mergeActions/repo.ts | 1 +
1 file changed, 1 insertion(+)
create mode 100644 services/libs/data-access-layer/src/mergeActions/repo.ts
diff --git a/services/libs/data-access-layer/src/mergeActions/repo.ts b/services/libs/data-access-layer/src/mergeActions/repo.ts
new file mode 100644
index 0000000000..ebdda79f60
--- /dev/null
+++ b/services/libs/data-access-layer/src/mergeActions/repo.ts
@@ -0,0 +1 @@
+// todo: mergeActions repo
From ade1eeab9ec3215cc434a72523114e7f8a8875a9 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 19 Jul 2024 13:10:25 +0530
Subject: [PATCH 02/16] Add step in mergeAction to track operation
---
.../scripts/merge-similar-organizations.ts | 7 +-
.../U1721311363__addStepToMergeActions.sql | 1 +
.../V1721311363__addStepToMergeActions.sql | 2 +
.../repositories/mergeActionsRepository.ts | 67 +++++++++++++++++--
backend/src/services/memberService.ts | 33 +++++++++
backend/src/services/organizationService.ts | 33 +++++++--
.../entity_merging_worker/src/activities.ts | 2 +-
.../src/activities/common.ts | 8 +--
.../src/workflows/all.ts | 37 ++++++++--
.../old/apps/entity_merging_worker/index.ts | 32 ++++++---
services/libs/types/src/enums/merging.ts | 12 ++++
services/libs/types/src/merging.ts | 2 +
12 files changed, 202 insertions(+), 34 deletions(-)
create mode 100644 backend/src/database/migrations/U1721311363__addStepToMergeActions.sql
create mode 100644 backend/src/database/migrations/V1721311363__addStepToMergeActions.sql
diff --git a/backend/src/bin/scripts/merge-similar-organizations.ts b/backend/src/bin/scripts/merge-similar-organizations.ts
index 61b7dc4268..7fb2dbe746 100644
--- a/backend/src/bin/scripts/merge-similar-organizations.ts
+++ b/backend/src/bin/scripts/merge-similar-organizations.ts
@@ -145,16 +145,19 @@ if (parameters.help || (!parameters.tenant && !parameters.allTenants)) {
row.organizationId,
row.toMergeId,
userContext,
+ undefined
)
await orgService.mergeSync(row.organizationId, row.toMergeId, null)
} catch (err) {
console.log('Error merging organizations - continuing with the rest', err)
- await MergeActionsRepository.setState(
+ await MergeActionsRepository.update(
MergeActionType.ORG,
row.organizationId,
row.toMergeId,
- MergeActionState.ERROR,
userContext,
+ {
+ state: MergeActionState.ERROR,
+ }
)
}
diff --git a/backend/src/database/migrations/U1721311363__addStepToMergeActions.sql b/backend/src/database/migrations/U1721311363__addStepToMergeActions.sql
new file mode 100644
index 0000000000..c5812669d5
--- /dev/null
+++ b/backend/src/database/migrations/U1721311363__addStepToMergeActions.sql
@@ -0,0 +1 @@
+alter table "mergeActions" drop column "step";
\ No newline at end of file
diff --git a/backend/src/database/migrations/V1721311363__addStepToMergeActions.sql b/backend/src/database/migrations/V1721311363__addStepToMergeActions.sql
new file mode 100644
index 0000000000..1bce0447f2
--- /dev/null
+++ b/backend/src/database/migrations/V1721311363__addStepToMergeActions.sql
@@ -0,0 +1,2 @@
+alter table "mergeActions" add column "step" text;
+
diff --git a/backend/src/database/repositories/mergeActionsRepository.ts b/backend/src/database/repositories/mergeActionsRepository.ts
index 3bb0b332d0..7cb57f37ab 100644
--- a/backend/src/database/repositories/mergeActionsRepository.ts
+++ b/backend/src/database/repositories/mergeActionsRepository.ts
@@ -8,6 +8,7 @@ import {
IUnmergeBackup,
MemberIdentityType,
MergeActionState,
+ MergeActionStep,
MergeActionType,
OrganizationIdentityType,
} from '@crowd/types'
@@ -20,6 +21,7 @@ class MergeActionsRepository {
primaryId: string,
secondaryId: string,
options: IRepositoryOptions,
+ step: MergeActionStep,
state: MergeActionState = MergeActionState.PENDING,
backup: IUnmergeBackup = undefined,
) {
@@ -29,8 +31,8 @@ class MergeActionsRepository {
await options.database.sequelize.query(
`
- INSERT INTO "mergeActions" ("tenantId", "type", "primaryId", "secondaryId", state, "unmergeBackup", "actionBy")
- VALUES (:tenantId, :type, :primaryId, :secondaryId, :state, :backup, :userId)
+ INSERT INTO "mergeActions" ("tenantId", "type", "primaryId", "secondaryId", state, step, "unmergeBackup", "actionBy")
+ VALUES (:tenantId, :type, :primaryId, :secondaryId, :state, :step, :backup, :userId)
ON CONFLICT ("tenantId", "type", "primaryId", "secondaryId")
DO UPDATE SET state = :state, "unmergeBackup" = :backup
`,
@@ -40,6 +42,7 @@ class MergeActionsRepository {
type,
primaryId,
secondaryId,
+ step,
state,
backup: backup ? JSON.stringify(backup) : null,
userId,
@@ -50,11 +53,62 @@ class MergeActionsRepository {
)
}
- static async setState(
+ static async setMergeAction(
type: MergeActionType,
primaryId: string,
secondaryId: string,
- state: MergeActionState,
+ options: IRepositoryOptions,
+ data: {
+ step?: MergeActionStep,
+ state?: MergeActionState,
+ }
+ ) {
+ const transaction = SequelizeRepository.getTransaction(options)
+ const tenantId = options.currentTenant.id
+
+ const setClauses = []
+ const replacements: any = {
+ tenantId,
+ type,
+ primaryId,
+ secondaryId,
+ }
+
+ if (data.step) {
+ setClauses.push(`step = :step`)
+ replacements.step = data.step
+ }
+
+ if (data.state) {
+ setClauses.push(`state = :state`)
+ replacements.state = data.state
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_, rowCount] = await options.database.sequelize.query(
+ `
+ UPDATE "mergeActions"
+ SET ${setClauses.join(', ')}
+ WHERE "tenantId" = :tenantId
+ AND type = :type
+ AND "primaryId" = :primaryId
+ AND "secondaryId" = :secondaryId
+ `,
+ {
+ replacements,
+ type: QueryTypes.UPDATE,
+ transaction,
+ },
+ )
+
+ return rowCount > 0
+ }
+
+ static async setStep(
+ type: MergeActionType,
+ primaryId: string,
+ secondaryId: string,
+ step: MergeActionStep,
options: IRepositoryOptions,
) {
const transaction = SequelizeRepository.getTransaction(options)
@@ -64,12 +118,11 @@ class MergeActionsRepository {
const [_, rowCount] = await options.database.sequelize.query(
`
UPDATE "mergeActions"
- SET state = :state
+ SET step = :step
WHERE "tenantId" = :tenantId
AND type = :type
AND "primaryId" = :primaryId
AND "secondaryId" = :secondaryId
- AND state != :state
`,
{
replacements: {
@@ -77,7 +130,7 @@ class MergeActionsRepository {
type,
primaryId,
secondaryId,
- state,
+ step,
},
type: QueryTypes.UPDATE,
transaction,
diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts
index cef6f97e38..e8076097b9 100644
--- a/backend/src/services/memberService.ts
+++ b/backend/src/services/memberService.ts
@@ -25,6 +25,7 @@ import {
MemberRoleUnmergeStrategy,
OrganizationIdentityType,
IMemberRoleWithOrganization,
+ MergeActionStep,
} from '@crowd/types'
import { randomUUID } from 'crypto'
import lodash from 'lodash'
@@ -697,6 +698,17 @@ export default class MemberService extends LoggerBase {
// create the secondary member
const secondaryMember = await MemberRepository.create(payload.secondary, repoOptions)
+
+ // track merge action
+ await MergeActionsRepository.add(
+ MergeActionType.MEMBER,
+ member.id,
+ secondaryMember.id,
+ repoOptions,
+ MergeActionStep.UNMERGE_STARTED,
+ MergeActionState.IN_PROGRESS
+ )
+
// move affiliations
if (payload.secondary.affiliations.length > 0) {
await MemberRepository.moveSelectedAffiliationsBetweenMembers(
@@ -823,6 +835,16 @@ export default class MemberService extends LoggerBase {
// trigger entity-merging-worker to move activities in the background
await SequelizeRepository.commitTransaction(tx)
+ await MergeActionsRepository.setMergeAction(
+ MergeActionType.MEMBER,
+ member.id,
+ secondaryMember.id,
+ repoOptions,
+ {
+ step: MergeActionStep.UNMERGE_SYNC_DONE,
+ }
+ )
+
// responsible for moving member's activities, syncing to opensearch afterwards, recalculating activity.organizationIds and notifying frontend via websockets
await this.options.temporal.workflow.start('finishMemberUnmerging', {
taskQueue: 'entity-merging',
@@ -1287,6 +1309,7 @@ export default class MemberService extends LoggerBase {
originalId,
toMergeId,
this.options,
+ MergeActionStep.MERGE_STARTED,
MergeActionState.IN_PROGRESS,
backup,
)
@@ -1365,6 +1388,16 @@ export default class MemberService extends LoggerBase {
}),
)
+ await MergeActionsRepository.setMergeAction(
+ MergeActionType.MEMBER,
+ originalId,
+ toMergeId,
+ this.options,
+ {
+ step: MergeActionStep.MERGE_SYNC_DONE,
+ }
+ )
+
await this.options.temporal.workflow.start('finishMemberMerging', {
taskQueue: 'entity-merging',
workflowId: `finishMemberMerging/${originalId}/${toMergeId}`,
diff --git a/backend/src/services/organizationService.ts b/backend/src/services/organizationService.ts
index e239dcd9a4..ad335a8be9 100644
--- a/backend/src/services/organizationService.ts
+++ b/backend/src/services/organizationService.ts
@@ -11,6 +11,7 @@ import {
IUnmergePreviewResult,
MemberRoleUnmergeStrategy,
MergeActionState,
+ MergeActionStep,
MergeActionType,
OrganizationIdentityType,
SyncMode,
@@ -230,6 +231,15 @@ export default class OrganizationService extends LoggerBase {
repoOptions,
)
+ await MergeActionsRepository.add(
+ MergeActionType.ORG,
+ organizationId,
+ secondaryOrganization.id,
+ this.options,
+ MergeActionStep.UNMERGE_STARTED,
+ MergeActionState.IN_PROGRESS,
+ )
+
if (payload.mergeActionId) {
const mergeAction = await MergeActionsRepository.findById(
payload.mergeActionId,
@@ -293,6 +303,16 @@ export default class OrganizationService extends LoggerBase {
// trigger entity-merging-worker to move activities in the background
await SequelizeRepository.commitTransaction(tx)
+ await MergeActionsRepository.setMergeAction(
+ MergeActionType.ORG,
+ organizationId,
+ secondaryOrganization.id,
+ this.options,
+ {
+ step: MergeActionStep.UNMERGE_SYNC_DONE,
+ }
+ )
+
// responsible for moving organization's activities, syncing to opensearch afterwards, recalculating activity.organizationIds and notifying frontend via websockets
await this.options.temporal.workflow.start('finishOrganizationUnmerging', {
taskQueue: 'entity-merging',
@@ -414,6 +434,7 @@ export default class OrganizationService extends LoggerBase {
// not using transaction here on purpose,
// so this change is visible until we finish
this.options,
+ MergeActionStep.MERGE_STARTED,
MergeActionState.IN_PROGRESS,
backup,
)
@@ -552,12 +573,14 @@ export default class OrganizationService extends LoggerBase {
this.log.info({ originalId, toMergeId }, '[Merge Organizations] - Transaction commited! ')
- await MergeActionsRepository.setState(
+ await MergeActionsRepository.setMergeAction(
MergeActionType.ORG,
originalId,
toMergeId,
- MergeActionState.FINISHING,
this.options,
+ {
+ step: MergeActionStep.MERGE_SYNC_DONE,
+ }
)
return { original, toMerge }
@@ -615,12 +638,14 @@ export default class OrganizationService extends LoggerBase {
toMergeId,
})
- await MergeActionsRepository.setState(
+ await MergeActionsRepository.setMergeAction(
MergeActionType.ORG,
originalId,
toMergeId,
- MergeActionState.ERROR,
this.options,
+ {
+ state: MergeActionState.ERROR,
+ }
)
if (tx) {
diff --git a/services/apps/entity_merging_worker/src/activities.ts b/services/apps/entity_merging_worker/src/activities.ts
index cb454e41b4..d34f34797d 100644
--- a/services/apps/entity_merging_worker/src/activities.ts
+++ b/services/apps/entity_merging_worker/src/activities.ts
@@ -18,4 +18,4 @@ export {
recalculateActivityAffiliationsOfOrganizationSynchronous,
} from './activities/organizations'
-export { setMergeActionState } from './activities/common'
+export { setMergeAction } from './activities/common'
diff --git a/services/apps/entity_merging_worker/src/activities/common.ts b/services/apps/entity_merging_worker/src/activities/common.ts
index 701c06cad4..eee7ff225f 100644
--- a/services/apps/entity_merging_worker/src/activities/common.ts
+++ b/services/apps/entity_merging_worker/src/activities/common.ts
@@ -1,12 +1,12 @@
-import { MergeActionState } from '@crowd/types'
+import { MergeActionState, MergeActionStep } from '@crowd/types'
import { svc } from '../main'
import { updateMergeActionState } from '@crowd/data-access-layer/src/old/apps/entity_merging_worker'
-export async function setMergeActionState(
+export async function setMergeAction(
primaryId: string,
secondaryId: string,
tenantId: string,
- state: MergeActionState,
+ data: { state?: MergeActionState; step?: MergeActionStep },
): Promise {
- await updateMergeActionState(svc.postgres.writer, primaryId, secondaryId, tenantId, state)
+ await updateMergeActionState(svc.postgres.writer, primaryId, secondaryId, tenantId, data)
}
diff --git a/services/apps/entity_merging_worker/src/workflows/all.ts b/services/apps/entity_merging_worker/src/workflows/all.ts
index d3b3e34e5f..16572e7ee8 100644
--- a/services/apps/entity_merging_worker/src/workflows/all.ts
+++ b/services/apps/entity_merging_worker/src/workflows/all.ts
@@ -1,7 +1,7 @@
import { proxyActivities } from '@temporalio/workflow'
import * as activities from '../activities'
-import { IMemberIdentity, MergeActionState } from '@crowd/types'
+import { IMemberIdentity, MergeActionState, MergeActionStep } from '@crowd/types'
const {
deleteMember,
@@ -13,7 +13,7 @@ const {
moveActivitiesWithIdentityToAnotherMember,
recalculateActivityAffiliationsOfMemberAsync,
recalculateActivityAffiliationsOfOrganizationSynchronous,
- setMergeActionState,
+ setMergeAction,
syncMember,
syncOrganization,
notifyFrontendMemberMergeSuccessful,
@@ -31,12 +31,18 @@ export async function finishMemberMerging(
tenantId: string,
userId: string,
): Promise {
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ step: MergeActionStep.MERGE_ASYNC_STARTED,
+ })
await moveActivitiesBetweenMembers(primaryId, secondaryId, tenantId)
await recalculateActivityAffiliationsOfMemberAsync(primaryId, tenantId)
await syncMember(primaryId)
await syncRemoveMember(secondaryId)
await deleteMember(secondaryId)
- await setMergeActionState(primaryId, secondaryId, tenantId, 'merged' as MergeActionState)
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ state: 'merged' as MergeActionState,
+ step: MergeActionStep.MERGE_DONE,
+ })
await notifyFrontendMemberMergeSuccessful(
primaryId,
secondaryId,
@@ -56,12 +62,18 @@ export async function finishMemberUnmerging(
tenantId: string,
userId: string,
): Promise {
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ step: MergeActionStep.UNMERGE_ASYNC_STARTED,
+ })
await moveActivitiesWithIdentityToAnotherMember(primaryId, secondaryId, identities, tenantId)
await syncMember(primaryId)
await syncMember(secondaryId)
await recalculateActivityAffiliationsOfMemberAsync(primaryId, tenantId)
await recalculateActivityAffiliationsOfMemberAsync(secondaryId, tenantId)
- await setMergeActionState(primaryId, secondaryId, tenantId, 'unmerged' as MergeActionState)
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ state: 'unmerged' as MergeActionState,
+ step: MergeActionStep.UNMERGE_DONE,
+ })
await notifyFrontendMemberUnmergeSuccessful(
primaryId,
secondaryId,
@@ -80,13 +92,20 @@ export async function finishOrganizationMerging(
tenantId: string,
userId: string,
): Promise {
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ step: MergeActionStep.MERGE_ASYNC_STARTED,
+ })
+
let movedSomething = true
do {
movedSomething = await moveActivitiesBetweenOrgs(primaryId, secondaryId, tenantId)
} while (movedSomething)
await deleteOrganization(secondaryId)
- await setMergeActionState(primaryId, secondaryId, tenantId, 'merged' as MergeActionState)
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ state: 'merged' as MergeActionState,
+ step: MergeActionStep.MERGE_DONE,
+ })
await notifyFrontendOrganizationMergeSuccessful(
primaryId,
secondaryId,
@@ -105,11 +124,17 @@ export async function finishOrganizationUnmerging(
tenantId: string,
userId: string,
): Promise {
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ step: MergeActionStep.UNMERGE_ASYNC_STARTED,
+ })
await recalculateActivityAffiliationsOfOrganizationSynchronous(primaryId, tenantId)
await recalculateActivityAffiliationsOfOrganizationSynchronous(secondaryId, tenantId)
await syncOrganization(primaryId, secondaryId)
await syncOrganization(secondaryId, primaryId)
- await setMergeActionState(primaryId, secondaryId, tenantId, 'unmerged' as MergeActionState)
+ await setMergeAction(primaryId, secondaryId, tenantId, {
+ state: 'unmerged' as MergeActionState,
+ step: MergeActionStep.UNMERGE_DONE,
+ })
await notifyFrontendOrganizationUnmergeSuccessful(
primaryId,
secondaryId,
diff --git a/services/libs/data-access-layer/src/old/apps/entity_merging_worker/index.ts b/services/libs/data-access-layer/src/old/apps/entity_merging_worker/index.ts
index 5486f769e4..eddf61eb24 100644
--- a/services/libs/data-access-layer/src/old/apps/entity_merging_worker/index.ts
+++ b/services/libs/data-access-layer/src/old/apps/entity_merging_worker/index.ts
@@ -1,5 +1,5 @@
import { DbStore } from '@crowd/database'
-import { IActivityIdentity, IMemberIdentity, MergeActionState } from '@crowd/types'
+import { IActivityIdentity, IMemberIdentity, MergeActionState, MergeActionStep } from '@crowd/types'
import { ISegmentIds } from './types'
export async function deleteMemberSegments(db: DbStore, memberId: string) {
@@ -56,18 +56,30 @@ export async function updateMergeActionState(
primaryId: string,
secondaryId: string,
tenantId: string,
- state: MergeActionState,
+ data: { step?: MergeActionStep; state?: MergeActionState },
) {
+ const setClauses = []
+ const replacements = [primaryId, secondaryId, tenantId]
+
+ if (data.step) {
+ setClauses.push(`step = $${replacements.length + 1}`)
+ replacements.push(data.step)
+ }
+
+ if (data.state) {
+ setClauses.push(`state = $${replacements.length + 1}`)
+ replacements.push(data.state)
+ }
+
return db.connection().query(
`
- UPDATE "mergeActions"
- SET state = $4
- WHERE "tenantId" = $3
- AND "primaryId" = $1
- AND "secondaryId" = $2
- AND state != $4
- `,
- [primaryId, secondaryId, tenantId, state],
+ UPDATE "mergeActions"
+ SET ${setClauses.join(', ')}
+ WHERE "primaryId" = $1
+ AND "secondaryId" = $2
+ AND "tenantId" = $3
+ `,
+ replacements,
)
}
diff --git a/services/libs/types/src/enums/merging.ts b/services/libs/types/src/enums/merging.ts
index 3a63e93113..9ca2460b16 100644
--- a/services/libs/types/src/enums/merging.ts
+++ b/services/libs/types/src/enums/merging.ts
@@ -12,6 +12,18 @@ export enum MergeActionState {
ERROR = 'error',
}
+export enum MergeActionStep {
+ MERGE_STARTED = 'merge-started',
+ MERGE_SYNC_DONE = 'merge-sync-done',
+ MERGE_ASYNC_STARTED = 'merge-async-started',
+ MERGE_DONE = 'merge-done',
+
+ UNMERGE_STARTED = 'unmerge-started',
+ UNMERGE_SYNC_DONE = 'unmerge-sync-done',
+ UNMERGE_ASYNC_STARTED = 'unmerge-async-started',
+ UNMERGE_DONE = 'unmerge-done',
+}
+
export enum MemberRoleUnmergeStrategy {
SAME_MEMBER = 'same-member',
SAME_ORGANIZATION = 'same-organization',
diff --git a/services/libs/types/src/merging.ts b/services/libs/types/src/merging.ts
index 6a8ad0b7ba..3a974caf75 100644
--- a/services/libs/types/src/merging.ts
+++ b/services/libs/types/src/merging.ts
@@ -14,6 +14,7 @@ import {
ITask,
LLMSuggestionVerdictType,
MergeActionState,
+ MergeActionStep,
MergeActionType,
} from '.'
@@ -36,6 +37,7 @@ export interface IMergeAction {
secondaryId: string
createdAt: string
updatedAt: string
+ step: MergeActionStep
state: MergeActionState
unmergeBackup: IUnmergeBackup
}
From 9870b37ba73120b992fc61bc848dc53ac2268d39 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 19 Jul 2024 13:27:40 +0530
Subject: [PATCH 03/16] make prettier happi
---
backend/src/bin/scripts/merge-similar-organizations.ts | 4 ++--
backend/src/database/repositories/mergeActionsRepository.ts | 6 +++---
backend/src/services/memberService.ts | 6 +++---
backend/src/services/organizationService.ts | 6 +++---
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/backend/src/bin/scripts/merge-similar-organizations.ts b/backend/src/bin/scripts/merge-similar-organizations.ts
index 7fb2dbe746..3fe7e1f0e5 100644
--- a/backend/src/bin/scripts/merge-similar-organizations.ts
+++ b/backend/src/bin/scripts/merge-similar-organizations.ts
@@ -145,7 +145,7 @@ if (parameters.help || (!parameters.tenant && !parameters.allTenants)) {
row.organizationId,
row.toMergeId,
userContext,
- undefined
+ undefined,
)
await orgService.mergeSync(row.organizationId, row.toMergeId, null)
} catch (err) {
@@ -157,7 +157,7 @@ if (parameters.help || (!parameters.tenant && !parameters.allTenants)) {
userContext,
{
state: MergeActionState.ERROR,
- }
+ },
)
}
diff --git a/backend/src/database/repositories/mergeActionsRepository.ts b/backend/src/database/repositories/mergeActionsRepository.ts
index 7cb57f37ab..f3e06f757f 100644
--- a/backend/src/database/repositories/mergeActionsRepository.ts
+++ b/backend/src/database/repositories/mergeActionsRepository.ts
@@ -59,9 +59,9 @@ class MergeActionsRepository {
secondaryId: string,
options: IRepositoryOptions,
data: {
- step?: MergeActionStep,
- state?: MergeActionState,
- }
+ step?: MergeActionStep
+ state?: MergeActionState
+ },
) {
const transaction = SequelizeRepository.getTransaction(options)
const tenantId = options.currentTenant.id
diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts
index e8076097b9..88948ce9df 100644
--- a/backend/src/services/memberService.ts
+++ b/backend/src/services/memberService.ts
@@ -706,7 +706,7 @@ export default class MemberService extends LoggerBase {
secondaryMember.id,
repoOptions,
MergeActionStep.UNMERGE_STARTED,
- MergeActionState.IN_PROGRESS
+ MergeActionState.IN_PROGRESS,
)
// move affiliations
@@ -842,7 +842,7 @@ export default class MemberService extends LoggerBase {
repoOptions,
{
step: MergeActionStep.UNMERGE_SYNC_DONE,
- }
+ },
)
// responsible for moving member's activities, syncing to opensearch afterwards, recalculating activity.organizationIds and notifying frontend via websockets
@@ -1395,7 +1395,7 @@ export default class MemberService extends LoggerBase {
this.options,
{
step: MergeActionStep.MERGE_SYNC_DONE,
- }
+ },
)
await this.options.temporal.workflow.start('finishMemberMerging', {
diff --git a/backend/src/services/organizationService.ts b/backend/src/services/organizationService.ts
index ad335a8be9..d62b188b85 100644
--- a/backend/src/services/organizationService.ts
+++ b/backend/src/services/organizationService.ts
@@ -310,7 +310,7 @@ export default class OrganizationService extends LoggerBase {
this.options,
{
step: MergeActionStep.UNMERGE_SYNC_DONE,
- }
+ },
)
// responsible for moving organization's activities, syncing to opensearch afterwards, recalculating activity.organizationIds and notifying frontend via websockets
@@ -580,7 +580,7 @@ export default class OrganizationService extends LoggerBase {
this.options,
{
step: MergeActionStep.MERGE_SYNC_DONE,
- }
+ },
)
return { original, toMerge }
@@ -645,7 +645,7 @@ export default class OrganizationService extends LoggerBase {
this.options,
{
state: MergeActionState.ERROR,
- }
+ },
)
if (tx) {
From 0a69bc9297fb30696f9f7ff4b8300be8cc986931 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 19 Jul 2024 13:35:35 +0530
Subject: [PATCH 04/16] fix linter
---
backend/src/bin/scripts/merge-similar-organizations.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/src/bin/scripts/merge-similar-organizations.ts b/backend/src/bin/scripts/merge-similar-organizations.ts
index 3fe7e1f0e5..adb29ea6db 100644
--- a/backend/src/bin/scripts/merge-similar-organizations.ts
+++ b/backend/src/bin/scripts/merge-similar-organizations.ts
@@ -150,7 +150,7 @@ if (parameters.help || (!parameters.tenant && !parameters.allTenants)) {
await orgService.mergeSync(row.organizationId, row.toMergeId, null)
} catch (err) {
console.log('Error merging organizations - continuing with the rest', err)
- await MergeActionsRepository.update(
+ await MergeActionsRepository.setMergeAction(
MergeActionType.ORG,
row.organizationId,
row.toMergeId,
From 1f8056c2990234c394b0cfa906e04731dd8095dd Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 19 Jul 2024 19:09:34 +0530
Subject: [PATCH 05/16] rm setStep method
---
.../repositories/mergeActionsRepository.ts | 36 -------------------
1 file changed, 36 deletions(-)
diff --git a/backend/src/database/repositories/mergeActionsRepository.ts b/backend/src/database/repositories/mergeActionsRepository.ts
index f3e06f757f..a9922e6003 100644
--- a/backend/src/database/repositories/mergeActionsRepository.ts
+++ b/backend/src/database/repositories/mergeActionsRepository.ts
@@ -104,42 +104,6 @@ class MergeActionsRepository {
return rowCount > 0
}
- static async setStep(
- type: MergeActionType,
- primaryId: string,
- secondaryId: string,
- step: MergeActionStep,
- options: IRepositoryOptions,
- ) {
- const transaction = SequelizeRepository.getTransaction(options)
- const tenantId = options.currentTenant.id
-
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [_, rowCount] = await options.database.sequelize.query(
- `
- UPDATE "mergeActions"
- SET step = :step
- WHERE "tenantId" = :tenantId
- AND type = :type
- AND "primaryId" = :primaryId
- AND "secondaryId" = :secondaryId
- `,
- {
- replacements: {
- tenantId,
- type,
- primaryId,
- secondaryId,
- step,
- },
- type: QueryTypes.UPDATE,
- transaction,
- },
- )
-
- return rowCount > 0
- }
-
static async findById(id: string, options: IRepositoryOptions): Promise {
const transaction = SequelizeRepository.getTransaction(options)
From 78c27388ee37a2aef5dc6ea69650cbfe357007ef Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Wed, 31 Jul 2024 17:47:09 +0530
Subject: [PATCH 06/16] add mergeAction endpoint
---
backend/src/api/index.ts | 1 +
backend/src/api/mergeAction/index.ts | 5 +++
.../src/api/mergeAction/mergeActionQuery.ts | 11 +++++
.../repositories/mergeActionsRepository.ts | 44 +++++++++++++++++++
backend/src/services/MergeActionsService.ts | 16 +++++++
5 files changed, 77 insertions(+)
create mode 100644 backend/src/api/mergeAction/index.ts
create mode 100644 backend/src/api/mergeAction/mergeActionQuery.ts
create mode 100644 backend/src/services/MergeActionsService.ts
diff --git a/backend/src/api/index.ts b/backend/src/api/index.ts
index d8640829d4..1ccedb52b1 100644
--- a/backend/src/api/index.ts
+++ b/backend/src/api/index.ts
@@ -242,6 +242,7 @@ setImmediate(async () => {
require('./eventTracking').default(routes)
require('./customViews').default(routes)
require('./dashboard').default(routes)
+ require('./mergeAction').default(routes)
// Loads the Tenant if the :tenantId param is passed
routes.param('tenantId', tenantMiddleware)
routes.param('tenantId', segmentMiddleware)
diff --git a/backend/src/api/mergeAction/index.ts b/backend/src/api/mergeAction/index.ts
new file mode 100644
index 0000000000..36f43838b2
--- /dev/null
+++ b/backend/src/api/mergeAction/index.ts
@@ -0,0 +1,5 @@
+import { safeWrap } from '../../middlewares/errorMiddleware'
+
+export default (app) => {
+ app.get(`/tenant/:tenantId/mergeActions`, safeWrap(require('./mergeActionQuery').default))
+}
diff --git a/backend/src/api/mergeAction/mergeActionQuery.ts b/backend/src/api/mergeAction/mergeActionQuery.ts
new file mode 100644
index 0000000000..bf91ed8a99
--- /dev/null
+++ b/backend/src/api/mergeAction/mergeActionQuery.ts
@@ -0,0 +1,11 @@
+import MergeActionsService from '@/services/MergeActionsService'
+import Permissions from '../../security/permissions'
+import PermissionChecker from '../../services/user/permissionChecker'
+
+export default async (req, res) => {
+ new PermissionChecker(req).validateHas(Permissions.values.noteRead)
+
+ const payload = await new MergeActionsService(req).query(req.body)
+
+ await req.responseHandler.success(req, res, payload)
+}
diff --git a/backend/src/database/repositories/mergeActionsRepository.ts b/backend/src/database/repositories/mergeActionsRepository.ts
index a9922e6003..ca2eeeabdd 100644
--- a/backend/src/database/repositories/mergeActionsRepository.ts
+++ b/backend/src/database/repositories/mergeActionsRepository.ts
@@ -151,6 +151,50 @@ class MergeActionsRepository {
return null
}
+ static async query(
+ { limit = 20, offset = 0, filter },
+ options: IRepositoryOptions,
+ ): Promise<{ rows: IMergeAction[]; count: number }> {
+ const transaction = SequelizeRepository.getTransaction(options)
+
+ let where = ''
+
+ if (filter?.entityId) {
+ where += ` AND (ma."primaryId" = :entityId OR ma."secondaryId" = :entityId)`
+ }
+
+ if (filter?.type) {
+ where += ` AND ma.type = :type`
+ }
+
+ const records = await options.database.sequelize.query(
+ `
+ SELECT
+ ma."primaryId",
+ ma."secondaryId",
+ ma."state",
+ ma."step",
+ FROM "mergeActions" ma
+ WHERE 1 = 1
+ ${where}
+ LIMIT :limit
+ OFFSET :offset;
+ `,
+ {
+ replacements: {
+ limit,
+ offset,
+ type: filter?.type,
+ entityId: filter?.entityId,
+ },
+ type: QueryTypes.SELECT,
+ transaction,
+ },
+ )
+
+ return records
+ }
+
static async findMergeBackup(
primaryMemberId: string,
type: MergeActionType,
diff --git a/backend/src/services/MergeActionsService.ts b/backend/src/services/MergeActionsService.ts
new file mode 100644
index 0000000000..f0c45e60e9
--- /dev/null
+++ b/backend/src/services/MergeActionsService.ts
@@ -0,0 +1,16 @@
+import { LoggerBase } from '@crowd/logging'
+import { IServiceOptions } from './IServiceOptions'
+import { MergeActionsRepository } from '@/database/repositories/mergeActionsRepository'
+
+export default class MergeActionsService extends LoggerBase {
+ options: IServiceOptions
+
+ constructor(options: IServiceOptions) {
+ super(options.log)
+ this.options = options
+ }
+
+ async query(args) {
+ return MergeActionsRepository.query(args, this.options)
+ }
+}
From a6f133ba87188aad0380fdc704e211a1e013b784 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Wed, 31 Jul 2024 21:11:31 +0530
Subject: [PATCH 07/16] endpoint response and QA
---
.../repositories/mergeActionsRepository.ts | 9 +++++--
backend/src/services/MergeActionsService.ts | 24 ++++++++++++++++++-
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/backend/src/database/repositories/mergeActionsRepository.ts b/backend/src/database/repositories/mergeActionsRepository.ts
index ca2eeeabdd..f8bb24aef8 100644
--- a/backend/src/database/repositories/mergeActionsRepository.ts
+++ b/backend/src/database/repositories/mergeActionsRepository.ts
@@ -154,7 +154,7 @@ class MergeActionsRepository {
static async query(
{ limit = 20, offset = 0, filter },
options: IRepositoryOptions,
- ): Promise<{ rows: IMergeAction[]; count: number }> {
+ ): Promise {
const transaction = SequelizeRepository.getTransaction(options)
let where = ''
@@ -167,13 +167,17 @@ class MergeActionsRepository {
where += ` AND ma.type = :type`
}
+ if (filter?.state) {
+ where += ` AND ma.state IN (:state)`
+ }
+
const records = await options.database.sequelize.query(
`
SELECT
ma."primaryId",
ma."secondaryId",
ma."state",
- ma."step",
+ ma.step
FROM "mergeActions" ma
WHERE 1 = 1
${where}
@@ -186,6 +190,7 @@ class MergeActionsRepository {
offset,
type: filter?.type,
entityId: filter?.entityId,
+ state: filter?.state,
},
type: QueryTypes.SELECT,
transaction,
diff --git a/backend/src/services/MergeActionsService.ts b/backend/src/services/MergeActionsService.ts
index f0c45e60e9..18c0da90c1 100644
--- a/backend/src/services/MergeActionsService.ts
+++ b/backend/src/services/MergeActionsService.ts
@@ -11,6 +11,28 @@ export default class MergeActionsService extends LoggerBase {
}
async query(args) {
- return MergeActionsRepository.query(args, this.options)
+ // filter merge actions that are in progress or error
+ args.filter = {
+ ...args.filter,
+ state: ['in-progress', 'error'],
+ }
+
+ const results = await MergeActionsRepository.query(args, this.options)
+
+ return results.map(result => ({
+ primaryId: result.primaryId,
+ secondaryId: result.secondaryId,
+ state: result.state,
+ // derive operation type from step and if step is null, default to merge
+ 'operation-type': result.step ? MergeActionsService.getOperationType(result.step) : 'merge',
+ }))
+ }
+
+ static getOperationType(step) {
+ if (step.startsWith('merge')) {
+ return 'merge'
+ }
+
+ return 'unmerge'
}
}
From 6e6fa8d5e11ba3333b0b1b34164ccd6884981f42 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Wed, 31 Jul 2024 21:49:49 +0530
Subject: [PATCH 08/16] make linter happy
---
backend/src/services/MergeActionsService.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/src/services/MergeActionsService.ts b/backend/src/services/MergeActionsService.ts
index 18c0da90c1..e9f3876340 100644
--- a/backend/src/services/MergeActionsService.ts
+++ b/backend/src/services/MergeActionsService.ts
@@ -19,7 +19,7 @@ export default class MergeActionsService extends LoggerBase {
const results = await MergeActionsRepository.query(args, this.options)
- return results.map(result => ({
+ return results.map((result) => ({
primaryId: result.primaryId,
secondaryId: result.secondaryId,
state: result.state,
@@ -31,7 +31,7 @@ export default class MergeActionsService extends LoggerBase {
static getOperationType(step) {
if (step.startsWith('merge')) {
return 'merge'
- }
+ }
return 'unmerge'
}
From c61f6de71f43b17972499316c31dbde52a510c0a Mon Sep 17 00:00:00 2001
From: Gasper Grom
Date: Thu, 1 Aug 2024 11:09:39 +0200
Subject: [PATCH 09/16] Frontend display for syncing activities
---
.../contributor-details-activities.vue | 13 +++++++
.../contributor-details-community.vue | 11 +++++-
.../shared/contributor-syncing-activities.vue | 36 +++++++++++++++++++
.../pages/contributor-details.page.vue | 4 ++-
.../contributor/store/contributor.actions.ts | 17 +++++++++
.../modules/contributor/types/Contributor.ts | 3 +-
.../merge/services/merge-actions.service.ts | 23 ++++++++++++
.../modules/merge/types/MemberActions.ts | 12 +++++++
frontend/src/ui-kit/index.scss | 1 +
.../src/ui-kit/loading/Loading.stories.ts | 34 ++++++++++++++++++
frontend/src/ui-kit/loading/Loading.vue | 29 +++++++++++++++
frontend/src/ui-kit/loading/loading.scss | 36 +++++++++++++++++++
12 files changed, 216 insertions(+), 3 deletions(-)
create mode 100644 frontend/src/modules/contributor/components/shared/contributor-syncing-activities.vue
create mode 100644 frontend/src/shared/modules/merge/services/merge-actions.service.ts
create mode 100644 frontend/src/shared/modules/merge/types/MemberActions.ts
create mode 100644 frontend/src/ui-kit/loading/Loading.stories.ts
create mode 100644 frontend/src/ui-kit/loading/Loading.vue
create mode 100644 frontend/src/ui-kit/loading/loading.scss
diff --git a/frontend/src/modules/contributor/components/details/contributor-details-activities.vue b/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
index 4401cb601f..ede21994c3 100644
--- a/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
+++ b/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
@@ -1,5 +1,16 @@
+
+
+
+ Syncing activities...
+
+
+ Re-syncing all the activities as this organization was recently merged.
+ This process may take some minutes.
+
+
# of activities
-
+
+
{{ props.contributor.activityCount && formatNumber(props.contributor.activityCount) || '-' }}
@@ -57,6 +64,8 @@ import { formatNumber } from '@/utils/number';
import LfContributorEngagementLevel from '@/modules/contributor/components/shared/contributor-engagement-level.vue';
import LfContributorSentiment from '@/modules/contributor/components/shared/contributor-sentiment.vue';
import { Contributor } from '@/modules/contributor/types/Contributor';
+import LfLoading from '@/ui-kit/loading/Loading.vue';
+import { MergeActionState } from '@/shared/modules/merge/types/MemberActions';
const props = defineProps<{
contributor: Contributor,
diff --git a/frontend/src/modules/contributor/components/shared/contributor-syncing-activities.vue b/frontend/src/modules/contributor/components/shared/contributor-syncing-activities.vue
new file mode 100644
index 0000000000..7b192fac30
--- /dev/null
+++ b/frontend/src/modules/contributor/components/shared/contributor-syncing-activities.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ Syncing activities...
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/modules/contributor/pages/contributor-details.page.vue b/frontend/src/modules/contributor/pages/contributor-details.page.vue
index 5686ef4831..946678e013 100644
--- a/frontend/src/modules/contributor/pages/contributor-details.page.vue
+++ b/frontend/src/modules/contributor/pages/contributor-details.page.vue
@@ -19,7 +19,8 @@
-
+
+
@@ -100,6 +101,7 @@ import LfContributorDetailsHeader from '@/modules/contributor/components/details
import LfContributorDetailsActions from '@/modules/contributor/components/details/contributor-details-actions.vue';
import LfContributorLastEnrichment from '@/modules/contributor/components/shared/contributor-last-enrichment.vue';
import { useContributorStore } from '@/modules/contributor/store/contributor.store';
+import LfContributorSyncingActivities from '@/modules/contributor/components/shared/contributor-syncing-activities.vue';
const { getMemberCustomAttributes } = useMemberStore();
diff --git a/frontend/src/modules/contributor/store/contributor.actions.ts b/frontend/src/modules/contributor/store/contributor.actions.ts
index ebeaf3ce6c..8e9e8a9f31 100644
--- a/frontend/src/modules/contributor/store/contributor.actions.ts
+++ b/frontend/src/modules/contributor/store/contributor.actions.ts
@@ -2,16 +2,33 @@ import { useLfSegmentsStore } from '@/modules/lf/segments/store';
import { storeToRefs } from 'pinia';
import { ContributorApiService } from '@/modules/contributor/services/contributor.api.service';
import { Contributor } from '@/modules/contributor/types/Contributor';
+import { MergeActionsService } from '@/shared/modules/merge/services/merge-actions.service';
+import { MergeAction } from '@/shared/modules/merge/types/MemberActions';
export default {
getContributor(id: string): Promise
{
const { selectedProjectGroup } = storeToRefs(useLfSegmentsStore());
+
return ContributorApiService.find(id, [selectedProjectGroup.value?.id as string])
.then((contributor) => {
this.contributor = contributor;
+ this.getContributorMergeActions(id);
return Promise.resolve(contributor);
});
},
+ getContributorMergeActions(id: string): Promise {
+ return MergeActionsService.list(id)
+ .then((mergeActions) => {
+ const inProgress = mergeActions.some((action) => action.state === 'in-progress');
+ const error = mergeActions.some((action) => action.state === 'error');
+ this.contributor = {
+ ...this.contributor,
+ // eslint-disable-next-line no-nested-ternary
+ activitySycning: inProgress ? 'in-progress' : (error ? 'error' : ''),
+ };
+ return Promise.resolve(mergeActions);
+ });
+ },
updateContributor(id: string, data: Partial) {
const { selectedProjectGroup } = storeToRefs(useLfSegmentsStore());
return ContributorApiService.update(id, data, [selectedProjectGroup.value?.id as string])
diff --git a/frontend/src/modules/contributor/types/Contributor.ts b/frontend/src/modules/contributor/types/Contributor.ts
index 39e8c08b6b..045ee4b052 100644
--- a/frontend/src/modules/contributor/types/Contributor.ts
+++ b/frontend/src/modules/contributor/types/Contributor.ts
@@ -82,5 +82,6 @@ export interface Contributor {
id: string;
name: string;
activityCount: string;
- }[]
+ }[],
+ activitySycning: string;
}
diff --git a/frontend/src/shared/modules/merge/services/merge-actions.service.ts b/frontend/src/shared/modules/merge/services/merge-actions.service.ts
new file mode 100644
index 0000000000..4f599847ec
--- /dev/null
+++ b/frontend/src/shared/modules/merge/services/merge-actions.service.ts
@@ -0,0 +1,23 @@
+import authAxios from '@/shared/axios/auth-axios';
+import { AuthService } from '@/modules/auth/services/auth.service';
+import { MergeAction } from '@/shared/modules/merge/types/MemberActions';
+
+export class MergeActionsService {
+ static async list(entityId: string, type: string = 'member'): Promise {
+ const tenantId = AuthService.getTenantId();
+
+ const response = await authAxios.get(
+ `/tenant/${tenantId}/mergeActions`,
+ {
+ params: {
+ filter: {
+ entityId,
+ type,
+ },
+ },
+ },
+ );
+
+ return response.data;
+ }
+}
diff --git a/frontend/src/shared/modules/merge/types/MemberActions.ts b/frontend/src/shared/modules/merge/types/MemberActions.ts
new file mode 100644
index 0000000000..a1ee6802b9
--- /dev/null
+++ b/frontend/src/shared/modules/merge/types/MemberActions.ts
@@ -0,0 +1,12 @@
+export enum MergeActionState {
+ IN_PROGRESS = 'in-progress',
+ DONE = 'done',
+ ERROR = 'error',
+}
+
+export interface MergeAction {
+ operationType: string;
+ primaryId: string;
+ secondaryId: string;
+ state: MergeActionState;
+}
diff --git a/frontend/src/ui-kit/index.scss b/frontend/src/ui-kit/index.scss
index d6fd201ba4..1a99d31025 100644
--- a/frontend/src/ui-kit/index.scss
+++ b/frontend/src/ui-kit/index.scss
@@ -6,6 +6,7 @@
@import 'dropdown/dropdown';
@import 'field/field';
@import 'input/input';
+@import 'loading/loading';
@import 'modal/modal';
@import 'field-message/field-message';
@import 'radio/radio';
diff --git a/frontend/src/ui-kit/loading/Loading.stories.ts b/frontend/src/ui-kit/loading/Loading.stories.ts
new file mode 100644
index 0000000000..03824b1838
--- /dev/null
+++ b/frontend/src/ui-kit/loading/Loading.stories.ts
@@ -0,0 +1,34 @@
+import LfLoading from './Loading.vue';
+
+export default {
+ title: 'LinuxFoundation/Loading',
+ component: LfLoading,
+ tags: ['autodocs'],
+ argTypes: {
+ // Propr
+ height: {
+ description: 'Loader height',
+ defaultValue: '100px',
+ control: 'text',
+ },
+ width: {
+ description: 'Loader width',
+ defaultValue: '100%',
+ control: 'text',
+ },
+ count: {
+ description: 'Number of loaders',
+ defaultValue: '1',
+ control: 'number',
+ },
+ },
+};
+
+export const Default = {
+ label: 'Primary',
+ args: {
+ height: '100px',
+ width: '100%',
+ count: 1,
+ },
+};
diff --git a/frontend/src/ui-kit/loading/Loading.vue b/frontend/src/ui-kit/loading/Loading.vue
new file mode 100644
index 0000000000..4f051f16b0
--- /dev/null
+++ b/frontend/src/ui-kit/loading/Loading.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/ui-kit/loading/loading.scss b/frontend/src/ui-kit/loading/loading.scss
new file mode 100644
index 0000000000..c5069bf1b9
--- /dev/null
+++ b/frontend/src/ui-kit/loading/loading.scss
@@ -0,0 +1,36 @@
+/* ---------------------------------- *\
+ Loading
+\* ---------------------------------- */
+
+@keyframes shimmer {
+ 100% {
+ transform: translateX(100%);
+ }
+}
+
+.c-loading {
+ display: inline-block;
+ height: 1em;
+ position: relative;
+ overflow: hidden;
+ background-color: var(--lf-color-secondary-50);
+
+ &:after {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ transform: translateX(-100%);
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 0.2) 20.24%,
+ rgba(255, 255, 255, 0.5) 42.57%,
+ rgba(255, 255, 255, 0) 66.35%
+ );
+ animation: shimmer 1.5s infinite;
+ content: '';
+ }
+}
+
From 2caf36a60fbec4739824471a629c4008b15c5a65 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Thu, 1 Aug 2024 14:51:52 +0530
Subject: [PATCH 10/16] improve endpoint structure and payload
---
.../src/api/mergeAction/mergeActionQuery.ts | 23 ++++++++-
.../repositories/mergeActionsRepository.ts | 49 ------------------
backend/src/security/permissions.ts | 11 ++++
backend/src/services/MergeActionsService.ts | 14 ++----
.../src/mergeActions/repo.ts | 50 ++++++++++++++++++-
5 files changed, 86 insertions(+), 61 deletions(-)
diff --git a/backend/src/api/mergeAction/mergeActionQuery.ts b/backend/src/api/mergeAction/mergeActionQuery.ts
index bf91ed8a99..93cb7db406 100644
--- a/backend/src/api/mergeAction/mergeActionQuery.ts
+++ b/backend/src/api/mergeAction/mergeActionQuery.ts
@@ -2,10 +2,29 @@ import MergeActionsService from '@/services/MergeActionsService'
import Permissions from '../../security/permissions'
import PermissionChecker from '../../services/user/permissionChecker'
+/**
+ * GET /tenant/{tenantId}/mergeAction
+ * @summary Query mergeActions
+ * @tag MergeActions
+ * @security Bearer
+ * @description Query mergeActions. It accepts filters and pagination.
+ * @pathParam {string} tenantId - Your workspace/tenant ID
+ * @queryParam {string} entityId - ID of the entity
+ * @queryParam {string} type - type of the entity (e.g., org or member)
+ * @queryParam {array} state - state of the merge action (e.g., in-progress, error)
+ * @queryParam {number} [limit] - number of records to return (optional, default to 20)
+ * @queryParam {number} [offset] - number of records to skip (optional, default to 0)
+ * @response 200 - Ok
+ * @responseContent {MergeActionList} 200.application/json
+ * @responseExample {MergeActionList} 200.application/json.MergeAction
+ * @response 401 - Unauthorized
+ * @response 404 - Not found
+ * @response 429 - Too many requests
+ */
export default async (req, res) => {
- new PermissionChecker(req).validateHas(Permissions.values.noteRead)
+ new PermissionChecker(req).validateHas(Permissions.values.mergeActionRead)
- const payload = await new MergeActionsService(req).query(req.body)
+ const payload = await new MergeActionsService(req).query(req.query)
await req.responseHandler.success(req, res, payload)
}
diff --git a/backend/src/database/repositories/mergeActionsRepository.ts b/backend/src/database/repositories/mergeActionsRepository.ts
index 041e240455..466662bdbe 100644
--- a/backend/src/database/repositories/mergeActionsRepository.ts
+++ b/backend/src/database/repositories/mergeActionsRepository.ts
@@ -153,55 +153,6 @@ class MergeActionsRepository {
return null
}
- static async query(
- { limit = 20, offset = 0, filter },
- options: IRepositoryOptions,
- ): Promise {
- const transaction = SequelizeRepository.getTransaction(options)
-
- let where = ''
-
- if (filter?.entityId) {
- where += ` AND (ma."primaryId" = :entityId OR ma."secondaryId" = :entityId)`
- }
-
- if (filter?.type) {
- where += ` AND ma.type = :type`
- }
-
- if (filter?.state) {
- where += ` AND ma.state IN (:state)`
- }
-
- const records = await options.database.sequelize.query(
- `
- SELECT
- ma."primaryId",
- ma."secondaryId",
- ma."state",
- ma.step
- FROM "mergeActions" ma
- WHERE 1 = 1
- ${where}
- LIMIT :limit
- OFFSET :offset;
- `,
- {
- replacements: {
- limit,
- offset,
- type: filter?.type,
- entityId: filter?.entityId,
- state: filter?.state,
- },
- type: QueryTypes.SELECT,
- transaction,
- },
- )
-
- return records
- }
-
static async findMergeBackup(
primaryMemberId: string,
type: MergeActionType,
diff --git a/backend/src/security/permissions.ts b/backend/src/security/permissions.ts
index 2636ee7272..ef216b2233 100644
--- a/backend/src/security/permissions.ts
+++ b/backend/src/security/permissions.ts
@@ -1195,6 +1195,17 @@ class Permissions {
TenantPlans.Scale,
],
},
+ mergeActionRead: {
+ id: 'mergeActionRead',
+ allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly],
+ allowedPlans: [
+ TenantPlans.Essential,
+ TenantPlans.Growth,
+ TenantPlans.EagleEye,
+ TenantPlans.Enterprise,
+ TenantPlans.Scale,
+ ],
+ },
}
}
diff --git a/backend/src/services/MergeActionsService.ts b/backend/src/services/MergeActionsService.ts
index e9f3876340..1307f00ea7 100644
--- a/backend/src/services/MergeActionsService.ts
+++ b/backend/src/services/MergeActionsService.ts
@@ -1,6 +1,7 @@
import { LoggerBase } from '@crowd/logging'
+import { queryMergeActions } from '@crowd/data-access-layer/src/mergeActions/repo'
import { IServiceOptions } from './IServiceOptions'
-import { MergeActionsRepository } from '@/database/repositories/mergeActionsRepository'
+import SequelizeRepository from '@/database/repositories/sequelizeRepository'
export default class MergeActionsService extends LoggerBase {
options: IServiceOptions
@@ -11,20 +12,15 @@ export default class MergeActionsService extends LoggerBase {
}
async query(args) {
- // filter merge actions that are in progress or error
- args.filter = {
- ...args.filter,
- state: ['in-progress', 'error'],
- }
-
- const results = await MergeActionsRepository.query(args, this.options)
+ const qx = SequelizeRepository.getQueryExecutor(this.options)
+ const results = await queryMergeActions(qx, args)
return results.map((result) => ({
primaryId: result.primaryId,
secondaryId: result.secondaryId,
state: result.state,
// derive operation type from step and if step is null, default to merge
- 'operation-type': result.step ? MergeActionsService.getOperationType(result.step) : 'merge',
+ operationType: result.step ? MergeActionsService.getOperationType(result.step) : 'merge',
}))
}
diff --git a/services/libs/data-access-layer/src/mergeActions/repo.ts b/services/libs/data-access-layer/src/mergeActions/repo.ts
index ebdda79f60..4ecfadac41 100644
--- a/services/libs/data-access-layer/src/mergeActions/repo.ts
+++ b/services/libs/data-access-layer/src/mergeActions/repo.ts
@@ -1 +1,49 @@
-// todo: mergeActions repo
+import validator from 'validator'
+import { QueryExecutor } from '../queryExecutor'
+import { IMergeAction } from '@crowd/types'
+
+export async function queryMergeActions(
+ qx: QueryExecutor,
+ { entityId, type, state = [], limit = 20, offset = 0 },
+): Promise {
+ let where = ''
+
+ if (entityId) {
+ if (!validator.isUUID(entityId)) {
+ return []
+ }
+ where += ` AND (ma."primaryId" = $(entityId) OR ma."secondaryId" = $(entityId))`
+ }
+
+ if (type) {
+ where += ` AND ma.type = $(type)`
+ }
+
+ if (state) {
+ where += ` AND ma.state IN ($(state:csv))`
+ }
+
+ const result = await qx.select(
+ `
+ SELECT
+ ma."primaryId",
+ ma."secondaryId",
+ ma."state",
+ ma."step"
+ FROM "mergeActions" ma
+ WHERE 1 = 1
+ ${where}
+ LIMIT $(limit)
+ OFFSET $(offset)
+ `,
+ {
+ entityId,
+ type,
+ state,
+ limit,
+ offset,
+ },
+ )
+
+ return result
+}
From 583909747f5f16c9422dcae45810877a870c3a61 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 2 Aug 2024 13:35:10 +0530
Subject: [PATCH 11/16] resolve pr comments
---
backend/src/services/MergeActionsService.ts | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/backend/src/services/MergeActionsService.ts b/backend/src/services/MergeActionsService.ts
index 1307f00ea7..d864eafa3c 100644
--- a/backend/src/services/MergeActionsService.ts
+++ b/backend/src/services/MergeActionsService.ts
@@ -27,8 +27,10 @@ export default class MergeActionsService extends LoggerBase {
static getOperationType(step) {
if (step.startsWith('merge')) {
return 'merge'
- }
-
- return 'unmerge'
+ } if (step.startsWith('unmerge')) {
+ return 'unmerge'
+ }
+
+ throw new Error(`Unrecognized merge action step: ${step}`)
}
}
From 47ec91b340cab93b41027693262bba02e401f3de Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 2 Aug 2024 13:39:05 +0530
Subject: [PATCH 12/16] prettiefy the code
---
backend/src/services/MergeActionsService.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/backend/src/services/MergeActionsService.ts b/backend/src/services/MergeActionsService.ts
index d864eafa3c..bd80760569 100644
--- a/backend/src/services/MergeActionsService.ts
+++ b/backend/src/services/MergeActionsService.ts
@@ -27,10 +27,11 @@ export default class MergeActionsService extends LoggerBase {
static getOperationType(step) {
if (step.startsWith('merge')) {
return 'merge'
- } if (step.startsWith('unmerge')) {
+ }
+ if (step.startsWith('unmerge')) {
return 'unmerge'
- }
-
+ }
+
throw new Error(`Unrecognized merge action step: ${step}`)
}
}
From b189c394eeb4447a4ca852ef63453922791989e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C5=A1per=20Grom?=
Date: Fri, 2 Aug 2024 14:28:20 +0200
Subject: [PATCH 13/16] Finalize frontend
---
.../contributor-details-activities.vue | 14 ++++++--
.../contributor-details-community.vue | 2 +-
.../shared/contributor-syncing-activities.vue | 6 ++--
.../pages/contributor-details.page.vue | 13 ++++++--
.../contributor/store/contributor.actions.ts | 4 +--
.../modules/contributor/types/Contributor.ts | 3 +-
.../components/member-merge-suggestions.vue | 4 ++-
.../components/member-unmerge-dialog.vue | 3 ++
.../organization-details-activities.vue | 23 +++++++++++++
.../organization-details-community.vue | 11 ++++++-
.../organization-unmerge-dialog.vue | 4 +++
.../organization-syncing-activities.vue | 33 +++++++++++++++++++
.../pages/organization-details.page.vue | 20 +++++++++--
.../organization/store/pinia/actions.ts | 14 ++++++++
.../organization/types/Organization.ts | 2 ++
.../merge/services/merge-actions.service.ts | 9 +++--
16 files changed, 144 insertions(+), 21 deletions(-)
create mode 100644 frontend/src/modules/organization/components/shared/organization-syncing-activities.vue
diff --git a/frontend/src/modules/contributor/components/details/contributor-details-activities.vue b/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
index ede21994c3..d67e774b5a 100644
--- a/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
+++ b/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
@@ -1,14 +1,24 @@
-
+
Syncing activities...
- Re-syncing all the activities as this organization was recently merged.
+ Re-syncing all the activities as this organization was recently
+ {{ props.contributor.activitySycning?.operationType === 'merge' ? 'merged' : 'unmerged' }}.
This process may take some minutes.
+
+
+
+ Error loading activities
+
+
+ An error occurred while syncing this profile activities. Please reach out to our team.
+
+
@@ -25,8 +26,7 @@ const props = defineProps<{
contributor: Contributor,
}>();
-const inProgress = computed(() => props.contributor.activitySycning === MergeActionState.IN_PROGRESS);
-// const error = computed(() => props.contributor.activitySycning === MergeActionState.ERROR);
+const inProgress = computed(() => props.contributor.activitySycning?.state === MergeActionState.IN_PROGRESS);
+
+
diff --git a/frontend/src/modules/organization/pages/organization-details.page.vue b/frontend/src/modules/organization/pages/organization-details.page.vue
index ffcc309006..297b1b4e33 100644
--- a/frontend/src/modules/organization/pages/organization-details.page.vue
+++ b/frontend/src/modules/organization/pages/organization-details.page.vue
@@ -19,7 +19,12 @@
-
+
+
@@ -60,7 +65,15 @@
People
- Activities
+
+ Activities
+
+
@@ -112,6 +125,9 @@ import LfOrganizationDetailsEmails from '@/modules/organization/components/detai
import { useOrganizationStore } from '@/modules/organization/store/pinia';
import LfOrganizationDetailsContributors
from '@/modules/organization/components/details/organization-details-contributors.vue';
+import LfOrganizationSyncingActivities
+ from '@/modules/organization/components/shared/organization-syncing-activities.vue';
+import { MergeActionState } from '@/shared/modules/merge/types/MemberActions';
const lsSegmentsStore = useLfSegmentsStore();
const { selectedProjectGroup } = storeToRefs(lsSegmentsStore);
diff --git a/frontend/src/modules/organization/store/pinia/actions.ts b/frontend/src/modules/organization/store/pinia/actions.ts
index 44fa052538..10843717ea 100644
--- a/frontend/src/modules/organization/store/pinia/actions.ts
+++ b/frontend/src/modules/organization/store/pinia/actions.ts
@@ -4,6 +4,8 @@ import { Organization } from '@/modules/organization/types/Organization';
import { OrganizationService } from '@/modules/organization/organization-service';
import { storeToRefs } from 'pinia';
import { useLfSegmentsStore } from '@/modules/lf/segments/store';
+import { MergeActionsService } from '@/shared/modules/merge/services/merge-actions.service';
+import { MergeAction } from '@/shared/modules/merge/types/MemberActions';
export default {
fetchOrganizations(this: OrganizationState, { body = {}, reload = false } :{ body?: any, reload?: boolean }): Promise> {
@@ -27,9 +29,21 @@ export default {
return OrganizationService.find(id, [selectedProjectGroup.value?.id as string])
.then((organization: Organization) => {
this.organization = organization;
+ this.getOrganizationMergeActions(id);
return Promise.resolve(organization);
});
},
+ getOrganizationMergeActions(id: string): Promise {
+ return MergeActionsService.list(id, 'organization')
+ .then((mergeActions) => {
+ this.organization = {
+ ...this.organization,
+ // eslint-disable-next-line no-nested-ternary
+ activitySycning: mergeActions.length > 0 ? mergeActions[0] : null,
+ };
+ return Promise.resolve(mergeActions);
+ });
+ },
updateOrganization(id: string, data: Partial): Promise {
const { selectedProjectGroup } = storeToRefs(useLfSegmentsStore());
return OrganizationService.update(id, data, [selectedProjectGroup.value?.id as string])
diff --git a/frontend/src/modules/organization/types/Organization.ts b/frontend/src/modules/organization/types/Organization.ts
index f12814ade7..843cf20998 100644
--- a/frontend/src/modules/organization/types/Organization.ts
+++ b/frontend/src/modules/organization/types/Organization.ts
@@ -1,4 +1,5 @@
import { Platform } from '@/shared/modules/platform/types/Platform';
+import { MergeAction } from '@/shared/modules/merge/types/MemberActions';
export interface OrganizationAttribute extends Record{
default: any;
@@ -99,4 +100,5 @@ export interface Organization {
updatedAt: string;
usageEndDate: string;
}
+ activitySycning: MergeAction | null;
}
diff --git a/frontend/src/shared/modules/merge/services/merge-actions.service.ts b/frontend/src/shared/modules/merge/services/merge-actions.service.ts
index 4f599847ec..a1ede44173 100644
--- a/frontend/src/shared/modules/merge/services/merge-actions.service.ts
+++ b/frontend/src/shared/modules/merge/services/merge-actions.service.ts
@@ -3,17 +3,16 @@ import { AuthService } from '@/modules/auth/services/auth.service';
import { MergeAction } from '@/shared/modules/merge/types/MemberActions';
export class MergeActionsService {
- static async list(entityId: string, type: string = 'member'): Promise {
+ static async list(entityId: string, type: string = 'member', limit = 1): Promise {
const tenantId = AuthService.getTenantId();
const response = await authAxios.get(
`/tenant/${tenantId}/mergeActions`,
{
params: {
- filter: {
- entityId,
- type,
- },
+ entityId,
+ type,
+ limit,
},
},
);
From 3a12ba9ba04699c9793e9c13d07b926a1bce8cf5 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 2 Aug 2024 20:33:42 +0530
Subject: [PATCH 14/16] interested only in most recent one
---
backend/src/api/mergeAction/mergeActionQuery.ts | 1 -
services/libs/data-access-layer/src/mergeActions/repo.ts | 3 ++-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/src/api/mergeAction/mergeActionQuery.ts b/backend/src/api/mergeAction/mergeActionQuery.ts
index 93cb7db406..2a9d9c4736 100644
--- a/backend/src/api/mergeAction/mergeActionQuery.ts
+++ b/backend/src/api/mergeAction/mergeActionQuery.ts
@@ -11,7 +11,6 @@ import PermissionChecker from '../../services/user/permissionChecker'
* @pathParam {string} tenantId - Your workspace/tenant ID
* @queryParam {string} entityId - ID of the entity
* @queryParam {string} type - type of the entity (e.g., org or member)
- * @queryParam {array} state - state of the merge action (e.g., in-progress, error)
* @queryParam {number} [limit] - number of records to return (optional, default to 20)
* @queryParam {number} [offset] - number of records to skip (optional, default to 0)
* @response 200 - Ok
diff --git a/services/libs/data-access-layer/src/mergeActions/repo.ts b/services/libs/data-access-layer/src/mergeActions/repo.ts
index 4ecfadac41..78719873a6 100644
--- a/services/libs/data-access-layer/src/mergeActions/repo.ts
+++ b/services/libs/data-access-layer/src/mergeActions/repo.ts
@@ -19,7 +19,7 @@ export async function queryMergeActions(
where += ` AND ma.type = $(type)`
}
- if (state) {
+ if (state.length) {
where += ` AND ma.state IN ($(state:csv))`
}
@@ -33,6 +33,7 @@ export async function queryMergeActions(
FROM "mergeActions" ma
WHERE 1 = 1
${where}
+ ORDER BY ma."createdAt" DESC
LIMIT $(limit)
OFFSET $(offset)
`,
From 17cb46ba47fbb834dc95c2f841743209e3dbb448 Mon Sep 17 00:00:00 2001
From: Yeganathan S
Date: Fri, 2 Aug 2024 20:37:34 +0530
Subject: [PATCH 15/16] fix frontend to use org type
---
frontend/src/modules/organization/store/pinia/actions.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/modules/organization/store/pinia/actions.ts b/frontend/src/modules/organization/store/pinia/actions.ts
index 10843717ea..06664fb987 100644
--- a/frontend/src/modules/organization/store/pinia/actions.ts
+++ b/frontend/src/modules/organization/store/pinia/actions.ts
@@ -34,7 +34,7 @@ export default {
});
},
getOrganizationMergeActions(id: string): Promise {
- return MergeActionsService.list(id, 'organization')
+ return MergeActionsService.list(id, 'org')
.then((mergeActions) => {
this.organization = {
...this.organization,
From 06a6edd8ec160dee7a57bc1533585254169114af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C5=A1per=20Grom?=
Date: Mon, 5 Aug 2024 15:29:47 +0200
Subject: [PATCH 16/16] Fix texts
---
.../components/details/contributor-details-activities.vue | 7 ++++---
.../components/details/organization-details-activities.vue | 5 +++--
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/frontend/src/modules/contributor/components/details/contributor-details-activities.vue b/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
index d67e774b5a..9a008f77c6 100644
--- a/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
+++ b/frontend/src/modules/contributor/components/details/contributor-details-activities.vue
@@ -5,7 +5,7 @@
Syncing activities...
- Re-syncing all the activities as this organization was recently
+ Re-syncing all the activities as this person was recently
{{ props.contributor.activitySycning?.operationType === 'merge' ? 'merged' : 'unmerged' }}.
This process may take some minutes.
@@ -13,10 +13,11 @@
- Error loading activities
+ Error syncing activities
- An error occurred while syncing this profile activities. Please reach out to our team.
+ An error occurred while syncing this profile activities.
+ Please contact our support team.
- Error loading activities
+ Error syncing activities
- An error occurred while syncing this organization activities. Please reach out to our team.
+ An error occurred while syncing this organization activities.
+ Please contact our support team.