diff --git a/README.md b/README.md index b8fa832..42e776a 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,9 @@ components of a test case file are: scenario within the case. Every test object includes: - **Title** (`title` attribute): A unique, descriptive title for the test object, differentiating it from others in the same test case. + - **Tags** (`tags` attribute): A list of strings that categorize the test + case into relevant groups. This attribute helps in organizing and + filtering test cases based on their scope or focus. See [Reserved Tags](#reserved-tags). - **ViewDefinition** (`view` attribute): Specifies the [ViewDefinition][] being tested. This attribute outlines the expected data view or transformation applied to the input fixtures. @@ -129,6 +132,7 @@ Below is an abstract representation of what a test case file might look like: ... { 'title': 'title of test case', + 'tags': ['shareable'], // ViewDefintion 'view': { 'select': [ @@ -145,6 +149,15 @@ Below is an abstract representation of what a test case file might look like: } ``` +### Reserved Tags + +The following tags are reserved for categorizing test cases based on their +applicability to profiles within the core specification: + +- **shareable**: Test cases that validate conformance with the Shareable View Definition profile. +- **tabular**: Test cases that validate conformance with the Tabular View Definition profile. +- **experimental**: Test cases that cover experimental aspects of the specification. + ## Implement Test Runner To ensure comprehensive validation and interoperability, it is recommended for diff --git a/input/fsh/profiles.fsh b/input/fsh/profiles.fsh index 66ce36b..efcf57c 100644 --- a/input/fsh/profiles.fsh +++ b/input/fsh/profiles.fsh @@ -16,13 +16,10 @@ Profile: ShareableViewDefinition Title: "Shareable View Definition" Parent: ViewDefinition Description: """ -A profile for View Definitions intended to be shared between multiple systems. This requires there -be a defined URL, name, and version. Also, each column must have specified type so consuming -systems - -Shareable View Definitions often also use the TabularViewDefinition profile, requiring -that the view be shareable and contain only tabular values, as is common in many databases -and analytic tools. +A profile for View Definitions intended to be shared between multiple systems. This requires that +the View Definition have a defined URL and name. It also requires declaration of the FHIR version +that the view is intended to be executed over, and the FHIR type of each column. This ensures +consistent interpretation of the view across different view runner implementations. """ * url 1..1 * name 1..1 @@ -34,8 +31,8 @@ Title: "Tabular View Definition" Parent: ViewDefinition Description: """ A profile for View Definitions where each resulting field must contain only a simple scalar value. -This is sometimes referred to as 'CSV Mode', but applies to any system that explicitly constrains its -views or tables to tabluar data. +This is sometimes referred to as 'CSV Mode', but applies to any system that explicitly constrains +its views or tables to tabular data. """ * select.column obeys no-collections -* select.column obeys primitives-only \ No newline at end of file +* select.column obeys primitives-only diff --git a/input/pagecontent/StructureDefinition-ShareableViewDefinition-notes.md b/input/pagecontent/StructureDefinition-ShareableViewDefinition-notes.md new file mode 100644 index 0000000..ce26914 --- /dev/null +++ b/input/pagecontent/StructureDefinition-ShareableViewDefinition-notes.md @@ -0,0 +1,30 @@ +### Required FHIRPath Expressions/Functions + +All View Runners claiming conformance to the Shareable View Definition profile must implement these [FHIRPath](https://hl7.org/fhirpath/) +capabilities: + +- [Literals](https://hl7.org/fhirpath/#literals) for String, Integer and Decimal +- [where](https://hl7.org/fhirpath/#wherecriteria-expression-collection)function +- [exists](https://hl7.org/fhirpath/#existscriteria-expression-boolean) function +- [empty](https://hl7.org/fhirpath/#empty-boolean) function +- [extension](https://hl7.org/fhir/R4/fhirpath.html#functions) function +- [ofType](https://hl7.org/fhirpath/#oftypetype-type-specifier-collection) + function +- [first](https://hl7.org/fhirpath/#first-collection) function +- Boolean + operators: [and](https://hl7.org/fhirpath/#and), [or](https://hl7.org/fhirpath/#or), [not](https://hl7.org/fhirpath/#not-boolean) +- Math + operators: [addition (+)](https://hl7.org/fhirpath/#addition), [subtraction (-)](https://hl7.org/fhirpath/#subtraction), [multiplication (\*)](https://hl7.org/fhirpath/#multiplication), [division (/)](https://hl7.org/fhirpath/#division) +- Comparison + operators: [equals (=)](https://hl7.org/fhirpath/#equals), [not equals (!=)](https://hl7.org/fhirpath/#not-equals), [greater than (>)](https://hl7.org/fhirpath/#greater-than), [less or equal (<=)](https://hl7.org/fhirpath/#less-or-equal) +- [Indexer expressions](https://hl7.org/fhirpath/#index-integer-collection) + +### Experimental FHIRPath Functions + +The following functions are intended for eventual inclusion in the required subset, however they are not yet a part of the normative [FHIRPath](https://hl7.org/fhirpath/) release and may still be subject to change: + +- [join](https://build.fhir.org/ig/HL7/FHIRPath/#joinseparator-string-string) + function +- [lowBoundary](https://build.fhir.org/ig/HL7/FHIRPath/#lowboundaryprecision-integer-decimal--date--datetime--time) + and [highBoundary](https://build.fhir.org/ig/HL7/FHIRPath/#highboundaryprecision-integer-decimal--date--datetime--time) + functions (including on [Period](https://hl7.org/fhir/datatypes.html#Period)) diff --git a/input/pagecontent/StructureDefinition-ViewDefinition-intro.md b/input/pagecontent/StructureDefinition-ViewDefinition-intro.md index e104975..de92255 100644 --- a/input/pagecontent/StructureDefinition-ViewDefinition-intro.md +++ b/input/pagecontent/StructureDefinition-ViewDefinition-intro.md @@ -46,7 +46,7 @@ chosen tech stack. See [System Layers](index.html#system-layers) for details. ## Profiling ViewDefinitions may be profiled to meet specific needs. For instance, -the [ShareableViewDefinition](StructureDefinition-ShareableViewDefinition.html) +the [Shareable View Definition](StructureDefinition-ShareableViewDefinition.html) profile adds constraints for ViewDefinitions intended to be shared between systems. Implementers may create their own ViewDefinition profiles for further specialized needs. diff --git a/input/pagecontent/StructureDefinition-ViewDefinition-notes.md b/input/pagecontent/StructureDefinition-ViewDefinition-notes.md index ed8442b..82cb1f4 100644 --- a/input/pagecontent/StructureDefinition-ViewDefinition-notes.md +++ b/input/pagecontent/StructureDefinition-ViewDefinition-notes.md @@ -1,46 +1,11 @@ -## Supported FHIRPath Functionality - -The [FHIRPath](https://hl7.org/fhirpath/) expressions used in views are -evaluated by the View Runner. Only a subset of FHIRPath features is required to -be supported by all View Runners, and a set of additional features can be -optionally supported. - -### Required FHIRPath Expressions/Functions - -All View Runners must implement these [FHIRPath](https://hl7.org/fhirpath/) -capabilities: - -* [Literals](https://hl7.org/fhirpath/#literals) for String, Integer and Decimal -* [where](https://hl7.org/fhirpath/#wherecriteria-expression-collection)function -* [exists](https://hl7.org/fhirpath/#existscriteria-expression-boolean) function -* [empty](https://hl7.org/fhirpath/#empty-boolean) function -* [extension](https://hl7.org/fhir/R4/fhirpath.html#functions) function -* [ofType](https://hl7.org/fhirpath/#oftypetype-type-specifier-collection) - function -* [first](https://hl7.org/fhirpath/#first-collection) function -* Boolean - operators: [and](https://hl7.org/fhirpath/#and), [or](https://hl7.org/fhirpath/#or), [not](https://hl7.org/fhirpath/#not-boolean) -* Math - operators: [addition (+)](https://hl7.org/fhirpath/#addition), [subtraction (-)](https://hl7.org/fhirpath/#subtraction), [multiplication (*)](https://hl7.org/fhirpath/#multiplication), [division (/)](https://hl7.org/fhirpath/#division) -* Comparison - operators: [equals (=)](https://hl7.org/fhirpath/#equals), [not equals (!=)](https://hl7.org/fhirpath/#not-equals), [greater than (>)](https://hl7.org/fhirpath/#greater-than), [less or equal (<=)](https://hl7.org/fhirpath/#less-or-equal) -* [Indexer expressions](https://hl7.org/fhirpath/#index-integer-collection) -* *[lowBoundary](https://build.fhir.org/ig/HL7/FHIRPath/#lowboundaryprecision-integer-decimal--date--datetime--time) - and [highBoundary](https://build.fhir.org/ig/HL7/FHIRPath/#highboundaryprecision-integer-decimal--date--datetime--time) - functions (including on [Period](https://hl7.org/fhir/datatypes.html#Period))** -* *[join](https://build.fhir.org/ig/HL7/FHIRPath/#joinseparator-string-string) - function** - -* Not yet part of the normative [FHIRPath](https://hl7.org/fhirpath/) -release, currently in draft. - -### Optional FHIRPath Functions - -View Runners are encouraged to implement these functions to support a broader -set of use cases: - -* [memberOf](https://hl7.org/fhir/fhirpath.html#fn-memberOf) function -* [toQuantity](https://hl7.org/fhirpath/#toquantityunit-string-quantity) function +## FHIRPath Functionality + +Columns within a view are defined using the [FHIRPath](https://hl7.org/fhirpath/) language. FHIRPath contains a large number of functions and syntax constructs - not all of these are required to be implemented within a SQL on FHIR view runner. + +View runner implementations MUST support the following required additional FHIRPath functions. In +addition to this, runners SHOULD implement the FHIRPath subset defined in the +[Shareable View Definition](StructureDefinition-ShareableViewDefinition.html) profile if the intent is for the runner to be able to +execute shared view definitions. ### Required Additional Functions @@ -48,10 +13,10 @@ All View Runners must implement these functions that extend the FHIRPath specification. Despite not being in the [FHIRPath](https://hl7.org/fhirpath/) specification, they are necessary in the context of defining views: -* [getResourceKey](#getresourcekey--keytype) function -* [getReferenceKey](#getreferencekeyresource-type-specifier--keytype) function +- [getResourceKey](#getresourcekey--keytype) function +- [getReferenceKey](#getreferencekeyresource-type-specifier--keytype) function -#### getResourceKey() : *KeyType* +#### getResourceKey() : _KeyType_ This is invoked at the root of a FHIR Resource and returns an opaque value to be used as the primary key for the row associated with the resource. In many cases @@ -62,7 +27,7 @@ with [getReferenceKey](#getreferencekeyresource-type-specifier--keytype), which returns an equal value from references that point to this resource. -The returned *KeyType* is implementation dependent, but must be a FHIR primitive +The returned _KeyType_ is implementation dependent, but must be a FHIR primitive type that can be used for efficient joins in the system's underlying data storage. Integers, strings, UUIDs, and other primitive types may be appropriate. @@ -70,7 +35,7 @@ See the [Joins with Resource and Reference Keys](#joins-with-resource-and-reference-keys) section below for details. -#### getReferenceKey([resource: type specifier]) : *KeyType* +#### getReferenceKey([resource: type specifier]) : _KeyType_ This is invoked on [Reference](https://hl7.org/fhir/references.html#Reference) elements and returns an opaque value that represents the database key of the row @@ -80,25 +45,25 @@ itself. Users may pass an optional resource type (e.g., Patient or Observation) to indicate the expected type that the reference should point to. The -getReferenceKey function will return an empty collection (effectively *null* +getReferenceKey function will return an empty collection (effectively _null_ since FHIRPath always returns collections) if the reference is not of the expected type. For example, `Observation.subject.getReferenceKey(Patient)` would return a row key if the subject is a Patient, or the empty collection ( i.e., `{}`) if it is not. -The returned *KeyType* is implementation dependent, but must be a FHIR primitive +The returned _KeyType_ is implementation dependent, but must be a FHIR primitive type that can be used for efficient joins in the systems underlying data storage. Integers, strings, UUIDs, and other primitive types may be appropriate. The getReferenceKey function has both required and optional functionality: -* Implementations MUST support the relative literal form of reference ( - e.g., `Patient/123`), and MAY support other types of references. If the - implementation does not support the reference type, or is unable to resolve - the reference, it MUST return the empty collection (i.e., `{}`). -* Implementations MAY generate a list of unprocessable references through query - responses, logging or reporting. The details of how this information would be - provided to the user is implementation specific. +- Implementations MUST support the relative literal form of reference ( + e.g., `Patient/123`), and MAY support other types of references. If the + implementation does not support the reference type, or is unable to resolve + the reference, it MUST return the empty collection (i.e., `{}`). +- Implementations MAY generate a list of unprocessable references through query + responses, logging or reporting. The details of how this information would be + provided to the user is implementation specific. See the [Joins with Resource and Reference Keys](#joins-with-resource-and-reference-keys) section below for details. @@ -180,7 +145,7 @@ resource has at least one `Patient.address`, similar to an `INNER JOIN` in SQL: In contrast, this view with [forEachOrNull](StructureDefinition-ViewDefinition-definitions.html#diff_ViewDefinition.select.forEachOrNull) -will produce the `id` column for *every* Patient resource. If the Patient +will produce the `id` column for _every_ Patient resource. If the Patient resource has no `Patient.address`, there will be a single row for the Patient resource and the `zip` column will contain null. For Patient resources with one or more `Patient.address`, the result will be identical to the expression above. @@ -207,10 +172,10 @@ parts of the resources. The multiple rows produced using `forEach` or `forEachOrNull` from a `select`are joined to others with the following rules: -* Parent/child `select`s will repeat values from the parent `select` for each - item in the child `select`. -* Sibling `select`s are effectively cross joined, where each row in - each `select` is duplicated for every row in sibling `select`s. +- Parent/child `select`s will repeat values from the parent `select` for each + item in the child `select`. +- Sibling `select`s are effectively cross joined, where each row in + each `select` is duplicated for every row in sibling `select`s. The [examples](StructureDefinition-ViewDefinition-examples.html) illustrate this behavior. @@ -344,75 +309,75 @@ order: ```json { - "name": "column_order_example", - "resource": "...", - "select": [ - { - "column": [ - { - "path": "'A'", - "name": "a" - }, - { - "path": "'B'", - "name": "b" - } - ], - "select": [ - { - "forEach": "aNestedStructure", - "column": [ - { - "path": "'C'", - "name": "c" - }, - { - "path": "'D'", - "name": "d" - } - ] - } - ], - "unionAll": [ - { - "column": [ - { - "path": "'E1'", - "name": "e" - }, - { - "path": "'F1'", - "name": "f" - } - ] - }, - { - "column": [ - { - "path": "'E2'", - "name": "e" - }, - { - "path": "'F2'", - "name": "f" - } - ] - } - ] - }, - { - "column": [ + "name": "column_order_example", + "resource": "...", + "select": [ { - "path": "'G'", - "name": "g" + "column": [ + { + "path": "'A'", + "name": "a" + }, + { + "path": "'B'", + "name": "b" + } + ], + "select": [ + { + "forEach": "aNestedStructure", + "column": [ + { + "path": "'C'", + "name": "c" + }, + { + "path": "'D'", + "name": "d" + } + ] + } + ], + "unionAll": [ + { + "column": [ + { + "path": "'E1'", + "name": "e" + }, + { + "path": "'F1'", + "name": "f" + } + ] + }, + { + "column": [ + { + "path": "'E2'", + "name": "e" + }, + { + "path": "'F2'", + "name": "f" + } + ] + } + ] }, { - "path": "'H'", - "name": "h" + "column": [ + { + "path": "'G'", + "name": "g" + }, + { + "path": "'H'", + "name": "h" + } + ] } - ] - } - ] + ] } ``` @@ -522,7 +487,7 @@ For example, a minimal view of Patient resources could look like this: } ``` -A view of Observation resources would then have its own row key and a foreign +A view of Observation resources would then have its own row key and a foreign key to easily join to the view of Patient resources, like this: ```js @@ -553,7 +518,7 @@ to `active_patients.id` using common join semantics. While [getResourceKey](#getresourcekey--keytype) and [getReferenceKey](#getreferencekeyresource-type-specifier--keytype) must -return matching values for the same row, *how* they do so is left to the +return matching values for the same row, _how_ they do so is left to the implementation. This is by design, allowing ViewDefinitions to be run across a wide set of systems that have different data invariants or pre-processing capabilities. @@ -684,7 +649,8 @@ model produces. **Purpose**: This step ensures that a ViewDefinition's columns are valid, by setting up a recursive call. **Inputs** -* `V`: a `ViewDefinition` to validate + +- `V`: a `ViewDefinition` to validate 1. Call `ValidateColumns(V, C)` according to the recursive step below. @@ -693,35 +659,41 @@ model produces. **Purpose:** This step ensures that column names are unique across `S` and disjoint from `C` **Inputs** -* `S`: a single Selection Structure -* `C`: a list of Columns that exist prior to this call + +- `S`: a single Selection Structure +- `C`: a list of Columns that exist prior to this call **Outputs** -* `Ret`: a list of Columns + +- `Ret`: a list of Columns **Errors** -* Column Already Defined -* Union Branches Inconsistent + +- Column Already Defined +- Union Branches Inconsistent 0. Initialize `Ret` to equal `C` 1. For each Column `col` in `S.column[]` - * If a Column with name `col.name` already exists in `Ret`, throw "Column Already Defined" - * Otherwise, append `col` to `Ret` + + - If a Column with name `col.name` already exists in `Ret`, throw "Column Already Defined" + - Otherwise, append `col` to `Ret` 2. For each Selection Structure `sel` in `S.select[]` - * For each Column `c` in `Validate(sel, Ret)` - * If a Column with name `c.name` already exists in `Ret`, throw "Column Already Defined" - * Otherwise, append `c` to the end of `Ret` + + - For each Column `c` in `Validate(sel, Ret)` + - If a Column with name `c.name` already exists in `Ret`, throw "Column Already Defined" + - Otherwise, append `c` to the end of `Ret` 3. If `S.unionAll[]` is present + 1. Define `u0` as `Validate(S.unionAll[0], Ret)` 2. For each Selection Structure `sel` in `S.unionAll[]` - * Define `u` as `ValidateColumns(sel, Ret)` - * If the list of names from `u0` is different from the list of names from `u`, throw "Union Branches Inconsistent" - * Otherwise, continue + - Define `u` as `ValidateColumns(sel, Ret)` + - If the list of names from `u0` is different from the list of names from `u`, throw "Union Branches Inconsistent" + - Otherwise, continue 3. For each Column `col` in `u0` - * Append `col` to `Ret` + - Append `col` to `Ret` 4. Return `Ret` @@ -732,21 +704,21 @@ Resource, by setting up a recursive call. **Inputs** -* `V`: a `ViewDefinition` -* `R`: a FHIR Resource to process with `V` +- `V`: a `ViewDefinition` +- `R`: a FHIR Resource to process with `V` **Emits:** one output row at a time 1. Ensure resource type is correct - * If `R.resourceType` is different from `V.resource`, return immediately + - If `R.resourceType` is different from `V.resource`, return immediately without emitting any rows - * Otherwise, continue + - Otherwise, continue 2. If `V.where` is defined, ensure constraints are met - * Evaluate `fhirpath(V.where.path, R)` to determine whether `R` is a + - Evaluate `fhirpath(V.where.path, R)` to determine whether `R` is a candidate for `V` - * If `R` is not a candidate for `V`, return immediately without emitting + - If `R` is not a candidate for `V`, return immediately without emitting any rows - * Otherwise, continue + - Otherwise, continue 3. Emit all rows from `Process(S, V)` ### `Process(S, N)` (recursive step) @@ -756,81 +728,94 @@ We first generate sets of "partial rows" (i.e., sets of incomplete column bindings from the various clauses of `V`) and combine them to emit complete rows. For example, if there are two sets of partial rows: -* `[{"a": 1},{"a": 2}]` with bindings for the variable `a` -* `[{"b": 3},{"b": 4}]` with bindings for the variable `b` +- `[{"a": 1},{"a": 2}]` with bindings for the variable `a` +- `[{"b": 3},{"b": 4}]` with bindings for the variable `b` Then the Cartesian product of these sets consists of four complete rows: ```json [ - {"a": 1, "b": 3}, - {"a": 1, "b": 4}, - {"a": 2, "b": 3}, - {"a": 2, "b": 4} + { "a": 1, "b": 3 }, + { "a": 1, "b": 4 }, + { "a": 2, "b": 3 }, + { "a": 2, "b": 4 } ] ``` **Inputs** -* `S`: a Selection Structure -* `N`: a Node (element) from a FHIR resource + +- `S`: a Selection Structure +- `N`: a Node (element) from a FHIR resource **Errors** -* Multiple values found but not expected for column + +- Multiple values found but not expected for column **Emits:** One output row at a time 1. Define a list of Nodes `foci` as - * If `S.forEach` is defined: `fhirpath(S.forEach, N)` - * Else if `S.forEachOrNull` is defined: `fhirpath(S.forEachOrNull, N)` - * Otherwise: `[N]` (a list with just the input node) + + - If `S.forEach` is defined: `fhirpath(S.forEach, N)` + - Else if `S.forEachOrNull` is defined: `fhirpath(S.forEachOrNull, N)` + - Otherwise: `[N]` (a list with just the input node) 2. For each element `f` of `foci` + 1. Initialize an empty list `parts` (each element of `parts` will be a list of partial rows) 2. Process Columns: - * For each Column `col` of `S.column`, define `val` - as `fhirpath(col.path, f)` + + - For each Column `col` of `S.column`, define `val` + as `fhirpath(col.path, f)` + 1. Define `b` as a row whose column named `col.name` takes the value - * If `val` was the empty set: `null` - * Else if `val` has a single element `e`: `e` - * Else if `col.collection` is true: `val` - * Else: throw "Multiple values found but not expected for + - If `val` was the empty set: `null` + - Else if `val` has a single element `e`: `e` + - Else if `col.collection` is true: `val` + - Else: throw "Multiple values found but not expected for column" 2. Append `[b]` to `parts` - * (Note: append a list so the final element of `parts` is now a list + - (Note: append a list so the final element of `parts` is now a list containing the single row `b`). + 3. Process Selects: - * For each selection structure `sel` of `S.select` + + - For each selection structure `sel` of `S.select` + 1. Define `rows` as the collection of all rows emitted by `Process(sel, f)` 2. Append `rows` to `parts` - * (Note: do not append the elements but the whole list, so the final + - (Note: do not append the elements but the whole list, so the final element of `parts` is now the list `rows`) + 4. Process UnionAlls: + 1. Initialize `urows` as an empty list of rows 2. For each selection structure `u` of `S.unionAll` - * For each row `r` in `Process(u, f)` - * Append `r` to `urows` + - For each row `r` in `Process(u, f)` + - Append `r` to `urows` 3. Append `urows` to `parts` - * (Note: do not append the elements but the whole list, so the final + - (Note: do not append the elements but the whole list, so the final element of `parts` is now the list `urows`) + 5. For every list of partial rows `prows` in the Cartesian product of `parts` 1. Initialize a blank row `r` 2. For each partial row `p` in `prows` - * Add `p`'s column bindings to the row `r` + - Add `p`'s column bindings to the row `r` 3. Emit the row `r` - * (Note: the Cartesian product is always between a Selection + - (Note: the Cartesian product is always between a Selection Structure and its direct children, not deeper descendants. Because the process is recursive, rows generated by, for example, a `.select[0].select[0].select[0]` will eventually bubble up to the top level, but the bubbling happens one level at a time.) + 3. If `foci` is an empty list and `S.forEachOrNull` is defined (Note: when this condition is met, no rows have been emitted so far) 1. Initialize a blank row `r` 2. For each Column `c` in `ValidateColumns(V, [])` - * Bind the column `c.name` to `null` in the row `r` + - Bind the column `c.name` to `null` in the row `r` 3. Emit the row `r` diff --git a/test_report/src/Impls.svelte b/test_report/src/Impls.svelte index 35a2305..aab884f 100644 --- a/test_report/src/Impls.svelte +++ b/test_report/src/Impls.svelte @@ -2,6 +2,27 @@ import { onMount } from "svelte"; import implementations from '../public/implementations.json'; import tests from '../public/tests.json'; + + // Get all the unique tags in the test suite. + const tags = new Set(); + tests.forEach(test => { + test.tests.forEach(t => { + t.tags.forEach(tag => tags.add(tag)); + }); + }); + // Construct a mapping of tag to display name, with the reserved tags first. + const reservedTags = { + shareable: "Profile: Shareable View Definition", + tabular: "Profile: Tabular View Definition", + experimental: "Experimental" + } + const otherTags = Array.from(tags) + .filter(tag => !Object.keys(reservedTags).includes(tag)) + .reduce((acc, tag) => { + acc[tag] = tag; + return acc; + }, {}); + const sections = { ...reservedTags, ...otherTags } async function load(file){ try { @@ -31,7 +52,7 @@ - + {#each impls as impl} - {#each tests as test} - - + {#each Object.keys(sections) as section} + {#if tags.has(section)} + + - {#each test?.tests || [] as t, i} + {#each tests as test} + {#if (test?.tests || []).find(t => t.tags.includes(section))} - - {#each impls as impl} - {@const res = ((((impl.results || {})[test.file] || {}).tests || [])[i]?.result?.passed)} - - {/each} + {#each test?.tests || [] as t, i} + {#if t.tags.includes(section)} + + + {#each impls as impl} + {@const res = ((((impl.results || {})[test.file] || {}).tests || [])[i]?.result?.passed)} + + {/each} + + {/if} + {/each} + {/if} {/each} + {/if} {/each}
TestTest {impl.name} @@ -41,31 +62,44 @@
- {test.title} -
+ {sections[section]} +
- {t.title} + + {test.title} - {#if res === true} - - {:else if res === false } - - {:else} - - - {/if} -
+ {t.title} + + {#if res === true} + + {:else if res === false } + + {:else} + - + {/if} +
diff --git a/tests.schema.json b/tests.schema.json index 4549bf8..532a276 100644 --- a/tests.schema.json +++ b/tests.schema.json @@ -75,6 +75,13 @@ "type": "string", "description": "A description of the test." }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of tags that can be used to categorize this test." + }, "view": { "type": "object", "description": "The view to run the test against." diff --git a/tests/basic.json b/tests/basic.json index 2e91b29..2e4d4bf 100644 --- a/tests/basic.json +++ b/tests/basic.json @@ -31,6 +31,7 @@ "tests": [ { "title": "basic attribute", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -60,6 +61,7 @@ }, { "title": "boolean attribute with false", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -97,6 +99,7 @@ }, { "title": "two columns", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -134,6 +137,7 @@ }, { "title": "two selects with columns", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -175,6 +179,7 @@ }, { "title": "where - 1", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -203,6 +208,7 @@ }, { "title": "where - 2", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -231,6 +237,7 @@ }, { "title": "where returns non-boolean for some cases", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -259,6 +266,7 @@ }, { "title": "where as expr - 1", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -287,6 +295,7 @@ }, { "title": "where as expr - 2", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -315,6 +324,7 @@ }, { "title": "select & column", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -357,6 +367,7 @@ }, { "title": "column ordering", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ diff --git a/tests/collection.json b/tests/collection.json index d04bd5d..9b5f2a6 100644 --- a/tests/collection.json +++ b/tests/collection.json @@ -1,5 +1,6 @@ { "title": "collection", + "tags": ["shareable"], "description": "TBD", "fhirVersion": ["5.0.0", "4.0.1", "3.0.2"], "resources": [ @@ -46,6 +47,7 @@ "tests": [ { "title": "fail when 'collection' is not true", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -77,6 +79,7 @@ }, { "title": "collection = true", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -119,6 +122,7 @@ }, { "title": "collection = false relative to forEach parent", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -178,6 +182,7 @@ }, { "title": "collection = false relative to forEachOrNull parent", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", diff --git a/tests/combinations.json b/tests/combinations.json index 5993dfa..c301241 100644 --- a/tests/combinations.json +++ b/tests/combinations.json @@ -19,6 +19,7 @@ "tests": [ { "title": "select", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -51,6 +52,7 @@ }, { "title": "column + select", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -93,6 +95,7 @@ }, { "title": "sibling select", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -133,6 +136,7 @@ }, { "title": "sibling select inside a select", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -177,6 +181,7 @@ }, { "title": "column + select, with where", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -216,6 +221,7 @@ }, { "title": "unionAll + forEach + column + select", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ diff --git a/tests/constant.json b/tests/constant.json index c790d62..051776f 100644 --- a/tests/constant.json +++ b/tests/constant.json @@ -36,6 +36,7 @@ "tests": [ { "title": "constant in path", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -75,6 +76,7 @@ }, { "title": "constant in forEach", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -105,6 +107,7 @@ }, { "title": "constant in where element", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -139,6 +142,7 @@ }, { "title": "constant in unionAll", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -193,6 +197,7 @@ }, { "title": "integer constant", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -232,6 +237,7 @@ }, { "title": "boolean constant", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -266,6 +272,7 @@ }, { "title": "accessing an undefined constant", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -292,6 +299,7 @@ }, { "title": "incorrect constant definition", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", diff --git a/tests/constant_types.json b/tests/constant_types.json index 40945b9..ca5ddcd 100644 --- a/tests/constant_types.json +++ b/tests/constant_types.json @@ -294,6 +294,7 @@ "tests": [ { "title": "base64Binary", + "tags": ["shareable"], "view": { "resource": "Device", "status": "active", @@ -337,6 +338,7 @@ }, { "title": "code", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -380,6 +382,7 @@ }, { "title": "date", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -423,6 +426,7 @@ }, { "title": "dateTime", + "tags": ["shareable"], "view": { "resource": "DetectedIssue", "status": "active", @@ -466,6 +470,7 @@ }, { "title": "decimal", + "tags": ["shareable"], "view": { "resource": "Observation", "status": "active", @@ -517,6 +522,7 @@ }, { "title": "id", + "tags": ["shareable"], "view": { "resource": "Task", "status": "active", @@ -584,6 +590,7 @@ }, { "title": "instant", + "tags": ["shareable"], "view": { "resource": "Observation", "status": "active", @@ -635,6 +642,7 @@ }, { "title": "oid", + "tags": ["shareable"], "view": { "resource": "Task", "status": "active", @@ -702,6 +710,7 @@ }, { "title": "positiveInt", + "tags": ["shareable"], "view": { "resource": "ClaimResponse", "status": "active", @@ -745,6 +754,7 @@ }, { "title": "time", + "tags": ["shareable"], "view": { "resource": "Observation", "status": "active", @@ -796,6 +806,7 @@ }, { "title": "unsignedInt", + "tags": ["shareable"], "view": { "resource": "ImagingStudy", "status": "active", @@ -839,6 +850,7 @@ }, { "title": "uri", + "tags": ["shareable"], "view": { "resource": "Measure", "status": "active", @@ -882,6 +894,7 @@ }, { "title": "url", + "tags": ["shareable"], "view": { "resource": "Task", "status": "active", @@ -949,6 +962,7 @@ }, { "title": "uuid", + "tags": ["shareable"], "view": { "resource": "Task", "status": "active", diff --git a/tests/fhirpath.json b/tests/fhirpath.json index a667e12..8f06329 100644 --- a/tests/fhirpath.json +++ b/tests/fhirpath.json @@ -47,6 +47,7 @@ "tests": [ { "title": "one element", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -76,6 +77,7 @@ }, { "title": "two elements + first", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -105,6 +107,7 @@ }, { "title": "collection", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -135,6 +138,7 @@ }, { "title": "index[0]", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -164,6 +168,7 @@ }, { "title": "index[1]", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -193,6 +198,7 @@ }, { "title": "out of index", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -222,6 +228,7 @@ }, { "title": "where", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -251,6 +258,7 @@ }, { "title": "exists", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -288,6 +296,7 @@ }, { "title": "nested exists", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -325,6 +334,7 @@ }, { "title": "string join", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -362,6 +372,7 @@ }, { "title": "string join: default separator", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", diff --git a/tests/fhirpath_numbers.json b/tests/fhirpath_numbers.json index fc9b510..faf6f5d 100644 --- a/tests/fhirpath_numbers.json +++ b/tests/fhirpath_numbers.json @@ -23,6 +23,7 @@ "tests": [ { "title": "add observation", + "tags": ["shareable"], "view": { "resource": "Observation", "status": "active", diff --git a/tests/fn_boundary.json b/tests/fn_boundary.json index 25a850c..65c260d 100644 --- a/tests/fn_boundary.json +++ b/tests/fn_boundary.json @@ -48,6 +48,7 @@ "tests": [ { "title": "decimal lowBoundary", + "tags": ["experimental"], "view": { "resource": "Observation", "status": "active", @@ -89,6 +90,7 @@ }, { "title": "decimal highBoundary", + "tags": ["experimental"], "view": { "resource": "Observation", "status": "active", @@ -130,6 +132,7 @@ }, { "title": "datetime lowBoundary", + "tags": ["experimental"], "view": { "resource": "Observation", "status": "active", @@ -171,6 +174,7 @@ }, { "title": "datetime highBoundary", + "tags": ["experimental"], "view": { "resource": "Observation", "status": "active", @@ -212,6 +216,7 @@ }, { "title": "date lowBoundary", + "tags": ["experimental"], "view": { "resource": "Patient", "status": "active", @@ -241,6 +246,7 @@ }, { "title": "date highBoundary", + "tags": ["experimental"], "view": { "resource": "Patient", "status": "active", @@ -270,6 +276,7 @@ }, { "title": "time lowBoundary", + "tags": ["experimental"], "view": { "resource": "Observation", "status": "active", @@ -311,6 +318,7 @@ }, { "title": "time highBoundary", + "tags": ["experimental"], "view": { "resource": "Observation", "status": "active", diff --git a/tests/fn_empty.json b/tests/fn_empty.json index a8450b0..8db72b2 100644 --- a/tests/fn_empty.json +++ b/tests/fn_empty.json @@ -21,6 +21,7 @@ "tests": [ { "title": "empty names", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", diff --git a/tests/fn_extension.json b/tests/fn_extension.json index 53448e4..68b75ae 100644 --- a/tests/fn_extension.json +++ b/tests/fn_extension.json @@ -95,6 +95,7 @@ "tests": [ { "title": "simple extension", + "tags": ["shareable"], "description": "flatten simple extension", "view": { "resource": "Patient", @@ -132,6 +133,7 @@ }, { "title": "nested extension", + "tags": ["shareable"], "description": "flatten simple extension", "view": { "resource": "Patient", diff --git a/tests/fn_first.json b/tests/fn_first.json index b5bf0ad..9d6aa89 100644 --- a/tests/fn_first.json +++ b/tests/fn_first.json @@ -29,6 +29,7 @@ "tests": [ { "title": "table level first()", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -51,6 +52,7 @@ }, { "title": "table and field level first()", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ diff --git a/tests/fn_join.json b/tests/fn_join.json index c2f5cfa..edfc999 100644 --- a/tests/fn_join.json +++ b/tests/fn_join.json @@ -17,6 +17,7 @@ "tests": [ { "title": "join with comma", + "tags": ["experimental"], "view": { "resource": "Patient", "select": [ @@ -45,6 +46,7 @@ }, { "title": "join with empty value", + "tags": ["experimental"], "view": { "resource": "Patient", "select": [ @@ -73,6 +75,7 @@ }, { "title": "join with no value - default to no separator", + "tags": ["experimental"], "view": { "resource": "Patient", "select": [ diff --git a/tests/fn_oftype.json b/tests/fn_oftype.json index 50a92fa..173cde0 100644 --- a/tests/fn_oftype.json +++ b/tests/fn_oftype.json @@ -33,6 +33,7 @@ "tests": [ { "title": "select string values", + "tags": ["shareable"], "view": { "resource": "Observation", "status": "active", @@ -70,6 +71,7 @@ }, { "title": "select integer values", + "tags": ["shareable"], "view": { "resource": "Observation", "status": "active", diff --git a/tests/fn_reference_keys.json b/tests/fn_reference_keys.json index 30844c1..2db587f 100644 --- a/tests/fn_reference_keys.json +++ b/tests/fn_reference_keys.json @@ -29,6 +29,7 @@ "tests": [ { "title": "getReferenceKey result matches getResourceKey without type specifier", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -54,6 +55,7 @@ }, { "title": "getReferenceKey result matches getResourceKey with right type specifier", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -79,6 +81,7 @@ }, { "title": "getReferenceKey result matches getResourceKey with wrong type specifier", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ diff --git a/tests/foreach.json b/tests/foreach.json index 0745fe3..5a08cc9 100644 --- a/tests/foreach.json +++ b/tests/foreach.json @@ -60,6 +60,7 @@ "tests": [ { "title": "forEach: normal", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -106,6 +107,7 @@ }, { "title": "forEachOrNull: basic", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -156,6 +158,7 @@ }, { "title": "forEach: empty", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -185,6 +188,7 @@ }, { "title": "forEach: two on the same level", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -232,6 +236,7 @@ }, { "title": "forEach: two on the same level (empty result)", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -271,6 +276,7 @@ }, { "title": "forEachOrNull: null case", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -313,6 +319,7 @@ }, { "title": "forEach and forEachOrNull on the same level", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -373,6 +380,7 @@ }, { "title": "nested forEach", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -432,6 +440,7 @@ }, { "title": "nested forEach: select & column", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -489,6 +498,7 @@ }, { "title": "forEachOrNull & unionAll on the same level", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -560,6 +570,7 @@ }, { "title": "forEach & unionAll on the same level", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -623,6 +634,7 @@ }, { "title": "forEach & unionAll & column & select on the same level", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -714,6 +726,7 @@ }, { "title": "forEachOrNull & unionAll & column & select on the same level", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ diff --git a/tests/logic.json b/tests/logic.json index 80554c9..ee3f733 100644 --- a/tests/logic.json +++ b/tests/logic.json @@ -30,6 +30,7 @@ "tests": [ { "title": "filtering with 'and'", + "tags": ["shareable"], "view": { "resource": "Patient", "where": [ @@ -57,6 +58,7 @@ }, { "title": "filtering with 'or'", + "tags": ["shareable"], "view": { "resource": "Patient", "where": [ @@ -90,6 +92,7 @@ }, { "title": "filtering with 'not'", + "tags": ["shareable"], "view": { "resource": "Patient", "where": [ diff --git a/tests/union.json b/tests/union.json index 1edaf63..548fa54 100644 --- a/tests/union.json +++ b/tests/union.json @@ -91,6 +91,7 @@ "tests": [ { "title": "basic", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -200,6 +201,7 @@ }, { "title": "unionAll + column", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -307,6 +309,7 @@ }, { "title": "duplicates", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -409,6 +412,7 @@ }, { "title": "empty results", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -450,6 +454,7 @@ }, { "title": "empty with forEachOrNull", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -524,6 +529,7 @@ }, { "title": "forEachOrNull and forEach", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -582,6 +588,7 @@ }, { "title": "nested", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -662,6 +669,7 @@ }, { "title": "one empty operand", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -746,6 +754,7 @@ }, { "title": "column mismatch", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -788,6 +797,7 @@ }, { "title": "column order mismatch", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", diff --git a/tests/validate.json b/tests/validate.json index 4a99fb8..892c46b 100644 --- a/tests/validate.json +++ b/tests/validate.json @@ -20,11 +20,13 @@ "tests": [ { "title": "empty", + "tags": ["shareable"], "view": {}, "expectError": true }, { "title": "missing resource", + "tags": ["shareable"], "view": { "select": [ { @@ -42,6 +44,7 @@ }, { "title": "wrong fhirpath", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -55,6 +58,7 @@ }, { "title": "wrong type in forEach", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -68,6 +72,7 @@ }, { "title": "where with path resolving to not boolean", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", diff --git a/tests/view_resource.json b/tests/view_resource.json index 458a6d1..5b39d1b 100644 --- a/tests/view_resource.json +++ b/tests/view_resource.json @@ -23,6 +23,7 @@ "tests": [ { "title": "only pts", + "tags": ["shareable"], "view": { "resource": "Patient", "status": "active", @@ -49,6 +50,7 @@ }, { "title": "only obs", + "tags": ["shareable"], "view": { "resource": "Observation", "status": "active", @@ -72,6 +74,7 @@ }, { "title": "resource not specified", + "tags": ["shareable"], "view": { "status": "active", "select": [ diff --git a/tests/where.json b/tests/where.json index 4a9d856..1a8b77e 100644 --- a/tests/where.json +++ b/tests/where.json @@ -48,6 +48,7 @@ "tests": [ { "title": "simple where path with result", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -75,6 +76,7 @@ }, { "title": "where path with no results", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -98,6 +100,7 @@ }, { "title": "where path with greater than inequality", + "tags": ["shareable"], "view": { "resource": "Observation", "select": [ @@ -125,6 +128,7 @@ }, { "title": "where path with less than inequality", + "tags": ["shareable"], "view": { "resource": "Observation", "select": [ @@ -152,6 +156,7 @@ }, { "title": "multiple where paths", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -182,6 +187,7 @@ }, { "title": "where path with an 'and' connector", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -209,6 +215,7 @@ }, { "title": "where path with an 'or' connector", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [ @@ -239,6 +246,7 @@ }, { "title": "where path that evaluates to true when empty", + "tags": ["shareable"], "view": { "resource": "Patient", "select": [