diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e621482 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 + +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" diff --git a/README.md b/README.md index fbf1d81..4bd9a08 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - Work anniversaries, including a welcome message on the first day - Company-observed holidays -## Setup +## ⚙️ Setup - Clone the repository and run `npm install`. - Ensure your [AWS credentials are available](https://serverless.com/framework/docs/providers/aws/guide/credentials/). @@ -40,7 +40,7 @@ To run it locally, you can create an `.env` file. To deploy it in the cloud, you Apart from the env variables, it will read the `BAMBOOHR_KEY` secret from [AWS Secrets Manager](https://aws.amazon.com/es/secrets-manager/). -## Usage +## 🗒️ Usage ### Deployment @@ -58,7 +58,7 @@ After successful deployment, you can invoke the deployed function by using the f serverless invoke --function main ``` -## Local development +## 🏗️ Local development First you need to create an `.env` file with the following variables: @@ -99,6 +99,11 @@ When you're finished, remember to run: npm run infra:dev:stop ``` +## 🔗 Useful links + +- [Slack Block Kit documentation](https://api.slack.com/block-kit) +- [Slack Block Kit builder](https://app.slack.com/block-kit-builder) + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -122,4 +127,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/src/constants/defaultMessage.ts b/src/constants/defaultMessage.ts index be6f89c..d2dbf26 100644 --- a/src/constants/defaultMessage.ts +++ b/src/constants/defaultMessage.ts @@ -3,8 +3,7 @@ export default { elements: [ { type: 'mrkdwn', - plain_text: - ':mad-hatter::teapot: *A very Merry Unbirthday to you all!* :mad-hatter::teapot:', + text: ':mad-hatter::teapot: *A very Merry Unbirthday to you all!* :mad-hatter::teapot:', }, ], }; diff --git a/src/handler.ts b/src/handler.ts index e5f3864..7f7c873 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -9,7 +9,7 @@ import { publishEmployeesAtOffice, publishEmployeesCelebrations, pusblishBankHolidays, -} from './publisher'; +} from './publishers'; import { getSecret, initializeSecretsManager } from './secrets'; import { TSecrets } from '.'; diff --git a/src/publishers/bank-holidays-publisher.ts b/src/publishers/bank-holidays-publisher.ts new file mode 100644 index 0000000..6de16e0 --- /dev/null +++ b/src/publishers/bank-holidays-publisher.ts @@ -0,0 +1,85 @@ +import moment from 'moment'; + +import { TWhosOut } from '..'; +import { + FRIDAY_ISO_WEEKDAY, + HUMAN_READABLE_DATE, + MONDAY_ISO_WEEKDAY, +} from '../common'; +import { postSlackMessage } from '../http'; + +export const pusblishBankHolidays = async ( + bankHolidays: TWhosOut, + today: moment.Moment +): Promise => { + const monthBankHolidaysBlocks: any[] = []; + let nextBankHolidaysBlocks: any[] = []; + + if ( + bankHolidays.month.length > 0 && + ((today.date() === 1 && today.isoWeekday() <= FRIDAY_ISO_WEEKDAY) || + (today.date() === 2 && today.isoWeekday() === MONDAY_ISO_WEEKDAY) || + (today.date() === 3 && today.isoWeekday() === MONDAY_ISO_WEEKDAY)) + ) { + monthBankHolidaysBlocks.push({ + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: '*This month bank holidays:*', + }, + ], + }); + + monthBankHolidaysBlocks.push( + ...bankHolidays.month.map(holiday => ({ + type: 'section', + text: { + type: 'mrkdwn', + text: `*${holiday.name}*\n${moment(holiday.start).format( + HUMAN_READABLE_DATE + )}`, + }, + })) + ); + + monthBankHolidaysBlocks.push({ + type: 'divider', + }); + } + + if (bankHolidays.next.length > 0) { + nextBankHolidaysBlocks = bankHolidays.next.map(holiday => ({ + type: 'section', + text: { + type: 'mrkdwn', + text: `*${holiday.name}*\n${moment(holiday.start).format( + HUMAN_READABLE_DATE + )}`, + }, + })); + } + + if (monthBankHolidaysBlocks.length > 0 || nextBankHolidaysBlocks.length > 0) { + const message = { + text: '🏖️ Bank Holidays', + blocks: [ + { + type: 'header', + text: { + type: 'plain_text', + text: '🏖️ Bank Holidays', + emoji: true, + }, + }, + ...monthBankHolidaysBlocks, + ...nextBankHolidaysBlocks, + ], + }; + + await postSlackMessage( + process.env.BANK_HOLIDAYS_WEBHOOK_URL ?? '', + message + ); + } +}; diff --git a/src/publishers/employees-at-office-publisher.ts b/src/publishers/employees-at-office-publisher.ts new file mode 100644 index 0000000..fbf2441 --- /dev/null +++ b/src/publishers/employees-at-office-publisher.ts @@ -0,0 +1,72 @@ +import moment from 'moment'; + +import { + TBambooHREmployeeAtOffice, + TBambooHREmployeeExtended, + TWhosOut, +} from '..'; +import { FRIDAY_ISO_WEEKDAY } from '../common'; +import { postSlackMessage } from '../http'; + +export const publishEmployeesAtOffice = async ( + employees: TBambooHREmployeeExtended[], + employeesAtOffice: TBambooHREmployeeAtOffice[], + today: moment.Moment +): Promise => { + // First filter 'employeesAtOffice' to those included in 'employees' + const filteredEmployeesAtOffice = employeesAtOffice.reduce< + TBambooHREmployeeExtended[] + >((previousValue, currentEmployee) => { + const employee = employees.find(e => e.id === currentEmployee.employeeId); + return employee ? previousValue.concat(employee) : previousValue; + }, []); + + const blocks = + filteredEmployeesAtOffice.length > 0 + ? filteredEmployeesAtOffice.map(e => ({ + type: 'context', + elements: [ + { + type: 'image', + image_url: e.photoUrl, + alt_text: 'employee avatar', + }, + { + type: 'mrkdwn', + text: `*${e.displayName}*`, + }, + ], + })) + : [ + { + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: ':eyes: *Nobody at the office!*', + }, + ], + }, + ]; + + const message = { + text: `🏢 ${ + today.isoWeekday() < FRIDAY_ISO_WEEKDAY ? 'Tomorrow' : 'Next Monday' + } at One Beyond offices`, + blocks: [ + { + type: 'header', + text: { + type: 'plain_text', + text: `🏢 ${ + today.isoWeekday() < FRIDAY_ISO_WEEKDAY ? 'Tomorrow' : 'Next Monday' + } at One Beyond offices`, + emoji: true, + }, + }, + ...blocks, + ], + }; + + await postSlackMessage(process.env.OFFICE_WEBHOOK_URL ?? '', message); +}; diff --git a/src/publisher.ts b/src/publishers/employees-celebrations-publisher.ts similarity index 56% rename from src/publisher.ts rename to src/publishers/employees-celebrations-publisher.ts index 9b6795d..9d18735 100644 --- a/src/publisher.ts +++ b/src/publishers/employees-celebrations-publisher.ts @@ -1,165 +1,61 @@ import moment from 'moment'; -import defaultMessage from './constants/defaultMessage'; +import defaultMessage from '../constants/defaultMessage'; -import { - TBambooHREmployeeAtOffice, - TBambooHREmployeeExtended, - TWhosOut, -} from '.'; -import { - FRIDAY_ISO_WEEKDAY, - HUMAN_READABLE_DATE, - MONDAY_ISO_WEEKDAY, - ordinalSuffixOf, - YEAR_MONTH_DATE_FORMAT, -} from './common'; -import { postSlackMessage } from './http'; +import { TBambooHREmployeeExtended } from '..'; +import { FRIDAY_ISO_WEEKDAY, ordinalSuffixOf } from '../common'; +import { postSlackMessage } from '../http'; -export const pusblishBankHolidays = async ( - bankHolidays: TWhosOut, +export const publishEmployeesCelebrations = async ( + employees: TBambooHREmployeeExtended[], today: moment.Moment ): Promise => { - const monthBankHolidaysBlocks: any[] = []; - let nextBankHolidaysBlocks: any[] = []; - - if ( - bankHolidays.month.length > 0 && - ((today.date() === 1 && today.isoWeekday() <= FRIDAY_ISO_WEEKDAY) || - (today.date() === 2 && today.isoWeekday() === MONDAY_ISO_WEEKDAY) || - (today.date() === 3 && today.isoWeekday() === MONDAY_ISO_WEEKDAY)) - ) { - monthBankHolidaysBlocks.push({ - type: 'context', - elements: [ - { - type: 'mrkdwn', - text: '*This month bank holidays:*', - }, - ], - }); - - monthBankHolidaysBlocks.push( - ...bankHolidays.month.map(holiday => ({ - type: 'section', - text: { - type: 'mrkdwn', - text: `*${holiday.name}*\n${moment(holiday.start).format( - HUMAN_READABLE_DATE - )}`, - }, - })) - ); - - monthBankHolidaysBlocks.push({ - type: 'divider', - }); - } - - if (bankHolidays.next.length > 0) { - nextBankHolidaysBlocks = bankHolidays.next.map(holiday => ({ - type: 'section', - text: { - type: 'mrkdwn', - text: `*${holiday.name}*\n${moment(holiday.start).format( - HUMAN_READABLE_DATE - )}`, - }, - })); - } + const firstDayBlocks: any[] = buildFirstDayBlocks(employees, today); + const birthdaysBlocks: any[] = buildBirthdaysBlocks(employees, today); + const anniversariesBlocks: any[] = buildAnniversariesBlocks(employees, today); - if (monthBankHolidaysBlocks.length > 0 || nextBankHolidaysBlocks.length > 0) { - const message = { - text: '🏖️ Bank Holidays', - blocks: [ - { - type: 'header', - text: { - type: 'plain_text', - text: '🏖️ Bank Holidays', - emoji: true, - }, - }, - ...monthBankHolidaysBlocks, - ...nextBankHolidaysBlocks, - ], - }; + const celebrationMessages = [ + ...firstDayBlocks, + ...birthdaysBlocks, + ...anniversariesBlocks, + ]; - await postSlackMessage( - process.env.BANK_HOLIDAYS_WEBHOOK_URL ?? '', - message - ); - } + await postSlackMessage( + process.env.CELEBRATIONS_WEBHOOK_URL ?? '', + buildMessageToSend(celebrationMessages) + ); }; -export const publishEmployeesAtOffice = async ( - employees: TBambooHREmployeeExtended[], - employeesAtOffice: TBambooHREmployeeAtOffice[], - today: moment.Moment -): Promise => { - // First filter 'employeesAtOffice' to those included in 'employees' - const filteredEmployeesAtOffice = employeesAtOffice.reduce< - TBambooHREmployeeExtended[] - >((previousValue, currentEmployee) => { - const employee = employees.find(e => e.id === currentEmployee.employeeId); - return employee ? previousValue.concat(employee) : previousValue; - }, []); - - const blocks = - filteredEmployeesAtOffice.length > 0 - ? filteredEmployeesAtOffice.map(e => ({ - type: 'context', - elements: [ - { - type: 'image', - image_url: e.photoUrl, - alt_text: 'employee avatar', - }, - { - type: 'mrkdwn', - text: `*${e.displayName}*`, - }, - ], - })) - : [ - { - type: 'context', - elements: [ - { - type: 'mrkdwn', - text: ':eyes: *Nobody at the office!*', - }, - ], - }, - ]; - - const message = { - text: `🏢 ${ - today.isoWeekday() < FRIDAY_ISO_WEEKDAY ? 'Tomorrow' : 'Next Monday' - } at One Beyond offices`, +const buildMessageToSend = (messages: object[]) => { + const base = { + text: "🥳 Let's celebrate together", blocks: [ { type: 'header', text: { type: 'plain_text', - text: `🏢 ${ - today.isoWeekday() < FRIDAY_ISO_WEEKDAY ? 'Tomorrow' : 'Next Monday' - } at One Beyond offices`, + text: "🥳 Let's celebrate together", emoji: true, }, }, - ...blocks, ], }; - await postSlackMessage(process.env.OFFICE_WEBHOOK_URL ?? '', message); + return messages.length > 0 + ? { + ...base, + blocks: [...base.blocks, ...messages], + } + : { + ...base, + blocks: [...base.blocks, defaultMessage], + }; }; -export const publishEmployeesCelebrations = async ( +const buildFirstDayBlocks = ( employees: TBambooHREmployeeExtended[], today: moment.Moment -): Promise => { - // FIRST DAY +): any[] => { const firstDayEmployees = employees .filter(employee => moment(employee.hireDate).isValid()) .filter(employee => moment(employee.hireDate).isSame(today, 'day')); @@ -197,8 +93,13 @@ export const publishEmployeesCelebrations = async ( type: 'divider', }); } + return firstDayBlocks; +}; - // BIRTHDAY +const buildBirthdaysBlocks = ( + employees: TBambooHREmployeeExtended[], + today: moment.Moment +): any[] => { const birthdays = employees .filter(employee => employee.birthday) .reduce((previousValue, employee) => { @@ -270,8 +171,13 @@ export const publishEmployeesCelebrations = async ( type: 'divider', }); } + return birthdaysBlocks; +}; - // ANNIVERSARY +const buildAnniversariesBlocks = ( + employees: TBambooHREmployeeExtended[], + today: moment.Moment +): any[] => { const anniversaries = employees .filter(employee => moment(employee.hireDate).isValid()) .reduce((previousValue, employee) => { @@ -359,43 +265,5 @@ export const publishEmployeesCelebrations = async ( }); } - const celebrationMessages = [ - ...firstDayBlocks, - ...birthdaysBlocks, - ...anniversariesBlocks, - ]; - - const buildMessageToSend = (messages: object[]) => { - const base = { - text: "🥳 Let's celebrate together", - blocks: [ - { - type: 'header', - text: { - type: 'plain_text', - text: "🥳 Let's celebrate together", - emoji: true, - }, - }, - ], - }; - - return messages.length > 0 - ? { - ...base, - blocks: [...base.blocks, ...messages], - } - : { - ...base, - blocks: { - ...base.blocks, - ...defaultMessage, - }, - }; - }; - - await postSlackMessage( - process.env.CELEBRATIONS_WEBHOOK_URL ?? '', - buildMessageToSend(celebrationMessages) - ); + return anniversariesBlocks; }; diff --git a/src/publishers/index.ts b/src/publishers/index.ts new file mode 100644 index 0000000..cac8660 --- /dev/null +++ b/src/publishers/index.ts @@ -0,0 +1,3 @@ +export { pusblishBankHolidays } from './bank-holidays-publisher'; +export { publishEmployeesAtOffice } from './employees-at-office-publisher'; +export { publishEmployeesCelebrations } from './employees-celebrations-publisher';