From d3888a09dd709df96292889f411a8170b1b0ddd7 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 28 Jul 2022 17:04:15 -0400 Subject: [PATCH 01/25] Added function capabilities Added basic function capabilities --- .gitignore | 4 ++- app.py | 1 + listeners/__init__.py | 4 ++- listeners/functions/__init__.py | 6 +++++ listeners/functions/reverse_string.py | 16 ++++++++++++ manifest.json | 35 ++++++++++++++++++++++++--- requirements.txt | 3 ++- slack.json | 5 ++++ 8 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 listeners/functions/__init__.py create mode 100644 listeners/functions/reverse_string.py create mode 100644 slack.json diff --git a/.gitignore b/.gitignore index 954bfb3..34c45d5 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ tmp.txt .DS_Store logs/ *.db -.pytype/ \ No newline at end of file +.pytype/ +scripts +.slack \ No newline at end of file diff --git a/app.py b/app.py index 854f80e..538f77a 100644 --- a/app.py +++ b/app.py @@ -6,6 +6,7 @@ from listeners import register_listeners + # Initialization app = App(token=os.environ.get("SLACK_BOT_TOKEN")) logging.basicConfig(level=logging.DEBUG) diff --git a/listeners/__init__.py b/listeners/__init__.py index f95a68d..a9916a1 100644 --- a/listeners/__init__.py +++ b/listeners/__init__.py @@ -1,9 +1,10 @@ -from listeners import actions +from listeners import actions, functions from listeners import commands from listeners import events from listeners import messages from listeners import shortcuts from listeners import views +from listeners import functions def register_listeners(app): @@ -13,3 +14,4 @@ def register_listeners(app): messages.register(app) shortcuts.register(app) views.register(app) + functions.register(app) diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py new file mode 100644 index 0000000..57a3394 --- /dev/null +++ b/listeners/functions/__init__.py @@ -0,0 +1,6 @@ +from slack_bolt import App +from .reverse_string import reverse_string + + +def register(app: App): + app.function("reverse")(reverse_string) diff --git a/listeners/functions/reverse_string.py b/listeners/functions/reverse_string.py new file mode 100644 index 0000000..6ba9afa --- /dev/null +++ b/listeners/functions/reverse_string.py @@ -0,0 +1,16 @@ +from slack_bolt import Success +from slack_bolt import Error +from logging import Logger + + +def reverse_string(event, success: Success, err: Error, logger: Logger): + try: + logger.info(str(event)) + string_to_reverse = event["inputs"]["stringToReverse"] + success({ + "reverseString": string_to_reverse[::-1] + }) + except Exception as e: + logger.error(e) + err("Cannot reverse string") + raise e diff --git a/manifest.json b/manifest.json index f973e42..0192ec4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "_metadata": { - "major_version": 1, - "minor_version": 1 + "major_version": 2, + "minor_version": 2 }, "display_information": { "name": "Bolt Template App" @@ -54,5 +54,34 @@ "org_deploy_enabled": false, "socket_mode_enabled": true, "token_rotation_enabled": false - } + }, + "functions": { + "reverse": { + "title": "Reverse", + "description": "Takes a string and reverses it", + "input_parameters": { + "properties": { + "stringToReverse": { + "type": "string", + "description": "The string to reverse" + } + }, + "required": [ + "stringToReverse" + ] + }, + "output_parameters": { + "properties": { + "reverseString": { + "type": "string", + "description": "The string in reverse" + } + }, + "required": [ + "reverseString" + ] + } + } + }, + "outgoing_domains": [] } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4ad1d05..4fca96b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -slack-bolt +# slack-bolt +/Users/wbergamin/Documents/slack/tools/forks/bolt-python/dist/slack_bolt-1.14.3-py2.py3-none-any.whl pytest flake8==5.0.4 black==22.8.0 diff --git a/slack.json b/slack.json new file mode 100644 index 0000000..eeaa5ab --- /dev/null +++ b/slack.json @@ -0,0 +1,5 @@ +{ + "hooks": { + "get-hooks": "bolt-get-hooks" + } +} \ No newline at end of file From a4bb1c11cd25e5e13f63429e61bf6675c1a4815d Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 28 Jul 2022 20:43:07 -0400 Subject: [PATCH 02/25] Added more logs and reformatted --- app.py | 6 ++++++ listeners/functions/reverse_string.py | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index 538f77a..d345526 100644 --- a/app.py +++ b/app.py @@ -14,6 +14,12 @@ # Register Listeners register_listeners(app) + +@app.middleware # or app.use(log_request) +def log_request(logger, body, next): + logger.debug(body) + return next() + # Start Bolt app if __name__ == "__main__": SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start() diff --git a/listeners/functions/reverse_string.py b/listeners/functions/reverse_string.py index 6ba9afa..54819a9 100644 --- a/listeners/functions/reverse_string.py +++ b/listeners/functions/reverse_string.py @@ -1,16 +1,15 @@ -from slack_bolt import Success -from slack_bolt import Error +from slack_bolt import CompleteSuccess +from slack_bolt import CompleteError from logging import Logger -def reverse_string(event, success: Success, err: Error, logger: Logger): +def reverse_string(event, complete_success: CompleteSuccess, complete_error: CompleteError, logger: Logger): try: - logger.info(str(event)) string_to_reverse = event["inputs"]["stringToReverse"] - success({ + complete_success({ "reverseString": string_to_reverse[::-1] }) except Exception as e: logger.error(e) - err("Cannot reverse string") + complete_error("Cannot reverse string") raise e From 42503a19485d365705ece45bb9a512030eb5a976 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Fri, 29 Jul 2022 12:14:34 -0400 Subject: [PATCH 03/25] Cleaned up project --- app.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app.py b/app.py index d345526..c057098 100644 --- a/app.py +++ b/app.py @@ -15,11 +15,6 @@ register_listeners(app) -@app.middleware # or app.use(log_request) -def log_request(logger, body, next): - logger.debug(body) - return next() - # Start Bolt app if __name__ == "__main__": SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start() From dc84f3563b815a0f08820ed7200ed6bb64775bdf Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Fri, 5 Aug 2022 13:00:47 -0400 Subject: [PATCH 04/25] clean up code --- listeners/messages/sample_message.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/listeners/messages/sample_message.py b/listeners/messages/sample_message.py index 29429a3..6c2e288 100644 --- a/listeners/messages/sample_message.py +++ b/listeners/messages/sample_message.py @@ -4,7 +4,7 @@ from slack_sdk import WebClient -def sample_message_callback(context: BoltContext, client: WebClient, say: Say, logger: Logger): +def sample_message_callback(context: BoltContext, say: Say, logger: Logger): try: greeting = context["matches"][0] say(f"{greeting}, how are you?") diff --git a/requirements.txt b/requirements.txt index 4fca96b..b0e0f6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # slack-bolt -/Users/wbergamin/Documents/slack/tools/forks/bolt-python/dist/slack_bolt-1.14.3-py2.py3-none-any.whl +../../forks/bolt-python/dist/slack_bolt-1.14.3-py2.py3-none-any.whl pytest flake8==5.0.4 black==22.8.0 From 9dbddb310867937dbd4cc615f59a59e62ad510bc Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 18 Aug 2022 18:35:46 -0400 Subject: [PATCH 05/25] added trigger file --- trigger.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 trigger.json diff --git a/trigger.json b/trigger.json new file mode 100644 index 0000000..85c13ac --- /dev/null +++ b/trigger.json @@ -0,0 +1,14 @@ +{ + "type": "shortcut", + "name": "Reverse a String", + "description": "Starts the workflow to test reversing a string", + "workflow": "#/workflows/test_reverse", + "inputs": { + "interactivity": { + "value": "{{data.interactivity}}" + }, + "channel": { + "value": "{{data.channel_id}}" + } + } +} \ No newline at end of file From fe38f7462ca472b834a10d8d9ede90f05288888b Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 18 Aug 2022 18:36:36 -0400 Subject: [PATCH 06/25] updated manifest with workflows --- manifest.json | 160 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 47 deletions(-) diff --git a/manifest.json b/manifest.json index 0192ec4..53ce65d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,59 +1,59 @@ { "_metadata": { - "major_version": 2, - "minor_version": 2 + "major_version": 2, + "minor_version": 2 }, "display_information": { - "name": "Bolt Template App" + "name": "Bolt Template App" }, "features": { - "app_home": { - "home_tab_enabled": true, - "messages_tab_enabled": false, - "messages_tab_read_only_enabled": true - }, - "bot_user": { - "display_name": "Bolt Template App", - "always_online": false - }, - "shortcuts": [ - { - "name": "Run sample shortcut", - "type": "global", - "callback_id": "sample_shortcut_id", - "description": "Runs a sample shortcut" - } - ], - "slash_commands": [ - { - "command": "/sample-command", - "description": "Runs a sample command", - "should_escape": false - } - ] + "app_home": { + "home_tab_enabled": true, + "messages_tab_enabled": false, + "messages_tab_read_only_enabled": true + }, + "bot_user": { + "display_name": "Bolt Template App", + "always_online": false + }, + "shortcuts": [ + { + "name": "Run sample shortcut", + "type": "global", + "callback_id": "sample_shortcut_id", + "description": "Runs a sample shortcut" + } + ], + "slash_commands": [ + { + "command": "/sample-command", + "description": "Runs a sample command", + "should_escape": false + } + ] }, "oauth_config": { - "scopes": { - "bot": [ - "channels:history", - "chat:write", - "commands" - ] - } + "scopes": { + "bot": [ + "channels:history", + "chat:write", + "commands" + ] + } }, "settings": { - "event_subscriptions": { - "bot_events": [ - "app_home_opened", - "message.channels" - ] - }, - "interactivity": { - "is_enabled": true - }, - "org_deploy_enabled": false, - "socket_mode_enabled": true, - "token_rotation_enabled": false + "event_subscriptions": { + "bot_events": [ + "app_home_opened", + "message.channels" + ] + }, + "interactivity": { + "is_enabled": true + }, + "org_deploy_enabled": false, + "socket_mode_enabled": true, + "token_rotation_enabled": false }, "functions": { "reverse": { @@ -83,5 +83,71 @@ } } }, + "types": {}, + "workflows": { + "test_reverse": { + "title": "Test Reverse Function", + "description": "test the reverse function", + "input_parameters": { + "properties": { + "interactivity": { + "type": "slack#/types/interactivity" + }, + "channel": { + "type": "slack#/types/channel_id" + } + }, + "required": [ + "interactivity" + ] + }, + "steps": [ + { + "id": "0", + "function_id": "slack#/functions/open_form", + "inputs": { + "title": "Reverse string form", + "submit_label": "Submit form", + "description": "Submit a string to reverse", + "interactivity": "{{inputs.interactivity}}", + "fields": { + "required": [ + "channel", + "stringInput" + ], + "elements": [ + { + "name": "stringInput", + "title": "String input", + "type": "string" + }, + { + "name": "channel", + "title": "Post in", + "type": "slack#/types/channel_id", + "default": "{{inputs.channel}}" + } + ] + } + } + }, + { + "id": "1", + "function_id": "#/functions/reverse", + "inputs": { + "stringToReverse": "{{steps.0.fields.stringInput}}" + } + }, + { + "id": "2", + "function_id": "slack#/functions/send_message", + "inputs": { + "channel_id": "{{steps.0.fields.channel}}", + "message": "{{steps.1.reverseString}}" + } + } + ] + } + }, "outgoing_domains": [] -} \ No newline at end of file +} From af2c18782837d5f1c5c4d725529b8edc036fa452 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 18 Aug 2022 18:38:37 -0400 Subject: [PATCH 07/25] added trigger section in readme --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index dfae2fa..a65b985 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,28 @@ black . Every incoming request is routed to a "listener". Inside this directory, we group each listener based on the Slack Platform feature used, so `/listeners/shortcuts` handles incoming [Shortcuts](https://api.slack.com/interactivity/shortcuts) requests, `/listeners/views` handles [View submissions](https://api.slack.com/reference/interaction-payloads/views#view_submission) and so on. +### triggers +In order to run this project using the slack cli you must first set up triggers in your workspace. + +These triggers are defined in `trigger.json`, run the following command to add them to your workspace +```bash +slack trigger create --trigger-def "./trigger.json" +``` + +### manifest +The `manifest.json` defines the behavior of your application, here are a vew helpful commands +```bash +slack manifest # view the compiled manifest +slack manifest validate # to validate your manifest +``` + +### run application +To start your application with the cli +```bash +slack run +``` + +**NOTE:** you my create your triggers in your workspace before ## App Distribution / OAuth From 72964ab4ae6048b83425a940b639fd63091edb06 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 25 Aug 2022 19:31:40 -0400 Subject: [PATCH 08/25] added function interactivity --- app.py | 5 + listeners/__init__.py | 2 +- listeners/functions/__init__.py | 30 +++++- listeners/functions/actions/__init__.py | 8 ++ listeners/functions/actions/approve_action.py | 32 ++++++ listeners/functions/actions/deny_action.py | 33 +++++++ listeners/functions/request_approval.py | 70 +++++++++++++ manifest.json | 97 ++++++++++++++++++- trigger.json => triggers/test_reverse.json | 0 triggers/time_off_request_wf.json | 12 +++ 10 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 listeners/functions/actions/__init__.py create mode 100644 listeners/functions/actions/approve_action.py create mode 100644 listeners/functions/actions/deny_action.py create mode 100644 listeners/functions/request_approval.py rename trigger.json => triggers/test_reverse.json (100%) create mode 100644 triggers/time_off_request_wf.json diff --git a/app.py b/app.py index c057098..d345526 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,11 @@ register_listeners(app) +@app.middleware # or app.use(log_request) +def log_request(logger, body, next): + logger.debug(body) + return next() + # Start Bolt app if __name__ == "__main__": SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start() diff --git a/listeners/__init__.py b/listeners/__init__.py index a9916a1..22c383d 100644 --- a/listeners/__init__.py +++ b/listeners/__init__.py @@ -1,4 +1,4 @@ -from listeners import actions, functions +from listeners import actions from listeners import commands from listeners import events from listeners import messages diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index 57a3394..95a740d 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -1,6 +1,34 @@ +from logging import Logger +from slack_bolt import CompleteError +from slack_bolt import CompleteSuccess from slack_bolt import App from .reverse_string import reverse_string +from .request_approval import request_approval +from .actions import approve_action, deny_action + +from slack_bolt.function import Function def register(app: App): - app.function("reverse")(reverse_string) + + # @app.function("reverse") + # def reverse_string(event, complete_success: CompleteSuccess, complete_error: CompleteError, logger: Logger): + # try: + # string_to_reverse = event["inputs"]["stringToReverse"] + # complete_success({ + # "reverseString": string_to_reverse[::-1] + # }) + # except Exception as e: + # logger.error(e) + # complete_error("Cannot reverse string") + # raise e + + # @reverse_string.action("hello") + # def hello(): + # pass + + # app.function("reverse")(reverse_string) + + request_approval_function: Function = app.function("review_approval")(request_approval) + request_approval_function.action("approve_action_id")(approve_action) + request_approval_function.action("deny_action_id")(deny_action) diff --git a/listeners/functions/actions/__init__.py b/listeners/functions/actions/__init__.py new file mode 100644 index 0000000..9d9cf7d --- /dev/null +++ b/listeners/functions/actions/__init__.py @@ -0,0 +1,8 @@ +from .approve_action import approve_action +from .deny_action import deny_action + + +__all__ = [ + "approve_action", + "deny_action" +] diff --git a/listeners/functions/actions/approve_action.py b/listeners/functions/actions/approve_action.py new file mode 100644 index 0000000..403fead --- /dev/null +++ b/listeners/functions/actions/approve_action.py @@ -0,0 +1,32 @@ +from datetime import datetime +from logging import Logger + +from slack_bolt import Ack, Say, CompleteError + + +def approve_action(ack: Ack, say: Say, body, complete_error: CompleteError, logger: Logger): + try: + ack() + inputs = body["function_data"]["inputs"] + manager = inputs["manager"] + end_date = datetime.fromtimestamp(inputs["end_date"]*1000.0).strptime("%m/%d/%Y %H:%M") + start_date = datetime.fromtimestamp(inputs["start_date"]*1000.0).strptime("%m/%d/%Y %H:%M") + say( + text=f":white_check_mark: Time-off request for ${start_date} to ${end_date} approved by <@${manager}>", + blocks=[ + { + "type": 'context', + "elements": [ + { + "type": 'mrkdwn', + "text": f":white_check_mark: Time-off request for ${start_date} to ${end_date} approved by <@${manager}>", + }, + ], + } + ] + ) + complete_error("there is no error") + except Exception as e: + logger.error(e) + complete_error("Cannot request approval") + raise e diff --git a/listeners/functions/actions/deny_action.py b/listeners/functions/actions/deny_action.py new file mode 100644 index 0000000..d0ebca3 --- /dev/null +++ b/listeners/functions/actions/deny_action.py @@ -0,0 +1,33 @@ +from datetime import datetime +from logging import Logger + +from slack_bolt import Ack, Say, CompleteError + + +def deny_action(ack: Ack, say: Say, body, complete_error: CompleteError, logger: Logger): + try: + ack() + inputs = body["function_data"]["inputs"] + manager = inputs["manager"] + employee = inputs["employee"] + end_date = datetime.fromtimestamp(inputs["end_date"]*1000.0).strptime("%m/%d/%Y %H:%M") + start_date = datetime.fromtimestamp(inputs["start_date"]*1000.0).strptime("%m/%d/%Y %H:%M") + say( + text=f":x: Time-off request for ${end_date} to ${start_date} denied by <@${manager}>", + blocks=[ + { + "type": 'context', + "elements": [ + { + "type": 'mrkdwn', + "text": f": x: Time-off request for ${start_date} to ${end_date} denied by < @${manager} >", + }, + ], + } + ] + ) + complete_error("there is no error") + except Exception as e: + logger.error(e) + complete_error("Cannot request approval") + raise e diff --git a/listeners/functions/request_approval.py b/listeners/functions/request_approval.py new file mode 100644 index 0000000..c79b961 --- /dev/null +++ b/listeners/functions/request_approval.py @@ -0,0 +1,70 @@ +from slack_sdk import WebClient +from slack_bolt import CompleteError +from logging import Logger + +from datetime import datetime + + +def request_approval(event, client: WebClient, complete_error: CompleteError, logger: Logger): + try: + inputs = event["inputs"] + manager = inputs["manager"] + employee = inputs["employee"] + end_date = datetime.fromtimestamp(inputs["end_date"]).strftime("%m/%d/%Y %H:%M") + start_date = datetime.fromtimestamp(inputs["start_date"]).strftime("%m/%d/%Y %H:%M") + + client.chat_postMessage( + channel=manager, + text='A new time-off request has been submitted.', + blocks=[ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "A new time-off request has been submitted" + } + }, + { + "type": 'section', + "text": { + "type": 'mrkdwn', + "text": f"*From: * < @${employee} >", + }, + }, + { + "type": 'section', + "text": { + "type": 'mrkdwn', + "text": f"*Dates: * ${start_date} to ${end_date}", + }, + }, + { + "type": 'actions', + "block_id": 'approve-deny-buttons', + "elements": [ + { + "type": 'button', + "text": { + "type": 'plain_text', + "text": 'Approve', + }, + "action_id": 'approve_action_id', + "style": 'primary', + }, + { + "type": 'button', + "text": { + "type": 'plain_text', + "text": 'Deny', + }, + "action_id": 'deny_action_id', + "style": 'danger', + } + ] + } + ]) + + except Exception as e: + logger.error(e) + complete_error("Cannot request approval") + raise e diff --git a/manifest.json b/manifest.json index 53ce65d..5b47a02 100644 --- a/manifest.json +++ b/manifest.json @@ -9,7 +9,7 @@ "features": { "app_home": { "home_tab_enabled": true, - "messages_tab_enabled": false, + "messages_tab_enabled": true, "messages_tab_read_only_enabled": true }, "bot_user": { @@ -37,7 +37,8 @@ "bot": [ "channels:history", "chat:write", - "commands" + "commands", + "chat:write.public" ] } }, @@ -81,6 +82,40 @@ "reverseString" ] } + }, + "review_approval": { + "title": "Approval Function", + "description": "Get approval for a request", + "input_parameters": { + "properties": { + "employee": { + "type": "slack#/types/user_id", + "description": "Requester" + }, + "manager": { + "type": "slack#/types/user_id", + "description": "Manager" + }, + "start_date": { + "type": "slack#/types/timestamp", + "description": "Start Date" + }, + "end_date": { + "type": "slack#/types/timestamp", + "description": "End Date" + } + }, + "required": [ + "employee", + "manager", + "start_date", + "end_date" + ] + }, + "output_parameters": { + "properties": {}, + "required": [] + } } }, "types": {}, @@ -147,6 +182,64 @@ } } ] + }, + "time_off_request_wf": { + "title": "Time Off Request Workflow", + "description": "", + "input_parameters": { + "properties": { + "interactivity": { + "type": "slack#/types/interactivity" + } + }, + "required": [] + }, + "steps": [ + { + "id": "0", + "function_id": "slack#/functions/open_form", + "inputs": { + "title": "Request Time Off", + "submit_label": "Request", + "description": "Please describe your request", + "interactivity": "{{inputs.interactivity}}", + "fields": { + "required": [ + "manager", + "start_date", + "end_date" + ], + "elements": [ + { + "name": "manager", + "title": "Manager", + "type": "slack#/types/user_id" + }, + { + "name": "start_date", + "title": "Start Date", + "type": "slack#/types/timestamp" + }, + { + "name": "end_date", + "title": "End Date", + "type": "slack#/types/timestamp" + } + ] + } + } + }, + { + "id": "1", + "function_id": "#/functions/review_approval", + "inputs": { + "employee": "{{inputs.interactivity.interactor.id}}", + "manager": "{{steps.0.fields.manager}}", + "start_date": "{{steps.0.fields.start_date}}", + "end_date": "{{steps.0.fields.end_date}}" + } + } + ] } }, "outgoing_domains": [] diff --git a/trigger.json b/triggers/test_reverse.json similarity index 100% rename from trigger.json rename to triggers/test_reverse.json diff --git a/triggers/time_off_request_wf.json b/triggers/time_off_request_wf.json new file mode 100644 index 0000000..8fdef9e --- /dev/null +++ b/triggers/time_off_request_wf.json @@ -0,0 +1,12 @@ +{ + "type": "shortcut", + "name": "Take Your Time", + "description": "Submit a request to take time off", + "workflow": "#/workflows/time_off_request_wf", + "shortcut": {}, + "inputs": { + "interactivity": { + "value": "{{data.interactivity}}" + } + } +} \ No newline at end of file From 6dda522289f63786a25dcb7ff9805744890e2728 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Mon, 29 Aug 2022 17:24:29 -0400 Subject: [PATCH 09/25] Clean up project --- app.py | 4 +- listeners/functions/actions/approve_action.py | 55 +++++++++++++------ listeners/functions/actions/deny_action.py | 54 ++++++++++++------ listeners/functions/request_approval.py | 5 +- 4 files changed, 81 insertions(+), 37 deletions(-) diff --git a/app.py b/app.py index d345526..9a75139 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,6 @@ import os import logging +from copy import deepcopy from slack_bolt import App from slack_bolt.adapter.socket_mode import SocketModeHandler @@ -16,7 +17,8 @@ @app.middleware # or app.use(log_request) -def log_request(logger, body, next): +def log_request(client, logger, body, next): + logger.info(f"this is my token: {client.token}") logger.debug(body) return next() diff --git a/listeners/functions/actions/approve_action.py b/listeners/functions/actions/approve_action.py index 403fead..beaae19 100644 --- a/listeners/functions/actions/approve_action.py +++ b/listeners/functions/actions/approve_action.py @@ -1,32 +1,53 @@ from datetime import datetime from logging import Logger -from slack_bolt import Ack, Say, CompleteError +from slack_bolt import Ack, CompleteError +from slack_sdk import WebClient -def approve_action(ack: Ack, say: Say, body, complete_error: CompleteError, logger: Logger): +def approve_action(ack: Ack, client: WebClient, body, complete_error: CompleteError, logger: Logger): try: ack() inputs = body["function_data"]["inputs"] manager = inputs["manager"] - end_date = datetime.fromtimestamp(inputs["end_date"]*1000.0).strptime("%m/%d/%Y %H:%M") - start_date = datetime.fromtimestamp(inputs["start_date"]*1000.0).strptime("%m/%d/%Y %H:%M") - say( - text=f":white_check_mark: Time-off request for ${start_date} to ${end_date} approved by <@${manager}>", - blocks=[ - { - "type": 'context', - "elements": [ - { - "type": 'mrkdwn', - "text": f":white_check_mark: Time-off request for ${start_date} to ${end_date} approved by <@${manager}>", - }, - ], - } - ] + employee = inputs["employee"] + end_date = datetime.fromtimestamp(inputs["end_date"]).strftime("%m/%d/%Y %H:%M") + start_date = datetime.fromtimestamp(inputs["start_date"]).strftime("%m/%d/%Y %H:%M") + container = body["container"] + + context_block = get_context_block(start_date, end_date, manager) + + updated_blocks = body["message"]["blocks"][:-1] + updated_blocks.append(context_block) + + text = f'Time-off request for {start_date} to {end_date} approved by <@{manager}>' + + client.chat_postMessage( + channel=employee, + text=text, + blocks=[context_block]) + + client.chat_update( + channel=container["channel_id"], + ts=container["message_ts"], + text=text, + blocks=updated_blocks ) + complete_error("there is no error") except Exception as e: logger.error(e) complete_error("Cannot request approval") raise e + + +def get_context_block(start_date, end_date, manager): + return { + "type": 'context', + "elements": [ + { + "type": 'mrkdwn', + "text": f":white_check_mark: Time-off request for _{start_date}_ :right_arrow: _{end_date}_ approved by <@{manager}>", + }, + ], + } diff --git a/listeners/functions/actions/deny_action.py b/listeners/functions/actions/deny_action.py index d0ebca3..b4ea0f9 100644 --- a/listeners/functions/actions/deny_action.py +++ b/listeners/functions/actions/deny_action.py @@ -1,33 +1,53 @@ from datetime import datetime +from difflib import context_diff from logging import Logger -from slack_bolt import Ack, Say, CompleteError +from slack_bolt import Ack, CompleteError +from slack_sdk import WebClient -def deny_action(ack: Ack, say: Say, body, complete_error: CompleteError, logger: Logger): +def deny_action(ack: Ack, client: WebClient, body, complete_error: CompleteError, logger: Logger): try: ack() inputs = body["function_data"]["inputs"] manager = inputs["manager"] employee = inputs["employee"] - end_date = datetime.fromtimestamp(inputs["end_date"]*1000.0).strptime("%m/%d/%Y %H:%M") - start_date = datetime.fromtimestamp(inputs["start_date"]*1000.0).strptime("%m/%d/%Y %H:%M") - say( - text=f":x: Time-off request for ${end_date} to ${start_date} denied by <@${manager}>", - blocks=[ - { - "type": 'context', - "elements": [ - { - "type": 'mrkdwn', - "text": f": x: Time-off request for ${start_date} to ${end_date} denied by < @${manager} >", - }, - ], - } - ] + end_date = datetime.fromtimestamp(inputs["end_date"]).strftime("%m/%d/%Y %H:%M") + start_date = datetime.fromtimestamp(inputs["start_date"]).strftime("%m/%d/%Y %H:%M") + + context_block = get_context_block(start_date, end_date, manager) + + updated_blocks = body["message"]["blocks"][:-1] + updated_blocks.append(context_block) + + text = f"Time-off request for {end_date} to {start_date} denied by <@{manager}>" + + client.chat_postMessage( + channel=employee, + text=text, + blocks=[context_block] + ) + + client.chat_update( + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], + text=text, + blocks=updated_blocks ) complete_error("there is no error") except Exception as e: logger.error(e) complete_error("Cannot request approval") raise e + + +def get_context_block(start_date, end_date, manager): + return { + "type": 'context', + "elements": [ + { + "type": 'mrkdwn', + "text": f":no_entry: Time-off request for _{start_date}_ :arrow_right: _{end_date}_ denied by <@{manager}>", + }, + ], + } diff --git a/listeners/functions/request_approval.py b/listeners/functions/request_approval.py index c79b961..797206a 100644 --- a/listeners/functions/request_approval.py +++ b/listeners/functions/request_approval.py @@ -7,6 +7,7 @@ def request_approval(event, client: WebClient, complete_error: CompleteError, logger: Logger): try: + logger.info(f"my real token: {client.token}") inputs = event["inputs"] manager = inputs["manager"] employee = inputs["employee"] @@ -28,14 +29,14 @@ def request_approval(event, client: WebClient, complete_error: CompleteError, lo "type": 'section', "text": { "type": 'mrkdwn', - "text": f"*From: * < @${employee} >", + "text": f"*From: * <@{employee}>", }, }, { "type": 'section', "text": { "type": 'mrkdwn', - "text": f"*Dates: * ${start_date} to ${end_date}", + "text": f"*Dates: * _{start_date}_ :arrow_right: _{end_date}_", }, }, { From fc2b7ecf29f6e5c3d467c949cb578fafccd305f8 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Mon, 29 Aug 2022 18:51:04 -0400 Subject: [PATCH 10/25] Fix typo --- listeners/functions/actions/approve_action.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/listeners/functions/actions/approve_action.py b/listeners/functions/actions/approve_action.py index beaae19..da94e50 100644 --- a/listeners/functions/actions/approve_action.py +++ b/listeners/functions/actions/approve_action.py @@ -13,7 +13,6 @@ def approve_action(ack: Ack, client: WebClient, body, complete_error: CompleteEr employee = inputs["employee"] end_date = datetime.fromtimestamp(inputs["end_date"]).strftime("%m/%d/%Y %H:%M") start_date = datetime.fromtimestamp(inputs["start_date"]).strftime("%m/%d/%Y %H:%M") - container = body["container"] context_block = get_context_block(start_date, end_date, manager) @@ -28,8 +27,8 @@ def approve_action(ack: Ack, client: WebClient, body, complete_error: CompleteEr blocks=[context_block]) client.chat_update( - channel=container["channel_id"], - ts=container["message_ts"], + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], text=text, blocks=updated_blocks ) @@ -47,7 +46,7 @@ def get_context_block(start_date, end_date, manager): "elements": [ { "type": 'mrkdwn', - "text": f":white_check_mark: Time-off request for _{start_date}_ :right_arrow: _{end_date}_ approved by <@{manager}>", + "text": f":white_check_mark: Time-off request for _{start_date}_ :arrow_right: _{end_date}_ approved by <@{manager}>", }, ], } From 7400f6bbdf1789e121f87bd227d7700136f150c0 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Tue, 30 Aug 2022 18:02:00 -0400 Subject: [PATCH 11/25] update --- listeners/functions/__init__.py | 33 ++++++++----------- listeners/functions/actions/approve_action.py | 8 ++--- listeners/functions/actions/deny_action.py | 9 +++-- listeners/functions/request_approval.py | 7 ++-- listeners/functions/reverse_string.py | 10 +++--- 5 files changed, 30 insertions(+), 37 deletions(-) diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index 95a740d..ee890aa 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -1,6 +1,5 @@ from logging import Logger -from slack_bolt import CompleteError -from slack_bolt import CompleteSuccess +from slack_bolt import Complete from slack_bolt import App from .reverse_string import reverse_string from .request_approval import request_approval @@ -11,23 +10,19 @@ def register(app: App): - # @app.function("reverse") - # def reverse_string(event, complete_success: CompleteSuccess, complete_error: CompleteError, logger: Logger): - # try: - # string_to_reverse = event["inputs"]["stringToReverse"] - # complete_success({ - # "reverseString": string_to_reverse[::-1] - # }) - # except Exception as e: - # logger.error(e) - # complete_error("Cannot reverse string") - # raise e - - # @reverse_string.action("hello") - # def hello(): - # pass - - # app.function("reverse")(reverse_string) + @app.function("reverse") + def reverse_string(event, complete: Complete, logger: Logger): + try: + string_to_reverse = event["inputs"]["stringToReverse"] + complete( + outputs={ + "reverseString": string_to_reverse[::-1] + } + ) + except Exception as e: + logger.error(e) + complete(error="Cannot reverse string") + raise e request_approval_function: Function = app.function("review_approval")(request_approval) request_approval_function.action("approve_action_id")(approve_action) diff --git a/listeners/functions/actions/approve_action.py b/listeners/functions/actions/approve_action.py index da94e50..ca709ce 100644 --- a/listeners/functions/actions/approve_action.py +++ b/listeners/functions/actions/approve_action.py @@ -1,11 +1,11 @@ from datetime import datetime from logging import Logger -from slack_bolt import Ack, CompleteError +from slack_bolt import Ack, Complete from slack_sdk import WebClient -def approve_action(ack: Ack, client: WebClient, body, complete_error: CompleteError, logger: Logger): +def approve_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): try: ack() inputs = body["function_data"]["inputs"] @@ -33,10 +33,10 @@ def approve_action(ack: Ack, client: WebClient, body, complete_error: CompleteEr blocks=updated_blocks ) - complete_error("there is no error") + complete() except Exception as e: logger.error(e) - complete_error("Cannot request approval") + complete("Cannot request approval") raise e diff --git a/listeners/functions/actions/deny_action.py b/listeners/functions/actions/deny_action.py index b4ea0f9..6d66500 100644 --- a/listeners/functions/actions/deny_action.py +++ b/listeners/functions/actions/deny_action.py @@ -1,12 +1,11 @@ from datetime import datetime -from difflib import context_diff from logging import Logger -from slack_bolt import Ack, CompleteError +from slack_bolt import Ack, Complete from slack_sdk import WebClient -def deny_action(ack: Ack, client: WebClient, body, complete_error: CompleteError, logger: Logger): +def deny_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): try: ack() inputs = body["function_data"]["inputs"] @@ -34,10 +33,10 @@ def deny_action(ack: Ack, client: WebClient, body, complete_error: CompleteError text=text, blocks=updated_blocks ) - complete_error("there is no error") + complete() except Exception as e: logger.error(e) - complete_error("Cannot request approval") + complete(error="Cannot request approval") raise e diff --git a/listeners/functions/request_approval.py b/listeners/functions/request_approval.py index 797206a..7b25bc1 100644 --- a/listeners/functions/request_approval.py +++ b/listeners/functions/request_approval.py @@ -1,11 +1,11 @@ from slack_sdk import WebClient -from slack_bolt import CompleteError +from slack_bolt import Complete from logging import Logger from datetime import datetime -def request_approval(event, client: WebClient, complete_error: CompleteError, logger: Logger): +def request_approval(event, client: WebClient, complete: Complete, logger: Logger): try: logger.info(f"my real token: {client.token}") inputs = event["inputs"] @@ -64,8 +64,7 @@ def request_approval(event, client: WebClient, complete_error: CompleteError, lo ] } ]) - except Exception as e: logger.error(e) - complete_error("Cannot request approval") + complete(error="Cannot request approval") raise e diff --git a/listeners/functions/reverse_string.py b/listeners/functions/reverse_string.py index 54819a9..3a60f3c 100644 --- a/listeners/functions/reverse_string.py +++ b/listeners/functions/reverse_string.py @@ -1,15 +1,15 @@ -from slack_bolt import CompleteSuccess -from slack_bolt import CompleteError +from slack_bolt import Complete from logging import Logger -def reverse_string(event, complete_success: CompleteSuccess, complete_error: CompleteError, logger: Logger): +def reverse_string(event, complete: Complete, logger: Logger): try: string_to_reverse = event["inputs"]["stringToReverse"] - complete_success({ + complete( + outputs={ "reverseString": string_to_reverse[::-1] }) except Exception as e: logger.error(e) - complete_error("Cannot reverse string") + complete(error="Cannot reverse string") raise e From da14d0d66d56ea38d37a3f17b693552bdd9095c5 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 31 Aug 2022 15:33:26 -0400 Subject: [PATCH 12/25] changed approve deny example --- listeners/functions/__init__.py | 12 +- listeners/functions/approve_me.py | 109 ++++++++++++++++++ listeners/functions/request_approval.py | 70 ----------- manifest.json | 74 ++++-------- ...me_off_request_wf.json => approve_me.json} | 4 +- 5 files changed, 141 insertions(+), 128 deletions(-) create mode 100644 listeners/functions/approve_me.py delete mode 100644 listeners/functions/request_approval.py rename triggers/{time_off_request_wf.json => approve_me.json} (71%) diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index ee890aa..12e0f41 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -1,9 +1,7 @@ from logging import Logger from slack_bolt import Complete from slack_bolt import App -from .reverse_string import reverse_string -from .request_approval import request_approval -from .actions import approve_action, deny_action +from .approve_me import approve_me, approve_action, deny_action, APPROVE_ID, DENY_ID from slack_bolt.function import Function @@ -11,7 +9,7 @@ def register(app: App): @app.function("reverse") - def reverse_string(event, complete: Complete, logger: Logger): + def reverse_string(event, context, complete: Complete, logger: Logger): try: string_to_reverse = event["inputs"]["stringToReverse"] complete( @@ -24,6 +22,6 @@ def reverse_string(event, complete: Complete, logger: Logger): complete(error="Cannot reverse string") raise e - request_approval_function: Function = app.function("review_approval")(request_approval) - request_approval_function.action("approve_action_id")(approve_action) - request_approval_function.action("deny_action_id")(deny_action) + approve_me_function: Function = app.function("approve_me")(approve_me) + approve_me_function.action(APPROVE_ID)(approve_action) + approve_me_function.action(DENY_ID)(deny_action) diff --git a/listeners/functions/approve_me.py b/listeners/functions/approve_me.py new file mode 100644 index 0000000..5c6f239 --- /dev/null +++ b/listeners/functions/approve_me.py @@ -0,0 +1,109 @@ +from slack_sdk import WebClient +from slack_bolt import Complete, Ack +from logging import Logger + + +APPROVE_ID = "approve_action_id" +DENY_ID = "deny_action_id" + + +def approve_me(event, client: WebClient, complete: Complete, logger: Logger): + try: + channel = event["inputs"]["channel"] + + client.chat_postMessage( + channel=channel, + text='Approve me please', + blocks=[ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Approve me please* :wink:" + } + }, + { + "type": 'actions', + "block_id": 'approve-deny-buttons', + "elements": [ + { + "type": 'button', + "text": { + "type": 'plain_text', + "text": 'Approve', + }, + "action_id": APPROVE_ID, + "style": 'primary', + }, + { + "type": 'button', + "text": { + "type": 'plain_text', + "text": 'Deny', + }, + "action_id": DENY_ID, + "style": 'danger', + } + ] + } + ]) + except Exception as e: + logger.error(e) + complete(error="Cannot request approval") + raise e + + +def approve_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): + try: + ack() + blocks = body["message"]["blocks"][:-1] + blocks.append({ + "type": 'context', + "elements": [ + { + "type": 'mrkdwn', + "text": f":white_check_mark: I have been approved", + }, + ], + }) + + client.chat_update( + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], + text="I have been approved", + blocks=blocks + ) + + complete() + except Exception as e: + logger.error(e) + complete("Cannot request approval") + raise e + + +def deny_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): + try: + ack() + blocks = body["message"]["blocks"][:-1] + blocks.append({ + "type": 'context', + "elements": [ + { + "type": 'mrkdwn', + "text": f":no_entry: I have been denied", + }, + ], + }) + + client.chat_update( + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], + text="I have been denied", + blocks=blocks + ) + + complete() + except Exception as e: + logger.error(e) + complete("Cannot request approval") + raise e diff --git a/listeners/functions/request_approval.py b/listeners/functions/request_approval.py deleted file mode 100644 index 7b25bc1..0000000 --- a/listeners/functions/request_approval.py +++ /dev/null @@ -1,70 +0,0 @@ -from slack_sdk import WebClient -from slack_bolt import Complete -from logging import Logger - -from datetime import datetime - - -def request_approval(event, client: WebClient, complete: Complete, logger: Logger): - try: - logger.info(f"my real token: {client.token}") - inputs = event["inputs"] - manager = inputs["manager"] - employee = inputs["employee"] - end_date = datetime.fromtimestamp(inputs["end_date"]).strftime("%m/%d/%Y %H:%M") - start_date = datetime.fromtimestamp(inputs["start_date"]).strftime("%m/%d/%Y %H:%M") - - client.chat_postMessage( - channel=manager, - text='A new time-off request has been submitted.', - blocks=[ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "A new time-off request has been submitted" - } - }, - { - "type": 'section', - "text": { - "type": 'mrkdwn', - "text": f"*From: * <@{employee}>", - }, - }, - { - "type": 'section', - "text": { - "type": 'mrkdwn', - "text": f"*Dates: * _{start_date}_ :arrow_right: _{end_date}_", - }, - }, - { - "type": 'actions', - "block_id": 'approve-deny-buttons', - "elements": [ - { - "type": 'button', - "text": { - "type": 'plain_text', - "text": 'Approve', - }, - "action_id": 'approve_action_id', - "style": 'primary', - }, - { - "type": 'button', - "text": { - "type": 'plain_text', - "text": 'Deny', - }, - "action_id": 'deny_action_id', - "style": 'danger', - } - ] - } - ]) - except Exception as e: - logger.error(e) - complete(error="Cannot request approval") - raise e diff --git a/manifest.json b/manifest.json index 5b47a02..588dd61 100644 --- a/manifest.json +++ b/manifest.json @@ -83,33 +83,18 @@ ] } }, - "review_approval": { + "approve_me": { "title": "Approval Function", - "description": "Get approval for a request", + "description": "Get approval for a message", "input_parameters": { "properties": { - "employee": { - "type": "slack#/types/user_id", - "description": "Requester" - }, - "manager": { - "type": "slack#/types/user_id", - "description": "Manager" - }, - "start_date": { - "type": "slack#/types/timestamp", - "description": "Start Date" - }, - "end_date": { - "type": "slack#/types/timestamp", - "description": "End Date" + "channel": { + "type": "slack#/types/channel_id", + "description": "channel ID" } }, "required": [ - "employee", - "manager", - "start_date", - "end_date" + "channel" ] }, "output_parameters": { @@ -183,47 +168,41 @@ } ] }, - "time_off_request_wf": { - "title": "Time Off Request Workflow", - "description": "", + "approve_me_wf": { + "title": "Approve Me Workflow", + "description": "Message that allows users to approve it", "input_parameters": { "properties": { "interactivity": { "type": "slack#/types/interactivity" + }, + "channel": { + "type": "slack#/types/channel_id" } }, - "required": [] + "required": [ + "interactivity" + ] }, "steps": [ { "id": "0", "function_id": "slack#/functions/open_form", "inputs": { - "title": "Request Time Off", - "submit_label": "Request", - "description": "Please describe your request", + "title": "Approve me form", + "submit_label": "Send", + "description": "Please select a channel to send the message", "interactivity": "{{inputs.interactivity}}", "fields": { "required": [ - "manager", - "start_date", - "end_date" + "channel" ], "elements": [ { - "name": "manager", - "title": "Manager", - "type": "slack#/types/user_id" - }, - { - "name": "start_date", - "title": "Start Date", - "type": "slack#/types/timestamp" - }, - { - "name": "end_date", - "title": "End Date", - "type": "slack#/types/timestamp" + "name": "channel", + "title": "Post in", + "type": "slack#/types/channel_id", + "default": "{{inputs.channel}}" } ] } @@ -231,12 +210,9 @@ }, { "id": "1", - "function_id": "#/functions/review_approval", + "function_id": "#/functions/approve_me", "inputs": { - "employee": "{{inputs.interactivity.interactor.id}}", - "manager": "{{steps.0.fields.manager}}", - "start_date": "{{steps.0.fields.start_date}}", - "end_date": "{{steps.0.fields.end_date}}" + "channel": "{{steps.0.fields.channel}}" } } ] diff --git a/triggers/time_off_request_wf.json b/triggers/approve_me.json similarity index 71% rename from triggers/time_off_request_wf.json rename to triggers/approve_me.json index 8fdef9e..8eff545 100644 --- a/triggers/time_off_request_wf.json +++ b/triggers/approve_me.json @@ -1,8 +1,8 @@ { "type": "shortcut", - "name": "Take Your Time", + "name": "Approve Me", "description": "Submit a request to take time off", - "workflow": "#/workflows/time_off_request_wf", + "workflow": "#/workflows/approve_me_wf", "shortcut": {}, "inputs": { "interactivity": { From e70f5d55bc8562fdd7775ddaa5c2214de8f5d369 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 31 Aug 2022 15:34:34 -0400 Subject: [PATCH 13/25] cleaned up project --- listeners/functions/__init__.py | 2 +- listeners/functions/actions/__init__.py | 8 --- listeners/functions/actions/approve_action.py | 52 ------------------- listeners/functions/actions/deny_action.py | 52 ------------------- listeners/functions/reverse_string.py | 15 ------ 5 files changed, 1 insertion(+), 128 deletions(-) delete mode 100644 listeners/functions/actions/__init__.py delete mode 100644 listeners/functions/actions/approve_action.py delete mode 100644 listeners/functions/actions/deny_action.py delete mode 100644 listeners/functions/reverse_string.py diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index 12e0f41..544528a 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -9,7 +9,7 @@ def register(app: App): @app.function("reverse") - def reverse_string(event, context, complete: Complete, logger: Logger): + def reverse_string(event, complete: Complete, logger: Logger): try: string_to_reverse = event["inputs"]["stringToReverse"] complete( diff --git a/listeners/functions/actions/__init__.py b/listeners/functions/actions/__init__.py deleted file mode 100644 index 9d9cf7d..0000000 --- a/listeners/functions/actions/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .approve_action import approve_action -from .deny_action import deny_action - - -__all__ = [ - "approve_action", - "deny_action" -] diff --git a/listeners/functions/actions/approve_action.py b/listeners/functions/actions/approve_action.py deleted file mode 100644 index ca709ce..0000000 --- a/listeners/functions/actions/approve_action.py +++ /dev/null @@ -1,52 +0,0 @@ -from datetime import datetime -from logging import Logger - -from slack_bolt import Ack, Complete -from slack_sdk import WebClient - - -def approve_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): - try: - ack() - inputs = body["function_data"]["inputs"] - manager = inputs["manager"] - employee = inputs["employee"] - end_date = datetime.fromtimestamp(inputs["end_date"]).strftime("%m/%d/%Y %H:%M") - start_date = datetime.fromtimestamp(inputs["start_date"]).strftime("%m/%d/%Y %H:%M") - - context_block = get_context_block(start_date, end_date, manager) - - updated_blocks = body["message"]["blocks"][:-1] - updated_blocks.append(context_block) - - text = f'Time-off request for {start_date} to {end_date} approved by <@{manager}>' - - client.chat_postMessage( - channel=employee, - text=text, - blocks=[context_block]) - - client.chat_update( - channel=body["container"]["channel_id"], - ts=body["container"]["message_ts"], - text=text, - blocks=updated_blocks - ) - - complete() - except Exception as e: - logger.error(e) - complete("Cannot request approval") - raise e - - -def get_context_block(start_date, end_date, manager): - return { - "type": 'context', - "elements": [ - { - "type": 'mrkdwn', - "text": f":white_check_mark: Time-off request for _{start_date}_ :arrow_right: _{end_date}_ approved by <@{manager}>", - }, - ], - } diff --git a/listeners/functions/actions/deny_action.py b/listeners/functions/actions/deny_action.py deleted file mode 100644 index 6d66500..0000000 --- a/listeners/functions/actions/deny_action.py +++ /dev/null @@ -1,52 +0,0 @@ -from datetime import datetime -from logging import Logger - -from slack_bolt import Ack, Complete -from slack_sdk import WebClient - - -def deny_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): - try: - ack() - inputs = body["function_data"]["inputs"] - manager = inputs["manager"] - employee = inputs["employee"] - end_date = datetime.fromtimestamp(inputs["end_date"]).strftime("%m/%d/%Y %H:%M") - start_date = datetime.fromtimestamp(inputs["start_date"]).strftime("%m/%d/%Y %H:%M") - - context_block = get_context_block(start_date, end_date, manager) - - updated_blocks = body["message"]["blocks"][:-1] - updated_blocks.append(context_block) - - text = f"Time-off request for {end_date} to {start_date} denied by <@{manager}>" - - client.chat_postMessage( - channel=employee, - text=text, - blocks=[context_block] - ) - - client.chat_update( - channel=body["container"]["channel_id"], - ts=body["container"]["message_ts"], - text=text, - blocks=updated_blocks - ) - complete() - except Exception as e: - logger.error(e) - complete(error="Cannot request approval") - raise e - - -def get_context_block(start_date, end_date, manager): - return { - "type": 'context', - "elements": [ - { - "type": 'mrkdwn', - "text": f":no_entry: Time-off request for _{start_date}_ :arrow_right: _{end_date}_ denied by <@{manager}>", - }, - ], - } diff --git a/listeners/functions/reverse_string.py b/listeners/functions/reverse_string.py deleted file mode 100644 index 3a60f3c..0000000 --- a/listeners/functions/reverse_string.py +++ /dev/null @@ -1,15 +0,0 @@ -from slack_bolt import Complete -from logging import Logger - - -def reverse_string(event, complete: Complete, logger: Logger): - try: - string_to_reverse = event["inputs"]["stringToReverse"] - complete( - outputs={ - "reverseString": string_to_reverse[::-1] - }) - except Exception as e: - logger.error(e) - complete(error="Cannot reverse string") - raise e From 85b377baecf33e613bb8feccaf1ddec7b1b5f701 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 1 Sep 2022 13:15:50 -0400 Subject: [PATCH 14/25] cleaned up project --- listeners/functions/__init__.py | 4 +-- listeners/functions/approve_me.py | 44 ++++++++++++------------------- manifest.json | 34 +++--------------------- triggers/approve_me.json | 4 +-- 4 files changed, 25 insertions(+), 61 deletions(-) diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index 544528a..c741143 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -3,7 +3,7 @@ from slack_bolt import App from .approve_me import approve_me, approve_action, deny_action, APPROVE_ID, DENY_ID -from slack_bolt.function import Function +from slack_bolt.slack_function import SlackFunction def register(app: App): @@ -22,6 +22,6 @@ def reverse_string(event, complete: Complete, logger: Logger): complete(error="Cannot reverse string") raise e - approve_me_function: Function = app.function("approve_me")(approve_me) + approve_me_function: SlackFunction = app.function("approve_me")(approve_me) approve_me_function.action(APPROVE_ID)(approve_action) approve_me_function.action(DENY_ID)(deny_action) diff --git a/listeners/functions/approve_me.py b/listeners/functions/approve_me.py index 5c6f239..1767192 100644 --- a/listeners/functions/approve_me.py +++ b/listeners/functions/approve_me.py @@ -9,10 +9,8 @@ def approve_me(event, client: WebClient, complete: Complete, logger: Logger): try: - channel = event["inputs"]["channel"] - client.chat_postMessage( - channel=channel, + channel=event["inputs"]["channel"], text='Approve me please', blocks=[ { @@ -57,27 +55,17 @@ def approve_action(ack: Ack, client: WebClient, body, complete: Complete, logger try: ack() blocks = body["message"]["blocks"][:-1] - blocks.append({ - "type": 'context', - "elements": [ - { - "type": 'mrkdwn', - "text": f":white_check_mark: I have been approved", - }, - ], - }) - + blocks.append(_get_context_block(":white_check_mark: I have been approved")) client.chat_update( channel=body["container"]["channel_id"], ts=body["container"]["message_ts"], text="I have been approved", blocks=blocks ) - complete() except Exception as e: logger.error(e) - complete("Cannot request approval") + complete(error="Cannot request approval") raise e @@ -85,25 +73,27 @@ def deny_action(ack: Ack, client: WebClient, body, complete: Complete, logger: L try: ack() blocks = body["message"]["blocks"][:-1] - blocks.append({ - "type": 'context', - "elements": [ - { - "type": 'mrkdwn', - "text": f":no_entry: I have been denied", - }, - ], - }) - + blocks.append(_get_context_block(":no_entry: I have been denied")) client.chat_update( channel=body["container"]["channel_id"], ts=body["container"]["message_ts"], text="I have been denied", blocks=blocks ) - complete() except Exception as e: logger.error(e) - complete("Cannot request approval") + complete(error="Cannot request approval") raise e + + +def _get_context_block(mrkdwn): + return { + "type": 'context', + "elements": [ + { + "type": 'mrkdwn', + "text": mrkdwn, + }, + ], + } diff --git a/manifest.json b/manifest.json index 588dd61..4ca6f78 100644 --- a/manifest.json +++ b/manifest.json @@ -84,13 +84,13 @@ } }, "approve_me": { - "title": "Approval Function", + "title": "Approve a message", "description": "Get approval for a message", "input_parameters": { "properties": { "channel": { "type": "slack#/types/channel_id", - "description": "channel ID" + "description": "channel id" } }, "required": [ @@ -173,46 +173,20 @@ "description": "Message that allows users to approve it", "input_parameters": { "properties": { - "interactivity": { - "type": "slack#/types/interactivity" - }, "channel": { "type": "slack#/types/channel_id" } }, "required": [ - "interactivity" + "channel" ] }, "steps": [ { "id": "0", - "function_id": "slack#/functions/open_form", - "inputs": { - "title": "Approve me form", - "submit_label": "Send", - "description": "Please select a channel to send the message", - "interactivity": "{{inputs.interactivity}}", - "fields": { - "required": [ - "channel" - ], - "elements": [ - { - "name": "channel", - "title": "Post in", - "type": "slack#/types/channel_id", - "default": "{{inputs.channel}}" - } - ] - } - } - }, - { - "id": "1", "function_id": "#/functions/approve_me", "inputs": { - "channel": "{{steps.0.fields.channel}}" + "channel": "{{inputs.channel}}" } } ] diff --git a/triggers/approve_me.json b/triggers/approve_me.json index 8eff545..c256f7b 100644 --- a/triggers/approve_me.json +++ b/triggers/approve_me.json @@ -5,8 +5,8 @@ "workflow": "#/workflows/approve_me_wf", "shortcut": {}, "inputs": { - "interactivity": { - "value": "{{data.interactivity}}" + "channel": { + "value": "{{data.channel_id}}" } } } \ No newline at end of file From 81b1d1e1730b8dfbf6b30936004a0b012fd11704 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Thu, 1 Sep 2022 17:06:07 -0400 Subject: [PATCH 15/25] Refactored for sample function --- README.md | 6 +- app.py | 1 + listeners/functions/__init__.py | 24 +---- listeners/functions/approve_me.py | 99 ------------------- listeners/functions/sample_function.py | 16 +++ manifest.json => manifest/manifest.json | 97 ++++++------------ .../triggers/sample_trigger.json | 6 +- triggers/approve_me.json | 12 --- 8 files changed, 53 insertions(+), 208 deletions(-) delete mode 100644 listeners/functions/approve_me.py create mode 100644 listeners/functions/sample_function.py rename manifest.json => manifest/manifest.json (59%) rename triggers/test_reverse.json => manifest/triggers/sample_trigger.json (58%) delete mode 100644 triggers/approve_me.json diff --git a/README.md b/README.md index a65b985..68dbef0 100644 --- a/README.md +++ b/README.md @@ -69,13 +69,13 @@ Every incoming request is routed to a "listener". Inside this directory, we grou ### triggers In order to run this project using the slack cli you must first set up triggers in your workspace. -These triggers are defined in `trigger.json`, run the following command to add them to your workspace +These triggers are defined in `manifest/triggers` folder, run the following command to add the defined one to your workspace ```bash -slack trigger create --trigger-def "./trigger.json" +slack trigger create --trigger-def "./manifest/triggers/sample_trigger.json" ``` ### manifest -The `manifest.json` defines the behavior of your application, here are a vew helpful commands +The `manifest/manifest.json` defines the behavior of your application, here are a vew helpful commands ```bash slack manifest # view the compiled manifest slack manifest validate # to validate your manifest diff --git a/app.py b/app.py index 9a75139..b2dd7d5 100644 --- a/app.py +++ b/app.py @@ -22,6 +22,7 @@ def log_request(client, logger, body, next): logger.debug(body) return next() + # Start Bolt app if __name__ == "__main__": SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start() diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index c741143..21e8a12 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -1,27 +1,7 @@ -from logging import Logger -from slack_bolt import Complete from slack_bolt import App -from .approve_me import approve_me, approve_action, deny_action, APPROVE_ID, DENY_ID -from slack_bolt.slack_function import SlackFunction +from .sample_function import sample_function def register(app: App): - - @app.function("reverse") - def reverse_string(event, complete: Complete, logger: Logger): - try: - string_to_reverse = event["inputs"]["stringToReverse"] - complete( - outputs={ - "reverseString": string_to_reverse[::-1] - } - ) - except Exception as e: - logger.error(e) - complete(error="Cannot reverse string") - raise e - - approve_me_function: SlackFunction = app.function("approve_me")(approve_me) - approve_me_function.action(APPROVE_ID)(approve_action) - approve_me_function.action(DENY_ID)(deny_action) + app.function("sample_function")(sample_function) diff --git a/listeners/functions/approve_me.py b/listeners/functions/approve_me.py deleted file mode 100644 index 1767192..0000000 --- a/listeners/functions/approve_me.py +++ /dev/null @@ -1,99 +0,0 @@ -from slack_sdk import WebClient -from slack_bolt import Complete, Ack -from logging import Logger - - -APPROVE_ID = "approve_action_id" -DENY_ID = "deny_action_id" - - -def approve_me(event, client: WebClient, complete: Complete, logger: Logger): - try: - client.chat_postMessage( - channel=event["inputs"]["channel"], - text='Approve me please', - blocks=[ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Approve me please* :wink:" - } - }, - { - "type": 'actions', - "block_id": 'approve-deny-buttons', - "elements": [ - { - "type": 'button', - "text": { - "type": 'plain_text', - "text": 'Approve', - }, - "action_id": APPROVE_ID, - "style": 'primary', - }, - { - "type": 'button', - "text": { - "type": 'plain_text', - "text": 'Deny', - }, - "action_id": DENY_ID, - "style": 'danger', - } - ] - } - ]) - except Exception as e: - logger.error(e) - complete(error="Cannot request approval") - raise e - - -def approve_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): - try: - ack() - blocks = body["message"]["blocks"][:-1] - blocks.append(_get_context_block(":white_check_mark: I have been approved")) - client.chat_update( - channel=body["container"]["channel_id"], - ts=body["container"]["message_ts"], - text="I have been approved", - blocks=blocks - ) - complete() - except Exception as e: - logger.error(e) - complete(error="Cannot request approval") - raise e - - -def deny_action(ack: Ack, client: WebClient, body, complete: Complete, logger: Logger): - try: - ack() - blocks = body["message"]["blocks"][:-1] - blocks.append(_get_context_block(":no_entry: I have been denied")) - client.chat_update( - channel=body["container"]["channel_id"], - ts=body["container"]["message_ts"], - text="I have been denied", - blocks=blocks - ) - complete() - except Exception as e: - logger.error(e) - complete(error="Cannot request approval") - raise e - - -def _get_context_block(mrkdwn): - return { - "type": 'context', - "elements": [ - { - "type": 'mrkdwn', - "text": mrkdwn, - }, - ], - } diff --git a/listeners/functions/sample_function.py b/listeners/functions/sample_function.py new file mode 100644 index 0000000..410a2da --- /dev/null +++ b/listeners/functions/sample_function.py @@ -0,0 +1,16 @@ +from logging import Logger +from slack_bolt import Complete + + +def sample_function(event, complete: Complete, logger: Logger): + try: + message = event["inputs"]["message"] + complete( + outputs={ + "updatedMsg": f":wave: You submitted the following message: \n\n>{message}" + } + ) + except Exception as e: + logger.error(e) + complete(error="Cannot submit the message") + raise e diff --git a/manifest.json b/manifest/manifest.json similarity index 59% rename from manifest.json rename to manifest/manifest.json index 4ca6f78..1dc8a42 100644 --- a/manifest.json +++ b/manifest/manifest.json @@ -57,57 +57,38 @@ "token_rotation_enabled": false }, "functions": { - "reverse": { - "title": "Reverse", - "description": "Takes a string and reverses it", + "sample_function": { + "title": "Sample function", + "description": "A sample function", "input_parameters": { "properties": { - "stringToReverse": { + "message": { "type": "string", - "description": "The string to reverse" + "description": "Message to be posted" } }, "required": [ - "stringToReverse" + "message" ] }, "output_parameters": { "properties": { - "reverseString": { + "updatedMsg": { "type": "string", - "description": "The string in reverse" + "description": "Updated message to be posted" } }, "required": [ - "reverseString" + "updatedMsg" ] } - }, - "approve_me": { - "title": "Approve a message", - "description": "Get approval for a message", - "input_parameters": { - "properties": { - "channel": { - "type": "slack#/types/channel_id", - "description": "channel id" - } - }, - "required": [ - "channel" - ] - }, - "output_parameters": { - "properties": {}, - "required": [] - } } }, "types": {}, "workflows": { - "test_reverse": { - "title": "Test Reverse Function", - "description": "test the reverse function", + "sample_workflow": { + "title": "Sample workflow", + "description": "A sample workflow", "input_parameters": { "properties": { "interactivity": { @@ -126,36 +107,37 @@ "id": "0", "function_id": "slack#/functions/open_form", "inputs": { - "title": "Reverse string form", - "submit_label": "Submit form", - "description": "Submit a string to reverse", + "title": "Send message to channel", + "submit_label": "Send message", + "description": "Send a message to a channel", "interactivity": "{{inputs.interactivity}}", "fields": { - "required": [ - "channel", - "stringInput" - ], "elements": [ { - "name": "stringInput", - "title": "String input", - "type": "string" + "name": "message", + "title": "Message", + "type": "string", + "long": true }, { "name": "channel", - "title": "Post in", + "title": "Channel to send message to", "type": "slack#/types/channel_id", "default": "{{inputs.channel}}" } + ], + "required": [ + "channel", + "message" ] } } }, { "id": "1", - "function_id": "#/functions/reverse", + "function_id": "#/functions/sample_function", "inputs": { - "stringToReverse": "{{steps.0.fields.stringInput}}" + "message": "{{steps.0.fields.message}}" } }, { @@ -163,34 +145,11 @@ "function_id": "slack#/functions/send_message", "inputs": { "channel_id": "{{steps.0.fields.channel}}", - "message": "{{steps.1.reverseString}}" - } - } - ] - }, - "approve_me_wf": { - "title": "Approve Me Workflow", - "description": "Message that allows users to approve it", - "input_parameters": { - "properties": { - "channel": { - "type": "slack#/types/channel_id" - } - }, - "required": [ - "channel" - ] - }, - "steps": [ - { - "id": "0", - "function_id": "#/functions/approve_me", - "inputs": { - "channel": "{{inputs.channel}}" + "message": "{{steps.1.updatedMsg}}" } } ] } }, "outgoing_domains": [] -} +} \ No newline at end of file diff --git a/triggers/test_reverse.json b/manifest/triggers/sample_trigger.json similarity index 58% rename from triggers/test_reverse.json rename to manifest/triggers/sample_trigger.json index 85c13ac..4a51c44 100644 --- a/triggers/test_reverse.json +++ b/manifest/triggers/sample_trigger.json @@ -1,8 +1,8 @@ { "type": "shortcut", - "name": "Reverse a String", - "description": "Starts the workflow to test reversing a string", - "workflow": "#/workflows/test_reverse", + "name": "Sample trigger", + "description": "A sample trigger", + "workflow": "#/workflows/sample_workflow", "inputs": { "interactivity": { "value": "{{data.interactivity}}" diff --git a/triggers/approve_me.json b/triggers/approve_me.json deleted file mode 100644 index c256f7b..0000000 --- a/triggers/approve_me.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "shortcut", - "name": "Approve Me", - "description": "Submit a request to take time off", - "workflow": "#/workflows/approve_me_wf", - "shortcut": {}, - "inputs": { - "channel": { - "value": "{{data.channel_id}}" - } - } -} \ No newline at end of file From 7eee75819c4382384e496af3c188849ab36fe623 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Mon, 12 Sep 2022 15:12:43 -0400 Subject: [PATCH 16/25] Added function view example --- listeners/functions/__init__.py | 5 ++ listeners/functions/sample_view.py | 61 +++++++++++++++++++++ manifest/manifest.json | 62 +++++++++++++++++++++- manifest/triggers/sample_view_trigger.json | 14 +++++ 4 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 listeners/functions/sample_view.py create mode 100644 manifest/triggers/sample_view_trigger.json diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index 21e8a12..d738cc7 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -1,7 +1,12 @@ from slack_bolt import App +from slack_bolt.slack_function import SlackFunction from .sample_function import sample_function +from .sample_view import sample_view, sample_view_submission, sample_view_closed def register(app: App): app.function("sample_function")(sample_function) + sample_view_func: SlackFunction = app.function("sample_view_function")(sample_view) + sample_view_func.view_submission("func_sample_view_id")(sample_view_submission) + sample_view_func.view_closed("func_sample_view_id")(sample_view_closed) diff --git a/listeners/functions/sample_view.py b/listeners/functions/sample_view.py new file mode 100644 index 0000000..1506933 --- /dev/null +++ b/listeners/functions/sample_view.py @@ -0,0 +1,61 @@ +import os +import logging + +from slack_sdk import WebClient +from slack_bolt import Complete, Ack + + +def sample_view(event, client: WebClient, complete: Complete, logger: logging.Logger): + try: + interactivity_pointer = event["inputs"]["interactivity.interactivity_pointer"] + client.views_open( + interactivity_pointer=interactivity_pointer, + trigger_id=None, + view={ + "type": "modal", + "callback_id": "func_sample_view_id", + "title": {"type": "plain_text", "text": "Sample modal title"}, + "blocks": [ + { + "type": "input", + "block_id": "input_block_id", + "label": { + "type": "plain_text", + "text": "What are your hopes and dreams?", + }, + "element": { + "type": "plain_text_input", + "action_id": "sample_input_id", + "multiline": True, + }, + }, + ], + "submit": {"type": "plain_text", "text": "Submit"}, + "notify_on_close": True, + }, + ) + except Exception as e: + logger.error(e) + complete(error="Cannot create view") + raise e + + +def sample_view_submission(ack: Ack, view, complete: Complete, logger: logging.Logger): + try: + ack() + message = view["state"]["values"]["input_block_id"]["sample_input_id"]["value"] + complete(outputs={"markdown": f":wave: You submitted the following message: \n\n>{message}"}) + except Exception as e: + logger.error(e) + complete(error="Cannot submit form") + raise e + + +def sample_view_closed(ack: Ack, complete: Complete, logger: logging.Logger): + try: + ack() + complete(outputs={"markdown": f"You closed the form"}) + except Exception as e: + logger.error(e) + complete(error="Cannot close form") + raise e diff --git a/manifest/manifest.json b/manifest/manifest.json index 1dc8a42..712cc32 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -52,7 +52,7 @@ "interactivity": { "is_enabled": true }, - "org_deploy_enabled": false, + "org_deploy_enabled": true, "socket_mode_enabled": true, "token_rotation_enabled": false }, @@ -82,6 +82,28 @@ "updatedMsg" ] } + }, + "sample_view_function": { + "title": "Sample view function", + "description": "A sample function thats uses views", + "input_parameters": { + "properties": { + "interactivity": { + "type": "slack#/types/interactivity" + } + } + }, + "output_parameters": { + "properties": { + "markdown": { + "type": "string", + "description": "message to be send to slack" + } + }, + "required": [ + "markdown" + ] + } } }, "types": {}, @@ -128,7 +150,8 @@ ], "required": [ "channel", - "message" + "message", + "interactivity" ] } } @@ -149,6 +172,41 @@ } } ] + }, + "sample_view_workflow": { + "title": "Sample view workflow", + "description": "A sample view workflow", + "input_parameters": { + "properties": { + "interactivity": { + "type": "slack#/types/interactivity" + }, + "channel": { + "type": "slack#/types/channel_id" + } + }, + "required": [ + "interactivity", + "channel" + ] + }, + "steps": [ + { + "id": "0", + "function_id": "#/functions/sample_view_function", + "inputs": { + "interactivity": "{{inputs.interactivity}}" + } + }, + { + "id": "1", + "function_id": "slack#/functions/send_message", + "inputs": { + "channel_id": "{{inputs.channel}}", + "message": "{{steps.0.markdown}}" + } + } + ] } }, "outgoing_domains": [] diff --git a/manifest/triggers/sample_view_trigger.json b/manifest/triggers/sample_view_trigger.json new file mode 100644 index 0000000..df3a22b --- /dev/null +++ b/manifest/triggers/sample_view_trigger.json @@ -0,0 +1,14 @@ +{ + "type": "shortcut", + "name": "Sample view trigger", + "description": "A sample view trigger", + "workflow": "#/workflows/sample_view_workflow", + "inputs": { + "interactivity": { + "value": "{{data.interactivity}}" + }, + "channel": { + "value": "{{data.channel_id}}" + } + } +} \ No newline at end of file From a4d19f69afacb55800e41faaa356089956ca39e3 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 14 Sep 2022 11:08:49 -0400 Subject: [PATCH 17/25] clean up project --- listeners/functions/__init__.py | 1 + listeners/functions/sample_view.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index d738cc7..e4970fa 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -7,6 +7,7 @@ def register(app: App): app.function("sample_function")(sample_function) + sample_view_func: SlackFunction = app.function("sample_view_function")(sample_view) sample_view_func.view_submission("func_sample_view_id")(sample_view_submission) sample_view_func.view_closed("func_sample_view_id")(sample_view_closed) diff --git a/listeners/functions/sample_view.py b/listeners/functions/sample_view.py index 1506933..c0a7d55 100644 --- a/listeners/functions/sample_view.py +++ b/listeners/functions/sample_view.py @@ -7,7 +7,7 @@ def sample_view(event, client: WebClient, complete: Complete, logger: logging.Logger): try: - interactivity_pointer = event["inputs"]["interactivity.interactivity_pointer"] + interactivity_pointer = event["inputs"]["interactivity"]["interactivity_pointer"] client.views_open( interactivity_pointer=interactivity_pointer, trigger_id=None, From f950d65e4bc62f260144e21134e79818085324cf Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 14 Sep 2022 13:17:04 -0400 Subject: [PATCH 18/25] fixed typo --- manifest/manifest.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/manifest/manifest.json b/manifest/manifest.json index 712cc32..49cfe6e 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -150,8 +150,7 @@ ], "required": [ "channel", - "message", - "interactivity" + "message" ] } } From c72cff9b9a98b89c819b194fed69540b4f44acdd Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Mon, 24 Oct 2022 13:28:27 -0400 Subject: [PATCH 19/25] Update requirements.txt --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b0e0f6c..11ad808 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -# slack-bolt -../../forks/bolt-python/dist/slack_bolt-1.14.3-py2.py3-none-any.whl +slack-bolt==1.15.0.dev0 pytest flake8==5.0.4 black==22.8.0 From 9da53ee736dbb7799cfde9f1691fcf2d2fa5ecd0 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Mon, 24 Oct 2022 14:02:29 -0400 Subject: [PATCH 20/25] Clean up future starter template --- app.py | 8 --- listeners/functions/__init__.py | 6 --- listeners/functions/sample_view.py | 61 ---------------------- manifest/manifest.json | 57 -------------------- manifest/triggers/sample_view_trigger.json | 14 ----- 5 files changed, 146 deletions(-) delete mode 100644 listeners/functions/sample_view.py delete mode 100644 manifest/triggers/sample_view_trigger.json diff --git a/app.py b/app.py index b2dd7d5..39f9c63 100644 --- a/app.py +++ b/app.py @@ -15,14 +15,6 @@ # Register Listeners register_listeners(app) - -@app.middleware # or app.use(log_request) -def log_request(client, logger, body, next): - logger.info(f"this is my token: {client.token}") - logger.debug(body) - return next() - - # Start Bolt app if __name__ == "__main__": SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start() diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index e4970fa..21e8a12 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -1,13 +1,7 @@ from slack_bolt import App -from slack_bolt.slack_function import SlackFunction from .sample_function import sample_function -from .sample_view import sample_view, sample_view_submission, sample_view_closed def register(app: App): app.function("sample_function")(sample_function) - - sample_view_func: SlackFunction = app.function("sample_view_function")(sample_view) - sample_view_func.view_submission("func_sample_view_id")(sample_view_submission) - sample_view_func.view_closed("func_sample_view_id")(sample_view_closed) diff --git a/listeners/functions/sample_view.py b/listeners/functions/sample_view.py deleted file mode 100644 index c0a7d55..0000000 --- a/listeners/functions/sample_view.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import logging - -from slack_sdk import WebClient -from slack_bolt import Complete, Ack - - -def sample_view(event, client: WebClient, complete: Complete, logger: logging.Logger): - try: - interactivity_pointer = event["inputs"]["interactivity"]["interactivity_pointer"] - client.views_open( - interactivity_pointer=interactivity_pointer, - trigger_id=None, - view={ - "type": "modal", - "callback_id": "func_sample_view_id", - "title": {"type": "plain_text", "text": "Sample modal title"}, - "blocks": [ - { - "type": "input", - "block_id": "input_block_id", - "label": { - "type": "plain_text", - "text": "What are your hopes and dreams?", - }, - "element": { - "type": "plain_text_input", - "action_id": "sample_input_id", - "multiline": True, - }, - }, - ], - "submit": {"type": "plain_text", "text": "Submit"}, - "notify_on_close": True, - }, - ) - except Exception as e: - logger.error(e) - complete(error="Cannot create view") - raise e - - -def sample_view_submission(ack: Ack, view, complete: Complete, logger: logging.Logger): - try: - ack() - message = view["state"]["values"]["input_block_id"]["sample_input_id"]["value"] - complete(outputs={"markdown": f":wave: You submitted the following message: \n\n>{message}"}) - except Exception as e: - logger.error(e) - complete(error="Cannot submit form") - raise e - - -def sample_view_closed(ack: Ack, complete: Complete, logger: logging.Logger): - try: - ack() - complete(outputs={"markdown": f"You closed the form"}) - except Exception as e: - logger.error(e) - complete(error="Cannot close form") - raise e diff --git a/manifest/manifest.json b/manifest/manifest.json index 49cfe6e..77e8f04 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -82,28 +82,6 @@ "updatedMsg" ] } - }, - "sample_view_function": { - "title": "Sample view function", - "description": "A sample function thats uses views", - "input_parameters": { - "properties": { - "interactivity": { - "type": "slack#/types/interactivity" - } - } - }, - "output_parameters": { - "properties": { - "markdown": { - "type": "string", - "description": "message to be send to slack" - } - }, - "required": [ - "markdown" - ] - } } }, "types": {}, @@ -171,41 +149,6 @@ } } ] - }, - "sample_view_workflow": { - "title": "Sample view workflow", - "description": "A sample view workflow", - "input_parameters": { - "properties": { - "interactivity": { - "type": "slack#/types/interactivity" - }, - "channel": { - "type": "slack#/types/channel_id" - } - }, - "required": [ - "interactivity", - "channel" - ] - }, - "steps": [ - { - "id": "0", - "function_id": "#/functions/sample_view_function", - "inputs": { - "interactivity": "{{inputs.interactivity}}" - } - }, - { - "id": "1", - "function_id": "slack#/functions/send_message", - "inputs": { - "channel_id": "{{inputs.channel}}", - "message": "{{steps.0.markdown}}" - } - } - ] } }, "outgoing_domains": [] diff --git a/manifest/triggers/sample_view_trigger.json b/manifest/triggers/sample_view_trigger.json deleted file mode 100644 index df3a22b..0000000 --- a/manifest/triggers/sample_view_trigger.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "shortcut", - "name": "Sample view trigger", - "description": "A sample view trigger", - "workflow": "#/workflows/sample_view_workflow", - "inputs": { - "interactivity": { - "value": "{{data.interactivity}}" - }, - "channel": { - "value": "{{data.channel_id}}" - } - } -} \ No newline at end of file From ad2f2b05f330504b5970b9f6306ed1cc58932597 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Mon, 24 Oct 2022 14:13:46 -0400 Subject: [PATCH 21/25] Linted project --- README.md | 27 ++++++++++++++++++-------- app.py | 1 - listeners/functions/sample_function.py | 6 +----- listeners/messages/sample_message.py | 1 - 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 68dbef0..9feb049 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,19 @@ This is a generic Bolt for Python template app used to build out Slack apps. Before getting started, make sure you have a development workspace where you have permissions to install apps. If you don’t have one setup, go ahead and [create one](https://slack.com/create). + ## Installation -#### Create a Slack App +### Create a Slack App + 1. Open [https://api.slack.com/apps/new](https://api.slack.com/apps/new) and choose "From an app manifest" 2. Choose the workspace you want to install the application to 3. Copy the contents of [manifest.json](./manifest.json) into the text box that says `*Paste your manifest code here*` (within the JSON tab) and click *Next* 4. Review the configuration and click *Create* 5. Click *Install to Workspace* and *Allow* on the screen that follows. You'll then be redirected to the App Configuration dashboard. -#### Environment Variables +### Environment Variables + Before you can run the app, you'll need to store some environment variables. 1. Open your apps configuration page from this list, click **OAuth & Permissions** in the left hand menu, then copy the Bot User OAuth Token. You will store this in your environment as `SLACK_BOT_TOKEN`. @@ -25,6 +28,7 @@ export SLACK_APP_TOKEN= ``` ### Setup Your Local Project + ```zsh # Clone this project onto your machine git clone https://github.com/slackapi/bolt-python-template.git @@ -44,6 +48,7 @@ python3 app.py ``` #### Linting + ```zsh # Run flake8 from root directory for linting flake8 *.py && flake8 listeners/ @@ -67,27 +72,33 @@ black . Every incoming request is routed to a "listener". Inside this directory, we group each listener based on the Slack Platform feature used, so `/listeners/shortcuts` handles incoming [Shortcuts](https://api.slack.com/interactivity/shortcuts) requests, `/listeners/views` handles [View submissions](https://api.slack.com/reference/interaction-payloads/views#view_submission) and so on. ### triggers + In order to run this project using the slack cli you must first set up triggers in your workspace. These triggers are defined in `manifest/triggers` folder, run the following command to add the defined one to your workspace + ```bash slack trigger create --trigger-def "./manifest/triggers/sample_trigger.json" ``` ### manifest + The `manifest/manifest.json` defines the behavior of your application, here are a vew helpful commands + ```bash slack manifest # view the compiled manifest slack manifest validate # to validate your manifest ``` ### run application + To start your application with the cli + ```bash slack run ``` -**NOTE:** you my create your triggers in your workspace before +**NOTE:** you my create your triggers in your workspace before ## App Distribution / OAuth @@ -95,20 +106,20 @@ Only implement OAuth if you plan to distribute your application across multiple When using OAuth, Slack requires a public URL where it can send requests. In this template app, we've used [`ngrok`](https://ngrok.com/download). Checkout [this guide](https://ngrok.com/docs#getting-started-expose) for setting it up. -Start `ngrok` to access the app on an external network and create a redirect URL for OAuth. +Start `ngrok` to access the app on an external network and create a redirect URL for OAuth. -``` +```bash ngrok http 3000 ``` This output should include a forwarding address for `http` and `https` (we'll use `https`). It should look something like the following: -``` +```bash Forwarding https://3cb89939.ngrok.io -> http://localhost:3000 ``` Navigate to **OAuth & Permissions** in your app configuration and click **Add a Redirect URL**. The redirect URL should be set to your `ngrok` forwarding address with the `slack/oauth_redirect` path appended. For example: -``` +```bash https://3cb89939.ngrok.io/slack/oauth_redirect -``` \ No newline at end of file +``` diff --git a/app.py b/app.py index 39f9c63..538f77a 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,5 @@ import os import logging -from copy import deepcopy from slack_bolt import App from slack_bolt.adapter.socket_mode import SocketModeHandler diff --git a/listeners/functions/sample_function.py b/listeners/functions/sample_function.py index 410a2da..5ec5038 100644 --- a/listeners/functions/sample_function.py +++ b/listeners/functions/sample_function.py @@ -5,11 +5,7 @@ def sample_function(event, complete: Complete, logger: Logger): try: message = event["inputs"]["message"] - complete( - outputs={ - "updatedMsg": f":wave: You submitted the following message: \n\n>{message}" - } - ) + complete(outputs={"updatedMsg": f":wave: You submitted the following message: \n\n>{message}"}) except Exception as e: logger.error(e) complete(error="Cannot submit the message") diff --git a/listeners/messages/sample_message.py b/listeners/messages/sample_message.py index 6c2e288..9f280e7 100644 --- a/listeners/messages/sample_message.py +++ b/listeners/messages/sample_message.py @@ -1,7 +1,6 @@ from logging import Logger from slack_bolt import BoltContext, Say -from slack_sdk import WebClient def sample_message_callback(context: BoltContext, say: Say, logger: Logger): From 4f0d20469b91616d049b18abeb9585f0e2b7ddf1 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 2 Nov 2022 14:04:04 -0400 Subject: [PATCH 22/25] Update project with new standards --- manifest/manifest.json => manifest.json | 1 + requirements.txt | 2 +- {manifest/triggers => triggers}/sample_trigger.json | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename manifest/manifest.json => manifest.json (97%) rename {manifest/triggers => triggers}/sample_trigger.json (100%) diff --git a/manifest/manifest.json b/manifest.json similarity index 97% rename from manifest/manifest.json rename to manifest.json index 77e8f04..697fdb6 100644 --- a/manifest/manifest.json +++ b/manifest.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/slackapi/manifest-schema/main/manifest.schema.json", "_metadata": { "major_version": 2, "minor_version": 2 diff --git a/requirements.txt b/requirements.txt index 11ad808..3db35e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -slack-bolt==1.15.0.dev0 +slack-bolt==1.15.2.dev0 pytest flake8==5.0.4 black==22.8.0 diff --git a/manifest/triggers/sample_trigger.json b/triggers/sample_trigger.json similarity index 100% rename from manifest/triggers/sample_trigger.json rename to triggers/sample_trigger.json From 844ad511a0e57efd0e94ecdf34a02f387a2f0bdd Mon Sep 17 00:00:00 2001 From: Ashley <12901850+hello-ashleyintech@users.noreply.github.com> Date: Tue, 24 Jan 2023 23:13:16 -0500 Subject: [PATCH 23/25] Update README to match standard structure (#17) --- README.md | 171 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 102 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 9feb049..bc11c6f 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,158 @@ -# Bolt for Python Template App -This is a generic Bolt for Python template app used to build out Slack apps. +# Bolt Python Starter Template + +This app contains a generic Bolt for Python template app used to build out Slack apps on Slack's +[next-generation platform](https://api.slack.com/future). Before getting started, make sure you have a development workspace where you have permissions to install apps. If you don’t have one setup, go ahead and [create one](https://slack.com/create). -## Installation +**Guide Outline**: -### Create a Slack App +- [Bolt Python Starter Template ](#bolt-python-starter-template) + - [Supported Workflows](#supported-workflows) + - [Setup](#setup) + - [Install the Slack CLI](#install-the-slack-cli) + - [Clone the Sample App](#clone-the-sample-app) + - [Linting](#linting) + - [Create a Link Trigger](#create-a-link-trigger) + - [Running Your Project Locally](#running-your-project-locally) + - [Project Structure](#project-structure) + - [`manifest.json`](#manifestjson) + - [`/triggers`](#triggers) + - [`slack.json`](#slackjson) + - [Resources](#resources) -1. Open [https://api.slack.com/apps/new](https://api.slack.com/apps/new) and choose "From an app manifest" -2. Choose the workspace you want to install the application to -3. Copy the contents of [manifest.json](./manifest.json) into the text box that says `*Paste your manifest code here*` (within the JSON tab) and click *Next* -4. Review the configuration and click *Create* -5. Click *Install to Workspace* and *Allow* on the screen that follows. You'll then be redirected to the App Configuration dashboard. +--- -### Environment Variables +## Supported Workflows -Before you can run the app, you'll need to store some environment variables. +- **Sample workflow**: Enter details to send a message to a channel -1. Open your apps configuration page from this list, click **OAuth & Permissions** in the left hand menu, then copy the Bot User OAuth Token. You will store this in your environment as `SLACK_BOT_TOKEN`. -2. Click ***Basic Information** from the left hand menu and follow the steps in the App-Level Tokens section to create an app-level token with the `connections:write` scope. Copy this token. You will store this in your environment as `SLACK_APP_TOKEN`. +## Setup -```zsh -# Replace with your app token and bot token -export SLACK_BOT_TOKEN= -export SLACK_APP_TOKEN= -``` +Before getting started, make sure you have a development workspace where you +have permissions to install apps. If you don’t have one set up, go ahead and +[create one](https://slack.com/create). Also, please note that the workspace +requires any of [the Slack paid plans](https://slack.com/pricing). + +### Install the Slack CLI + +To use this sample, you first need to install and configure the Slack CLI. +Step-by-step instructions can be found in our +[Quickstart Guide](https://api.slack.com/future/quickstart). + +### Clone the Sample App -### Setup Your Local Project +Start by cloning this repository: ```zsh # Clone this project onto your machine -git clone https://github.com/slackapi/bolt-python-template.git +$ slack create my-app -t slack-samples/bolt-python-starter-template -b future # Change into this project directory -cd bolt-python-starter-template +$ cd my-app # Setup your python virtual environment -python3 -m venv .venv -source .venv/bin/activate +$ python3 -m venv .venv +$ source .venv/bin/activate -# Install the dependencies -pip install -r requirements.txt - -# Start your local server -python3 app.py +# Install the project dependencies +$ pip install -r requirements.txt ``` #### Linting ```zsh # Run flake8 from root directory for linting -flake8 *.py && flake8 listeners/ +flake8 *.py && flake8 functions/ # Run black from root directory for code formatting black . ``` -## Project Structure +## Create a Link Trigger -### `manifest.json` +[Triggers](https://api.slack.com/future/triggers) are what cause workflows to +run. These triggers can be invoked by a user, or automatically as a response to +an event within Slack. -`manifest.json` is a configuration for Slack apps. With a manifest, you can create an app with a pre-defined configuration, or adjust the configuration of an existing app. +A [link trigger](https://api.slack.com/future/triggers/link) is a type of +trigger that generates a **Shortcut URL** which, when posted in a channel or +added as a bookmark, becomes a link. When clicked, the link trigger will run the +associated workflow. -### `app.py` +Link triggers are _unique to each installed version of your app_. This means +that Shortcut URLs will be different across each workspace, as well as between +[locally run](#running-your-project-locally). When creating a trigger, you must select +the Workspace that you'd like to create the trigger in. Each Workspace has a +development version (denoted by `(dev)`), as well as a deployed version. -`app.py` is the entry point for the application and is the file you'll run to start the server. This project aims to keep this file as thin as possible, primarily using it as a way to route inbound requests. +To create a link trigger for the sample workflow, run the following +command: -### `/listeners` +```zsh +slack trigger create --trigger-def triggers/sample_trigger.json +``` -Every incoming request is routed to a "listener". Inside this directory, we group each listener based on the Slack Platform feature used, so `/listeners/shortcuts` handles incoming [Shortcuts](https://api.slack.com/interactivity/shortcuts) requests, `/listeners/views` handles [View submissions](https://api.slack.com/reference/interaction-payloads/views#view_submission) and so on. +After selecting a Workspace, the output provided will include the link trigger +Shortcut URL. Copy and paste this URL into a channel as a message, or add it as +a bookmark in a channel of the Workspace you selected. -### triggers +**Note: this link won't run the workflow until the app is either running locally +or deployed!** Read on to learn how to run your app locally and eventually +deploy it to Slack hosting. -In order to run this project using the slack cli you must first set up triggers in your workspace. +## Running Your Project Locally -These triggers are defined in `manifest/triggers` folder, run the following command to add the defined one to your workspace +While building your app, you can see your changes propagated to your workspace +in real-time with `slack run`. In both the CLI and in Slack, you'll know an app +is the development version if the name has the string `(dev)` appended. -```bash -slack trigger create --trigger-def "./manifest/triggers/sample_trigger.json" +```zsh +# Run app locally +$ slack run + +⚡️ Bolt app is running! ⚡️ ``` -### manifest +Once running, click the +[previously created Shortcut URL](#create-a-link-trigger) associated with the +`(dev)` version of your app. This should start a workflow that opens a form used +to send a message to a certain channel! -The `manifest/manifest.json` defines the behavior of your application, here are a vew helpful commands +To stop running locally, press ` + C` to end the process. -```bash -slack manifest # view the compiled manifest -slack manifest validate # to validate your manifest -``` - -### run application +## Project Structure -To start your application with the cli +### `manifest.json` -```bash -slack run -``` +`manifest.json` is a configuration for Slack CLI apps in JSON. This file will +establish all basic configurations for your application, including app name +and description. -**NOTE:** you my create your triggers in your workspace before +Within the manifest are initializations for [workflows](https://api.slack.com/future/workflows) and [functions](https://api.slack.com/future/functions) are reusable building blocks +of automation that accept inputs, perform calculations, and provide outputs. +Functions can be used independently or as steps in workflows. -## App Distribution / OAuth +### `/triggers` -Only implement OAuth if you plan to distribute your application across multiple workspaces. A separate `app-oauth.py` file can be found with relevant OAuth settings. +All trigger configuration files live in here - for this example, +`sample_trigger.json` is the trigger config for a trigger that starts the workflow + initialized in `/manifest/manifest.json`. -When using OAuth, Slack requires a public URL where it can send requests. In this template app, we've used [`ngrok`](https://ngrok.com/download). Checkout [this guide](https://ngrok.com/docs#getting-started-expose) for setting it up. +### `slack.json` -Start `ngrok` to access the app on an external network and create a redirect URL for OAuth. +Used by the CLI to interact with the project's SDK dependencies. It contains +script hooks that are executed by the CLI and implemented by the SDK. -```bash -ngrok http 3000 -``` -This output should include a forwarding address for `http` and `https` (we'll use `https`). It should look something like the following: +## Resources -```bash -Forwarding https://3cb89939.ngrok.io -> http://localhost:3000 -``` +To learn more about developing with the CLI, you can visit the following guides: -Navigate to **OAuth & Permissions** in your app configuration and click **Add a Redirect URL**. The redirect URL should be set to your `ngrok` forwarding address with the `slack/oauth_redirect` path appended. For example: +- [Creating a new app with the CLI](https://api.slack.com/future/create) +- [Configuring your app](https://api.slack.com/future/manifest) +- [Developing locally](https://api.slack.com/future/run) -```bash -https://3cb89939.ngrok.io/slack/oauth_redirect -``` +To view all documentation and guides available, visit the +[Overview page](https://api.slack.com/future/overview). From bf870ef4a5657af5b1379fe0cb137e14ed3fcab9 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 15 Feb 2023 18:35:29 -0500 Subject: [PATCH 24/25] Add explicit function_runtime --- manifest.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 697fdb6..aaca8ae 100644 --- a/manifest.json +++ b/manifest.json @@ -55,7 +55,8 @@ }, "org_deploy_enabled": true, "socket_mode_enabled": true, - "token_rotation_enabled": false + "token_rotation_enabled": false, + "function_runtime": "remote" }, "functions": { "sample_function": { @@ -153,4 +154,4 @@ } }, "outgoing_domains": [] -} \ No newline at end of file +} From 59d8fb9a2aa3845bbde4d11d3360885b7831b760 Mon Sep 17 00:00:00 2001 From: Ashley <12901850+hello-ashleyintech@users.noreply.github.com> Date: Thu, 2 Mar 2023 15:23:51 -0500 Subject: [PATCH 25/25] Update README linter instructions (#18) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc11c6f..55ceca6 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ $ pip install -r requirements.txt ```zsh # Run flake8 from root directory for linting -flake8 *.py && flake8 functions/ +flake8 *.py && flake8 listeners/ # Run black from root directory for code formatting black .