Skip to Content
DocumentationGuides de migrationMigrer vers Workspace v2

Migrer vers Workspace v2

Workspace v2 est un système de workspaces repensé qui introduit un modèle hiérarchique de groupes, fenêtres et workspaces. Il remplace le modèle plat de workspace v1 par une approche structurée offrant un meilleur support des workspaces enfants, un suivi correct des modifications non enregistrées et une gestion de cycle de vie scopée.

Ce guide explique comment migrer les workspaces de votre module frontend de l’API v1 vers v2.

Contexte

Le système workspace v1 avait plusieurs limitations :

  • Structure plate — tous les workspaces partageaient un seul conteneur, rendant difficile la gestion de flux complexes en plusieurs étapes.
  • Suivi manuel des modifications non enregistrées — les workspaces devaient appeler promptBeforeClosing() de manière impérative.
  • Pas de support des workspaces enfants — les flux en plusieurs étapes (ex. panier de commandes → recherche de médicaments) nécessitaient des contournements ad-hoc.
  • Gestion des titres — les workspaces devaient appeler setTitle() pour mettre à jour leur titre.

Workspace v2 résout ces problèmes avec une hiérarchie à trois niveaux :

  • Groupe de workspaces — un conteneur de niveau supérieur qui possède le menu d’actions (siderail). Les groupes définissent la portée, le comportement d’overlay et la persistance.
  • Fenêtre de workspace — un panneau au sein d’un groupe qui peut être maximisé, minimisé ou masqué. Chaque fenêtre appartient à un groupe.
  • Workspace — le contenu réel rendu à l’intérieur d’une fenêtre. Les workspaces peuvent lancer des workspaces enfants dans la même fenêtre.

Concepts clés

Avant de migrer, il est utile de comprendre comment les trois niveaux sont liés :

  1. Un groupe de workspaces est déclaré dans workspaceGroups2. Il peut être ouvert explicitement avec launchWorkspaceGroup2, ou implicitement lorsque launchWorkspace2 ouvre un workspace qui appartient au groupe. Si le groupe possède des fenêtres avec icônes, il rend le menu d’actions (siderail) tant que le groupe est ouvert. Un seul groupe peut être ouvert à la fois.
  2. Une fenêtre de workspace est déclarée dans workspaceWindows2 et appartient à un groupe. Une fenêtre avec une icon correspond à un bouton du menu d’actions. L’icône est enregistrée comme une extension dont l’ID d’extension doit correspondre au nom de la fenêtre. C’est ainsi que ActionMenuButton2 sait quelle fenêtre il contrôle.
  3. Un workspace est déclaré dans workspaces2 et appartient à une fenêtre. Les workspaces sont les panneaux de contenu réels. Une fenêtre peut contenir une pile de workspaces (parent + enfants).

Les props de groupe fournissent un contexte partagé (ex. patient actuel, visite) à tous les workspaces du groupe. Les props de fenêtre et les props de workspace sont scopées à leurs niveaux respectifs. Lorsque l’une de ces props change de manière incompatible, le système invite l’utilisateur à fermer les workspaces avec des modifications non enregistrées.

Étapes de migration

Mettre à jour routes.json

Remplacez la section workspaces dans votre routes.json par les nouvelles sections workspaces2, workspaceWindows2 et workspaceGroups2.

Avant (v1) :

{ "workspaces": [ { "name": "my-form-workspace", "component": "myFormWorkspace", "title": "myFormTitle", "type": "form" } ] }

Après (v2) :

{ "workspaces2": [ { "name": "my-form-workspace", "component": "myFormWorkspace", "window": "my-form-window" } ], "workspaceWindows2": [ { "name": "my-form-window", "group": "my-workspace-group" } ], "workspaceGroups2": [ { "name": "my-workspace-group" } ] }

Différences clés :

  • Les propriétés title et type sont supprimées des définitions de workspace. Les titres sont désormais définis par le composant wrapper <Workspace2>.
  • Chaque workspace doit spécifier la window à laquelle il appartient.
  • Chaque fenêtre doit spécifier le group auquel elle appartient.
  • Les groupes peuvent optionnellement configurer les propriétés overlay, persistence et scopePattern.

Si les workspaces de votre application appartiennent à un groupe possédé par une autre application (ex. le groupe patient-chart), vous n’avez besoin de déclarer que workspaces2 et workspaceWindows2. Vous n’avez pas besoin de déclarer workspaceGroups2 — c’est la responsabilité de l’application qui possède le groupe.

Options de fenêtre de workspace

Les fenêtres supportent les propriétés optionnelles suivantes :

PropriétéTypeDescription
canMaximizebooleanSi la fenêtre peut être maximisée
groupstringObligatoire. Le groupe auquel cette fenêtre appartient
iconstringComposant d’icône de menu d’actions exporté pour cette fenêtre
ordernumberOrdre d’affichage dans le menu d’actions
width"narrow" | "wider" | "extra-wide"Largeur du panneau de workspace

