-
Notifications
You must be signed in to change notification settings - Fork 393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for remote functions #2026
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome 💯 first pass everything looks good! I left some comments on naming alignment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
One higher-level API that bolt needs to consider (and that the deno SDK does not) is token selection when creating the helper API client and other convenience utility functions like say
. Some bolt apps implement OAuth and ask for user scopes, so a user token may come into play. While the function complete/fail helpers should always use the JIT / workflow / function-execution-specific token, that is not so clear for say
or for the general API client
available in certain event handlers. We need to think about how to expose the option of which token to use in these situations.
src/WorkflowFunction.ts
Outdated
|
||
function selectToken(context: Context): string | undefined { | ||
// If attachFunctionToken = false, fallback to botToken or userToken | ||
return context.functionBotToken ? context.functionBotToken : context.botToken || context.userToken; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To @WilliamBergamin's earlier point, this area may need to be exposed as an API to the dev. Here we are making an assumption that for non-function-scoped events, the dev will want to use a bot token over a user token - that will not always be the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how WorkflowStep
was implemented, so merely a carry over. That was long ago, but is there a reality where an app has both present -- a bot token and a user token? In my mind, I've always thought of it as one or the other (either it's a bot, or an app acting on behalf of the user), but you bringing this up makes me think that's a faulty mental model.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, an app can ask for scopes of both types (if you head to the OAuth & Permissions app config page, you can subscribe to both).
Just this week I was helping someone who was using a user token for one API (uploading files) but a bot token in another (posting a message containing the uploaded file).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, for any event that currently comes in, when we run selectToken
when that event is processed, we opt for botToken
first, followed by userToken
. I think it's always been this way, or has for the last 4+ years.
@seratch your name is attached to one of those implementation lines. Can you advise here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've always found this area to be poorly documented on our part and glossed over. For example bolt-js only mentions user scopes if you REALLY dig deep: you gotta scroll to the end of the OAuth docs, expand the hidden "Customizing OAuth options" section and then piece together the pieces based on the example and the one-line description of the userScopes
field.
I believe, but may be wrong on this, that this has always been a userland concern that we don't provide guidance on. Like, for an app that is installable on-demand to workspace (implements OAuth) and collects both bot and user tokens, I'm pretty sure it is up to the installation store implementation (so userland code) to both: figure out which token to store as well as which token to select and return when doing the 'authorization' process that bolt exposes APIs for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I overlooked this thread last week. The logic to select a primary token for context object from bot / user tokens is by design from the beginning, and all Bolt frameworks align.
We basically encourage developers to have a bot token, but technically it still should be allowed to go with only a user token (I don't think this is a common use case though). Thus, when a bot token is missing, setting a user token instead is a reassonable design for supporting such a scenario.
I don't think bolt should expose a way to customize this because:
- we've never received such feature request in the last few years because most apps have their bot token
- developers still can accesss both a bot and user token
As for the documentation topic, indeed this could be improved in the advanced topic section although the number of devs who need it is not so large.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK good to know and thanks for that context Kaz 🙇
If Kaz says so then I defer to his suggestion / feel free to ignore my point
c63f1f4
to
1b79873
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2026 +/- ##
=========================================
+ Coverage 0 81.59% +81.59%
=========================================
Files 0 19 +19
Lines 0 1646 +1646
Branches 0 464 +464
=========================================
+ Hits 0 1343 +1343
- Misses 0 194 +194
- Partials 0 109 +109 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Left a few comments
src/CustomFunction.ts
Outdated
|
||
// Making calls with a functionBotAccessToken establishes continuity between | ||
// a function_executed event and subsequent interactive events (actions) | ||
const client = new WebClient(token, { ...functionArgs.client }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first time to see this way to initialize a new instance. Other code in bolt-js uses clientOptions for the second argument instead. I haven't verified on my end yet but have you confirmed this code can make the same effect with new WebClient(token, clientOptions)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm 99% sure I tested this when you brought it up as a concern back in January, but we will make doubly-sure before we cut the stable release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to take a slightly different tact here: expose the web client options as assembled in the bolt App constructor via a getter method, and simply re-use those here instead.
src/types/events/base-events.ts
Outdated
id: string; | ||
callback_id: string; | ||
title: string; | ||
description: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
description: string; | |
description?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Also included in pre-stable-fixes branch.
* 1. removes the next() passed in from App-level middleware processing | ||
* - events will *not* continue down global middleware chain to subsequent listeners |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Is removing this needed for correct function handling? I can imagine adding middleware after the function listener handler to do something specific to functions after some response like so:
app.function("example_callback", filterInputs, async ({ inputs, complete, next }) => {
await complete({ outputs: inputs });
await next();
}, saveResponseToDatabase);
It's not super clear that this is possible in docs - looking into this now - but I'm finding that listener middleware can go in any order, unrelated to the main listener handler.
Things do get interesting if functionBotAccessToken: true
is used in that example though since the attached xwfp
token
is revoked soon after complete
, meaning saveResponseToDatabase
will get a token_revoked
error if it tries using it...
But I think that error is better than swapping for the original bot
or user
token too since those tokens can be found again. I'm curious about the middleware handling most though! Leaning towards having processMiddleware
handle callbacks and middleware without change if that's possible 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global middleware should also run before these listeners too, but app.error
might catch thrown errors if that happens. Thinking this is all fine as is!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior works as expected, and is modeled off of the original WorkflowStep
class. next
is still available for developers to use within the callback in the case of additional middleware that they've included, but it's removed here so as not to allow the event to fall further down.
Admittedly, this is overkill, as WorkflowStep
featured events that could be handled by more than one handler (ie, view_submission
). We don't have a downside of keeping it, and it makes it extensible if and when there is overlap, but if folks feel strongly about it, we can remove it (though it will need to be re-tested).
Co-authored-by: Fil Maj <maj.fil@gmail.com>
Summary
This PR adds support for remote (i.e., remotely hosted) functions for use in Workflow Builder.
What's New
A shiny new listener:
.function()
Support of remote functions brings a new
.function()
listener that featurescomplete
andfail
utilities to quickly and easily signal if the function has successfully executed or not.Function-specific interactivity support
We've beefed up the existing
action
listener to automatically determine if the action/interactivity event being received is associated with a function. If so, the callback will make available thecomplete
andfail
utility functions. Note: this requires that the interactivity call was made with the incoming JIT token. See below for details.Automatic use of JIT token (with opt-out option)
When a function-related event is received, a JIT token is included in the payload. The subsequent use of this token when making API calls is what allows for interactivity to be associated with that function (and eventually see the function through to
complete
orfail
). Out of respect for all of our developers and their individual setups, we've provided an easy way to opt-out of this JIT token attachment with theattachFunctionToken
property inAppOptions
.Outstanding / To Do
complete
/fail
utility logic (App.ts
/WorkflowFunction.ts
)Requirements (place an
x
in each[ ]
)