Skip to content
Merged
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
0412c35
init new github
garrrikkotua Oct 31, 2024
5705571
types and grid
garrrikkotua Oct 31, 2024
040b95c
client
garrrikkotua Oct 31, 2024
d96883f
move snowflake to lib
garrrikkotua Nov 5, 2024
9c7b4b0
stuff
garrrikkotua Nov 5, 2024
7200b42
integration
garrrikkotua Nov 6, 2024
164240a
progress
garrrikkotua Nov 7, 2024
c9fbc20
Cleanup github webhooks routes
Nov 12, 2024
a5d30d4
remove webhooks logic
garrrikkotua Nov 13, 2024
41d3353
remove some types
garrrikkotua Nov 13, 2024
a5b8aae
Merge branches 'feat/github-archive' and 'main' of github.com:CrowdDo…
gaspergrom Nov 13, 2024
2d59559
types and stuff
garrrikkotua Nov 14, 2024
2377e64
progress
garrrikkotua Nov 14, 2024
d2371e7
Github archive integration frontend
gaspergrom Nov 14, 2024
62d222d
Merge branch 'feat/github-archive' of github.com:CrowdDotDev/crowd.de…
gaspergrom Nov 14, 2024
edb5c80
issues and stuff
garrrikkotua Nov 15, 2024
aedbb35
stuff
garrrikkotua Nov 18, 2024
84ce248
Add org list and design changes
gaspergrom Nov 18, 2024
dffbfac
Merge branch 'feat/github-archive' of github.com:CrowdDotDev/crowd.de…
gaspergrom Nov 18, 2024
bdfd13a
stuff
garrrikkotua Nov 18, 2024
a74fad9
Github archive integration finish and connect
gaspergrom Nov 18, 2024
f18dd04
Merge branch 'feat/github-archive' of github.com:CrowdDotDev/crowd.de…
gaspergrom Nov 18, 2024
b11fc25
Strip trailing spaces
Nov 18, 2024
ddafb39
Make a way to create snowflake client shortly
Nov 18, 2024
45eae0f
Create endpoints for new github onboarding flow
Nov 18, 2024
189c093
Github mapping fix
gaspergrom Nov 18, 2024
c0b1616
Merge branch 'feat/github-archive' of github.com:CrowdDotDev/crowd.de…
gaspergrom Nov 18, 2024
3b1276e
Fetch mappings
gaspergrom Nov 18, 2024
8ed8aac
remove locs from commits
garrrikkotua Nov 19, 2024
7ec4541
kinda working
garrrikkotua Nov 19, 2024
6639a40
kinda working
garrrikkotua Nov 19, 2024
6541967
stuff
garrrikkotua Nov 20, 2024
c1a7b09
associate commits with PRs
garrrikkotua Nov 25, 2024
66d482a
Create migration for github integration settings format from old to new
Nov 26, 2024
408c155
fix ilp
garrrikkotua Nov 26, 2024
2d4e896
dont throw error on reopened
garrrikkotua Nov 26, 2024
bf609e8
logic when we update
garrrikkotua Nov 26, 2024
4b36d45
clean up a little bit
garrrikkotua Nov 26, 2024
c5272f7
remove endpoint
garrrikkotua Nov 27, 2024
9c55d68
remove sf test
garrrikkotua Nov 27, 2024
416218b
remove some more missing repos
garrrikkotua Nov 27, 2024
b380ae4
some changes
garrrikkotua Nov 28, 2024
6c00277
fix messageSentAt
garrrikkotua Nov 28, 2024
a26afcf
fix
garrrikkotua Nov 28, 2024
fde1f73
fix tokens
garrrikkotua Nov 28, 2024
f920447
increase threshold
garrrikkotua Nov 28, 2024
c3c45dd
Use lower and upper limits correctly when querying activities
Nov 29, 2024
9acea9d
Populate github attributes with attributes from matching git activity
Nov 28, 2024
a8d814c
Exchange attributes only for authored-commit activities
Nov 29, 2024
244da57
Include channel in finding matching git/github activity
Nov 29, 2024
3889452
handle repo deletions
garrrikkotua Dec 3, 2024
18c305a
remapping
garrrikkotua Dec 3, 2024
cceceda
ignore deleted mapping
garrrikkotua Dec 3, 2024
0851772
logging
garrrikkotua Dec 3, 2024
ce902ae
delete mappings on frontend
garrrikkotua Dec 3, 2024
323ab88
overwrite deleted at
garrrikkotua Dec 3, 2024
06a7850
disable settings updates when repo is in progress
garrrikkotua Dec 3, 2024
bcf88d1
fix snowlake idle connections
garrrikkotua Dec 3, 2024
acbcc05
refresh integrations after deletions
garrrikkotua Dec 3, 2024
bc722de
add tooltip
garrrikkotua Dec 3, 2024
ddbdc1f
fix conversation starter
garrrikkotua Dec 4, 2024
d1bb5b6
refresh repo settings
garrrikkotua Dec 4, 2024
ce32d7d
repo_id and fix conversations
garrrikkotua Dec 5, 2024
7e84fcd
fix init client
garrrikkotua Dec 5, 2024
da1ef1b
Add some logging to progress wrapper
joanagmaia Dec 5, 2024
7e878b2
Use useTimeoutPoll to fetch progress endpoint
joanagmaia Dec 5, 2024
f03246f
change segments the right way
garrrikkotua Dec 6, 2024
6b5e8c9
don't use in-progress state when updating
garrrikkotua Dec 6, 2024
fbc9961
Merge branch 'main' into feat/github-archive
garrrikkotua Dec 9, 2024
6e81949
some formating
garrrikkotua Dec 9, 2024
1e333c1
bring back webhooks
garrrikkotua Dec 9, 2024
48b4bdb
fix import
garrrikkotua Dec 9, 2024
ee0b036
fix things for webhooks
garrrikkotua Dec 9, 2024
e343209
fix eslint
garrrikkotua Dec 10, 2024
2eb7db7
Merge branch 'main' into feat/github-archive
garrrikkotua Dec 10, 2024
999b266
remove unneeded envs
garrrikkotua Dec 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions backend/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,5 +222,13 @@
"clientSecret": "CROWD_GITLAB_CLIENT_SECRET",
"callbackUrl": "CROWD_GITLAB_CALLBACK_URL",
"webhookToken": "CROWD_GITLAB_WEBHOOK_TOKEN"
},
"snowflake": {
"privateKey": "CROWD_SNOWFLAKE_PRIVATE_KEY",
"account": "CROWD_SNOWFLAKE_ACCOUNT",
"username": "CROWD_SNOWFLAKE_USERNAME",
"database": "CROWD_SNOWFLAKE_DATABASE",
"warehouse": "CROWD_SNOWFLAKE_WAREHOUSE",
"role": "CROWD_SNOWFLAKE_ROLE"
}
}
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@crowd/telemetry": "workspace:*",
"@crowd/temporal": "workspace:*",
"@crowd/types": "workspace:*",
"@crowd/snowflake": "workspace:*",
"@google-cloud/storage": "5.3.0",
"@octokit/auth-app": "^3.6.1",
"@octokit/core": "^6.1.2",
Expand Down
9 changes: 0 additions & 9 deletions backend/src/api/integration/helpers/getGithubInstallations.ts

