Skip to content

Commit 50068e3

Browse files
committed
Merge branch 'main' into github-sync-CM-514
2 parents 1de1159 + 6b0cc88 commit 50068e3

43 files changed

Lines changed: 1395 additions & 1616 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/src/database/repositories/memberRepository.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ class MemberRepository {
122122
if (!data.attributes) {
123123
data.attributes = {}
124124
}
125-
data.attributes.isBot = { ...existingIsBot, default: true, system: true }
125+
// When bot detection confirms a bot, set system flag and don't preserve custom flag
126+
// Custom flag should only be set when user manually marks as bot, not when system detects it
127+
data.attributes.isBot = { default: true, system: true }
126128
}
127129
}
128130

backend/src/database/repositories/organizationRepository.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,16 +1703,26 @@ class OrganizationRepository {
17031703
})
17041704

17051705
rows.forEach((org) => {
1706-
org.lfxMembership = lfxMemberships.find((lm) => lm.organizationId === org.id)
1706+
const membership = lfxMemberships.find((lm) => lm.organizationId === org.id)
1707+
org.lfxMembership = !!membership
17071708
})
17081709
}
1710+
17091711
if (include.identities) {
17101712
const identities = await fetchManyOrgIdentities(qx, orgIds)
17111713

17121714
rows.forEach((org) => {
1713-
org.identities = identities.find((i) => i.organizationId === org.id)?.identities || []
1715+
const orgIdentities = identities.find((i) => i.organizationId === org.id)?.identities || []
1716+
1717+
org.identities = orgIdentities.map((identity) => ({
1718+
type: identity.type,
1719+
value: identity.value,
1720+
platform: identity.platform,
1721+
verified: identity.verified,
1722+
}))
17141723
})
17151724
}
1725+
17161726
if (include.segments) {
17171727
const orgSegments = await fetchManyOrgSegments(qx, orgIds)
17181728

@@ -1723,6 +1733,7 @@ class OrganizationRepository {
17231733
?.segments.filter((segment) => segment !== null) || []
17241734
})
17251735
}
1736+
17261737
if (include.attributes) {
17271738
const attributes = await findManyOrgAttributes(qx, orgIds)
17281739

backend/src/services/integrationService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ export default class IntegrationService {
233233
segmentId,
234234
txOptions,
235235
})
236-
: []
236+
: insightsProject.repositories || []
237237

238238
await this.updateInsightsProject({
239239
insightsProjectId,

backend/src/services/member/memberOrganizationsService.ts

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import SequelizeRepository from '@/database/repositories/sequelizeRepository'
2121

2222
import { IServiceOptions } from '../IServiceOptions'
2323

24-
type IOrganizationSummary = Pick<IOrganization, 'id' | 'displayName' | 'logo'>
24+
type IOrganizationSummary = Pick<IOrganization, 'id' | 'displayName' | 'logo' | 'createdAt'>
2525

2626
export default class MemberOrganizationsService extends LoggerBase {
2727
options: IServiceOptions
@@ -64,7 +64,12 @@ export default class MemberOrganizationsService extends LoggerBase {
6464
in: orgIds,
6565
},
6666
},
67-
fields: [OrganizationField.ID, OrganizationField.DISPLAY_NAME, OrganizationField.LOGO],
67+
fields: [
68+
OrganizationField.ID,
69+
OrganizationField.DISPLAY_NAME,
70+
OrganizationField.LOGO,
71+
OrganizationField.CREATED_AT,
72+
],
6873
})
6974
}
7075

@@ -84,15 +89,64 @@ export default class MemberOrganizationsService extends LoggerBase {
8489
{},
8590
)
8691

