From 08f5004c7b0e5054d630fd25213deb68fa3d9484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Saint-Roch?= Date: Mon, 8 Jan 2024 10:46:28 +0100 Subject: [PATCH 1/8] Select only tenants with active paid plan (#2037) --- .../src/activities/weekly-analytics/getNextEmails.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/apps/emails_worker/src/activities/weekly-analytics/getNextEmails.ts b/services/apps/emails_worker/src/activities/weekly-analytics/getNextEmails.ts index 36603288ed..9f55db901b 100644 --- a/services/apps/emails_worker/src/activities/weekly-analytics/getNextEmails.ts +++ b/services/apps/emails_worker/src/activities/weekly-analytics/getNextEmails.ts @@ -35,7 +35,10 @@ export async function weeklyGetNextEmails(): Promise { try { rows = await svc.postgres.reader.connection().query(` SELECT id as "tenantId", name as "tenantName" - FROM tenants WHERE "deletedAt" IS NULL; + FROM tenants + WHERE "deletedAt" IS NULL + AND plan IN ('Scale', 'Growth', 'Essential') + AND ("trialEndsAt" > NOW() OR "trialEndsAt" IS NULL); `) } catch (err) { throw new Error(err) From 0d9a708181ff0d4622c2aa771e1bac827380e596 Mon Sep 17 00:00:00 2001 From: Mish Savelyev <1564970+sausage-todd@users.noreply.github.com> Date: Mon, 8 Jan 2024 12:54:19 +0100 Subject: [PATCH 2/8] Fix missing members joined at data (#2019) --- .../U1704365919__fix-member-joined-at.sql | 0 .../V1704365919__fix-member-joined-at.sql | 36 +++++++++++++++++++ pnpm-lock.yaml | 6 ++++ services/apps/data_sink_worker/package.json | 2 ++ .../src/service/member.service.ts | 10 ++++++ 5 files changed, 54 insertions(+) create mode 100644 backend/src/database/migrations/U1704365919__fix-member-joined-at.sql create mode 100644 backend/src/database/migrations/V1704365919__fix-member-joined-at.sql diff --git a/backend/src/database/migrations/U1704365919__fix-member-joined-at.sql b/backend/src/database/migrations/U1704365919__fix-member-joined-at.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/src/database/migrations/V1704365919__fix-member-joined-at.sql b/backend/src/database/migrations/V1704365919__fix-member-joined-at.sql new file mode 100644 index 0000000000..ab41e713c2 --- /dev/null +++ b/backend/src/database/migrations/V1704365919__fix-member-joined-at.sql @@ -0,0 +1,36 @@ +DO $$ +DECLARE + _member_id UUID; + _first_acitivity TIMESTAMP; +BEGIN + FOR _member_id IN + SELECT id + FROM members + WHERE EXTRACT(YEAR FROM "joinedAt") = 1970 -- those who have the wrong joinedAt + AND EXISTS ( -- yet have at least one activity with a non-1970 timestamp + SELECT 1 + FROM activities a + WHERE a."memberId" = members.id + AND EXTRACT(YEAR FROM a.timestamp) != 1970 + ) + LOOP + RAISE NOTICE 'member_id: %', _member_id; + + -- find the actual first non-1970 activity timestamp + SELECT MIN(a.timestamp) INTO _first_acitivity + FROM activities a + WHERE EXTRACT(YEAR FROM a.timestamp) != 1970 + AND a."memberId" = _member_id; + + IF _first_acitivity IS NULL THEN + CONTINUE; + END IF; + + RAISE NOTICE 'first_acitivity: %', _first_acitivity; + + UPDATE members + SET "joinedAt" = _first_acitivity + WHERE id = _member_id; + END LOOP; +END; +$$; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce10cdf4e0..6b296b28a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -541,6 +541,12 @@ importers: lodash.uniqby: specifier: ^4.7.0 version: 4.7.0 + moment: + specifier: 2.29.4 + version: 2.29.4 + moment-timezone: + specifier: ^0.5.34 + version: 0.5.43 ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@18.18.14)(typescript@5.3.2) diff --git a/services/apps/data_sink_worker/package.json b/services/apps/data_sink_worker/package.json index 379881bd8c..1b057f2db2 100644 --- a/services/apps/data_sink_worker/package.json +++ b/services/apps/data_sink_worker/package.json @@ -43,6 +43,8 @@ "lodash.isequal": "^4.5.0", "lodash.mergewith": "^4.6.2", "lodash.uniqby": "^4.7.0", + "moment": "2.29.4", + "moment-timezone": "^0.5.34", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.0.4" diff --git a/services/apps/data_sink_worker/src/service/member.service.ts b/services/apps/data_sink_worker/src/service/member.service.ts index f1e781fd1f..28615e09bf 100644 --- a/services/apps/data_sink_worker/src/service/member.service.ts +++ b/services/apps/data_sink_worker/src/service/member.service.ts @@ -19,6 +19,7 @@ import { } from '@crowd/types' import mergeWith from 'lodash.mergewith' import isEqual from 'lodash.isequal' +import moment from 'moment-timezone' import { IMemberCreateData, IMemberUpdateData } from './member.data' import MemberAttributeService from './memberAttribute.service' import IntegrationRepository from '../repo/integration.repo' @@ -546,6 +547,15 @@ export default class MemberService extends LoggerBase { if (member.joinedAt) { const newDate = member.joinedAt const oldDate = new Date(dbMember.joinedAt) + // If either the new or the old date are earlier than 1970 + // it means they come from an activity without timestamp + // and we want to keep the other one + if (moment(oldDate).subtract(5, 'days').unix() < 0) { + joinedAt = newDate.toISOString() + } + if (moment(newDate).unix() < 0) { + joinedAt = undefined + } if (oldDate <= newDate) { // we already have the oldest date in the db, so we don't need to update it From c67928e0253511998c7a54dd958c12920f0f86d5 Mon Sep 17 00:00:00 2001 From: Yeganathan S Date: Mon, 8 Jan 2024 12:59:15 +0000 Subject: [PATCH 3/8] Replace parent segment with leaf segments for member `lastActivity` (#2036) --- .../database/repositories/memberRepository.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/backend/src/database/repositories/memberRepository.ts b/backend/src/database/repositories/memberRepository.ts index 78eb891ed3..4f34f05f66 100644 --- a/backend/src/database/repositories/memberRepository.ts +++ b/backend/src/database/repositories/memberRepository.ts @@ -2222,26 +2222,29 @@ class MemberRepository { const memberIds = translatedRows.map((r) => r.id) if (memberIds.length > 0) { const seq = SequelizeRepository.getSequelize(options) - const segmentIds = segments const lastActivities = await seq.query( ` - WITH - raw_data AS ( - SELECT *, ROW_NUMBER() OVER (PARTITION BY "memberId" ORDER BY timestamp DESC) AS rn - FROM activities - WHERE "tenantId" = :tenantId - AND "memberId" IN (:memberIds) - AND "segmentId" IN (:segmentIds) - ) - SELECT * - FROM raw_data - WHERE rn = 1; + WITH + leaf_segment_ids AS ( + select id + from segments + where "tenantId" = :tenantId and "parentSlug" is not null and "grandparentSlug" is not null + ), + raw_data AS ( + SELECT *, ROW_NUMBER() OVER (PARTITION BY "memberId" ORDER BY timestamp DESC) AS rn + FROM activities + INNER JOIN leaf_segment_ids ON activities."segmentId" = leaf_segment_ids.id + WHERE "tenantId" = :tenantId + AND "memberId" IN (:memberIds) + ) + SELECT * + FROM raw_data + WHERE rn = 1; `, { replacements: { tenantId: tenant.id, - segmentIds, memberIds, }, type: QueryTypes.SELECT, From 31f906ec26d5af6e4855fec1e37a3d473c1061d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Mon, 8 Jan 2024 14:47:37 +0100 Subject: [PATCH 4/8] Merge suggestion preview (#2040) --- .../member-merge-suggestions-details.vue | 19 +++++++--- .../pages/member-merge-suggestions-page.vue | 35 +++++++++++++++++- ...organization-merge-suggestions-details.vue | 33 +++++++++++------ .../organization-merge-suggestions-page.vue | 36 ++++++++++++++++++- .../components/identities-vertical-list.vue | 4 +-- 5 files changed, 108 insertions(+), 19 deletions(-) diff --git a/frontend/src/modules/member/components/suggestions/member-merge-suggestions-details.vue b/frontend/src/modules/member/components/suggestions/member-merge-suggestions-details.vue index 12190832ae..5cf7d3dfb1 100644 --- a/frontend/src/modules/member/components/suggestions/member-merge-suggestions-details.vue +++ b/frontend/src/modules/member/components/suggestions/member-merge-suggestions-details.vue @@ -26,7 +26,13 @@
+ Preview +
+
Primary contact @@ -92,7 +98,7 @@

Location

-

+

{{ member.attributes.location?.default || '-' }}

@@ -117,7 +123,7 @@

Title

-

+

{{ member.attributes.jobTitle?.default || '-' }}

@@ -128,7 +134,7 @@

Contact since

-

+

{{ moment(member.joinedAt).format('YYYY-MM-DD') }}

@@ -189,6 +195,11 @@ const props = defineProps({ required: false, default: false, }, + isPreview: { + type: Boolean, + required: false, + default: false, + }, loading: { type: Boolean, required: false, diff --git a/frontend/src/modules/member/pages/member-merge-suggestions-page.vue b/frontend/src/modules/member/pages/member-merge-suggestions-page.vue index 9db1912980..71519822ee 100644 --- a/frontend/src/modules/member/pages/member-merge-suggestions-page.vue +++ b/frontend/src/modules/member/pages/member-merge-suggestions-page.vue @@ -99,7 +99,7 @@
+
+ +
@@ -139,6 +146,7 @@ import { mapGetters } from '@/shared/vuex/vuex.helpers'; import AppLoading from '@/shared/loading/loading-placeholder.vue'; import AppMemberMergeSuggestionsDetails from '@/modules/member/components/suggestions/member-merge-suggestions-details.vue'; import { useRoute } from 'vue-router'; +import { merge } from 'lodash'; import { MemberService } from '../member-service'; import { MemberPermissions } from '../member-permissions'; @@ -162,6 +170,31 @@ const isEditLockedForSampleData = computed( .editLockedForSampleData, ); +const clearMember = (member) => { + const cleanedMember = { ...member }; + // eslint-disable-next-line no-restricted-syntax + for (const key in cleanedMember.attributes) { + if (!cleanedMember.attributes[key].default) { + delete cleanedMember.attributes[key]; + } + } + return cleanedMember; +}; + +const preview = computed(() => { + const primaryMember = membersToMerge.value.members[primary.value]; + const secondaryMember = membersToMerge.value.members[(primary.value + 1) % 2]; + const mergedMembers = merge({}, clearMember(secondaryMember), clearMember(primaryMember)); + Object.keys(mergedMembers?.username || {}).forEach((key) => { + if (!primaryMember.username[key] || !secondaryMember.username[key]) { + return; + } + mergedMembers.username[key] = [...Object.values(primaryMember.username[key]), ...Object.values(secondaryMember.username[key])]; + }); + mergedMembers.score = Math.max(primaryMember.score, secondaryMember.score); + return mergedMembers; +}); + const confidence = computed(() => { if (membersToMerge.value.similarity >= 0.8) { return { diff --git a/frontend/src/modules/organization/components/suggestions/organization-merge-suggestions-details.vue b/frontend/src/modules/organization/components/suggestions/organization-merge-suggestions-details.vue index b23664e208..a663e1eba1 100644 --- a/frontend/src/modules/organization/components/suggestions/organization-merge-suggestions-details.vue +++ b/frontend/src/modules/organization/components/suggestions/organization-merge-suggestions-details.vue @@ -26,7 +26,13 @@
+ Preview +
+
Primary organization @@ -96,7 +102,7 @@ :href="withHttp(props.organization.website)" target="_blank" rel="noopener noreferrer" - class="text-xs text-gray-900 text-right" + class="text-xs text-gray-900 text-right whitespace-normal" >{{ props.organization.website || '-' }}
Location

-

+

{{ props.organization.location || '-' }}

@@ -123,7 +129,7 @@

Number of employees

-

+

{{ props.organization.employees || '-' }}

@@ -137,7 +143,7 @@

Annual Revenue

-

+

{{ revenueRange.displayValue( props.organization.revenueRange, ) || '-' }} @@ -153,7 +159,7 @@

Industry

-

+

{{ props.organization.industry || '-' }}

@@ -167,7 +173,7 @@

Type

-

+

{{ props.organization.type || '-' }}

@@ -181,7 +187,7 @@

Founded

-

+

{{ props.organization.founded || '-' }}

@@ -195,7 +201,7 @@

Joined date

-

+

{{ formatDateToTimeAgo(props.organization.joinedAt) || '-' }}

@@ -209,7 +215,7 @@

# of contacts

-

+

{{ props.organization.memberCount || '-' }}

@@ -223,7 +229,7 @@

# of Activities

-

+

{{ props.organization.activityCount || '-' }}

@@ -269,6 +275,11 @@ const props = defineProps({ required: false, default: false, }, + isPreview: { + type: Boolean, + required: false, + default: false, + }, loading: { type: Boolean, required: false, diff --git a/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue b/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue index 92d0deb431..13af3cf488 100644 --- a/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue +++ b/frontend/src/modules/organization/pages/organization-merge-suggestions-page.vue @@ -99,7 +99,7 @@
+
+ +
@@ -142,6 +149,7 @@ import AppOrganizationMergeSuggestionsDetails from '@/modules/organization/compo import { useOrganizationStore } from '@/modules/organization/store/pinia'; import { storeToRefs } from 'pinia'; import { useRoute } from 'vue-router'; +import { merge } from 'lodash'; import { OrganizationService } from '../organization-service'; import { OrganizationPermissions } from '../organization-permissions'; @@ -170,6 +178,32 @@ const isEditLockedForSampleData = computed( .editLockedForSampleData, ); +const clearOrganization = (organization) => { + const cleanedOrganization = { ...organization }; + // eslint-disable-next-line no-restricted-syntax + for (const key in cleanedOrganization) { + if (!cleanedOrganization[key]) { + delete cleanedOrganization[key]; + } + } + return cleanedOrganization; +}; + +const preview = computed(() => { + const primaryOrganization = organizationsToMerge.value.organizations[primary.value]; + const secondaryOrganization = organizationsToMerge.value.organizations[(primary.value + 1) % 2]; + const mergedOrganizations = merge({}, clearOrganization(secondaryOrganization), clearOrganization(primaryOrganization)); + if (!Array.isArray(primaryOrganization.identities)) { + primaryOrganization.identities = []; + } + if (!Array.isArray(secondaryOrganization.identities)) { + secondaryOrganization.identities = []; + } + + mergedOrganizations.identities = [...(primaryOrganization.identities || []), ...(secondaryOrganization.identities || [])]; + return mergedOrganizations; +}); + const confidence = computed(() => { if (organizationsToMerge.value.similarity >= 0.8) { return { diff --git a/frontend/src/shared/modules/identities/components/identities-vertical-list.vue b/frontend/src/shared/modules/identities/components/identities-vertical-list.vue index ba3e791500..b79e336ea9 100644 --- a/frontend/src/shared/modules/identities/components/identities-vertical-list.vue +++ b/frontend/src/shared/modules/identities/components/identities-vertical-list.vue @@ -18,8 +18,8 @@ :show-platform-tooltip="true" /> -
-
+
+