Skip to content
This repository has been archived by the owner on Mar 24, 2021. It is now read-only.

Latest commit

 

History

History
227 lines (143 loc) · 20.8 KB

request-lifecycle.md

File metadata and controls

227 lines (143 loc) · 20.8 KB

Tracing a request through spotlight

If you hit a generic spotlight dashboard or module page, this is what happens.

What does "module" mean to spotlight?
In spotlight, the term "module" roughtly equates to a graph. It defines the type of graph, its associated metadata (title and description, etc), as well as the data associated with it.
  • A "dashboard page" shows a bunch of graphs -- each of these is a module.

  • A "module page" is a page with a graph and a table, which are just different representations of the same data.

After getting a request, you'll start off in appBuilder.js, which lists all the routes. The explicitly named routes are quite straightforward (/performance/about, etc), but they represent a minority of pages in the application.

Most of the application is either

  • dashboard pages: pages with lots of graphs
  • module pages: pages with one graph and a table

There’s one route that handles both of these, and it fobs you off to process_request.js.

At a high level, process_request.js is doing two things.

  1. It has a setup() function, which builds and returns a stagecraft API client object from get_dashboard_and_render.js
  2. There’s a renderContent() function which (because in JavaScript, functions are first-class citizens) gets passed further and further down potentially endless fractals of nested objects as a callback

The setup function is called first.

app/server/mixins/get_dashboard_and_render.js

Mostly what this code is doing is creating the stagecraft API client. It passes in some data from the URL (needed to call the Stagecraft API) and sets up event listeners on the API client. Its most important function, get_dashboard_and_render(), takes renderContent() (from the previous file) as a callback and runs it once the API client has been synced. (sync is a Backbone convention -- more on this later).

The StagecraftApiClient is an object that makes the initial async call to the API (using fetch(), another Backbone convention) and then parses the JSON response into objects the application is expecting. As part of this, it assigns a controller to each module.

(So a grouped_timeseries module will be assigned a GroupedTimeseriesModule controller. Controllers (also confusingly called "modules") are defined in server/controller_map.js.)

Once the API client has been initialised, then setup() is finished. At this point, we wait for the API client to trigger an event: either it will sync (likely) or it will error (unlikely).

Backbone follows an event-driven paradigm: ie, instead of writing procedural code that executes sequentially until the end of the file, you react to events in the DOM. So, for example, you could set up a "start" button and wait for a "click" event before starting anything.

Since Backbone is event-driven, there’s a point at which just following the code is misleading, because it will just stop somewhere and start again somewhere else. There are also several points in the code where events are triggered programmatically (ie, you can just call 'button.click()' to force an event.)

The general idea here is that spotlight sets up what it can set up with just the URL of the request, and then it waits for the sync event, triggered once a response has been successfully returned from the API, before doing any more.

There are two important syncs that need to happen.

  1. First, get a JSON file which defines the modules on the page. (ie, a dashboard might have 6 modules: 3 big numbers at the top of the page, 2 line graphs, and 1 bar graph)
  2. Secondly, for each module, the a separate call has to be made (not using the API client) to get the data for the graph

Once the .setup() function is completed then we wait for the first of these: for the stagecraft API client to return a list of modules.

Once the data from the API is synced, it triggers the event listener in this file. This function then calls renderContent(), which was defined in process_request.js.

In renderContent(), the model variable at this point is the API client which now has a bunch of config data about the page we're loading and a bunch of application defaults set by PageConfig.

If you were looking to add a new generic parameter to the application, a good place to put it would be in the commonConfig in page_config.js.

Our model also has a controller, which contains the logic for the particular page it's loading. This is set by the stagecraft API client. (For dashboard pages and module pages, this controller will always be the DashboardController)

Once we have the initial JSON response, we know

  • what the main controller is (ie, a dashboard controller)
  • what all of the submodules are (ie, each of the graphs) as well as each of their controllers

A ready event listener is set up on the controller, which doesn't do anything right now. This is the code that actually sends the response back to the browser, but at this point we haven't built the HTML yet.

Finally, near the bottom, there's a controller.render() call. This is pretty much what builds the page. render() is called for whichever controller was set by the API client.

This isn't functionally important, but should probably be mentioned. Spotlight has wrappers that start timers and then call your business logic as callback methods. controller.render() is being timed. If you’re debugging and you end up in a timer, look for the callback and keep going from there. (ie, cachedFn.apply(object, arguments);)

render() calls this.renderDashboard() and then passes the Controller.prototype.render function through as a callback. This means the generic render() method attached to the Controller base class (app/extensions/controllers/controller.js) will be called somewhere down the line.

renderDashboard() pretty much immediately just calls this.renderModules(). The callback it passes through is the Controller.prototype.render() function.

renderModules() sets the base controller (ie, the dashboard) to listen for the loaded event, which will kick off the render method of the highest-level controller. It then it runs map over all the submodules, adding a ready event listener to each of them, and then calling render() on each of them individually.

render() is also a function in this file. Since all of the submodule controllers are also instances of this base Controller prototype, calling render() on them will run the function in this file. render() sets up one-time event listeners for both reset and error events, and then it calls collection.fetch(). In practice, we expect the reset to be triggered.

fetch() is a Backbone convention that connects to some external source to get some data. Calling fetch() should be possible on models or collections of models, after which some data will be returned and an event will be triggered. This is a pretty key to how Backbone works.

What is happening here is that each of the modules contains the metadata for a graph (ie, its name and description and what kind of graph it is), but they still need to actually populate the graph with values. So calling collection.fetch() should return all the actual data that we can then use to build the graph and the data table.

fetch() eventually calls sync() which is defined in this file. It escapes the returned values but otherwise it is just using the Backbone default logic. Interestingly, this fetch() doesn't use the stagecraft API client.

And here's where we finally start building our HTML -- effectively a global variable we can keep referring to and populating.

The base (outermost) View will assign itself a template (this.$el.html(this.template(context));), which is a page that has lots of empty elements.

for example,

<div class=“visualisation-inner”></div>
<div class=“visualisation-table”></div>

Once this is set up, it calls this.renderSubviews().

renderSubviews() cycles through the submodules contained by this root View and tries to build them. Each submodule has a classname or id as an identifier, so for each one, we will look in the current HTML for the id or classname to latch onto, and then create a new View(options) which is immediately rendered, effectively building the HTML that describes what they are inside their (initially empty) container element.

As an example, if our submodule is a graph, then calling the new View in the base view file will create a new TableView, and then view.render() will call the the render() method of the TableView. This is probably what were were looking for in the first place.

Once we're here, we're finally in the guts of the page we're trying to build and we can start making changes to the output of the page.