├── .github └── workflows │ └── deno.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── assets └── default_new_app_icon.png ├── deno.jsonc ├── functions ├── reverse.ts └── reverse_test.ts ├── import_map.json ├── manifest.ts ├── slack.json ├── triggers └── reverse_string.ts └── workflows └── reverse_string.ts /.github/workflows/deno.yml: -------------------------------------------------------------------------------- 1 | name: Deno 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | deno: 11 | if: github.repository == 'slack-samples/deno-reverse-string' 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Setup repo 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Deno 19 | uses: denoland/setup-deno@v1 20 | with: 21 | deno-version: v1.x 22 | 23 | - name: Verify formatting 24 | run: deno fmt --check 25 | 26 | - name: Run linter 27 | run: deno lint 28 | 29 | - name: Run tests 30 | run: deno task test 31 | 32 | - name: Run type check 33 | run: deno check *.ts && deno check **/*.ts 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | package 3 | .DS_Store 4 | .slack/apps.dev.json 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.suggest.imports.hosts": { 5 | "https://deno.land": false 6 | }, 7 | "[typescript]": { 8 | "editor.formatOnSave": true, 9 | "editor.defaultFormatter": "denoland.vscode-deno" 10 | }, 11 | "editor.tabSize": 2 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Slack Technologies, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### ⚠️ This sample has been archived and is no longer maintained. 2 | 3 | --- 4 | 5 | # Reverse String 6 | 7 | This automation demonstrates reversing a string. 8 | 9 | **Guide Outline**: 10 | 11 | - [Setup](#setup) 12 | - [Install the Slack CLI](#install-the-slack-cli) 13 | - [Clone the Template](#clone-the-template) 14 | - [Running Your Project Locally](#running-your-project-locally) 15 | - [Creating Triggers](#creating-triggers) 16 | - [Datastores](#datastores) 17 | - [Testing](#testing) 18 | - [Deploying Your App](#deploying-your-app) 19 | - [Viewing Activity Logs](#viewing-activity-logs) 20 | - [Project Structure](#project-structure) 21 | - [Resources](#resources) 22 | 23 | --- 24 | 25 | ## Setup 26 | 27 | Before getting started, first make sure you have a development workspace where 28 | you have permission to install apps. **Please note that the features in this 29 | project require that the workspace be part of 30 | [a Slack paid plan](https://slack.com/pricing).** 31 | 32 | ### Install the Slack CLI 33 | 34 | To use this template, you need to install and configure the Slack CLI. 35 | Step-by-step instructions can be found in our 36 | [Quickstart Guide](https://api.slack.com/automation/quickstart). 37 | 38 | ### Clone the Sample 39 | 40 | Start by cloning this repository: 41 | 42 | ```zsh 43 | # Clone this project onto your machine 44 | $ slack create reverse-string-app -t slack-samples/deno-reverse-string 45 | 46 | # Change into the project directory 47 | $ cd reverse-string-app 48 | ``` 49 | 50 | ## Running Your Project Locally 51 | 52 | While building your app, you can see your changes appear in your workspace in 53 | real-time with `slack run`. You'll know an app is the development version if the 54 | name has the string `(local)` appended. 55 | 56 | ```zsh 57 | # Run app locally 58 | $ slack run 59 | 60 | Connected, awaiting events 61 | ``` 62 | 63 | To stop running locally, press ` + C` to end the process. 64 | 65 | ## Creating Triggers 66 | 67 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 68 | to run. These triggers can be invoked by a user, or automatically as a response 69 | to an event within Slack. 70 | 71 | When you `run` or `deploy` your project for the first time, the CLI will prompt 72 | you to create a trigger if one is found in the `triggers/` directory. For any 73 | subsequent triggers added to the application, each must be 74 | [manually added using the `trigger create` command](#manual-trigger-creation). 75 | 76 | When creating triggers, you must select the workspace and environment that you'd 77 | like to create the trigger in. Each workspace can have a local development 78 | version (denoted by `(local)`), as well as a deployed version. _Triggers created 79 | in a local environment will only be available to use when running the 80 | application locally._ 81 | 82 | ### Link Triggers 83 | 84 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 85 | trigger that generates a **Shortcut URL** which, when posted in a channel or 86 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 87 | associated workflow. 88 | 89 | Link triggers are _unique to each installed version of your app_. This means 90 | that Shortcut URLs will be different across each workspace, as well as between 91 | [locally run](#running-your-project-locally) and 92 | [deployed apps](#deploying-your-app). 93 | 94 | With link triggers, after selecting a workspace and environment, the output 95 | provided will include a Shortcut URL. Copy and paste this URL into a channel as 96 | a message, or add it as a bookmark in a channel of the workspace you selected. 97 | Interacting with this link will run the associated workflow. 98 | 99 | **Note: triggers won't run the workflow unless the app is either running locally 100 | or deployed!** 101 | 102 | ### Manual Trigger Creation 103 | 104 | To manually create a trigger, use the following command: 105 | 106 | ```zsh 107 | $ slack trigger create --trigger-def triggers/reverse_string.ts 108 | ``` 109 | 110 | ## Datastores 111 | 112 | For storing data related to your app, datastores offer secure storage on Slack 113 | infrastructure. The use of a datastore requires the 114 | `datastore:write`/`datastore:read` scopes to be present in your manifest. 115 | 116 | ## Testing 117 | 118 | For an example of how to test a function, see `functions/reverse_test.ts`. Test 119 | filenames should be suffixed with `_test`. 120 | 121 | Run all tests with `deno test`: 122 | 123 | ```zsh 124 | $ deno test 125 | ``` 126 | 127 | ## Deploying Your App 128 | 129 | Once development is complete, deploy the app to Slack infrastructure using 130 | `slack deploy`: 131 | 132 | ```zsh 133 | $ slack deploy 134 | ``` 135 | 136 | When deploying for the first time, you'll be prompted to 137 | [create a new link trigger](#creating-triggers) for the deployed version of your 138 | app. When that trigger is invoked, the workflow should run just as it did when 139 | developing locally (but without requiring your server to be running). 140 | 141 | ## Viewing Activity Logs 142 | 143 | Activity logs of your application can be viewed live and as they occur with the 144 | following command: 145 | 146 | ```zsh 147 | $ slack activity --tail 148 | ``` 149 | 150 | ## Project Structure 151 | 152 | ### `.slack/` 153 | 154 | Contains `apps.dev.json` and `apps.json`, which include installation details for 155 | development and deployed apps. 156 | 157 | ### `datastores/` 158 | 159 | [Datastores](https://api.slack.com/automation/datastores) securely store data 160 | for your application on Slack infrastructure. Required scopes to use datastores 161 | include `datastore:write` and `datastore:read`. 162 | 163 | ### `functions/` 164 | 165 | [Functions](https://api.slack.com/automation/functions) are reusable building 166 | blocks of automation that accept inputs, perform calculations, and provide 167 | outputs. Functions can be used independently or as steps in workflows. 168 | 169 | ### `triggers/` 170 | 171 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 172 | are run. A trigger file describes the scenario in which a workflow should be 173 | run, such as a user pressing a button or when a specific event occurs. 174 | 175 | ### `workflows/` 176 | 177 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps 178 | (functions) that are executed in order. 179 | 180 | Workflows can be configured to run without user input or they can collect input 181 | by beginning with a [form](https://api.slack.com/automation/forms) before 182 | continuing to the next step. 183 | 184 | ### `manifest.ts` 185 | 186 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 187 | configuration. This file defines attributes like app name and description. 188 | 189 | ### `slack.json` 190 | 191 | Used by the CLI to interact with the project's SDK dependencies. It contains 192 | script hooks that are executed by the CLI and implemented by the SDK. 193 | 194 | ## Resources 195 | 196 | To learn more about developing automations on Slack, visit the following: 197 | 198 | - [Automation Overview](https://api.slack.com/automation) 199 | - [CLI Quick Reference](https://api.slack.com/automation/cli/quick-reference) 200 | - [Samples and Templates](https://api.slack.com/automation/samples) 201 | -------------------------------------------------------------------------------- /assets/default_new_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/deno-reverse-string/bafcabd9ba9416c2c40f1082ad7264929a7fefe4/assets/default_new_app_icon.png -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", 3 | "fmt": { 4 | "include": [ 5 | "README.md", 6 | "datastores", 7 | "external_auth", 8 | "functions", 9 | "manifest.ts", 10 | "triggers", 11 | "types", 12 | "views", 13 | "workflows" 14 | ] 15 | }, 16 | "importMap": "import_map.json", 17 | "lint": { 18 | "include": [ 19 | "datastores", 20 | "external_auth", 21 | "functions", 22 | "manifest.ts", 23 | "triggers", 24 | "types", 25 | "views", 26 | "workflows" 27 | ] 28 | }, 29 | "lock": false, 30 | "tasks": { 31 | "test": "deno fmt --check && deno lint && deno test --allow-read --allow-none" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /functions/reverse.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * Functions are reusable building blocks of automation that accept 5 | * inputs, perform calculations, and provide outputs. Functions can 6 | * be used independently or as steps in workflows. 7 | * https://api.slack.com/automation/functions/custom 8 | */ 9 | export const ReverseFunctionDefinition = DefineFunction({ 10 | callback_id: "reverse", 11 | title: "Reverse", 12 | description: "Takes a string and reverses it", 13 | source_file: "functions/reverse.ts", 14 | input_parameters: { 15 | properties: { 16 | stringToReverse: { 17 | type: Schema.types.string, 18 | description: "The string to reverse", 19 | }, 20 | }, 21 | required: ["stringToReverse"], 22 | }, 23 | output_parameters: { 24 | properties: { 25 | reversedString: { 26 | type: Schema.types.string, 27 | description: "The string in reverse", 28 | }, 29 | }, 30 | required: ["reversedString"], 31 | }, 32 | }); 33 | 34 | export default SlackFunction( 35 | ReverseFunctionDefinition, 36 | ({ inputs }) => { 37 | const reversedString = inputs.stringToReverse.split("").reverse().join(""); 38 | return { 39 | outputs: { reversedString }, 40 | }; 41 | }, 42 | ); 43 | -------------------------------------------------------------------------------- /functions/reverse_test.ts: -------------------------------------------------------------------------------- 1 | import { SlackFunctionTester } from "deno-slack-sdk/mod.ts"; 2 | import { assertEquals } from "https://deno.land/std@0.153.0/testing/asserts.ts"; 3 | import ReverseFunction from "./reverse.ts"; 4 | 5 | const { createContext } = SlackFunctionTester("reverse"); 6 | 7 | Deno.test("Reverse string function test", async () => { 8 | const inputs = { stringToReverse: "foo" }; 9 | const { outputs } = await ReverseFunction(createContext({ inputs })); 10 | assertEquals(outputs?.reversedString, "oof"); 11 | }); 12 | -------------------------------------------------------------------------------- /import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "deno-slack-sdk/": "https://deno.land/x/deno_slack_sdk@2.3.0/", 4 | "deno-slack-api/": "https://deno.land/x/deno_slack_api@2.1.2/" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /manifest.ts: -------------------------------------------------------------------------------- 1 | import { Manifest } from "deno-slack-sdk/mod.ts"; 2 | import ReverseWorkflow from "./workflows/reverse_string.ts"; 3 | 4 | /** 5 | * The app manifest contains the app's configuration. This 6 | * file defines attributes like app name and description. 7 | * https://api.slack.com/automation/manifest 8 | */ 9 | export default Manifest({ 10 | name: "deno-reverse-string", 11 | description: "Post the reversed version of a string to a selected channel", 12 | icon: "assets/default_new_app_icon.png", 13 | workflows: [ReverseWorkflow], 14 | outgoingDomains: [], 15 | botScopes: ["commands", "chat:write", "chat:write.public"], 16 | }); 17 | -------------------------------------------------------------------------------- /slack.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "get-hooks": "deno run -q --allow-read --allow-net https://deno.land/x/deno_slack_hooks@1.1.0/mod.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /triggers/reverse_string.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import ReverseWorkflow from "../workflows/reverse_string.ts"; 4 | 5 | /** 6 | * Triggers determine when workflows are executed. A trigger 7 | * file describes a scenario in which a workflow should be run, 8 | * such as a user pressing a button or when a specific event occurs. 9 | * https://api.slack.com/automation/triggers 10 | */ 11 | const trigger: Trigger = { 12 | type: TriggerTypes.Shortcut, 13 | name: "Reverse a string", 14 | description: "Starts the workflow to reverse a string", 15 | workflow: `#/workflows/${ReverseWorkflow.definition.callback_id}`, 16 | inputs: { 17 | interactivity: { 18 | value: TriggerContextData.Shortcut.interactivity, 19 | }, 20 | channel: { 21 | value: TriggerContextData.Shortcut.channel_id, 22 | }, 23 | }, 24 | }; 25 | 26 | export default trigger; 27 | -------------------------------------------------------------------------------- /workflows/reverse_string.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { ReverseFunctionDefinition } from "../functions/reverse.ts"; 3 | 4 | /** 5 | * A workflow is a set of steps that are executed in order. 6 | * Each step in a workflow is a function. 7 | * https://api.slack.com/automation/workflows 8 | */ 9 | const ReverseWorkflow = DefineWorkflow({ 10 | callback_id: "reverse_string", 11 | title: "Test Reverse Function", 12 | description: "test the reverse function", 13 | input_parameters: { 14 | properties: { 15 | interactivity: { 16 | type: Schema.slack.types.interactivity, 17 | }, 18 | channel: { 19 | type: Schema.slack.types.channel_id, 20 | }, 21 | }, 22 | required: ["interactivity"], 23 | }, 24 | }); 25 | 26 | const formData = ReverseWorkflow.addStep(Schema.slack.functions.OpenForm, { 27 | title: "Reverse string form", 28 | submit_label: "Submit form", 29 | description: "Submit a string to reverse", 30 | interactivity: ReverseWorkflow.inputs.interactivity, 31 | fields: { 32 | required: ["channel", "stringInput"], 33 | elements: [ 34 | { 35 | name: "stringInput", 36 | title: "String input", 37 | type: Schema.types.string, 38 | }, 39 | { 40 | name: "channel", 41 | title: "Post in", 42 | type: Schema.slack.types.channel_id, 43 | default: ReverseWorkflow.inputs.channel, 44 | }, 45 | ], 46 | }, 47 | }); 48 | 49 | const reverseStep = ReverseWorkflow.addStep(ReverseFunctionDefinition, { 50 | stringToReverse: formData.outputs.fields.stringInput, 51 | }); 52 | 53 | ReverseWorkflow.addStep(Schema.slack.functions.SendMessage, { 54 | channel_id: formData.outputs.fields.channel, 55 | message: reverseStep.outputs.reversedString, 56 | }); 57 | 58 | export default ReverseWorkflow; 59 | --------------------------------------------------------------------------------