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

Support SlackFunction and Function Localized Interactivity handling #1567

Merged
merged 16 commits into from Aug 31, 2022

Conversation

srajiang
Copy link
Member

@srajiang srajiang commented Aug 26, 2022

Todo:

  • Some failing CI/CD, but it looks like it's related to building the @slack/deno-slack-sdk types. And I don't see this issue when I npm install and build locally, so need to investigate Issue was due to breaking change that snuck it's way into a minor(?) version of typescript released on Friday. Pinning to v.4.7.4 resolved the issue for now. Long term we might want to revisit whether or not to keep the version of TS pinned.

Summary

Adds support for Function Localized Interactivity.

Adds a SlackFunction class

  • Configure a SlackFunction's callbackId, and any expected inputs and outputs in your project's manifest file (json or js).
  • Use this class to declare your handling logic and register on your app:
    Example:
       const myFunc = new SlackFunction('fn_callback_id', () => {});
       app.function(myFunc);
  • You can also declare optional handlers for block_action and view events related to your function.
    Example:
       myFunc.action('action_id', actionHandler).view('view_callback_id', viewHandler);
  • Note: app.function().action is not equivalent to app.action() or app.view(). Even though devs will write their interactivity handling logic similarly there are some differences including 1) Function context 2) complete() - ing one's function.

Completing your function

  • Call the supplied utility complete() in any handler with either outputs or an error or nothing when your function is done.

  • This tells Slack whether to proceed with any next steps in any workflow this function might be included in. Your outputs should match what is defined in your manifest.

    Example:

       const myFunc = new SlackFunction('fn_callback_id', ({ complete }) => {
         // do my work here
    
         complete() // or
         complete({ outputs: {} }); // or
         complete({ error: {} });
      });

Dev Experience

Try it out yourself: hermes create -t slack-samples/take-your-time -b initial-submission
Use this branch of bolt-js on this branch npm link, then in the sample project npm link @slack/bolt

  • Here's an example of a best-practice function file.

  • Note we can add out Slack Function interactivity handlers with constraints / regex
    Screen Shot 2022-08-26 at 2 29 45 PM

  • SlackFunction logging notifies devs of Function execution handler activity. This is non-optional to begin with.

function-handler-logging-devxp.mov
  • Handling throws and promise rejection
function-localized-error-handling.mov

Requirements (place an x in each [ ])

@srajiang srajiang changed the title Future function interactivity Support SlackFunction and Function Localized Interactivity handling Aug 26, 2022
@srajiang srajiang force-pushed the future-function-interactivity branch 3 times, most recently from 265a89f to c9981db Compare August 26, 2022 21:10
Copy link
Member Author

@srajiang srajiang left a comment

Choose a reason for hiding this comment

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

Left some notes for reviewers. Thanks in advance for your 👀 !

* Register subcription middleware
* Register WorkflowStep middleware
*
* Not to be confused with next-gen platform Workflows + Functions
Copy link
Member Author

Choose a reason for hiding this comment

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

Adds some documentation to indicate WorkflowStep is an unrelated legacy feature.

