reopenWidget with AJAX call

This is a topic that’s come up several times and stalled out (usually because the dev doesn’t actually require the AJAX call). However, I’m developing a plugin that accesses data not stored within Discourse’s database (self contained Discord bot) and I’m trying to use that data to decorate posts.

export default Component.extend({
init() {
this._super(…arguments);

var self = this;
const store = getOwner(this).lookup("service:store");

  let positions = [];

store
  .findAll("position", {
    ...args,
  })
  .then((data) => {

    for (const [key, position] of Object.entries(data.content)) {
      positions.pushObject(EmberObject.create({ ...position }));
    }
  });

withPluginApi("1.4.0", (api) => {
  api.reopenWidget("post", {
    html(attrs) {
      let position = positions.filter(
        (position) => position.user.id == attrs.user.id
      );

      if (position.length > 0) {
        attrs.cooked = "Has Position";
      }

      return this.attach("post-article", attrs);
    },
  });
});

},
});

Now I’ve tried different variations of this code, including the api function call in the .then portion of the store query and so on and so forth and they all wind up the same which is not rendering the changes to the posts until you scroll around.

All these attempts were done late at night so it’s definitely likely I’m missing something super obvious, however, any help would be greatly appreciated.

Depends what you are trying to do I suppose but why not use the official Discord Ruby API to get the data into the Discourse db, serialize it to the client and then use it in the widget?

Seemed like unneeded complexity when the primary applications API is good enough for most things but I could be misunderstanding the implementation as admittedly I’ll need to study the Ruby side of discourse a bit more.

Although if I can’t get post decorating to work I’ll likely have to go that route.

Circling back around to give thanks to @merefield I looked more into serializing and was able to accomplish the task by making the API call in the serializer instead of requesting it from JS as I was doing.

I’m sure there are a plethora of reasons why this isn’t suggested but it works for me so I’m running with it, for those finding this in the future, you can slide data into a serializer with the following code:

add_to_serializer(:topic_view, :data_name) do
    JSON.parse(Net::HTTP.get(URI('https://yourwebsite.com?topic_id=' + object.topic.id.to_s)))
end

Which then allows you to access the data within a call to reopenWidget. In my case I’m tacking the data pertinent to the topic onto the topic_view serializer and accessing it while modifying the post like so:

api.reopenWidget("post", {
          html(attrs) {
            let data_name = this.model.topic.data_name;

            // Do stuff with data

            return this.attach("post-article", attrs);
          },
        });

As I said before there’s probably a ton of reasons this isn’t the right way to go about it but it is working for my use case with minimal impact on load times.

For those looking to accomplish the task within JS for whatever reason, pertinent code seems to exist in the discourse-encrypt plugin where they decrypt posts, but I found this method far easier to implement: discourse-encrypt/decrypt-posts.js at 255724ebc5fc3956f26beca09c1f7cb273d76eb2 · discourse/discourse-encrypt · GitHub

Weirdly this seems to break the Topic Thumbnails theme component by making the images supersized GitHub - discourse/discourse-topic-thumbnails: Display thumbnails in topic lists

Truly a strange interaction, nothing else noticeable however.

Hmmm … I’m not sure that is architecturally sound. Whilst it’s great you are exploring a back-end solution (remote server calls should almost always happen between processes on the back end especially where authentication and authorization are involved), the data should arguably load asynchronously, and the serializer should not rely on a remote call to finish to conclude. At the very least you should wrap that call in a cache?

See how you go, but pay close attention to the timing of loading the page before and after that change, you may be introducing a significant delay.

In practice it’s adding between 10-50ms per load if that. The connection is super low latency as both boxes exist in the same datacenter. In fact, the method I’m using to pull in data to components loads pages marginally faster than Discourse rendering it’s own static pages (difference of about 100ms) and that is including a DB operation that uses Discord ID’s to add User data into the API responses before they’re forwarded to the client. (Ruby requests data from External Application → Parse JSON → DB query.each loop to append data to response-> Generate JSON and return to frontend client). It’s understandable considering how large the codebase is but there is definitely an overhead internally that makes room for these requests that are traditionally seen as “expensive” to go completely unnoticed. With that said, we’re talking about the speed of light and sound here which to the end user is still incredibly fast as Discourse is an amazing platform.

A cache would be a great addition and will be something I look into, very new to Ruby so I’ll update as I learn more about the available functions. I’m going to update this as I go forward because I think some sort of method for this, albeit hacky at the moment, could be potentially very beneficial to the plugin development side. For people with existing applications, being able to use the API they know to slide the data they need where they need it efficiently without having to build out systems to sync data between their database and Discourse or mess around with additional models or db migrations drastically cuts down on development time and keeps plugin authors in some of the better documented areas of the application (i.e. the Plugin API).

I greatly appreciate the insight, definitely excited to continue to learn more about this platform and hopefully contribute in the future.

3 Likes