Implementer Tools UI

Goal

To provide an interface that allows non-technical system administrators to configure the frontend application.

Secondarily, to provide other interfaces (other tabs/panels, for example) which provide additional insights, controls, and diagnostic information to the system administrator. For example, diagnostic information about missing backend modules.

Driving User Story: Developers and project leaders for OpenMRS EMRs want their remote sites to be empowered to make the changes they need to make, and depend much less on core developer resources, (and so that devs are not doing a bunch of custom UI hacks with expensive dev time). Central dev support teams often get requests like "We would like the registration page to lead directly to the patient's chart, instead of the registration summary". These Implementer Tools should make changes like that easier. 

Designs & Tickets

Based on Design Cycles (including Squad involvement and user testing) in Sept/Oct/Nov 2021: 

Jira Epic:

Error rendering macro 'jira' : Unable to locate Jira server for this macro. It may be due to Application Link configuration.
 

User Goals

All user stories below are for non-developer admins of OpenMRS. These folks range in skills from being new to using MS Office or GSuite tools, to being fluent in JSON.  

PriorityNeedReasonNotes
HighestCONFIG: Configure existing widgetsI need it to be easy to find and change the config for each widget, so I can quickly made the changes needed for my particular organization or site, so that our custom Concept Codes can be applied to the widgets. 

Each widget is designed to work with default settings, but users may have to change certain things.

  • E.g. Vitals Form: Configured using default CIEL UUIDs, but Ampath has different UUIDs, and needs to change them to the Ampath ones. 
HigherADD WIDGETS: Add ready-made widgets to my existing pages or my custom pagesI need to easily find alternative/additional widgets that are already available, so that I can quickly add content of interest throughout my EMR, without having to ask developers to build new custom things for us. 


HighPAGES: Create my own custom pages for the patient chart

I need to build a page specific to a particular program or site, so that patient chart information that only that program area is interested in is easy to find. 

e.g. Imagine new pages dedicated to program/topics like: 

  • Pregnancy History
  • Surgical Notes
  • HIV Testing Program

Workflow:

  • New Page: I want to create a page specifically designed for our HIV program.
  • Create: I go here to create a page.
  • Page Name: I name the page "HIV" → now this shows up on Pt Chart Nav Bar.
  • (Then: Add Widgets: (as in story above?) I find and order related widgets; I look through the options and grab the ones I want)
HighWIDGET TEMPLATES: Customize my own widget from a widget template I need to be able to adapt a "templated"/"vanilla" version to suit my needs, so that when there's no pre-existing widget like what I want, I have the ability to unblock myself without needing developers to build an entirely new widget for me.

aka Configure your own "vanilla" widget:

E.g. Table Example: I want a Table widget showing a few key peices of information that matter to our pre-surgical clinic - e.g. BMI, Blood Sugar, and Blood Clotting tests results at each of their most recent pre-surgical Visits. I imagine something like this: 

(Visit Date) | (BMI) | (Blood Sugar) mmol/L | (Blood Clotting test result)

1) Search for existing widget that meets that description, and don't find one (side note: many users might not even want to bother with searching and might want to jump right in to creating their own thing; but probably a good idea to enforce a search workflow first to reduce duplication of widgets & work)

2) Identify "I want to build my own widget"

3) Select "Table" type (which then provides me with a blank vanilla Table widget to start from)

4) I can then identify the concept(s) I want to track in each column (e.g. Visit Date, BMI, etc).


E.g. Chart Example: I want a Chart showing Pregnancy-related Weight Gain over time. Possible workflow:

1) Search for existing widget that meets that description, and don't find one

2) Identify "I want to build my own widget"

3) Select "Chart" type (which then provides me with a blank vanilla Chart widget to start from)

4) I can then identify the concept(s) I want to track (e.g. "Pregnancy Weight"), and further configure things like the color and style of the line.

Reorder arrays (like custom pages in patient chart)

Brandon calls this a "list of multiple complex things (aka array of objects)" where the user has the ability to further define what sits inside the table/list/widget. His early example design is here:





Starting Point


We have an application, the Implementer Tools, which provides a janky interface for managing configuration.

It is basically functional, but unambiguously ugly.

We also have these slides which sketch a UI. They are similarly ugly.

All of the above, despite its ugliness, should be reviewed to understand the types of functionality that have been envisaged so far.

Functional Specification

User Story: I need a way to publish my changes, so that...

(this is a JSON file I download, and upload somewhere; or they get saved to the server directly)

User Story: IUntil I publish my changes, I need my changes to only be applied to my  browser, so that the changes aren't happening on the fly in production and impacting users live at my clinic. 


There are essentially the following main parts: microfrontend configuration, extension configuration, and the UI editor.

The application is configured with one or more config files. These files override the default config values. All config values have defaults. The Implementer Tools provides an interface for editing the configuration of the application. When an administrator "edits the configuration," everything they do is local only to them, saved in the "temporary config," which is essentially a config overrides file that lives in LocalStorage. In order to deploy their changes to the server, they can download that temporary config and add it as a config file to their application. Brandon is working on a feature that would allow saving the new config values to the server by clicking a button.

A config file is a JSON file with microfrontend names for top-level keys. e.g.

{
  "@openmrs/esm-login-app": {
    "chooseLocation": {
      "enabled": false
    }
  },
  "@openmrs/esm-home-app": {
    "buttons": {
      "enabled": true
    }
  }
}

Microfrontend Configuration

Microfrontend Configuration is used to tell microfrontends how to behave; for example, the login page can be configured to show a different logo than the default.

There are the following types of possible config values:

  • Boolean
  • Number
  • String
    • UUID
      • ConceptUuid
  • Array (e.g. `["foo", "bar"]`)
  • Freeform Object (e.g. `{ foo: "bar", baz: 3 }`)

To clarify a little, the entire config tree is an "object" in the sense that it is a thing with keys and values, potentially nested. However, its keys are completely prescribed. Hence the type "Freeform Object", which is a value in which the keys are not prescribed. These are rare and you don't need to worry about them too much.

Arrays and Freeform Objects can contain any of the above as values, including other Arrays and Freeform Objects. The most common structures involving these things are 1. a simple Array of Strings and 2. an Array of predefined Objects.

An Array of Objects carries some specification of its member objects (c.f. an Array of Freeform Objects). So it might want objects with keys "foo" and "bar," where "foo" is a string and "bar" is an optional boolean. So

[{ foo: "hey" }, { foo: "you", bar: false }, { foo: "get" }]

would be a valid value but

[{ foo: "hey" }, { bar: false }, { foo: "get" }]

would not, because the required element "foo" is absent. Nor would

[{ foo: "hey" }, { buzz: "bad" }, { foo: "get" }]

wherein one object has an element that is not part of the member object spec.

Editing

  • Editing basic config values. These are booleans, numbers, or strings.
  • Editing arrays. Should be able to add, remove, and reorder elements. For arrays of objects, adding an element to the array should add an object with the correct shape.
  • Editing freeform objects? Literal JSON editing might be the best we can do, here, since values can be of any type.

Feedback

  • Each config element has a description, a default value, a source for the current value, and a set of validators. These should be displayed.
  • Some config elements have corresponding "edit buttons" in the UI editor (which may or may not be present on the page which the administrator is on. Maybe there's a good way to highlight the relationship, when the admin happens to be on the right page?
  • When the admin inputs a value that is invalid (either because it is of the wrong type or because it fails a validator), they should know about it.
  • It should probably be visually apparent which config values are defaults, which have been set in a config file, and which have been set in the UI / temporary config.
  • If there is a config file that is providing configuration for a microfrontend that doesn't exist, the admin should be made aware of this, as it might indicate an error (but does not necessarily).

Extension Configuration


Extension Configuration is used to customize the relationships between extensions and slots. Every extension is attached to some slots in code, and has some order-priority (think z-index) within those slots. Using configuration, this behavior can be changed: extensions can be removed from slots they are attached to in code, added to slots that they are not attached to in code, and reordered within slots. Each extension is also itself configurable, like a microfrontend. Think of a line graph which can plot observations for any numeric concept. If it is provided as an extension, it can be attached in multiple places, configured to plot a different concept in each place.

Whereas the microfrontend configuration is completely variable, the extension configuration follows a very predictable structure. Each slot can be configured as

  • (containing module name)
    • slots
      • (slot name)
        • add: (array of extension IDs)
        • remove: (array of extension IDs)
        • order: (array of extension IDs)
        • configure
          • (extension ID): (whatever config object the extension wants)

Other than the "configure" element, it's quite straightforward.

Any extension can technically be added to any slot, but few extension-slot pairings will actually work correctly. It's unclear how we should address this, but perhaps the simplest way would be to assign slots and extensions "types" so that they can be associated with each other. Each "type" would have some expected shape of state object. Alternatively, extensions could make their requirements known to the extension system (the extension system already has access to the state provided by slots).

Here we see the "remove" element editor, which offers a multi-select of the extensions which are currently attached to the slot (and thus available to be removed).

Feedback

  • The admin should be able to see which extensions are attached to each slot.
  • The admin may or may not want to see the list of extensions which would be attached to each slot by default (i.e. prior to the config).
  • The admin should be able to identify irrelevant extension IDs in "remove", "order", and the keys of "configure"; i.e., extension IDs which do not correspond to any extension which would actually be attached to that slot. This is not an error, but just cruft—stuff that probably doesn't do anything and so can be removed.

UI Editor


The UI Editor provides the administrator with a visual understanding of what the configuration means.

For the microfrontend configuration, it's simply "edit buttons" in sensible places in the UI which allow the admin to "hop" to the relevant piece of config. For example, turning on the UI Editor, they might see a little Edit button by the logo on the login page. When they click that button, they would be taken to the relevant section of the configuration in the Implementer Tools panel.

The bulk of what the UI Editor does, however, has to do with extensions. It makes it clear to the administrator where all the slots and extensions are.

It should probably also provide features for visually editing the extension configuration—buttons that trigger the corresponding config changes to e.g. remove an extension or reorder it within a slot. This is featured prominently in the presentation linked above.

Challenges

The shape of the configuration is subject to change. New microfrontends may do new and previously unanticipated things with config structure.

The same is true of extensions and extension slots. Who knows what insane things people might try to use them for.