From 36323fbf872e3280bb28d8e1f443f6cab9d2d4b4 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Mon, 8 Jul 2024 16:21:15 +0930 Subject: [PATCH 01/22] Add search to docs --- documentation/docusaurus.config.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index a7f1b34f..73f3cb0b 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -153,6 +153,28 @@ const config: Config = { copyright: `Copyright © ${new Date().getFullYear()} Commonwealth Scientific and Industrial Research - Organisation (CSIRO).` }, + // Refer to https://docusaurus.io/docs/search#connecting-algolia + algolia: { + // The application ID provided by Algolia + appId: 'SL7YXI16RH', + + // Public API key: it is safe to commit it + apiKey: 'a4c401a7bac65bc81b7dd7efe958b951', + + indexName: 'smartforms-csiro', + + // Optional: see doc section below + contextualSearch: true, + + // Optional: Algolia search parameters + searchParameters: {}, + + // Optional: path for search page that enabled by default (`false` to disable it) + searchPagePath: 'search', + + // Optional: whether the insights feature is enabled or not on Docsearch (`false` by default) + insights: false + }, prism: { theme: prismThemes.github, darkTheme: prismThemes.dracula From 67332a7740500f37a061474628f2d40168819ecf Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Mon, 8 Jul 2024 17:01:00 +0930 Subject: [PATCH 02/22] Create a generic way to update questionnaire response based on a recursive function --- .../src/utils/calculatedExpression.ts | 50 ++--------- .../src/utils/repopulateIntoResponse.ts | 62 +------------ .../src/utils/updateQr.ts | 88 +++++++++++++++++++ 3 files changed, 98 insertions(+), 102 deletions(-) create mode 100644 packages/smart-forms-renderer/src/utils/updateQr.ts diff --git a/packages/smart-forms-renderer/src/utils/calculatedExpression.ts b/packages/smart-forms-renderer/src/utils/calculatedExpression.ts index bc2a5ceb..43a5aaf5 100644 --- a/packages/smart-forms-renderer/src/utils/calculatedExpression.ts +++ b/packages/smart-forms-renderer/src/utils/calculatedExpression.ts @@ -34,7 +34,7 @@ import { getQrItemsIndex, mapQItemsIndex } from './mapItem'; import { updateQrItemsInGroup } from './qrItem'; import cloneDeep from 'lodash.clonedeep'; import dayjs from 'dayjs'; -import { qrItemHasItemsOrAnswer } from './manageForm'; +import { updateQuestionnaireResponse } from './updateQr'; interface EvaluateInitialCalculatedExpressionsParams { initialResponse: QuestionnaireResponse; @@ -165,15 +165,6 @@ export function initialiseCalculatedExpressionValues( populatedResponse: QuestionnaireResponse, calculatedExpressions: Record ): QuestionnaireResponse { - if ( - !questionnaire.item || - questionnaire.item.length === 0 || - !populatedResponse.item || - populatedResponse.item.length === 0 - ) { - return populatedResponse; - } - // Filter calculated expressions, only preserve key-value pairs with values const calculatedExpressionsWithValues: Record = {}; for (const linkId in calculatedExpressions) { @@ -186,41 +177,12 @@ export function initialiseCalculatedExpressionValues( } } - // Populate calculated expression values into QR - const qItemsIndexMap = mapQItemsIndex(questionnaire); - const topLevelQRItemsByIndex = getQrItemsIndex( - questionnaire.item, - populatedResponse.item, - qItemsIndexMap + return updateQuestionnaireResponse( + questionnaire, + populatedResponse, + initialiseItemCalculatedExpressionValueRecursive, + calculatedExpressionsWithValues ); - - const topLevelQrItems: QuestionnaireResponseItem[] = []; - for (const [index, topLevelQItem] of questionnaire.item.entries()) { - const topLevelQRItemOrItems = topLevelQRItemsByIndex[index] ?? { - linkId: topLevelQItem.linkId, - text: topLevelQItem.text, - item: [] - }; - - const updatedTopLevelQRItem = initialiseItemCalculatedExpressionValueRecursive( - topLevelQItem, - topLevelQRItemOrItems, - calculatedExpressionsWithValues - ); - - if (Array.isArray(updatedTopLevelQRItem)) { - if (updatedTopLevelQRItem.length > 0) { - topLevelQrItems.push(...updatedTopLevelQRItem); - } - continue; - } - - if (updatedTopLevelQRItem && qrItemHasItemsOrAnswer(updatedTopLevelQRItem)) { - topLevelQrItems.push(updatedTopLevelQRItem); - } - } - - return { ...populatedResponse, item: topLevelQrItems }; } function initialiseItemCalculatedExpressionValueRecursive( diff --git a/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts b/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts index 936d7105..57210c38 100644 --- a/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts +++ b/packages/smart-forms-renderer/src/utils/repopulateIntoResponse.ts @@ -1,14 +1,9 @@ -import type { - Questionnaire, - QuestionnaireItem, - QuestionnaireResponse, - QuestionnaireResponseItem -} from 'fhir/r4'; +import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4'; import type { ItemToRepopulate } from './repopulateItems'; import { getQrItemsIndex, mapQItemsIndex } from './mapItem'; import { isSpecificItemControl } from './itemControl'; import { questionnaireResponseStore, questionnaireStore } from '../stores'; -import { qrItemHasItemsOrAnswer } from './manageForm'; +import { updateQuestionnaireResponse } from './updateQr'; /** * Re-populate checked items in the re-population dialog into the current QuestionnaireResponse @@ -19,63 +14,14 @@ export function repopulateResponse(checkedItemsToRepopulate: Record -): QuestionnaireResponse { - if ( - !questionnaire.item || - questionnaire.item.length === 0 || - !updatableResponse.item || - updatableResponse.item.length === 0 - ) { - return updatableResponse; - } - - const qItemsIndexMap = mapQItemsIndex(questionnaire); - const topLevelQRItemsByIndex = getQrItemsIndex( - questionnaire.item, - updatableResponse.item, - qItemsIndexMap - ); - - const topLevelQrItems: QuestionnaireResponseItem[] = []; - for (const [index, topLevelQItem] of questionnaire.item.entries()) { - const topLevelQRItemOrItems = topLevelQRItemsByIndex[index] ?? { - linkId: topLevelQItem.linkId, - text: topLevelQItem.text, - item: [] - }; - - const updatedTopLevelQRItem = repopulateItemRecursive( - topLevelQItem, - topLevelQRItemOrItems, - checkedItemsToRepopulate - ); - - if (Array.isArray(updatedTopLevelQRItem)) { - if (updatedTopLevelQRItem.length > 0) { - topLevelQrItems.push(...updatedTopLevelQRItem); - } - continue; - } - - if (updatedTopLevelQRItem && qrItemHasItemsOrAnswer(updatedTopLevelQRItem)) { - topLevelQrItems.push(updatedTopLevelQRItem); - } - } - - return { ...updatableResponse, item: topLevelQrItems }; -} - function repopulateItemRecursive( qItem: QuestionnaireItem, qrItemOrItems: QuestionnaireResponseItem | QuestionnaireResponseItem[] | null, diff --git a/packages/smart-forms-renderer/src/utils/updateQr.ts b/packages/smart-forms-renderer/src/utils/updateQr.ts new file mode 100644 index 00000000..1c3b0c39 --- /dev/null +++ b/packages/smart-forms-renderer/src/utils/updateQr.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2024 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + Questionnaire, + QuestionnaireItem, + QuestionnaireResponse, + QuestionnaireResponseItem +} from 'fhir/r4'; +import { getQrItemsIndex, mapQItemsIndex } from './mapItem'; +import { qrItemHasItemsOrAnswer } from './manageForm'; + +export type RepopulateFunction = ( + qItem: QuestionnaireItem, + qrItemOrItems: QuestionnaireResponseItem | QuestionnaireResponseItem[] | null, + extraData: T +) => QuestionnaireResponseItem | QuestionnaireResponseItem[] | null; + +/** + * A generic (and safe) way to update a QuestionnaireResponse given a recursive function and a set of data i.e. Record, Record + * This function relies heavily on mapQItemsIndex() and getQrItemsIndex() to accurately pinpoint the locations of QR items based on their positions in the Q, taking into account repeating group answers, non-filled questions, etc + * + * @author Sean Fong + */ +export function updateQuestionnaireResponse( + questionnaire: Questionnaire, + questionnaireResponse: QuestionnaireResponse, + recursiveUpdateFunction: RepopulateFunction, + extraData: T +) { + if ( + !questionnaire.item || + questionnaire.item.length === 0 || + !questionnaireResponse.item || + questionnaireResponse.item.length === 0 + ) { + return questionnaireResponse; + } + + const qItemsIndexMap = mapQItemsIndex(questionnaire); + const topLevelQRItemsByIndex = getQrItemsIndex( + questionnaire.item, + questionnaireResponse.item, + qItemsIndexMap + ); + + const topLevelQrItems = []; + for (const [index, topLevelQItem] of questionnaire.item.entries()) { + const topLevelQRItemOrItems = topLevelQRItemsByIndex[index] ?? { + linkId: topLevelQItem.linkId, + text: topLevelQItem.text, + item: [] + }; + + const updatedTopLevelQRItem = recursiveUpdateFunction( + topLevelQItem, + topLevelQRItemOrItems, + extraData + ); + + if (Array.isArray(updatedTopLevelQRItem)) { + if (updatedTopLevelQRItem.length > 0) { + topLevelQrItems.push(...updatedTopLevelQRItem); + } + continue; + } + + if (updatedTopLevelQRItem && qrItemHasItemsOrAnswer(updatedTopLevelQRItem)) { + topLevelQrItems.push(updatedTopLevelQRItem); + } + } + + return { ...questionnaireResponse, item: topLevelQrItems }; +} From 6322350f7ecd740d6513757586e7ec7da184e60b Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Tue, 9 Jul 2024 10:41:24 +0930 Subject: [PATCH 03/22] Fix docs trailing slash issue when clicking on top header --- documentation/docusaurus.config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index 73f3cb0b..e71ce9c9 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -22,6 +22,8 @@ const config: Config = { onBrokenLinks: 'warn', onBrokenMarkdownLinks: 'warn', + trailingSlash: false, + // Even if you don't use internationalization, you can use this field to set // useful metadata like html lang. For example, if your site is Chinese, you // may want to replace "en" with "zh-Hans". From 70409d77800359a57f87e014af289d5c1ea9aa88 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Tue, 9 Jul 2024 19:17:39 +0930 Subject: [PATCH 04/22] Add populate and assemble operation docs --- documentation/docs/operations/assemble.mdx | 33 +++++++++++++++ documentation/docs/operations/index.mdx | 31 +++++++------- documentation/docs/operations/populate.mdx | 47 ++++++++++++++++++++++ 3 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 documentation/docs/operations/assemble.mdx create mode 100644 documentation/docs/operations/populate.mdx diff --git a/documentation/docs/operations/assemble.mdx b/documentation/docs/operations/assemble.mdx new file mode 100644 index 00000000..b6129b29 --- /dev/null +++ b/documentation/docs/operations/assemble.mdx @@ -0,0 +1,33 @@ +--- +sidebar_position: 3 +--- + +# $assemble + +## Useful links + +**Deployed service: https://smartforms.csiro.au/api/fhir/Questionnaire/$assemble** + +FHIR Operation definition: http://hl7.org/fhir/uv/sdc/OperationDefinition/Questionnaire-assemble + +Github: https://github.com/aehrc/smart-forms/tree/main/services/assemble-express + +Dockerhub: https://hub.docker.com/r/aehrc/smart-forms-assemble + +## Usage + +A Questionnaire resource can be assembled using a **POST** request to a URL such as: + +```http request +https://smartforms.csiro.au/api/fhir/Questionnaire/$assemble (type-level) +``` + +#### Parameters + +| Name | Cardinality | Type | Documentation | +| ------------- | ----------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| questionnaire | 1..1 | Questionnaire | The [Modular Questionnaire](https://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire-modular.html) to assemble the content of. | + +#### Try it out + +[Run In Postman](https://elements.getpostman.com/redirect?entityId=22885901-2af2cfbb-3a0a-49c6-8404-105ef0751415&entityType=collection) diff --git a/documentation/docs/operations/index.mdx b/documentation/docs/operations/index.mdx index 7c36fa14..a0a6dfd1 100644 --- a/documentation/docs/operations/index.mdx +++ b/documentation/docs/operations/index.mdx @@ -8,18 +8,19 @@ sidebar_label: Introduction Smart Forms provides reference implementations for `$populate` and `$assemble` operations as [ExpressJS](https://expressjs.com/) services to complement the FHIR Questionnaire renderer. These operations are available as Docker images and can be deployed as microservices. -[//]: # '1. **`$populate`**' -[//]: # ' Operation definition: [SDCPopulateQuestionnaire](http://hl7.org/fhir/uv/sdc/OperationDefinition/Questionnaire-populate)' -[//]: # ' Github repository:' -[//]: # -[//]: # ' A React-based library that contains the rendering engine. It acts as a reference implementation for the [SDC Form Filler](https://hl7.org/fhir/uv/sdc/CapabilityStatement-sdc-form-filler.html).' -[//]: # -[//]: # '2. **`$assemble`** ([@aehrc/sdc-populate](https://www.npmjs.com/package/@aehrc/sdc-populate))' -[//]: # -[//]: # ' A reference implementation of the [SDC Populate Questionnaire](https://hl7.org/fhir/uv/sdc/OperationDefinition-Questionnaire-populate.html) operation, also known as $populate.' -[//]: # ' Currently, there are no written documentation available for this library. Please refer to the [API](/docs/api/sdc-populate) for more information.' -[//]: # -[//]: # '3. **SDC Assemble** ([@aehrc/sdc-assemble](https://www.npmjs.com/package/@aehrc/sdc-assemble))' -[//]: # -[//]: # ' A reference implementation of the [SDC Assemble Questionnaire](https://hl7.org/fhir/uv/sdc/OperationDefinition-Questionnaire-assemble.html) operation, also known as $assemble.' -[//]: # ' Currently, there are no written documentation available for this library. Please refer to the [API](/docs/api/sdc-assemble) for more information.' +1. **$populate** (https://smartforms.csiro.au/api/fhir/Questionnaire/$populate) + + A reference implementation of the [SDC Populate Questionnaire](https://hl7.org/fhir/uv/sdc/OperationDefinition-Questionnaire-populate.html) operation, also known as $populate. + It builds on the [@aehrc/sdc-populate](https://www.npmjs.com/package/@aehrc/sdc-populate) library. + +2. **$assemble** (https://smartforms.csiro.au/api/fhir/Questionnaire/$assemble) + + A reference implementation of the [SDC Assemble](https://hl7.org/fhir/uv/sdc/OperationDefinition-Questionnaire-assemble.html) operation, also known as $assemble. + It builds on the [@aehrc/sdc-assemble](https://www.npmjs.com/package/@aehrc/sdc-assemble) library. + +3. **$extract** (https://proxy.smartforms.io/fhir/QuestionnaireResponse/$extract) + + A **proof-of-concept** reference implementation of the [SDC QuestionnaireResponse Extract](https://hl7.org/fhir/uv/sdc/OperationDefinition-QuestionnaireResponse-extract.html) operation, also known as $extract. + + This $extract POC reference implementation is an abstraction on top of an existing StructureMap $transform operation. + We leveraged Brian Postlethwaite's [.NET FHIR Mapping Language engine](https://github.com/brianpos/fhir-net-mappinglanguage/tree/main/demo-map-server) to expose a StructureMap $transform operation on https://proxy.smartforms.io/fhir/StructureMap/$transform. diff --git a/documentation/docs/operations/populate.mdx b/documentation/docs/operations/populate.mdx new file mode 100644 index 00000000..7f4b9fca --- /dev/null +++ b/documentation/docs/operations/populate.mdx @@ -0,0 +1,47 @@ +--- +sidebar_position: 2 +--- + +# $populate + +## Useful links + +**Deployed service: https://smartforms.csiro.au/api/fhir/Questionnaire/$populate** + +FHIR Operation definition: http://hl7.org/fhir/uv/sdc/OperationDefinition/Questionnaire-populate + +Github: https://github.com/aehrc/smart-forms/tree/main/services/populate-express + +Dockerhub: https://hub.docker.com/r/aehrc/smart-forms-populate + +## Usage + +A Questionnaire resource can be populated using a **POST** request to a URL such as: + +```http request +https://smartforms.csiro.au/api/fhir/Questionnaire/$populate (type-level) +``` + +#### Parameters + +| Name | Cardinality | Type | Documentation | +| --------------- | ----------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| questionnaire | 1..1 | Questionnaire | The Questionnaire is provided directly as part of the request. | +| subject | 1..1 | Reference | The resource that is to be the QuestionnaireResponse.subject. The QuestionnaireResponse instance will reference the provided subject. | +| context | 0..\* | | Resources containing information to be used to help populate the QuestionnaireResponse. These will typically be FHIR resources. | +| context.name | 0..\* | string | The name of the launchContext or root Questionnaire variable the passed content should be used as for population purposes. The name SHALL correspond to a launchContext or variable declared at the root of the Questionnaire. | +| context.content | 0..\* | Resource | The actual resource (or resources) to use as the value of the launchContext or variable. | + +https://smartforms.csiro.au/api/fhir only stores Questionnaire definitions and does not contain any clinical data. Therefore when using this sample implementation, contextual information for pre-population should be provided as actual FHIR resources, not references. + +#### Debugging + +You can add the `debug=true` query parameter to return an additional `contextResult-custom` output parameter in the response. + +```http request +https://smartforms.csiro.au/api/fhir/Questionnaire/$populate?debug=true +``` + +#### Try it out + +[Run In Postman](https://elements.getpostman.com/redirect?entityId=22885901-2af2cfbb-3a0a-49c6-8404-105ef0751415&entityType=collection) From cf84fb92074c6f75cc92e4019543f3d04592c5a1 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 13:55:28 +0930 Subject: [PATCH 05/22] Add $convert endpoint --- services/extract-express/package.json | 2 +- services/extract-express/src/debug.ts | 76 +++++++++++++++++ .../src/fhirMappingLanguage.ts | 24 ++++++ services/extract-express/src/index.ts | 83 ++++++++++++++++++- .../extract-express/src/operationOutcome.ts | 28 +++++++ services/extract-express/src/transform.ts | 51 ++++++++++-- 6 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 services/extract-express/src/debug.ts create mode 100644 services/extract-express/src/fhirMappingLanguage.ts diff --git a/services/extract-express/package.json b/services/extract-express/package.json index 17a61ca5..767ee9b1 100644 --- a/services/extract-express/package.json +++ b/services/extract-express/package.json @@ -5,7 +5,7 @@ "main": "lib/index.js", "scripts": { "compile": "tsc", - "start": "node lib/index.js", + "start": "node --inspect lib/index.js", "start:watch": "node --inspect --watch lib/index.js", "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/services/extract-express/src/debug.ts b/services/extract-express/src/debug.ts new file mode 100644 index 00000000..cdc7979d --- /dev/null +++ b/services/extract-express/src/debug.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2024 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { OperationOutcome, Parameters, ParametersParameter, StructureMap } from 'fhir/r4b'; + +export function responseIsParametersResource(parameters: any): parameters is DebugOutputParameters { + return ( + parameters && + parameters.resourceType === 'Parameters' && + !!parameters.parameter && + parameters.parameter.length > 0 && + parameters.parameter[2].name === 'parameters' && + parameters.parameter[2].part.length > 0 && + !!parameters.parameter[2].part[1] && + parameters.parameter[2].part[1].name === 'source' && + !!parameters.parameter[2].part[1].resource && + parameters.parameter[2].part[1].resource.resourceType === 'StructureMap' + ); +} + +export function getStructureMapFromDebugOutputParameters( + outputParameters: any +): StructureMap | null { + if (!responseIsParametersResource(outputParameters)) { + return null; + } + + return outputParameters.parameter[2].part[1].resource; +} + +export interface DebugOutputParameters extends Parameters { + parameter: [OutcomeParameter, ResultParameter, DebugParametersParameter, TraceParameter]; +} + +interface OutcomeParameter extends ParametersParameter { + name: 'outcome'; + resource: OperationOutcome; +} + +interface ResultParameter extends ParametersParameter { + name: 'result'; + valueString: string; +} + +interface DebugParametersParameter extends ParametersParameter { + name: 'parameters'; + part: [ + { + name: 'evaluator'; + valueString: string; + }, + { + name: 'source'; + resource: StructureMap; + } + ]; +} + +interface TraceParameter extends ParametersParameter { + name: 'content'; + part: any[]; +} diff --git a/services/extract-express/src/fhirMappingLanguage.ts b/services/extract-express/src/fhirMappingLanguage.ts new file mode 100644 index 00000000..315fc076 --- /dev/null +++ b/services/extract-express/src/fhirMappingLanguage.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function getFhirMappingLanguageMap(body: any): string | null { + if (typeof body === 'string' && body.includes('map')) { + return body; + } + + return null; +} diff --git a/services/extract-express/src/index.ts b/services/extract-express/src/index.ts index d77ff000..6924c5e0 100644 --- a/services/extract-express/src/index.ts +++ b/services/extract-express/src/index.ts @@ -19,6 +19,8 @@ import express from 'express'; import cors from 'cors'; import { getQuestionnaireResponse } from './questionnaireResponse'; import { + createFailStructureMapConversionOutcome, + createInvalidFhirMappingLanguageMap, createInvalidParametersOutcome, createInvalidQuestionnaireCanonicalOutcome, createNoFormsServerUrlSetOutcome, @@ -29,8 +31,14 @@ import { } from './operationOutcome'; import { getQuestionnaire } from './questionnaire'; import { getTargetStructureMap, getTargetStructureMapCanonical } from './structureMap'; -import { createTransformInputParameters, invokeTransform } from './transform'; +import { + createTransformInputParametersForConvert, + createTransformInputParametersForExtract, + invokeTransform +} from './transform'; import dotenv from 'dotenv'; +import { getFhirMappingLanguageMap } from './fhirMappingLanguage'; +import { getStructureMapFromDebugOutputParameters } from './debug'; const app = express(); const port = 3003; @@ -52,6 +60,7 @@ app.use( // Allows the app to accept JSON and URL encoded data up to 50MB app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ extended: true })); +app.use(express.text()); // Allows the app to work behind reverse proxies, forwarding the correct req.protocol to the /StructureMap/$transform call // Without this, doing a HTTPS $extract call will result in a HTTP $transform call @@ -136,7 +145,7 @@ app.post('/fhir/QuestionnaireResponse/\\$extract', async (req, res) => { return; } - const transformInputParameters = createTransformInputParameters( + const transformInputParameters = createTransformInputParametersForExtract( targetStructureMap, questionnaireResponse ); @@ -165,6 +174,76 @@ app.post('/fhir/QuestionnaireResponse/\\$extract', async (req, res) => { } }); +app.get('/fhir/\\$convert', (_, res) => { + res.send( + 'This service is healthy!\nHowever, this server only supports StructureMap/$convert.\nPerform a POST request to /fhir/StructureMap/$convert to convert a FHIR Mapping Language map to a StructureMap resource.' + ); +}); + +app.get('/fhir/StructureMap/\\$convert', (_, res) => { + res.send( + 'This service is healthy!\nPerform a POST request to the same path to convert a FHIR Mapping Language map to a StructureMap resource.' + ); +}); + +app.post('/fhir/StructureMap/\\$convert', async (req, res) => { + let ehrServerUrl = req.protocol + '://' + req.get('host') + '/fhir'; + let ehrServerAuthToken: string | null = null; + + // Set EHR server URL and auth token if provided in env variables + if (EHR_SERVER_URL) { + ehrServerUrl = EHR_SERVER_URL; + ehrServerAuthToken = EHR_SERVER_AUTH_TOKEN ?? null; + } + + try { + // Get FHIR Mapping Language map from the request body + const body = req.body; + const fhirMappingLanguageMap = getFhirMappingLanguageMap(body); + + if (!fhirMappingLanguageMap) { + const outcome = createInvalidFhirMappingLanguageMap(); + res.status(400).json(outcome); + return; + } + + const transformInputParameters = + createTransformInputParametersForConvert(fhirMappingLanguageMap); + + const outputParameters = await invokeTransform( + transformInputParameters, + ehrServerUrl, + ehrServerAuthToken ?? undefined, + true + ); + + // Get StructureMap resource from the output parameters + const structureMap = getStructureMapFromDebugOutputParameters(outputParameters); + if (!structureMap) { + const outcome = createFailStructureMapConversionOutcome(); + res.status(400).json(outcome); + return; + } + + res.json(structureMap); + } catch (error) { + console.error(error); + if (error instanceof Error) { + res.status(500).json(createOperationOutcome(error?.message)); // Sending the error message as an OperationOutcome + return; + } + + // If the error is not an instance of Error, send a generic error message + res + .status(500) + .json( + createOperationOutcome( + 'Something went wrong here. Please raise a GitHub issue at https://github.com/aehrc/smart-forms/issues/new' + ) + ); + } +}); + app.listen(port, () => { console.log(`Transform Express app listening on port ${port}`); }); diff --git a/services/extract-express/src/operationOutcome.ts b/services/extract-express/src/operationOutcome.ts index 5d130370..e58c78a2 100644 --- a/services/extract-express/src/operationOutcome.ts +++ b/services/extract-express/src/operationOutcome.ts @@ -43,6 +43,19 @@ export function createInvalidParametersOutcome(): OperationOutcome { }; } +export function createInvalidFhirMappingLanguageMap(): OperationOutcome { + return { + resourceType: 'OperationOutcome', + issue: [ + { + severity: 'error', + code: 'invalid', + details: { text: 'Input provided is not a valid FHIR Mapping Language map.' } + } + ] + }; +} + export function createInvalidQuestionnaireCanonicalOutcome(): OperationOutcome { return { resourceType: 'OperationOutcome', @@ -109,6 +122,21 @@ export function createNoTargetStructureMapFoundOutcome( }; } +export function createFailStructureMapConversionOutcome(): OperationOutcome { + return { + resourceType: 'OperationOutcome', + issue: [ + { + severity: 'error', + code: 'invalid', + details: { + text: `Failed to convert the provided FHIR Mapping Language map to a StructureMap.` + } + } + ] + }; +} + export function createOperationOutcome(errorMessage: string): OperationOutcome { return { resourceType: 'OperationOutcome', diff --git a/services/extract-express/src/transform.ts b/services/extract-express/src/transform.ts index f9595318..cf6eecf5 100644 --- a/services/extract-express/src/transform.ts +++ b/services/extract-express/src/transform.ts @@ -24,7 +24,7 @@ import type { } from 'fhir/r4b'; import { HEADERS } from './globals'; -export function createTransformInputParameters( +export function createTransformInputParametersForExtract( targetStructureMap: StructureMap, questionnaireResponse: QuestionnaireResponse ): TransformInputParameters { @@ -43,12 +43,41 @@ export function createTransformInputParameters( }; } +export function createTransformInputParametersForConvert( + fhirMappingLanguageMap: string +): TransformInputParameters { + return { + resourceType: 'Parameters', + parameter: [ + { + name: 'source', + valueString: fhirMappingLanguageMap + }, + // Hardcoded content to be empty QuestionnaireResponse since it is not used in the conversion + { + name: 'content', + resource: { + resourceType: 'QuestionnaireResponse', + status: 'in-progress' + } + } + ] + }; +} + export async function invokeTransform( transformInputParameters: TransformInputParameters, ehrServerUrl: string, - ehrServerAuthToken?: string + ehrServerAuthToken?: string, + debugMode?: boolean ): Promise { - const requestUrl = `${ehrServerUrl}/StructureMap/$transform`; + let requestUrl = `${ehrServerUrl}/StructureMap/$transform`; + + // Brian's demo-map-server by default return xml when debug=true, explicitly set it to json + if (debugMode) { + requestUrl += '?debug=true&_format=json'; + } + const headers = ehrServerAuthToken ? { ...HEADERS, Authorization: `Bearer ${ehrServerAuthToken}` } : HEADERS; @@ -59,8 +88,11 @@ export async function invokeTransform( }); if (!response.ok) { + const debugModeMessage = debugMode + ? '\nNote: The input FML map might not be valid. If it is valid but you are still getting this error, please raise a GitHub issue at https://github.com/aehrc/smart-forms/issues/new' + : ''; throw new Error( - `HTTP error when performing ${ehrServerUrl}/StructureMap/$transform. Status: ${response.status}` + `HTTP error when performing ${ehrServerUrl}/StructureMap/$transform. Status: ${response.status}${debugModeMessage}` ); } @@ -68,14 +100,21 @@ export async function invokeTransform( } export interface TransformInputParameters extends Parameters { - parameter: [SourceParameter, ContentParameter]; + parameter: + | [SourceResourceParameter, ContentParameter] + | [SourceValueStringParameter, ContentParameter]; } -interface SourceParameter extends ParametersParameter { +interface SourceResourceParameter extends ParametersParameter { name: 'source'; resource: StructureMap; } +interface SourceValueStringParameter extends ParametersParameter { + name: 'source'; + valueString: string; +} + interface ContentParameter extends ParametersParameter { name: 'content'; resource: FhirResource; From 5343ad04dae7e701e934d3ac070dd61dfa2d5ad5 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 14:11:56 +0930 Subject: [PATCH 06/22] Update extract-express and docker push script --- push-extract-image.sh | 2 +- services/extract-express/README.md | 7 +++++-- services/extract-express/package.json | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/push-extract-image.sh b/push-extract-image.sh index 7a0687de..2468377a 100644 --- a/push-extract-image.sh +++ b/push-extract-image.sh @@ -24,5 +24,5 @@ cd services/extract-express && npm run compile && cd - # Build the Docker image for multiple architectures, then push to Docker Hub. docker buildx build --file ./services/extract-express/Dockerfile \ --tag aehrc/smart-forms-extract:latest \ - --tag aehrc/smart-forms-extract:v0.3.1 \ + --tag aehrc/smart-forms-extract:v0.4.0 \ --platform linux/amd64,linux/arm64/v8 --push --no-cache . diff --git a/services/extract-express/README.md b/services/extract-express/README.md index 45452def..07c31f50 100644 --- a/services/extract-express/README.md +++ b/services/extract-express/README.md @@ -5,6 +5,8 @@ It is an abstraction on top of an existing StructureMap [$transform](https://hl7 A proof-of-concept StructureMap $transform is defined on https://proxy.smartforms.io/fhir/StructureMap/$transform, leveraging Brian's .NET mapping engine from https://github.com/brianpos/fhir-net-mappinglanguage/tree/main/demo-map-server. +A `StructureMap/$convert` operation is also defined in the POC implementation to convert a FHIR Mapping Language map to a StructureMap resource, using the same .NET mapping engine. + ## Configuration Create a .env file (or copy from example.env) in the root of the project with the following: ```env @@ -34,6 +36,7 @@ Docker image: https://hub.docker.com/r/aehrc/smart-forms-extract **By default, ```FORMS_SERVER_URL``` is set to https://smartforms.csiro.au/api/fhir in the Docker image.** ## Sample implementation -A sample implementation of this service is available at https://proxy.smartforms.io/fhir/QuestionnaireResponse/$extract. +A sample implementation of the `$extract` service is available at https://proxy.smartforms.io/fhir/QuestionnaireResponse/$extract. +`StructureMap/$convert` is available at https://proxy.smartforms.io/fhir/StructureMap/$convert. -Note: The $extract service on https://smartforms.csiro.au/api/fhir only performs processing - it does not persist any data. +Note: The $extract and $convert service on https://proxy.smartforms.io/fhir only performs processing - it does not persist any data. diff --git a/services/extract-express/package.json b/services/extract-express/package.json index 767ee9b1..d99674b9 100644 --- a/services/extract-express/package.json +++ b/services/extract-express/package.json @@ -1,6 +1,6 @@ { "name": "extract-express", - "version": "0.3.1", + "version": "0.4.0", "description": "", "main": "lib/index.js", "scripts": { From 36a30b6a80826bf92390c4fdc8ef811b112fa7ef Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 15:35:39 +0930 Subject: [PATCH 07/22] Fix correct AWS deployment route for $convert --- .../ehr-proxy/ehr-proxy-app/lib/ehr-proxy-app-stack.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deployment/ehr-proxy/ehr-proxy-app/lib/ehr-proxy-app-stack.ts b/deployment/ehr-proxy/ehr-proxy-app/lib/ehr-proxy-app-stack.ts index 655d1fa3..f6d6553b 100644 --- a/deployment/ehr-proxy/ehr-proxy-app/lib/ehr-proxy-app-stack.ts +++ b/deployment/ehr-proxy/ehr-proxy-app/lib/ehr-proxy-app-stack.ts @@ -73,7 +73,12 @@ export class EhrProxyAppStack extends cdk.Stack { listener.addAction('EhrProxyExtractAction', { action: ListenerAction.forward([extractTargetGroup]), priority: 1, - conditions: [ListenerCondition.pathPatterns(['/fhir/QuestionnaireResponse/$extract'])] + conditions: [ + ListenerCondition.pathPatterns([ + '/fhir/QuestionnaireResponse/$extract', + '/fhir/StructureMap/$convert' + ]) + ] }); // Create a target for the transform service From c48bb08a61068eae0b7c9742c6d2266675a5bcaa Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 16:36:51 +0930 Subject: [PATCH 08/22] Add docs for extract --- documentation/docs/operations/extract.mdx | 196 ++++++++++++++++++++++ services/extract-express/README.md | 2 +- 2 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 documentation/docs/operations/extract.mdx diff --git a/documentation/docs/operations/extract.mdx b/documentation/docs/operations/extract.mdx new file mode 100644 index 00000000..4727bb69 --- /dev/null +++ b/documentation/docs/operations/extract.mdx @@ -0,0 +1,196 @@ +--- +sidebar_position: 4 +--- + +# StructureMap $extract + +This $extract **proof-of-concept** reference implementation is an abstraction on top of an existing StructureMap $transform operation. +We leveraged Brian Postlethwaite's [.NET FHIR Mapping Language engine](https://github.com/brianpos/fhir-net-mappinglanguage/tree/main/demo-map-server) to expose a StructureMap $transform operation on https://proxy.smartforms.io/fhir/StructureMap/$transform. + +:::note + +This reference implementation is a proof-of-concept. It is highly likely that the underlying implementation will change in the future. + +::: + +## Useful links + +#### Services links + +Deployed service: https://proxy.smartforms.io/fhir/QuestionnaireResponse/$extract + +Underlying $transform service: https://proxy.smartforms.io/fhir/StructureMap/$transform + +FHIR Mapping Language to StructureMap $convert service: https://proxy.smartforms.io/fhir/StructureMap/$convert + +#### Specification links + +FHIR $extract operation definition: http://hl7.org/fhir/uv/sdc/OperationDefinition/QuestionnaireResponse-extract + +FHIR $transform operation definition: https://hl7.org/fhir/r4/structuremap-operation-transform.html + +FHIR Mapping Language $convert workflow: https://confluence.hl7.org/pages/viewpage.action?pageId=76158820#UsingtheFHIRMappingLanguage-WebServices + +FHIR StructureMap-based extraction: https://hl7.org/fhir/uv/sdc/extraction.html#structuremap-based-extraction + +#### Source code links + +Github: https://github.com/aehrc/smart-forms/tree/main/services/extract-express + +Dockerhub: https://hub.docker.com/r/aehrc/smart-forms-extract + +## Usage + +Resource(s) can be extracted from a QuestionnaireResponse using a **POST** request to a URL such as: + +```http request +https://proxy.smartforms.io/fhir/QuestionnaireResponse/$extract (type-level) +``` + +#### Parameters + +| Name | Cardinality | Type | Documentation | +| ---------------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------- | +| questionnaire-response | 1..1 | Resource | The QuestionnaireResponse to extract data from. Used when the operation is invoked at the 'type' level. | + +#### Try it out + +[Run In Postman](https://elements.getpostman.com/redirect?entityId=22885901-2af2cfbb-3a0a-49c6-8404-105ef0751415&entityType=collection) + +## How it works + +The $extract operation is an abstraction on top of an existing StructureMap $transform operation. + +#### The underlying $transform + +A [$transform](https://hl7.org/fhir/r4/structuremap-operation-transform.html) operation requires two input parameters: + +1. `source` - Contains the structure map defining the mapping rules +2. `content` - Contains the data to be transformed (in terms of SDC, this is the QuestionnaireResponse) + +The output of $transform is the transformed data, a FHIR resource. + +Taking this logic, we can use a StructureMap `$transform` operation to perform the extraction of resources from a QuestionnaireResponse. Let's say we want to extract a bundle containing an Observation resource from a QuestionnaireResponse. +We need a StructureMap that maps the data from the QuestionnaireResponse to the Observation resource. Normally we would write this mapping in [FHIR Mapping Language](https://www.hl7.org/fhir/mapping-language.html) and convert it to a StructureMap. + +For more information on using the FHIR Mapping Language, refer to https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Mapping+Language. Brian has a really awesome tool that can help you write and test your mappings at https://fhirpath-lab.com/FhirMapper2. + +Once we have both the `source` StructureMap and the `content` QuestionnaireResponse, our $transform request body should look roughly like this: + +```json +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "source", + "resource": // - + } + }, + { + "name": "content", + "resource": // - a filled QR from + } + ] +} +``` + +Relevant resources: + +`source`- https://smartforms.csiro.au/api/fhir/StructureMap/extract-bmi + +`content`- http://smartforms.csiro.au/fhir/Questionnaire/CalculatedExpressionBMICalculatorPrepop + +[//]: # 'turn it into a microservice' + +Running the $transform operation will return the transformed data, which in this case is a Bundle resource containing the Observation resource: + +```json +{ + "resourceType": "Bundle", + "id": "", + "type": "transaction", + "entry": [ + { + "request": { + "method": "POST", + "url": "Observation" + }, + "resource": { + "resourceType": "Observation", + "status": "final", + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "60621009", + "display": "Body mass index" + } + ] + }, + "subject": { + "reference": "Patient/pat-sf" + }, + "valueQuantity": { + "value": 29.55, + "unit": "kg/m2", + "system": "http://unitsofmeasure.org", + "code": "kg/m2" + } + } + } + ] +} +``` + +#### The $extract operation + +Using the logic above, we can abstract the $transform operation into a $extract operation. The $extract operation requires only one input parameter (or you can just provide the QuestionnaireResponse in the request body): + +`questionnaire-response` - Contains the QuestionnaireResponse to extract data from + +The provided QuestionnaireResponse should fulfill two criteria: + +1. It needs to contain a canonical reference in its `questionnaire` property. + +```json +{ + ... + "questionnaire": "https://smartforms.csiro.au/docs/sdc/population/calculated-expression-1|0.1.0", + ... +} +``` + +2. The referenced Questionnaire should have a [questionnaire-targetStructureMap](http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap) extension. This extension needs to contain a canonical reference to a StructureMap that maps the data from the QuestionnaireResponse to the desired resource(s). + +```json +{ + ... + "extension": [ + { + "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-targetStructureMap", + "valueCanonical": "https://smartforms.csiro.au/docs/StructureMap/extract-bmi" + } + ], + ... +} +``` + +The $extract POC implementation defines a definitional repository to resolve Questionnaires and StructureMaps - https://smartforms.csiro.au/api/fhir. See https://hub.docker.com/r/aehrc/smart-forms-extract for more information. + +The $extract operation will resolve the referenced Questionnaire + StructureMap, and set the StructureMap as the `source` in the $transform operation. The `content` will be the provided QuestionnaireResponse. + +The underlying $transform operation will be executed, and the transformed data will be returned as the output of the $extract operation. + +## The $convert operation + +Brian's [.NET FHIR Mapping Language engine](https://github.com/brianpos/fhir-net-mappinglanguage/tree/main/demo-map-server) has a handy debug mode that can be activated by adding `debug=true` to the $transform query parameters. +The debug payload contains useful details such as warnings/errors, trace output, the converted StructureMap, and the output resource. + +Conveniently, it also accepts both a FHIR Mapping Language map and a StructureMap resource as the `source` input. +This means we can use it as a $convert operation to convert a FHIR Mapping Language map to a StructureMap resource. + +``` +https://proxy.smartforms.io/fhir/StructureMap/$convert +``` + +It expects the request headers for the `Content-Type` to be `text/plain`. The request body should contain the FHIR Mapping Language map, and the response will contain the converted StructureMap resource. diff --git a/services/extract-express/README.md b/services/extract-express/README.md index 07c31f50..3c7ea5e0 100644 --- a/services/extract-express/README.md +++ b/services/extract-express/README.md @@ -33,7 +33,7 @@ You can use `docker run -p 3003:3003 -e EHR_SERVER_URL=https://proxy.smartforms. Docker image: https://hub.docker.com/r/aehrc/smart-forms-extract -**By default, ```FORMS_SERVER_URL``` is set to https://smartforms.csiro.au/api/fhir in the Docker image.** +**By default, ```FORMS_SERVER_URL``` is set to https://smartforms.csiro.au/api/fhir in the Docker image.** This endpoint is used to resolve referenced FHIR Questionnaires and StructureMaps. ## Sample implementation A sample implementation of the `$extract` service is available at https://proxy.smartforms.io/fhir/QuestionnaireResponse/$extract. From 20548a7afba130830e11d8075c5c25a6f98904a8 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 17:48:23 +0930 Subject: [PATCH 09/22] Fix Github workflows and docusaurus config --- .github/workflows/build_test_lint.yml | 27 ++++++++ .../{deploy_app.yml => deploy_app_docs.yml} | 27 ++++++++ .github/workflows/deploy_docs.yml | 64 ------------------- documentation/docs/index.md | 1 + documentation/docusaurus.config.ts | 5 +- 5 files changed, 58 insertions(+), 66 deletions(-) rename .github/workflows/{deploy_app.yml => deploy_app_docs.yml} (71%) delete mode 100644 .github/workflows/deploy_docs.yml diff --git a/.github/workflows/build_test_lint.yml b/.github/workflows/build_test_lint.yml index ac6840ea..f3a5fd15 100644 --- a/.github/workflows/build_test_lint.yml +++ b/.github/workflows/build_test_lint.yml @@ -79,3 +79,30 @@ jobs: - name: Check formatting run: npm run check-formatting + + deploy-storybook: + name: Deploy Storybook to S3 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js 18.x + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment + aws-region: ap-southeast-2 + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build-storybook -w packages/smart-forms-renderer + + - name: Upload static Storybook site to S3 + run: aws s3 sync packages/smart-forms-renderer/storybook-static s3://smart-forms-storybook/storybook diff --git a/.github/workflows/deploy_app.yml b/.github/workflows/deploy_app_docs.yml similarity index 71% rename from .github/workflows/deploy_app.yml rename to .github/workflows/deploy_app_docs.yml index 3a1600fc..92828657 100644 --- a/.github/workflows/deploy_app.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -43,6 +43,33 @@ jobs: - name: Upload static React site to S3 run: aws s3 sync apps/smart-forms-app/dist s3://smart-forms-react-app/ + deploy-docusaurus-s3: + name: Deploy Docusaurus to S3 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js 18.x + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment + aws-region: ap-southeast-2 + + - name: Install dependencies + run: npm ci + + - name: Build documentation website + run: npm run build -w documentation + + - name: Upload static Docusaurus site to S3 + run: aws s3 sync documentation/build s3://smart-forms-docs/docs + chromatic: name: Run Chromatic runs-on: ubuntu-latest diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml deleted file mode 100644 index dafee6e6..00000000 --- a/.github/workflows/deploy_docs.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Smart Forms Docs Deployment Workflow - -on: - push: - -permissions: - contents: read - pages: write - id-token: write - -jobs: - deploy-docusaurus-s3: - name: Deploy Docusaurus to S3 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: npm - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment - aws-region: ap-southeast-2 - - - name: Install dependencies - run: npm ci - - - name: Build documentation website - run: npm run build -w documentation - - - name: Upload static Docusaurus site to S3 - run: aws s3 sync documentation/build s3://smart-forms-docs/docs - - deploy-storybook: - name: Deploy Storybook to S3 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: npm - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment - aws-region: ap-southeast-2 - - - name: Install dependencies - run: npm ci - - - name: Build application - run: npm run build-storybook -w packages/smart-forms-renderer - - - name: Upload static Storybook site to S3 - run: aws s3 sync packages/smart-forms-renderer/storybook-static s3://smart-forms-storybook/storybook diff --git a/documentation/docs/index.md b/documentation/docs/index.md index 9dd5222b..b05c1197 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -14,6 +14,7 @@ This documentation is intended to provide a guide on how to use Smart Forms. It - [Components](/docs/components): A showcase of supported Questionnaire form components. - [SDC](/docs/sdc): A section around the conformance and usage of functionalities defined in the SDC specification. - [Developer Usage](/docs/dev): A guide on how to use the form renderer in your own application. +- [FHIR Operations](/docs/operations): A guide on using the $populate, $assemble and $extract operations. ### Referenced FHIR Specifications diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index e71ce9c9..e4b7ea52 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -37,11 +37,12 @@ const config: Config = { 'classic', { docs: { + showLastUpdateTime: true, routeBasePath: '/', - sidebarPath: './sidebars.ts', + sidebarPath: './sidebars.ts' // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: 'https://github.com/aehrc/smart-forms/' + // editUrl: 'https://github.com/aehrc/smart-forms/' }, theme: { customCss: './src/css/custom.css' From cc4eb2d3bce40c122ee99d5b283d5e0113f531dc Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 17:56:44 +0930 Subject: [PATCH 10/22] Fix AWS id token permissions --- .github/workflows/deploy_app_docs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app_docs.yml index 92828657..30f12669 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -4,12 +4,14 @@ on: push: branches: ['main'] +permissions: + id-token: write + jobs: build: name: Deploy Smart Forms app to S3 runs-on: ubuntu-latest permissions: - id-token: write contents: read steps: - uses: actions/checkout@v4 From 47d2d389d77066c48e09bf7940185b609c4cad94 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 17:59:27 +0930 Subject: [PATCH 11/22] Fix AWS id token permissions 2nd attempt --- .github/workflows/deploy_app_docs.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app_docs.yml index 30f12669..6db0bac7 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -4,9 +4,6 @@ on: push: branches: ['main'] -permissions: - id-token: write - jobs: build: name: Deploy Smart Forms app to S3 @@ -48,6 +45,10 @@ jobs: deploy-docusaurus-s3: name: Deploy Docusaurus to S3 runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write steps: - uses: actions/checkout@v4 From 2e2be2602da444db590c747109f0b631878d1a53 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 18:07:44 +0930 Subject: [PATCH 12/22] Fix AWS id token permissions 3rd attempt --- .github/workflows/build_test_lint.yml | 4 ++++ .github/workflows/deploy_app_docs.yml | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_test_lint.yml b/.github/workflows/build_test_lint.yml index f3a5fd15..953b4685 100644 --- a/.github/workflows/build_test_lint.yml +++ b/.github/workflows/build_test_lint.yml @@ -83,6 +83,10 @@ jobs: deploy-storybook: name: Deploy Storybook to S3 runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app_docs.yml index 6db0bac7..97a4e384 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -45,10 +45,6 @@ jobs: deploy-docusaurus-s3: name: Deploy Docusaurus to S3 runs-on: ubuntu-latest - permissions: - contents: read - pages: write - id-token: write steps: - uses: actions/checkout@v4 From d397410be8709143436bd590aba0f9c543a15430 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 18:16:39 +0930 Subject: [PATCH 13/22] Fix AWS id token permissions 4th attempt --- .github/workflows/deploy_app_docs.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app_docs.yml index 97a4e384..7c42c3b1 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -4,6 +4,11 @@ on: push: branches: ['main'] +permissions: + contents: read + pages: write + id-token: write + jobs: build: name: Deploy Smart Forms app to S3 From cc41ec9e2410fa4d1aff520aa32c9e55a2c31ad9 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 18:45:44 +0930 Subject: [PATCH 14/22] Fix AWS id token permissions 5th attempt --- apps/smart-forms-app/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/smart-forms-app/vite.config.ts b/apps/smart-forms-app/vite.config.ts index 3436febf..003842ad 100644 --- a/apps/smart-forms-app/vite.config.ts +++ b/apps/smart-forms-app/vite.config.ts @@ -12,6 +12,6 @@ export default defineConfig({ commonjsOptions: { include: [/node_modules/, '@aehrc/sdc-assemble', '@aehrc/sdc-populate'] } - }, - resolve: { preserveSymlinks: true } + } + // resolve: { preserveSymlinks: true } }); From dae586f449081e3b5b8e3b78fbe78c6374b91412 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 18:47:17 +0930 Subject: [PATCH 15/22] Fix AWS id token permissions 5th attempt --- .github/workflows/deploy_app_docs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app_docs.yml index 7c42c3b1..650c5e12 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -13,8 +13,6 @@ jobs: build: name: Deploy Smart Forms app to S3 runs-on: ubuntu-latest - permissions: - contents: read steps: - uses: actions/checkout@v4 From d5b1fc75a5934dd8bddca21f9deae83aa7bb8d77 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Wed, 10 Jul 2024 18:47:57 +0930 Subject: [PATCH 16/22] Revert vite config --- apps/smart-forms-app/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/smart-forms-app/vite.config.ts b/apps/smart-forms-app/vite.config.ts index 003842ad..3436febf 100644 --- a/apps/smart-forms-app/vite.config.ts +++ b/apps/smart-forms-app/vite.config.ts @@ -12,6 +12,6 @@ export default defineConfig({ commonjsOptions: { include: [/node_modules/, '@aehrc/sdc-assemble', '@aehrc/sdc-populate'] } - } - // resolve: { preserveSymlinks: true } + }, + resolve: { preserveSymlinks: true } }); From 96cc4292c0845a23a9e0d9422e826b2b775b295c Mon Sep 17 00:00:00 2001 From: Sean Fong <52597778+fongsean@users.noreply.github.com> Date: Thu, 11 Jul 2024 09:28:21 +0930 Subject: [PATCH 17/22] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d37e6328..e876677e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Powered by SMART on FHIR and Structured Data Capture, Smart Forms allow you to e

