Retort - a reaction-style plugin for Discourse

This is a definite need for me at least and I’d be super grateful to have the functionality.

You probably want post.publish_change_to_clients!

Not a problem. I enjoyed taking it on, as it taught me a bit more about these plugin changes and let me see some more “advanced” things a plugin can do, that I’ve yet to get into.

@riking, Do you know of a plugin where I can see that as an example? Or even within core. I’ll try to dig into it further tomorrow.

Babble does it here:
https://github.com/gdpelican/babble/blob/master/plugin.rb#L222

Retort is supposed to be doing it here:
https://github.com/gdpelican/retort/blob/master/plugin.rb#L69

The idea is to subscribe to the channel using MessageBus on the frontend:
https://github.com/gdpelican/retort/blob/master/assets/javascripts/discourse/initializers/topic.js.es6#L19

And then publish it to all the clients using MessageBus.publish on the backend (post.publish_change_to_clients! calls MessageBus.publish internally)

4 Likes

@cpradio I had to figure this out recently for the Ratings plugin. The flow is (references are to the relevant code in Retort):

  1. Send the updated post to the server. See

  2. Receive the post on the server. See

  3. Process the post on the server. See

  4. Send the result back to the clients. [See] (https://github.com/gdpelican/retort/blob/2938c6ead6097bc65f433863847974f0491230b6/plugin.rb#L69)

  5. Pick up the result in the client. See

@gdpelican has already set all this up so there’s probably a way to hook in to the existing flow. The post method publish_change_to_clients is defined in core here. As you can see, it basically uses the MessageBus to update the clients, which is what @gdpelican has already set up (see 4.).

To see another example of ‘5.’ i.e. picking up the updated post in the client, have a look at the topic controller here. You can see all the different cases in which the topic controller updates posts when it gets a message from the MessageBus that some action has taken place.

1 Like

Beat me to it :smile:

1 Like

So something else must have changed then? As all of the places listed above, I didn’t have to alter for vdom.

Maybe, this isn’t getting invoked anymore?
https://github.com/gdpelican/retort/blob/master/assets/javascripts/discourse/initializers/topic.js.es6#L19

Going to setup a debugger to see if that’s the case. As I know the other locations are invoking, as when you refresh the page, the data is there. So it does indeed get to the server, and is processed.

Ahh, I see. I think this is going to have to change, which sort of makes things complicated, as that would break older instances of Discourse (that are not using vdom).

The reason is, vdom seems to want something at the post level versus responding to a topic level message and refreshing all posts.

Does that sound right?

Actually, I’m wrong. So here is the problem:
The messageBus logic in Retort is working, but for whatever reason, vdom isn’t picking up on anything. How do I force vdom to pickup on the change? As the retort plugin is interacting with messageBus on a topic level. And vdom seems to be concerned with Post Level.

@cpradio I’m not sure if this is the problem you’re having, but make sure the subscription to the messageBus in the topic route is actually working properly.

Currently I’m trying to find the best hook or event to trigger the subscription on the client as it seems as though the setupTopicController event in the topic route actually gets fired before the topic controller subscription() method gets fired and that the controller subscription() method unsubscribes before it subscribes (see here). So (I think) it’s possible that the subscription in this plugin will get unsubscribed just after it is subscribed.

@gdpelican @riking thoughts?

Having a peek at this this evening. (Haven’t gotten as far as the message bus stuff yet, sorry.)

@cpradio 's written some code that looks like this:

    var html = '<div class="post-retorts">';
    post.retorts.forEach(function (item, index, enumerable){ /* do more html stuff */ }
    html += '</div>';
    return dec.rawHtml(html); 

…which (no offense to him!) is making me pretty sad. I know these days that templates all get bundled up and popped into javascript anyway, but I still would really rather have a way to separate my initializer code from my template, rather than string-building templates like Mom used to make, React/JSX be damned.

It also makes me a bit weary to think of porting all of the Babble views (they’re relatively extensive!) into a javascript initializer.

What would be super neat would be

api.decorateWidget('post-contents:after-cooked', 'templates/post_retorts.hbs')

Is there a way to do this that I’m missing @eviltrout?

EDIT: Ah, I’ve found the connect method after some source code diving, which seems to be what I’m looking for. Didn’t realize there were performance tradeoffs to be considered by rendering this way, so it’ll be interesting to see what works best for the plugins which have some somewhat extensive template code. :sunny:
https://github.com/discourse/discourse/blob/954ae7a08a6babbfccb6cefb7628df65d2bbbb40/app/assets/javascripts/discourse/widgets/decorator-helper.js.es6#L87

3 Likes

Ah, yeah, sorry, I didn’t look for a template way primarily because @eviltrout put a lot of emphasis on using rawHtml and the like, but it may be that converting it to a widget would be best? But there aren’t many examples of that “yet”. Although I do agree that I don’t really like the idea of building HTML to output, instead of extracting it out into a template.

(still trying to find where @eviltrout recommended the usage of rawHtml and similar methods, but not finding it)

I wouldn’t recommend rawHtml unless you absolutely need to, so I would be curious to see where I suggested that :slightly_smiling: Having said that it’s useful if you want to share some code with a previous version of the plugin that was already creating HTML by hand. It is slower due to overhead in jQuery parsing the HTML but it is probably not significant.

I’ll be writing up some documentation on the widget stuff today. Hopefully that will help people understand how to build stuff that will appear in the post stream.

1 Like

Okay, could be I misinterpretted something that was said. But I thought that was the encouraged way for interacting with the post stream. It was something along the lines of “if the plugin doesn’t have a lot of HTML to output building it within JavaScript is preferred and shouldn’t be overly complicated, but those with a lot of HTML output may need to use a widget” (or so I thought).

Oh right, I think I understand the confusion.

If you have a lot of pre-computed HTML from elsewhere, (for example the contents of a cooked post, or if you have an existing helper that already produces HTML), you can use rawHtml to wrap it. However, going forward if you want to build controls and widgets, the best way is via the widget system.

6 Likes

I believe last update screwed up something?

I, [2016-03-01T06:39:50.507171 #40]  INFO -- : > cd /var/www/discourse && su dis                                                                         course -c 'bundle exec rake db:migrate'
rake aborted!
SyntaxError: /var/www/discourse/plugins/retort/plugin.rb:93: syntax error, unexp                                                                         ected ')'
/var/www/discourse/plugins/retort/plugin.rb:99: syntax error, unexpected ')'
/var/www/discourse/plugins/retort/plugin.rb:103: syntax error, unexpected ','
    def self.find_by(post:, retort:)
                           ^
/var/www/discourse/plugins/retort/plugin.rb:105: syntax error, unexpected keywor                                                                         d_end, expecting end-of-input

FAILED
--------------------
RuntimeError: cd /var/www/discourse && su discourse -c 'bundle exec rake db:migr                                                                         ate' failed with return #<Process::Status: pid 19644 exit 1>
Location of failure: /pups/lib/pups/exec_command.rb:105:in `spawn'
exec failed with the params {"cd"=>"$home", "hook"=>"bundle_exec", "cmd"=>["su d                                                                         iscourse -c 'bundle install --deployment --verbose --without test --without deve                                                                         lopment'", "su discourse -c 'bundle exec rake db:migrate'", "su discourse -c 'bu                                                                         ndle exec rake assets:precompile'"]}
f012265c918390f5ecf8bfb950efa5cc52ce67ba23458bc3c5d08a93ffaedd93
** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages,                                                                          there may be more than one
1 Like

Seeing the same issue, yep

Retort is now causing rebuilds to fail.

1 Like

Ack, that syntax is valid as of ruby 2.1 (which was released in 2013…); is Discourse running all the way back on ruby 2.0 on rebuilds? :confused:

We are going to drop 2.0 after the next major release

At the moment we release on 2.0

2.1 and up have slightly higher memory reqs so we held off on the upgrade

5 Likes

Alright, I’ve updated the offending line and merged in the live update fixes.

Also it now does grouping, yay!
kPGsOqmzcd

9 Likes