Skip to content

Commit

Permalink
Feat: Github Integration (#234)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
saimanoj and harshithmullapudi authored Sep 9, 2024
1 parent 7fb63ce commit 6dbf42f
Show file tree
Hide file tree
Showing 117 changed files with 5,549 additions and 442 deletions.
59 changes: 59 additions & 0 deletions actions/github/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/** Copyright (c) 2024, Tegon, all rights reserved. **/

module.exports = {
extends: [
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended',
],
plugins: ['@typescript-eslint', 'prettier', 'unused-imports', 'import'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
rules: {
curly: 'warn',
eqeqeq: 'error',
'prettier/prettier': 'warn',
'unused-imports/no-unused-imports': 'warn',
'no-else-return': 'warn',
'no-lonely-if': 'warn',
'no-inner-declarations': 'off',
'no-unused-vars': 'off',
'no-useless-computed-key': 'warn',
'no-useless-return': 'warn',
'no-var': 'warn',
'object-shorthand': ['warn', 'always'],
'prefer-arrow-callback': 'warn',
'prefer-const': 'warn',
'prefer-destructuring': ['warn', { AssignmentExpression: { array: true } }],
'prefer-object-spread': 'warn',
'prefer-template': 'warn',
'spaced-comment': ['warn', 'always', { markers: ['/'] }],
yoda: 'warn',
'@typescript-eslint/array-type': ['warn', { default: 'array-simple' }],
'@typescript-eslint/ban-ts-comment': [
'warn',
{
'ts-expect-error': 'allow-with-description',
},
],
'@typescript-eslint/ban-types': 'warn',
'@typescript-eslint/consistent-indexed-object-style': ['warn', 'record'],
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/no-unused-vars': 'warn',
},
parser: '@typescript-eslint/parser',
ignorePatterns: ['src/@@generated/**/*.tsx', 'src/@@generated/**/*.ts'],
overrides: [
{
files: ['scripts/**/*'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
},
],
};
4 changes: 4 additions & 0 deletions actions/github/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.trigger
trigger
trigger.config.ts
4 changes: 4 additions & 0 deletions actions/github/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
17 changes: 17 additions & 0 deletions actions/github/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Overview

Effortlessly integrate Tegon with Slack to transform your project communication and management:

1. Create Issues from Slack Messages:

- Use the "..." menu on any Slack message to effortlessly create a Tegon issue.

2. Automate Issue Creation with Emoji 👀:

- Assign 👀 emoji to a Slack thread to automatically trigger Tegon AI to create a triage issue.
- This feature empowers even non-Tegon users to easily report issues.

3. Synchronized Conversation Threads:
- When issues are reported in Slack, a synced comment thread is automatically created within the corresponding Tegon issue.
- Tegon comments are automatically reflected in the Slack thread, keeping everyone in the loop and vice versa.
- Both the Slack thread and the Tegon comments update in real-time, ensuring everyone stays on the same page regardless of their chosen platform.
21 changes: 21 additions & 0 deletions actions/github/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"slug": "github",
"name": "Github",
"icon": "github",
"description": "Create issues from Github messages, sync Issues and Comments",
"triggers": [
{
"type": "on_create",
"entities": [ "Issue", "IssueComment", "LinkedIssue" ]
},
{
"type": "on_update",
"entities": [ "Issue", "IssueComment" ]
},
{
"type": "source_webhook",
"entities": [ "github" ]
}
],
"integrations": [ "github" ]
}
97 changes: 97 additions & 0 deletions actions/github/handlers/get-inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
ActionEventPayload,
getLabels,
getTeams,
Label,
Team,
} from '@tegonhq/sdk';
import axios from 'axios';
import { getGithubHeaders } from 'utils';

export const getInputs = async (payload: ActionEventPayload) => {
const { workspaceId, integrationAccounts } = payload;

const integrationAccount = integrationAccounts.github;
const integrationConfig =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
integrationAccount.integrationConfiguration as Record<string, any>;
// Fetch teams from the API
const teams = await getTeams({ workspaceId });

// Create a map of teams with label and value properties
const teamOptions = teams.map((team: Team) => ({
label: team.name,
value: team.id,
}));

const labels = await getLabels({ workspaceId, teamId: null });
// Create a map of label with label and value properties
const labelOptions = labels.map((label: Label) => ({
label: label.name,
value: label.id,
}));

const response = (
await axios.get(
`https://api.github.com/user/installations/${integrationAccount.accountId}/repositories`,
getGithubHeaders(integrationConfig.token),
)
).data;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
let repo: any = {
type: 'text',
title: 'Repository Id',
validation: {
required: true,
},
};
if (response) {
const repositoryOptions = response.repositories.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(repo: any) => ({
label: repo.full_name,
value: repo.id,
}),
);
repo = {
type: 'select',
title: 'Repositories',
validation: {
required: true,
},
options: repositoryOptions,
};
}

return {
type: 'object',
properties: {
repoTeamMappings: {
type: 'array',
title: 'Repo to Team Mappings',
description: 'Map each repo to a team',
items: {
type: 'object',
properties: {
repo,
teamId: {
type: 'select',
title: 'Teams',
validation: {
required: true,
},
options: teamOptions,
},
},
},
},
githubLabel: {
type: 'select',
title: 'Select a label to sync with Github',
validate: { required: true },
options: labelOptions,
},
},
};
};
22 changes: 22 additions & 0 deletions actions/github/handlers/on-create-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ActionEventPayload, ModelNameEnum, logger } from '@tegonhq/sdk';
import { onLabelHandler } from './on-label-handler';
import { commentSync } from 'triggers/comment-sync';

