diff --git a/packages/twenty-apps/hacktoberfest-2025/activity-summary/README.md b/packages/twenty-apps/hacktoberfest-2025/activity-summary/README.md index 63516cee59..812699492f 100644 --- a/packages/twenty-apps/hacktoberfest-2025/activity-summary/README.md +++ b/packages/twenty-apps/hacktoberfest-2025/activity-summary/README.md @@ -20,29 +20,10 @@ A TypeScript-based reporting bot that summarizes activity from your Twenty CRM w - A [Twenty CRM](https://twenty.com) account with API access - Optional: Slack webhook, Discord webhook, and/or WhatsApp Business API access -## Installation +## Installing dependencies ```bash -# Clone the repository -git clone -cd - # Install dependencies -npm install -``` - -## Configuration - -Create a `.env` file in the root directory with the following variables: -```bash -# Required -TWENTY_API_KEY=your_twenty_api_key_here -DAYS_AGO=7 # Number of days to look back - -# Optional - Include only the platforms you want to use -SLACK_HOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL -DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR/WEBHOOK/URL -FB_GRAPH_TOKEN=your_facebook_graph_api_token -WHATSAPP_RECIPIENT_PHONE_NUMBER=+1234567890 +yarn install ``` ### Environment Variables @@ -56,17 +37,6 @@ WHATSAPP_RECIPIENT_PHONE_NUMBER=+1234567890 | `FB_GRAPH_TOKEN` | ❌ No | Facebook Graph API token for WhatsApp | | `WHATSAPP_RECIPIENT_PHONE_NUMBER` | ❌ No | WhatsApp recipient phone number (with country code) | -## Usage -```bash -# Run the application -npm start -# or -node dist/index.js # if compiled - -# For development -npm run dev # if you have ts-node configured -``` - ## Project Structure ``` . @@ -112,23 +82,10 @@ Bonjour! 🥖 Je m'appelle Kylian Mbaguette. Over the last X days: This bot uses the [Twenty CRM REST API](https://api.twenty.com/rest/). The following endpoints are used: - `GET /people` - Fetch people data -- `GET /companies` - Fetch company data - `GET /opportunities` - Fetch opportunity data - `GET /tasks` - Fetch task data - `GET /workspaceMembers/{id}` - Fetch workspace member details -## Development -```bash -# Install dependencies -npm install - -# Compile TypeScript -npx tsc - -# Run in development mode -npx ts-node index.ts -``` - ## Notes - The "slacker" detection is lighthearted and identifies team members with the most overdue tasks diff --git a/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/people-creation-summariser.ts b/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/people-creation-summariser.ts index 5bbdae49d8..3c4c6cd598 100644 --- a/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/people-creation-summariser.ts +++ b/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/people-creation-summariser.ts @@ -2,6 +2,7 @@ import { request } from "./utils" type Person = { companyId: string + company: Company } type Company = { @@ -11,7 +12,7 @@ type Company = { export const summarisePeopleCreation = async (date: string) => { const { people }: { people: Person[] } = await request( - `people?filter=createdAt[gte]:${date}`, + `people?depth=1&filter=createdAt[gte]:${date}`, ) if (people.length === 0) { @@ -24,12 +25,9 @@ export const summarisePeopleCreation = async (date: string) => { for (const person of people) { const isCompanyTracked = createdForCompanies[person.companyId] if (person.companyId && !isCompanyTracked) { - const { company }: { company: Company } = await request( - `companies/${person.companyId}`, - ) - createdForCompanies[company.id] = company + createdForCompanies[person.company.id] = person.company - if (!company.accountOwnerId) { + if (!person?.company?.accountOwnerId) { numberOfAccountOwnerlessCompanies += 1 } } diff --git a/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/senders.ts b/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/senders.ts index 647fa8865a..618cec6cac 100644 --- a/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/senders.ts +++ b/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/senders.ts @@ -77,7 +77,7 @@ export const sendToSlack = async (params: { }); return { - formattedMesage: slackMessage, + formattedMessage: slackMessage, webhookStatus: response.status, }; }; @@ -92,7 +92,7 @@ export const sendToDiscord = async (params: { opportunityCreationSummary, taskCreationSummary, } = params; - const formattedMesage = `Bonjour! 🥖 Je m'appelle Kylian Mbaguette. Over the last ${process.env.DAYS_AGO} days: + const formattedMessage = `Bonjour! 🥖 Je m'appelle Kylian Mbaguette. Over the last ${process.env.DAYS_AGO} days: **🧑‍💻 People & Companies** ${peopleCreationSummary} @@ -105,7 +105,7 @@ ${taskCreationSummary}`; const body = { username: 'Twenty Bot', - content: formattedMesage, + content: formattedMessage, }; const response = await fetch(process.env.DISCORD_WEBHOOK_URL ?? '', { @@ -115,7 +115,7 @@ ${taskCreationSummary}`; }); return { - formattedMesage, + formattedMessage, webhookStatus: response.status, }; }; @@ -130,7 +130,7 @@ export const sendToWhatsApp = async (params: { opportunityCreationSummary, taskCreationSummary, } = params; - const formattedMesage = `Bonjour! 🥖 Je m'appelle Kylian Mbaguette. Over the last ${process.env.DAYS_AGO} days: + const formattedMessage = `Bonjour! 🥖 Je m'appelle Kylian Mbaguette. Over the last ${process.env.DAYS_AGO} days: *🧑‍💻 People & Companies* ${peopleCreationSummary} @@ -156,7 +156,7 @@ ${taskCreationSummary}`; type: 'text', text: { preview_url: true, - body: formattedMesage, + body: formattedMessage, }, }), }, @@ -165,7 +165,7 @@ ${taskCreationSummary}`; const responseBody = await response.json(); return { - formattedMesage, + formattedMessage, webhookStatus: response.status, webhookResponse: responseBody, }; diff --git a/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/task-creation-summariser.ts b/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/task-creation-summariser.ts index 2de81ba6fe..47530df2da 100644 --- a/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/task-creation-summariser.ts +++ b/packages/twenty-apps/hacktoberfest-2025/activity-summary/serverlessFunctions/summarise-and-send/src/task-creation-summariser.ts @@ -16,26 +16,23 @@ export const summariseTaskCreation = async (date: string) => { return '- No Tasks were added' } + const presentDateISOString = new Date().toISOString() const workspaceMemberIdsDueDateCounter: Record = {} let workspaceMembers = [] - const completedTasks: Task[] = [] - const pendingTasksWithPastDueDates = tasks.reduce((collection: Task[], task) => { + tasks.forEach((task) => { if (task.status === 'DONE') { - completedTasks.push(task) - return collection + return } - if (new Date().toISOString() > task.dueAt) { + if (presentDateISOString >= task.dueAt) { if (!workspaceMemberIdsDueDateCounter[task.createdBy.workspaceMemberId]) { workspaceMemberIdsDueDateCounter[task.createdBy.workspaceMemberId] = 0 } workspaceMemberIdsDueDateCounter[task.createdBy.workspaceMemberId] += 1 - collection.push(task) } - return collection - }, []) + }) for (const { userId, count } of findMaxIncompleteKeys(workspaceMemberIdsDueDateCounter)) { const data = await request(`workspaceMembers/${userId}`) @@ -61,8 +58,17 @@ export const summariseTaskCreation = async (date: string) => { } }, '') : 'No one was caught slacking!' - const taskCompletionPercentage = pendingTasksWithPastDueDates.length > 0 ? 100 - (100 - ((pendingTasksWithPastDueDates.length / (tasks.length - completedTasks.length)) * 100)) : NaN - const taskCompletionMessage = isNaN(taskCompletionPercentage) ? 'No Tasks to be completed' : `${taskCompletionPercentage.toFixed(2)}% Tasks were completed on time` + const tasksCompletedOnTime = tasks.filter( + task => task.status === 'DONE' && task.dueAt >= presentDateISOString + ) + + const taskCompletionPercentage = tasksCompletedOnTime.length > 0 + ? (tasksCompletedOnTime.length / tasks.length) * 100 + : NaN + + const taskCompletionMessage = isNaN(taskCompletionPercentage) + ? 'No completed Tasks yet' + : `${taskCompletionPercentage.toFixed(2)}% of Tasks were completed on time` return `- ${tasks.length} Tasks were created - ${taskCompletionMessage}