Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

npm: @openmrs/esm-module-confg

Maintainer: Brandon Istenes (bistenes@pih.org)

What is this?

openmrs-esm-module-config is the configuration library for OpenMRS microfrontends. It strives to establish configuration as a first-class concern of OpenMRS components. To this end, it provides mechanisms to make configurability easier for developers and configuring easier for implementers.

...

{
  "@openmrs/esm-login": {
    "logo": {
      "src": "https://pbs.twimg.com/media/C1w_czvWgAAWONL.jpg"
    }
  },
  "@openmrs/esm-home": {
    "search": {
      "numResultsPerPage": 20
    }
  }
}

How do I

...

configure my OpenMRS implementation?

There are two methods for doing so.

...

You should use this module, esm-module-config, to make your modules configurable. Getting set up is easy.

Defining a schema

You need to tell

Start by npm install --save @openmrs/esm-module-config. This is a runtime dependency, so it should be included in your webpack externals.

The main task is to create a config schema for your module. The config schema is what tells esm-module-config config what configuration files should look like, including defaults and validations.

Designing a schema

You'll probably start with some idea of what you want configs for your module should to look like. You will also Try and put yourself in the implementer's shoes an imagine what features they will expect to be configurable, and what they might expect the configuration property to be called. Assume they don't know anything about the internal workings of your module.

By way of example, let's say we're building a module for a virtual provider functionality at a very futuristic hospital. Maybe we want an implementer to be able to write the following in their config file:


"@openmrs/esm-hologram-doctor": {
  "hologram": {
    "color": true
  },
  "virtualProvider": {
    "name": {
      "given": ["Qui", "Gon"]
    }
  },
  "robots": [
    { "name": "R2-D2", "homeworld": "Naboo" },
    { "name": "BB-8", "homeworld": "Hosnian Prime" }
  ]
}

In the following section, we'll see how to write a config schema that supports these config elements.

Defining a schema

We'll start with just that first nested config element from above, hologram.color. We must provide defaults for all of the values — in OpenMRS ESMs, all configuration is optional.

import { defineConfigSchema, validators } from "@openmrs/esm-module-config"

defineConfigSchema("@openmrs/esm-hologram-doctor", { hologram: { color: { default: false,
validators: [validators.isBoolean] } }
}

Note that each configuration element should have an object for a value, and that this object must define the default for that element. Do not do this:

BAD defineConfigSchema("@openmrs/esm-hologram-doctor",
BAD hologram: { BAD salutation: "Some friendly default salutation! ? this is wrong!" BAD })

The following names are reserved and cannot be used as config keys: default, validators, and arrayElements. Doing so will result in undefined behavior. Do not do this:

BAD defineConfigSchema("@openmrs/esm-hologram-doctor",
BAD hologram: { BAD virtualProvidersalutation: { BAD namedefault: { BAD family default: "Greetings ? this is bad don't do it" BAD }}})

Validators

You should provide validators for your configuration elements wherever possible. This reduces the probability that implementers using your module will have hard-to-debug runtime errors. It gives you, the module developer, the opportunity to provide implementers with very helpful explanations about why their configuration won't work.

robot: {
  name: {
    default: "Kenobi"
      }R2D2",
    validators: [
      validators.isString,
      validator(n => /\d/.test(n), "all robots must have numbers in their names")
    }]
  }
}

(note that this piece of schema is not part of our above example – it only supports a single robot, whereas we need to allow the implementer to provide an array of robots)

...

Using config values

In React

The moduleName provided to the openmrsRootDecorator is used to look up the configuration elsewhere in the application. So in the example above, the root decorator call would look something like

export default openmrsRootDecorator({
  featureName: "hologram doctor",
  moduleName: "@openmrs/esm-hologram-doctor"
})(Root)

given an application root component named Root.

You can then get the config tree as an object using the useConfig React hook.

...

.

A validator can be created using the validator function, as above.

The first argument is a function that takes the config value as its only argument. If the function returns something truthy, validation passes. If the function returns something falsy, an error is thrown with the second argument as an explanation.

For convenience, some common validators are provided out of the box. These are

  • isBoolean
  • isString

Arrays

You can accept and validate arrays, and arrays containing objects, in your configuration schema. This is configured with the arrayElements parameter. For example, a schema which would accept an array of strings:

virtualProvider: {
  name: {
    given: {
      default: ["Obi", "Wan"]
      arrayElements: {
        validators: [validators.isString]
}}}}

Here is an example of a schema that expects an array of objects structured in a particular way.

robots: {
  default: [
    { name: "R2-D2", homeworld: "Naboo" },
    { name: "C-3PO", homeworld: "Tatooine" }
  ],
  arrayElements: {
    name: { validators: [robotNameValidator] },
    homeworld: {
      default: null  // not required
      validators: [validators.isString]
    }
  }
}

This schema will require that any objects in the robots array must only have the keys name and homeworld.

Using config values

The generic way

The config is fetched asynchronously using getConfig(moduleName). Continuing the above example, we would have something like

import { getConfig } from "@openmrs/esm-module-config"
                                                       
async function doctorGreeting() {     
  const config = await getConfig("@openmrs/esm-hologram-doctor")
  return "Hello, my name is Dr. " + config.virtualProvider.name.family
}

The content of config will be pulled from the config files, falling back to the defaults for configuration elements for which no values have been provided.

React support

A React Hook is provided to hide the asynchronicity of config loading. The moduleName provided to the openmrsRootDecorator is used to look up the configuration elsewhere in the application.

export default openmrsRootDecorator({
  featureName: "hologram doctor",
  moduleName: "@openmrs/esm-hologram-doctor"
})(Root)

given an application root component named Root.

You can then get the config tree as an object using the useConfig React hook.

import { useConfig } from "@openmrs/esm-module-config"

export default function DoctorGreeting() {     
  const config = useConfig()
  const greeting = "Hello, my name is Dr. " + config.virtualProvider.name.family
  return (
    <div>{ greeting }</div>
  )
}

The content of config will be pulled from the config files, falling back to the defaults for configuration elements for which no values have been provided.

...

Support in other frameworks (Angular, Vue, Svelte,

...

etc.)

This hasn't been implemented yet, but we would like to implement it! Please reach out to bistenes@pih.org and/or open an issue on the OpenMRS Jira.

...