Skip to content

Commit

Permalink
Merge pull request #3 from theapexlab/ask-birthday-tests
Browse files Browse the repository at this point in the history
APEX-1588 - Ask birthday tests
  • Loading branch information
BaDo2001 authored Oct 26, 2023
2 parents b6e9d28 + 06f401a commit 98e768d
Show file tree
Hide file tree
Showing 32 changed files with 1,028 additions and 148 deletions.
14 changes: 14 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# The ApiEndpoint of the deployed backend
VITE_API_URL=
# The slack bot token
VITE_SLACK_BOT_TOKEN=
# The slack signing secret
VITE_SLACK_SIGNING_SECRET=
# Your slack user id
VITE_SLACK_USER_ID=
# The slack bots user id
VITE_SLACK_BOT_USER_ID=
# The slack channel id used for testing
VITE_CORE_SLACK_CHANNEL_ID=
# The slack dm id between the bot and you
VITE_SLACK_DM_ID=
74 changes: 69 additions & 5 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
name: PR checks

on: pull_request
on:
pull_request:
types: [opened, synchronize, closed]
branches: [main]

jobs:
lint-build-and-test:
if: github.event.action == 'opened' || github.event.action == 'synchronize'
runs-on: ubuntu-latest
steps:
- name: Inject slug variables
uses: rlespinasse/github-slug-action@v4

- run: echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" >> $GITHUB_ENV

- name: Checkout repository
uses: actions/checkout@v3

Expand Down Expand Up @@ -49,10 +58,65 @@ jobs:
run: pnpm format:check

- name: Build
run: pnpm build --stage pr

- name: Test
run: pnpm test:ci
run: pnpm build --stage ${{ env.STAGE }}

- name: SIB
run: pnpm sib --pipeline

- name: Run unit tests
run: pnpm test:unit

- name: Set secrets
run: |
npx sst secrets set SLACK_LOG_LEVEL "${{ secrets.SLACK_LOG_LEVEL_TEST }}" --stage ${{ env.STAGE }}
npx sst secrets set SLACK_BOT_TOKEN "${{ secrets.SLACK_BOT_TOKEN_TEST }}" --stage ${{ env.STAGE }}
npx sst secrets set SLACK_SIGNING_SECRET "${{ secrets.SLACK_SIGNING_SECRET_TEST }}" --stage ${{ env.STAGE }}
npx sst secrets set CORE_SLACK_CHANNEL_ID "${{ secrets.CORE_SLACK_CHANNEL_ID_TEST }}" --stage ${{ env.STAGE }}
npx sst secrets set RANDOM_SLACK_CHANNEL_ID "${{ secrets.RANDOM_SLACK_CHANNEL_ID_TEST }}" --stage ${{ env.STAGE }}
- name: Deploy stack
run: pnpm run deploy --stage ${{ env.STAGE }}

- name: Extract api endpoint
id: sst-output
run: |
URL=$(jq -r '.["${{env.STAGE}}-birthday-slack-bot-MyStack"].ApiEndpoint' .sst/outputs.json)
echo "apiEndpoint=$URL" >> "$GITHUB_OUTPUT"
- name: Run integration tests
run: pnpm test:integration
env:
VITE_API_URL: ${{ steps.sst-output.outputs.apiEndpoint }}
VITE_SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN_TEST }}
VITE_SLACK_SIGNING_SECRET: ${{ secrets.SLACK_SIGNING_SECRET_TEST }}
VITE_SLACK_USER_ID: ${{ secrets.SLACK_USER_ID_TEST }}
VITE_SLACK_BOT_USER_ID: ${{ secrets.SLACK_BOT_USER_ID_TEST }}
VITE_CORE_SLACK_CHANNEL_ID: ${{ secrets.CORE_SLACK_CHANNEL_ID_TEST }}
VITE_SLACK_DM_ID: ${{ secrets.SLACK_DM_ID_TEST }}

destroy:
if: github.event.action == 'closed'
runs-on: ubuntu-latest

steps:
- name: Inject slug variables
uses: rlespinasse/github-slug-action@v4

