Skip to content

Commit 5d822a2

Browse files
author
Joan Reyero
authored
Added the Git integration to segments, and tweaks for the GitHub integration (#994)
1 parent 32e1324 commit 5d822a2

File tree

10 files changed

+4379
-15229
lines changed

10 files changed

+4379
-15229
lines changed

backend/package-lock.json

Lines changed: 4231 additions & 15206 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"cors": "2.8.5",
8282
"cron": "^2.1.0",
8383
"cron-time-generator": "^1.3.0",
84+
"crowd-sentiment": "^1.1.7",
8485
"crypto-js": "^4.1.1",
8586
"discord.js": "^14.7.1",
8687
"dotenv": "8.2.0",

backend/src/database/repositories/integrationRepository.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ class IntegrationRepository {
155155
await this._createAuditLog(AuditLogRepository.DELETE, record, record, options)
156156
}
157157

158+
static async findAllByPlatform(platform, options: IRepositoryOptions) {
159+
const transaction = SequelizeRepository.getTransaction(options)
160+
161+
const include = []
162+
163+
const currentTenant = SequelizeRepository.getCurrentTenant(options)
164+
165+
const records = await options.database.integration.findAll({
166+
where: {
167+
platform,
168+
tenantId: currentTenant.id,
169+
},
170+
include,
171+
transaction,
172+
})
173+
174+
return records.map((record) => record.get({ plain: true }))
175+
}
176+
158177
static async findByPlatform(platform, options: IRepositoryOptions) {
159178
const transaction = SequelizeRepository.getTransaction(options)
160179

backend/src/database/repositories/memberRepository.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ class MemberRepository {
5656
const transaction = SequelizeRepository.getTransaction(options)
5757

5858
const segment = SequelizeRepository.getStrictlySingleActiveSegment(options)
59-
6059
const record = await options.database.member.create(
6160
{
6261
...lodash.pick(data, [

backend/src/database/repositories/sequelizeRepository.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export default class SequelizeRepository {
6666
`This operation can have exactly one segment. Found ${options.currentSegments.length} segments.`,
6767
)
6868
}
69-
7069
return options.currentSegments[0]
7170
}
7271

backend/src/serverless/dbOperations/handler.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getServiceChildLogger } from '@crowd/logging'
22
import { KUBE_MODE } from '../../conf/index'
33
import bulkOperations from './operationsWorker'
44
import getUserContext from '../../database/utils/getUserContext'
5+
import SegmentRepository from '../../database/repositories/segmentRepository'
56

67
const log = getServiceChildLogger('dbOperations.handler')
78

@@ -22,6 +23,8 @@ export async function consumer(event) {
2223
}
2324

2425
const context = await getUserContext(tenantId)
26+
const segmentRepository = new SegmentRepository(context)
27+
context.currentSegments = [await segmentRepository.findById(event.segments[0])]
2528

2629
const result = await bulkOperations(event.operation, event.records, context)
2730

backend/src/serverless/integrations/services/integrations/githubIntegrationService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ export class GithubIntegrationService extends IntegrationServiceBase {
233233
}))
234234
}
235235

236-
newStreams = [...prCommentStreams, ...prReviewThreads, ...prCommitsStreams]
236+
// It is very important to keep commits first. Otherwise, we have problems
237+
// creating conversations if the Git integration has already ran for those data points.
238+
newStreams = [...prCommitsStreams, ...prCommentStreams, ...prReviewThreads]
237239
break
238240
case GithubStreamType.PULL_COMMENTS: {
239241
const pullRequestNumber = stream.metadata.prNumber

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,57 @@ describe('ActivityService tests', () => {
554554
expect(activityCreated2.body).toBe(activity2.body)
555555
})
556556

557+
it('Should keep isMainBranch as true', async () => {
558+
const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db)
559+
await populateSegments(mockIRepositoryOptions)
560+
const memberService = new MemberService(mockIRepositoryOptions)
561+
const activityService = new ActivityService(mockIRepositoryOptions)
562+
563+
const cm = await memberService.upsert({
564+
username: {
565+
[PlatformType.DISCORD]: 'test',
566+
},
567+
platform: PlatformType.DISCORD,
568+
})
569+
570+
const activity1 = {
571+
type: 'message',
572+
timestamp: '2020-05-27T15:13:30Z',
573+
username: 'test',
574+
member: cm.id,
575+
platform: PlatformType.DISCORD,
576+
sourceId: 'sourceId#1',
577+
attributes: {
578+
isMainBranch: true,
579+
other: 'other',
580+
},
581+
}
582+
583+
await activityService.upsert(activity1)
584+
585+
const activity2 = {
586+
type: 'message',
587+
timestamp: '2022-05-27T15:13:30Z',
588+
username: 'test',
589+
member: cm.id,
590+
platform: PlatformType.DISCORD,
591+
sourceId: 'sourceId#1',
592+
body: 'What is love?',
593+
attributes: {
594+
isMainBranch: false,
595+
other2: 'other2',
596+
},
597+
}
598+
599+
const activityCreated2 = await activityService.upsert(activity2)
600+
601+
expect(activityCreated2.attributes).toStrictEqual({
602+
isMainBranch: true,
603+
other: 'other',
604+
other2: 'other2',
605+
})
606+
})
607+
557608
it('Should create various conversations successfully with given parent-child relationships of activities [descending timestamp order]', async () => {
558609
const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db)
559610
await populateSegments(mockIRepositoryOptions)

backend/src/services/activityService.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { LoggerBase, logExecutionTime } from '@crowd/logging'
22
import { Blob } from 'buffer'
3+
import vader from 'crowd-sentiment'
34
import { Transaction } from 'sequelize/types'
45
import { PlatformType } from '@crowd/types'
5-
import { IS_DEV_ENV, IS_TEST_ENV } from '../conf'
6+
import { IS_DEV_ENV, IS_TEST_ENV, GITHUB_CONFIG } from '../conf'
67
import ActivityRepository from '../database/repositories/activityRepository'
78
import MemberAttributeSettingsRepository from '../database/repositories/memberAttributeSettingsRepository'
89
import MemberRepository from '../database/repositories/memberRepository'
@@ -20,6 +21,8 @@ import MemberService from './memberService'
2021
import SegmentRepository from '../database/repositories/segmentRepository'
2122
import SegmentService from './segmentService'
2223

24+
const IS_GITHUB_COMMIT_DATA_ENABLED = GITHUB_CONFIG.isCommitDataEnabled === 'true'
25+
2326
export default class ActivityService extends LoggerBase {
2427
options: IServiceOptions
2528

@@ -94,10 +97,24 @@ export default class ActivityService extends LoggerBase {
9497
const toUpdate = merge(existing, data, {
9598
// eslint-disable-next-line @typescript-eslint/no-unused-vars
9699
timestamp: (oldValue, _newValue) => oldValue,
100+
attributes: (oldValue, newValue) => {
101+
if (oldValue && newValue) {
102+
const out = { ...oldValue, ...newValue }
103+
// If either of the two has isMainBranch set to true, then set it to true
104+
if (oldValue.isMainBranch || newValue.isMainBranch) {
105+
out.isMainBranch = true
106+
}
107+
return out
108+
}
109+
return newValue
110+
},
97111
// eslint-disable-next-line @typescript-eslint/no-unused-vars
98112
organizationId: (oldValue, _newValue) => oldValue,
99113
})
100114
record = await ActivityRepository.update(id, toUpdate, repositoryOptions)
115+
if (data.parent) {
116+
await this.addToConversation(record.id, data.parent, transaction)
117+
}
101118
} else {
102119
if (!data.sentiment) {
103120
const sentiment = await this.getSentiment(data)
@@ -226,6 +243,31 @@ export default class ActivityService extends LoggerBase {
226243
}
227244
}
228245

246+
// When we implement Kern.ais's sentiment, we will get rid of this. In the meantime, we use Vader
247+
// because we don't have an agreement with LF for comprehend.
248+
if (IS_GITHUB_COMMIT_DATA_ENABLED) {
249+
const text = data.sourceParentId ? data.body : `${data.title} ${data.body}`
250+
const sentiment = vader.SentimentIntensityAnalyzer.polarity_scores(text)
251+
const compound = Math.round(((sentiment.compound + 1) / 2) * 100)
252+
// Some activities are inherently different, we might want to dampen their sentiment
253+
254+
let label = 'neutral'
255+
if (compound < 33) {
256+
label = 'negative'
257+
} else if (compound > 66) {
258+
label = 'positive'
259+
}
260+
261+
return {
262+
positive: Math.round(sentiment.pos * 100),
263+
negative: Math.round(sentiment.neg * 100),
264+
neutral: Math.round(sentiment.neu * 100),
265+
mixed: Math.round(sentiment.neu * 100),
266+
sentiment: compound,
267+
label,
268+
}
269+
}
270+
229271
try {
230272
data.body = data.body ?? ''
231273
data.title = data.title ?? ''

backend/src/services/integrationService.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ export default class IntegrationService {
9191
return IntegrationRepository.findByPlatform(platform, this.options)
9292
}
9393

94+
async findAllByPlatform(platform) {
95+
return IntegrationRepository.findAllByPlatform(platform, this.options)
96+
}
97+
9498
async create(data, transaction?: any) {
9599
try {
96100
const record = await IntegrationRepository.create(data, {
@@ -291,20 +295,21 @@ export default class IntegrationService {
291295

292296
const repos = await getInstalledRepositories(installToken)
293297

294-
// If the git integration is configured, we add the repos to the git config
295-
let isGitintegrationConfigured
296-
try {
297-
await this.findByPlatform(PlatformType.GIT)
298-
isGitintegrationConfigured = true
299-
} catch (err) {
300-
isGitintegrationConfigured = false
301-
}
302-
if (isGitintegrationConfigured) {
303-
const gitRemotes = await this.gitGetRemotes()
304-
await this.gitConnectOrUpdate({
305-
remotes: [...gitRemotes.default, ...repos.map((repo) => repo.cloneUrl)],
306-
})
307-
}
298+
// TODO: I will do this later. For now they can add it manually.
299+
// // If the git integration is configured, we add the repos to the git config
300+
// let isGitintegrationConfigured
301+
// try {
302+
// await this.findByPlatform(PlatformType.GIT)
303+
// isGitintegrationConfigured = true
304+
// } catch (err) {
305+
// isGitintegrationConfigured = false
306+
// }
307+
// if (isGitintegrationConfigured) {
308+
// const gitRemotes = await this.gitGetRemotes()
309+
// await this.gitConnectOrUpdate({
310+
// remotes: [...gitRemotes, ...repos.map((repo) => repo.cloneUrl)],
311+
// })
312+
// }
308313

309314
integration = await this.createOrUpdate(
310315
{
@@ -629,11 +634,15 @@ export default class IntegrationService {
629634
*/
630635
async gitGetRemotes() {
631636
try {
632-
const integration = await this.findByPlatform(PlatformType.GIT)
633-
return {
634-
// We are returning this until we have segments
635-
default: integration.settings.remotes,
636-
}
637+
const integrations = await this.findAllByPlatform(PlatformType.GIT)
638+
return integrations.reduce((acc, integration) => {
639+
const {
640+
segmentId,
641+
settings: { remotes },
642+
} = integration
643+
acc[segmentId] = remotes
644+
return acc
645+
}, {})
637646
} catch (err) {
638647
throw new Error400(this.options.language, 'errors.git.noIntegration')
639648
}

0 commit comments

Comments
 (0)