Skip to content

Commit 4f79805

Browse files
committed
Merge branch 'main' of github.com:peoray/crowd.dev into organization-filters
2 parents 0dc4ce4 + d4d2202 commit 4f79805

File tree

29 files changed

+181
-41
lines changed

29 files changed

+181
-41
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { QueryTypes } from 'sequelize'
2+
import { getServiceLogger } from '@crowd/logging'
3+
import SequelizeRepository from '../../database/repositories/sequelizeRepository'
4+
import OrganizationService from '../../services/organizationService'
5+
import SegmentRepository from '@/database/repositories/segmentRepository'
6+
7+
/* eslint-disable no-continue */
8+
/* eslint-disable @typescript-eslint/no-loop-func */
9+
10+
const log = getServiceLogger()
11+
12+
async function mergeOrganizationsWithSameWebsite(): Promise<void> {
13+
const dbOptions = await SequelizeRepository.getDefaultIRepositoryOptions()
14+
15+
const BATCH_SIZE = 500
16+
17+
let offset
18+
let mergeableOrganizations
19+
20+
const seq = SequelizeRepository.getSequelize(dbOptions)
21+
22+
log.info('Querying database for organizations with same website in a tenant..')
23+
24+
do {
25+
offset = mergeableOrganizations ? offset + BATCH_SIZE : 0
26+
mergeableOrganizations = await seq.query(
27+
`
28+
select
29+
array_agg(o.id order by o."createdAt" asc) as "organizationIds",
30+
o.website,
31+
o."tenantId"
32+
from organizations o
33+
where o.website is not null and o."deletedAt" is null
34+
group by o."tenantId", o.website
35+
having count(o.id) > 1
36+
limit ${BATCH_SIZE}
37+
offset ${offset};`,
38+
{
39+
type: QueryTypes.SELECT,
40+
},
41+
)
42+
43+
log.info(`Found ${mergeableOrganizations.length} mergeable organizations.`)
44+
45+
for (const orgInfo of mergeableOrganizations) {
46+
const segmentRepository = new SegmentRepository({
47+
...dbOptions,
48+
currentTenant: {
49+
id: orgInfo.tenantId,
50+
},
51+
})
52+
53+
const segments = (await segmentRepository.querySubprojects({ limit: null, offset: 0 })).rows
54+
55+
const service = new OrganizationService({
56+
...dbOptions,
57+
currentTenant: {
58+
id: orgInfo.tenantId,
59+
},
60+
currentSegments: segments,
61+
})
62+
63+
const primaryOrganizationId = orgInfo.organizationIds.shift()
64+
for (const orgId of orgInfo.organizationIds) {
65+
log.info(`Merging organization ${orgId} into ${primaryOrganizationId}!`)
66+
await service.merge(primaryOrganizationId, orgId)
67+
}
68+
}
69+
} while (mergeableOrganizations.length > 0)
70+
}
71+
72+
setImmediate(async () => {
73+
log.info('Starting merging organizations with same website!')
74+
await mergeOrganizationsWithSameWebsite()
75+
})

backend/src/database/migrations/U1695669657__organizationsAddWebsiteUniqueIdx.sql

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE UNIQUE INDEX "ix_organizations_tenantId_website_not_null" ON organizations (website, "tenantId") WHERE website IS NOT NULL;

backend/src/database/repositories/organizationRepository.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,10 @@ class OrganizationRepository {
10691069
}
10701070

10711071
const identityParams = identities
1072-
.map((identity) => `('${identity.platform}', '${identity.name}')`)
1072+
.map(
1073+
(identity) =>
1074+
`('${identity.platform.replace(/'/g, "''")}', '${identity.name.replace(/'/g, "''")}')`,
1075+
)
10731076
.join(', ')
10741077

10751078
const results = (await sequelize.query(

backend/src/services/organizationService.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export default class OrganizationService extends LoggerBase {
127127
currentSegments: secondMemberSegments,
128128
})
129129

130-
// Delete toMerge member
130+
// Delete toMerge organization
131131
await OrganizationRepository.destroy(toMergeId, repoOptions, true)
132132

133133
await SequelizeRepository.commitTransaction(tx)
@@ -422,25 +422,28 @@ export default class OrganizationService extends LoggerBase {
422422
data.website = websiteNormalizer(data.website)
423423
}
424424

425-
const originalIdentities = data.identities
425+
if (data.identities) {
426+
const originalIdentities = data.identities
426427

427-
// check identities
428-
await OrganizationRepository.checkIdentities(data, { ...this.options, transaction }, id)
428+
// check identities
429+
await OrganizationRepository.checkIdentities(data, { ...this.options, transaction }, id)
429430

430-
// if we found any strong identities sent already existing in another organization
431-
// instead of making it a weak identity we throw an error here, because this function
432-
// is mainly used for doing manual updates through UI and possibly
433-
// we don't wanna do an auto-merge here or make strong identities sent by user as weak
434-
if (originalIdentities.length !== data.identities.length) {
435-
const alreadyExistingStrongIdentities = originalIdentities.filter(
436-
(oi) => !data.identities.some((di) => di.platform === oi.platform && di.name === oi.name),
437-
)
431+
// if we found any strong identities sent already existing in another organization
432+
// instead of making it a weak identity we throw an error here, because this function
433+
// is mainly used for doing manual updates through UI and possibly
434+
// we don't wanna do an auto-merge here or make strong identities sent by user as weak
435+
if (originalIdentities.length !== data.identities.length) {
436+
const alreadyExistingStrongIdentities = originalIdentities.filter(
437+
(oi) =>
438+
!data.identities.some((di) => di.platform === oi.platform && di.name === oi.name),
439+
)
438440

439-
throw new Error(
440-
`Organization identities ${JSON.stringify(
441-
alreadyExistingStrongIdentities,
442-
)} already exist in another organization!`,
443-
)
441+
throw new Error(
442+
`Organization identities ${JSON.stringify(
443+
alreadyExistingStrongIdentities,
444+
)} already exist in another organization!`,
445+
)
446+
}
444447
}
445448

446449
const record = await OrganizationRepository.update(id, data, {

frontend/src/integrations/crunchbase/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ export default {
22
image: '/images/integrations/crunchbase.png',
33
name: 'Crunchbase',
44
hideAsIntegration: true,
5+
organization: {
6+
handle: (identity) => (identity.url ? identity.url.split('/').at(-1) : identity.name),
7+
},
58
};

frontend/src/integrations/devto/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ export default {
2323
number: conversation.activityCount - 1,
2424
}),
2525
},
26+
organization: {
27+
handle: (identity) => (identity.url ? identity.url.split('/').at(-1) : identity.name),
28+
},
2629
};

frontend/src/integrations/discord/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ export default {
2323
number: conversation.activityCount - 1,
2424
}),
2525
},
26+
organization: {
27+
handle: (identity) => (identity.url ? identity.url.split('/').at(-1) : identity.name),
28+
},
2629
};

frontend/src/integrations/discourse/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ export default {
1414
showLinkToUrl: true,
1515
},
1616
url: ({ attributes }) => attributes?.url?.discourse,
17+
organization: {
18+
handle: (identity) => (identity.url ? identity.url.split('/').at(-1) : identity.name),
19+
},
1720
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
export default {
22
image: '/images/integrations/facebook.png',
33
hideAsIntegration: true,
4+
organization: {
5+
handle: (identity) => (identity.url ? identity.url.split('/').at(-1) : identity.name),
6+
},
47
};

0 commit comments

Comments
 (0)