87-
// Format the results
88-
return memberOrganizations.map((mo) => ({
89-
...(orgByid[mo.organizationId] || {}),
90-
id: mo.organizationId,
91-
memberOrganizations: {
92-
...mo,
93-
affiliationOverride: affiliationOverrides.find((ao) => ao.memberOrganizationId === mo.id),
94-
},
95-
}))
92+
// Format the results and order by dateStart and dateEnd
93+
const allOrganizations = memberOrganizations
94+
.filter((mo) => orgByid[mo.organizationId]) // Only include non-deleted organizations
95+
.map((mo) => ({
96+
...(orgByid[mo.organizationId] || {}),
97+
id: mo.organizationId,
98+
memberOrganizations: {
99+
...mo,
100+
affiliationOverride: affiliationOverrides.find((ao) => ao.memberOrganizationId === mo.id),
101+
},
102+
}))
103+
.sort((a, b) => {
104+
if (!a || !b) {
105+
return 0
106+
}
107+
108+
// Sort by dateStart (newest first), then by dateEnd (active first - null dateEnd comes first)
109+
const aDateStart = a.memberOrganizations.dateStart
110+
? new Date(a.memberOrganizations.dateStart).getTime()
111+
: 0
112+
const bDateStart = b.memberOrganizations.dateStart
113+
? new Date(b.memberOrganizations.dateStart).getTime()
114+
: 0
115+
116+
if (aDateStart !== bDateStart) {
117+
return bDateStart - aDateStart // Newest dateStart first
118+
}
119+
120+
// If dateStart is the same, prioritize active memberships (null dateEnd)
121+
const aDateEnd = a.memberOrganizations.dateEnd
122+
const bDateEnd = b.memberOrganizations.dateEnd
123+
124+
if (!aDateEnd && bDateEnd) return -1 // a is active, b is not
125+
if (aDateEnd && !bDateEnd) return 1 // b is active, a is not
126+
127+
// Both have null dateEnd and dateStart - sort by createdAt, then alphabetically
128+
if (!aDateEnd && !bDateEnd && aDateStart === 0 && bDateStart === 0) {
129+
// First try to sort by createdAt
130+
const aCreatedAt = a.createdAt ? new Date(a.createdAt).getTime() : 0
131+
const bCreatedAt = b.createdAt ? new Date(b.createdAt).getTime() : 0
132+
133+
if (aCreatedAt !== bCreatedAt) {
134+
return bCreatedAt - aCreatedAt // Newest createdAt first
135+
}
136+
137+
// If createdAt is also the same, sort alphabetically by displayName
138+
const aName = (a.displayName || '').toLowerCase()
139+
const bName = (b.displayName || '').toLowerCase()
140+
return aName.localeCompare(bName)
141+
}
142+
143+
if (!aDateEnd && !bDateEnd) return 0 // both are active with same dateStart
144+
145+
// Both have dateEnd, sort by dateEnd (newest first)
146+
return new Date(bDateEnd).getTime() - new Date(aDateEnd).getTime()
147+
})
148+
149+
return allOrganizations
96150
}
97151

98152
// Member organization creation

backend/src/services/organizationService.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,25 +1135,7 @@ export default class OrganizationService extends LoggerBase {
11351135
limit,
11361136
offset,
11371137
segmentId: segments.length > 0 ? segments[0] : undefined,
1138-
fields: [
1139-
'id',
1140-
'segmentId',
1141-
'displayName',
1142-
'headline',
1143-
'memberCount',
1144-
'activityCount',
1145-
'lastActive',
1146-
'joinedAt',
1147-
'location',
1148-
'industry',
1149-
'size',
1150-
'revenueRange',
1151-
'founded',
1152-
'employeeGrowthRate',
1153-
'tags',
1154-
'logo',
1155-
'lastEnrichedAt',
1156-
],
1138+
fields: ['id', 'segmentId', 'displayName', 'memberCount', 'activityCount', 'logo'],
11571139
include: { aggregates: true, identities: true, lfxMemberships: true },
11581140
},
11591141
this.options,

frontend/src/modules/admin/modules/integration/components/integration-list-item.vue

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
:grandparent-id="route.params.grandparentId"
9393
@open-setting="isSettingsOpen = true"
9494
/>
95-
<lf-dropdown-item type="danger" @click="disconnectIntegration()">
95+
<lf-dropdown-item type="danger" @click="isModalOpen = true">
9696
<lf-icon name="link-simple-slash" type="regular" />
9797
Disconnect integration
9898
</lf-dropdown-item>
@@ -114,6 +114,37 @@
114114
v-if="props.config.mappedReposComponent"
115115
:segment-id="route.params.id"
116116
/>
117+
118+
<lf-modal v-model="isModalOpen">
119+
<template #default>
120+
<div class="px-6 pt-6 flex gap-4">
121+
<div class="min-w-10 w-10 h-10 rounded-full bg-red-50 flex items-center justify-center">
122+
<lf-icon name="link-simple-slash" class="text-red-500" :size="16" />
123+
</div>
124+
<div class="flex flex-col gap-6">
125+
<div class="flex flex-col gap-2">
126+
<span class="font-semibold">Are you sure you want to disconnect this integration?</span>
127+
<p class="text-gray-500 text-small">
128+
Once disconnected, data will no longer sync from this source.
129+
You can reconnect anytime to resume syncing, but this action can’t be undone.
130+
</p>
131+
</div>
132+
<div class="flex flex-col gap-1">
133+
<span class="font-semibold">Type DISCONNECT to confirm</span>
134+
<lf-input v-model="disconnectConfirm" placeholder="DISCONNECT" class="w-full" />
135+
</div>
136+
</div>
137+
</div>
138+
<div class="px-6 py-4.5 bg-gray-50 mt-8 flex justify-end gap-4">
139+
<lf-button type="secondary-ghost-light" @click="isModalOpen = false">
140+
Cancel
141+
</lf-button>
142+
<lf-button type="danger" :disabled="disconnectConfirm !== 'DISCONNECT'" @click="disconnectIntegration()">
143+
Disconnect integration
144+
</lf-button>
145+
</div>
146+
</template>
147+
</lf-modal>
117148
</article>
118149
</template>
119150

