Repository: FHIR/sushi
Pull request #666 (2020-11-13 01:34:45)

Use name and description extensions to set values in generated IG
Fixes https://github.com/FHIR/sushi/issues/654 and addresses [CIMPL-591](https://standardhealthrecord.atlassian.net/browse/CIMPL-591).

If a BYO resource has an extension on `meta` with a URL of "http://hl7.org/fhir/StructureDefinition/instance-name", then the value of that extension is used to populate `ImplementationGuide.definition.resource.name`. If this extension is found on a resource that has a `title` field, then it is ignored. If the resource has an extension on `meta` with a URL of "http://hl7.org/fhir/StructureDefinition/instance-description", then the value of that extension is used to populate `ImplementationGuide.definition.resource.description` for that resource. If the extension is found on a resource that has a `description` field, then it is ignored. This is per the description given here: https://jira.hl7.org/browse/FHIR-29406.

If a resource has a configured name and/or description, that value is preferred, since I think it is less likely someone adds those values to their configuration by accident.

To test this, you can make a sample IG with resources like this:
```
{
  "resourceType": "Patient",
  "meta": {
    "extension": [
      {
        "url": "http://hl7.org/fhir/StructureDefinition/instance-description",
        "valueString": "Patient description"
      },
      {
        "url": "http://hl7.org/fhir/StructureDefinition/instance-name",
        "valueString": "Patient name"
      }
    ]
  },
  "id": "MyPatient",
  "name": {
    "family": "Test"
  }
}
```
in the non-fsh `input/*` folders.