- run: echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" >> $GITHUB_ENV

- name: Checkout repository
uses: actions/checkout@v3

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_STAGING }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_STAGING }}
aws-region: eu-central-1

- uses: actions/setup-node@v3
with:
node-version: "18"

- name: Delete stack
run: npx sst remove --stage ${{ env.STAGE }}
2 changes: 1 addition & 1 deletion .github/workflows/staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ jobs:
npx sst secrets set RANDOM_SLACK_CHANNEL_ID "${{ secrets.RANDOM_SLACK_CHANNEL_ID_STAGING }}" --stage "${{ env.stage }}"
- name: Deploy stack
run: pnpm deploy --stage "${{ env.stage }}"
run: pnpm run deploy --stage "${{ env.stage }}"
3 changes: 1 addition & 2 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm lint-staged
pnpm build && npx sib --pipeline
pnpm lint-staged
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Birthday slack bot

## Description

Helps teams to find the best birthday gift for their colleagues.

## Get started

### Create a New Slack App

1. Create a new Slack app on the [Slack App Dashboard](https://api.slack.com/apps). (From scratch)
2. Navigate to `Basic Information` and make a copy of your `Signing Secret`.
3. Navigate to `OAuth & Permissions` sub-page -> install app to workspace -> save your `Bot User OAuth Token`.
4. Then scroll below to the `Scopes` section and add these Bot Token Scopes:

- channels:read
- chat:write
- groups:read
- im:write
- mpim:read
- users:read
- channels:history
- groups:history
- im:history
- mpim:history

5. Open the `Event Subscriptions` sub-page -> enable events. (We will add the url later.)
6. Scroll below `Subscribe to bot events` and add these scopes:

- member_joined_channel
- member_left_channel

### The bot works with two channels:

- The core channel is the single source of truth regarding members who are part of the team.
- The random channel is where the bot will post the gift wish teaser messsages.

You can use the same channel for both it is up to you.

Make sure you have the channel id(s) and add the bot to both channel(s).

### SST Setup

1. Ensure you have an AWS IAM user.
2. Set secrets:

```bash
npx sst secrets set SLACK_LOG_LEVEL debug
npx sst secrets set SLACK_BOT_TOKEN <your-bot-token>
npx sst secrets set SLACK_SIGNING_SECRET <your-signing-secret>
npx sst secrets set CORE_SLACK_CHANNEL_ID <your-test-channel>
npx sst secrets set RANDOM_SLACK_CHANNEL_ID <your-test-channel>
```

3. Install dependencies: `pnpm i`
4. Run sst: `pnpm dev`

### Add webhook url to Slack

1. Find the ApiEndpoint url of your deployed app in the console output.
2. Open the `Event Subscriptions` sub-page.
3. Add the url: `<ApiEndpoint>/api/slack/callback` to the `Request URL` field.
4. Slack sends a challenge request to the url to verify the endpoint. Make sure you have the app running locally for it to succeed.

## Run tests

Copy the `.env` file to a `.env.local` file and add the secrets.

```bash
pnpm test
```
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@
"format": "prettier --write \"**/*.ts\"",
"format:check": "prettier --check \"**/*.ts\"",
"prepare": "husky install",
"test:ci": "vitest run --passWithNoTests"
"test": "vitest watch",
"test:unit": "vitest run unit",
"test:integration": "vitest run integration --threads false --single-thread"
},
"dependencies": {
"sst": "^2.28.1"
},
"devDependencies": {
"@aws-sdk/client-eventbridge": "^3.429.0",
"@slack/bolt": "^3.14.0",
"@theapexlab/serverless-icebreaker": "^1.1.3",
"@tsconfig/node18": "^18.2.2",
"@types/aws-lambda": "^8.10.124",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"aws-cdk-lib": "2.95.1",
Expand All @@ -37,6 +42,7 @@
"lint-staged": "^14.0.1",
"prettier": "^3.0.3",
"typescript": "^5.2.2",
"vite": "^4.5.0",
"vitest": "^0.34.6"
}
}
4 changes: 4 additions & 0 deletions packages/core/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ const Events = z.object({
memberJoinedChannel: z.object({
channel: z.string(),
user: z.string(),
eventId: z.string(),
}),
askBirthday: z.object({
user: z.string(),
eventId: z.string(),
}),
botJoined: z.object({
channel: z.string(),
eventId: z.string(),
}),
memberLeftChannel: z.object({
user: z.string(),
eventId: z.string(),
}),
});

