Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions COMPONENTS_INVENTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |
Expand Down Expand Up @@ -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)*
Original file line number Diff line number Diff line change
Expand Up @@ -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} :
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
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(() => [
{id: "titleResizable", value: titleFilter},
{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,
Expand Down Expand Up @@ -66,6 +85,23 @@ const DatasetDictionaryTable = ({ datasetDictionary, pageSize} : {datasetDiction

return (
<>
<div className="ds-u-margin-bottom--1 ds-u-display--flex ds-u-flex-wrap--wrap ds-u-justify-content--end">
<Button className="ds-l-col--12 ds-l-sm-col--6 ds-l-md-col--4" onClick={() => window.open(datasetDictionaryEndpoint)} type="button" >
<i className="fa fa-file-download ds-u-color--primary ds-u-padding-right--1"></i> View Dictionary JSON
</Button>
{showDownloadButton && (
<div className="ds-l-col--12 ds-l-sm-col--6 ds-l-md-col--4 ds-u-margin-top--2 ds-u-sm-margin-top--0 ds-u-padding--0 ds-u-sm-padding-left--2">
<a
href={datasetDictionaryEndpoint + "/csv"}
className="ds-c-button"
style={{width: '100%'}}
>
<i className="fa fa-file-download ds-u-color--primary ds-u-padding-right--1"></i>
Download Dictionary CSV
</a>
</div>
)}
</div>
<div className="dc-query-builder ds-u-margin-bottom--3">
<div className="ds-c-accordion ds-c-accordion--bordered">
<AccordionItem
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.data-dictionary-iframe-container {
width: 100%;
position: relative;
padding-top: 120%;
> .data-dictionary-iframe {
width: 100%;
height: 100%;
display: block;
position: absolute;
top: 0;
left: 0;
}
}
32 changes: 32 additions & 0 deletions src/components/DataDictionary/DatasetDictionaryPDF/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import './datasetDictionaryPDF.scss';

const DatasetDictionaryPDF = ({datasetDictionaryEndpoint} : {datasetDictionaryEndpoint: string}) => {
return (
<>
<div className="ds-u-margin-bottom--1 ds-u-display--flex ds-u-flex-wrap--wrap ds-u-justify-content--end">
<div className="ds-l-col--12 ds-l-sm-col--6 ds-l-md-col--4 ds-u-margin-top--2 ds-u-sm-margin-top--0 ds-u-padding--0 ds-u-sm-padding-left--2">
<a
href={datasetDictionaryEndpoint}
target='_blank'
rel='noopener noreferrer'
className="ds-c-button"
>
<i className="fa fa-file-download ds-u-color--primary ds-u-padding-right--1"></i> Download Dictionary PDF
</a>
</div>
</div>
{(!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
<p>The data dictionary file can not be displayed at this time.</p>
) : (
<div className='data-dictionary-iframe-container ds-u-margin-top--3'>
<iframe src={datasetDictionaryEndpoint} className='data-dictionary-iframe'></iframe>
</div>
)}
</>
)
}

export default DatasetDictionaryPDF;
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ describe('<DataDictionary />', () => {
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
}
Expand All @@ -35,12 +38,13 @@ describe('<DataDictionary />', () => {
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(<MemoryRouter>
<DataDictionary
datasetDictionaryEndpoint='http://dkan.com/api/1/metastore/schemas/data-dictionary/items/71ec19df-f5ef-5b99-b43b-e566e22670b7'
title="Dataset test title"
datasetDictionaryFileType='application/vnd.tableschema+json'
/>
</MemoryRouter>);
});
Expand All @@ -53,4 +57,20 @@ describe('<DataDictionary />', () => {
expect(screen.queryByText('Description')).toBeInTheDocument();
});
});
});
test("Renders PDF dataset dictionary correctly", async () => {
await render(
<MemoryRouter>
<DataDictionary
datasetDictionaryEndpoint='http://dkan.com/api/1/provider-data/sites/default/files/data_dictionaries/hospital/HOSPITAL_Data_Dictionary.pdf'
title="Dataset test PDF"
datasetDictionaryFileType='application/pdf'
/>
</MemoryRouter>);

expect(screen.getByRole('heading', {name: 'Dataset test PDF'}));
await waitFor(() => {
expect(axios.get).toHaveBeenCalled();
expect(screen.queryByText('Download Dictionary PDF')).toBeInTheDocument();
});
})
});
58 changes: 12 additions & 46 deletions src/components/DatasetDataDictionaryTab/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div data-testid="dataset-dictionary-tab">
<h2 className="ds-text-heading--2xl ds-u-margin-y--3">{title}</h2>
{datasetDictionary && (
<>
<div className="ds-u-margin-bottom--1 ds-u-display--flex ds-u-flex-wrap--wrap ds-u-justify-content--end">
<Button className="ds-l-col--12 ds-l-sm-col--6 ds-l-md-col--4" onClick={() => window.open(datasetDictionaryEndpoint)} type="button" >
<i className="fa fa-file-download ds-u-color--primary ds-u-padding-right--1"></i> View Dictionary JSON
</Button>
{csvDownload && (
<div className="ds-l-col--12 ds-l-sm-col--6 ds-l-md-col--4 ds-u-margin-top--2 ds-u-sm-margin-top--0 ds-u-padding--0 ds-u-sm-padding-left--2">
<a
href={datasetDictionaryEndpoint + "/csv"}
className="ds-c-button"
style={{width: '100%'}}
>
<i className="fa fa-file-download ds-u-color--primary ds-u-padding-right--1"></i>
Download Dictionary CSV
</a>
</div>
)}
</div>
<DatasetDictionaryTable datasetDictionary={datasetDictionary} pageSize={pageSize}/>
</>
{datasetDictionaryFileType === 'application/vnd.tableschema+json' && (
<DatasetDictionaryJSON datasetDictionaryEndpoint={datasetDictionaryEndpoint} pageSize={pageSize} showDownloadButton={csvDownload} />
)}
{datasetDictionaryFileType === 'application/pdf' && (
<DatasetDictionaryPDF datasetDictionaryEndpoint={datasetDictionaryEndpoint}/>
)}

{datasetSitewideDictionary && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/DatasetSearchListItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/SitewideDataDictionaryTable/index.tsx
Original file line number Diff line number Diff line change
@@ -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<DatasetDictionaryItemType>()
Expand Down
8 changes: 7 additions & 1 deletion src/templates/Dataset/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 : '';
Expand All @@ -78,6 +82,7 @@ const Dataset = ({
distribution = distributions[0];
}


const resource = useDatastore(
'',
rootUrl,
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -246,6 +251,7 @@ const Dataset = ({
<DataDictionary
datasetSitewideDictionary={datasetSitewideDictionary}
datasetDictionaryEndpoint={distribution.data.describedBy}
datasetDictionaryFileType={distribution.data.describedByType}
title={"Data Dictionary"}
csvDownload={dataDictionaryCSV}
/>
Expand Down