Proposal to add more configurable components in O3 esm-core

I want to propose having more components in esm-core that are configurable and globally re-useable.

Currently, I’m working on the having a new table in the patient chart to display a list of past visits for the patient. The visit table needs to be configurable to display different column types, like:

  • start date
  • visit type
  • obs of particular concepts / concept sets,
    • Different concept types might need to be displayed differently. For example, some obs (like visit notes) can be displayed as free-form text, while others (like coded obs) we might want to display as Carbon tags. We can regard these as different column types.
  • encounters of particular encounter types

This pattern is similar to what’s already in the ward app; see screenshot above. There, we have ward patient cards configurable to display different obs. In the screenshot, “REASON” and “Gravita” are obs values displayed as plain strings, while “pregnancy complications” are coded obs. The configuration for these elements, within the esm-ward-app namespace, looks something like this:

      "obs-text": [
        {
          "id": "obs-text-gravida",
          "conceptUuid": "${concept.gravida.uuid}",
          "onlyWithinCurrentVisit": true,
          "orderBy": "descending",
          "label": "Gravita",
          "limit": 1
        },
        {
          "id": "obs-text-admission-reason",
          "conceptUuid": "${concept.admissionReason.uuid}",
          "onlyWithinCurrentVisit": true,
          "orderBy": "descending",
          "label": "REASON",
          "limit": 1
        },
        
      ],
      "obs-coded": [
        {
          "id": "obs-coded-pregnancy-complications",
          "conceptUuid": "${concept.complicationsAtDelivery.uuid}",
          "summaryLabel": "Pregnancy Complication(s)",
          "tags": [
            {
              "color": "red",
              "appliedToConceptSets": ["${concept.complicationsAtDelivery.uuid}"]
            }
          ]
        }
      ], 

It’s easy to envision the same configuration for these elements to be in the visit table as well. For examples, we can configure the visit table to include columns by specifying an array of element ids, like this:

{
  visitTable: {
    columns: ["obs-text-admission-reason", "obs-coded-pregnancy-complications"]
  }
}

I think it would make sense to have these configurable components be somewhere in esm-core so they can be used in multiple apps. Historically, esm-styleguide is where we put re-useable components, but they have meant to be “style guide” elements that, other than their look-and-feel, are not opinionated in configuration or logic (like data fetching). I think we need a place for opinionated components that are re-useable and configurable . The configurability should be flexible in a way that we can configure different instances of the same component. For example, the above example has obs-text component configured twice, one to display Gravida and another to display admission reason. (This is similar to how widgets work in O2.)

cc @ibacher @dkigen @mogoodrich @mseaton

1 Like

When we discussed this within PIH, we also talked about how some of these elements might need to be implemented differently in different apps.

  • In the example, the “REASON” label of obs-text-admission-reason would need to be implemented as a column header in the visit table, vs a plain <span> in the ward patient card.
  • The same elements might need different fetching strategy in different places. For example, when we want to display obs value one particular concept from one visit (say within the patient chart when looking at details from one visit), we can probably just do GET /ws/rest/v1/obs?conceptUuid=<uuid> . However, if we need to display obs value for a particular concept across many visits (say within the visit table), or across many patients (say within the ward app), we might want to fetch data differently.

In these cases, it might make sense to have more app-specific components, but have them ingest the same global configs. So our config could look something like this:

// in patient chart: 
{ 
  visitTable: { 
    columns: ["obs-text-admission-reason", "obs-coded-pregnancy-complications"]
  }
} 
// in ward app: 
{ 
  wardPatientCard: {
    elements: ["obs-text-gravida", "obs-text-admission-reason", "obs-coded-pregnancy-complications"]
  }
}

In the above example, both the patient chart visit table and ward app patient cards are configured to display the obs-text-admission-reason and obs-coded-pregnancy-complications, but the two apps can have app-specific component to render those 2 things differently.

1 Like

So, I’ve got a lot to say here.

To start, one thing I’ve just managed to realize for myself is something about the configuration system itself. The target users of the configuration system are not developers but BAs, technical product managers, and others on the implementation side that do not write code. I say that because it’s an important principle in how we set this up.

In light of that, I think something like second version of what you’re proposing is much better than the current setup. However, it may imply that we need a second, more developer-focused configuration mechanism.

That said, I wonder about the specificity of some of the components you’re proposing here. Do we really need a component called obs-coded-pregnancy-complications or do we need something more like a obs-coded widget with a “configuration” of some sort, e.g., something more similar to obs-coded#pregnancy-complications where the obs-coded part is the component and the #pregnancy-complications points it to some sort of configuration registration.

Historically, esm-styleguide is where we put re-useable components, but they have meant to be “style guide” elements that, other than their look-and-feel, are not opinionated in configuration or logic (like data fetching)

I would say that the idea is that they should be agnostic as to how data is fetched. I don’t think they are otherwise not opinionated about configuration or logic—many of our components actually are; it’s just that this configuration or logic is basically driven by props rather than the configuration system. And I think that’s a perfectly fine pattern for, e.g., an obs-numeric widget that takes an observation (potentially with a reference range) and renders it.

If we need specific data-fetching logic, we can always add hooks for that to the react-utils. But I actually think that it might make sense to try to work on how we could “batch” the data for multiple components and have, e.g., a component that loads the data for a table or table row and then push that data into the individual components. Ultimately, I think that will result in a better UX than having a table of several different components each responsible for loading their own data. There are patterns for doing this, e.g., with most routers that allow data to be “prefetched” and have the route only render once the data is available, with individual components able to then access that prefetched data contextually. We can look into how to emulate something like that behaviour in our setup.

Yeah, I meant just a obs-coded widget, with an instance of it specifically configured for showing pregnancy complication, and that we can refer to that instance by its id obs-coded-pregnancy-complication. (The id is prefixed with obs-coded more so as a naming convention to it’s easy to tell what kind of widget it is)

If I understand correctly, you’re suggesting something similar, but that we can refer to these instances of configured widgets by <widget-name>#<config-name>, and that there is some first-class support for it in the implementer tools config UI? If so, I think this will be good enough for both BAs / project managers and developers to use.

I would say that the idea is that they should be agnostic as to how data is fetched. If we need specific data-fetching logic, we can always add hooks for that to the react-utils.

Yeah, I like the idea of providing both widgets that are agnostic to data fetching in styleguide and data-fetching hooks in react-utils, and have specific apps mix and match them as appropriate.

There are patterns for doing this, e.g., with most routers that allow data to be “prefetched” and have the route only render once the data is available, with individual components able to then access that prefetched data contextually.

I was thinking of doing batched fetching with useSWR with a custom fetcher, but I haven’t tried that yet.

There isn’t yet, but I was trying to suggest something that’s similar to what we currently do with the extension system. I get that it’s just a syntax thing, but it also makes it easier to parse the provided value to separate the “widget” from the “config”.

I was thinking specifically here of basically “pushing up” the data fetching, which, yeah, we could wrap in useSWR(). We can leverage, e.g., single-spa’s lifecycles to do a sort of “pre-fetch” before we render an extension and we can likely combine that set of prefetches into a set of calls.