Salta ai contenuti

Scrivere un plugin di attività

Un plugin di attività aggiunge un tipo di annotazione: cosa risponde un lettore (il body), come un amministratore del progetto imposta un’attività di quel tipo (la sua configurazione), il controllo del lettore e l’editor della procedura guidata. Questa guida costruisce il plugin modello, org.example.rating@1: una valutazione da una stella al massimo previsto dall’attività. Leggi prima I plugin e il catalogo dei plugin per sapere di cosa si affida a un plugin.

Finestra del terminale
cp -r examples/plugin-template my-plugin

Il modello è un pacchetto del workspace del repository: per la build ha bisogno di @yggdrasil/plugin-api (i tipi del contratto e il kit per autori) e di Vite. In src/manifest.json e src/index.tsx, cambia l’ID org.example.rating con uno nel tuo namespace (il contrario di un dominio che controlli); il namespace ygg è riservato.

src/index.tsx
export interface RatingBody { readonly stars: number } // what a reader answers
export interface RatingConfig { readonly max: number } // a task's settings

L’app non compila mai uno schema a runtime (la sua Content Security Policy vieta eval), quindi un plugin verifica body e configurazioni con funzioni. schemaCheck trasforma una funzione che elenca i problemi (un JSON pointer e un codice) nello SchemaCheck del contratto:

body: h.api.schemaCheck<RatingBody>("urn:org.example.rating:body:1", bodyProblems),
config: h.api.schemaCheck<RatingConfig>("urn:org.example.rating:config:1", configProblems),

I controlli che richiedono la configurazione vanno in validate (qui, al massimo max stelle). I body sono cifrati: li verificano solo le app dei membri, mai il server.

3. Scrivi il controllo del lettore e l’editor della procedura guidata

Sezione intitolata “3. Scrivi il controllo del lettore e l’editor della procedura guidata”

I componenti ricevono React, React Intl e il design system dall’host, quindi condividono il React dell’app e ne hanno l’aspetto:

/** @jsxImportSource @yggdrasil/plugin-api */
import { definePlugin, host } from "@yggdrasil/plugin-api/author";
function RatingInput({ config, value, onChange, label, required, disabled, errorMessage }) {
const { RadioGroup, Radio } = host().ui;
// …one radio button per star count; onChange(undefined) when cleared
}
  • Il controllo riceve la configurazione dell’attività, il valore corrente (undefined significa senza risposta), onChange, il nome dell’attività come label, se è obbligatoria, e il messaggio di errore dell’app. Segnala ogni modifica: l’app la salva automaticamente, in una bozza cifrata sul dispositivo e poi sul server, e invia l’attività quando il lettore la invia. Non c’è un pulsante Salva.
  • L’editor riceve la configurazione e segnala ogni modifica; defaultConfig è il punto di partenza di una nuova attività.
  • Il pragma @jsxImportSource @yggdrasil/plugin-api compila il JSX in chiamate al React dell’app. (Un plugin compilato fuori da questo repository può invece impostare "jsxImportSource": "@yggdrasil/plugin-api" nel suo tsconfig.json.)

definePlugin fornisce l’host alla factory e restituisce ciò che il plugin registra:

export default definePlugin((h) => ({ annotationTypes: [ratingType(h)] }));

Il tipo (AnnotationTypePlugin) indica anche:

Membro Cosa fa
levels I livelli di attività a cui si adatta: paziente, studio, serie, asset, regione.
modalities Plugin di modalità integrati (ygg.image2d@1, …) le cui attività a livello di serie possono usarlo. Senza, il tipo è offerto a livello di paziente e di studio, e sulle modalità che lo elencano.
selector Se una risposta porta con sé una forma. Un tipo con forma ne indica la geometria e le etichette (shape), e i visualizzatori la disegnano.
conditionValue Il valore confrontato dalle condizioni show-if.
aggregation, consensus Come si combinano le risposte di più lettori (qui la mediana).
agreement Come la pagina delle statistiche confronta i lettori (qui una scala a intervalli).
extent, compare Quanto è grande una risposta (un volume, un’area, una durata, un conteggio…) e come si confrontano a coppie le risposte di due lettori (sovrapposizione o distanza) quando nessuna scala si adatta.
display La risposta come testo, mostrata dal pannello delle attività fuori dalla modalità di annotazione (API dei plugin 1.8.0). Senza, il pannello mostra il controllo disabilitato.
numberKeys Cosa selezionano i tasti numerici.
references, guidelines Gli ID della configurazione usati dalle risposte, e le linee guida per singola voce, per i tipi con opzioni o etichette.

pnpm --filter @yggdrasil/plugin-template test renderizza il controllo e l’editor con l’harness di test dell’API dei plugin e un host costruito con le librerie reali, e verifica che il plugin fornisca esattamente ciò che il suo manifest dichiara.

Finestra del terminale
pnpm --filter @yggdrasil/plugin-template build

dist/ contiene poi il pacchetto: plugin.js (un modulo ES di pochi kilobyte, senza React al suo interno) e yggdrasil-plugin.json (src/manifest.json con l’integrità del modulo, scritto da scripts/package.ts). Ogni ricompilazione che cambia il modulo cambia l’integrità: dai a ogni release la sua versione.

Un superamministratore aggiunge entrambi i file nella sezione Plugin della console, controlla i dettagli e abilita il plugin (I plugin e il catalogo dei plugin). Ricarica l’app; il tipo compare allora tra i tipi di attività della procedura guidata di configurazione, e il manifest di un progetto può indicarlo:

{ "id": "quality", "name": "Image quality", "type": "org.example.rating@1", "level": "patient", "config": { "max": 5 } }

Il test end-to-end della piattaforma fa esattamente questo: compila il modello, lo aggiunge e lo abilita nella console, e un lettore risponde alla sua attività in un progetto.