Options de groupe de workspaces

Les groupes supportent les propriétés optionnelles suivantes :

PropriétéTypeDescription
overlaybooleanSi les workspaces se rendent en overlay
persistence"app-wide" | "closable"Mode de persistance du cycle de vie. Utilisez "closable" pour rendre le menu d’actions du groupe avec un bouton de fermeture.
scopePatternstringModèle d’URL définissant où les workspaces persistent

Si persistence est omis, le groupe se comporte comme un groupe app-wide : il peut garder plusieurs fenêtres ouvertes et ne se ferme que lors d’un changement d’app ou de scopePattern. Utilisez "closable" pour les flux ciblés où le menu d’actions doit inclure un bouton de fermeture. Dans un groupe closable, lancer une autre fenêtre vérifie les workspaces concernés pour détecter les changements non enregistrés puis ferme les autres fenêtres de ce groupe.

Initialiser le groupe de workspaces uniquement si nécessaire

Déclarer un groupe dans workspaceGroups2 ne signifie pas automatiquement que vous devez appeler launchWorkspaceGroup2. Un appel normal à launchWorkspace2 ouvre le groupe du workspace, la fenêtre cible et le workspace s’ils ne sont pas déjà ouverts.

Appelez launchWorkspaceGroup2 depuis l’application propriétaire lorsque le groupe doit être actif avant le premier lancement de workspace, ou lorsque le groupe a besoin de props de contexte partagées avant que les boutons du menu d’actions et les workspaces ne soient rendus. Patient-chart le fait pour que le contexte patient et visite soit disponible à tous les workspaces du groupe patient-chart.

import { launchWorkspaceGroup2 } from "@openmrs/esm-framework"; // Dans le composant racine de votre application : useEffect(() => { launchWorkspaceGroup2("patient-chart", { patient, patientUuid, visitContext, mutateVisitContext, }); }, [patient, patientUuid, visitContext, mutateVisitContext]);

Un seul groupe de workspaces peut être ouvert à la fois. Si launchWorkspaceGroup2 est appelé avec un nom de groupe différent (ou des props incompatibles), O3 vérifie les workspaces ouverts pour détecter des changements non enregistrés et demande confirmation si nécessaire avant d’ouvrir le nouveau groupe.

La plupart des modules n’ont pas besoin de cette étape. Les modules qui ajoutent des workspaces à un groupe existant, comme patient-chart, doivent ignorer entièrement l’initialisation du groupe. Les modules qui possèdent un groupe simple peuvent aussi s’appuyer sur launchWorkspace2, sauf si le groupe a besoin d’un contexte app-wide ou d’un menu d’actions avant l’ouverture d’un workspace.

Mettre à jour les signatures des composants workspace

Les composants workspace reçoivent des props différentes en v2. Le changement principal est que les props personnalisées sont désormais imbriquées sous workspaceProps au lieu d’être étalées en props de niveau supérieur, et le contexte partagé est disponible via groupProps.

Avant (v1) :