Show me the app ➡️

Check out the documentation 📚

+Update 11/07/2024: The documentation link isn't working. It is currently being fixed. --- Smart Forms is a Typescript-based [React](https://reactjs.org/) forms web application currently ongoing development by [CSIRO's Australian e-Health Research Centre](https://aehrc.csiro.au/) as part of the Primary Care Data Quality project funded by the Australian Government Department of Health. From 5455eeb85bbfbc8595a0c01c20859b91c308488c Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Thu, 11 Jul 2024 09:36:11 +0930 Subject: [PATCH 18/22] Test docs deployment 1st attempt --- .github/workflows/deploy_test_docs.yml | 37 +++++++++++++++++++ .../SmartFormsRedirectToCorrectRoute.js | 22 +++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/deploy_test_docs.yml diff --git a/.github/workflows/deploy_test_docs.yml b/.github/workflows/deploy_test_docs.yml new file mode 100644 index 00000000..e9ecdb8a --- /dev/null +++ b/.github/workflows/deploy_test_docs.yml @@ -0,0 +1,37 @@ +name: Smart Forms Docs Deployment Workflow + +on: + push: + +permissions: + contents: read + pages: write + id-token: write + +jobs: + deploy-docusaurus-s3: + name: Deploy Docusaurus to S3 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js 18.x + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment + aws-region: ap-southeast-2 + + - name: Install dependencies + run: npm ci + + - name: Build documentation website + run: npm run build -w documentation + + - name: Upload static Docusaurus site to S3 + run: aws s3 sync documentation/build s3://smart-forms-test-docs/docz --cache-control no-cache diff --git a/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js b/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js index c6c5cdb7..b085e05b 100644 --- a/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js +++ b/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js @@ -103,6 +103,28 @@ function handler(event) { return request; } + + // Handle Docs routes + if (uri.includes('/docs')) { + // Reroute to smartforms.csiro.au/docs/index.html + if (uri === '/docs/') { + request.uri += 'index.html'; + return request; + } + + if (uri === '/docs') { + request.uri = '/redirect.html'; + return request; + } + + if (!uri.includes('.')) { + request.uri += '/index.html'; + return request; + } + + return request; + } + // Handle Forms Server API routes if (uri.includes('/api')) { // Remove the /api prefix From ac399e707a096f21e2d966f450da1a108dcdca02 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Thu, 11 Jul 2024 09:54:39 +0930 Subject: [PATCH 19/22] Test docs deployment 2nd attempt --- .github/workflows/deploy_app_docs.yml | 52 +++++++++++++------------- .github/workflows/deploy_test_docs.yml | 2 +- package-lock.json | 4 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app_docs.yml index 650c5e12..fdd427da 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -45,32 +45,32 @@ jobs: - name: Upload static React site to S3 run: aws s3 sync apps/smart-forms-app/dist s3://smart-forms-react-app/ - deploy-docusaurus-s3: - name: Deploy Docusaurus to S3 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: npm - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment - aws-region: ap-southeast-2 - - - name: Install dependencies - run: npm ci - - - name: Build documentation website - run: npm run build -w documentation - - - name: Upload static Docusaurus site to S3 - run: aws s3 sync documentation/build s3://smart-forms-docs/docs +# deploy-docusaurus-s3: +# name: Deploy Docusaurus to S3 +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# +# - name: Use Node.js 18.x +# uses: actions/setup-node@v4 +# with: +# node-version: 18 +# cache: npm +# +# - name: Configure AWS credentials +# uses: aws-actions/configure-aws-credentials@v4 +# with: +# role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment +# aws-region: ap-southeast-2 +# +# - name: Install dependencies +# run: npm ci +# +# - name: Build documentation website +# run: npm run build -w documentation +# +# - name: Upload static Docusaurus site to S3 +# run: aws s3 sync documentation/build s3://smart-forms-docs/docs chromatic: name: Run Chromatic diff --git a/.github/workflows/deploy_test_docs.yml b/.github/workflows/deploy_test_docs.yml index e9ecdb8a..96f0c6d3 100644 --- a/.github/workflows/deploy_test_docs.yml +++ b/.github/workflows/deploy_test_docs.yml @@ -34,4 +34,4 @@ jobs: run: npm run build -w documentation - name: Upload static Docusaurus site to S3 - run: aws s3 sync documentation/build s3://smart-forms-test-docs/docz --cache-control no-cache + run: aws s3 sync documentation/build s3://smart-forms-docs/docs --cache-control no-cache diff --git a/package-lock.json b/package-lock.json index 8a21e486..bd9c8c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42626,7 +42626,7 @@ } }, "services/extract-express": { - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { "cors": "^2.8.5", @@ -42642,7 +42642,7 @@ } }, "services/populate-express": { - "version": "2.2.6", + "version": "2.2.7", "license": "Apache-2.0", "dependencies": { "@aehrc/sdc-populate": "^2.2.7", From 001a754a60573acc8c0a902b4fbe70559bdf54e5 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Thu, 11 Jul 2024 10:28:48 +0930 Subject: [PATCH 20/22] Test docs deployment 3rd attempt --- documentation/docusaurus.config.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index e4b7ea52..4f545b00 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -12,7 +12,7 @@ const config: Config = { url: 'https://smartforms.csiro.au', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' - baseUrl: '/docs', + baseUrl: '/docs/', // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. @@ -20,9 +20,7 @@ const config: Config = { projectName: '', // Usually your repo name.\ onBrokenLinks: 'warn', - onBrokenMarkdownLinks: 'warn', - - trailingSlash: false, + onBrokenMarkdownLinks: 'throw', // Even if you don't use internationalization, you can use this field to set // useful metadata like html lang. For example, if your site is Chinese, you From b69a6f19e92b198bc5802d7c00fccd35ff442910 Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Thu, 11 Jul 2024 10:36:53 +0930 Subject: [PATCH 21/22] Test docs deployment 4th attempt and fix linting --- .github/workflows/deploy_app_docs.yml | 52 +++++++++++++-------------- documentation/docusaurus.config.ts | 2 ++ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app_docs.yml index fdd427da..e355c448 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app_docs.yml @@ -45,32 +45,32 @@ jobs: - name: Upload static React site to S3 run: aws s3 sync apps/smart-forms-app/dist s3://smart-forms-react-app/ -# deploy-docusaurus-s3: -# name: Deploy Docusaurus to S3 -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v4 -# -# - name: Use Node.js 18.x -# uses: actions/setup-node@v4 -# with: -# node-version: 18 -# cache: npm -# -# - name: Configure AWS credentials -# uses: aws-actions/configure-aws-credentials@v4 -# with: -# role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment -# aws-region: ap-southeast-2 -# -# - name: Install dependencies -# run: npm ci -# -# - name: Build documentation website -# run: npm run build -w documentation -# -# - name: Upload static Docusaurus site to S3 -# run: aws s3 sync documentation/build s3://smart-forms-docs/docs + # deploy-docusaurus-s3: + # name: Deploy Docusaurus to S3 + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # + # - name: Use Node.js 18.x + # uses: actions/setup-node@v4 + # with: + # node-version: 18 + # cache: npm + # + # - name: Configure AWS credentials + # uses: aws-actions/configure-aws-credentials@v4 + # with: + # role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment + # aws-region: ap-southeast-2 + # + # - name: Install dependencies + # run: npm ci + # + # - name: Build documentation website + # run: npm run build -w documentation + # + # - name: Upload static Docusaurus site to S3 + # run: aws s3 sync documentation/build s3://smart-forms-docs/docs chromatic: name: Run Chromatic diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index 4f545b00..cd2ba413 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -22,6 +22,8 @@ const config: Config = { onBrokenLinks: 'warn', onBrokenMarkdownLinks: 'throw', + trailingSlash: false, + // Even if you don't use internationalization, you can use this field to set // useful metadata like html lang. For example, if your site is Chinese, you // may want to replace "en" with "zh-Hans". From fa84a345a0031998d5df78dada0d48e2be91b58e Mon Sep 17 00:00:00 2001 From: Sean Fong Date: Thu, 11 Jul 2024 11:25:32 +0930 Subject: [PATCH 22/22] CLean up github actions workflow, doc config and cloudfront redirect script --- .../{deploy_app_docs.yml => deploy_app.yml} | 27 ---------------- .../{deploy_test_docs.yml => deploy_docs.yml} | 0 .../SmartFormsRedirectToCorrectRoute.js | 31 +++++-------------- documentation/docusaurus.config.ts | 1 - 4 files changed, 8 insertions(+), 51 deletions(-) rename .github/workflows/{deploy_app_docs.yml => deploy_app.yml} (69%) rename .github/workflows/{deploy_test_docs.yml => deploy_docs.yml} (100%) diff --git a/.github/workflows/deploy_app_docs.yml b/.github/workflows/deploy_app.yml similarity index 69% rename from .github/workflows/deploy_app_docs.yml rename to .github/workflows/deploy_app.yml index e355c448..e48f9093 100644 --- a/.github/workflows/deploy_app_docs.yml +++ b/.github/workflows/deploy_app.yml @@ -45,33 +45,6 @@ jobs: - name: Upload static React site to S3 run: aws s3 sync apps/smart-forms-app/dist s3://smart-forms-react-app/ - # deploy-docusaurus-s3: - # name: Deploy Docusaurus to S3 - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - # - # - name: Use Node.js 18.x - # uses: actions/setup-node@v4 - # with: - # node-version: 18 - # cache: npm - # - # - name: Configure AWS credentials - # uses: aws-actions/configure-aws-credentials@v4 - # with: - # role-to-assume: arn:aws:iam::209248795938:role/SmartFormsReactAppDeployment - # aws-region: ap-southeast-2 - # - # - name: Install dependencies - # run: npm ci - # - # - name: Build documentation website - # run: npm run build -w documentation - # - # - name: Upload static Docusaurus site to S3 - # run: aws s3 sync documentation/build s3://smart-forms-docs/docs - chromatic: name: Run Chromatic runs-on: ubuntu-latest diff --git a/.github/workflows/deploy_test_docs.yml b/.github/workflows/deploy_docs.yml similarity index 100% rename from .github/workflows/deploy_test_docs.yml rename to .github/workflows/deploy_docs.yml diff --git a/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js b/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js index b085e05b..93db4651 100644 --- a/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js +++ b/deployment/cloudfront/SmartFormsRedirectToCorrectRoute.js @@ -82,28 +82,6 @@ function handler(event) { } - // Handle Docz routes - if (uri.includes('/docz')) { - // Reroute to smartforms.csiro.au/docz/index.html - if (uri === '/docz/') { - request.uri += 'index.html'; - return request; - } - - if (uri === '/docz') { - request.uri = '/redirect.html'; - return request; - } - - if (!uri.includes('.')) { - request.uri += '/index.html'; - return request; - } - - return request; - } - - // Handle Docs routes if (uri.includes('/docs')) { // Reroute to smartforms.csiro.au/docs/index.html @@ -118,7 +96,14 @@ function handler(event) { } if (!uri.includes('.')) { - request.uri += '/index.html'; + // For https://smartforms.csiro.au/docs/sdc/population/ cases + if (uri.endsWith('/')) { + request.uri += 'index.html'; + } + // For https://smartforms.csiro.au/docs/sdc/population cases + else { + request.uri += '/index.html'; + } return request; } diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index cd2ba413..bc66a853 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -56,7 +56,6 @@ const config: Config = { // Replace with your project's social card image: 'img/logo-sf.svg', navbar: { - title: 'Smart Forms', logo: { alt: 'Smart Forms', src: 'img/logo-sf.svg',