Skip to content

Commit 53fc7c1

Browse files
committed
New FHIR Viewer
1 parent e5cc9f3 commit 53fc7c1

File tree

12 files changed

+3428
-55
lines changed

12 files changed

+3428
-55
lines changed

.astro/data-store.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.15.3","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[]},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false},\"legacy\":{\"collections\":false}}"]
1+
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.16.0","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[]},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}"]

.astro/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"_variables": {
3-
"lastUpdateCheck": 1763653789860
3+
"lastUpdateCheck": 1765300660767
44
}
55
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
3+
// Props passed from MDX
4+
const {
5+
definition,
6+
src,
7+
filePath,
8+
id = "fhir-viewer",
9+
title
10+
} = Astro.props;
11+
import fs from 'node:fs/promises';
12+
import { existsSync } from 'fs';
13+
import FHIRSchemaViewer from '@components/SchemaExplorer/FHIRSchemaViewer'; // <-- path to your React component
14+
import {resolveProjectPath, getAbsoluteFilePathForAstroFile } from '@utils/files';
15+
16+
let schema;
17+
18+
try {
19+
const schemaPath = getAbsoluteFilePathForAstroFile(filePath, src);
20+
const exists = existsSync(schemaPath);
21+
if (exists) {
22+
schema = await fs.readFile(schemaPath, 'utf-8');
23+
schema = JSON.parse(schema);
24+
if (!schema) {
25+
throw new Error(`<FHIRViewer> needs either a "definition" object or a "src" JSON file path`);
26+
}
27+
}
28+
} catch (error) {
29+
console.log('Failed to process schemas');
30+
console.log(error);
31+
}
32+
---
33+
34+
<div class="my-4">
35+
<FHIRSchemaViewer
36+
structureDefinition={schema}
37+
title={title}
38+
/>
39+
</div>

eventcatalog/src/components/MDX/SchemaViewer/SchemaViewerRoot.astro

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ try {
1515
const absoluteFilePath = resolveProjectPath(filePath);
1616
const file = await fs.readFile(absoluteFilePath, 'utf-8');
1717
const schemaViewers = getMDXComponentsByName(file, 'SchemaViewer');
18-
1918
// Loop around all the possible SchemaViewers in the file.
2019
const getAllComponents = schemaViewers.map(async (schemaViewerProps: any, index: number) => {
2120
const schemaPath = getAbsoluteFilePathForAstroFile(filePath, schemaViewerProps.file);

eventcatalog/src/components/MDX/components.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import DrawIO from '@components/MDX/DrawIO/DrawIO.astro';
2727
import FigJam from '@components/MDX/FigJam/FigJam.astro';
2828
import Design from '@components/MDX/Design/Design.astro';
2929
import MermaidFileLoader from '@components/MDX/MermaidFileLoader/MermaidFileLoader.astro';
30+
import FHIRViewer from '@components/MDX/FHIRViewer/FHIRViewer.astro';
3031
// Portals: required for server/client components
3132
import NodeGraphPortal from '@components/MDX/NodeGraph/NodeGraphPortal';
3233
import SchemaViewerPortal from '@components/MDX/SchemaViewer/SchemaViewerPortal';
@@ -66,6 +67,7 @@ const components = (props: any) => {
6667
DrawIO: (mdxProp: any) => jsx(DrawIO, { ...props, ...mdxProp }),
6768
FigJam: (mdxProp: any) => jsx(FigJam, { ...props, ...mdxProp }),
6869
MermaidFileLoader: (mdxProp: any) => jsx(MermaidFileLoader, { ...props, ...mdxProp }),
70+
FHIRViewer: (mdxProp: any) => jsx(FHIRViewer, { ...props, ...mdxProp }),
6971
};
7072
};
7173

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import React, { useMemo } from "react";
2+
3+
import $RefParser from '@apidevtools/json-schema-ref-parser';
4+
import pkg from 'fhir-react';
5+
const {FhirResource, fhirVersions} = pkg;
6+
interface FHIRSchemaViewerProps {
7+
structureDefinition: any;
8+
title?: string;
9+
}
10+
11+
interface FhirElementNode {
12+
id: string;
13+
path: string;
14+
name: string;
15+
min: number;
16+
max: string;
17+
types: string []
18+
sliceName?: string;
19+
children: FhirElementNode[];
20+
}
21+
22+
/**
23+
* Take snapshot.element[] and transform the flat list
24+
* into a nested tree based on the element paths.
25+
*/
26+
function buildElementTree(elements: any[]): FhirElementNode[] {
27+
const nodeMap = new Map<string, FhirElementNode>();
28+
const roots: FhirElementNode[] = [];
29+
console.log(elements.length);
30+
// First pass: build nodes
31+
elements.forEach((el: any) => {
32+
const path = el.path;
33+
const parts = path.split(".");
34+
const name = el.sliceName ?? parts[parts.length - 1];
35+
const node: FhirElementNode = {
36+
id: el.id, // FHIR ID (with slice names)
37+
path, // hierarchical path
38+
name, // display name
39+
min: el.min ?? 0,
40+
max: el.max ?? "1",
41+
types: Array.isArray(el.type)
42+
? el.type.map((t: any) => t.code)
43+
: [],
44+
sliceName: el.sliceName,
45+
children: [],
46+
};
47+
48+
nodeMap.set(path, node);
49+
});
50+
51+
// Second pass: connect nodes into a tree
52+
elements.forEach((el: any) => {
53+
const path = el.path;
54+
const parts = path.split(".");
55+
const parentPath = parts.slice(0, -1).join(".");
56+
57+
const node = nodeMap.get(path);
58+
59+
if (parentPath && nodeMap.has(parentPath)) {
60+
nodeMap.get(parentPath)!.children.push(node!);
61+
} else {
62+
// no parent => top-level root
63+
roots.push(node!);
64+
}
65+
});
66+
67+
return roots;
68+
}
69+
70+
/**
71+
* Simple recursive UI renderer for FHIR element tree.
72+
*/
73+
import { useState } from "react";
74+
75+
function ElementNodeView({ node, depth }: { node: FhirElementNode; depth: number }) {
76+
const [expanded, setExpanded] = useState(depth === 0); // root expanded by default
77+
78+
const hasChildren = node.children && node.children.length > 0;
79+
80+
return (
81+
<div style={{ marginLeft: depth * 18 }}>
82+
<div
83+
style={{
84+
display: "flex",
85+
alignItems: "center",
86+
cursor: hasChildren ? "pointer" : "default",
87+
lineHeight: "1.5em",
88+
}}
89+
onClick={() => hasChildren && setExpanded(!expanded)}
90+
>
91+
{/* Expand / collapse triangle */}
92+
{hasChildren ? (
93+
<span style={{ width: 14, display: "inline-block", fontSize: 12 }}>
94+
{expanded ? "▼" : "▶"}
95+
</span>
96+
) : (
97+
<span style={{ width: 14, display: "inline-block" }} />
98+
)}
99+
100+
{/* Element path */}
101+
<span style={{ fontWeight: depth === 0 ? "bold" : "normal" }}>
102+
{node.path}
103+
</span>
104+
105+
{/* Min/max */}
106+
<span style={{ opacity: 0.6, marginLeft: 6 }}>
107+
({node.min}..{node.max})
108+
</span>
109+
110+
{/* Type info */}
111+
{(node.types?.length ?? 0) > 0 && (
112+
<span style={{ marginLeft: 8, fontSize: "0.9em", opacity: 0.8 }}>
113+
: {node.types!.join(" | ")}
114+
</span>
115+
)}
116+
</div>
117+
118+
{/* Child nodes */}
119+
{expanded &&
120+
hasChildren &&
121+
node.children.map((child) => (
122+
<ElementNodeView key={child.path} node={child} depth={depth + 1} />
123+
))}
124+
</div>
125+
);
126+
}
127+
/**
128+
* Main viewer component.
129+
*/
130+
export default function FHIRSchemaViewer({ structureDefinition, title }: FHIRSchemaViewerProps) {
131+
132+
133+
if (!structureDefinition) {
134+
return <div>No StructureDefinition provided.</div>;
135+
}
136+
137+
return (
138+
<div style={{ padding: "1rem", fontFamily: "Arial" }}>
139+
<h3 style={{ marginBottom: "0.5rem" }}>
140+
{title ?? structureDefinition.title ?? structureDefinition.name}
141+
</h3>
142+
143+
<div style={{ fontSize: "0.9rem", color: "#555", marginBottom: "1rem" }}>
144+
{structureDefinition.description}
145+
</div>
146+
147+
{/* Render root elements */}
148+
149+
<FhirResource
150+
fhirResource={structureDefinition}
151+
fhirVersion={fhirVersions.R4}
152+
/>
153+
154+
</div>
155+
);
156+
}

eventcatalog/src/components/SchemaExplorer/JSONSchemaViewer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,10 @@ async function processSchema(schema: any, rootSchema?: any): Promise<any> {
106106

107107
let dereferencedSchema = await $RefParser.dereference(schema, {
108108
dereference: {
109-
circular: 'ignore', // Don't allow circular $refs
109+
circular: true, // Don't allow circular $refs
110110
},
111111
});
112+
console.log(dereferencedSchema);
112113
return dereferencedSchema;
113114
}
114115

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
declare module 'fhir-react' {
2+
3+
import { ComponentType } from 'react';
4+
5+
6+
7+
export const fhirVersions: {
8+
9+
DSTU2: string;
10+
11+
STU3: string;
12+
13+
R4: string;
14+
15+
};
16+
17+
18+
19+
export interface FhirResourceProps {
20+
21+
fhirResource: any;
22+
23+
fhirVersion?: string;
24+
25+
withCarinBBProfile?: boolean;
26+
27+
}
28+
29+
30+
31+
export const FhirResource: ComponentType<FhirResourceProps>;
32+
33+
}

examples/default/domains/E-Commerce/subdomains/Orders/services/InventoryService/queries/GetInventoryList/index.mdx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,13 @@ import Footer from '@catalog/components/footer.astro';
1919

2020
The GetInventoryList message is a query used to retrieve a comprehensive list of all available inventory items within a system. It is designed to return detailed information about each item, such as product names, quantities, availability status, and potentially additional metadata like categories or locations. This query is typically utilized by systems or services that require a real-time view of current stock, ensuring that downstream applications or users have accurate and up-to-date information for decision-making or operational purposes. The GetInventoryList is ideal for use cases such as order processing, stock management, or reporting, providing visibility into the full range of inventory data.
2121

22-
<NodeGraph />
22+
<NodeGraph />
23+
24+
<FHIRViewer
25+
title="FHIR UK Core AllergyIntolerance (R4)"
26+
src="./schema.json"
27+
maxHeight="500"
28+
search="true"
29+
expand="false"
30+
/>
31+

0 commit comments

Comments
 (0)