This file was deleted.

13 changes: 0 additions & 13 deletions backend/src/api/integration/helpers/githubAuthenticate.ts

This file was deleted.

This file was deleted.

2 changes: 1 addition & 1 deletion backend/src/api/integration/helpers/githubMapRepos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import PermissionChecker from '../../../services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.tenantEdit)
const payload = await new IntegrationService(req).mapGithubRepos(req.params.id, req.body.mapping)
const payload = await new IntegrationService(req).mapGithubRepos(req.params.id, req.body.mapping, true, req.body?.isUpdateTransaction ?? false)
await req.responseHandler.success(req, res, payload)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add explicit error handling.

While the code uses a response handler, it should explicitly handle potential errors from mapGithubRepos to ensure proper error responses.

export default async (req, res) => {
  new PermissionChecker(req).validateHas(Permissions.values.tenantEdit)
-  const payload = await new IntegrationService(req).mapGithubRepos(req.params.id, req.body.mapping, true, req.body?.isUpdateTransaction ?? false)
-  await req.responseHandler.success(req, res, payload)
+  try {
+    const payload = await new IntegrationService(req).mapGithubRepos(
+      req.params.id,
+      req.body.mapping,
+      true,
+      req.body?.isUpdateTransaction ?? false
+    )
+    await req.responseHandler.success(req, res, payload)
+  } catch (error) {
+    await req.responseHandler.error(req, res, error)
+  }
}

