Skip to Content
DocumentationSystème de configuration

Le Système de Configuration

Introduit par RFC-14 , le système de configuration fournit un cadre pour rendre les modules frontaux configurables par les implémenteurs sans nécessiter de modifications de code. Il permet aux développeurs de définir des schémas qui spécifient quelles options de configuration sont disponibles, leurs types, leurs valeurs par défaut et leurs règles de validation.

ℹ️

Pour les implémenteurs : Si vous cherchez à configurer une instance O3 existante (changer les logos, les traductions, les paramètres des modules, etc.), consultez plutôt le guide Configurer O3. Ce guide est destiné aux développeurs qui souhaitent rendre leurs modules configurables.

Utilisation du système de configuration

Un cadre est fourni pour faciliter la configuration pour les développeurs et la configuration pour les implémenteurs.

Pour les implémenteurs : Pour savoir comment configurer votre instance O3, consultez le guide Configurer O3, qui couvre les tâches pratiques de configuration comme le branding, les traductions et les paramètres spécifiques aux modules.

Comment rendre un module frontal configurable

Vous devez utiliser l’OpenMRS Frontend Framework pour rendre les modules configurables.

La tâche principale est de créer un schéma de configuration pour votre module. Le schéma de configuration indique au framework à quoi doivent ressembler les fichiers de configuration, y compris les valeurs par défaut et les validations.

Note : Les schémas de configuration sont optionnels. Si votre module n’a pas besoin d’options de configuration (par exemple, s’il s’agit d’un module bibliothèque comme le React Form Engine  qui reçoit toute sa configuration via des props ou des paramètres de fonction), vous n’avez pas besoin de définir un schéma. Le système de configuration fonctionnera toujours; les modules sans schéma utilisent le schéma implicite pour Display conditions et Translation overrides.

Conception d’un schéma

Vous commencerez probablement par avoir une idée de ce à quoi vous voulez que les fichiers de configuration de votre module ressemblent. Essayez de vous mettre à la place de l’implémenteur et imaginez les fonctionnalités qu’il s’attend à voir configurer, et comment il s’attend à ce que la propriété de configuration soit appelée. Supposez qu’il ne sait rien du fonctionnement interne de votre module.

À titre d’exemple, disons que nous construisons un module pour une fonctionnalité de fournisseur virtuel dans un hôpital très futuriste. Peut-être voulons-nous qu’un implémenteur puisse écrire ce qui suit dans son fichier de configuration:

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

Dans la section suivante, nous verrons comment écrire un schéma de configuration qui supporte ces éléments de configuration.

Définir un schéma

Nous allons commencer par le premier élément de configuration imbriqué, hologram.color. Nous devons fournir des valeurs par défaut pour toutes les valeurs - dans OpenMRS Frontend 3.0, toute la configuration est optionnelle. Tous les modules doivent faire quelque chose de raisonnable dès le départ.

import { defineConfigSchema, Type } from "@openmrs/esm-framework"; defineConfigSchema("@openmrs/esm-hologram-doctor", { hologram: { color: { _type: Type.Boolean, _default: false, _description: "Whether the hologram supports color display." } } });

Notez que chaque élément de configuration doit avoir un objet pour une valeur, et que cet objet doit définir les propriétés de cet élément. Ne faites pas ceci:

// Ce n'est pas correct. `salutation` est une valeur brute, pas un objet de schéma. defineConfigSchema("@openmrs/esm-hologram-doctor", { hologram: { salutation: "Some friendly default salutation!" } });

Les mots préfixés par _ sont des mots-clés de schéma. Ne préfixez pas les noms de vos éléments de configuration avec des underscores. En particulier, n’utilisez pas un mot-clé de schéma comme nom d’élément de configuration.

Clés de configuration réservées :

Les clés suivantes sont réservées et ne peuvent pas être utilisées comme noms d’éléments de configuration :

  • Display conditions - Utilisé par le système d’extension pour le contrôle de visibilité des extensions
  • Translation overrides - Utilisé pour les remplacements de traduction par langue

Ces clés sont automatiquement incluses dans le schéma implicite de chaque module et provoqueront des erreurs de validation si vous essayez de les utiliser comme clés de configuration personnalisées.

// Ne faites pas ça. `_default` est un mot-clé de schéma, pas un nom d'élément de configuration. defineConfigSchema("@openmrs/esm-hologram-doctor", { hologram: { salutation: { _default: { _default: "Greetings" } } } });

Typage

Bien que ce ne soit pas strictement nécessaire dans la version actuelle, vous devriez fournir un type pour chaque élément de configuration que vous définissez. Le mot-clé _type accepte les valeurs de l’enum Type.

