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

Response middleware #1021

Merged
merged 12 commits into from
Nov 12, 2015
Merged

Response middleware #1021

merged 12 commits into from
Nov 12, 2015

Conversation

bhuga
Copy link
Contributor

@bhuga bhuga commented Aug 6, 2015

Hot on the heels of #1019, response middleware gives you the chance to edit outgoing messages.

It looks like this:

# Replace markdown links with slack's <url|text> format.
module.exports = (robot) ->
  robot.responseMiddleware (context, next, done) ->
    regex = /\[([^\[\]]*?)\]\((https?:\/\/.*?)\)/g

    context.string = context.string.replace(regex, "<$2|$1>")
    next(done)

This was referenced Aug 11, 2015
@technicalpickles
Copy link
Member

By calling robot.adapter.whatever multiple times, rather than passing it an array of strings..., I have a feeling this is going to break some things. The thing that comes to mind is calling response.send multiple times causing messages to come out in random order for the campfire adapter, where response.send with multiple arguments comes out in the correct order.

Ben Lavender added 3 commits August 31, 2015 10:06
@bhuga
Copy link
Contributor Author

bhuga commented Aug 31, 2015

Okay, with #1019 landed, I picked this back up.

By calling robot.adapter.whatever multiple times, rather than passing it an array of strings..., I have a feeling this is going to break some things.

I adjusted this to send the list of strings to each middleware instead of each string. It's slightly more cumbersome on the middleware, but 100% backwards compatible.

I also added some docs and a couple of tests. Ready for 👀 again.

@michaelansel
Copy link
Collaborator

We probably also need a suite of tests for message sending pre-middleware. Not much there right now to protect the existing state.

@bhuga
Copy link
Contributor Author

bhuga commented Sep 1, 2015

We probably also need a suite of tests for message sending pre-middleware. Not much there right now to protect the existing state.

This is tough, because hubot-mock-adapter does not actually obey the string, string, string, function() semantics (though now that I look at it, neither does the shell adapter). I'm afraid the different adapter behaviors make the current behavior not terribly well-defined.

@michaelansel
Copy link
Collaborator

😭 Okay, I guess we just leave the Response/Adapter interface as it is right now (a crazy mess) and put our hopes and dreams on #1036.

@bhuga
Copy link
Contributor Author

bhuga commented Sep 8, 2015

Okay, I guess we just leave the Response/Adapter interface as it is right now (a crazy mess) and put our hopes and dreams on #1036.

Singing songs of #1036...

Would this interfere with that? I don't think it would; can we move forward on this separately?

@michaelansel
Copy link
Collaborator

Yeah, I think this can move forward separately. As long as this is bound to the normal response interface (all the existing function calls), we can ensure backwards compatibility with all manner of shim layers in the core. It ain't gonna be pretty, but it does allow us to segment out the decision making.

@technicalpickles
Copy link
Member

As an author of response middleware, do you think you'd need to be able to target specific Response methods? For example, would you want to be modify topics vs send and reply differently? I suspect you would, so that would be a good thing to include in the context.

@bhuga
Copy link
Contributor Author

bhuga commented Sep 9, 2015

I suspect you would, so that would be a good thing to include in the context.

Really good point! Do you think just context.method would be okay?

@bhuga
Copy link
Contributor Author

bhuga commented Sep 10, 2015

I added context.method as the string representation of the method, so send, reply, etc.

@michaelansel
Copy link
Collaborator

How does this look after #1036 merges and we port all the existing methods over to a singular flow (similar to how Message encompasses all kinds of input)?

@michaelansel
Copy link
Collaborator

Edit: (completely forgot I had already commented on this and not refreshed. lolwhoops)

My first reaction is concern, but I think this will be okay when mixed in with #1036 as long as the context.method value doesn't change. This has a similar feel to #949 (using named object types instead of inheritance). Without spending time thinking through the long-term design of #1036, I'm not really sure how this will fit, but I think it is okay???

@bhuga, can you try to rough out what this would look like when merged with the ideas in #1036? (i.e. custom prompts, sendImage, sendSticker, etc.) Should we make the response message types more abstract? (e.g. send/reply are both text responses, just like hear/respond are both text listeners)

#michaelneedsmoretimeforthis 😭

@bhuga
Copy link
Contributor Author

bhuga commented Sep 15, 2015

