Skip to content

Commit ca3f4a5

Browse files
joanagmaiaepipav
andauthored
Support team organizations (#602)
Co-authored-by: anilb <epipav@gmail.com>
1 parent 79f6e45 commit ca3f4a5

16 files changed

Lines changed: 340 additions & 152 deletions

backend/src/database/migrations/U1678183368__add-isTeamOrganization.sql

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE organizations ADD COLUMN "isTeamOrganization" BOOLEAN NOT NULL DEFAULT FALSE;

backend/src/database/models/organization.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ export default (sequelize) => {
8383
len: [0, 255],
8484
},
8585
},
86+
isTeamOrganization: {
87+
type: DataTypes.BOOLEAN,
88+
defaultValue: false,
89+
allowNull: false,
90+
},
8691
},
8792
{
8893
indexes: [

backend/src/database/repositories/__tests__/organizationRepository.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ describe('OrganizationRepository tests', () => {
128128
tenantId: mockIRepositoryOptions.currentTenant.id,
129129
createdById: mockIRepositoryOptions.currentUser.id,
130130
updatedById: mockIRepositoryOptions.currentUser.id,
131+
isTeamOrganization: false,
131132
}
132133
expect(organizationCreated).toStrictEqual(expectedOrganizationCreated)
133134
})
@@ -177,6 +178,7 @@ describe('OrganizationRepository tests', () => {
177178
tenantId: mockIRepositoryOptions.currentTenant.id,
178179
createdById: mockIRepositoryOptions.currentUser.id,
179180
updatedById: mockIRepositoryOptions.currentUser.id,
181+
isTeamOrganization: false,
180182
}
181183
expect(organizationCreated).toStrictEqual(expectedOrganizationCreated)
182184

@@ -218,6 +220,7 @@ describe('OrganizationRepository tests', () => {
218220
tenantId: mockIRepositoryOptions.currentTenant.id,
219221
createdById: mockIRepositoryOptions.currentUser.id,
220222
updatedById: mockIRepositoryOptions.currentUser.id,
223+
isTeamOrganization: false,
221224
}
222225
const organizationById = await OrganizationRepository.findById(
223226
organizationCreated.id,
@@ -265,6 +268,7 @@ describe('OrganizationRepository tests', () => {
265268
tenantId: mockIRepositoryOptions.currentTenant.id,
266269
createdById: mockIRepositoryOptions.currentUser.id,
267270
updatedById: mockIRepositoryOptions.currentUser.id,
271+
isTeamOrganization: false,
268272
}
269273
const organizatioFound = await OrganizationRepository.findByName(
270274
organizationCreated.name,
@@ -301,6 +305,7 @@ describe('OrganizationRepository tests', () => {
301305
tenantId: mockIRepositoryOptions.currentTenant.id,
302306
createdById: mockIRepositoryOptions.currentUser.id,
303307
updatedById: mockIRepositoryOptions.currentUser.id,
308+
isTeamOrganization: false,
304309
}
305310
const organizatioFound = await OrganizationRepository.findByUrl(
306311
organizationCreated.url,
@@ -1204,6 +1209,7 @@ describe('OrganizationRepository tests', () => {
12041209
tenantId: mockIRepositoryOptions.currentTenant.id,
12051210
createdById: mockIRepositoryOptions.currentUser.id,
12061211
updatedById: mockIRepositoryOptions.currentUser.id,
1212+
isTeamOrganization: false,
12071213
}
12081214

12091215
expect(organizationUpdated).toStrictEqual(organizationExpected)

backend/src/database/repositories/organizationRepository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class OrganizationRepository {
3838
'employees',
3939
'revenueRange',
4040
'importHash',
41+
'isTeamOrganization',
4142
]),
4243

4344
tenantId: tenant.id,
@@ -97,6 +98,7 @@ class OrganizationRepository {
9798
'employees',
9899
'revenueRange',
99100
'importHash',
101+
'isTeamOrganization',
100102
]),
101103
updatedById: currentUser.id,
102104
},
@@ -564,6 +566,7 @@ class OrganizationRepository {
564566
'tenantId',
565567
'createdById',
566568
'updatedById',
569+
'isTeamOrganization',
567570
],
568571
'organization',
569572
),
@@ -637,6 +640,7 @@ class OrganizationRepository {
637640
'tenantId',
638641
'createdById',
639642
'updatedById',
643+
'isTeamOrganization',
640644
],
641645
'organization',
642646
),

backend/src/services/__tests__/memberService.test.ts

Lines changed: 4 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ describe('MemberService tests', () => {
544544
tenantId: mockIServiceOptions.currentTenant.id,
545545
createdById: mockIServiceOptions.currentUser.id,
546546
updatedById: mockIServiceOptions.currentUser.id,
547+
isTeamOrganization: false,
547548
})
548549
})
549550

@@ -600,6 +601,7 @@ describe('MemberService tests', () => {
600601
tenantId: mockIServiceOptions.currentTenant.id,
601602
createdById: mockIServiceOptions.currentUser.id,
602603
updatedById: mockIServiceOptions.currentUser.id,
604+
isTeamOrganization: false,
603605
})
604606
})
605607

