diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts index e7ce319821..37481d1c94 100644 --- a/backend/src/services/memberService.ts +++ b/backend/src/services/memberService.ts @@ -32,7 +32,12 @@ import { randomUUID } from 'crypto' import lodash from 'lodash' import moment from 'moment-timezone' import validator from 'validator' -import { captureApiChange, memberEditIdentitiesAction, memberMergeAction } from '@crowd/audit-logs' +import { + captureApiChange, + memberEditIdentitiesAction, + memberMergeAction, + memberUnmergeAction, +} from '@crowd/audit-logs' import { addMemberNotes, addMemberTags, @@ -685,205 +690,227 @@ export default class MemberService extends LoggerBase { delete payload.secondary.organizations try { - const qx = SequelizeRepository.getQueryExecutor(this.options) - const member = await findMemberById(qx, memberId, [MemberField.ID, MemberField.DISPLAY_NAME]) - const [memberTasks, memberTags, memberNotes] = await Promise.all([ - (await findMemberTasks(qx, memberId)).map((t) => ({ id: t.taskId })), - (await findMemberTags(qx, memberId)).map((t) => ({ id: t.tagId })), - (await findMemberNotes(qx, memberId)).map((t) => ({ id: t.noteId })), - ]) - - const repoOptions: IRepositoryOptions = - await SequelizeRepository.createTransactionalRepositoryOptions(this.options) - tx = repoOptions.transaction - - const txqx = SequelizeRepository.getQueryExecutor(repoOptions, tx) - - // remove identities in secondary member from primary member - await MemberRepository.removeIdentitiesFromMember( - memberId, - payload.secondary.identities.filter( - (i) => - i.verified === undefined || // backwards compatibility for old identity backups - i.verified === true || - (i.verified === false && - !payload.primary.identities.some( - (pi) => - pi.verified === false && - pi.platform === i.platform && - pi.value === i.value && - pi.type === i.type, - )), - ), - repoOptions, - ) - - // we need to exclude identities in secondary that still exists in some other member - const identitiesToExclude = await MemberRepository.findAlreadyExistingIdentities( - payload.secondary.identities.filter((i) => i.verified), - repoOptions, - ) - - payload.secondary.identities = payload.secondary.identities.filter( - (i) => - !identitiesToExclude.some( - (ie) => - ie.platform === i.platform && - ie.value === i.value && - ie.type === i.type && - ie.verified, - ), - ) + const { member, secondaryMember } = await captureApiChange( + this.options, + memberUnmergeAction(memberId, async (captureOldState, captureNewState) => { + const qx = SequelizeRepository.getQueryExecutor(this.options) + const member = await findMemberById(qx, memberId, [ + MemberField.ID, + MemberField.DISPLAY_NAME, + ]) + const [memberTasks, memberTags, memberNotes] = await Promise.all([ + (await findMemberTasks(qx, memberId)).map((t) => ({ id: t.taskId })), + (await findMemberTags(qx, memberId)).map((t) => ({ id: t.tagId })), + (await findMemberNotes(qx, memberId)).map((t) => ({ id: t.noteId })), + ]) - // create the secondary member - const secondaryMember = await MemberRepository.create(payload.secondary, repoOptions) + captureOldState({ + primary: payload.primary, + }) - // track merge action - await MergeActionsRepository.add( - MergeActionType.MEMBER, - member.id, - secondaryMember.id, - repoOptions, - MergeActionStep.UNMERGE_STARTED, - MergeActionState.IN_PROGRESS, - ) + const repoOptions: IRepositoryOptions = + await SequelizeRepository.createTransactionalRepositoryOptions(this.options) + tx = repoOptions.transaction - // move affiliations - if (payload.secondary.affiliations.length > 0) { - await MemberRepository.moveSelectedAffiliationsBetweenMembers( - memberId, - secondaryMember.id, - payload.secondary.affiliations.map((a) => a.id), - repoOptions, - ) - } + const txqx = SequelizeRepository.getQueryExecutor(repoOptions, tx) - // move tags - if (payload.secondary.tags.length > 0) { - await addMemberTags( - txqx, - secondaryMember.id, - payload.secondary.tags.map((t) => t.id), - ) - // check if anything to delete in primary - const tagsToDelete = memberTags.filter( - (t) => !payload.primary.tags.some((pt) => pt.id === t.id), - ) - if (tagsToDelete.length > 0) { - await removeMemberTags( - txqx, + // remove identities in secondary member from primary member + await MemberRepository.removeIdentitiesFromMember( memberId, - tagsToDelete.map((t) => t.id), + payload.secondary.identities.filter( + (i) => + i.verified === undefined || // backwards compatibility for old identity backups + i.verified === true || + (i.verified === false && + !payload.primary.identities.some( + (pi) => + pi.verified === false && + pi.platform === i.platform && + pi.value === i.value && + pi.type === i.type, + )), + ), + repoOptions, ) - } - } - // move tasks - if (payload.secondary.tasks.length > 0) { - await addMemberTasks( - txqx, - secondaryMember.id, - payload.secondary.tasks.map((t) => t.id), - ) - // check if anything to delete in primary - const tasksToDelete = memberTasks.filter( - (t) => !payload.primary.tasks.some((pt) => pt.id === t.id), - ) - if (tasksToDelete.length > 0) { - await removeMemberTasks( - txqx, - memberId, - tasksToDelete.map((t) => t.id), + // we need to exclude identities in secondary that still exists in some other member + const identitiesToExclude = await MemberRepository.findAlreadyExistingIdentities( + payload.secondary.identities.filter((i) => i.verified), + repoOptions, ) - } - } - // move notes - if (payload.secondary.notes.length > 0) { - await addMemberNotes( - txqx, - secondaryMember.id, - payload.secondary.notes.map((n) => n.id), - ) - // check if anything to delete in primary - const notesToDelete = memberNotes.filter( - (n) => !payload.primary.notes.some((pn) => pn.id === n.id), - ) - if (notesToDelete.length > 0) { - await removeMemberNotes( - txqx, - memberId, - notesToDelete.map((n) => n.id), + payload.secondary.identities = payload.secondary.identities.filter( + (i) => + !identitiesToExclude.some( + (ie) => + ie.platform === i.platform && + ie.value === i.value && + ie.type === i.type && + ie.verified, + ), ) - } - } - // move memberOrganizations - if (payload.secondary.memberOrganizations.length > 0) { - const nonExistingOrganizationIds = await OrganizationRepository.findNonExistingIds( - payload.secondary.memberOrganizations.map((o) => o.organizationId), - repoOptions, - ) - for (const role of payload.secondary.memberOrganizations.filter( - (r) => !nonExistingOrganizationIds.includes(r.organizationId), - )) { - await MemberOrganizationRepository.addMemberRole( - { ...role, memberId: secondaryMember.id }, + // 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, ) - } - const memberOrganizations = await MemberOrganizationRepository.findMemberRoles( - member.id, - repoOptions, - ) - // check if anything to delete in primary - const rolesToDelete = memberOrganizations.filter( - (r) => - r.source !== 'ui' && - !payload.primary.memberOrganizations.some( - (pr) => - pr.organizationId === r.organizationId && - pr.title === r.title && - pr.dateStart === r.dateStart && - pr.dateEnd === r.dateEnd, - ), - ) + // move affiliations + if (payload.secondary.affiliations.length > 0) { + await MemberRepository.moveSelectedAffiliationsBetweenMembers( + memberId, + secondaryMember.id, + payload.secondary.affiliations.map((a) => a.id), + repoOptions, + ) + } - for (const role of rolesToDelete) { - await MemberOrganizationRepository.removeMemberRole(role, repoOptions) - } - } + // move tags + if (payload.secondary.tags.length > 0) { + await addMemberTags( + txqx, + secondaryMember.id, + payload.secondary.tags.map((t) => t.id), + ) + // check if anything to delete in primary + const tagsToDelete = memberTags.filter( + (t) => !payload.primary.tags.some((pt) => pt.id === t.id), + ) + if (tagsToDelete.length > 0) { + await removeMemberTags( + txqx, + memberId, + tagsToDelete.map((t) => t.id), + ) + } + } - // delete relations from payload, since we already handled those - delete payload.primary.identities - delete payload.primary.username - delete payload.primary.memberOrganizations - delete payload.primary.organizations - delete payload.primary.tags - delete payload.primary.notes - delete payload.primary.tasks - delete payload.primary.affiliations + // move tasks + if (payload.secondary.tasks.length > 0) { + await addMemberTasks( + txqx, + secondaryMember.id, + payload.secondary.tasks.map((t) => t.id), + ) + // check if anything to delete in primary + const tasksToDelete = memberTasks.filter( + (t) => !payload.primary.tasks.some((pt) => pt.id === t.id), + ) + if (tasksToDelete.length > 0) { + await removeMemberTasks( + txqx, + memberId, + tasksToDelete.map((t) => t.id), + ) + } + } - // update rest of the primary member fields - await MemberRepository.update(memberId, payload.primary, repoOptions) + // move notes + if (payload.secondary.notes.length > 0) { + await addMemberNotes( + txqx, + secondaryMember.id, + payload.secondary.notes.map((n) => n.id), + ) + // check if anything to delete in primary + const notesToDelete = memberNotes.filter( + (n) => !payload.primary.notes.some((pn) => pn.id === n.id), + ) + if (notesToDelete.length > 0) { + await removeMemberNotes( + txqx, + memberId, + notesToDelete.map((n) => n.id), + ) + } + } + + // move memberOrganizations + if (payload.secondary.memberOrganizations.length > 0) { + const nonExistingOrganizationIds = await OrganizationRepository.findNonExistingIds( + payload.secondary.memberOrganizations.map((o) => o.organizationId), + repoOptions, + ) + for (const role of payload.secondary.memberOrganizations.filter( + (r) => !nonExistingOrganizationIds.includes(r.organizationId), + )) { + await MemberOrganizationRepository.addMemberRole( + { ...role, memberId: secondaryMember.id }, + repoOptions, + ) + } - // add primary and secondary to no merge so they don't get suggested again - await MemberRepository.addNoMerge(memberId, secondaryMember.id, repoOptions) + const memberOrganizations = await MemberOrganizationRepository.findMemberRoles( + member.id, + repoOptions, + ) + // check if anything to delete in primary + const rolesToDelete = memberOrganizations.filter( + (r) => + r.source !== 'ui' && + !payload.primary.memberOrganizations.some( + (pr) => + pr.organizationId === r.organizationId && + pr.title === r.title && + pr.dateStart === r.dateStart && + pr.dateEnd === r.dateEnd, + ), + ) + + for (const role of rolesToDelete) { + await MemberOrganizationRepository.removeMemberRole(role, repoOptions) + } + } + + // delete relations from payload, since we already handled those + delete payload.primary.identities + delete payload.primary.username + delete payload.primary.memberOrganizations + delete payload.primary.organizations + delete payload.primary.tags + delete payload.primary.notes + delete payload.primary.tasks + delete payload.primary.affiliations + + captureNewState({ + primary: payload.primary, + secondary: { + ...payload.secondary, + ...secondaryMember, + }, + }) + + // update rest of the primary member fields + await MemberRepository.update(memberId, payload.primary, repoOptions) + + // add primary and secondary to no merge so they don't get suggested again + await MemberRepository.addNoMerge(memberId, secondaryMember.id, repoOptions) + + // trigger entity-merging-worker to move activities in the background + await SequelizeRepository.commitTransaction(tx) + + return { member, secondaryMember } + }), + ) await MergeActionsRepository.setMergeAction( MergeActionType.MEMBER, member.id, secondaryMember.id, - repoOptions, + this.options, { step: MergeActionStep.UNMERGE_SYNC_DONE, }, ) - // trigger entity-merging-worker to move activities in the background - await SequelizeRepository.commitTransaction(tx) - // 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', diff --git a/backend/src/services/organizationService.ts b/backend/src/services/organizationService.ts index 933e39ff8f..bfc9c500f4 100644 --- a/backend/src/services/organizationService.ts +++ b/backend/src/services/organizationService.ts @@ -1,4 +1,8 @@ -import { captureApiChange, organizationMergeAction } from '@crowd/audit-logs' +import { + captureApiChange, + organizationMergeAction, + organizationUnmergeAction, +} from '@crowd/audit-logs' import { Error400, websiteNormalizer } from '@crowd/common' import { hasLfxMembership } from '@crowd/data-access-layer/src/lfx_memberships' import { LoggerBase } from '@crowd/logging' @@ -216,106 +220,130 @@ export default class OrganizationService extends LoggerBase { let tx try { - const organization = await OrganizationRepository.findById(organizationId, this.options) + const { organization, secondaryOrganization } = await captureApiChange( + this.options, + organizationUnmergeAction(organizationId, async (captureOldState, captureNewState) => { + const organization = await OrganizationRepository.findById(organizationId, this.options) - const repoOptions: IRepositoryOptions = - await SequelizeRepository.createTransactionalRepositoryOptions(this.options) - tx = repoOptions.transaction + captureOldState({ + primary: organization, + }) - // remove identities in secondary organization from primary - await OrganizationRepository.removeIdentitiesFromOrganization( - organizationId, - payload.secondary.identities.filter( - (i) => - i.verified === undefined || // backwards compatibility for old identity backups - i.verified === true || - (i.verified === false && - !payload.primary.identities.some( - (pi) => - pi.verified === false && - pi.platform === i.platform && - pi.value === i.value && - pi.type === i.type, - )), - ), - repoOptions, - ) + const repoOptions: IRepositoryOptions = + await SequelizeRepository.createTransactionalRepositoryOptions(this.options) + tx = repoOptions.transaction - // create the secondary org - const secondaryOrganization = await OrganizationRepository.create( - payload.secondary, - repoOptions, - ) + // remove identities in secondary organization from primary + await OrganizationRepository.removeIdentitiesFromOrganization( + organizationId, + payload.secondary.identities.filter( + (i) => + i.verified === undefined || // backwards compatibility for old identity backups + i.verified === true || + (i.verified === false && + !payload.primary.identities.some( + (pi) => + pi.verified === false && + pi.platform === i.platform && + pi.value === i.value && + pi.type === i.type, + )), + ), + repoOptions, + ) - await MergeActionsRepository.add( - MergeActionType.ORG, - organizationId, - secondaryOrganization.id, - this.options, - MergeActionStep.UNMERGE_STARTED, - MergeActionState.IN_PROGRESS, - ) + // create the secondary org + const secondaryOrganization = await OrganizationRepository.create( + payload.secondary, + repoOptions, + ) - if (payload.mergeActionId) { - const mergeAction = await MergeActionsRepository.findById( - payload.mergeActionId, - this.options, - ) + await MergeActionsRepository.add( + MergeActionType.ORG, + organizationId, + secondaryOrganization.id, + this.options, + MergeActionStep.UNMERGE_STARTED, + MergeActionState.IN_PROGRESS, + ) - if (mergeAction.unmergeBackup.secondary.memberOrganizations.length > 0) { - for (const role of mergeAction.unmergeBackup.secondary.memberOrganizations) { - await MemberOrganizationRepository.addMemberRole( - { ...role, organizationId: secondaryOrganization.id }, - repoOptions, + if (payload.mergeActionId) { + const mergeAction = await MergeActionsRepository.findById( + payload.mergeActionId, + this.options, ) + + if (mergeAction.unmergeBackup.secondary.memberOrganizations.length > 0) { + for (const role of mergeAction.unmergeBackup.secondary.memberOrganizations) { + await MemberOrganizationRepository.addMemberRole( + { ...role, organizationId: secondaryOrganization.id }, + repoOptions, + ) + } + + const memberOrganizations = + await MemberOrganizationRepository.findRolesInOrganization( + organization.id, + repoOptions, + ) + + const primaryUnmergedRoles = await MemberOrganizationService.unmergeRoles( + memberOrganizations, + mergeAction.unmergeBackup.primary.memberOrganizations, + mergeAction.unmergeBackup.secondary.memberOrganizations, + MemberRoleUnmergeStrategy.SAME_ORGANIZATION, + ) + + // check if anything to delete in primary + const rolesToDelete = memberOrganizations.filter( + (r) => + r.source !== 'ui' && + !primaryUnmergedRoles.some( + (pr) => + pr.memberId === r.memberId && + pr.title === r.title && + pr.dateStart === r.dateStart && + pr.dateEnd === r.dateEnd, + ), + ) + + for (const role of rolesToDelete) { + await MemberOrganizationRepository.removeMemberRole(role, repoOptions) + } + } } - const memberOrganizations = await MemberOrganizationRepository.findRolesInOrganization( - organization.id, - repoOptions, - ) + // delete identity related stuff, we already moved these + delete payload.primary.identities - const primaryUnmergedRoles = await MemberOrganizationService.unmergeRoles( - memberOrganizations, - mergeAction.unmergeBackup.primary.memberOrganizations, - mergeAction.unmergeBackup.secondary.memberOrganizations, - MemberRoleUnmergeStrategy.SAME_ORGANIZATION, - ) + captureNewState({ + primary: payload.primary, + secondary: secondaryOrganization, + }) - // check if anything to delete in primary - const rolesToDelete = memberOrganizations.filter( - (r) => - r.source !== 'ui' && - !primaryUnmergedRoles.some( - (pr) => - pr.memberId === r.memberId && - pr.title === r.title && - pr.dateStart === r.dateStart && - pr.dateEnd === r.dateEnd, - ), + // update rest of the primary org fields + await OrganizationRepository.update( + organizationId, + payload.primary, + repoOptions, + false, + false, ) - for (const role of rolesToDelete) { - await MemberOrganizationRepository.removeMemberRole(role, repoOptions) - } - } - } + // add primary and secondary to no merge so they don't get suggested again + await OrganizationRepository.addNoMerge( + organizationId, + secondaryOrganization.id, + repoOptions, + ) - // delete identity related stuff, we already moved these - delete payload.primary.identities + // trigger entity-merging-worker to move activities in the background + await SequelizeRepository.commitTransaction(tx) - // update rest of the primary org fields - await OrganizationRepository.update( - organizationId, - payload.primary, - repoOptions, - false, - false, + return { organization, secondaryOrganization } + }), ) - // add primary and secondary to no merge so they don't get suggested again - await OrganizationRepository.addNoMerge(organizationId, secondaryOrganization.id, repoOptions) - await MergeActionsRepository.setMergeAction( MergeActionType.ORG, organizationId, @@ -326,9 +354,6 @@ export default class OrganizationService extends LoggerBase { }, ) - // trigger entity-merging-worker to move activities in the background - await SequelizeRepository.commitTransaction(tx) - // 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', diff --git a/frontend/src/modules/lf/config/audit-logs/filters/action/options.ts b/frontend/src/modules/lf/config/audit-logs/filters/action/options.ts index c5728b6cda..685cf7947d 100644 --- a/frontend/src/modules/lf/config/audit-logs/filters/action/options.ts +++ b/frontend/src/modules/lf/config/audit-logs/filters/action/options.ts @@ -8,6 +8,10 @@ const options: SelectFilterOptionGroup[] = [ label: 'Profiles merged', value: 'contributors-merged', }, + { + label: 'Profiles unmerged', + value: 'contributors-unmerged', + }, { label: 'Profile identities updated', value: 'contributor-identities-updated', @@ -37,6 +41,10 @@ const options: SelectFilterOptionGroup[] = [ label: 'Organizations merged', value: 'organizations-merged', }, + { + label: 'Organizations unmerged', + value: 'organizations-unmerged', + }, { label: 'Organization identities updated', value: 'organization-identities-updated', diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts index 48cf4dab35..ac563e1d42 100644 --- a/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/index.ts @@ -7,10 +7,12 @@ import membersEditManualAffiliation from './members-edit-manual-affiliation'; import membersEditOrganizations from './members-edit-organizations'; import membersEditProfile from './members-edit-profile'; import membersMerge from './members-merge'; +import membersUnmerge from './members-unmerge'; import organizationsCreate from './organizations-create'; import organizationsEditIdentities from './organizations-edit-identities'; import organizationsEditProfile from './organizations-edit-profile'; import organizationsMerge from './organizations-merge'; +import organizationsUnmerge from './organizations-unmerge'; export interface LogRenderingConfig { label: string; @@ -39,10 +41,12 @@ export const logRenderingConfig: Record = { [ActionType.MEMBERS_EDIT_ORGANIZATIONS]: membersEditOrganizations, [ActionType.MEMBERS_EDIT_PROFILE]: membersEditProfile, [ActionType.MEMBERS_MERGE]: membersMerge, + [ActionType.MEMBERS_UNMERGE]: membersUnmerge, // Organizations [ActionType.ORGANIZATIONS_CREATE]: organizationsCreate, [ActionType.ORGANIZATIONS_EDIT_IDENTITIES]: organizationsEditIdentities, [ActionType.ORGANIZATIONS_EDIT_PROFILE]: organizationsEditProfile, [ActionType.ORGANIZATIONS_MERGE]: organizationsMerge, + [ActionType.ORGANIZATIONS_UNMERGE]: organizationsUnmerge, }; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/members-unmerge.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-unmerge.ts new file mode 100644 index 0000000000..621b269a97 --- /dev/null +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/members-unmerge.ts @@ -0,0 +1,42 @@ +import { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; + +const membersMerge: LogRenderingConfig = { + label: 'Profiles unmerged', + changes: (log) => { + const primary = log.oldState?.primary; + const secondary = log.newState?.secondary; + const merged = log.newState?.primary; + return { + removals: merged ? [ + `${primary?.displayName} ・ ID: ${primary?.id}`, + `${secondary?.displayName} ・ ID: ${secondary?.id}`, + ] : [], + additions: merged ? [ + `${merged?.displayName || primary?.displayName} ・ ID: ${merged?.id || primary?.id}`, + ] : [], + changes: [], + }; + }, + description: (log) => { + const member = log.newState?.primary?.displayName || log.oldState?.primary?.displayName; + + if (member) { + return `${member}
ID: ${log.entityId}`; + } + + return ''; + }, + properties: (log) => { + const member = log.newState?.primary?.displayName || log.oldState?.primary?.displayName; + + if (member) { + return [{ + label: 'Profile', + value: `${member}
ID: ${log.entityId}`, + }]; + } + return []; + }, +}; + +export default membersMerge; diff --git a/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-unmerge.ts b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-unmerge.ts new file mode 100644 index 0000000000..e37640a2eb --- /dev/null +++ b/frontend/src/modules/lf/config/audit-logs/log-rendering/organizations-unmerge.ts @@ -0,0 +1,39 @@ +import { LogRenderingConfig } from '@/modules/lf/config/audit-logs/log-rendering/index'; + +const organizationsMerge: LogRenderingConfig = { + label: 'Organizations unmerged', + changes: (log) => { + const primary = log.oldState?.primary; + const secondary = log.newState?.secondary; + const merged = log.newState?.primary; + return { + removals: merged ? [ + `${primary?.displayName} ・ ID: ${primary?.id}`, + `${secondary?.displayName} ・ ID: ${secondary?.id}`, + ] : [], + additions: merged ? [ + `${merged?.displayName || primary.displayName} ・ ID: ${merged?.id || primary.id}`, + ] : [], + changes: [], + }; + }, + description: (log) => { + const organization = log.newState?.primary?.displayName || log.oldState?.primary?.displayName; + if (organization) { + return `${organization}
ID: ${log.entityId}`; + } + return ''; + }, + properties: (log) => { + const organization = log.newState?.primary?.displayName || log.oldState?.primary?.displayName; + if (organization) { + return [{ + label: 'Organization', + value: `${organization}
ID: ${log.entityId}`, + }]; + } + return []; + }, +}; + +export default organizationsMerge; diff --git a/frontend/src/modules/lf/segments/types/AuditLog.ts b/frontend/src/modules/lf/segments/types/AuditLog.ts index d14136638e..e895962500 100644 --- a/frontend/src/modules/lf/segments/types/AuditLog.ts +++ b/frontend/src/modules/lf/segments/types/AuditLog.ts @@ -1,11 +1,13 @@ export enum ActionType { MEMBERS_MERGE = 'members-merge', + MEMBERS_UNMERGE = 'members-unmerge', MEMBERS_EDIT_IDENTITIES = 'members-edit-identities', MEMBERS_EDIT_ORGANIZATIONS = 'members-edit-organizations', MEMBERS_EDIT_MANUAL_AFFILIATION = 'members-edit-manual-affiliation', MEMBERS_EDIT_PROFILE = 'members-edit-profile', MEMBERS_CREATE = 'members-create', ORGANIZATIONS_MERGE = 'organizations-merge', + ORGANIZATIONS_UNMERGE = 'organizations-unmerge', ORGANIZATIONS_EDIT_IDENTITIES = 'organizations-edit-identities', ORGANIZATIONS_EDIT_PROFILE = 'organizations-edit-profile', ORGANIZATIONS_CREATE = 'organizations-create', diff --git a/services/libs/audit-logs/src/actions.ts b/services/libs/audit-logs/src/actions.ts index 7590d10dd5..1fbf2cad10 100644 --- a/services/libs/audit-logs/src/actions.ts +++ b/services/libs/audit-logs/src/actions.ts @@ -91,6 +91,13 @@ export function memberMergeAction(entityId: string, captureFn: CaptureFn): return modifyEntityAction(ActionType.MEMBERS_MERGE, entityId, captureFn) } +export function memberUnmergeAction( + entityId: string, + captureFn: CaptureFn, +): BuildActionFn { + return modifyEntityAction(ActionType.MEMBERS_UNMERGE, entityId, captureFn) +} + export function memberCreateAction( entityId: string, captureFn: CaptureOneFn, @@ -147,6 +154,13 @@ export function organizationMergeAction( return modifyEntityAction(ActionType.ORGANIZATIONS_MERGE, entityId, captureFn) } +export function organizationUnmergeAction( + entityId: string, + captureFn: CaptureFn, +): BuildActionFn { + return modifyEntityAction(ActionType.ORGANIZATIONS_UNMERGE, entityId, captureFn) +} + export function memberEditAffiliationsAction( entityId: string, captureFn: CaptureFn, diff --git a/services/libs/data-access-layer/src/audit_logs/repo.ts b/services/libs/data-access-layer/src/audit_logs/repo.ts index 57c684eea4..75c919921f 100644 --- a/services/libs/data-access-layer/src/audit_logs/repo.ts +++ b/services/libs/data-access-layer/src/audit_logs/repo.ts @@ -26,12 +26,14 @@ export enum EntityType { export enum ActionType { MEMBERS_MERGE = 'members-merge', + MEMBERS_UNMERGE = 'members-unmerge', MEMBERS_EDIT_IDENTITIES = 'members-edit-identities', MEMBERS_EDIT_ORGANIZATIONS = 'members-edit-organizations', MEMBERS_EDIT_MANUAL_AFFILIATION = 'members-edit-manual-affiliation', MEMBERS_EDIT_PROFILE = 'members-edit-profile', MEMBERS_CREATE = 'members-create', ORGANIZATIONS_MERGE = 'organizations-merge', + ORGANIZATIONS_UNMERGE = 'organizations-unmerge', ORGANIZATIONS_EDIT_IDENTITIES = 'organizations-edit-identities', ORGANIZATIONS_EDIT_PROFILE = 'organizations-edit-profile', ORGANIZATIONS_CREATE = 'organizations-create', @@ -41,12 +43,14 @@ export enum ActionType { const ACTION_TYPES_ENTITY_TYPES = { [ActionType.MEMBERS_MERGE]: EntityType.MEMBER, + [ActionType.MEMBERS_UNMERGE]: EntityType.MEMBER, [ActionType.MEMBERS_EDIT_IDENTITIES]: EntityType.MEMBER, [ActionType.MEMBERS_EDIT_ORGANIZATIONS]: EntityType.MEMBER, [ActionType.MEMBERS_EDIT_MANUAL_AFFILIATION]: EntityType.MEMBER, [ActionType.MEMBERS_EDIT_PROFILE]: EntityType.MEMBER, [ActionType.MEMBERS_CREATE]: EntityType.MEMBER, [ActionType.ORGANIZATIONS_MERGE]: EntityType.ORGANIZATION, + [ActionType.ORGANIZATIONS_UNMERGE]: EntityType.ORGANIZATION, [ActionType.ORGANIZATIONS_EDIT_IDENTITIES]: EntityType.ORGANIZATION, [ActionType.ORGANIZATIONS_EDIT_PROFILE]: EntityType.ORGANIZATION, [ActionType.ORGANIZATIONS_CREATE]: EntityType.ORGANIZATION,