Skip to content

Commit fd98789

Browse files
epipavgaspergrom
andauthored
Organization identities and merging (#1458)
Co-authored-by: Gašper Grom <[email protected]>
1 parent fe5b3f0 commit fd98789

File tree

52 files changed

+3903
-1427
lines changed

Some content is hidden

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

52 files changed

+3903
-1427
lines changed

backend/src/api/organization/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,13 @@ export default (app) => {
1515
)
1616
app.get(`/tenant/:tenantId/organization`, safeWrap(require('./organizationList').default))
1717
app.get(`/tenant/:tenantId/organization/:id`, safeWrap(require('./organizationFind').default))
18+
19+
app.put(
20+
`/tenant/:tenantId/organization/:organizationId/merge`,
21+
safeWrap(require('./organizationMerge').default),
22+
)
23+
// app.put(
24+
// `/tenant/:tenantId/organization/:organizationId/no-merge`,
25+
// safeWrap(require('./organizationNoMerge').default),
26+
// )
1827
}

backend/src/api/organization/organizationCreate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default async (req, res) => {
2222
new PermissionChecker(req).validateHas(Permissions.values.organizationCreate)
2323

2424
const enrichP = req.body?.shouldEnrich || false
25-
const payload = await new OrganizationService(req).findOrCreate(req.body, enrichP)
25+
const payload = await new OrganizationService(req).createOrUpdate(req.body, enrichP)
2626

2727
track('Organization Manually Created', { ...payload }, { ...req })
2828

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import OrganizationService from '@/services/organizationService'
2+
import Permissions from '../../security/permissions'
3+
import track from '../../segment/track'
4+
import PermissionChecker from '../../services/user/permissionChecker'
5+
6+
export default async (req, res) => {
7+
new PermissionChecker(req).validateHas(Permissions.values.organizationEdit)
8+
9+
const payload = await new OrganizationService(req).merge(
10+
req.params.organizationId,
11+
req.body.organizationToMerge,
12+
)
13+
14+
track('Merge organizations', { ...payload }, { ...req })
15+
16+
const status = payload.status || 200
17+
18+
await req.responseHandler.success(req, res, payload, status)
19+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
drop table if exists "public"."organizationIdentities";
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
create table "organizationIdentities" (
2+
"organizationId" uuid not null references organizations on delete cascade,
3+
platform text not null,
4+
name text not null,
5+
"sourceId" text,
6+
"url" text,
7+
"tenantId" uuid not null references tenants on delete cascade,
8+
"integrationId" uuid,
9+
"createdAt" timestamp with time zone default now() not null,
10+
"updatedAt" timestamp with time zone default now() not null,
11+
primary key ("organizationId", platform, name),
12+
unique (platform, name, "tenantId")
13+
);
14+
create index "ix_organizationIdentities" on "organizationIdentities" (platform, name, "organizationId");
15+
create index "ix_organizationIdentities_tenantId" on "organizationIdentities" ("tenantId");
16+
create index "ix_organizationIdentities_organizationId" on "organizationIdentities" ("organizationId");
17+
create index "organizationIdentities_createdAt_index" on "organizationIdentities" ("createdAt" desc);
18+
create index "organizationIdentities_name_index" on "organizationIdentities" (name);
19+
create trigger organization_identities_updated_at before
20+
update on "organizationIdentities" for each row execute procedure trigger_set_updated_at();
21+
22+
23+
alter table organizations
24+
add "weakIdentities" jsonb default '[]'::jsonb not null;
25+
26+
DO
27+
$$
28+
DECLARE
29+
org organizations%ROWTYPE;
30+
act activities%ROWTYPE;
31+
gin integrations%ROWTYPE;
32+
BEGIN
33+
FOR org IN SELECT * FROM organizations
34+
WHERE NOT EXISTS (SELECT 1 FROM "organizationIdentities" WHERE "organizationId" = org.id)
35+
LOOP
36+
BEGIN
37+
-- check for organization activity on github
38+
SELECT INTO act * FROM activities WHERE "platform" = 'github' AND "organizationId" = org.id LIMIT 1;
39+
BEGIN
40+
-- If activity is found
41+
IF FOUND THEN
42+
SELECT INTO gin *
43+
FROM integrations
44+
WHERE "platform" = 'github'
45+
AND "tenantId" = org."tenantId"
46+
AND "deletedAt" IS NULL
47+
LIMIT 1;
48+
-- If integration is found
49+
IF FOUND THEN
50+
INSERT INTO "organizationIdentities" ("organizationId", "platform", "name", "url",
51+
"sourceId", "integrationId", "tenantId")
52+
VALUES (org.id, 'github', org.name, org.url, null, gin.id, org."tenantId");
53+
-- If integration is not found, `platform` is set to 'custom' and `integrationId` is set to null
54+
ELSE
55+
INSERT INTO "organizationIdentities" ("organizationId", "platform", "name", "sourceId",
56+
"integrationId", "tenantId")
57+
VALUES (org.id, 'custom', org.name, null, null, org."tenantId");
58+
END IF;
59+
-- If no activity is found, `platform` is set to 'custom' and `integrationId` is set to null
60+
ELSE
61+
INSERT INTO "organizationIdentities" ("organizationId", "platform", "name", "sourceId",
62+
"integrationId", "tenantId")
63+
VALUES (org.id, 'custom', org.name, null, null, org."tenantId");
64+
END IF;
65+
EXCEPTION
66+
WHEN unique_violation THEN
67+
-- If conflict happens, insert this identity into organizations."weakIdentities" jsonb array
68+
UPDATE organizations
69+
SET "weakIdentities" = COALESCE("weakIdentities", '{}'::jsonb) ||
70+
jsonb_build_object('platform', 'github', 'name', org.name)
71+
WHERE id = org.id;
72+
END;
73+
74+
-- check for non-null LinkedIn handle
75+
IF org.linkedin -> 'handle' IS NOT NULL THEN
76+
BEGIN
77+
INSERT INTO "organizationIdentities" ("organizationId", "platform", "name", "sourceId",
78+
"integrationId", "tenantId", "url")
79+
VALUES (org.id, 'linkedin', replace(org.linkedin ->> 'handle', 'company/', ''), null, null,
80+
org."tenantId", CONCAT('https://linkedin.com/company/',
81+
replace(org.linkedin ->> 'handle', 'company/', '')));
82+
EXCEPTION
83+
WHEN unique_violation THEN
84+
-- If conflict happens, insert this identity into organizations."weakIdentities" jsonb array
85+
UPDATE organizations
86+
SET "weakIdentities" = COALESCE("weakIdentities", '{}'::jsonb) ||
87+
jsonb_build_object('platform', 'linkedin', 'name',
88+
replace(org.linkedin ->> 'handle', 'company/', ''),
89+
'url', CONCAT('https://linkedin.com/company/',
90+
replace(org.linkedin ->> 'handle', 'company/', '')))
91+
WHERE id = org.id;
92+
END;
93+
END IF;
94+
95+
-- check for non-null Twitter handle
96+
IF org.twitter -> 'handle' IS NOT NULL THEN
97+
BEGIN
98+
INSERT INTO "organizationIdentities" ("organizationId", "platform", "name", "sourceId",
99+
"integrationId", "tenantId", "url")
100+
VALUES (org.id, 'twitter', org.twitter ->> 'handle'::text, null, null, org."tenantId",
101+
CONCAT('https://twitter.com/', org.twitter ->> 'handle'::text));
102+
EXCEPTION
103+
WHEN unique_violation THEN
104+
-- If conflict happens, insert this identity into organizations."weakIdentities" jsonb array
105+
UPDATE organizations
106+
SET "weakIdentities" = COALESCE("weakIdentities", '{}'::jsonb) ||
107+
jsonb_build_object('platform', 'twitter', 'name',
108+
org.twitter ->> 'handle', 'url',
109+
CONCAT('https://twitter.com/', org.twitter ->> 'handle'::text))
110+
WHERE id = org.id;
111+
END;
112+
END IF;
113+
EXCEPTION
114+
WHEN others THEN
115+
ROLLBACK; -- Rollback the transaction when an unexpected exception occurs
116+
RAISE NOTICE 'Unexpected error for organization %: %', org.id, SQLERRM; -- Log the error
117+
END;
118+
COMMIT;
119+
END LOOP;
120+
END ;
121+
$$;
122+
123+
124+
drop index if exists "organizations_name_tenant_id";
125+
126+
alter table organizations DROP COLUMN "name";
127+
128+
alter table organizations DROP COLUMN "url";

backend/src/database/models/organization.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ export default (sequelize) => {
99
defaultValue: DataTypes.UUIDV4,
1010
primaryKey: true,
1111
},
12-
name: {
13-
type: DataTypes.TEXT,
14-
allowNull: false,
15-
},
1612
displayName: {
1713
type: DataTypes.TEXT,
1814
allowNull: false,
@@ -33,10 +29,6 @@ export default (sequelize) => {
3329
allowNull: true,
3430
comment: 'A detailed description of the company',
3531
},
36-
url: {
37-
type: DataTypes.TEXT,
38-
allowNull: true,
39-
},
4032
immediateParent: {
4133
type: DataTypes.TEXT,
4234
allowNull: true,

backend/src/database/repositories/memberRepository.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ class MemberRepository {
888888
s.name as "segmentName",
889889
s."parentName" as "segmentParentName",
890890
o.id as "organizationId",
891-
o.name as "organizationName",
891+
o."displayName" as "organizationName",
892892
o.logo as "organizationLogo",
893893
msa."dateStart" as "dateStart",
894894
msa."dateEnd" as "dateEnd"
@@ -963,7 +963,7 @@ class MemberRepository {
963963
const include = [
964964
{
965965
model: options.database.organization,
966-
attributes: ['id', 'name'],
966+
attributes: ['id', 'displayName'],
967967
as: 'organizations',
968968
order: [['createdAt', 'ASC']],
969969
through: {
@@ -2711,7 +2711,7 @@ class MemberRepository {
27112711
include: [
27122712
{
27132713
model: options.database.organization,
2714-
attributes: ['id', 'name'],
2714+
attributes: ['id', 'displayName'],
27152715
as: 'organizations',
27162716
},
27172717
{

0 commit comments

Comments
 (0)