Skip to content

Commit

Permalink
Feat: slack support tickets
Browse files Browse the repository at this point in the history
  • Loading branch information
saimanoj authored and harshithmullapudi committed Jan 20, 2025
1 parent 9b921fd commit 3b74333
Show file tree
Hide file tree
Showing 17 changed files with 4,991 additions and 0 deletions.
59 changes: 59 additions & 0 deletions actions/slack-support/.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/slack-support/.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/slack-support/.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/slack-support/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.
13 changes: 13 additions & 0 deletions actions/slack-support/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"slug": "slack-support",
"name": "Slack-Support",
"icon": "slack",
"description": "Create support tickets from Slack messages and sync threads",
"triggers": [
{
"type": "source_webhook",
"entities": ["slack-support"]
}
],
"integrations": ["slack"]
}
122 changes: 122 additions & 0 deletions actions/slack-support/handlers/get-inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { getLabels, getTeams, Label } from '@tegonhq/sdk';
import { ActionEventPayload, IntegrationAccount } from '@tegonhq/sdk';
import axios from 'axios';
import { getSlackHeaders } from 'utils';

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

const integrationAccount = integrationAccounts.slack;

const labels = await getLabels({
workspaceId,
});

const teams = await getTeams({ workspaceId });

const teamOptions = teams.map((team) => ({
label: team.name,
value: team.id,
}));

const labelOptions = labels.map((label: Label) => ({
label: label.name,
value: label.id,
}));

const slackChannels = await getAllSlackChannels(integrationAccount);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
let channelId: any = {
type: 'text',
title: 'Channels',
validation: {
required: true,
},
};

if (slackChannels.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const channelOptions = slackChannels.map((channel: any) => ({
label: channel.name,
value: channel.id,
}));
channelId = {
type: 'select',
title: 'Channels',
validation: {
required: true,
},
options: channelOptions,
};
}

return {
type: 'object',
properties: {
teamId: {
type: 'select',
title: 'Team',
description: 'Select a support team',
validation: {
required: true,
},
options: teamOptions,
},
channelLabelMappings: {
type: 'array',
title: 'Channel to Customer Mappings',
description: 'Map each channel to a Customer',
items: {
type: 'object',
properties: {
channelId,
labelId: {
type: 'select',
title: 'Customer',
validation: {
required: true,
},
options: labelOptions,
},
},
},
},
},
};
};

async function getAllSlackChannels(integrationAccount: IntegrationAccount) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let allChannels: any[] = [];
let nextCursor = '';
const baseUrl =
'https://slack.com/api/conversations.list?types=private_channel,public_channel&limit=200';

try {
do {
const url = nextCursor ? `${baseUrl}&cursor=${nextCursor}` : baseUrl;
const response = await axios.get(
url,
getSlackHeaders(integrationAccount),
);
const slackChannels = response.data;

if (!slackChannels.ok) {
throw new Error(
`Error fetching Slack channels: ${slackChannels.error}`,
);
}

// Add the retrieved channels to the list
allChannels = [...allChannels, ...slackChannels.channels];

// Get the next cursor if available
nextCursor = slackChannels.response_metadata?.next_cursor || '';
} while (nextCursor); // Continue if there's a next page

return allChannels;
} catch (error) {
return [];
}
}
49 changes: 49 additions & 0 deletions actions/slack-support/handlers/webhook-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ActionEventPayload, logger } from '@tegonhq/sdk';

import { slackTriage } from '../triggers/triage';

import { SlackIntegrationSettings } from '../types';

export const webhookHandler = async (payload: ActionEventPayload) => {
const { eventBody, integrationAccounts, userId, action } = payload;

// Check if the event is a URL verification challenge
if (eventBody.type === 'url_verification') {
logger.log('Responding to Slack URL verification challenge');
return { challenge: eventBody.challenge };
}

const { event, team_id: teamId } = eventBody;

const integrationAccount = integrationAccounts.slack;

// If no integration account is found, log and return undefined
if (!integrationAccount) {
logger.debug('No integration account found for team:', teamId);
return { message: `No integration account found for team: ${teamId}` };
}

const slackSettings =
integrationAccount.settings as unknown as SlackIntegrationSettings;
// Check if the message is from the bot user
const isBotMessage = slackSettings.botUserId === event.user;

// If the message is from the bot, ignore it
if (isBotMessage) {
logger.debug('Ignoring bot message');
return { message: `Ignoring bot message` };
}
logger.log('its here');

logger.log('Processing Slack event:', event.type);

// Handle different event types
switch (event.type) {
case 'message':
return await slackTriage(integrationAccount, userId, eventBody, action);

default:
logger.debug('Unhandled Slack event type:', event.type);
return undefined;
}
};
19 changes: 19 additions & 0 deletions actions/slack-support/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ActionEventPayload, ActionTypesEnum } from '@tegonhq/sdk';

import { webhookHandler } from './handlers/webhook-handler';
import { getInputs } from 'handlers/get-inputs';

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

case ActionTypesEnum.SOURCE_WEBHOOK:
return webhookHandler(eventPayload);

default:
return {
message: `The event payload type "${eventPayload.event}" is not recognized`,
};
}
}
30 changes: 30 additions & 0 deletions actions/slack-support/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "slack-support",
"version": "0.1.0",
"description": "Basic slack workflows",
"private": true,
"scripts": {
"lint": "eslint . --fix",
"trigger-dev": "npx trigger.dev@beta dev -a http://localhost:3030"
},
"packageManager": "[email protected]",
"dependencies": {
"@tegonhq/sdk": "^0.1.12",
"@trigger.dev/sdk": "3.0.0-beta.56",
"axios": "^1.6.7",
"form-data": "^4.0.0"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^22.1.0",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unused-imports": "^3.0.0",
"jsonwebtoken": "^9.0.2",
"typescript": "^5.3.3"
}
}
Loading

0 comments on commit 3b74333

Please sign in to comment.