import { type DefaultWorkspaceProps } from "@openmrs/esm-framework"; interface MyFormProps extends DefaultWorkspaceProps { patientUuid: string; appointment?: Appointment; } const MyForm: React.FC<MyFormProps> = ({ patientUuid, appointment, closeWorkspace, closeWorkspaceWithSavedChanges, promptBeforeClosing, setTitle, }) => { // ... };

Après (v2) :

import { Workspace2, type Workspace2DefinitionProps, } from "@openmrs/esm-framework"; interface MyFormProps { patientUuid: string; appointment?: Appointment; } export default function MyForm({ workspaceProps, groupProps, closeWorkspace, }: Workspace2DefinitionProps<MyFormProps>) { const { patientUuid, appointment } = workspaceProps ?? {}; return ( <Workspace2 title={t("myForm", "Mon formulaire")} hasUnsavedChanges={isDirty}> {/* contenu du workspace */} </Workspace2> ); }

Changements clés :

  • Remplacer DefaultWorkspaceProps par Workspace2DefinitionProps<VosProps>. Le générique accepte jusqu’à trois paramètres de type : <WorkspaceProps, WindowProps, GroupProps>.
  • Les props personnalisées sont accessibles via workspaceProps au lieu d’être étalées au niveau supérieur. Notez que workspaceProps, windowProps et groupProps peuvent être null.
  • Le contexte partagé (ex. patient actuel) est disponible via groupProps au lieu d’être passé à chaque workspace individuellement.
  • promptBeforeClosing et setTitle sont supprimés. À la place, passez hasUnsavedChanges et title comme props au composant wrapper <Workspace2>.
  • closeWorkspaceWithSavedChanges est remplacé par closeWorkspace({ discardUnsavedChanges: true }).
  • closeWorkspace accepte également { closeWindow: true } pour fermer la fenêtre entière (tous les workspaces qu’elle contient), pas seulement le workspace actuel.
  • Enveloppez le contenu de votre workspace dans <Workspace2>, qui rend la barre d’en-tête du workspace avec le titre et les boutons maximiser/minimiser et fermer.

Mettre à jour les appels de lancement de workspace

Remplacez launchWorkspace par launchWorkspace2. La signature complète est :

launchWorkspace2<WorkspaceProps, WindowProps, GroupProps>( workspaceName: string, workspaceProps?: WorkspaceProps | null, windowProps?: WindowProps | null, groupProps?: GroupProps | null, ): Promise<boolean>

L’option workspaceTitle n’est plus acceptée — les titres sont définis par le composant workspace lui-même.

Avant (v1) :

launchWorkspace("my-form-workspace", { patientUuid, appointment, workspaceTitle: t("editAppointment", "Modifier le rendez-vous"), });

Après (v2) :

launchWorkspace2("my-form-workspace", { patientUuid, appointment, });

Pour lancer un workspace nécessitant des props de fenêtre ou de groupe, passez-les comme arguments supplémentaires :

launchWorkspace2( "start-visit-workspace-form", { openedFrom: "patient-chart-start-visit" }, // props du workspace {}, // props de la fenêtre { patient, patientUuid: patient.id, visitContext: null, mutateVisitContext }, // props du groupe );

Mettre à jour les boutons du menu d’actions

Remplacez ActionMenuButton par ActionMenuButton2. Le nouveau composant remplace handler et type par un objet workspaceToLaunch. Il gère également automatiquement la visibilité de la fenêtre — cliquer sur le bouton masquera, restaurera ou lancera le workspace selon l’état actuel.

Référencez le composant de bouton depuis la propriété icon de la fenêtre propriétaire dans workspaceWindows2. Le framework enregistre cette icône comme une extension dont l’ID est le nom de la fenêtre.

Avant (v1) :

<ActionMenuButton getIcon={(props) => <EditIcon {...props} />} label={t("edit", "Modifier")} iconDescription={t("edit", "Modifier")} handler={() => launchWorkspace("my-form-workspace", { patientUuid })} type="form" />

Après (v2) :

<ActionMenuButton2 icon={(props) => <EditIcon {...props} />} label={t("edit", "Modifier")} workspaceToLaunch={{ workspaceName: "my-form-workspace", workspaceProps: { patientUuid }, }} />

ActionMenuButton2 supporte également un callback optionnel onBeforeWorkspaceLaunch qui peut empêcher l’ouverture du workspace (par exemple pour inviter l’utilisateur à démarrer une visite d’abord) :

<ActionMenuButton2 icon={(props) => <DocumentIcon {...props} />} label={t("clinicalForms", "Formulaires cliniques")} workspaceToLaunch={{ workspaceName: "clinical-forms-workspace", workspaceProps: {}, }} onBeforeWorkspaceLaunch={startVisitIfNeeded} />

Mettre à jour les patterns de workspaces enfants

Si votre workspace lance des sous-workspaces (ex. un panier de commandes qui ouvre une recherche de médicaments), utilisez la prop launchChildWorkspace au lieu d’appeler launchWorkspace directement.

Avant (v1) :

const OrderBasket: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace, }) => { const handleAddDrug = () => { launchWorkspace("drug-search-workspace", { onSelect: handleDrugSelect }); }; // ... };

Après (v2) :

function OrderBasket({ closeWorkspace, launchChildWorkspace, }: Workspace2DefinitionProps<OrderBasketProps>) { const handleAddDrug = () => { launchChildWorkspace("drug-search-workspace", { onSelect: handleDrugSelect, }); }; // ... }

Les workspaces enfants sont rendus dans la même fenêtre et sont automatiquement fermés lorsque leur workspace parent est fermé.

Mettre à jour les tests

Les tests doivent fournir la nouvelle structure de props lors du rendu des composants workspace.

Avant (v1) :

const defaultProps = { patientUuid: mockPatient.id, closeWorkspace: vi.fn(), closeWorkspaceWithSavedChanges: vi.fn(), promptBeforeClosing: vi.fn(), setTitle: vi.fn(), }; render(<MyForm {...defaultProps} />);

Après (v2) :

const defaultProps: Workspace2DefinitionProps<MyFormProps> = { workspaceProps: { patientUuid: mockPatient.id, }, closeWorkspace: vi.fn(), launchChildWorkspace: vi.fn(), groupProps: null, windowProps: null, workspaceName: "my-form-workspace", windowName: "my-form-window", isRootWorkspace: true, showActionMenu: false, }; render(<MyForm {...defaultProps} />);

Exemples de migration

Dernière mise à jour le