Patch:
diff --git a/src/fhirtypes/dataTypes.ts b/src/fhirtypes/dataTypes.ts
index 4853520..8bc66ff 100644
--- a/src/fhirtypes/dataTypes.ts
+++ b/src/fhirtypes/dataTypes.ts
@@ -73,6 +73,7 @@ export type Element = {
  */
 export type Extension = {
   url: string;
+  [key: string]: any;
 };
 
 /**
diff --git a/src/fhirtypes/specialTypes.ts b/src/fhirtypes/specialTypes.ts
index 6006086..8756b13 100644
--- a/src/fhirtypes/specialTypes.ts
+++ b/src/fhirtypes/specialTypes.ts
@@ -1,4 +1,4 @@
-import { Coding } from './dataTypes';
+import { Coding, Extension } from './dataTypes';
 
 /**
  * Type to represent FHIR R4 resource metadata.
@@ -14,4 +14,5 @@ export type Meta = {
   profile?: string[];
   security?: Coding[];
   tag?: Coding[];
+  extension?: Extension[];
 };
diff --git a/src/ig/IGExporter.ts b/src/ig/IGExporter.ts
index 25912ef..acce5b7 100644
--- a/src/ig/IGExporter.ts
+++ b/src/ig/IGExporter.ts
@@ -986,8 +986,13 @@ export class IGExporter {
                 existingResource?.exampleBoolean || existingResource?.exampleCanonical;
               const existingName = existingIsExample ? existingResource.name : null;
               const existingDescription = existingIsExample ? existingResource.description : null;
+
+              const metaExtensionDescription = this.getMetaExtensionDescription(resourceJSON);
+              const metaExtensionName = this.getMetaExtensionName(resourceJSON);
+
               newResource.description =
                 configResource?.description ??
+                metaExtensionDescription ??
                 existingDescription ??
                 (CONFORMANCE_AND_TERMINOLOGY_RESOURCES.has(resourceJSON.resourceType)
                   ? resourceJSON.description
@@ -1000,7 +1005,8 @@ export class IGExporter {
                 this.addGroup(newResource.groupingId);
               }
               if (pathEnd === 'examples') {
-                newResource.name = configResource?.name ?? existingName ?? resourceJSON.id;
+                newResource.name =
+                  configResource?.name ?? metaExtensionName ?? existingName ?? resourceJSON.id;
                 // set exampleCanonical or exampleBoolean, preferring configured values
                 if (configResource?.exampleCanonical) {
                   newResource.exampleCanonical = configResource.exampleCanonical;
@@ -1031,6 +1037,7 @@ export class IGExporter {
                 const name = typeof resourceJSON.name === 'string' ? resourceJSON.name : null;
                 newResource.name =
                   configResource?.name ??
+                  metaExtensionName ??
                   existingResource?.name ??
                   title ??
                   name ??
@@ -1143,6 +1150,24 @@ export class IGExporter {
     }
   }
 
+  private getMetaExtensionDescription(resource: InstanceDefinition): string {
+    const description = resource.meta?.extension?.find(
+      e => e.url === 'http://hl7.org/fhir/StructureDefinition/instance-description'
+    )?.valueMarkdown;
+    if (!CONFORMANCE_AND_TERMINOLOGY_RESOURCES.has(resource.resourceType)) {
+      return description;
+    }
+  }
+
+  private getMetaExtensionName(resource: InstanceDefinition): string {
+    const name = resource.meta?.extension?.find(
+      e => e.url === 'http://hl7.org/fhir/StructureDefinition/instance-name'
+    )?.valueString;
+    if (!CONFORMANCE_AND_TERMINOLOGY_RESOURCES.has(resource.resourceType)) {
+      return name;
+    }
+  }
+
   /**
    * Writes the in-memory ImplementationGuide JSON to the IG output folder.
    *


Test patch:
diff --git a/test/ig/IGExporter.IG.test.ts b/test/ig/IGExporter.IG.test.ts
index 3623ded..ee9067f 100644
--- a/test/ig/IGExporter.IG.test.ts
+++ b/test/ig/IGExporter.IG.test.ts
@@ -867,12 +867,16 @@ describe('IGExporter', () => {
         'StructureDefinition-MyPatient.json',
         'StructureDefinition-MyTitlePatient.json'
       ]);
-      expect(directoryContents.get('resources')).toEqual(['Patient-BazPatient.json']);
+      expect(directoryContents.get('resources')).toEqual([
+        'Patient-BazPatient.json',
+        'Patient-MetaExtensionNotExamplePatient.json'
+      ]);
       expect(directoryContents.get('vocabulary')).toEqual(['ValueSet-MyVS.json']);
       expect(directoryContents.get('examples')).toEqual([
         'Goal-GoalWithDescription.json',
         'Patient-BarPatient.json',
-        'Patient-FooPatient.json' // Renamed from "PoorlyNamedPatient.json"
+        'Patient-FooPatient.json', // Renamed from "PoorlyNamedPatient.json"
+        'Patient-MetaExtensionPatient.json'
       ]);
     });
   });
@@ -1117,6 +1121,104 @@ describe('IGExporter', () => {
       });
     });
 
+    it('should add resource references with a description and name extension 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).toContainEqual({
+        reference: {
+          reference: 'Patient/MetaExtensionPatient'
+        },
+        name: 'MetaExtension Patient Name',
+        description: 'MetaExtension Patient Description',
+        exampleBoolean: true
+      });
+    });
+
+    it('should prefer configured names and descriptions to extensions on resource references', () => {
+      config.resources = [
+        {
+          reference: {
+            reference: 'Patient/MetaExtensionPatient'
+          },
+          name: 'Configured Name',
+          description: 'Configured Description'
+        }
+      ];
+      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).toContainEqual({
+        reference: {
+          reference: 'Patient/MetaExtensionPatient'
+        },
+        name: 'Configured Name',
+        description: 'Configured Description',
+        exampleBoolean: true
+      });
+    });
+
+    it('should add non example resource references with a description and name extension 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).toContainEqual({
+        reference: {
+          reference: 'Patient/MetaExtensionNotExamplePatient'
+        },
+        name: 'MetaExtensionNotExample Patient Name',
+        description: 'MetaExtensionNotExample Patient Description',
+        exampleBoolean: false
+      });
+    });
+
+    it('should prefer non example configured names and descriptions to extensions on resource references', () => {
+      config.resources = [
+        {
+          reference: {
+            reference: 'Patient/MetaExtensionNotExamplePatient'
+          },
+          name: 'Configured Name',
+          description: 'Configured Description'
+        }
+      ];
+      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).toContainEqual({
+        reference: {
+          reference: 'Patient/MetaExtensionNotExamplePatient'
+        },
+        name: 'Configured Name',
+        description: 'Configured Description',
+        exampleBoolean: false
+      });
+    });
+
     it('should omit predefined resources that are configured to be omitted', () => {
       config.resources = [
         {
diff --git a/test/ig/fixtures/customized-ig-with-resources/ig-data/input/examples/Patient-MetaExtensionPatient.json b/test/ig/fixtures/customized-ig-with-resources/ig-data/input/examples/Patient-MetaExtensionPatient.json
new file mode 100644
index 0000000..e9274cb
--- /dev/null
+++ b/test/ig/fixtures/customized-ig-with-resources/ig-data/input/examples/Patient-MetaExtensionPatient.json
@@ -0,0 +1,17 @@
+{
+  "resourceType": "Patient",
+  "id": "MetaExtensionPatient",
+  "meta": {
+    "extension": [
+      {
+        "url": "http://hl7.org/fhir/StructureDefinition/instance-description",
+        "valueMarkdown": "MetaExtension Patient Description"
+      },
+      {
+        "url": "http://hl7.org/fhir/StructureDefinition/instance-name",
+        "valueString": "MetaExtension Patient Name"
+      }
+    ]
+  },
+  "name": [{ "family": "MetaExtension" }]
+}
\ No newline at end of file
diff --git a/test/ig/fixtures/customized-ig-with-resources/ig-data/input/resources/MetaExtensionNotExamplePatient.json b/test/ig/fixtures/customized-ig-with-resources/ig-data/input/resources/MetaExtensionNotExamplePatient.json
new file mode 100644
index 0000000..fc2bf53
--- /dev/null
+++ b/test/ig/fixtures/customized-ig-with-resources/ig-data/input/resources/MetaExtensionNotExamplePatient.json
@@ -0,0 +1,17 @@
+{
+  "resourceType": "Patient",
+  "id": "MetaExtensionNotExamplePatient",
+  "meta": {
+    "extension": [
+      {
+        "url": "http://hl7.org/fhir/StructureDefinition/instance-description",
+        "valueMarkdown": "MetaExtensionNotExample Patient Description"
+      },
+      {
+        "url": "http://hl7.org/fhir/StructureDefinition/instance-name",
+        "valueString": "MetaExtensionNotExample Patient Name"
+      }
+    ]
+  },
+  "name": [{ "family": "MetaExtensionNotExample" }]
+}
\ No newline at end of file
diff --git a/test/ig/fixtures/customized-ig-with-resources/ig-data/input/vocabulary/ValueSet-MyVS.json b/test/ig/fixtures/customized-ig-with-resources/ig-data/input/vocabulary/ValueSet-MyVS.json
index 205adb3..0498df0 100644
--- a/test/ig/fixtures/customized-ig-with-resources/ig-data/input/vocabulary/ValueSet-MyVS.json
+++ b/test/ig/fixtures/customized-ig-with-resources/ig-data/input/vocabulary/ValueSet-MyVS.json
@@ -1,6 +1,18 @@
 {
   "resourceType": "ValueSet",
   "id": "MyVS",
+  "meta": {
+    "extension": [
+      {
+        "url": "http://hl7.org/fhir/StructureDefinition/instance-description",
+        "valueMarkdown": "Ignored Description"
+      },
+      {
+        "url": "http://hl7.org/fhir/StructureDefinition/instance-name",
+        "valueString": "Ignored Name"
+      }
+    ]
+  },
   "url": "http://hl7.org/fhir/ValueSet/MyVS",
   "version": "4.0.1",
   "name": "Yes/No/Don't Know",