Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 6dbf42f

Browse files
Feat: Github Integration (#234)
* Feat: Issue creation and sync comments with Github * Feat: show respective action icon when the resources are created * Fix: lint errors * Fix: github thread comment and activity * Fix: ignore comments from other integrations * Fix: slack is broken * Fix: slack and github integrations * Fix: lint errors --------- Co-authored-by: Harshith Mullapudi <harshithmullapudi@gmail.com>
1 parent 7fb63ce commit 6dbf42f

File tree

117 files changed

+5549
-442
lines changed

Some content is hidden

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

117 files changed

+5549
-442
lines changed

actions/github/.eslintrc.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/** Copyright (c) 2024, Tegon, all rights reserved. **/
2+
3+
module.exports = {
4+
extends: [
5+
'plugin:@typescript-eslint/recommended',
6+
'prettier',
7+
'plugin:prettier/recommended',
8+
],
9+
plugins: ['@typescript-eslint', 'prettier', 'unused-imports', 'import'],
10+
parserOptions: {
11+
ecmaVersion: 2020,
12+
sourceType: 'module',
13+
ecmaFeatures: {
14+
jsx: true,
15+
},
16+
},
17+
rules: {
18+
curly: 'warn',
19+
eqeqeq: 'error',
20+
'prettier/prettier': 'warn',
21+
'unused-imports/no-unused-imports': 'warn',
22+
'no-else-return': 'warn',
23+
'no-lonely-if': 'warn',
24+
'no-inner-declarations': 'off',
25+
'no-unused-vars': 'off',
26+
'no-useless-computed-key': 'warn',
27+
'no-useless-return': 'warn',
28+
'no-var': 'warn',
29+
'object-shorthand': ['warn', 'always'],
30+
'prefer-arrow-callback': 'warn',
31+
'prefer-const': 'warn',
32+
'prefer-destructuring': ['warn', { AssignmentExpression: { array: true } }],
33+
'prefer-object-spread': 'warn',
34+
'prefer-template': 'warn',
35+
'spaced-comment': ['warn', 'always', { markers: ['/'] }],
36+
yoda: 'warn',
37+
'@typescript-eslint/array-type': ['warn', { default: 'array-simple' }],
38+
'@typescript-eslint/ban-ts-comment': [
39+
'warn',
40+
{
41+
'ts-expect-error': 'allow-with-description',
42+
},
43+
],
44+
'@typescript-eslint/ban-types': 'warn',
45+
'@typescript-eslint/consistent-indexed-object-style': ['warn', 'record'],
46+
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
47+
'@typescript-eslint/no-unused-vars': 'warn',
48+
},
49+
parser: '@typescript-eslint/parser',
50+
ignorePatterns: ['src/@@generated/**/*.tsx', 'src/@@generated/**/*.ts'],
51+
overrides: [
52+
{
53+
files: ['scripts/**/*'],
54+
rules: {
55+
'@typescript-eslint/no-var-requires': 'off',
56+
},
57+
},
58+
],
59+
};

actions/github/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
.trigger
3+
trigger
4+
trigger.config.ts

actions/github/.prettierrc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all"
4+
}

actions/github/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Overview
2+
3+
Effortlessly integrate Tegon with Slack to transform your project communication and management:
4+
5+
1. Create Issues from Slack Messages:
6+
7+
- Use the "..." menu on any Slack message to effortlessly create a Tegon issue.
8+
9+
2. Automate Issue Creation with Emoji 👀:
10+
11+
- Assign 👀 emoji to a Slack thread to automatically trigger Tegon AI to create a triage issue.
12+
- This feature empowers even non-Tegon users to easily report issues.
13+
14+
3. Synchronized Conversation Threads:
15+
- When issues are reported in Slack, a synced comment thread is automatically created within the corresponding Tegon issue.
16+
- Tegon comments are automatically reflected in the Slack thread, keeping everyone in the loop and vice versa.
17+
- Both the Slack thread and the Tegon comments update in real-time, ensuring everyone stays on the same page regardless of their chosen platform.

