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

Commit 3165836

Browse files
saimanojharshithmullapudi
authored andcommitted
Fix: whatsapp actions with notification
1 parent 1a00ad9 commit 3165836

File tree

15 files changed

+311
-168
lines changed

15 files changed

+311
-168
lines changed

actions/whatsapp/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
{
1616
"type": "on_update",
17-
"entities": ["LinkedIssue"]
17+
"entities": ["Issue"]
1818
}
1919
],
2020
"integrations": ["whatsapp"]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ActionEventPayload, ModelNameEnum, logger } from '@tegonhq/sdk';
2+
import { issueSync } from 'triggers/update-issue';
3+
4+
export const onUpdateHandler = async (actionPayload: ActionEventPayload) => {
5+
// Handle different event types
6+
switch (actionPayload.type) {
7+
case ModelNameEnum.Issue:
8+
return await issueSync(actionPayload);
9+
10+
default:
11+
logger.debug('Unhandled Whatsapp event type:', actionPayload.type);
12+
}
13+
14+
return { status: 200 };
15+
};

actions/whatsapp/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ActionEventPayload, ActionTypesEnum } from '@tegonhq/sdk';
33
import { webhookHandler } from './handlers/webhook-handler';
44
import { getInputs } from 'handlers/get-inputs';
55
import { onCreateHandler } from 'handlers/on-create-handler';
6+
import { onUpdateHandler } from 'handlers/on-update-handler';
67

