diff --git a/COMPONENTS_INVENTORY.md b/COMPONENTS_INVENTORY.md index 029910a5..9dc05c14 100644 --- a/COMPONENTS_INVENTORY.md +++ b/COMPONENTS_INVENTORY.md @@ -11,7 +11,7 @@ This document provides a comprehensive inventory of all components, services, te | Component | [ApiRowLimitNotice](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/ApiRowLimitNotice) | ✅ Public | ✅ Has Story | ❌ No Tests | | Component | [Breadcrumb](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/Breadcrumb) | ✅ Public | ✅ Has Story | ❌ No Tests | | Component | [CMSTopNav](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/CMSTopNav) | ✅ Public | ✅ Has Story | ❌ No Tests | -| Component | [DataDictionaryTable](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DataDictionaryTable) | ❌ Internal | ✅ Has Story | ❌ No Tests | +| Component | [DataDictionary](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DataDictionary) | ❌ Internal | ❌ No Story | ❌ No Tests | | Component | [DataTableControls](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DataTableControls) | ❌ Internal | ❌ No Story | ✅ Has Tests | | Component | [DataTableDensity](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DataTableDensity) | ❌ Internal | ✅ Has Story | ✅ Has Tests | | Component | [DataTablePageResults](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DataTablePageResults) | ✅ Public | ✅ Has Story | ❌ No Tests | @@ -23,7 +23,6 @@ This document provides a comprehensive inventory of all components, services, te | Component | [DatasetDate](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DatasetDate) | ✅ Public | ❌ No Story | ✅ Has Tests | | Component | [DatasetDateItem](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DatasetDateItem) | ✅ Public | ❌ No Story | ✅ Has Tests | | Component | [DatasetDescription](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DatasetDescription) | ❌ Internal | ✅ Has Story | ✅ Has Tests | -| Component | [DatasetDictionaryTable](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DatasetDictionaryTable) | ❌ Internal | ✅ Has Story | ❌ No Tests | | Component | [DatasetListItem](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DatasetListItem) | ✅ Public | ❌ No Story | ✅ Has Tests | | Component | [DatasetListSubmenu](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DatasetListSubmenu) | ✅ Public | ❌ No Story | ❌ No Tests | | Component | [DatasetListSubmenuItem](https://github.com/GetDKAN/cmsds-open-data-components/tree/main/src/components/DatasetListSubmenuItem) | ❌ Internal | ✅ Has Story | ✅ Has Tests | @@ -121,19 +120,19 @@ This document provides a comprehensive inventory of all components, services, te | Category | Total | With Stories | With Tests | With Both | With Neither | |----------|-------|--------------|------------|-----------|--------------| -| Components | 64 | 37 (58%) | 23 (36%) | 10 (16%) | 14 (22%) | +| Components | 63 | 35 (56%) | 23 (37%) | 10 (16%) | 15 (24%) | | Templates | 11 | 7 (64%) | 3 (27%) | 1 (9%) | 2 (18%) | | Services/Hooks/Contexts | 9 | 0 (0%) | 0 (0%) | 0 (0%) | 9 (100%) | | Utilities/Types/Assets | 10 | 0 (0%) | 0 (0%) | 0 (0%) | 10 (100%) | -| **Project Total** | **94** | **44 (47%)** | **26 (28%)** | **11 (12%)** | **35 (37%)** | +| **Project Total** | **93** | **42 (45%)** | **26 (28%)** | **11 (12%)** | **36 (39%)** | --- ### Export Summary - **Public**: 57 items (33 components, 11 templates, 3 services, 2 hooks, 4 contexts, 2 asset) -- **Internal**: 37 items (31 components, 0 templates, 0 services, 3 utilities, 3 types, 0 asset) +- **Internal**: 36 items (30 components, 0 templates, 0 services, 3 utilities, 3 types, 0 asset) --- -*Last updated: December 12, 2025* +*Last updated: December 23, 2025* *Repository: [GetDKAN/cmsds-open-data-components](https://github.com/GetDKAN/cmsds-open-data-components)* diff --git a/src/components/DataDictionaryTable/DataDictionaryTable.stories.tsx b/src/components/DataDictionary/DataDictionaryTable/DataDictionaryTable.stories.tsx similarity index 100% rename from src/components/DataDictionaryTable/DataDictionaryTable.stories.tsx rename to src/components/DataDictionary/DataDictionaryTable/DataDictionaryTable.stories.tsx diff --git a/src/components/DataDictionaryTable/dataDictionaryTable.scss b/src/components/DataDictionary/DataDictionaryTable/dataDictionaryTable.scss similarity index 100% rename from src/components/DataDictionaryTable/dataDictionaryTable.scss rename to src/components/DataDictionary/DataDictionaryTable/dataDictionaryTable.scss diff --git a/src/components/DataDictionaryTable/index.tsx b/src/components/DataDictionary/DataDictionaryTable/index.tsx similarity index 98% rename from src/components/DataDictionaryTable/index.tsx rename to src/components/DataDictionary/DataDictionaryTable/index.tsx index 8af5f41a..1bdc37cc 100644 --- a/src/components/DataDictionaryTable/index.tsx +++ b/src/components/DataDictionary/DataDictionaryTable/index.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { useReactTable, flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, SortingState, getFilteredRowModel, ColumnFiltersState } from '@tanstack/react-table'; import { useMediaQuery } from 'react-responsive'; import { Table, TableHead, TableRow, TableCell, TableBody, Pagination, Dropdown, DropdownChangeObject, Alert } from '@cmsgov/design-system'; -import HeaderResizeElement from '../Datatable/HeaderResizeElement'; +import HeaderResizeElement from '../../Datatable/HeaderResizeElement'; import './dataDictionaryTable.scss'; const DataDictionaryTable = ({tableColumns, tableData, pageSize, columnFilters} : diff --git a/src/components/DatasetDictionaryTable/DatasetDictionaryTable.stories.tsx b/src/components/DataDictionary/DatasetDictionaryJSON/DatasetDictionaryJSON.stories.tsx similarity index 100% rename from src/components/DatasetDictionaryTable/DatasetDictionaryTable.stories.tsx rename to src/components/DataDictionary/DatasetDictionaryJSON/DatasetDictionaryJSON.stories.tsx diff --git a/src/components/DatasetDictionaryTable/dataDictionary.scss b/src/components/DataDictionary/DatasetDictionaryJSON/dataDictionaryJSON.scss similarity index 100% rename from src/components/DatasetDictionaryTable/dataDictionary.scss rename to src/components/DataDictionary/DatasetDictionaryJSON/dataDictionaryJSON.scss diff --git a/src/components/DatasetDictionaryTable/index.tsx b/src/components/DataDictionary/DatasetDictionaryJSON/index.tsx similarity index 61% rename from src/components/DatasetDictionaryTable/index.tsx rename to src/components/DataDictionary/DatasetDictionaryJSON/index.tsx index c2c38795..58ed8809 100644 --- a/src/components/DatasetDictionaryTable/index.tsx +++ b/src/components/DataDictionary/DatasetDictionaryJSON/index.tsx @@ -1,13 +1,17 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useContext } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import qs from 'qs'; +import axios from 'axios'; import { createColumnHelper } from '@tanstack/react-table'; -import { TextField, Dropdown, AccordionItem, Button, DropdownChangeObject } from '@cmsgov/design-system'; -import { DatasetDictionaryItemType } from '../../types/dataset'; +import { TextField, Dropdown, AccordionItem, Button, DropdownChangeObject, Tooltip, TooltipIcon } from '@cmsgov/design-system'; +import { DatasetDictionaryItemType } from '../../../types/dataset'; import DataDictionaryTable from '../DataDictionaryTable'; -import { Tooltip, TooltipIcon } from '@cmsgov/design-system'; -import "./dataDictionary.scss" -import ClearFiltersButton from '../QueryBuilder/ClearFiltersButton'; +import "./dataDictionaryJSON.scss" +import ClearFiltersButton from '../../QueryBuilder/ClearFiltersButton'; +import { acaToParams } from '../../../utilities/aca'; +import { ACAContext } from '../../../utilities/ACAContext'; -const DatasetDictionaryTable = ({ datasetDictionary, pageSize} : {datasetDictionary: DatasetDictionaryItemType[], pageSize: number}) => { +const DatasetDictionaryTable = ({ datasetDictionaryEndpoint, pageSize, showDownloadButton} : {datasetDictionaryEndpoint: string, pageSize: number, showDownloadButton: boolean}) => { const [titleFilter, setTitleFilter ] = useState(""); const [typeFilter, setTypeFilter ] = useState("all"); const columnFilters = useMemo(() => [ @@ -15,7 +19,22 @@ const DatasetDictionaryTable = ({ datasetDictionary, pageSize} : {datasetDiction {id: "type", value: typeFilter === "all" ? "" : typeFilter} ], [titleFilter, typeFilter]) - const tableData = datasetDictionary.map((item) => { + const {ACA} = useContext(ACAContext); + + const {data} = useQuery({ + queryKey: ["dictionary" + datasetDictionaryEndpoint], + queryFn: () => { + return axios.get(`${datasetDictionaryEndpoint}?${qs.stringify(acaToParams({}, ACA))}`) + .then((res) => res.data) + .catch((error) => console.error(error)) + }, + enabled: datasetDictionaryEndpoint !== undefined + }); + + const datasetDictionary = data && data.data && data.data.fields && data.data.fields.length ? data.data.fields : null; + if(!datasetDictionary) return <>; + + const tableData = datasetDictionary.map((item : {title: string, description: string, type: string}) => { return { titleResizable: item.title, description: item.description, @@ -66,6 +85,23 @@ const DatasetDictionaryTable = ({ datasetDictionary, pageSize} : {datasetDiction return ( <> +
+ + {showDownloadButton && ( +
+ + + Download Dictionary CSV + +
+ )} +
.data-dictionary-iframe { + width: 100%; + height: 100%; + display: block; + position: absolute; + top: 0; + left: 0; + } +} diff --git a/src/components/DataDictionary/DatasetDictionaryPDF/index.tsx b/src/components/DataDictionary/DatasetDictionaryPDF/index.tsx new file mode 100644 index 00000000..f6d81087 --- /dev/null +++ b/src/components/DataDictionary/DatasetDictionaryPDF/index.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import './datasetDictionaryPDF.scss'; + +const DatasetDictionaryPDF = ({datasetDictionaryEndpoint} : {datasetDictionaryEndpoint: string}) => { + return ( + <> +
+
+ + Download Dictionary PDF + +
+
+ {(!window.location.host.includes('cms.gov') && datasetDictionaryEndpoint) ? ( + // We can not iframe the data dictionary file on a domain other than https://*.cms.gov + // because there is a Content Security Policy directive set +

The data dictionary file can not be displayed at this time.

+ ) : ( +
+ +
+ )} + + ) +} + +export default DatasetDictionaryPDF; diff --git a/src/components/DatasetDataDictionaryTab/DatasetDataDictionaryTab.test.jsx b/src/components/DatasetDataDictionaryTab/DatasetDataDictionaryTab.test.jsx index 37cfac32..55d7f776 100644 --- a/src/components/DatasetDataDictionaryTab/DatasetDataDictionaryTab.test.jsx +++ b/src/components/DatasetDataDictionaryTab/DatasetDataDictionaryTab.test.jsx @@ -17,6 +17,9 @@ describe('', () => { return Promise.resolve({data: siteWideDataDictionary}); case 'http://dkan.com/api/1/metastore/schemas/data-dictionary/items/71ec19df-f5ef-5b99-b43b-e566e22670b7?': return Promise.resolve({data: datasetDictionary}); + case 'http://dkan.com/api/1/provider-data/sites/default/files/data_dictionaries/hospital/HOSPITAL_Data_Dictionary.pdf': + const pdf = new Blob(["testing"], { type: "application/pdf" }); + return pdf; default: return } @@ -35,12 +38,13 @@ describe('', () => { expect(screen.queryByText('Description')).not.toBeInTheDocument(); expect(screen.queryByText('Download Dictionary JSON')).not.toBeInTheDocument(); }); - test("Renders dataset dictionary correctly", async () => { + test("Renders JSON dataset dictionary correctly", async () => { await act(async () => { await render( ); }); @@ -53,4 +57,20 @@ describe('', () => { expect(screen.queryByText('Description')).toBeInTheDocument(); }); }); -}); \ No newline at end of file + test("Renders PDF dataset dictionary correctly", async () => { + await render( + + + ); + + expect(screen.getByRole('heading', {name: 'Dataset test PDF'})); + await waitFor(() => { + expect(axios.get).toHaveBeenCalled(); + expect(screen.queryByText('Download Dictionary PDF')).toBeInTheDocument(); + }); + }) +}); diff --git a/src/components/DatasetDataDictionaryTab/index.tsx b/src/components/DatasetDataDictionaryTab/index.tsx index b0986847..78f7984e 100644 --- a/src/components/DatasetDataDictionaryTab/index.tsx +++ b/src/components/DatasetDataDictionaryTab/index.tsx @@ -1,64 +1,30 @@ -import React, { useContext } from 'react'; +import React from 'react'; import withQueryProvider from '../../utilities/QueryProvider/QueryProvider'; -import { useQuery } from '@tanstack/react-query'; -import qs from 'qs'; -import axios from 'axios'; - import { DatasetDictionaryItemType } from '../../types/dataset'; import SitewideDataDictionaryTable from '../SitewideDataDictionaryTable'; -import DatasetDictionaryTable from '../DatasetDictionaryTable'; -import { Button, Spinner } from '@cmsgov/design-system'; -import { acaToParams } from '../../utilities/aca'; -import { ACAContext } from '../../utilities/ACAContext'; +import DatasetDictionaryJSON from "../DataDictionary/DatasetDictionaryJSON"; +import DatasetDictionaryPDF from '../DataDictionary/DatasetDictionaryPDF'; const DataDictionary = ( - { datasetDictionaryEndpoint, datasetSitewideDictionary, title, pageSize = 20, csvDownload } : + { datasetDictionaryEndpoint, datasetSitewideDictionary, datasetDictionaryFileType, title, pageSize = 20, csvDownload } : { datasetDictionaryEndpoint: string, - datasetSitewideDictionary: DatasetDictionaryItemType[] + datasetSitewideDictionary: DatasetDictionaryItemType[], + datasetDictionaryFileType: string, title: string, pageSize: number, additionalParams: any, - csvDownload : boolean + csvDownload : boolean, }) => { - const {ACA} = useContext(ACAContext) - - const {data, isPending, error} = useQuery({ - queryKey: ["dictionary" + datasetDictionaryEndpoint], - queryFn: () => { - return axios.get(`${datasetDictionaryEndpoint}?${qs.stringify(acaToParams({}, ACA))}`) - .then((res) => res.data) - .catch((error) => console.error(error)) - }, - enabled: datasetDictionaryEndpoint !== undefined - }); - - const datasetDictionary = data && data.data && data.data.fields && data.data.fields.length ? data.data.fields : null; return (

{title}

- {datasetDictionary && ( - <> -
- - {csvDownload && ( - - )} -
- - + {datasetDictionaryFileType === 'application/vnd.tableschema+json' && ( + + )} + {datasetDictionaryFileType === 'application/pdf' && ( + )} {datasetSitewideDictionary && ( diff --git a/src/components/DatasetSearchListItem/index.tsx b/src/components/DatasetSearchListItem/index.tsx index 08587155..2e9ab38b 100644 --- a/src/components/DatasetSearchListItem/index.tsx +++ b/src/components/DatasetSearchListItem/index.tsx @@ -140,7 +140,7 @@ const DatasetSearchListItem = (props: SearchItemProps) => { const dataDictionaryExists = (): boolean => { if (distribution && "data" in distribution) { if ("describedBy" in distribution.data && "describedByType" in distribution.data) { - return distribution.data.describedByType === 'application/vnd.tableschema+json'; + return distribution.data.describedByType === 'application/vnd.tableschema+json' || distribution.data.describedByType === "application/pdf"; } } diff --git a/src/components/SitewideDataDictionaryTable/index.tsx b/src/components/SitewideDataDictionaryTable/index.tsx index 89d9a8fa..b8ddb46a 100644 --- a/src/components/SitewideDataDictionaryTable/index.tsx +++ b/src/components/SitewideDataDictionaryTable/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { createColumnHelper } from '@tanstack/react-table'; import { DatasetDictionaryItemType } from '../../types/dataset'; -import DataDictionaryTable from '../DataDictionaryTable'; +import DataDictionaryTable from '../DataDictionary/DataDictionaryTable'; const SitewideDataDictionaryTable = ({ datasetDictionary, pageSize} : {datasetDictionary: DatasetDictionaryItemType[], pageSize: number}) => { const columnHelper = createColumnHelper() diff --git a/src/templates/Dataset/index.tsx b/src/templates/Dataset/index.tsx index 9a5079ed..029ceee8 100644 --- a/src/templates/Dataset/index.tsx +++ b/src/templates/Dataset/index.tsx @@ -64,6 +64,10 @@ const Dataset = ({ const options = location.search ? { ...qs.parse(location.search, { ignoreQueryPrefix: true }) } : { conditions: [] }; + const dataDictionaryTypes = [ + 'application/vnd.tableschema+json', + 'application/pdf', + ] const { dataset, isPending } = useMetastoreDataset(id, rootUrl); const title = dataset.title ? dataset.title : ''; @@ -78,6 +82,7 @@ const Dataset = ({ distribution = distributions[0]; } + const resource = useDatastore( '', rootUrl, @@ -145,7 +150,7 @@ const Dataset = ({ setSelectedTab(window.location.hash.substring(1)) }, [distribution, window.location.hash]) - const displayDataDictionaryTab = ((distribution.data && distribution.data.describedBy && distribution.data.describedByType === 'application/vnd.tableschema+json') || (datasetSitewideDictionary && datasetSitewideDictionary.length > 0)) as boolean; + const displayDataDictionaryTab = (distribution.data && distribution.data.describedBy && dataDictionaryTypes.includes( distribution.data.describedByType) || (datasetSitewideDictionary && datasetSitewideDictionary.length > 0)) as boolean; const date = {modified: dataset.modified, released: dataset.released, refresh: dataset.nextUpdateDate}; return ( @@ -246,6 +251,7 @@ const Dataset = ({