@bhuga, can you try to rough out what this would look like when merged with the ideas in #1036? (i.e. custom prompts, sendImage, sendSticker, etc.) Should we make the response message types more abstract? (e.g. send/reply are both text responses, just like hear/respond are both text listeners)

I'll noodle on this. We're running into this sort of thing too, and it's plain that a pile of strings is not going to cut it.

We should also decide if the callback as the last argument to send and friends is something we support or not.

@bhuga
Copy link
Contributor Author

bhuga commented Sep 16, 2015

@bhuga, can you try to rough out what this would look like when merged with the ideas in #1036? (i.e. custom prompts, sendImage, sendSticker, etc.) Should we make the response message types more abstract? (e.g. send/reply are both text responses, just like hear/respond are both text listeners)

I thought on this some. I don't think we should make them more abstract. My term strings is wrong.

Different chat systems use structured data to handle messages. I don't think it's possible for a generic middleware to understand all of this structured data, or to provide an interface to make it "plaintext enough". If a script returns structured data, we pass that through to this middleware. So I would rename strings to, say, messages, but I think it's still fine to have a method and a list of data objects.

But I don't see #1036 coming up with something that wouldn't be able to fit that pattern. The method might become keyboard for telegram, and middleware might receive structured data and need to know how to handle it. But I think this response middleware interface would work.

@bhuga
Copy link
Contributor Author

bhuga commented Sep 23, 2015

Any objections to merging? Would love to get starting using this.

@technicalpickles
Copy link
Member

I was thinking about how to deal with adapter's having different maximum lengths of message they support sending, and it occurred to me that response middleware could be useful for that.

Currently, if you send a large message with most adapters, they silently truncate, or the service itself truncates on their behalf. As a developer of a hubot, you wouldn't know that. I was thinking one approach to that would be to have a response middleware that checks the length of a message, and it could do something like emit an error so the developers could track down what was generating large messages.

- `strings`
- An array of strings being sent to the chat room adapter. You can edit these, or use `context.strings = ["new strings"]` to replace them.
- `method`
- A string representing which type of response message the listener sent, such as `send`, `reply`, `emote` or `topic`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this instead be a message type instead of the method name? I think it is better to specify if message.type == text instead of `if message.type in ['send','reply'], but that depends on whether we want to allow differentiation between those types of messages... Maybe not worth worrying about?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good thought, I hadn't considered that.

Types might be better in some cases, but the edges are fuzzier. Internally we're abstracting away colors in slack attachments, for example, and I would want to consider a helper for that as "text". But is an emote text? Is a topic? What type would a slack attachment, a sent file, or telegram buttons be?

My first instinct is that type would be fuzzy for some of the more basic messages, and have a 1:1 correlation with advanced message formats in specific adapters. It's thus worse than send in the base case and gains no value in the adapter-specific case, instead being a piece of metadata boilerplate that custom message types require without much benefit.

But I'm not really sure. Can you think of any complex messages that could safely share the same type?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you're right here. I want there to be a way to group and abstract outgoing messages, but maybe it isn't so easy. I'm really just squeamish about seeing this everywhere: if message.type in ['send','reply']. Can you think of another way to make that extremely common case less gross?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm. message.plaintext? Adapters could set it alongside basic send and reply, and it arguably applies to topic and emote, too.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Missed this, but independently had the exact same idea. Thinking in the same style as Ruby message.plaintext? returns a boolean. No ? in CS though, so I like just having it as a "read-only" property. Thinking about applying the same idea to inbound messages to make robot.listen easier to work with (instead of needing to do message instanceof TextMessage every time)

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 adopted the context.plaintext pattern. See what you think. f0acd0f...0544029 has the diff from the last time you looked until now.

@MrSaints
Copy link
Contributor

I wrote a really simple, and hacky response middleware a while back.

I much prefer this PR though. I'm really glad this is being done, and hopefully it'll be merged soon 👍

@michaelansel
Copy link
Collaborator

👍 I like it. I think we can probably merge this.

@bhuga
Copy link
Contributor Author

bhuga commented Nov 12, 2015

The triumvirate of middleware is complete. Half life 3 confirmed.

bhuga pushed a commit that referenced this pull request Nov 12, 2015
@bhuga bhuga merged commit f8506cd into master Nov 12, 2015
@bhuga bhuga deleted the response-middleware branch November 12, 2015 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants