Set up custom new business workflow (Policy Wizard)

This articles details efforts related to per-client customization on the policy wizard. The audience for this document is anyone that needs to interact with these new mechanics at a code level and anyone interested in knowing the new capabilities in more details.

Background

Product-level customization

It is now possible to define different UI compositions by creating a high-level description for the UI and then referencing this description by name using a setting. The BriteCore Administrative Portal uses the the setting britecore.active-uispec, which allows users to configure the wizard for admin site. The BriteCore Agent Portal (BriteQuote) uses the composition with the name defined in the setting britequote.active-uispec. This composition, when looked at from a usage perspective represents “a specification of exchangeable parts for the UI”, thus we named it uispec. Currently, all the different uispecs are stored as plain code in the repository until things start to cool down and we are able to decide how best to store/serve its data. Not all aspects of customization are driven straight from the uispec because it is a very high-level representation of some parts of the software, there is extra complexity to make the software reflect the spec in a decoupled way (coupling decreases maintainability and we can’t afford more maintenance problems). The uispec data structure is version-typed and its most important structures are also version-typed independently to allow maintainers some sanity and dignity when future changes in the data structure are needed. The uispec was thought to support changes throughout the whole UI, but its consumption on the software side is currently restricted to the wizard. This means that names, values, and types for the uispec declaration must be chosen in such a way to be more or less future-conflict safe and it is currently forbidden to use it to customize anything outside the wizard.

Wizard coarse customization

Changes can be made to the wizard at a coarse level quite easily. By adding sections of type policy_wizard_section:1.0 to the uispec, it is possible to determine which sections should exist and in which order. This type contains some core attributes (name, js_class, js_file and html_file) that are really straightforward and together are the basis of a simple dependency injection mechanism that allows one to switch software artifacts to achieve very specific customization goals. These core attributes are likely to appear for different section types once they are created, while the other attributes prefixed with opt_ are very specific to policy_wizard_section:1.0, and are also very coupled to the sensitive mechanics of the wizard (be careful about changing them). Special mention to the attributes opt_wizard_step_id that is unique and represents a page of content in the wizard regardless of the tab under which it sits, and opt_wizard_section_id which is not unique and allow different pages to be grouped under the same tab. (The words are a little overloaded in the wizard implementation code but overall, a step refers to a page in the wizard while a section refers to a group of one or more steps materialized under the same wizard navigation tab). Finer grained customization is achieved by switching some pieces of software by naming them under the customizers collection in the uispec.

Wizard steps assembly customization

In order to allow for a wizard that is constructed dynamically, its initialization was postponed, and a new startup phase was added to page load: the wizard assembly. It is possible to customize the default assembly routine by injecting a new factory function on a per-client basis. To understand how to do that, look for policy_wizard_assembler_interceptor in the code.

Wizard steps navigation customization

One of the features of the wizard is the ability to automatically move forward when the “Continue” button is pressed. This button has different labels in different moments, but this smart navigation is something that needs to change per-client for a number of reasons. To understand how to do that, look for policy_wizard_navigator_interceptor in the code.

Wizard fine customization

Additionally, UI widgets needed to gain additional flexibility to work differently for different clients. Therefore, a system of cascading feature flags were created. Basically, this means that a feature registry was created for the wizard. The registry can be customized on a per client basis to override the attributes for the features that need to change. To understand how to do that, look for policy_wizard_features_interceptor in the code.

Wizard plain content customization

Adding, removing, reordering, and grouping steps is not enough to satisfy current needs. We also need to change bits of content. For plain text translations, we adopted a system of placeholders where a dictionary is provided in the uispec that provides specific text mapped to generic lookup terms. The dictionary is provided to the software so it can query it to resolve these terms dynamically then present whatever text is found.

Pesky subjective topics and good practices

Using these new pieces is straightforward once they are in place, but extending the framework requires some pattern recognition skills: take a look at everything and try to understand the existing patterns before adding new keys, types, or changing the structure of anything as all can collapse quite easily with a few misguided PRs (there is not much we can formally enforce in javascript to protect us from our own bad decisions in the future).

Here are a couple of items to keep in mind in areas most vulnerable to fragmentation

The feature registry is a key-value pair, where keys are strings and values are objects containing only primitive types. Do not attempt to attach functions to it or do smart tricks. Hopefully, we will be able to move the registry up to the uispec, but as soon as the registry gains behavior or complexity, the dream is gone.

Names of keys for the feature registry could be anything, but for now we are trying to stick to names that map to a wizard entity using three different levels of “namespacing”:

  • A section, a widget, an element (for example policy_setup.form.insured_name)
  • Then the attributes will try to describe in a concise way an intention of a UI feature related to the named entity
    • For example, the notable attribute enabled when associated to a feature named after a UI block element, clearly states that the block element will either be added to the DOM or not

Eventually, we will need to create a feature in the registry that does not map nicely to the naming pattern described above, and when that happens we will have to decide what second pattern to foment and accept.

Prefer a fine customization technique (such as using the feature registry) over a coarse one (such as injecting a new customized section through the assembler). The reason is that coarse mechanics imply higher maintenance costs due to the amount of new code created when compared to finer technique.

That said, if you have dozens of small customizations to make on a single section for a single client, consider creating a new section to avoid the creation of dozens of new features that will also increase the maintenance cost and make it harder to understand the moving parts. Balance is key.

Feature Registry fictional samples

Below are some samples of bad and good uispec feature entries:

  • BAD: the target of this feature is an action itself
    • Ex: 'policy_setup.do_something': {enabled: false}
  • BAD: The target of this entry is a behaviour definition
    • Ex: 'policy_setup.some_initialization_should_be_skipped': {enabled: true}
  • BAD: the target is an abstract concept that can potentially relate to several existing UI entities in a coarse way. This can easily promote coupling between these entities, that makes the system brittle and hard to maintain.
    • Ex: 'policy_contacts.high_level_feature_A': {mode: 'quick-and-fast'}
  • GOOD: the target is a reasonably precise entity in the UI, while the associated object defines behavior for that entity
    • Ex: 'policy_contacts.insured.address': {preload: 'first-risk'}

Maintenance guides

Below is a simplified guideline to some situations that come up quite often

Overwriting a whole wizard step

When you need to change a whole wizard step. Removing, reordering or grouping steps should be similar but simpler.

  1. Make sure the target client has its own uispec derived from default.
  2. Create your custom content (javascript and/or html files) and place them in the custom area for the client.
  3. Change the uispec section for the target wizard section by updating its js_file, js_class, and (or) html_template properties to point to your custom files.

Changing behavior for a form input field

When you need to make an optional input field required for a single client:

  • Make sure the target client has its own uispec derived from default.
  • Search for a feature named after your element in the default registry.
  • Create or update the feature, with an object containing an attribute with a meaningful name (required), then set it to false.
  • Change codes in template and javascript to consume the feature from the registry instead of doing it hardcoded.
  • Change the feature registry customizer installed by the client’s custom uispec so that it overwrites the default feature by setting required to true.

 

Enable the Wizard

To enable:

  1. Log in to the Provider Administrator portal.
  2. Navigate to Settings > Advanced.
  3. Search use-wizard.
  4. Select True (default: False) for the Agent portal (BriteQuote) and/or Provider Administrator portal (BriteCore).