How-to: Build Bolt API widgets

An Impac! Bolt API is a plug-in unit of computations, comprising of multiple Widgets (and KPIs), that are made available to Impac! front-end, via Impac! API. 

Impac! v1 was essentially a big Bolt. Impac! v2 is instead a router, which forwards requests to Bolts. This allows for the engines to be modularised, and to be created & hosted by third parties. 

This doc will guide you through setting up our first Bolt, the Impac! Finance Bolt, and get you started with how to build a new widget using the frameworks currently in place. 


1. Setting up the ecosystem for development

1.1. Configuring Connec! to send Webhooks to the Impac! bolt notifications router

Bolts work differently than Impac! v1 widget engines when it comes to data retrieval. In Impac! v1 (caching aside), data is retrieved directly from Connec! before being run through calculations and returned to the client. What’s different with a Bolt is that it has its own database, and when data is updated in Connec!, the Bolt can subscribe to webhooks to update its own database accordingly.

You can create an instance of Entity::Webhook in Connec! to register a service to receive webhooks when data is created or updated. You can set up the Bolt with Connec! by creating an Entity::Webhook in the Connec! rails console like below (where root keys are the SystemIdentity credentials): 

Create a webhook for your Bolt
Entity::Webhook.create!(name: 'Impac v2', endpoint: 'http://localhost:4000/api/v2/maestrano/finance/notifications', api_key: 'ROOT_KEY', api_secret: 'ROOT_SECRET', detailed_notifications: true, subscribed_entities: %w(Accounts Journals Company Invoices TimeActivities))


Alternatively, the Connec! webhooks can be managed via API, see doc: /wiki/spaces/DEV/pages/90439768.

Now your Bolt should be subscribed to Connec! webhooks and will receive all further data syncs.

1.2. Register the Impac! Finance Bolt with Impac!

Impac! API has a model for registering Bolts, similar to the Entity::Webhook model in Connec!. A Bolt can also be created via the rails console or API.

For the rails console, the snippet below demonstrates creating the Bolt & accessing the generated credentials.

b = Bolt.create(provider: 'maestrano', name: 'finance', protocol: 'http', host: 'localhost:4001', api_path: 'api/v1', subscribed_entities: %w(Accounts Journals Company Invoices TimeActivities))
b.api_key
# => 'your decrypted api key'
b.api_secret
# => 'your decrypted api secret'


The credentials you have generated above are used to authenticate Impac! with the Bolt, these will be needed in the next step (1.3).

 POST request to ‘localhost:4000/api/v2/bolts/’ using Basic Auth with your root (SystemIdentity) credentials. In the request body, choose “raw” and “json”, and paste the following:

Creating a bolt
{
    "bolts": {
     	"protocol": "http",
     	"provider": "maestrano",
     	"name": "finance",
     	“host": "localhost:4001",
     	"api_path": "api/v1"
   }
}

The api_key and api_secret should be in the response, but note that they are only returned when the bolt is created (after which they are encrypted for the database), and you would need the rails console to access them (like shown above).

1.3. Add Impac! credentials to the Impac! Finance Bolt to allow requests from Impac!

In the Impac! Finance Bolt’s config/application.yml file, add your bolt api key & secret generated in step 1.2.

Impac! Finance Bolt application.yml
IMPAC_KEY = 'bolt-api-key'
IMPAC_SECRET = 'bolt-api-secret'


1.4. Populating the Impac! Finance Bolt with data


Warning - Deprecated

This section is deprecated and should be updated - In the meantime the simplest way to populate the Bolt with data is to sync a Xero demo company after the bolt is registered in Impac!


If using a new organization, just create records with linked apps, and run a sync! Data will be pushed to the Bolt via Connec! & Impac! webhooks. 

If you are not using a new organization, note that as of right now, nothing has been implemented to make sure the Bolt is up to date with Connec!, so if the Bolt is linked up after data has already been synced with Connec!, it will miss out on the webhooks and will have nothing in its own database.

If you run into this issue, you can catch your Bolt up with your current database using the following:

With all of the components running on your machine, run the script below in the Connec! rails console, replacing the webhook_id with the id of the Entity::Webhook you just created, and the channel_id with your organization id:

Catching up your Bolt's database
channel_id = 'org-fbbj'
[Entity::Account, Entity::Company, Entity::Invoice, Entity::Journal].each do |klass|
 k = klass.to_s.demodulize.pluralize
 k = 'Company' if k == Entity::Company
 klass.on_channel(channel_id).all.each do |entity|
   args = {
     messages: [
       { channel_id: channel_id, entity: k, id: entity.id.to_s }.to_json
     ],
     webhook_id: "5989cc91251fe7107052547e"
   }
   Webhooks::Notifier.notify(args)
 end
end


2. Building your first Bolt Widget

The dashboard service in Impac! Angular fetches the widgets from the Impac! Finance Bolt with a request to /api/v2/maestrano/finance/widgets/.

In the Bolt, your first step is to add the name of your new widget to the WIDGETS_LIST so that it may be included in the response for this endpoint.

Add new widget to bolt widgets endpoint
class Api::V1::WidgetsController < ApplicationController
	...
	WIDGETS_LIST = %w(cash_balance cash_projection your_new_widget).freeze
	...
end

The next step is to create the model for your new widget and let it inherit the base widget class. Create this file in the /app/models/widgets/ directory.

New widget class
class Widgets::YourNewWidget < Widgets::Base
end

Before you start with the calculations, think about what you want your widget to display. Will your widget hold a chart? A grouped table? Both? Your new widget’s model will use data from the Bolt’s database to manipulate and run the calculations necessary for it’s requested report. The report output, however, is based on the design of your widget.

These displays (chart, table, grouped_table, etc) have reporting layouts, which are unique data structures that determine how a widget will return the result of its calculation. Layouts are predefined, and will maintain the same data structure regardless of any requested report.

The data structures have been designed with the intentions of being directly usable by a reporting front-end or charting library with little transformation.

Every report will be output to one or several layouts. You must define a SUPPORTED_LAYOUTS constant for each individual widget (an array of the layouts for your report). The supported layouts will be listed for each widget at the public endpoint (/api/v2/maestrano/finance/widgets/). 

Supported Layouts
class Widgets::YourNewWidget < Widgets::Base
	...
	SUPPORTED_LAYOUTS = %w(chart grouped_table).freeze
	...
end

A compute method must be defined for each widget, and it must return self. It fetches the report and calculates the widget. 

Compute Method
...
	def compute
		...
		return self
	end
...

The compute method will fetch the calculated data you need for your report, but you still have to fill your layout objects for rendering. Each layout has a class defining its structure, and can be filled with your report data by defining a fill method ("fill_#{layout.name}") which adheres to that structure. 

Layout class defines structure
class Layouts::GroupedTable < Layouts::Base
  attr_accessor :title, :headers, :groups

  def initialize
    self.headers = []
    self.groups = []
    super
  end
end


Fill Method in Your Widget's Class
...
	def fill_grouped_table
		my_items =	{
						"Type1" => [ item1, item2],
						"Type2" => [ item3, item4]
					}

		my_items.each do |header, group|
			layout.headers << header
			layout.groups << group
		end		
	end
...