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
Expand Up @@ -1155,6 +1155,7 @@ class OrganizationRepository {
OrganizationField.INDUSTRY,
OrganizationField.FOUNDED,
OrganizationField.IS_TEAM_ORGANIZATION,
OrganizationField.IS_AFFILIATION_BLOCKED,
OrganizationField.MANUALLY_CREATED,
])

Expand Down
69 changes: 46 additions & 23 deletions backend/src/services/organizationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
findOrgById,
upsertOrgIdentities,
} from '@crowd/data-access-layer/src/organizations'
import { findSegmentByName } from '@crowd/data-access-layer/src/segments'
import { LoggerBase } from '@crowd/logging'
import { WorkflowIdReusePolicy } from '@crowd/temporal'
import {
Expand Down Expand Up @@ -913,6 +914,20 @@ export default class OrganizationService extends LoggerBase {

await upsertOrgIdentities(qx, record.id, data.identities)
} else {
if (data.displayName) {
// Block organization affiliation if a segment (project, subproject, or project group)
// has the same name as the organization when creating one.
const segment = await findSegmentByName(qx, data.displayName)
if (segment) {
this.log.info(
{ displayName: data.displayName },
'Found segment with the same name as the organization, blocking affiliation!',
)

data.isAffiliationBlocked = true
}
}

record = await OrganizationRepository.create(data, txOptions)
telemetryTrack(
'Organization created',
Expand Down Expand Up @@ -944,10 +959,9 @@ export default class OrganizationService extends LoggerBase {

await SequelizeRepository.commitTransaction(transaction)

if (syncOptions.doSync) {
const searchSyncService = new SearchSyncService(this.options, syncOptions.mode)
await searchSyncService.triggerOrganizationSync(record.id)
}
await this.startOrganizationUpdateWorkflow(record.id, {
syncToOpensearch: syncOptions.doSync,
})

return result
} catch (error) {
Expand Down Expand Up @@ -1069,25 +1083,9 @@ export default class OrganizationService extends LoggerBase {

await SequelizeRepository.commitTransaction(tx)

await this.options.temporal.workflow.start('organizationUpdate', {
taskQueue: 'profiles',
workflowId: `${TemporalWorkflowId.ORGANIZATION_UPDATE}/${id}`,
workflowIdReusePolicy: WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING,
retry: {
maximumAttempts: 10,
},
args: [
{
organization: {
id: record.id,
},
recalculateAffiliations,
syncOptions: {
doSync: syncToOpensearch,
withAggs: false,
},
},
],
await this.startOrganizationUpdateWorkflow(record.id, {
recalculateAffiliations,
syncToOpensearch,
})

return record
Expand Down Expand Up @@ -1232,4 +1230,29 @@ export default class OrganizationService extends LoggerBase {
throw error
}
}

async startOrganizationUpdateWorkflow(
organizationId: string,
{ syncToOpensearch = false, recalculateAffiliations = false },
) {
await this.options.temporal.workflow.start('organizationUpdate', {
taskQueue: 'profiles',
workflowId: `${TemporalWorkflowId.ORGANIZATION_UPDATE}/${organizationId}`,
workflowIdReusePolicy: WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING,
retry: {
maximumAttempts: 10,
},
args: [
{
organization: {
id: organizationId,
},
recalculateAffiliations,
syncOptions: {
doSync: syncToOpensearch,
},
},
],
})
}
}
104 changes: 99 additions & 5 deletions backend/src/services/segmentService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Transaction } from 'sequelize'

import { Error400, validateNonLfSlug } from '@crowd/common'
import { QueryExecutor } from '@crowd/data-access-layer'
import {
QueryExecutor,
findOrganizationsByName,
updateOrganization,
} from '@crowd/data-access-layer'
import { ICreateInsightsProject, findBySlug } from '@crowd/data-access-layer/src/collections'
import { applyOrganizationAffiliationPolicyToMembers } from '@crowd/data-access-layer/src/member_organization_affiliation_overrides'
import {
buildSegmentActivityTypes,
isSegmentSubproject,
Expand All @@ -25,6 +30,7 @@ import SequelizeRepository from '../database/repositories/sequelizeRepository'

import { IServiceOptions } from './IServiceOptions'
import { CollectionService } from './collectionService'
import OrganizationService from './organizationService'

interface UnnestedActivityTypes {
[key: string]: any
Expand Down Expand Up @@ -131,8 +137,24 @@ export default class SegmentService extends LoggerBase {
transaction,
)

// Block org affiliation if project group name matches an organization name
const orgIds = await this.blockOrganizationAffiliationIfSegmentNameMatches(
data.name,
transaction,
)

await SequelizeRepository.commitTransaction(transaction)

const organizationService = new OrganizationService(this.options)

for (const orgId of orgIds) {
// Trigger org update workflow to recalculate affiliations
await organizationService.startOrganizationUpdateWorkflow(orgId, {
syncToOpensearch: true,
recalculateAffiliations: true,
})
}

return await this.findById(projectGroup.id)
} catch (error) {
await SequelizeRepository.rollbackTransaction(transaction)
Expand Down Expand Up @@ -192,8 +214,24 @@ export default class SegmentService extends LoggerBase {
transaction,
)

const organizationService = new OrganizationService(this.options)

// Block org affiliation if project name matches an organization name
const orgIds = await this.blockOrganizationAffiliationIfSegmentNameMatches(
data.name,
transaction,
)

await SequelizeRepository.commitTransaction(transaction)

for (const orgId of orgIds) {
// Trigger org update workflow to recalculate affiliations
await organizationService.startOrganizationUpdateWorkflow(orgId, {
syncToOpensearch: true,
recalculateAffiliations: true,
})
}

return await this.findById(project.id)
} catch (error) {
await SequelizeRepository.rollbackTransaction(transaction)
Expand All @@ -202,10 +240,35 @@ export default class SegmentService extends LoggerBase {
}

async createSubproject(data: SegmentData): Promise<SegmentData> {
return SequelizeRepository.withTx(this.options, async (tx) => {
const qx = SequelizeRepository.getQueryExecutor({ ...this.options, transaction: tx })
return this.createSubprojectInternal(data, qx, tx)
})
const transaction = await SequelizeRepository.createTransaction(this.options)
const qx = SequelizeRepository.getQueryExecutor({ ...this.options, transaction })

try {
const subproject = await this.createSubprojectInternal(data, qx, transaction)

const orgIds = await this.blockOrganizationAffiliationIfSegmentNameMatches(
data.name,
transaction,
)

await SequelizeRepository.commitTransaction(transaction)

if (orgIds?.length) {
const organizationService = new OrganizationService(this.options)

for (const orgId of orgIds) {
await organizationService.startOrganizationUpdateWorkflow(orgId, {
syncToOpensearch: true,
recalculateAffiliations: true,
})
}
}

return await this.findById(subproject.id)
} catch (error) {
await SequelizeRepository.rollbackTransaction(transaction)
throw error
}
}

async createSubprojectInternal(
Expand Down Expand Up @@ -638,6 +701,37 @@ export default class SegmentService extends LoggerBase {
}
}

private async blockOrganizationAffiliationIfSegmentNameMatches(
segmentName: string,
transaction: Transaction,
): Promise<string[]> {
const qx = SequelizeRepository.getQueryExecutor({
...this.options,
transaction,
})

// Check if there is an existing organization with segment name
const organizations = await findOrganizationsByName(qx, segmentName)

if (organizations.length === 0) {
return []
}

const result: string[] = []

for (const o of organizations) {
if (!o.isAffiliationBlocked) {
const updatedOrgId = await updateOrganization(qx, o.id, { isAffiliationBlocked: true })
if (updatedOrgId) {
await applyOrganizationAffiliationPolicyToMembers(qx, updatedOrgId, false)
result.push(updatedOrgId)
}
}
}

return result
}

/**
* Throws an appropriate error message when a segment conflict is detected.
* This method dynamically generates error messages based on the existing conflicting segment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const ORG_DB_FIELDS = [
'importHash',
'location',
'isTeamOrganization',
'isAffiliationBlocked',
'type',
'size',
'headline',
Expand Down
Loading