Skip to main content
Glama
BackboneElementDisplay.tsx5.44 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { ElementsContextType, TypedValue } from '@medplum/core'; import { buildElementsContext, getPathDisplayName, isEmpty, tryGetDataType } from '@medplum/core'; import type { AccessPolicyResource } from '@medplum/fhirtypes'; import type { JSX } from 'react'; import { useContext, useMemo } from 'react'; import { DEFAULT_IGNORED_NON_NESTED_PROPERTIES, DEFAULT_IGNORED_PROPERTIES } from '../constants'; import { DescriptionList, DescriptionListEntry } from '../DescriptionList/DescriptionList'; import { ElementsContext } from '../ElementsInput/ElementsInput.utils'; import { ResourcePropertyDisplay } from '../ResourcePropertyDisplay/ResourcePropertyDisplay'; import { getValueAndType } from '../ResourcePropertyDisplay/ResourcePropertyDisplay.utils'; import { maybeWrapWithContext } from '../utils/maybeWrapWithContext'; const EXTENSION_KEYS = ['extension', 'modifierExtension']; const IGNORED_PROPERTIES = DEFAULT_IGNORED_PROPERTIES.filter((prop) => !EXTENSION_KEYS.includes(prop)); export interface BackboneElementDisplayProps { readonly value: TypedValue; /** The path identifies the element and is expressed as a "."-separated list of ancestor elements, beginning with the name of the resource or extension. */ readonly path: string; readonly compact?: boolean; readonly ignoreMissingValues?: boolean; readonly link?: boolean; /** (optional) Profile URL of the structure definition represented by the backbone element */ readonly profileUrl?: string; /** * (optional) If provided, inputs specified in `accessPolicyResource.hiddenFields` are not shown. */ readonly accessPolicyResource?: AccessPolicyResource; } export function BackboneElementDisplay(props: BackboneElementDisplayProps): JSX.Element | null { const typedValue = props.value; const { value, type: typeName } = typedValue; const parentElementsContext = useContext(ElementsContext); const profileUrl = props.profileUrl ?? parentElementsContext?.profileUrl; const typeSchema = useMemo(() => tryGetDataType(typeName, profileUrl), [profileUrl, typeName]); const newElementsContext: ElementsContextType | undefined = useMemo(() => { if (!typeSchema) { return undefined; } return buildElementsContext({ parentContext: parentElementsContext, elements: typeSchema.elements, path: props.path, profileUrl: typeSchema.url, accessPolicyResource: props.accessPolicyResource, }); }, [typeSchema, parentElementsContext, props.path, props.accessPolicyResource]); if (isEmpty(value)) { return null; } if (!typeSchema) { return <div>{typeName}&nbsp;not implemented</div>; } if ( typeof value === 'object' && 'name' in value && Object.keys(value).length === 1 && typeof value.name === 'string' ) { // Special case for common BackboneElement pattern // Where there is an object with a single property 'name' // Just display the name value. return <div>{value.name}</div>; } // Since this component may create a new ElementsContext, compute the effective context for use in this component const elementsContext = newElementsContext ?? parentElementsContext; return maybeWrapWithContext( ElementsContext.Provider, newElementsContext, <DescriptionList compact={props.compact}> {Object.entries(elementsContext.elements).map(([key, property]) => { if (EXTENSION_KEYS.includes(key) && isEmpty(property.slicing?.slices)) { // an extension property without slices has no nested extensions return null; } else if (IGNORED_PROPERTIES.includes(key)) { return null; } else if (DEFAULT_IGNORED_NON_NESTED_PROPERTIES.includes(key) && property.path.split('.').length === 2) { return null; } // Profiles can include nested elements in addition to their containing element, e.g.: // identifier, identifier.use, identifier.system // Skip nested elements, e.g. identifier.use, since they are handled by the containing element if (key.includes('.')) { return null; } const [propertyValue, propertyType] = getValueAndType(typedValue, key, elementsContext.profileUrl); if ((props.ignoreMissingValues || property.max === 0) && isEmpty(propertyValue)) { return null; } if (props.path.endsWith('.extension') && (key === 'url' || key === 'id')) { return null; } // Array values provide their own DescriptionListEntry wrapper(s) const isArrayProperty = property.max > 1 || property.isArray; const resourcePropertyDisplay = ( <ResourcePropertyDisplay key={key} property={property} propertyType={propertyType} path={props.path + '.' + key} value={propertyValue} ignoreMissingValues={props.ignoreMissingValues} includeArrayDescriptionListEntry={isArrayProperty} link={props.link} /> ); if (isArrayProperty) { return resourcePropertyDisplay; } return ( <DescriptionListEntry key={key} term={getPathDisplayName(key)}> {resourcePropertyDisplay} </DescriptionListEntry> ); })} </DescriptionList> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server