Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
alter table "organizations"
add column "isAffiliationBlocked" boolean not null default false;

5 changes: 5 additions & 0 deletions backend/src/database/models/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export default (sequelize) => {
defaultValue: false,
allowNull: false,
},
isAffiliationBlocked: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false,
},

lastEnrichedAt: {
type: DataTypes.DATE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
changeOverride as changeMemberOrganizationAffiliationOverride,
changeMemberOrganizationAffiliationOverrides,
findMemberAffiliationOverrides,
findPrimaryWorkExperiencesOfMember,
} from '@crowd/data-access-layer/src/member_organization_affiliation_overrides'
Expand All @@ -15,10 +15,11 @@ class MemberOrganizationAffiliationOverridesRepository {
static async changeOverride(data: IChangeAffiliationOverrideData, options: IRepositoryOptions) {
const qx = SequelizeRepository.getQueryExecutor(options)

await changeMemberOrganizationAffiliationOverride(qx, data)
await changeMemberOrganizationAffiliationOverrides(qx, [data])
const overrides = await findMemberAffiliationOverrides(qx, data.memberId, [
data.memberOrganizationId,
])

return overrides[0]
}

Expand Down
4 changes: 4 additions & 0 deletions backend/src/database/repositories/organizationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class OrganizationRepository {
['tags', 'o."tags"'],
['type', 'o."type"'],
['isTeamOrganization', 'o."isTeamOrganization"'],
['isAffiliationBlocked', 'o."isAffiliationBlocked"'],

// basic fields for querying
['displayName', 'o."displayName"'],
Expand Down Expand Up @@ -135,6 +136,7 @@ class OrganizationRepository {
'logo',
'importHash',
'isTeamOrganization',
'isAffiliationBlocked',
'lastEnrichedAt',
'manuallyCreated',
]),
Expand Down Expand Up @@ -228,6 +230,7 @@ class OrganizationRepository {
static ORGANIZATION_UPDATE_COLUMNS = [
'importHash',
'isTeamOrganization',
'isAffiliationBlocked',
'headline',
'lastEnrichedAt',

Expand Down Expand Up @@ -256,6 +259,7 @@ class OrganizationRepository {
logo: (a, b) => a === b,
location: (a, b) => a === b,
isTeamOrganization: (a, b) => a === b,
isAffiliationBlocked: (a, b) => a === b,
attributes: (a, b) => lodash.isEqual(a, b),
}

Expand Down
3 changes: 2 additions & 1 deletion backend/src/services/member/memberAffiliationsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ export default class MemberAffiliationsService extends LoggerBase {
}
}

const override = MemberOrganizationAffiliationOverridesRepository.changeOverride(
const override = await MemberOrganizationAffiliationOverridesRepository.changeOverride(
data,
this.options,
)

const commonMemberService = new CommonMemberService(
optionsQx(this.options),
this.options.temporal,
Expand Down
22 changes: 20 additions & 2 deletions backend/src/services/member/memberOrganizationsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Error404 } from '@crowd/common'
import { CommonMemberService } from '@crowd/common_services'
import {
OrganizationField,
checkOrganizationAffiliationPolicy,
cleanSoftDeletedMemberOrganization,
createMemberOrganization,
deleteMemberOrganizations,
Expand All @@ -13,7 +14,10 @@ import {
queryOrgs,
updateMemberOrganization,
} from '@crowd/data-access-layer'
import { findMemberAffiliationOverrides } from '@crowd/data-access-layer/src/member_organization_affiliation_overrides'
import {
changeMemberOrganizationAffiliationOverrides,
findMemberAffiliationOverrides,
} from '@crowd/data-access-layer/src/member_organization_affiliation_overrides'
import { LoggerBase } from '@crowd/logging'
import { IMemberOrganization, IOrganization, IRenderFriendlyMemberOrganization } from '@crowd/types'

Expand Down Expand Up @@ -164,7 +168,21 @@ export default class MemberOrganizationsService extends LoggerBase {
await cleanSoftDeletedMemberOrganization(qx, memberId, data.organizationId, data)

// Create new member organization
await createMemberOrganization(qx, memberId, data)
const newMemberOrgId = await createMemberOrganization(qx, memberId, data)

// Check if organization affiliation is blocked
const isAffiliationBlocked = await checkOrganizationAffiliationPolicy(qx, data.organizationId)

// If organization affiliation is blocked, create an affiliation override
if (newMemberOrgId && isAffiliationBlocked) {
await changeMemberOrganizationAffiliationOverrides(qx, [
{
memberId,
memberOrganizationId: newMemberOrgId,
allowAffiliation: false,
},
])
}

// Start affiliation recalculation within the same transaction
await this.commonMemberService.startAffiliationRecalculation(memberId, [data.organizationId])
Expand Down
78 changes: 62 additions & 16 deletions backend/src/services/organizationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ import {
organizationMergeAction,
organizationUnmergeAction,
} from '@crowd/audit-logs'
import { Error400, Error409, mergeObjects, normalizeHostname } from '@crowd/common'
import { Error400, Error404, Error409, mergeObjects, normalizeHostname } from '@crowd/common'
import {
addMemberRole,
moveMembersBetweenOrganizations,
optionsQx,
removeMemberRole,
} from '@crowd/data-access-layer'
import { hasLfxMembership } from '@crowd/data-access-layer/src/lfx_memberships'
import { applyOrganizationAffiliationPolicyToMembers } from '@crowd/data-access-layer/src/member_organization_affiliation_overrides'
import {
addMergeAction,
queryMergeActions,
setMergeAction,
} from '@crowd/data-access-layer/src/mergeActions/repo'
import {
OrganizationField,
addOrgsToSegments,
findOrgAttributes,
findOrgById,
upsertOrgIdentities,
} from '@crowd/data-access-layer/src/organizations'
import { LoggerBase } from '@crowd/logging'
Expand Down Expand Up @@ -77,6 +80,7 @@ export default class OrganizationService extends LoggerBase {

'joinedAt',
'isTeamOrganization',
'isAffiliationBlocked',
'manuallyCreated',

'activityCount',
Expand Down Expand Up @@ -498,11 +502,13 @@ export default class OrganizationService extends LoggerBase {
throw new Error409(this.options.language, 'merge.errors.multiple', mergeActions[0].state)
}

let blockAffiliations = false

try {
const { original, toMerge } = await captureApiChange(
this.options,
organizationMergeAction(originalId, async (captureOldState, captureNewState) => {
this.log.info('[Merge Organizations] - Finding organizations! ')
this.log.info('[Merge Organizations] - Finding organizations!')
let original = await OrganizationRepository.findById(originalId, this.options, segmentId)
let toMerge = await OrganizationRepository.findById(toMergeId, this.options, segmentId)

Expand All @@ -517,7 +523,7 @@ export default class OrganizationService extends LoggerBase {
await OrganizationRepository.addNoMerge(originalId, toMergeId, this.options)
this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Skipping merge of two LFX membership orgs! ',
'[Merge Organizations] - Skipping merge of two LFX membership orgs!',
)

return {
Expand All @@ -530,7 +536,7 @@ export default class OrganizationService extends LoggerBase {
throw new Error400(this.options.language, 'merge.errors.mergeLfxSecondary')
}

this.log.info({ originalId, toMergeId }, '[Merge Organizations] - Found organizations! ')
this.log.info({ originalId, toMergeId }, '[Merge Organizations] - Found organizations!')

captureOldState({
primary: original,
Expand Down Expand Up @@ -607,7 +613,7 @@ export default class OrganizationService extends LoggerBase {

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Moving identities between organizations! ',
'[Merge Organizations] - Moving identities between organizations!',
)

// move non existing identities
Expand Down Expand Up @@ -636,7 +642,7 @@ export default class OrganizationService extends LoggerBase {

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Generating merge object! ',
'[Merge Organizations] - Generating merge object!',
)

// Performs a merge and returns the fields that were changed so we can update
Expand All @@ -646,14 +652,14 @@ export default class OrganizationService extends LoggerBase {

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Generating merge object done! ',
'[Merge Organizations] - Generating merge object done!',
)

const txService = new OrganizationService(repoOptions as IServiceOptions)

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Updating original organisation! ',
'[Merge Organizations] - Updating original organisation!',
)

// check if website is being updated, if yes we need to set toMerge.website to null before doing the update
Expand All @@ -663,29 +669,29 @@ export default class OrganizationService extends LoggerBase {
}

// Update original organization
await txService.update(originalId, toUpdate, false, false)
await txService.update(originalId, toUpdate, false, false, false, true)

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Updating original organisation done! ',
'[Merge Organizations] - Updating original organisation done!',
)

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Moving members to original organisation! ',
'[Merge Organizations] - Moving members to original organisation!',
)

// update members that belong to source organization to destinati
await moveMembersBetweenOrganizations(optionsQx(repoOptions), toMergeId, originalId)

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Moving members to original organisation done! ',
'[Merge Organizations] - Moving members to original organisation done!',
)

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Including original organisation into secondary organisation segments! ',
'[Merge Organizations] - Including original organisation into secondary organisation segments!',
)

const secondMemberSegments = await OrganizationRepository.getOrganizationSegments(
Expand All @@ -703,12 +709,27 @@ export default class OrganizationService extends LoggerBase {

this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Including original organisation into secondary organisation segments done! ',
'[Merge Organizations] - Including original organisation into secondary organisation segments done!',
)

if (toUpdate.isAffiliationBlocked) {
this.log.info(
{ originalId, toMergeId },
'[Merge Organizations] - Organization wide affiliation block detected!',
)

await applyOrganizationAffiliationPolicyToMembers(
optionsQx(repoOptions),
originalId,
false,
)

blockAffiliations = true
}

await SequelizeRepository.commitTransaction(tx)

this.log.info({ originalId, toMergeId }, '[Merge Organizations] - Transaction commited! ')
this.log.info({ originalId, toMergeId }, '[Merge Organizations] - Transaction commited!')

await setMergeAction(
optionsQx(this.options),
Expand All @@ -735,6 +756,7 @@ export default class OrganizationService extends LoggerBase {
toMergeId,
original.displayName,
toMerge.displayName,
blockAffiliations,
this.options.currentUser.id,
],
})
Expand Down Expand Up @@ -772,6 +794,7 @@ export default class OrganizationService extends LoggerBase {
createdById: keepPrimary,
updatedById: keepPrimary,
isTeamOrganization: keepPrimaryIfExists,
isAffiliationBlocked: keepPrimary,
lastEnrichedAt: keepPrimary,
searchSyncedAt: keepPrimary,
manuallyCreated: keepPrimary,
Expand Down Expand Up @@ -946,15 +969,29 @@ export default class OrganizationService extends LoggerBase {
overrideIdentities = false,
syncToOpensearch = true,
manualChange = false,
skipAffiliationBlockUpdate = false,
) {
let tx
let recalculateAffiliations = false

try {
const repoOptions = await SequelizeRepository.createTransactionalRepositoryOptions(
this.options,
)

const qx = SequelizeRepository.getQueryExecutor(repoOptions)
tx = repoOptions.transaction

// findOrgById to get the existing organization
const existingOrg = await findOrgById(qx, id, [
OrganizationField.ID,
OrganizationField.IS_AFFILIATION_BLOCKED,
])

if (!existingOrg) {
throw new Error404(this.options.language, 'Organization not found!')
}

if (data.identities) {
// Normalize the website identities
for (const i of data.identities.filter((i) =>
Expand Down Expand Up @@ -1021,6 +1058,15 @@ export default class OrganizationService extends LoggerBase {
manualChange,
)

if (
!skipAffiliationBlockUpdate &&
typeof data.isAffiliationBlocked === 'boolean' &&
data.isAffiliationBlocked !== existingOrg.isAffiliationBlocked
) {
await applyOrganizationAffiliationPolicyToMembers(qx, record.id, !data.isAffiliationBlocked)
recalculateAffiliations = true
}

await SequelizeRepository.commitTransaction(tx)

await this.options.temporal.workflow.start('organizationUpdate', {
Expand All @@ -1035,7 +1081,7 @@ export default class OrganizationService extends LoggerBase {
organization: {
id: record.id,
},
recalculateAffiliations: false,
recalculateAffiliations,
syncOptions: {
doSync: syncToOpensearch,
withAggs: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
<lf-icon name="people-group" />
{{ org.isTeamOrganization ? 'Unmark' : 'Mark' }} as team organization
</lf-dropdown-item>
<lf-dropdown-item @click="toggleOrganizationAffiliations(org)">
<lf-icon name="ban" />
{{ org.isAffiliationBlocked ? 'Enable' : 'Block' }} affiliations
</lf-dropdown-item>
<lf-dropdown-separator />
<lf-dropdown-item type="danger" @click="deleteOrganization(org)">
<lf-icon name="trash-can" />
Expand Down Expand Up @@ -162,6 +166,23 @@ const markAsTeamOrganization = (organization: any) => {
ToastStore.error('Something went wrong');
});
};

const toggleOrganizationAffiliations = (organization: any) => {
ToastStore.info('Organization is being updated');
OrganizationService.update(organization.id, {
isAffiliationBlocked: !organization.isAffiliationBlocked,
}, organization.segments)
.then(() => {
ToastStore.closeAll();
ToastStore.success('Organization updated successfully');
emit('reload');
})
.catch(() => {
ToastStore.closeAll();
ToastStore.error('Something went wrong');
});
};

const deleteOrganization = (organization: any) => {
ConfirmDialog({
type: 'danger',
Expand Down
Loading