diff --git a/backend/src/api/organization/organizationCreate.ts b/backend/src/api/organization/organizationCreate.ts index 197ecf42a8..f5c242ec2d 100644 --- a/backend/src/api/organization/organizationCreate.ts +++ b/backend/src/api/organization/organizationCreate.ts @@ -21,8 +21,7 @@ import PermissionChecker from '../../services/user/permissionChecker' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.organizationCreate) - const enrichP = req.body?.shouldEnrich || false - const payload = await new OrganizationService(req).createOrUpdate(req.body, enrichP) + const payload = await new OrganizationService(req).createOrUpdate(req.body) track('Organization Manually Created', { ...payload }, { ...req }) diff --git a/backend/src/database/initializers/suggested-tasks.json b/backend/src/database/initializers/suggested-tasks.json index a0e5058b31..222b8393ef 100644 --- a/backend/src/database/initializers/suggested-tasks.json +++ b/backend/src/database/initializers/suggested-tasks.json @@ -16,11 +16,11 @@ "body": "React to activities with very negative sentiment" }, { - "name": "Setup your workpace integrations", + "name": "Set up your workpace integrations", "body": "Connect with at least 2 data sources that are relevant to your community" }, { - "name": "Setup your team", + "name": "Set up your team", "body": "Invite colleagues to your community workspace" } ] diff --git a/backend/src/services/__tests__/memberService.test.ts b/backend/src/services/__tests__/memberService.test.ts index 410d37929d..b5d9800218 100644 --- a/backend/src/services/__tests__/memberService.test.ts +++ b/backend/src/services/__tests__/memberService.test.ts @@ -691,7 +691,7 @@ describe('MemberService tests', () => { }) }) - it('Should create non existent member - organization as id, no enrichment', async () => { + it('Should create non existent member - organization as id', async () => { const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) const oCreated = await new OrganizationService(mockIServiceOptions).createOrUpdate({ @@ -793,119 +793,6 @@ describe('MemberService tests', () => { }) }) - it('Should create non existent member - organization with enrichment', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( - db, - Plans.values.growth, - ) - - const member1 = { - username: 'anil', - platform: PlatformType.GITHUB, - emails: ['lala@gmail.com'], - score: 10, - attributes: {}, - reach: 10, - bio: 'Computer Science', - organizations: [{ name: 'crowd.dev', url: 'https://crowd.dev', description: 'Here' }], - joinedAt: '2020-05-28T15:13:30Z', - location: 'Istanbul', - } - - const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1) - - memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0] - memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0] - - const organization = (await OrganizationRepository.findAndCountAll({}, mockIServiceOptions)) - .rows[0] - - const foundMember = await MemberRepository.findById(memberCreated.id, mockIServiceOptions) - - const o1 = foundMember.organizations[0].get({ plain: true }) - delete o1.createdAt - delete o1.updatedAt - - expect(o1).toStrictEqual({ - id: organization.id, - displayName: 'crowd.dev', - github: null, - location: null, - website: null, - description: - 'Understand, grow, and engage your developer community with zero hassle. With crowd.dev, you can build developer communities that drive your business forward.', - emails: ['hello@crowd.dev', 'jonathan@crowd.dev', 'careers@crowd.dev'], - phoneNumbers: ['+42 424242'], - logo: 'https://logo.clearbit.com/crowd.dev', - memberOrganizations: { - dateEnd: null, - dateStart: null, - title: null, - source: null, - }, - tags: [], - twitter: { - id: '1362101830923259908', - bio: 'Community-led Growth for Developer-first Companies.\nJoin our private beta. 👇', - site: 'https://t.co/GRLDhqFWk4', - avatar: 'https://pbs.twimg.com/profile_images/1419741008716251141/6exZe94-_normal.jpg', - handle: 'CrowdDotDev', - location: '🌍 remote', - followers: 107, - following: 0, - }, - linkedin: { - handle: 'company/crowddevhq', - }, - crunchbase: { - handle: null, - }, - employees: 5, - revenueRange: { - max: 1, - min: 0, - }, - importHash: null, - deletedAt: null, - tenantId: mockIServiceOptions.currentTenant.id, - createdById: mockIServiceOptions.currentUser.id, - updatedById: mockIServiceOptions.currentUser.id, - isTeamOrganization: false, - type: null, - ticker: null, - size: null, - naics: null, - lastEnrichedAt: null, - industry: null, - headline: null, - geoLocation: null, - founded: null, - employeeCountByCountry: null, - address: null, - profiles: null, - attributes: {}, - manuallyCreated: false, - affiliatedProfiles: null, - allSubsidiaries: null, - alternativeDomains: null, - alternativeNames: null, - averageEmployeeTenure: null, - averageTenureByLevel: null, - averageTenureByRole: null, - directSubsidiaries: null, - employeeChurnRate: null, - employeeCountByMonth: null, - employeeGrowthRate: null, - employeeCountByMonthByLevel: null, - employeeCountByMonthByRole: null, - gicsSector: null, - grossAdditionsByMonth: null, - grossDeparturesByMonth: null, - ultimateParent: null, - immediateParent: null, - }) - }) - it('Should update existent member succesfully - simple', async () => { const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) diff --git a/backend/src/services/__tests__/organizationService.test.ts b/backend/src/services/__tests__/organizationService.test.ts index ba8cf1bbdc..5843e402e1 100644 --- a/backend/src/services/__tests__/organizationService.test.ts +++ b/backend/src/services/__tests__/organizationService.test.ts @@ -5,42 +5,6 @@ import OrganizationService from '../organizationService' const db = null -const expectedEnriched = { - identities: [ - { - name: 'crowd.dev', - platform: 'crowd', - }, - ], - description: - 'Understand, grow, and engage your developer community with zero hassle. With crowd.dev, you can build developer communities that drive your business forward.', - emails: ['hello@crowd.dev', 'jonathan@crowd.dev', 'careers@crowd.dev'], - phoneNumbers: ['+42 424242'], - logo: 'https://logo.clearbit.com/crowd.dev', - tags: [], - twitter: { - handle: 'CrowdDotDev', - id: '1362101830923259908', - bio: 'Community-led Growth for Developer-first Companies.\nJoin our private beta. 👇', - followers: 107, - following: 0, - location: '🌍 remote', - site: 'https://t.co/GRLDhqFWk4', - avatar: 'https://pbs.twimg.com/profile_images/1419741008716251141/6exZe94-_normal.jpg', - }, - linkedin: { - handle: 'company/crowddevhq', - }, - crunchbase: { - handle: null, - }, - employees: 5, - revenueRange: { - min: 0, - max: 1, - }, -} - describe('OrganizationService tests', () => { beforeEach(async () => { await SequelizeTestUtils.wipeDatabase(db) @@ -52,44 +16,7 @@ describe('OrganizationService tests', () => { }) describe('Create method', () => { - it('Should add without enriching when enrichP is false', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( - db, - Plans.values.growth, - ) - const service = new OrganizationService(mockIServiceOptions) - - const toAdd = { - identities: [ - { - name: 'crowd.dev', - platform: 'crowd', - }, - ], - } - - const added = await service.createOrUpdate(toAdd, false) - expect(added.identities[0].url).toEqual(null) - }) - - it('Should add without enriching when tenant is not growth', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) - const service = new OrganizationService(mockIServiceOptions) - - const toAdd = { - identities: [ - { - name: 'crowd.dev', - platform: 'crowd', - }, - ], - } - - const added = await service.createOrUpdate(toAdd, true) - expect(added.identities[0].url).toEqual(null) - }) - - it('Should enrich and add an organization by identity name', async () => { + it('Should create organization', async () => { const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( db, Plans.values.growth, @@ -101,120 +28,12 @@ describe('OrganizationService tests', () => { { name: 'crowd.dev', platform: 'crowd', - url: 'https://crowd.dev', }, ], } const added = await service.createOrUpdate(toAdd) - expect(added.identities[0].url).toEqual(toAdd.identities[0].url) - expect(added.identities[0].name).toEqual(toAdd.identities[0].name) - expect(added.description).toEqual(expectedEnriched.description) - expect(added.emails).toEqual(expectedEnriched.emails) - expect(added.phoneNumbers).toEqual(expectedEnriched.phoneNumbers) - expect(added.logo).toEqual(expectedEnriched.logo) - expect(added.tags).toStrictEqual(expectedEnriched.tags) - expect(added.twitter).toStrictEqual(expectedEnriched.twitter) - expect(added.linkedin).toStrictEqual(expectedEnriched.linkedin) - expect(added.crunchbase).toStrictEqual(expectedEnriched.crunchbase) - expect(added.employees).toEqual(expectedEnriched.employees) - expect(added.revenueRange).toStrictEqual(expectedEnriched.revenueRange) - - // Check cache table was created - const foundCache = await organizationCacheRepository.findByName( - 'crowd.dev', - mockIServiceOptions, - ) - - expect(foundCache.url).toEqual('crowd.dev') - expect(foundCache.name).toEqual(toAdd.identities[0].name) - expect(foundCache.description).toEqual(expectedEnriched.description) - expect(foundCache.emails).toEqual(expectedEnriched.emails) - expect(foundCache.phoneNumbers).toEqual(expectedEnriched.phoneNumbers) - expect(foundCache.logo).toEqual(expectedEnriched.logo) - expect(foundCache.tags).toStrictEqual(expectedEnriched.tags) - expect(foundCache.twitter).toStrictEqual(expectedEnriched.twitter) - expect(foundCache.linkedin).toStrictEqual(expectedEnriched.linkedin) - expect(foundCache.crunchbase).toStrictEqual(expectedEnriched.crunchbase) - expect(foundCache.employees).toEqual(expectedEnriched.employees) - expect(foundCache.revenueRange).toStrictEqual(expectedEnriched.revenueRange) - }) - - it('Should not re-enrich when the record is already in the cache table. By Name', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( - db, - Plans.values.growth, - ) - const mockIServiceOptions2 = await SequelizeTestUtils.getTestIServiceOptions( - db, - Plans.values.growth, - ) - - const service = new OrganizationService(mockIServiceOptions) - const service2 = new OrganizationService(mockIServiceOptions2) - - const toAdd = { - identities: [ - { - name: 'crowd.dev', - platform: 'crowd', - url: 'https://crowd.dev', - }, - ], - } - - const added = await service.createOrUpdate(toAdd) - expect(added.identities[0].url).toEqual(toAdd.identities[0].url) - expect(added.identities[0].name).toEqual(toAdd.identities[0].name) - expect(added.description).toEqual(expectedEnriched.description) - expect(added.emails).toEqual(expectedEnriched.emails) - expect(added.phoneNumbers).toEqual(expectedEnriched.phoneNumbers) - expect(added.logo).toEqual(expectedEnriched.logo) - expect(added.tags).toStrictEqual(expectedEnriched.tags) - expect(added.twitter).toStrictEqual(expectedEnriched.twitter) - expect(added.linkedin).toStrictEqual(expectedEnriched.linkedin) - expect(added.crunchbase).toStrictEqual(expectedEnriched.crunchbase) - expect(added.employees).toEqual(expectedEnriched.employees) - expect(added.revenueRange).toStrictEqual(expectedEnriched.revenueRange) - - // Check cache table was created - const foundCache = await organizationCacheRepository.findByName( - 'crowd.dev', - mockIServiceOptions, - ) - - expect(foundCache.name).toEqual('crowd.dev') - expect(foundCache.description).toEqual(expectedEnriched.description) - expect(foundCache.emails).toEqual(expectedEnriched.emails) - expect(foundCache.phoneNumbers).toEqual(expectedEnriched.phoneNumbers) - expect(foundCache.logo).toEqual(expectedEnriched.logo) - expect(foundCache.tags).toStrictEqual(expectedEnriched.tags) - expect(foundCache.twitter).toStrictEqual(expectedEnriched.twitter) - expect(foundCache.linkedin).toStrictEqual(expectedEnriched.linkedin) - expect(foundCache.crunchbase).toStrictEqual(expectedEnriched.crunchbase) - expect(foundCache.employees).toEqual(expectedEnriched.employees) - expect(foundCache.revenueRange).toStrictEqual(expectedEnriched.revenueRange) - - const added2 = await service2.createOrUpdate(toAdd) - expect(added2.identities[0].url).toEqual(toAdd.identities[0].url) - expect(added2.description).toEqual(expectedEnriched.description) - expect(added2.emails).toEqual(expectedEnriched.emails) - expect(added2.phoneNumbers).toEqual(expectedEnriched.phoneNumbers) - expect(added2.logo).toEqual(expectedEnriched.logo) - expect(added2.tags).toStrictEqual(expectedEnriched.tags) - expect(added2.twitter).toStrictEqual(expectedEnriched.twitter) - expect(added2.linkedin).toStrictEqual(expectedEnriched.linkedin) - expect(added2.crunchbase).toStrictEqual(expectedEnriched.crunchbase) - expect(added2.employees).toEqual(expectedEnriched.employees) - expect(added2.revenueRange).toStrictEqual(expectedEnriched.revenueRange) - // Check they are indeed in different tenants - expect(added2.tenantId).not.toBe(added.tenantId) - - const foundCache2 = await organizationCacheRepository.findByName( - 'crowd.dev', - mockIServiceOptions, - ) - expect(foundCache2.id).toEqual(foundCache.id) + expect(added.identities[0].url).toEqual(null) }) it('Should throw an error when name is not sent', async () => { diff --git a/backend/src/services/activityService.ts b/backend/src/services/activityService.ts index e4d69540d5..28e4ec8606 100644 --- a/backend/src/services/activityService.ts +++ b/backend/src/services/activityService.ts @@ -2,7 +2,7 @@ import { LoggerBase, logExecutionTime } from '@crowd/logging' import { Blob } from 'buffer' import vader from 'crowd-sentiment' import { Transaction } from 'sequelize/types' -import { FeatureFlag, PlatformType } from '@crowd/types' +import { FeatureFlag, PlatformType, SyncMode } from '@crowd/types' import { WorkflowIdReusePolicy } from '@crowd/temporal' import { IS_DEV_ENV, IS_TEST_ENV, GITHUB_CONFIG, TEMPORAL_CONFIG } from '../conf' import ActivityRepository from '../database/repositories/activityRepository' @@ -502,7 +502,7 @@ export default class ActivityService extends LoggerBase { async createWithMember(data, fireCrowdWebhooks: boolean = true) { const logger = this.options.log - const searchSyncService = new SearchSyncService(this.options) + const searchSyncService = new SearchSyncService(this.options, SyncMode.ASYNCHRONOUS) const errorDetails: any = {} diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts index 933a883a55..6e7de06832 100644 --- a/backend/src/services/memberService.ts +++ b/backend/src/services/memberService.ts @@ -4,7 +4,13 @@ import { LoggerBase } from '@crowd/logging' import lodash from 'lodash' import moment from 'moment-timezone' import validator from 'validator' -import { FeatureFlag, IOrganization, MemberAttributeType } from '@crowd/types' +import { + FeatureFlag, + IOrganization, + ISearchSyncOptions, + MemberAttributeType, + SyncMode, +} from '@crowd/types' import { isDomainExcluded } from '@crowd/common' import { WorkflowIdReusePolicy } from '@crowd/temporal' import { IRepositoryOptions } from '../database/repositories/IRepositoryOptions' @@ -197,7 +203,7 @@ export default class MemberService extends LoggerBase { data, existing: boolean | any = false, fireCrowdWebhooks: boolean = true, - fireSync: boolean = true, + syncToOpensearch = true, ) { const logger = this.options.log const searchSyncService = new SearchSyncService(this.options) @@ -336,6 +342,10 @@ export default class MemberService extends LoggerBase { // We createOrUpdate the organization and add it to the list of IDs const organizationRecord = await organizationService.createOrUpdate( data as IOrganization, + { + doSync: syncToOpensearch, + mode: SyncMode.ASYNCHRONOUS, + }, ) organizations.push({ id: organizationRecord.id }) } @@ -360,15 +370,21 @@ export default class MemberService extends LoggerBase { const organizationService = new OrganizationService(this.options) for (const domain of emailDomains) { if (domain) { - const org = await organizationService.createOrUpdate({ - website: domain, - identities: [ - { - name: domain, - platform: 'email', - }, - ], - }) + const org = await organizationService.createOrUpdate( + { + website: domain, + identities: [ + { + name: domain, + platform: 'email', + }, + ], + }, + { + doSync: syncToOpensearch, + mode: SyncMode.ASYNCHRONOUS, + }, + ) if (org) { organizations.push({ id: org.id }) @@ -435,7 +451,7 @@ export default class MemberService extends LoggerBase { await SequelizeRepository.commitTransaction(transaction) - if (fireSync) { + if (syncToOpensearch) { await searchSyncService.triggerMemberSync(this.options.currentTenant.id, record.id) } @@ -572,7 +588,11 @@ export default class MemberService extends LoggerBase { * @param toMergeId ID of the member that will be merged into the original member and deleted. * @returns Success/Error message */ - async merge(originalId, toMergeId) { + async merge( + originalId, + toMergeId, + syncOptions: ISearchSyncOptions = { doSync: true, mode: SyncMode.USE_FEATURE_FLAG }, + ) { this.options.log.info({ originalId, toMergeId }, 'Merging members!') let tx @@ -655,21 +675,23 @@ export default class MemberService extends LoggerBase { await SequelizeRepository.commitTransaction(tx) - try { - const searchSyncService = new SearchSyncService(this.options) + if (syncOptions.doSync) { + try { + const searchSyncService = new SearchSyncService(this.options, syncOptions.mode) - await searchSyncService.triggerMemberSync(this.options.currentTenant.id, originalId) - await searchSyncService.triggerRemoveMember(this.options.currentTenant.id, toMergeId) - } catch (emitError) { - this.log.error( - emitError, - { - tenantId: this.options.currentTenant.id, - originalId, - toMergeId, - }, - 'Error while triggering member sync changes!', - ) + await searchSyncService.triggerMemberSync(this.options.currentTenant.id, originalId) + await searchSyncService.triggerRemoveMember(this.options.currentTenant.id, toMergeId) + } catch (emitError) { + this.log.error( + emitError, + { + tenantId: this.options.currentTenant.id, + originalId, + toMergeId, + }, + 'Error while triggering member sync changes!', + ) + } } this.options.log.info({ originalId, toMergeId }, 'Members merged!') diff --git a/backend/src/services/organizationService.ts b/backend/src/services/organizationService.ts index f10a1992d1..b6d2576d84 100644 --- a/backend/src/services/organizationService.ts +++ b/backend/src/services/organizationService.ts @@ -1,18 +1,22 @@ +import { isEqual } from 'lodash' import { websiteNormalizer } from '@crowd/common' import { LoggerBase } from '@crowd/logging' -import { IOrganization, IOrganizationIdentity, OrganizationMergeSuggestionType } from '@crowd/types' +import { + IOrganization, + IOrganizationIdentity, + ISearchSyncOptions, + OrganizationMergeSuggestionType, + SyncMode, +} from '@crowd/types' import { IRepositoryOptions } from '@/database/repositories/IRepositoryOptions' import getObjectWithoutKey from '@/utils/getObjectWithoutKey' -import { CLEARBIT_CONFIG, IS_TEST_ENV } from '../conf' import MemberRepository from '../database/repositories/memberRepository' import organizationCacheRepository from '../database/repositories/organizationCacheRepository' import OrganizationRepository from '../database/repositories/organizationRepository' import SequelizeRepository from '../database/repositories/sequelizeRepository' import Error400 from '../errors/Error400' -import Plans from '../security/plans' import telemetryTrack from '../segment/telemetryTrack' import { IServiceOptions } from './IServiceOptions' -import { enrichOrganization } from './helpers/enrichment' import merge from './helpers/merge' import { keepPrimary, @@ -35,14 +39,6 @@ export default class OrganizationService extends LoggerBase { this.options = options } - async shouldEnrich(enrichP) { - const isPremium = this.options.currentTenant.plan === Plans.values.growth - if (!isPremium) { - return false - } - return enrichP && (CLEARBIT_CONFIG.apiKey || IS_TEST_ENV) - } - async mergeAsync(originalId, toMergeId) { const tenantId = this.options.currentTenant.id @@ -446,7 +442,10 @@ export default class OrganizationService extends LoggerBase { } } - async createOrUpdate(data: IOrganization, enrichP = true) { + async createOrUpdate( + data: IOrganization, + syncOptions: ISearchSyncOptions = { doSync: true, mode: SyncMode.USE_FEATURE_FLAG }, + ) { const transaction = await SequelizeRepository.createTransaction(this.options) if ((data as any).name && (!data.identities || data.identities.length === 0)) { @@ -471,8 +470,6 @@ export default class OrganizationService extends LoggerBase { } try { - const shouldDoEnrich = await this.shouldEnrich(enrichP) - const primaryIdentity = data.identities[0] const nameToCheckInCache = (data as any).name || primaryIdentity.name @@ -490,14 +487,40 @@ export default class OrganizationService extends LoggerBase { // if cache exists, merge current data with cache data // if it doesn't exist, create it from incoming data if (cache) { - data = { - ...cache, - ...data, - } - cache = await organizationCacheRepository.update(cache.id, data, { - ...this.options, - transaction, + // if exists in cache update it + const updateData: Partial = {} + const fields = [ + 'url', + 'description', + 'emails', + 'logo', + 'tags', + 'github', + 'twitter', + 'linkedin', + 'crunchbase', + 'employees', + 'location', + 'website', + 'type', + 'size', + 'headline', + 'industry', + 'founded', + ] + fields.forEach((field) => { + if (data[field] && !isEqual(data[field], cache[field])) { + updateData[field] = data[field] + } }) + if (Object.keys(updateData).length > 0) { + await organizationCacheRepository.update(cache.id, updateData, { + ...this.options, + transaction, + }) + + cache = { ...cache, ...updateData } // Update the cached data with the new data + } } else { // save it to cache cache = await organizationCacheRepository.create( @@ -512,29 +535,6 @@ export default class OrganizationService extends LoggerBase { ) } - // clearbit enrich - if (shouldDoEnrich && !cache.enriched) { - try { - const enrichedData = await enrichOrganization(primaryIdentity.name) - - // overwrite cache with enriched data, but keep the name because it's serving as a unique identifier - data = { - ...data, // to keep uncacheable data (like identities, weakIdentities) - ...cache, - ...enrichedData, - name: cache.name, - enriched: true, - } - - cache = await organizationCacheRepository.update(cache.id, data, { - ...this.options, - transaction, - }) - } catch (error) { - this.log.error(error, `Could not enrich ${primaryIdentity.name}!`) - } - } - if (data.members) { cache.members = await MemberRepository.filterIdsInTenant(data.members, { ...this.options, @@ -573,11 +573,45 @@ export default class OrganizationService extends LoggerBase { data.displayName = cache.name } - record = await OrganizationRepository.update( - existing.id, - { ...data, ...cache }, - { ...this.options, transaction }, - ) + // if it does exists update it + const updateData: Partial = {} + const fields = [ + 'displayName', + 'description', + 'emails', + 'logo', + 'tags', + 'github', + 'twitter', + 'linkedin', + 'crunchbase', + 'employees', + 'location', + 'website', + 'type', + 'size', + 'headline', + 'industry', + 'founded', + 'attributes', + 'weakIdentities', + ] + fields.forEach((field) => { + if (field === 'website' && !existing.website && cache.website) { + updateData[field] = cache[field] + } else if ( + field !== 'website' && + cache[field] && + !isEqual(cache[field], existing[field]) + ) { + updateData[field] = cache[field] + } + }) + + record = await OrganizationRepository.update(existing.id, updateData, { + ...this.options, + transaction, + }) } else { await OrganizationRepository.checkIdentities(data, this.options) @@ -624,9 +658,10 @@ export default class OrganizationService extends LoggerBase { await SequelizeRepository.commitTransaction(transaction) - const searchSyncService = new SearchSyncService(this.options) - - await searchSyncService.triggerOrganizationSync(this.options.currentTenant.id, record.id) + if (syncOptions.doSync) { + const searchSyncService = new SearchSyncService(this.options, syncOptions.mode) + await searchSyncService.triggerOrganizationSync(this.options.currentTenant.id, record.id) + } return await this.findById(record.id) } catch (error) { diff --git a/backend/src/services/premium/enrichment/memberEnrichmentService.ts b/backend/src/services/premium/enrichment/memberEnrichmentService.ts index ca7b6a4ce9..a52709461f 100644 --- a/backend/src/services/premium/enrichment/memberEnrichmentService.ts +++ b/backend/src/services/premium/enrichment/memberEnrichmentService.ts @@ -11,6 +11,7 @@ import { MemberEnrichmentAttributes, PlatformType, OrganizationSource, + SyncMode, } from '@crowd/types' import { ENRICHMENT_CONFIG, REDIS_CONFIG } from '../../../conf' import { AttributeData } from '../../../database/attributes/attribute' @@ -35,7 +36,7 @@ import OrganizationService from '../../organizationService' import MemberRepository from '../../../database/repositories/memberRepository' import OrganizationRepository from '../../../database/repositories/organizationRepository' import SequelizeRepository from '@/database/repositories/sequelizeRepository' -import SearchSyncService, { SyncMode } from '@/services/searchSyncService' +import SearchSyncService from '@/services/searchSyncService' export default class MemberEnrichmentService extends LoggerBase { options: IServiceOptions @@ -286,7 +287,16 @@ export default class MemberEnrichmentService extends LoggerBase { const existingMember = await memberService.memberExists(username, platform) if (existingMember) { - await memberService.merge(memberId, existingMember.id) + await memberService.merge(memberId, existingMember.id, { + doSync: false, + mode: SyncMode.ASYNCHRONOUS, + }) + + // we also need to trigger remove existing member from opensearch, because the transaction isn't commited yet while merging + await searchSyncService.triggerRemoveMember( + this.options.currentTenant.id, + existingMember.id, + ) } } } @@ -300,9 +310,13 @@ export default class MemberEnrichmentService extends LoggerBase { // upsert always takes the existing displayName if (!/\W/.test(member.displayName)) { if (enrichmentData.first_name && enrichmentData.last_name) { - await memberService.update(member.id, { - displayName: `${enrichmentData.first_name} ${enrichmentData.last_name}`, - }) + await memberService.update( + member.id, + { + displayName: `${enrichmentData.first_name} ${enrichmentData.last_name}`, + }, + false, + ) } } @@ -331,14 +345,20 @@ export default class MemberEnrichmentService extends LoggerBase { const organizationService = new OrganizationService(options) if (enrichmentData.work_experiences) { for (const workExperience of enrichmentData.work_experiences) { - const org = await organizationService.createOrUpdate({ - identities: [ - { - name: workExperience.company, - platform: PlatformType.ENRICHMENT, - }, - ], - }) + const org = await organizationService.createOrUpdate( + { + identities: [ + { + name: workExperience.company, + platform: PlatformType.ENRICHMENT, + }, + ], + }, + { + doSync: true, + mode: SyncMode.ASYNCHRONOUS, + }, + ) const dateEnd = workExperience.endDate ? moment.utc(workExperience.endDate).toISOString() diff --git a/backend/src/services/premium/enrichment/organizationEnrichmentService.ts b/backend/src/services/premium/enrichment/organizationEnrichmentService.ts index f0c7c72fbc..ab393418ac 100644 --- a/backend/src/services/premium/enrichment/organizationEnrichmentService.ts +++ b/backend/src/services/premium/enrichment/organizationEnrichmentService.ts @@ -9,6 +9,7 @@ import { IOrganization, IOrganizationCache, PlatformType, + SyncMode, } from '@crowd/types' import { REDIS_CONFIG } from '../../../conf' import OrganizationRepository from '../../../database/repositories/organizationRepository' @@ -17,7 +18,7 @@ import { IServiceOptions } from '../../IServiceOptions' import { EnrichmentParams, IEnrichmentResponse } from './types/organizationEnrichmentTypes' import SequelizeRepository from '@/database/repositories/sequelizeRepository' import OrganizationService from '@/services/organizationService' -import SearchSyncService, { SyncMode } from '@/services/searchSyncService' +import SearchSyncService from '@/services/searchSyncService' export default class OrganizationEnrichmentService extends LoggerBase { tenantId: string diff --git a/backend/src/services/searchSyncService.ts b/backend/src/services/searchSyncService.ts index 7cc169ceb0..8ce3c5469b 100644 --- a/backend/src/services/searchSyncService.ts +++ b/backend/src/services/searchSyncService.ts @@ -1,19 +1,13 @@ import { LoggerBase } from '@crowd/logging' import { SearchSyncApiClient } from '@crowd/opensearch' import { SearchSyncWorkerEmitter } from '@crowd/sqs' -import { FeatureFlag } from '@crowd/types' +import { FeatureFlag, SyncMode } from '@crowd/types' import { getSearchSyncApiClient } from '../utils/apiClients' import { getSearchSyncWorkerEmitter } from '@/serverless/utils/serviceSQS' import isFeatureEnabled from '@/feature-flags/isFeatureEnabled' import { IS_TEST_ENV } from '@/conf' import { IServiceOptions } from './IServiceOptions' -export enum SyncMode { - SYNCHRONOUS = 'synchronous', - ASYNCHRONOUS = 'asynchronous', - USE_FEATURE_FLAG = 'use-feature-flag', -} - export type SearchSyncClient = SearchSyncApiClient | SearchSyncWorkerEmitter export default class SearchSyncService extends LoggerBase { diff --git a/frontend/public/icons/crowd-icons.svg b/frontend/public/icons/crowd-icons.svg index 5709d2437c..025e368da7 100644 --- a/frontend/public/icons/crowd-icons.svg +++ b/frontend/public/icons/crowd-icons.svg @@ -26,15 +26,13 @@ - - + + - + diff --git a/frontend/public/images/integrations/twitter-x.svg b/frontend/public/images/integrations/twitter-x.svg new file mode 100644 index 0000000000..08e9871860 --- /dev/null +++ b/frontend/public/images/integrations/twitter-x.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/public/images/integrations/twitter-zapier.jpg b/frontend/public/images/integrations/twitter-zapier.jpg index 365414f053..32cd5b1040 100644 Binary files a/frontend/public/images/integrations/twitter-zapier.jpg and b/frontend/public/images/integrations/twitter-zapier.jpg differ diff --git a/frontend/src/i18n/en.js b/frontend/src/i18n/en.js index 61181c59ef..5c00a32e2a 100644 --- a/frontend/src/i18n/en.js +++ b/frontend/src/i18n/en.js @@ -263,7 +263,7 @@ const en = { revenueRange: 'Annual revenue', activeSince: 'Active since', github: 'GitHub', - twitter: 'Twitter', + twitter: 'X/Twitter', linkedin: 'LinkedIn', crunchbase: 'Crunchbase', }, diff --git a/frontend/src/integrations/twitter/components/twitter-connect-drawer.vue b/frontend/src/integrations/twitter/components/twitter-connect-drawer.vue index 4e30d93cb5..a1fb824319 100644 --- a/frontend/src/integrations/twitter/components/twitter-connect-drawer.vue +++ b/frontend/src/integrations/twitter/components/twitter-connect-drawer.vue @@ -2,11 +2,11 @@