export const onCreateHandler = async (actionPayload: ActionEventPayload) => {
// Handle different event types
switch (actionPayload.type) {
case ModelNameEnum.Issue:
return await onLabelHandler(actionPayload);

case ModelNameEnum.IssueComment:
return await commentSync(actionPayload);

case ModelNameEnum.LinkedIssue:
return undefined;

default:
logger.debug('Unhandled Github event type:', actionPayload.type);
}

return { status: 200 };
};
17 changes: 17 additions & 0 deletions actions/github/handlers/on-label-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ActionEventPayload, getIssueById, logger } from '@tegonhq/sdk';
import { issueSync } from 'triggers/issue-sync';

export const onLabelHandler = async (actionPayload: ActionEventPayload) => {
const { modelId: issueId, action } = actionPayload;
const issue = await getIssueById({ issueId });

const githubLabel = action.data.inputs.githubLabel;

if (githubLabel && issue.labelIds.includes(githubLabel)) {
logger.log(`Github mapped label present in the issue`);
return await issueSync(actionPayload);
}

logger.log(`Github mapped label is not present in the issue`);
return { message: 'Github mapped label is not present in the issue' };
};
53 changes: 53 additions & 0 deletions actions/github/handlers/on-update-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
ActionEventPayload,
LinkedIssue,
ModelNameEnum,
logger,
} from '@tegonhq/sdk';
import { onLabelHandler } from './on-label-handler';
import axios from 'axios';
import { issueSync } from 'triggers/issue-sync';

export const onUpdateHandler = async (actionPayload: ActionEventPayload) => {
// Handle different event types
switch (actionPayload.type) {
case ModelNameEnum.Issue:
// TODO(new): Modify this to service
// const linkedIssue = await getLinkedIssuesByIssueId({issueId: actionPayload.modelId})
const linkedIssues = (
await axios.get(
`/api/v1/linked_issues/issue?issueId=${actionPayload.modelId}`,
)
).data;
if (linkedIssues.length > 0) {
const githubLinkedIssues = linkedIssues.filter(
(linkedIssue: LinkedIssue) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sourceData = linkedIssue.sourceData as Record<string, any>;
return sourceData.type === 'github';
},
);

if (githubLinkedIssues.length > 0) {
return await Promise.all(
githubLinkedIssues.map(async (linkedIssue: LinkedIssue) => {
actionPayload.linkedIssue = linkedIssue;
return await issueSync(actionPayload);
}),
);
}
}
return await onLabelHandler(actionPayload);

case ModelNameEnum.IssueComment:
return undefined;

case ModelNameEnum.LinkedIssue:
return undefined;

default:
logger.debug('Unhandled Github event type:', actionPayload.type);
}

return { status: 200 };
};
39 changes: 39 additions & 0 deletions actions/github/handlers/webhook-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ActionEventPayload, logger } from '@tegonhq/sdk';
import { commentEvent } from 'triggers/comment-event';

export const webhookHandler = async (
payload: ActionEventPayload,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> => {
const { eventBody, eventHeaders } = payload;

const eventType = eventHeaders['x-github-event'];
if (
['tegon-bot[bot]', 'tegon-bot-dev[bot]'].includes(eventBody.sender.login)
) {
logger.log('Ignoring BOT message from Github');
return undefined;
}

switch (eventType) {
case 'issues':
return undefined;

case 'issue_comment':
return await commentEvent(payload);

case 'pull_request':
return undefined;

case 'installation':
return undefined;

case 'installation_repositories':
return undefined;

default:
logger.log(`couldn't find eventType ${eventType}`);
}

return undefined;
};
26 changes: 26 additions & 0 deletions actions/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ActionEventPayload, ActionTypesEnum } from '@tegonhq/sdk';
import { getInputs } from 'handlers/get-inputs';
import { onCreateHandler } from 'handlers/on-create-handler';
import { onUpdateHandler } from 'handlers/on-update-handler';
import { webhookHandler } from 'handlers/webhook-handler';

export async function run(eventPayload: ActionEventPayload) {
switch (eventPayload.event) {
case ActionTypesEnum.GET_INPUTS:
return getInputs(eventPayload);

case ActionTypesEnum.ON_CREATE:
return await onCreateHandler(eventPayload);

case ActionTypesEnum.ON_UPDATE:
return await onUpdateHandler(eventPayload);

case ActionTypesEnum.SOURCE_WEBHOOK:
return webhookHandler(eventPayload);

default:
return {
message: `The event payload type "${eventPayload.event}" is not recognized`,
};
}
}
Loading

0 comments on commit 6dbf42f

Please sign in to comment.