Expand Down
2 changes: 1 addition & 1 deletion packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/types/*": ["types/*"],
"@/types/*": ["types/*"]
}
}
}
3 changes: 3 additions & 0 deletions packages/core/types/SlackEventRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type SlackEvent = z.infer<typeof SlackEventSchema>;
export const SlackEventRequestSchema = z.object({
event: SlackEventSchema,
type: z.literal("event_callback"),
event_id: z.string(),
});

export type SlackEventRequest = z.infer<typeof SlackEventRequestSchema>;
Expand All @@ -29,3 +30,5 @@ export const SlackCallbackRequestSchema = z.union([
SlackEventRequestSchema,
SlackChallengeRequestSchema,
]);

export type SlackCallbackRequest = z.infer<typeof SlackCallbackRequestSchema>;
74 changes: 42 additions & 32 deletions packages/functions/events/askBirthday.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,53 @@ import { createSlackApp } from "@/services/slack/createSlackApp";
import { getUserInfo } from "@/services/slack/getUserInfo";
import { handleEvent } from "@/utils/eventBridge/handleEvent";

export const handler = handleEvent("askBirthday", async ({ user }) => {
const userInfo = await getUserInfo(user);
export const handler = handleEvent("askBirthday", async ({ user, eventId }) => {
try {
const userInfo = await getUserInfo(user);

if (!userInfo.user || userInfo.user.is_bot || userInfo.user.deleted) {
return;
}
if (!userInfo.user || userInfo.user.is_bot) {
return;
}

const app = createSlackApp();
const app = createSlackApp();

await app.client.chat.postMessage({
channel: user,
text: "Please share your birthday with us! :birthday:",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `Hey ${userInfo.user?.profile?.first_name}! :wave:`,
},
},
{
type: "section",
text: {
type: "mrkdwn",
text: "Please share your birthday with us! :birthday:",
await app.client.chat.postMessage({
channel: user,
text: "Please share your birthday with us! :birthday:",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `Hey ${userInfo.user?.profile?.first_name}! :wave:`,
},
},
accessory: {
type: "datepicker",
initial_date: "1995-01-01",
placeholder: {
type: "plain_text",
text: "Select a date",
emoji: true,
{
type: "section",
text: {
type: "mrkdwn",
text: "Please share your birthday with us! :birthday:",
},
action_id: "birthday",
accessory: {
type: "datepicker",
initial_date: "1995-01-01",
placeholder: {
type: "plain_text",
text: "Select a date",
emoji: true,
},
action_id: "birthday",
},
},
],
metadata: {
event_type: "askBirthday",
event_payload: {
originalEventId: eventId,
},
},
],
});
});
} catch (error) {
console.error("Error processing askBirthday event: ", error);
}
});
28 changes: 17 additions & 11 deletions packages/functions/events/botJoined.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ import { getChannelMembers } from "@/services/slack/getChannelMembers";
import { handleEvent } from "@/utils/eventBridge/handleEvent";
import { publishEvent } from "@/utils/eventBridge/publishEvent";

export const handler = handleEvent("botJoined", async ({ channel }) => {
try {
const users = await getChannelMembers(channel);
export const handler = handleEvent(
"botJoined",
async ({ channel, eventId }) => {
try {
const users = await getChannelMembers(channel);

for (const user of users) {
publishEvent("askBirthday", {
user,
});
await Promise.all(
users.map((user) =>
publishEvent("askBirthday", {
user,
eventId,
}),
),
);
} catch (error) {
console.error("Error processing botJoined event: ", error);
}
} catch (error) {
console.error("Error processing botJoined event: ", error as string);
}
});
},
);
Loading

0 comments on commit 98e768d

Please sign in to comment.