Committable suggestion skipped: line range outside the PR's diff.

}
10 changes: 10 additions & 0 deletions backend/src/api/integration/helpers/githubOrgRepos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Permissions from '@/security/permissions'
import GithubIntegrationService from '@/services/githubIntegrationService'
import PermissionChecker from '@/services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.integrationEdit)

const payload = await GithubIntegrationService.getOrgRepos(req.params.org)
await req.responseHandler.success(req, res, payload)
}
10 changes: 10 additions & 0 deletions backend/src/api/integration/helpers/githubSearchOrgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Permissions from '@/security/permissions'
import GithubIntegrationService from '@/services/githubIntegrationService'
import PermissionChecker from '@/services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.integrationEdit)

const payload = await GithubIntegrationService.findOrgs(req.query.query)
await req.responseHandler.success(req, res, payload)
}
10 changes: 10 additions & 0 deletions backend/src/api/integration/helpers/githubSearchRepos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Permissions from '@/security/permissions'
import GithubIntegrationService from '@/services/githubIntegrationService'
import PermissionChecker from '@/services/user/permissionChecker'

export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.integrationEdit)

const payload = await new GithubIntegrationService(req).findGithubRepos(req.query.query)
await req.responseHandler.success(req, res, payload)
}
26 changes: 12 additions & 14 deletions backend/src/api/integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ export default (app) => {
app.get(`/tenant/:tenantId/integration`, safeWrap(require('./integrationList').default))
app.get(`/tenant/:tenantId/integration/:id`, safeWrap(require('./integrationFind').default))

app.put(
`/authenticate/:tenantId/:code`,
safeWrap(require('./helpers/githubAuthenticate').default),
)
app.put(
`/tenant/:tenantId/integration/:id/github/repos`,
safeWrap(require('./helpers/githubMapRepos').default),
Expand All @@ -49,6 +45,18 @@ export default (app) => {
`/tenant/:tenantId/integration/:id/github/repos`,
safeWrap(require('./helpers/githubMapReposGet').default),
)
app.get(
`/tenant/:tenantId/integration/github/search/orgs`,
safeWrap(require('./helpers/githubSearchOrgs').default),
)
app.get(
`/tenant/:tenantId/integration/github/search/repos`,
safeWrap(require('./helpers/githubSearchRepos').default),
)
app.get(
`/tenant/:tenantId/integration/github/orgs/:org/repos`,
safeWrap(require('./helpers/githubOrgRepos').default),
)
app.put(
`/discord-authenticate/:tenantId/:guild_id`,
safeWrap(require('./helpers/discordAuthenticate').default),
Expand Down Expand Up @@ -202,16 +210,6 @@ export default (app) => {
safeWrap(require('./helpers/jiraConnectOrUpdate').default),
)

app.get(
'/tenant/:tenantId/github-installations',
safeWrap(require('./helpers/getGithubInstallations').default),
)

app.post(
'/tenant/:tenantId/github-connect-installation',
safeWrap(require('./helpers/githubConnectInstallation').default),
)

app.get('/gitlab/:tenantId/connect', safeWrap(require('./helpers/gitlabAuthenticate').default))

app.get(
Expand Down
148 changes: 118 additions & 30 deletions backend/src/bin/jobs/refreshGithubRepoSettings.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,144 @@
/* eslint-disable no-continue */
import cronGenerator from 'cron-time-generator'

import { timeout } from '@crowd/common'
import { getServiceChildLogger } from '@crowd/logging'

import SequelizeRepository from '../../database/repositories/sequelizeRepository'
import GithubIntegrationService from '../../services/githubIntegrationService'
import IntegrationService from '../../services/integrationService'
import { CrowdJob } from '../../types/jobTypes'

const log = getServiceChildLogger('refreshGithubRepoSettings')

const job: CrowdJob = {
name: 'Refresh Github repo settings',
// every day
cronTime: cronGenerator.every(1).days(),
onTrigger: async () => {
log.info('Updating Github repo settings.')
const dbOptions = await SequelizeRepository.getDefaultIRepositoryOptions()
interface Integration {
id: string
tenantId: string
segmentId: string
integrationIdentifier: string
settings: {
orgs: Array<{
name: string
logo: string
url: string
fullSync: boolean
updatedAt: string
repos: Array<{
name: string
url: string
updatedAt: string
}>
}>
}
}

interface Integration {
id: string
tenantId: string
integrationIdentifier: string
}
export const refreshGithubRepoSettings = async () => {
log.info('Updating Github repo settings.')
const dbOptions = await SequelizeRepository.getDefaultIRepositoryOptions()

const githubIntegrations = await dbOptions.database.sequelize.query(
`SELECT id, "tenantId", "integrationIdentifier" FROM integrations
const githubIntegrations = await dbOptions.database.sequelize.query(
`SELECT id, "tenantId", settings, "segmentId" FROM integrations
WHERE platform = 'github' AND "deletedAt" IS NULL
AND "createdAt" < NOW() - INTERVAL '1 day'`,
)
)
Comment on lines +39 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix SQL injection vulnerability

The raw SQL query is vulnerable to SQL injection. Use parameterized queries instead.

-  const githubIntegrations = await dbOptions.database.sequelize.query(
-    `SELECT id, "tenantId", settings, "segmentId" FROM integrations 
-       WHERE platform = 'github' AND "deletedAt" IS NULL
-       AND "createdAt" < NOW() - INTERVAL '1 day'`,
-  )
+  const githubIntegrations = await dbOptions.database.sequelize.query(
+    `SELECT id, "tenantId", settings, "segmentId" FROM integrations 
+       WHERE platform = :platform AND "deletedAt" IS NULL
+       AND "createdAt" < NOW() - INTERVAL '1 day'`,
+    {
+      replacements: { platform: 'github' },
+      type: QueryTypes.SELECT
+    }
+  )

Committable suggestion skipped: line range outside the PR's diff.


for (const integration of githubIntegrations[0] as Integration[]) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety

The type assertion as Integration[] should be replaced with proper type validation.

-  for (const integration of githubIntegrations[0] as Integration[]) {
+  const integrations = githubIntegrations[0];
+  if (!Array.isArray(integrations)) {
+    throw new Error('Expected array of integrations');
+  }
+  for (const integration of integrations as Integration[]) {
+    if (!isValidIntegration(integration)) {
+      log.warn(`Skipping invalid integration: ${integration?.id}`);
+      continue;
+    }

Add this type guard function:

function isValidIntegration(obj: any): obj is Integration {
  return (
    obj &&
    typeof obj.id === 'string' &&
    typeof obj.tenantId === 'string' &&
    typeof obj.segmentId === 'string' &&
    typeof obj.integrationIdentifier === 'string' &&
    obj.settings &&
    Array.isArray(obj.settings.orgs)
  );
}

log.info(`Updating repo settings for Github integration: ${integration.id}`)

try {
const options = await SequelizeRepository.getDefaultIRepositoryOptions()
options.currentTenant = { id: integration.tenantId }
options.currentSegments = [
// @ts-ignore
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove @ts-ignore and fix the type

Instead of suppressing the TypeScript error, properly type the segments array.

-      // @ts-ignore
-      {
+      {
+        id: integration.segmentId,
+      } as const

Committable suggestion skipped: line range outside the PR's diff.

{
id: integration.segmentId,
},
]

const integrationService = new IntegrationService(options)

if (!integration.settings.orgs) {
log.info(`No orgs found for Github integration: ${integration.id}`)
continue
}

// Get all orgs with fullSync enabled
const fullSyncOrgs = integration.settings.orgs.filter((org) => org.fullSync)
const currentRepos = new Set(
integration.settings.orgs.flatMap((org) => org.repos.map((r) => r.url)),
)
const newRepoMappings: Record<string, string> = {}

for (const integration of githubIntegrations[0] as Integration[]) {
log.info(`Updating repo settings for Github integration: ${integration.id}`)
// Fetch new repos for each org
for (const org of fullSyncOrgs) {
log.info(`Fetching repos for org ${org.name} with fullSync enabled`)
const githubRepos = await GithubIntegrationService.getOrgRepos(org.name)

try {
const options = await SequelizeRepository.getDefaultIRepositoryOptions()
options.currentTenant = { id: integration.tenantId }
// Find new repos that aren't in current settings
const newRepos = githubRepos.filter((repo) => !currentRepos.has(repo.url))

const integrationService = new IntegrationService(options)
// newly discovered repos will be mapped to default segment of the integration
await integrationService.updateGithubIntegrationSettings(integration.integrationIdentifier)
// Map new repos to the integration's segment
newRepos.forEach((repo) => {
newRepoMappings[repo.url] = integration.segmentId
})

log.info(`Successfully updated repo settings for Github integration: ${integration.id}`)
} catch (err) {
log.error(
`Error updating repo settings for Github integration ${integration.id}: ${err.message}`,
// Update org's repos list directly in the integration settings
const orgIndex = integration.settings.orgs.findIndex((o) => o.name === org.name)
if (orgIndex !== -1) {
integration.settings.orgs[orgIndex].repos = [
...integration.settings.orgs[orgIndex].repos,
...newRepos.map((repo) => ({
name: repo.name,
url: repo.url,
updatedAt: new Date().toISOString(),
})),
]
integration.settings.orgs[orgIndex].updatedAt = new Date().toISOString()
}
}

if (Object.keys(newRepoMappings).length > 0) {
log.info(`Found ${Object.keys(newRepoMappings).length} new repos to add`)

// Update integration with modified settings object
await integrationService.update(integration.id, {
settings: integration.settings,
status: 'in-progress',
})

// Map new repos
await integrationService.mapGithubRepos(
integration.id,
newRepoMappings,
true,
// this will fire onboarding only for new repos
true,
)
} finally {
await timeout(1000)

log.info(`Successfully mapped ${Object.keys(newRepoMappings).length} new repos`)
}

log.info(`Successfully updated repo settings for Github integration: ${integration.id}`)
} catch (err) {
log.error(
`Error updating repo settings for Github integration ${integration.id}: ${err.message}`,
)
// that's probably a rate limit error, let's sleep for a minute
await timeout(60000)
} finally {
await timeout(10000)
}
}

log.info('Finished updating Github repo settings.')
log.info('Finished updating Github repo settings.')
}

const job: CrowdJob = {
name: 'Refresh Github repo settings',
// every day
cronTime: cronGenerator.every(1).days(),
onTrigger: async () => {
await refreshGithubRepoSettings()
},
}

Expand Down
22 changes: 22 additions & 0 deletions backend/src/bin/scripts/refresh-github-repo-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getServiceChildLogger } from '@crowd/logging'

import { refreshGithubRepoSettings } from '../jobs/refreshGithubRepoSettings'

const logger = getServiceChildLogger('refreshGithubRepoSettings')

setImmediate(async () => {
try {
const startTime = Date.now()
logger.info('Starting refresh of Github repo settings')

await refreshGithubRepoSettings()

const duration = Date.now() - startTime
logger.info(`Completed refresh of Github repo settings in ${duration}ms`)

process.exit(0)
} catch (error) {
logger.error(`Error refreshing Github repo settings: ${error.message}`)
process.exit(1)
}
})
9 changes: 9 additions & 0 deletions backend/src/conf/configTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,12 @@ export interface IRedditConfig {
clientId: string
clientSecret: string
}

export interface SnowflakeConfiguration {
privateKey: string
account: string
username: string
database: string
warehouse: string
role: string
}
5 changes: 5 additions & 0 deletions backend/src/conf/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
TwitterConfiguration,
UnleashConfiguration,
WeeklyEmailsConfiguration,
SnowflakeConfiguration,
} from './configTypes'

// TODO-kube
Expand Down Expand Up @@ -172,3 +173,7 @@ export const OPEN_STATUS_API_CONFIG: IOpenStatusApiConfig =
export const GITLAB_CONFIG: GitlabConfiguration = config.get<GitlabConfiguration>('gitlab')

export const REDDIT_CONFIG: IRedditConfig = config.get<IRedditConfig>('reddit')


export const SNOWFLAKE_CONFIG: SnowflakeConfiguration =
config.get<SnowflakeConfiguration>('snowflake')
Loading
Loading