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

Convert say, ack, and respond to return promises #369

Merged
merged 6 commits into from
Feb 3, 2020

Conversation

barlock
Copy link
Contributor

@barlock barlock commented Jan 10, 2020

Summary

Makes say, ack, and respond return promises. Per discussion in #353

I possibly changed a few things from the discussion above, I'll leave comments.

Requirements (place an x in each [ ])

@claassistantio
Copy link

claassistantio commented Jan 10, 2020

CLA assistant check
All committers have signed the CLA.

@@ -29,7 +29,7 @@
"build": "tsc",
"build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output",
"lint": "tslint --project .",
"test-lint": "tslint \"src/**/*.spec.ts\" && tslint \"src/test-helpers.ts\"",
"test-lint": "tslint --project tsconfig.test.json \"src/**/*.spec.ts\" && tslint --project tsconfig.test.json \"src/test-helpers.ts\"",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to be able to lint the test cases for proper async handling, that requires types in tslint, so I made a config specifically for test.

Copy link
Member

Choose a reason for hiding this comment

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

👍

src/App.ts Outdated
const postMessageArguments: ChatPostMessageArguments = (typeof message === 'string') ?
{ token, text: message, channel: channelId } : { ...message, token, channel: channelId };
this.client.chat.postMessage(postMessageArguments)
return this.client.chat.postMessage(postMessageArguments)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought this made more sense resolving the result of this call rather than void.

Enables folks who want to the ability to get things like the ts off the result for later updating.

@codecov
Copy link

codecov bot commented Jan 10, 2020

Codecov Report

Merging #369 into master will increase coverage by 0.26%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #369      +/-   ##
==========================================
+ Coverage   83.29%   83.56%   +0.26%     
==========================================
  Files           7        7              
  Lines         497      505       +8     
  Branches      143      145       +2     
==========================================
+ Hits          414      422       +8     
  Misses         54       54              
  Partials       29       29
Impacted Files Coverage Δ
src/ExpressReceiver.ts 72.38% <100%> (+2.27%) ⬆️
src/App.ts 87.35% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8469052...7735013. Read the comment docs.