@@ -660,6 +662,7 @@ describe('MemberService tests', () => {
660662
tenantId: mockIServiceOptions.currentTenant.id,
661663
createdById: mockIServiceOptions.currentUser.id,
662664
updatedById: mockIServiceOptions.currentUser.id,
665+
isTeamOrganization: false,
663666
})
664667
})
665668

@@ -736,82 +739,10 @@ describe('MemberService tests', () => {
736739
tenantId: mockIServiceOptions.currentTenant.id,
737740
createdById: mockIServiceOptions.currentUser.id,
738741
updatedById: mockIServiceOptions.currentUser.id,
742+
isTeamOrganization: false,
739743
})
740744
})
741745

742-
// it('Should create non existent member - several organizations with enrichment', async () => {
743-
// const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, 'premium')
744-
//
745-
// const member1 = {
746-
// username: 'anil',
747-
// platform: PlatformType.GITHUB,
748-
// email: 'lala@l.com',
749-
// score: 10,
750-
// attributes: {},
751-
// reach: 10,
752-
// bio: 'Computer Science',
753-
// organizations: [
754-
// { name: 'crowd.dev', url: 'https://crowd.dev', description: 'Here' },
755-
// { url: 'crowd.dev' },
756-
// ],
757-
// joinedAt: '2020-05-28T15:13:30Z',
758-
// location: 'Istanbul',
759-
// }
760-
//
761-
// const memberCreated = await new MemberService(mockIServiceOptions).upsert(member1)
762-
//
763-
// memberCreated.createdAt = memberCreated.createdAt.toISOString().split('T')[0]
764-
// memberCreated.updatedAt = memberCreated.updatedAt.toISOString().split('T')[0]
765-
//
766-
// const organization = (await OrganizationRepository.findAndCountAll({}, mockIServiceOptions))
767-
// .rows[0]
768-
//
769-
// const foundMember = await MemberRepository.findById(memberCreated.id, mockIServiceOptions)
770-
//
771-
// const o1 = foundMember.organizations[0].dataValues
772-
// delete o1.createdAt
773-
// delete o1.updatedAt
774-
//
775-
// expect(o1).toStrictEqual({
776-
// id: organization.id,
777-
// name: 'Crowd.dev',
778-
// url: 'crowd.dev',
779-
// description:
780-
// 'Understand, grow, and engage your developer community with zero hassle. With crowd.dev, you can build developer communities that drive your business forward.',
781-
// parentUrl: null,
782-
// emails: ['hello@crowd.dev', 'jonathan@crowd.dev', 'careers@crowd.dev'],
783-
// phoneNumbers: ['+42 424242'],
784-
// logo: 'https://logo.clearbit.com/crowd.dev',
785-
// tags: [],
786-
// twitter: {
787-
// id: '1362101830923259908',
788-
// bio: 'Community-led Growth for Developer-first Companies.\nJoin our private beta. 👇',
789-
// site: 'https://t.co/GRLDhqFWk4',
790-
// avatar: 'https://pbs.twimg.com/profile_images/1419741008716251141/6exZe94-_normal.jpg',
791-
// handle: 'CrowdDotDev',
792-
// location: '🌍 remote',
793-
// followers: 107,
794-
// following: 0,
795-
// },
796-
// linkedin: {
797-
// handle: 'company/crowddevhq',
798-
// },
799-
// crunchbase: {
800-
// handle: null,
801-
// },
802-
// employees: 5,
803-
// revenueRange: {
804-
// max: 1,
805-
// min: 0,
806-
// },
807-
// importHash: null,
808-
// deletedAt: null,
809-
// tenantId: mockIServiceOptions.currentTenant.id,
810-
// createdById: mockIServiceOptions.currentUser.id,
811-
// updatedById: mockIServiceOptions.currentUser.id,
812-
// })
813-
// })
814-
//
815746
it('Should update existent member succesfully - simple', async () => {
816747
const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db)
817748

frontend/src/modules/dashboard/store/actions.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ export default {
268268
async getActiveOrganizations({ commit, state }) {
269269
state.organizations.loadingActive = true
270270
const { platform, period } = state.filters
271-
return OrganizationService.query(
271+
return OrganizationService.list(
272272
{
273273
and: [
274274
{
@@ -284,6 +284,11 @@ export default {
284284
.toISOString()
285285
}
286286
},
287+
{
288+
isTeamOrganization: {
289+
not: true
290+
}
291+
},
287292
...(platform !== 'all'
288293
? [
289294
{
@@ -313,7 +318,7 @@ export default {
313318
async getRecentOrganizations({ commit, state }) {
314319
state.organizations.loadingRecent = true
315320
const { platform, period } = state.filters
316-
return OrganizationService.query(
321+
return OrganizationService.list(
317322
{
318323
and: [
319324
{
@@ -329,6 +334,11 @@ export default {
329334
.toISOString()
330335
}
331336
},
337+
{
338+
isTeamOrganization: {
339+
not: true
340+
}
341+
},
332342
...(platform !== 'all'
333343
? [
334344
{
@@ -356,7 +366,17 @@ export default {
356366

357367
// Fetch organizations count
358368
async getOrganizationsCount({ state }) {
359-
return OrganizationService.list(null, '', 1, 0, false)
369+
return OrganizationService.list(
370+
{
371+
isTeamOrganization: {
372+
not: true
373+
}
374+
},
375+
'',
376+
1,
377+
0,
378+
false
379+
)
360380
.then(({ count }) => {
361381
state.organizations.total = count
362382
return Promise.resolve(count)

frontend/src/modules/member/components/member-badge.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div
33
v-if="isNew || isTeam || isBot"
4-
class="member-badge flex items-center ml-1"
4+
class="member-badge flex items-center ml-1 min-w-fit"
55
>
66
<el-tooltip
77
v-if="isNew"

frontend/src/modules/organization/components/list/organization-list-toolbar.vue

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,29 @@
1616
<i class="ri-xl ri-arrow-down-s-line"></i>
1717
</button>
1818
<template #dropdown>
19-
<el-dropdown-item command="export">
19+
<el-dropdown-item :command="{ action: 'export' }">
2020
<i class="ri-lg ri-file-download-line mr-1" />
2121
Export to CSV
2222
</el-dropdown-item>
23+
24+
<el-dropdown-item
25+
:command="{
26+
action: 'markAsTeamOrganization',
27+
value: markAsTeamOrganizationOptions.value
28+
}"
29+
:disabled="isPermissionReadOnly"
30+
>
31+
<i
32+
class="ri-lg mr-1"
33+
:class="markAsTeamOrganizationOptions.icon"
34+
/>
35+
{{ markAsTeamOrganizationOptions.copy }}
36+
</el-dropdown-item>
37+
2338
<hr class="border-gray-200 my-1 mx-2" />
39+
2440
<el-dropdown-item
25-
command="destroyAll"
41+
:command="{ action: 'destroyAll' }"
2642
:disabled="isPermissionReadOnly"
2743
>
2844
<div class="text-red-500 flex items-center">
@@ -50,10 +66,13 @@ import {
5066
mapActions
5167
} from '@/shared/vuex/vuex.helpers'
5268
import ConfirmDialog from '@/shared/dialog/confirm-dialog'
69+
import { OrganizationService } from '../../organization-service'
70+
import Message from '@/shared/message/message'
5371
5472
const { currentUser, currentTenant } = mapGetters('auth')
55-
const { selectedRows } = mapGetters('organization')
56-
const { doExport, doDestroyAll } =
73+
const { selectedRows, activeView } =
74+
mapGetters('organization')
75+
const { doExport, doDestroyAll, doFetch } =
5776
mapActions('organization')
5877
5978
const isPermissionReadOnly = computed(
@@ -64,6 +83,29 @@ const isPermissionReadOnly = computed(
6483
).edit === false
6584
)
6685
86+
const markAsTeamOrganizationOptions = computed(() => {
87+
const isTeamView = activeView.value.id === 'team'
88+
const organizationsCopy = pluralize(
89+
'organization',
90+
selectedRows.value.length,
91+
false
92+
)
93+
94+
if (isTeamView) {
95+
return {
96+
icon: 'ri-bookmark-2-line',
97+
copy: `Unmark as team ${organizationsCopy}`,
98+
value: false
99+
}
100+
}
101+
102+
return {
103+
icon: 'ri-bookmark-line',
104+
copy: `Mark as team ${organizationsCopy}`,
105+
value: true
106+
}
107+
})
108+
67109
const handleDoDestroyAllWithConfirm = async () => {
68110
try {
69111
await ConfirmDialog({
@@ -93,10 +135,30 @@ const handleDoExport = async () => {
93135
}
94136
95137
const handleCommand = async (command) => {
96-
if (command === 'export') {
138+
if (command.action === 'export') {
97139
await handleDoExport()
98-
} else if (command === 'destroyAll') {
140+
} else if (command.action === 'destroyAll') {
99141
await handleDoDestroyAllWithConfirm()
142+
} else if (command.action === 'markAsTeamOrganization') {
143+
Promise.all(
144+
selectedRows.value.map((row) => {
145+
return OrganizationService.update(row.id, {
146+
isTeamOrganization: command.value
147+
})
148+
})
149+
).then(() => {
150+
Message.success(
151+
`${pluralize(
152+
'Organization',
153+
selectedRows.length,
154+
false
155+
)} updated successfully`
156+
)
157+
158+
doFetch({
159+
keepPagination: true
160+
})
161+
})
100162
}
101163
}
102164
</script>

0 commit comments

Comments
 (0)