78
export async function run(eventPayload: ActionEventPayload) {
89
switch (eventPayload.event) {
@@ -15,6 +16,9 @@ export async function run(eventPayload: ActionEventPayload) {
1516
case ActionTypesEnum.ON_CREATE:
1617
return await onCreateHandler(eventPayload);
1718

19+
case ActionTypesEnum.ON_UPDATE:
20+
return onUpdateHandler(eventPayload);
21+
1822
default:
1923
return {
2024
message: `The event payload type "${eventPayload.event}" is not recognized`,

actions/whatsapp/triggers/comment-sync.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const commentSync = async (actionPayload: ActionEventPayload) => {
2727
}
2828

2929
const parentIssueComment = issueComment.parent;
30-
3130
const parentSourceMetadata = parentIssueComment
3231
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
3332
(parentIssueComment.sourceMetadata as Record<string, any>)
@@ -59,7 +58,7 @@ export const commentSync = async (actionPayload: ActionEventPayload) => {
5958
}
6059

6160
// Send a POST request to the Slack API to post the message
62-
await axios.post('http://localhost:3002/messages/broadcast', {
61+
await axios.post(`${process.env.SOCKET_SERVER_URL}/messages/broadcast`, {
6362
clientId: integrationAccount.accountId,
6463
payload: {
6564
type: 'ISSUE_COMMENT',

actions/whatsapp/triggers/triage.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ export const whatsappTriage = async (
2222
body,
2323
messageId: { _serialized: sourceId },
2424
messageId,
25-
chat: { name: username, id: chatId },
25+
chat: { name: chatName, id: chatId },
2626
from,
27+
user: {
28+
id: { _serialized: whatsappUserId },
29+
name: username,
30+
},
2731
} = eventBody;
2832

2933
// Skip status messages from WhatsApp
@@ -131,15 +135,15 @@ export const whatsappTriage = async (
131135
whatsappUsername,
132136
);
133137
const supportData = {
134-
email: chatId._serialized,
138+
email: whatsappUserId,
135139
name: username,
136140
source: 'Whatsapp',
137141
companyId,
138142
};
139143

140144
const createdIssue = await createIssue({ ...issueInput, supportData });
141145

142-
await axios.post('http://localhost:3002/messages/broadcast', {
146+
await axios.post(`${process.env.SOCKET_SERVER_URL}/messages/broadcast`, {
143147
clientId: integrationAccount.accountId,
144148
payload: {
145149
type: 'ISSUE_CREATED',
@@ -151,7 +155,7 @@ export const whatsappTriage = async (
151155
// Create Link Comment
152156
const issueComment = await createIssueComment({
153157
issueId: createdIssue.id,
154-
body: `Whatsapp conversation in #${whatsappUsername}`,
158+
body: `Whatsapp conversation in #${chatName}`,
155159
sourceMetadata: { ...sourceMetadata, synced: true },
156160
});
157161

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
ActionEventPayload,
3+
getIssueById,
4+
getLinkedIssuesByIssueId,
5+
getWorkflowsByTeam,
6+
JsonObject,
7+
} from '@tegonhq/sdk';
8+
import axios from 'axios';
9+
10+
export const issueSync = async (actionPayload: ActionEventPayload) => {
11+
const { changedData } = actionPayload;
12+
13+
if (!changedData.stateId) {
14+
return {
15+
message: 'No state change detected',
16+
};
17+
}
18+
19+
const integrationAccount = actionPayload.integrationAccounts.whatsapp;
20+
21+
const issue = await getIssueById({ issueId: actionPayload.modelId });
22+
23+
const workflows = await getWorkflowsByTeam({ teamId: issue.teamId });
24+
25+
const workflowState = workflows.find(
26+
(workflow) => workflow.id === changedData.stateId,
27+
);
28+
29+
if (!workflowState) {
30+
return {
31+
message: 'Workflow state not found',
32+
};
33+
}
34+
35+
const isDone = workflowState.category === 'COMPLETED';
36+
37+
if (isDone) {
38+
const message = `This ticket #${issue.number} is closed`;
39+
const linkedIssues = await getLinkedIssuesByIssueId({ issueId: issue.id });
40+
41+
linkedIssues.map(async (linkedIssue) => {
42+
const sourceData = linkedIssue.sourceData as JsonObject;
43+
const clientId = integrationAccount.accountId;
44+
const chatId = sourceData.chatId as JsonObject;
45+
46+
if (sourceData.type === integrationAccount.integrationDefinition.slug) {
47+
await axios.post(
48+
`${process.env.SOCKET_SERVER_URL}/messages/broadcast`,
49+
{
50+
clientId,
51+
payload: {
52+
chatId: chatId._serialized,
53+
message,
54+
},
55+
},
56+
{
57+
headers: {
58+
'Content-Type': 'application/json',
59+
},
60+
},
61+
);
62+
}
63+
});
64+
}
65+
66+
return {
67+
message: `Issue state is ${isDone ? 'completed' : 'not completed'}`,
68+
};
69+
};

actions/whatsapp/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export async function isCreateTicket(
9292
),
9393
},
9494
],
95-
llmModel: LLMMappings.GPT35TURBO,
95+
llmModel: LLMMappings.GPT4O,
9696
model: 'GroupCompanyName',
9797
workspaceId,
9898
};

apps/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
"rxjs": "7.8.1",
124124
"simple-oauth2": "^5.0.0",
125125
"socket.io": "^4.7.5",
126+
"socket.io-client": "^4.7.4",
126127
"supertokens-node": "^21.0.0",
127128
"turndown": "^7.2.0",
128129
"typesense": "^1.7.2",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { PrismaClient } from '@prisma/client';
2+
import axios from 'axios';
3+
import { createIntegrationAccount } from 'integrations/utils';
4+
5+
const prisma = new PrismaClient();
6+
7+
export const integrationCreate = async (
8+
userId: string,
9+
workspaceId: string,
10+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11+
data: any,
12+
) => {
13+
const { oauthResponse, personal, integrationDefinition, oauthParams } = data;
14+
const integrationConfiguration = {
15+
access_token: oauthResponse.access_token,
16+
access_expires_in: oauthResponse.expires_at,
17+
};
18+
19+
console.log(oauthResponse, oauthParams);
20+
const appData = await axios.get(
21+
`https://graph.facebook.com/v22.0/debug_token?input_token=${oauthParams.code}`,
22+
);
23+
24+
const businessManagementScope = appData.data.data.granular_scopes.find(
25+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26+
(scope: any) => scope.scope === 'whatsapp_business_management',
27+
);
28+
29+
console.log(businessManagementScope);
30+
const accountId = businessManagementScope?.target_ids[0];
31+
32+
if (!accountId) {
33+
throw new Error('No WhatsApp Business Account ID found');
34+
}
35+
36+
// Subscribe the app to the WhatsApp Business Account
37+
await axios.post(
38+
`https://graph.facebook.com/v22.0/${accountId}/subscribed_apps`,
39+
{},
40+
{
41+
headers: {
42+
Authorization: `Bearer ${integrationConfiguration.access_token}`,
43+
},
44+
},
45+
);
46+
47+
// Update the integration account with the new configuration in the database
48+
const integrationAccount = await createIntegrationAccount(prisma, {
49+
userId,
50+
accountId,
51+
config: integrationConfiguration,
52+
workspaceId,
53+
integrationDefinitionId: integrationDefinition.id,
54+
personal,
55+
});
56+
57+
return {
58+
message: `Created integration account ${integrationAccount.id}`,
59+
};
60+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
IntegrationEventPayload,
3+
IntegrationPayloadEventType,
4+
} from '@tegonhq/types';
5+
6+
import { integrationCreate } from './account-create';
7+
import { spec } from './spec';
8+
import { webhookResponse } from './webhook-response';
9+
10+
export default async function run(eventPayload: IntegrationEventPayload) {
11+
switch (eventPayload.event) {
12+
/**
13+
* This is used to identify to which integration account the webhook belongs to
14+
*/
15+
case IntegrationPayloadEventType.GET_CONNECTED_ACCOUNT_ID:
16+
return eventPayload.data.eventBody.accountId;
17+
18+
case IntegrationPayloadEventType.SPEC:
19+
return spec();
20+
21+
case IntegrationPayloadEventType.IS_ACTION_SUPPORTED_EVENT:
22+
return true;
23+
24+
// Used to save settings data
25+
case IntegrationPayloadEventType.CREATE:
26+
return await integrationCreate(
27+
eventPayload.userId,
28+
eventPayload.workspaceId,
29+
eventPayload.data,
30+
);
31+
32+
case IntegrationPayloadEventType.WEBHOOK_RESPONSE:
33+
return await webhookResponse(
34+
eventPayload.eventQueryParams,
35+
eventPayload.eventBody,
36+
);
37+
38+
default:
39+
return {
40+
message: `The event payload type is ${eventPayload.event}`,
41+
};
42+
}
43+
}

0 commit comments

Comments
 (0)