@@ -82,7 +82,7 @@ export default class ExpressReceiver extends EventEmitter implements Receiver {
);
const event: ReceiverEvent = {
body: req.body as { [key: string]: any },
ack: (response: any): void => {
ack: async (response: any): Promise<void> => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Express's res.send is actually synchronous and won't ever error. I believe this still makes sense to force the signature to a promise for other types of receivers.

this.emit('error', e);
});
event.respond = async (response) => {
await this.axios.post(req.body.response_url, response);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the catch so this will behave more like promises would expect. Left unchecked in the new middleware pattern, it will be bubbled up to the global error handler anyway I figured this was safe and more expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update: I'm going to update this pr to move this block into onIncommingEvent so that every receiver doesn't need to implement it.

Incoming shortly.

@stevengill stevengill added the enhancement M-T: A feature request for new functionality label Jan 10, 2020
// it’s a valid email, accept the submission
if (isEmail.test(action.submission.email)) {
ack();
await ack();
} else {
// if it isn’t a valid email, acknowledge with an error
ack({
Copy link
Member

Choose a reason for hiding this comment

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

nit: Is it intentional not to have await here?

Copy link
Contributor Author

@barlock barlock Jan 15, 2020

Choose a reason for hiding this comment

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

🤦‍♂ Not a nit, great catch. After missing this, I took another pass searching for these and updated a few more 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If only we could lint js inside markdown.

// メールアドレスが有効。ダイアログを受信
if (isEmail.test(action.submission.email)) {
ack();
await ack();
} else {
// メールアドレスが無効。エラーを確認
ack({
Copy link
Member

Choose a reason for hiding this comment

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

(same as above)

@@ -29,7 +29,7 @@
"build": "tsc",
"build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output",
"lint": "tslint --project .",
"test-lint": "tslint \"src/**/*.spec.ts\" && tslint \"src/test-helpers.ts\"",
"test-lint": "tslint --project tsconfig.test.json \"src/**/*.spec.ts\" && tslint --project tsconfig.test.json \"src/test-helpers.ts\"",
Copy link
Member

Choose a reason for hiding this comment

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

👍

src/App.ts Outdated
@@ -400,10 +418,10 @@ export default class App {
// Factory for say() utility
const createSay = (channelId: string): SayFn => {
const token = context.botToken !== undefined ? context.botToken : context.userToken;
return (message: Parameters<SayFn>[0]) => {
return (message: Parameters<SayFn>[0]): Promise<void | WebAPICallResult> => {
Copy link
Member

Choose a reason for hiding this comment

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

I'm still thinking but it may be better to return Promise<WebAPICallResult | CodedError> here even after applying onGlobalError. Any thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I clearly wasn't thinking straight here, I removed the global error handler as it will be called anyway if this goes uncaught.

This means that this returns what chat.postMessage is which is simply Promise<WebAPICallResult>

I just checked on CodedError, looks cool, I would think the right thing to do would be to update @slack/web-api to handle that rather than bolt. That way you have a consistent api between say and client.chat.postMessage What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

🙏 for the late reply here: I agree that we can remove the global error handler here to be consistent with others such as app.client calls and respond 👍

src/App.ts Outdated
listenerArgs.respond = respond;
if (body.response_url) {
listenerArgs.respond = async (response): Promise<any> => {
const postResponse = await this.axios.post(body.response_url, response);
Copy link
Member

Choose a reason for hiding this comment

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

Related to above, respond does await here. This means respond may return a failure while the current say doesn't. Also, should we be consistent about when to apply App's global error handler?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome catch, yes, I meant to remove the global handler catch from say as well. I'll update that.

@barlock
Copy link
Contributor Author

barlock commented Jan 15, 2020

Updated, added new commit 8469052

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.

This PR looks great to me. Just in case, could you take a look at this? @aoberoi @stevengill

@barlock barlock mentioned this pull request Jan 17, 2020
2 tasks
@barlock
Copy link
Contributor Author

barlock commented Jan 22, 2020

@aoberoi @stevengill Sorry for the bump, but it's been a while.

I have the one pr stacked on this one (#375) and another branch in progress. It would be great to start getting feedback soon or merge this so I don't have to keep merging up!

@@ -761,30 +814,6 @@ describe('App', () => {
// Assert
assert.equal(assertionAggregator.callCount, dummyReceiverEvents.length);
});

it('should handle failures through the App\'s global error handler', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe this is failing b/c the global handler doesn't know how to handle thrown errors (yet). I'm adding this test back in a later pr. Will link it when it's up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fakeReceiver.emit('message', { // IncomingEventType.Action (app.action)
body: {
type: 'block_actions',
response_url: responseUrl,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: block_actions type events will never have a response_url. not a big deal since the functionality that's being tested is independent of the actual event type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh! didn't know, the types lists it as a valid response. https://github.com/slackapi/bolt/blob/master/src/types/actions/block-action.ts#L220

I tend to not use response_urls in favor of actual actions, so I'm not always sure which ones do.

Is that a bug then?

@aoberoi
Copy link
Contributor

aoberoi commented Feb 3, 2020

@barlock this is amazing work! i'm sorry my review took longer than i had initially hoped. you were super thorough and i appreciate that. i'm landing this now, and moving on to review the other PRs you've sent.

@aoberoi
Copy link
Contributor

aoberoi commented Feb 3, 2020

Notes for producing the changelog:

  • Implements changes to say(), ack(), and respond() from Proposal: Chaining middleware using Promises #353
  • say() returns a Promise<WebAPICallResult>
  • Receivers no longer need to implement respond() for incoming messages. Now a Receiver can be implemented without importing yet another HTTP client.

@aoberoi aoberoi merged commit 5881471 into slackapi:@slack/bolt@next Feb 3, 2020
@barlock barlock deleted the receiver-promises branch February 3, 2020 17:26
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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants