SumOfUs / sumofus

0 stars 0 forks source link

gem install champaign-plugin #10

Closed osahyoun closed 9 years ago

osahyoun commented 9 years ago

ruby gem install champaign-call-representative

Important

This is probably so obvious it's not worth stating, but if all or parts of the framework I’m about to discuss should be agreed on, we'd only start with its development after we’ve shared with campaigners our first MVP as per the release spec outlined on pivotal ( i.e. a system where users can create and publish a petition page with formatted text, images, and a standard full name, address, email petition form.)


Before answering Neal's questions, I'd like to share an architecture I've been thinking a lot about since the weekend. When Eric mentioned yesterday his idea of having plugins, it became clear how best this design pattern could be encapsulated...

Champaign Plugins

Champaign plugins are a way to extend Champaign with extra features.

Out of the box, Champaign would have a core set of features. The thermometer would probably be in that set.

But for extending Champaign with new and wonderful functionality we'd provide a robust API for plugin development. Plugins would be developed and shared as ruby gems.

With this separation, organisations can be selective about the extra features they want to install, reducing code bloat and unnecessary functionality.

Also, plugins would allow developers to extend Champaign without having to commit directly to champaign’s codebase. This keeps Champaign focussed, lean and fast. It also lowers the bar for community contributions.

Specifics

A typical plugin would follow a directory structure, mirroring Rails’:

thermometer
|
|__ config.rb
|
|__ routes.rb
|
|__ en.yml
|
|__ assets
|   |__ stylesheets
|   |   |__ thermometer.scss.liquid
|   |
|   |__ javascripts
|       |__ thermometer.js
|
|__ views
|   |__ _thermometer.html.liquid
|
|__ models
|
|__ controllers
|
|_ migrations

The plugin would hook itself into Champaign via a config.rb file.

# Example configuration file.

Champaign::Plugins.register('thermometer') do |c|
  c.name 'Thermometer'

  c.description %{ Provides thermometer liquid partial with CSS animation. 
    Updates live, using Champaign's Campaign Page API }

  # Is the plugin toggable - can a campaigner switch it on or off from
  # the campaign page admin view?
  #
  c.toggable true

  # You can set any configurable params the plugin might depend on. These values
  # would then be editable on the admin campaign page view. They would also be stored on 
  # the campaign_page model.
  #
  c.config_parameters do |params|
    params.offset "integer"
    params.total  "integer"
  end
end

Typically a plugin would only provide a simple liquid template with some CSS and JavaScript behaviour. If it required any data, it would almost always access it via Champaign’s stable, well documented API. However, if the plugin’s behaviour is considerable, and can’t be fully served by our API, the developer can create additional models/controllers/migrations within the plugin’s namespace.

With a plugin installed, a campaigner can then extend their liquid layout with the plugin’s partial. Each plugin would come with detailed instructions on how to do this.

Here's an example template layout using the thermometer plugin:

<html>

{% include 'header' %}

<body>

  <div class='right-col-md-6'>
    Some <em>text</em>.
    {% if page.thermometer %}
      {% include 'thermometer' %}
    {% endif %}

  </div>

  {% include 'footer' %}  

</body>
</html>

Perhaps when our tech team has grown we can develop a WYSIWYG editor for editing liquid templates, allowing drop-in functionality, and completing hiding markup from the user.

Here’s what the liquid partial for the thermometer might look like:

<!-- All templates have access to a plugins object, which contains all the relevant data, conveniently namespaced. -->

<div class='thermometer' 
     data-offset='{{ plugins.thermometer.offset }}'
     data-start=' {{ plugins.thermometer.start  }}'>

</div>

I feel strongly that with an open system like champaign, it’s critical we expose the markup / css / and JS behaviour to the end user, and that’s why I think liquid is so important. But with plugins, templates will be out of reach, so that’s why our plugin API would provide a rails generate task for inserting the plugin’s assets into the database, so that they can be edited:

ruby rails generate champaign_plugins —copy_assets

I understand campaigners generally don’t want to see HTML, but organisations do want to customise the look and feel of their platform installation. With liquid templates, they can customise to the pixel.

With that framework in mind, I’ll now answer the set questions proffered for the call your representative action (I feel I've implicitly answered them already for the thermometer feature above):

EricBoersma commented 9 years ago

Great writeup!

I considered the gem option for plugins back at the start of the project, but there were a few major questions that I didn't feel like I could answer effectively. I'd be interested to hear your thoughts.

Positives, as I see them:

Questions that I think need to be answered to make this possible:

I think this is a pretty good proposal, and honestly I think it might be better than the existing plugin concept that I listed previously. I do think there are some questions which need to be answered before we can effectively jump in with both feet, though. It could be that the answers here are simple, and that we eschew the really difficult edge cases that would complicate things a lot (which I'm entirely in favor of, if that's what makes sense). I'd just like to see us talk through those edge cases thoroughly.

Thanks for doing this!

osahyoun commented 9 years ago

What happens when we need data to interact between different plugins? Is the dependency within the gem going to be enough to handle this?

I'm not totally sure what you mean here. I can see components within the UI wanting to talk to each other, and I think in this case we should present a PUB/SUB pattern of use that all components should respect, if they want to work with Champaign. At different points in its life cycle a component publishes an event. For example, when the petition form has been submitted, it would publish petition:submitted:started and then petition:submitted:success upon completion. Other components can then bind handlers, if they so choose, to those events:

$.on('petition:submitted:success', function(){ 
  showAmazingGif();
});

As part of our plugin toolset, we could offer a really lightweight JavaScript SDK.

For getting data it needs to operate, any plugin can access our comprehensive API.

Does that at least part answer your question?

How do we keep the gems DRY? Since they're going to be so distinct, and there's no guarantee of a gem being installed, how do we limit code repetition?

A gem should do a specific job - call your representative, etc. I'm not sure how there would be repetition. Most plugins' data needs would be met by a common champaign API. For boilerplate stuff, we could have a 'plugin gem' for most common abstractions, and other plugins would use.

How does this work with our 3-tier approach? Let's say a particular plugin needs to interface with an API - how does the processor code know how to do that? Does a person need to write two gems?

For external APIs the gem would manage this as any Rails app would. It can have its own classes and config files that communicate with whatever APIs it needs to. Not sure if I've answer your question here.

Additionally, how does a plugin get displayed on flute? Not sure about this point. Off the bat, I'd say it's the same as we currently do it. The liquid template is rendered and then saved to the DB.

NealJMD commented 9 years ago

What does a campaigner have to do to add one to the page? Paste a liquid {% include ‘call_representative’ %} tag into their layout template in the place they want it to appear in the flow of the document. They’d have to toggle in on when creating their campaign page.

What if a campaigner needs to add information (eg the blurb that they want the person to say, maybe some config if everyone should call one number like the FCC). does that go in the liquid tag, or can the plugin register a form? what if you want a dropdown of public officials? It seems a bit cumbersome to specify all the settings for a given widget inline in html

NealJMD commented 9 years ago

Also do you see a role for react in this? I was really liking components for widgets. Even if we don't require them, I would want to support them so we can write them.