Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support User Provided Examples of Logical Models #941

Merged
merged 5 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/fhirdefs/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ export function loadCustomResources(resourceDir: string, defs: FHIRDefinitions):
continue;
}
} catch (e) {
if (e.message.startsWith('Unknown resource type:')) {
// Skip unknown FHIR resource types. When we have instances of Logical Models,
// the resourceType will not be recognized as a known FHIR resourceType, but that's okay.
continue;
}
logger.error(`Loading ${file} failed with the following error:\n${e.message}`);
continue;
}
Expand Down
9 changes: 8 additions & 1 deletion src/fhirtypes/ImplementationGuide.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ContactDetail, UsageContext } from './metaDataTypes';
import { CodeableConcept, Reference, BackboneElement, DomainResource } from './dataTypes';
import {
BackboneElement,
CodeableConcept,
DomainResource,
Extension,
Reference
} from './dataTypes';

export type ImplementationGuide = DomainResource & {
resourceType: 'ImplementationGuide';
Expand Down Expand Up @@ -60,6 +66,7 @@ export type ImplementationGuideDefinitionResource = {
exampleBoolean?: boolean;
exampleCanonical?: string;
groupingId?: string;
extension?: Extension[];
};

export type ImplementationGuideDefinitionPage = {
Expand Down
19 changes: 18 additions & 1 deletion src/ig/IGExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,24 @@ export class IGExporter {
resource => resource.reference?.reference == referenceKey
);

if (configResource?.omit !== true) {
// For predefined examples of Logical Models, the user must provide an entry in config
// that specifies the reference as Binary/[id], the extension that specifies the resource format,
// and the exampleCanonical that references the LogicalModel the resource is an example of.
// In that case, we do not want to add our own entry for the predefined resource - we just
// want to use the resource entry from the sushi-config.yaml
const hasBinaryExampleReference = (this.config.resources ?? []).some(
resource =>
resource.reference?.reference === `Binary/${resourceJSON.id}` &&
resource.exampleCanonical ===
`${this.config.canonical}/StructureDefinition/${resourceJSON.resourceType}` &&
resource.extension?.some(
e =>
e.url ===
'http://hl7.org/fhir/StructureDefinition/implementationguide-resource-format'
)
);

if (configResource?.omit !== true && !hasBinaryExampleReference) {
const existingIndex = this.ig.definition.resource.findIndex(
r => r.reference.reference === referenceKey
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"resourceType": "CustomLogicalModel",
"id": "example-logical-model"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<CustomLogicalModel>
<id value="custom-logical-model" />
</CustomLogicalModel>
11 changes: 11 additions & 0 deletions test/fhirdefs/load.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,11 @@ describe('#loadCustomResources', () => {
expect(
defs.getPredefinedResource(path.join(pathToInput, 'examples', 'Patient-MyPatient.json')).id
).toBe('MyPatient');
expect(
defs.getPredefinedResource(
path.join(pathToInput, 'examples', 'Binary-LogicalModelExample.json')
).id
).toBe('example-logical-model');
expect(
defs.getPredefinedResource(
path.join(pathToInput, 'extensions', 'StructureDefinition-patient-birthPlace.json')
Expand Down Expand Up @@ -507,6 +512,12 @@ describe('#loadCustomResources', () => {
});
});

it('should not log an error for invalid FHIR types parsed from XML', () => {
loggerSpy.getAllMessages('error').forEach(m => {
expect(m).not.toMatch(/Unknown resource type:/);
});
});

it('should log an info message when it finds spreadsheets', () => {
expect(loggerSpy.getFirstMessage('info')).toMatch(/Found spreadsheets in directory/);
});
Expand Down
106 changes: 106 additions & 0 deletions test/ig/IGExporter.IG.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,112 @@ describe('IGExporter', () => {
});
});

describe('#customized-ig-with-logical-model-example', () => {
let pkg: Package;
let exporter: IGExporter;
let tempOut: string;
let fixtures: string;
let config: Configuration;
let defs: FHIRDefinitions;

beforeAll(() => {
defs = new FHIRDefinitions();
loadFromPath(
path.join(__dirname, '..', 'testhelpers', 'testdefs', 'package'),
'testPackage',
defs
);
fixtures = path.join(__dirname, 'fixtures', 'customized-ig-with-logical-model-example');
loadCustomResources(path.join(fixtures, 'input'), defs);
});

beforeEach(() => {
loggerSpy.reset();
tempOut = temp.mkdirSync('sushi-test');
config = cloneDeep(minimalConfig);
config.resources = [
{
reference: {
reference: 'Binary/example-logical-model-json'
},
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/implementationguide-resource-format',
valueCode: 'application/json'
}
],
name: 'Example of LM JSON',
exampleCanonical: `${config.canonical}/StructureDefinition/MyLM`
},
{
reference: {
reference: 'Binary/example-logical-model-xml'
},
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/implementationguide-resource-format',
valueCode: 'application/xml'
}
],
name: 'Example of LM XML',
exampleCanonical: `${config.canonical}/StructureDefinition/MyLM`
}
];
pkg = new Package(config);
exporter = new IGExporter(pkg, defs, fixtures);
});

afterAll(() => {
temp.cleanupSync();
});

it('should add logical model and example resource references to the ImplementationGuide resource', () => {
exporter.export(tempOut);
const igPath = path.join(
tempOut,
'fsh-generated',
'resources',
'ImplementationGuide-fhir.us.minimal.json'
);
expect(fs.existsSync(igPath)).toBeTruthy();
const igContent: ImplementationGuide = fs.readJSONSync(igPath);
expect(igContent.definition.resource).toHaveLength(3);
expect(igContent.definition.resource).toContainEqual({
reference: {
reference: 'StructureDefinition/MyLM'
},
name: 'MyLM',
exampleBoolean: false
});
expect(igContent.definition.resource).toContainEqual({
reference: {
reference: 'Binary/example-logical-model-json'
},
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/implementationguide-resource-format',
valueCode: 'application/json'
}
],
name: 'Example of LM JSON',
exampleCanonical: `${config.canonical}/StructureDefinition/MyLM`
});
expect(igContent.definition.resource).toContainEqual({
reference: {
reference: 'Binary/example-logical-model-xml'
},
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/implementationguide-resource-format',
valueCode: 'application/xml'
}
],
name: 'Example of LM XML',
exampleCanonical: `${config.canonical}/StructureDefinition/MyLM`
});
});
});

describe('#pages-folder-ig', () => {
let pkg: Package;
let exporter: IGExporter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"resourceType": "MyLM",
"id": "example-logical-model-json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<MyLM>
<id value="example-logical-model-xml" />
</MyLM>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"resourceType": "StructureDefinition",
"id": "MyLM",
"url": "http://hl7.org/fhir/us/mcode/StructureDefinition/MyLM",
"name": "MyLM",
"status": "active",
"fhirVersion": "4.0.1",
"type": "MyLM",
"baseDefinition" : "http://hl7.org/fhir/StructureDefinition/Element",
"kind": "logical",
"derivation": "specialization",
"snapshot": {
"element": [
{
"id": "modeling-woo",
"path": "modeling-woo",
"type": [
{
"code": "boolean"
}
]
}
]
}
}