@@ -133,6 +164,8 @@ import { EventType, FeatureEventKey } from '@/shared/modules/monitoring/types/ev
133164
import useProductTracking from '@/shared/modules/monitoring/useProductTracking';
134165
import { useRoute } from 'vue-router';
135166
import { dateHelper } from '@/shared/date-helper/date-helper';
167+
import LfModal from '@/ui-kit/modal/Modal.vue';
168+
import LfInput from '@/ui-kit/input/Input.vue';
136169
137170
const props = defineProps<{
138171
config: IntegrationConfig,
@@ -145,6 +178,9 @@ const route = useRoute();
145178
const { doDestroy } = mapActions('integration');
146179
const { findByPlatform } = mapGetters('integration');
147180
181+
const isModalOpen = ref(false);
182+
const disconnectConfirm = ref('');
183+
148184
const { trackEvent } = useProductTracking();
149185
150186
const integration = computed(() => findByPlatform.value(props.config.key));

frontend/src/modules/contributor/helpers/contributor.helpers.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { MemberIdentity } from '@/modules/member/types/Member';
2-
import memberOrder from '@/shared/modules/identities/config/identitiesOrder/member';
3-
import { Contributor } from '@/modules/contributor/types/Contributor';
41
import { lfIdentities } from '@/config/identities';
2+
import { Contributor } from '@/modules/contributor/types/Contributor';
3+
import { MemberIdentity } from '@/modules/member/types/Member';
54
import { dateHelper } from '@/shared/date-helper/date-helper';
5+
import memberOrder from '@/shared/modules/identities/config/identitiesOrder/member';
66

77
const useContributorHelpers = () => {
88
const avatar = (contributor: Contributor) => contributor.attributes?.avatarUrl?.default;
@@ -66,15 +66,7 @@ const useContributorHelpers = () => {
6666
}));
6767
};
6868

69-
const activeOrganization = (contributor: Contributor) => {
70-
const { organizations } = contributor;
71-
72-
return organizations.find((org) => org.memberOrganizations.affiliationOverride?.isPrimaryWorkExperience
73-
&& !!org.memberOrganizations.dateStart
74-
&& !org.memberOrganizations.dateEnd)
75-
|| organizations.find((org) => !!org.memberOrganizations.dateStart && !org.memberOrganizations.dateEnd)
76-
|| organizations.find((org) => !org.memberOrganizations.dateStart && !org.memberOrganizations.dateEnd) || null;
77-
};
69+
const activeOrganization = (contributor: Contributor) => contributor.organizations;
7870

7971
return {
8072
avatar,

frontend/src/modules/member/components/bulk/bulk-edit-attribute-dropdown.vue

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,6 @@
4141
</article>
4242
</template>
4343

44-
<!-- CUSTOM ATTRIBUTES -->
45-
<template v-if="filteredCustomOptions.length > 0">
46-
<div
47-
class="el-dropdown-title !my-3 !-ml-1"
48-
>
49-
Custom Attributes
50-
</div>
51-
<article
52-
v-for="attribute in filteredCustomOptions"
53-
:key="attribute.name"
54-
class="mb-1 p-3 rounded flex justify-between items-center transition whitespace-nowrap h-10 hover:bg-gray-50 text-xs !cursor-pointer"
55-
data-qa="filter-list-item-custom"
56-
@click="selectItem(attribute, 'custom')"
57-
>
58-
<span class="!text-gray-900">{{ attribute.label }}</span>
59-
</article>
60-
</template>
6144
<div
6245
v-if="filteredDefaultOptions.length === 0 && filteredCustomOptions.length === 0"
6346
class="el-dropdown-title !mt-2"

0 commit comments

Comments
 (0)