src/App.ts Outdated Show resolved Hide resolved
src/App.ts Outdated Show resolved Hide resolved
@@ -46,83 +113,412 @@ export class SlackFunction {
private callbackId: string;

/**
* @description fn to to process corresponding
* @description handler to to process corresponding
Copy link
Member Author

Choose a reason for hiding this comment

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

Deno Slack SDK is using the terminology "handlers" to I am being consistent with them.

if (manifestJS) {
manifest = merge(manifest, manifestJS, { arrayMerge: unionMerge });
}
let manifest = getManifestData(cwd);
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved the body of this logic out into a more generalized getManifestData() which can also be called from other parts of App or from SlackFunction.

@srajiang srajiang marked this pull request as ready for review August 26, 2022 21:45
@srajiang srajiang self-assigned this Aug 26, 2022
@srajiang srajiang added the enhancement M-T: A feature request for new functionality label Aug 26, 2022
@srajiang srajiang force-pushed the future-function-interactivity branch from c9981db to 73e0f1e Compare August 26, 2022 22:06
src/SlackFunction.ts Outdated Show resolved Hide resolved
src/SlackFunction.ts Show resolved Hide resolved
src/types/functions/index.ts Outdated Show resolved Hide resolved
@srajiang srajiang force-pushed the future-function-interactivity branch 2 times, most recently from d40ec6e to a32dd14 Compare August 26, 2022 23:11
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.

Tested out the app, happy path works great!

Sad path (i.e. introduce an exception in userland code) blows up the process. Left a comment in / near / around the code area. Not sure what we expect to happen in that situation.

Overall excellent work!

P.S. thanks for adding the JSDocs while you were at it 🙇

src/App.ts Outdated Show resolved Hide resolved
src/App.ts Show resolved Hide resolved
src/App.ts Show resolved Hide resolved
src/App.ts Outdated Show resolved Hide resolved
src/App.ts Show resolved Hide resolved
src/SlackFunction.ts Show resolved Hide resolved
this.logger.debug('🚀 Executing my main handler:', this.handler);
return await this.runHandler(args);
} catch (error) {
this.logger.error('⚠️ Something went wrong executing:', this.handler);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wanted to check what this log looks like but I couldn't trip it.

I edited the function body to throw an error, then this is the output I see:

[DEBUG]  socket-mode:SocketModeClient:0 Received a message on the WebSocket: {"envelope_id": ... very long payload here truncated}
[DEBUG]  socket-mode:SocketModeClient:0 Calling ack() - type: events_api, envelope_id: 523882e6-c036-43ba-8e7a-e5c15c7f7007, data: undefined
[DEBUG]  socket-mode:SocketModeClient:0 send() method was called in state: connected, state hierarchy: connected,ready
[DEBUG]  socket-mode:SocketModeClient:0 Sending a WebSocket message: {"envelope_id":"523882e6-c036-43ba-8e7a-e5c15c7f7007","payload":{}}
[DEBUG]  bolt-app initialized
[DEBUG]  bolt-app No conversation ID for incoming event
[DEBUG]  SlackFunction: [review_approval] 🚀 Executing my main handler: [AsyncFunction: notifyApprover]
/Users/fmaj/src/slapp-BoltJSHermes/listeners/functions/request-approval.js:12
  throw new Error('boomsies');
        ^

Error: boomsies
    at SlackFunction.notifyApprover [as handler] (/Users/fmaj/src/slapp-BoltJSHermes/listeners/functions/request-approval.js:12:9)
    at SlackFunction.runHandler (/Users/fmaj/src/bolt-js/dist/SlackFunction.js:60:18)
    at Array.<anonymous> (/Users/fmaj/src/bolt-js/dist/SlackFunction.js:184:39)
    at invokeMiddleware (/Users/fmaj/src/bolt-js/dist/middleware/process.js:12:53)
    at next (/Users/fmaj/src/bolt-js/dist/middleware/process.js:13:29)
    at Array.<anonymous> (/Users/fmaj/src/bolt-js/dist/conversation-store.js:67:15)
    at invokeMiddleware (/Users/fmaj/src/bolt-js/dist/middleware/process.js:12:53)
    at Object.next (/Users/fmaj/src/bolt-js/dist/middleware/process.js:13:29)
    at Array.<anonymous> (/Users/fmaj/src/bolt-js/dist/middleware/builtin.js:273:20)
    at invokeMiddleware (/Users/fmaj/src/bolt-js/dist/middleware/process.js:12:53)
bolt-app local run exited with code 1

Note also that this kills the entire process. Pretty weird as I would assume this try/catch block would catch it. Guess not. Is this expected? I would have expected to see "Something went wrong", and possibly also for the process not to die. Whatever the expectation is, let's make sure to add a test for this behaviour if we can.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, so the return await ing in the line you highlighted looks right, but I missed an await in runHandler, so that was immediately fulfilling with undefined, and then getting returned out all the way up, bypassing my well-intentioned catch block. I've simplified code little bit and I think it's much clearer now. Added a test for the case as well.

Screen Shot 2022-08-29 at 4 20 48 PM

src/SlackFunction.ts Outdated Show resolved Hide resolved
src/SlackFunction.ts Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Aug 29, 2022

Codecov Report

❗ No coverage uploaded for pull request base (future@b22a7fd). Click here to learn what that means.
The diff coverage is n/a.

@@            Coverage Diff            @@
##             future    #1567   +/-   ##
=========================================
  Coverage          ?   80.61%           
=========================================
  Files             ?       20           
  Lines             ?     1764           
  Branches          ?      502           
=========================================
  Hits              ?     1422           
  Misses            ?      221           
  Partials          ?      121           

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@@ -93,7 +93,7 @@
"source-map-support": "^0.5.12",
"ts-node": "^8.1.0",
"tsd": "^0.22.0",
"typescript": "^4.1.0"
"typescript": "4.7.4"
Copy link
Member Author

@srajiang srajiang Aug 29, 2022

Choose a reason for hiding this comment

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

Caret let 4.8.2 of typescript into the mix. This version has breaking changes, which were causing ts compiler complaints on npm install of @slack/deno-slack-sdk. Pinning to the latest compatible version.

Copy link
Member Author

@srajiang srajiang left a comment

Choose a reason for hiding this comment

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

Thanks @filmaj for the review! 🙇 I think I've resolved all of the inline comments!

src/App.ts Outdated Show resolved Hide resolved
src/App.ts Show resolved Hide resolved
src/App.ts Show resolved Hide resolved
src/App.ts Show resolved Hide resolved
src/SlackFunction.ts Show resolved Hide resolved
src/SlackFunction.ts Outdated Show resolved Hide resolved
src/SlackFunction.ts Outdated Show resolved Hide resolved
this.logger.debug('🚀 Executing my main handler:', this.handler);
return await this.runHandler(args);
} catch (error) {
this.logger.error('⚠️ Something went wrong executing:', this.handler);
Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, so the return await ing in the line you highlighted looks right, but I missed an await in runHandler, so that was immediately fulfilling with undefined, and then getting returned out all the way up, bypassing my well-intentioned catch block. I've simplified code little bit and I think it's much clearer now. Added a test for the case as well.

Screen Shot 2022-08-29 at 4 20 48 PM

@@ -1,4 +1,8 @@
/**
* Dialogs were a way to trigger interactions with users in messaging.
Copy link
Member Author

Choose a reason for hiding this comment

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

Left a note about this feature as being legacy (though not deprecated)

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.

Yeeeuhhhh epic work 🥇 :shipit:

@@ -55,7 +56,7 @@ export interface ViewSubmitAction {
view: ViewOutput;
api_app_id: string;
token: string;
trigger_id: string; // undocumented
trigger_id?: string; // undocumented
Copy link
Member Author

@srajiang srajiang Aug 30, 2022

Choose a reason for hiding this comment

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

trigger_id and token changes here as well as the similar change in BlockActions type is to reflect the fact that now in certain cases (aka when the payload is sent with FunctionContext, the trigger_id will NOT be included).

actionIdOrConstraints;

// declare our valid constraints keys
const validConstraintsKeys: ActionConstraintsKeys = ['action_id', 'block_id', 'callback_id', 'type'];
Copy link
Member Author

Choose a reason for hiding this comment

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

These constraints keys types are here to help us make sure we're never declaring validConstraintsKeys that are not corresponding to the underlying ActionConstraints or ViewsConstraints types.

@srajiang srajiang force-pushed the future-function-interactivity branch from e124960 to 9d6d22f Compare August 30, 2022 22:54
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.

Looks good to me 👍 Thanks a lot for working on this

@srajiang srajiang force-pushed the future-function-interactivity branch from 812f851 to de0b2c2 Compare August 31, 2022 16:46
@srajiang srajiang merged commit 1c7cff8 into future Aug 31, 2022
srajiang added a commit that referenced this pull request Sep 8, 2022
…1567)

* Support SlackFunction and Function Localized Interactivity handling
@srajiang srajiang deleted the future-function-interactivity branch September 9, 2022 17:53
srajiang added a commit that referenced this pull request Sep 9, 2022
…1567)

* Support SlackFunction and Function Localized Interactivity handling
srajiang added a commit that referenced this pull request Sep 9, 2022
…1567)

* Support SlackFunction and Function Localized Interactivity handling
srajiang added a commit that referenced this pull request Sep 13, 2022
…1567)

* Support SlackFunction and Function Localized Interactivity handling
srajiang added a commit that referenced this pull request Sep 28, 2022
…1567)

* Support SlackFunction and Function Localized Interactivity handling
hello-ashleyintech pushed a commit that referenced this pull request Oct 14, 2022
…1567)

* Support SlackFunction and Function Localized Interactivity handling
srajiang added a commit that referenced this pull request Nov 11, 2022
…1567)

* Support SlackFunction and Function Localized Interactivity handling
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement M-T: A feature request for new functionality semver:major
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants