-
Notifications
You must be signed in to change notification settings - Fork 421
docs: add "random fact generator" custom step workshop to tutorials #2694
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
6ddf1d9
193f8ba
f8db0da
605a301
5c1afeb
679d813
6b961f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,297 @@ | ||||||
| --- | ||||||
| title: Build a random fact generator | ||||||
| --- | ||||||
|
|
||||||
| In this tutorial, we’ll create a workflow that fetches data from a third-party API and sends a formatted message to a Slack channel with the results. We’ll use the [random useless facts API](https://uselessfacts.jsph.pl/) to post a daily fact to a Slack channel. You’ll also learn how to use the [Bolt for JavaScript](/tools/bolt-js) framework to add custom steps that can be used in Workflow Builder, the no-code workflow tool in Slack. | ||||||
|
|
||||||
|  | ||||||
|
|
||||||
| ## Create your new Slack app | ||||||
|
|
||||||
| All apps built for Slack have an [app manifest](/app-manifests). This is the configuration for the app, such as the name, settings, and required permissions. The app manifest also describes any functions (custom steps) your app will make available to your Slack workspace. We'll create an app from the manifest shown below. | ||||||
|
|
||||||
| First, log in to your Slack workspace or [join the Developer Program](https://api.slack.com/developer-program/join) to get a free enterprise sandbox. [Create a new app](https://api.slack.com/apps/new), then, choose **From a manifest**. Follow the prompts, copying and pasting the manifest contents below in the JSON tab, replacing the placeholder text that is there. | ||||||
|
|
||||||
| ```json | ||||||
| { | ||||||
| "display_information": { | ||||||
| "name": "Useless Fact App" | ||||||
| }, | ||||||
| "features": { | ||||||
| "app_home": { | ||||||
| "home_tab_enabled": true, | ||||||
| "messages_tab_enabled": true, | ||||||
| "messages_tab_read_only_enabled": true | ||||||
| }, | ||||||
| "bot_user": { | ||||||
| "display_name": "Useless Fact App", | ||||||
| "always_online": false | ||||||
| } | ||||||
| }, | ||||||
| "oauth_config": { | ||||||
| "scopes": { | ||||||
| "bot": [ | ||||||
| "chat:write", | ||||||
| "app_mentions:read", | ||||||
| "workflow.steps:execute" | ||||||
| ] | ||||||
| } | ||||||
| }, | ||||||
| "settings": { | ||||||
| "event_subscriptions": { | ||||||
| "user_events": [ | ||||||
| "message.app_home" | ||||||
| ], | ||||||
| "bot_events": [ | ||||||
| "app_mention", | ||||||
| "workflow_step_execute", | ||||||
| "function_executed" | ||||||
| ] | ||||||
| }, | ||||||
| "interactivity": { | ||||||
| "is_enabled": true | ||||||
| }, | ||||||
| "org_deploy_enabled": true, | ||||||
| "socket_mode_enabled": true, | ||||||
| "token_rotation_enabled": false, | ||||||
| "hermes_app_type": "remote", | ||||||
| "function_runtime": "remote" | ||||||
| }, | ||||||
| "functions": { | ||||||
| "useless_fact_step": { | ||||||
| "title": "Useless Fact Custom Step", | ||||||
| "description": "Runs useless fact step", | ||||||
| "input_parameters": {}, | ||||||
| "output_parameters": { | ||||||
| "message": { | ||||||
| "type": "string", | ||||||
| "title": "Fact", | ||||||
| "description": "A random useless fact", | ||||||
| "is_required": true, | ||||||
| "name": "message" | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Note the app manifest includes a function named `useless_fact_step` and declares the function returns one value named “Fact” as its output. We'll get to this later. | ||||||
|
|
||||||
| ### Save tokens | ||||||
|
|
||||||
| Once your app has been created, scroll down to **App-Level Tokens** on the **Basic Information** page and create a token that requests the [`connections:write`](/reference/scopes/connections.write) scope. This token will allow you to use [Socket Mode](/apis/events-api/using-socket-mode), which is a secure way to develop on Slack through the use of WebSockets. Save the value of your app token and store it in a safe place. Also, save the **Signing Secret** from the **App Credentials** section of this page. We’ll use these later. | ||||||
|
|
||||||
| ### Install app in workspace | ||||||
|
|
||||||
| Still in the app settings, navigate to the **Install App** page in the left sidebar. Install your app. When you press **Allow**, this means you’re agreeing to install your app with the permissions that it’s requesting. Copy the bot token that you receive and store it in a safe place for subsequent steps. | ||||||
|
|
||||||
| Now that you’ve completed the app setup, it’s time to write some code! | ||||||
|
|
||||||
| ### Install Node and set up the project | ||||||
|
|
||||||
| Open your terminal or command prompt. First, check to see that you have Node.js installed and it is a recent long-term support (LTS) version by typing the following command. | ||||||
|
|
||||||
| ```sh | ||||||
| node -v | ||||||
| ``` | ||||||
|
|
||||||
| If you get an error or if it’s an older version than what’s available for download on the [Node.js website](https://nodejs.org/), take a moment to install the latest version. | ||||||
|
|
||||||
| Next, create a new directory for the project named `useless-fact-app` and change to the new directory. | ||||||
|
|
||||||
| ```sh | ||||||
| mkdir useless-fact-app | ||||||
| cd useless-fact-app | ||||||
| ``` | ||||||
|
|
||||||
| For Windows, the command looks like this: | ||||||
|
|
||||||
| ```powershell | ||||||
| md useless-fact-app | ||||||
| ``` | ||||||
|
|
||||||
| Initialize a new Node.js project using the following command; answer each question with defaults. | ||||||
|
|
||||||
| ```sh | ||||||
| npm init | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📎 thought: In a follow up I'm wondering if we can start from the blank template to remove configuration to the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good callout. I'll create a follow-up task to rework this using the blank template! |
||||||
| ``` | ||||||
|
|
||||||
| Our project is going to use two packages: the Slack [Bolt for JavaScript](/tools/bolt-js/) framework and [Axios](https://www.npmjs.com/package/axios), a popular HTTP client for making API calls. Install these dependencies with the following command. | ||||||
|
|
||||||
| ```sh | ||||||
| npm install @slack/bolt axios | ||||||
| ``` | ||||||
zimeg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| Open the project in VS Code or your favorite code editor, then create a new file in the project folder named `.env`. Open the file and paste the following, replacing the placeholders with the values you saved earlier. | ||||||
|
|
||||||
| ```sh | ||||||
| SLACK_SIGNING_SECRET={your-app-signing-secret} | ||||||
| SLACK_BOT_TOKEN={your-bot-token-xoxb-1234} | ||||||
| SLACK_APP_TOKEN={your-app-token-xapp-1234} | ||||||
| PORT=3000 | ||||||
| ``` | ||||||
zimeg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| Next, edit the `package.json` file with the following settings, then save the file. | ||||||
| * Change `"main"` to: | ||||||
|
|
||||||
| ```json | ||||||
| "main": "app.js", | ||||||
| ``` | ||||||
|
|
||||||
| * Change `"scripts"` to: | ||||||
|
|
||||||
| ```json | ||||||
| "scripts": { | ||||||
| "dev": "node –env-file=.env –watch app.js" | ||||||
zimeg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| * Change `"type"` to `"module"` or add this line if it is not already present: | ||||||
|
|
||||||
| ```json | ||||||
| "type": "module" | ||||||
| ``` | ||||||
|
|
||||||
| ### Add function code | ||||||
|
|
||||||
| Create a new file named `app.js` and add the following code. | ||||||
|
|
||||||
| ```js | ||||||
| import bolt from "@slack/bolt"; | ||||||
| import axios from "axios"; | ||||||
zimeg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| const { App } = bolt; | ||||||
|
|
||||||
| // Initialize the Bolt app | ||||||
| const app = new App({ | ||||||
| token: process.env.SLACK_BOT_TOKEN, | ||||||
| signingSecret: process.env.SLACK_SIGNING_SECRET, | ||||||
zimeg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| appToken: process.env.SLACK_APP_TOKEN, | ||||||
| socketMode: true, | ||||||
| }); | ||||||
|
|
||||||
| // Make an API call to retrieve a random fact | ||||||
| async function getUselessFact() { | ||||||
| const res = await axios.get("https://uselessfacts.jsph.pl/api/v2/facts/random"); | ||||||
| return res.status === 200 ? res.data : null; | ||||||
zimeg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| } | ||||||
|
|
||||||
| // Define the function for Workflow Builder | ||||||
| app.function("useless_fact_step", async ({ complete, fail, logger }) => { | ||||||
| try { | ||||||
| app.logger.info("Running useless fact step..."); | ||||||
|
|
||||||
| // Get the fact using the API | ||||||
| const fact = await getUselessFact(); | ||||||
|
|
||||||
| if (fact && fact.text) { | ||||||
| // Use complete() to send results to Slack | ||||||
| await complete({ | ||||||
| outputs: { | ||||||
| message: fact.text, | ||||||
| }, | ||||||
| }); | ||||||
| } else { | ||||||
| // Use fail() to send an error to Slack | ||||||
| await fail({ error: "There was an error retrieving a random useless fact :cry:" }); | ||||||
| } | ||||||
| } catch (error) { | ||||||
| // Log the error and send an error message back to Slack | ||||||
| logger.error(error); | ||||||
| await fail({ error: `There was an error 😥 : ${error}` }) | ||||||
| } | ||||||
| }); | ||||||
|
|
||||||
| // Start the Bolt App! | ||||||
| async function main() { | ||||||
| await app.start(process.env.PORT || 3000); | ||||||
zimeg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| app.logger.info("⚡️ Bolt app is running!"); | ||||||
| } | ||||||
|
|
||||||
| main(); | ||||||
| ``` | ||||||
|
|
||||||
| The code in the `app.js` file initializes the Bolt app using your tokens and signing secret and enables Socket Mode. Next, it defines the `getUselessFact()` function that retrieves a random fact using Axios. It uses the Bolt framework to create a Slack function named `useless_fact_step`. This is the same function we registered in the app manifest previously; the function's name, inputs, and outputs in the code must match with the what is defined in the app manifest. Finally, a function named `main()` is used to start the Bolt app. | ||||||
|
||||||
| The code in the `app.js` file initializes the Bolt app using your tokens and signing secret and enables Socket Mode. Next, it defines the `getUselessFact()` function that retrieves a random fact using Axios. It uses the Bolt framework to create a Slack function named `useless_fact_step`. This is the same function we registered in the app manifest previously; the function's name, inputs, and outputs in the code must match with the what is defined in the app manifest. Finally, a function named `main()` is used to start the Bolt app. | |
| The code in the `app.js` file initializes the Bolt app using your tokens and signing secret and enables Socket Mode. Next, it defines the `getUselessFact()` function that fetches a random fact. It uses the Bolt framework to create a Slack function named `useless_fact_step`. This is the same function we registered in the app manifest previously; the function's name, inputs, and outputs in the code must match with the what is defined in the app manifest. Finally, a function named `main()` is used to start the Bolt app. |
👁️🗨️ suggestion: We're making "fetch" happen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zimeg I'm dead lol
Uh oh!
There was an error while loading. Please reload this page.