Les types disponibles sont :

  • Type.String - Valeurs de chaîne
  • Type.Number - Valeurs numériques
  • Type.Boolean - Valeurs booléennes (true/false)
  • Type.Array - Tableaux (nécessite _elements pour spécifier le schéma des éléments du tableau)
  • Type.Object - Valeurs d’objet libres. Lorsqu’il est utilisé avec _elements, O3 valide et applique les valeurs par défaut pour chaque valeur de l’objet.
  • Type.UUID - Chaînes UUID
  • Type.ConceptUuid - Chaînes UUID de concept
  • Type.PersonAttributeTypeUuid - Chaînes UUID de type d’attribut de personne
  • Type.PatientIdentifierTypeUuid - Chaînes UUID de type d’identifiant de patient

Ces types sont utilisés à la fois pour valider les entrées et pour supporter un comportement spécial dans les outils d’implémentation.

Validateurs

Dans la mesure du possible, vous devez fournir des validateurs pour vos éléments de configuration. Cela réduit la probabilité que les implémenteurs qui utilisent votre module aient des erreurs d’exécution difficiles à déboguer. Cela vous donne, en tant que développeur du module, l’opportunité de fournir aux implémenteurs des explications très utiles sur la raison pour laquelle leur configuration ne fonctionne pas.

robot: { name: { _type: Type.String, _default: "R2D2", _description: "What to call the robot", _validators: [ validator(n => /\d/.test(n), "Robots must have numbers in their names") ] } }

(Il convient de noter que ce schéma ne fait pas partie de l’exemple ci-dessus. Il ne supporte qu’un seul robot, alors que nous devons permettre à l’implémenteur de fournir un tableau de robots).

Un validateur peut être créé en utilisant la fonction validator, comme ci-dessus.

Le premier argument est une fonction qui prend la valeur de configuration comme seul argument. Si la fonction renvoie true, la validation réussit. Si elle renvoie false, la validation échoue et le deuxième argument de validator est utilisé comme message d’erreur. Vous pouvez aussi fournir directement une fonction de validation personnalisée dans _validators; les validateurs directs doivent renvoyer undefined en cas de succès ou une chaîne d’erreur en cas d’échec.

Important : Lorsque la validation échoue, l’erreur est enregistrée dans la console du navigateur, mais l’application continue de fonctionner. Les valeurs de configuration invalides sont toujours utilisées—les erreurs de validation servent d’avertissements pour aider les implémenteurs à identifier et corriger les problèmes de configuration, plutôt que d’empêcher l’application de démarrer.

Vous pouvez même valider des objets imbriqués:

colorPicker: { options: { _default: ["green", "red", "blue"] }, initial: { _default: "green" }, _description: "Le sélecteur de couleur des sabres laser", _validators: [ validator(o => o.options.includes(o.initial), "Initial must be one of the options") ] }

Pour des raisons de commodité, certains validateurs courants sont fournis d’office, notamment inRange, oneOf et isUrlWithTemplateParameters.

Tableaux

Vous pouvez accepter et valider des tableaux, et des tableaux contenant des objets, dans votre schéma de configuration. Ceci est configuré avec le paramètre _elements, utilisé avec _type: Type.Array. Par exemple, un schéma qui accepterait un tableau de chaînes de caractères d’une longueur maximale de 30 caractères ressemblerait à ceci:

virtualProvider: { name: { given: { _type: Type.Array, _default: ["Obi", "Wan"], _elements: { _type: Type.String, _validators: [validator(n => n.length < 30, "Must be less than 30 characters")] } }, _description: "Le nom de l'avatar. Il ne doit pas forcément être celui du praticien réel" }, _description: "L'avatar du praticien" }

Voici un exemple de schéma qui attend un tableau d’objets structuré d’une manière particulière.

robots: { _type: Type.Array, _default: [ { name: "R2-D2", homeworld: "Naboo" }, { name: "C-3PO", homeworld: "Tatooine" } ], _description: "La liste de tous les robots disponibles", _elements: { name: { _type: Type.String, _description: "What to call the robot", _validators: [robotNameValidator] }, homeworld: { _type: Type.String, _description: "Where the robot is from", _default: null // not required } } }

Ce schéma valide les clés et les types de valeurs des objets du tableau robots. Les clés inconnues sont consignées comme erreurs de configuration, et les valeurs par défaut sont appliquées lorsqu’une entrée de _elements fournit une _default.

Les objets dans les tableaux n’ont pas besoin de fournir des valeurs par défaut pour chaque clé. Si un champ comme name est indispensable au bon fonctionnement de votre module, ajoutez un validateur qui vérifie chaque objet et renvoie un message d’erreur utile lorsque le champ est absent.

Utilisation des valeurs de configuration

La méthode générique

La configuration est récupérée de manière asynchrone en utilisant getConfig(moduleName). En continuant l’exemple ci-dessus, nous aurions quelque chose comme:

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

Le contenu de config sera tiré des fichiers de configuration, en revenant aux valeurs par défaut pour les éléments de configuration pour lesquels aucune valeur n’a été fournie.

Comment les valeurs de configuration sont fusionnées :

Lorsque plusieurs sources de configuration fournissent des valeurs pour la même clé, elles sont fusionnées avec la priorité suivante (les sources ultérieures remplacent les précédentes) :

  1. Valeurs par défaut de la définition du schéma
  2. Fichiers de configuration (fusionnés dans l’ordre où ils sont chargés)
  3. Configuration temporaire de l’interface des outils d’implémentation (priorité la plus élevée)

Cela signifie que si vous définissez une valeur dans un fichier de configuration puis la modifiez en utilisant les outils d’implémentation, la valeur des outils d’implémentation prendra effet. De même, si la même clé apparaît dans plusieurs fichiers de configuration, la valeur du dernier fichier chargé sera utilisée.

Les objets imbriqués sont fusionnés en profondeur, mais les tableaux sont remplacés en bloc. Si une source de configuration plus tardive fournit un tableau pour une clé, il remplace le tableau précédent au lieu d’être fusionné par index.

Support React

Un React Hook est fourni pour masquer l’asynchronicité du chargement de la configuration. Le hook useConfig détermine automatiquement quelle configuration de module charger en fonction du ComponentContext, qui est défini lorsque vous utilisez getSyncLifecycle ou getAsyncLifecycle avec un moduleName.

import HologramDoctor from "./hologram-doctor.component"; export const hologramDoctor = getSyncLifecycle(HologramDoctor, { featureName: 'hologram doctor', moduleName: '@openmrs/esm-hologram-doctor', });

Vous pouvez ensuite obtenir l’arbre de configuration en tant qu’objet en utilisant le hook React useConfig :

import { useConfig } from "@openmrs/esm-framework"; export default function DoctorGreeting() { const config = useConfig(); const greeting = "Bonjour, mon nom est Dr. " + config.virtualProvider.name.given.join(" "); return <div>{greeting}</div>; }

Le hook useConfig :

  • Charge automatiquement la configuration du module en fonction du moduleName du ComponentContext
  • Fusionne la configuration spécifique à l’extension lorsqu’il est utilisé dans un composant d’extension
  • Revient aux valeurs par défaut pour les éléments de configuration pour lesquels aucune valeur n’a été fournie

Charger la configuration d’un autre module :

Vous pouvez optionnellement charger la configuration d’un module différent en utilisant l’option externalModuleName. Ceci ne devrait être utilisé qu’en cas de nécessité absolue, car cela peut créer un couplage entre les modules :

const otherModuleConfig = useConfig({ externalModuleName: "@openmrs/esm-other-module" });

Enregistrer les schémas de configuration :

Dans les modules Core v5+, les schémas de configuration doivent être enregistrés dans la fonction startupApp() :

import { defineConfigSchema } from "@openmrs/esm-framework"; import { configSchema } from "./config-schema"; const moduleName = "@openmrs/esm-hologram-doctor"; export function startupApp() { defineConfigSchema(moduleName, configSchema); }

Cela garantit que le schéma est enregistré avant que les composants du module ne soient rendus.

Fonctionnalités de schéma automatiques :

Chaque module reçoit automatiquement un schéma implicite qui inclut deux clés de configuration réservées :

  • Display conditions - Utilisé par le système d’extension pour contrôler la visibilité des extensions (pertinent uniquement pour les extensions)
  • Translation overrides - Utilisé pour les remplacements de traduction par langue. Voir Configurer les traductions pour plus de détails sur la façon dont les implémenteurs utilisent cette fonctionnalité.

Ce schéma implicite est disponible immédiatement, même avant d’appeler defineConfigSchema(). Cela permet aux remplacements de traduction d’être chargés tôt, ce qui est important car le système de traduction en a besoin avant que les modules puissent s’initialiser complètement. Lorsque vous définissez votre schéma personnalisé, il est automatiquement fusionné avec le schéma implicite, vous n’avez donc pas besoin de (et ne devriez pas) définir ces clés réservées vous-même.

Support dans d’autres frameworks (Angular, Vue, Svelte, etc.)

Cela n’a pas encore été mis en œuvre, mais vous êtes les bienvenus pour l’implémenter ! Voir le guide de contribution.

Typage

Il est intéressant d’avoir une validation de type pour votre utilisation de la configuration. Pour ce faire, définissez une interface à côté de votre schéma de configuration.

import { defineConfigSchema, Type } from "@openmrs/esm-framework"; defineConfigSchema("@openmrs/esm-hologram-doctor", { hologram: { color: { _type: Type.Boolean, _default: false, _description: "Whether the hologram supports color display.", }, }, }); export interface HologramDoctorConfig { hologram: { color: boolean; }; }

Vous pouvez ensuite utiliser ces informations de typage lors de l’appel des fonctions de configuration :

const config = useConfig<HologramDoctorConfig>();

Configuration spécifique aux extensions

Les extensions peuvent avoir leurs propres schémas de configuration qui sont séparés de la configuration de leur module parent. Ceci est utile lorsqu’une extension a besoin d’options de configuration qui ne s’appliquent pas au module dans son ensemble.

Pour définir un schéma de configuration spécifique à une extension, utilisez defineExtensionConfigSchema :

import { defineExtensionConfigSchema, Type } from "@openmrs/esm-framework"; export function startupApp() { defineExtensionConfigSchema("my-extension-name", { customSetting: { _type: Type.String, _default: "default value", _description: "Un paramètre spécifique à cette extension" } }); }

Le nom de l’extension doit correspondre à la propriété name de l’extension telle que définie dans routes.json. Lorsqu’une extension a son propre schéma de configuration, les valeurs de base de ce schéma sont fournies sous le nom de l’extension, pas sous le nom du module. useConfig() dans cette extension retourne toujours la configuration normale du module plus la configuration de l’extension. Les valeurs spécifiques à l’extension ont la priorité lorsque la même clé de premier niveau existe aux deux endroits.

Comment la configuration de l’extension est résolue :

Les valeurs de configuration de l’extension sont déterminées en fusionnant plusieurs sources dans cet ordre :

  1. Configuration de base de l’extension - Valeurs fournies sous le nom de l’extension lorsque l’extension a son propre schéma, ou sous le nom du module de l’extension lorsqu’elle utilise le schéma du module
  2. Remplacements spécifiques au slot - Valeurs de l’objet configure dans la configuration du slot (priorité la plus élevée)

Cette fusion permet aux administrateurs de personnaliser le comportement d’une extension différemment pour chaque slot où elle apparaît. Par exemple, la même extension pourrait avoir des paramètres différents lorsqu’elle est utilisée dans un en-tête de patient par rapport à un tableau de bord.

La clé dans configure doit correspondre à l’ID d’extension assigné au slot. Si vous attachez plusieurs fois la même extension avec des IDs d’instance comme my-extension#left et my-extension#right, configurez ces IDs complets séparément.

Par exemple, ceci fournit la configuration de base de l’extension :

{ "my-extension-name": { "customSetting": "base value" } }

La configuration de l’extension peut ensuite être remplacée par slot en utilisant l’objet configure dans la configuration du slot :

{ "@openmrs/esm-my-module": { "extensionSlots": { "my-slot": { "configure": { "my-extension-name": { "customSetting": "slot-specific value" } } } } } }

Référence au schéma

_default

Tous les éléments de configuration doivent avoir une valeur par défaut (à l’exception des éléments dans les tableaux ou les objets libres gouvernés par _elements).

La valeur par défaut n’a pas nécessairement besoin de satisfaire le _type ou les _validators de l’élément, mais cela pourrait changer dans les versions futures.

_type

Une des valeurs de l’enum Type. Utilisé pour la validation et pour aider les outils d’implémentation à travailler avec l’élément.

Doit toujours apparaître à côté de _default.

_description

Aide les implémenteurs à comprendre ce que l’élément de configuration fait réellement et comment il est censé être utilisé.

Peut être utilisé n’importe où dans la structure du schéma.

_validators

Un tableau d’objets validateurs.

Quelques validateurs courants sont fournis, notamment inRange, oneOf et isUrlWithTemplateParameters.

Les validateurs personnalisés doivent être créés avec la fonction validator.

Peut être utilisé n’importe où dans la structure du schéma.

_elements

Valable uniquement avec _type: Type.Array ou _type: Type.Object. Un _default doit également être fourni à ce niveau. Pour les tableaux, la valeur doit être un schéma pour chaque élément du tableau. Pour les objets libres, la valeur doit être un schéma pour chaque valeur de l’objet.

Documentation de l’API

Voir la documentation d’API générée pour defineConfigSchema, defineExtensionConfigSchema, getConfig, useConfig et validator.

Le RFC

Ce paquet a été établi à la suite de la RFC #14 . Ce document fournit la raison d’être et les principes directeurs du système de configuration.

Dernière mise à jour le