actions/github/config.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"slug": "github",
3+
"name": "Github",
4+
"icon": "github",
5+
"description": "Create issues from Github messages, sync Issues and Comments",
6+
"triggers": [
7+
{
8+
"type": "on_create",
9+
"entities": [ "Issue", "IssueComment", "LinkedIssue" ]
10+
},
11+
{
12+
"type": "on_update",
13+
"entities": [ "Issue", "IssueComment" ]
14+
},
15+
{
16+
"type": "source_webhook",
17+
"entities": [ "github" ]
18+
}
19+
],
20+
"integrations": [ "github" ]
21+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {
2+
ActionEventPayload,
3+
getLabels,
4+
getTeams,
5+
Label,
6+
Team,
7+
} from '@tegonhq/sdk';
8+
import axios from 'axios';
9+
import { getGithubHeaders } from 'utils';
10+
11+
export const getInputs = async (payload: ActionEventPayload) => {
12+
const { workspaceId, integrationAccounts } = payload;
13+
14+
const integrationAccount = integrationAccounts.github;
15+
const integrationConfig =
16+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
17+
integrationAccount.integrationConfiguration as Record<string, any>;
18+
// Fetch teams from the API
19+
const teams = await getTeams({ workspaceId });
20+
21+
// Create a map of teams with label and value properties
22+
const teamOptions = teams.map((team: Team) => ({
23+
label: team.name,
24+
value: team.id,
25+
}));
26+
27+
const labels = await getLabels({ workspaceId, teamId: null });
28+
// Create a map of label with label and value properties
29+
const labelOptions = labels.map((label: Label) => ({
30+
label: label.name,
31+
value: label.id,
32+
}));
33+
34+
const response = (
35+
await axios.get(
36+
`https://api.github.com/user/installations/${integrationAccount.accountId}/repositories`,
37+
getGithubHeaders(integrationConfig.token),
38+
)
39+
).data;
40+
41+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
42+
let repo: any = {
43+
type: 'text',
44+
title: 'Repository Id',
45+
validation: {
46+
required: true,
47+
},
48+
};
49+
if (response) {
50+
const repositoryOptions = response.repositories.map(
51+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
52+
(repo: any) => ({
53+
label: repo.full_name,
54+
value: repo.id,
55+
}),
56+
);
57+
repo = {
58+
type: 'select',
59+
title: 'Repositories',
60+
validation: {
61+
required: true,
62+
},
63+
options: repositoryOptions,
64+
};
65+
}
66+
67+
return {
68+
type: 'object',
69+
properties: {
70+
repoTeamMappings: {
71+
type: 'array',
72+
title: 'Repo to Team Mappings',
73+
description: 'Map each repo to a team',
74+
items: {
75+
type: 'object',
76+
properties: {
77+
repo,
78+
teamId: {
79+
type: 'select',
80+
title: 'Teams',
81+
validation: {
82+
required: true,
83+
},
84+
options: teamOptions,
85+
},
86+
},
87+
},
88+
},
89+
githubLabel: {
90+
type: 'select',
91+
title: 'Select a label to sync with Github',
92+
validate: { required: true },
93+
options: labelOptions,
94+
},
95+
},
96+
};
97+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ActionEventPayload, ModelNameEnum, logger } from '@tegonhq/sdk';
2+
import { onLabelHandler } from './on-label-handler';
3+
import { commentSync } from 'triggers/comment-sync';
4+
5+
export const onCreateHandler = async (actionPayload: ActionEventPayload) => {
6+
// Handle different event types
7+
switch (actionPayload.type) {
8+
case ModelNameEnum.Issue:
9+
return await onLabelHandler(actionPayload);
10+
11+
case ModelNameEnum.IssueComment:
12+
return await commentSync(actionPayload);
13+
14+
case ModelNameEnum.LinkedIssue:
15+
return undefined;
16+
17+
default:
18+
logger.debug('Unhandled Github event type:', actionPayload.type);
19+
}
20+
21+
return { status: 200 };
22+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ActionEventPayload, getIssueById, logger } from '@tegonhq/sdk';
2+
import { issueSync } from 'triggers/issue-sync';
3+
4+
export const onLabelHandler = async (actionPayload: ActionEventPayload) => {
5+
const { modelId: issueId, action } = actionPayload;
6+
const issue = await getIssueById({ issueId });
7+
8+
const githubLabel = action.data.inputs.githubLabel;
9+
10+
if (githubLabel && issue.labelIds.includes(githubLabel)) {
11+
logger.log(`Github mapped label present in the issue`);
12+
return await issueSync(actionPayload);
13+
}
14+
15+
logger.log(`Github mapped label is not present in the issue`);
16+
return { message: 'Github mapped label is not present in the issue' };
17+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {
2+
ActionEventPayload,
3+
LinkedIssue,
4+
ModelNameEnum,
5+
logger,
6+
} from '@tegonhq/sdk';
7+
import { onLabelHandler } from './on-label-handler';
8+
import axios from 'axios';
9+
import { issueSync } from 'triggers/issue-sync';
10+
11+
export const onUpdateHandler = async (actionPayload: ActionEventPayload) => {
12+
// Handle different event types
13+
switch (actionPayload.type) {
14+
case ModelNameEnum.Issue:
15+
// TODO(new): Modify this to service
16+
// const linkedIssue = await getLinkedIssuesByIssueId({issueId: actionPayload.modelId})
17+
const linkedIssues = (
18+
await axios.get(
19+
`/api/v1/linked_issues/issue?issueId=${actionPayload.modelId}`,
20+
)
21+
).data;
22+
if (linkedIssues.length > 0) {
23+
const githubLinkedIssues = linkedIssues.filter(
24+
(linkedIssue: LinkedIssue) => {
25+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26+
const sourceData = linkedIssue.sourceData as Record<string, any>;
27+
return sourceData.type === 'github';
28+
},
29+
);
30+
31+
if (githubLinkedIssues.length > 0) {
32+
return await Promise.all(
33+
githubLinkedIssues.map(async (linkedIssue: LinkedIssue) => {
34+
actionPayload.linkedIssue = linkedIssue;
35+
return await issueSync(actionPayload);
36+
}),
37+
);
38+
}
39+
}
40+
return await onLabelHandler(actionPayload);
41+
42+
case ModelNameEnum.IssueComment:
43+
return undefined;
44+
45+
case ModelNameEnum.LinkedIssue:
46+
return undefined;
47+
48+
default:
49+
logger.debug('Unhandled Github event type:', actionPayload.type);
50+
}
51+
52+
return { status: 200 };
53+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { ActionEventPayload, logger } from '@tegonhq/sdk';
2+
import { commentEvent } from 'triggers/comment-event';
3+
4+
export const webhookHandler = async (
5+
payload: ActionEventPayload,
6+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7+
): Promise<any> => {
8+
const { eventBody, eventHeaders } = payload;
9+
10+
const eventType = eventHeaders['x-github-event'];
11+
if (
12+
['tegon-bot[bot]', 'tegon-bot-dev[bot]'].includes(eventBody.sender.login)
13+
) {
14+
logger.log('Ignoring BOT message from Github');
15+
return undefined;
16+
}
17+
18+
switch (eventType) {
19+
case 'issues':
20+
return undefined;
21+
22+
case 'issue_comment':
23+
return await commentEvent(payload);
24+
25+
case 'pull_request':
26+
return undefined;
27+
28+
case 'installation':
29+
return undefined;
30+
31+
case 'installation_repositories':
32+
return undefined;
33+
34+
default:
35+
logger.log(`couldn't find eventType ${eventType}`);
36+
}
37+
38+
return undefined;
39+
};

0 commit comments

Comments
 (0)