Skip to content
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

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft

Add support for remote functions #2026

wants to merge 10 commits into from

Conversation

misscoded
Copy link
Contributor

@misscoded misscoded commented Jan 8, 2024

⚠️ RC / In Beta. ⚠️

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 features complete and fail utilities to quickly and easily signal if the function has successfully executed or not.

app.function('sample_function', async ({ inputs, complete, fail }) => {
  try {
    const { sample_input } = inputs;
    complete({ outputs: { sample_output: sample_input } });
  } catch (error) {
    console.error(error);
    fail({ error });
  }
});

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 the complete and fail utility functions. Note: this requires that the interactivity call was made with the incoming JIT token. See below for details.

app.action('sample_button', async ({ ack, context, complete, fail }) => {
  await ack();
  const { functionExecutionId } = context;

  try {
    complete({ function_execution_id: functionExecutionId });
  } catch (error) {
    console.error(error);
    fail({ error });
  }
});

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 or fail). 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 the attachFunctionToken property in AppOptions.

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  socketMode: true,
  appToken: process.env.SLACK_APP_TOKEN,
  logLevel: LogLevel.DEBUG,
  // To opt-out of using the JIT token to make `client` calls in
  // function-related callbacks, set attachFunctionToken to false.
  // attachFunctionToken: false,
});

Outstanding / To Do

  • DRY-up complete/fail utility logic (App.ts / WorkflowFunction.ts)
  • Shore up TS (specifically, review events)
  • Tests

Requirements (place an x in each [ ])

@misscoded misscoded added enhancement M-T: A feature request for new functionality semver:minor labels Jan 8, 2024
Copy link
Contributor

@WilliamBergamin WilliamBergamin left a 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

src/WorkflowFunction.ts Outdated Show resolved Hide resolved
src/App.ts Outdated Show resolved Hide resolved
src/App.ts Outdated Show resolved Hide resolved
src/types/events/base-events.ts Outdated Show resolved Hide resolved
src/WorkflowFunction.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@filmaj filmaj left a 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/App.ts Outdated Show resolved Hide resolved
src/WorkflowFunction.ts Outdated Show resolved Hide resolved
src/WorkflowFunction.ts Outdated Show resolved Hide resolved
src/WorkflowFunction.ts Outdated Show resolved Hide resolved

function selectToken(context: Context): string | undefined {
// If attachFunctionToken = false, fallback to botToken or userToken
return context.functionBotToken ? context.functionBotToken : context.botToken || context.userToken;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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).

Copy link
Contributor Author

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?

Copy link
Contributor

@filmaj filmaj Jan 19, 2024

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.

Copy link
Member

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:

  1. we've never received such feature request in the last few years because most apps have their bot token
  2. 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.

Copy link
Contributor

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

src/WorkflowFunction.ts Outdated Show resolved Hide resolved
src/types/events/base-events.ts Show resolved Hide resolved
Copy link

codecov bot commented Jan 26, 2024

Codecov Report

Attention: 24 lines in your changes are missing coverage. Please review.

Comparison is base (d366ef2) 81.97% compared to head (11a42c0) 81.40%.

Files Patch % Lines
src/App.ts 23.80% 10 Missing and 6 partials ⚠️
src/CustomFunction.ts 86.20% 5 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2026      +/-   ##
==========================================
- Coverage   81.97%   81.40%   -0.58%     
==========================================
  Files          18       19       +1     
  Lines        1531     1613      +82     
  Branches      440      456      +16     
==========================================
+ Hits         1255     1313      +58     
- Misses        178      193      +15     
- Partials       98      107       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@seratch seratch left a 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


// Making calls with a functionBotAccessToken establishes continuity between
// a function_executed event and subsequent interactive events (actions)
const client = new WebClient(token, { ...functionArgs.client });
Copy link
Member

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)?

@@ -425,6 +426,42 @@ export interface FileUnsharedEvent {
event_ts: string;
}

export interface FunctionParams {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: here is my investigation result: https://github.com/seratch/slack-edge/blob/0.10.1/src/request/payload/event.ts#L420-L427

type, name, is_required cannot be optional for sure.

There are a few more optional ones such as maximum, minimum, maxLength, and minLength. But you don't need to cover all the things this time.

id: string;
callback_id: string;
title: string;
description: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: string;
description?: string;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla:signed enhancement M-T: A feature request for new functionality semver:minor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants