Skip to content

Commit ab96196

Browse files
committed
Add Workflow step decorator
1 parent 6ce7bdc commit ab96196

15 files changed

+2720
-94
lines changed

examples/async_steps_from_apps.py renamed to examples/workflow_steps/async_steps_from_apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# instead of slack_bolt in requirements.txt
33
import sys
44

5-
sys.path.insert(1, "..")
5+
sys.path.insert(1, "../..")
66
# ------------------------------------------------
77

88
import logging
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# ------------------------------------------------
2+
# instead of slack_bolt in requirements.txt
3+
import sys
4+
5+
sys.path.insert(1, "../..")
6+
# ------------------------------------------------
7+
8+
import asyncio
9+
import logging
10+
11+
from slack_sdk.web.async_client import AsyncSlackResponse, AsyncWebClient
12+
from slack_bolt.async_app import AsyncApp, AsyncAck
13+
from slack_bolt.workflows.step.async_step import (
14+
AsyncConfigure,
15+
AsyncUpdate,
16+
AsyncComplete,
17+
AsyncFail,
18+
AsyncWorkflowStep,
19+
)
20+
21+
logging.basicConfig(level=logging.DEBUG)
22+
23+
# export SLACK_SIGNING_SECRET=***
24+
# export SLACK_BOT_TOKEN=xoxb-***
25+
app = AsyncApp()
26+
27+
28+
# https://api.slack.com/tutorials/workflow-builder-steps
29+
30+
copy_review_step = AsyncWorkflowStep.builder("copy_review")
31+
32+
33+
@copy_review_step.edit
34+
async def edit(ack: AsyncAck, step: dict, configure: AsyncConfigure):
35+
await ack()
36+
await configure(
37+
blocks=[
38+
{
39+
"type": "section",
40+
"block_id": "intro-section",
41+
"text": {
42+
"type": "plain_text",
43+
"text": "Create a task in one of the listed projects. The link to the task and other details will be available as variable data in later steps.",
44+
},
45+
},
46+
{
47+
"type": "input",
48+
"block_id": "task_name_input",
49+
"element": {
50+
"type": "plain_text_input",
51+
"action_id": "task_name",
52+
"placeholder": {
53+
"type": "plain_text",
54+
"text": "Write a task name",
55+
},
56+
},
57+
"label": {"type": "plain_text", "text": "Task name"},
58+
},
59+
{
60+
"type": "input",
61+
"block_id": "task_description_input",
62+
"element": {
63+
"type": "plain_text_input",
64+
"action_id": "task_description",
65+
"placeholder": {
66+
"type": "plain_text",
67+
"text": "Write a description for your task",
68+
},
69+
},
70+
"label": {"type": "plain_text", "text": "Task description"},
71+
},
72+
{
73+
"type": "input",
74+
"block_id": "task_author_input",
75+
"element": {
76+
"type": "plain_text_input",
77+
"action_id": "task_author",
78+
"placeholder": {
79+
"type": "plain_text",
80+
"text": "Write a task name",
81+
},
82+
},
83+
"label": {"type": "plain_text", "text": "Task author"},
84+
},
85+
]
86+
)
87+
88+
89+
@copy_review_step.save
90+
async def save(ack: AsyncAck, view: dict, update: AsyncUpdate):
91+
state_values = view["state"]["values"]
92+
await update(
93+
inputs={
94+
"taskName": {
95+
"value": state_values["task_name_input"]["task_name"]["value"],
96+
},
97+
"taskDescription": {
98+
"value": state_values["task_description_input"]["task_description"][
99+
"value"
100+
],
101+
},
102+
"taskAuthorEmail": {
103+
"value": state_values["task_author_input"]["task_author"]["value"],
104+
},
105+
},
106+
outputs=[
107+
{
108+
"name": "taskName",
109+
"type": "text",
110+
"label": "Task Name",
111+
},
112+
{
113+
"name": "taskDescription",
114+
"type": "text",
115+
"label": "Task Description",
116+
},
117+
{
118+
"name": "taskAuthorEmail",
119+
"type": "text",
120+
"label": "Task Author Email",
121+
},
122+
],
123+
)
124+
await ack()
125+
126+
127+
pseudo_database = {}
128+
129+
130+
async def additional_matcher(step):
131+
email = str(step.get("inputs", {}).get("taskAuthorEmail"))
132+
if "@" not in email:
133+
return False
134+
return True
135+
136+
137+
async def noop_middleware(next):
138+
return await next()
139+
140+
141+
async def notify_execution(client: AsyncWebClient, step: dict):
142+
await asyncio.sleep(5)
143+
await client.chat_postMessage(
144+
channel="#random", text=f"Step execution: ```{step}```"
145+
)
146+
147+
148+
@copy_review_step.execute(
149+
matchers=[additional_matcher],
150+
middleware=[noop_middleware],
151+
lazy=[notify_execution],
152+
)
153+
async def execute(
154+
step: dict, client: AsyncWebClient, complete: AsyncComplete, fail: AsyncFail
155+
):
156+
try:
157+
await complete(
158+
outputs={
159+
"taskName": step["inputs"]["taskName"]["value"],
160+
"taskDescription": step["inputs"]["taskDescription"]["value"],
161+
"taskAuthorEmail": step["inputs"]["taskAuthorEmail"]["value"],
162+
}
163+
)
164+
user_lookup: AsyncSlackResponse = await client.users_lookupByEmail(
165+
email=step["inputs"]["taskAuthorEmail"]["value"]
166+
)
167+
user_id = user_lookup["user"]["id"]
168+
new_task = {
169+
"task_name": step["inputs"]["taskName"]["value"],
170+
"task_description": step["inputs"]["taskDescription"]["value"],
171+
}
172+
tasks = pseudo_database.get(user_id, [])
173+
tasks.append(new_task)
174+
pseudo_database[user_id] = tasks
175+
176+
blocks = []
177+
for task in tasks:
178+
blocks.append(
179+
{
180+
"type": "section",
181+
"text": {"type": "plain_text", "text": task["task_name"]},
182+
}
183+
)
184+
blocks.append({"type": "divider"})
185+
186+
await client.views_publish(
187+
user_id=user_id,
188+
view={
189+
"type": "home",
190+
"title": {"type": "plain_text", "text": "Your tasks!"},
191+
"blocks": blocks,
192+
},
193+
)
194+
except Exception as err:
195+
await fail(error={"message": f"Something wrong! {err}"})
196+
197+
198+
app.step(copy_review_step)
199+
200+
if __name__ == "__main__":
201+
app.start(3000) # POST http://localhost:3000/slack/events

examples/async_steps_from_apps_primitive.py renamed to examples/workflow_steps/async_steps_from_apps_primitive.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# instead of slack_bolt in requirements.txt
33
import sys
44

5-
sys.path.insert(1, "..")
5+
sys.path.insert(1, "../..")
66
# ------------------------------------------------
77

88
import logging

examples/steps_from_apps.py renamed to examples/workflow_steps/steps_from_apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# instead of slack_bolt in requirements.txt
33
import sys
44

5-
sys.path.insert(1, "..")
5+
sys.path.insert(1, "../..")
66
# ------------------------------------------------
77

88
import logging

0 commit comments

Comments
 (0)