@databiosphere/findable-ui 21.1.1 → 21.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/lib/common/entities.d.ts +33 -0
  4. package/lib/components/DataDictionary/common/utils.d.ts +38 -0
  5. package/lib/components/DataDictionary/common/utils.js +122 -0
  6. package/lib/components/DataDictionary/components/Tooltip/components/Title/constants.d.ts +3 -0
  7. package/lib/components/DataDictionary/components/Tooltip/components/Title/constants.js +8 -0
  8. package/lib/components/DataDictionary/components/Tooltip/components/Title/title.d.ts +3 -0
  9. package/lib/components/DataDictionary/components/Tooltip/components/Title/title.js +9 -0
  10. package/lib/components/DataDictionary/components/Tooltip/components/Title/types.d.ts +5 -0
  11. package/lib/components/DataDictionary/components/Tooltip/components/Title/types.js +1 -0
  12. package/lib/components/DataDictionary/components/Tooltip/constants.d.ts +2 -0
  13. package/lib/components/DataDictionary/components/Tooltip/constants.js +24 -0
  14. package/lib/components/DataDictionary/components/Tooltip/tooltip.d.ts +4 -0
  15. package/lib/components/DataDictionary/components/Tooltip/tooltip.js +8 -0
  16. package/lib/components/DataDictionary/components/Tooltip/types.d.ts +5 -0
  17. package/lib/components/DataDictionary/components/Tooltip/types.js +1 -0
  18. package/lib/components/Filter/components/Filter/filter.js +1 -1
  19. package/lib/components/Filter/components/FilterLabel/filterLabel.d.ts +3 -1
  20. package/lib/components/Filter/components/FilterLabel/filterLabel.js +4 -2
  21. package/lib/components/Index/components/Tabs/common/utils.js +2 -1
  22. package/lib/components/Table/components/TableHead/tableHead.js +4 -1
  23. package/lib/components/common/Tabs/tabs.d.ts +2 -0
  24. package/lib/components/common/Tabs/tabs.js +14 -1
  25. package/lib/config/entities.d.ts +5 -1
  26. package/lib/hooks/useCategoryFilter.js +1 -0
  27. package/lib/providers/config.js +9 -2
  28. package/package.json +1 -1
  29. package/src/common/entities.ts +37 -0
  30. package/src/components/DataDictionary/common/utils.ts +160 -0
  31. package/src/components/DataDictionary/components/Tooltip/components/Title/constants.ts +11 -0
  32. package/src/components/DataDictionary/components/Tooltip/components/Title/title.tsx +19 -0
  33. package/src/components/DataDictionary/components/Tooltip/components/Title/types.ts +6 -0
  34. package/src/components/DataDictionary/components/Tooltip/constants.ts +26 -0
  35. package/src/components/DataDictionary/components/Tooltip/tooltip.tsx +26 -0
  36. package/src/components/DataDictionary/components/Tooltip/types.ts +10 -0
  37. package/src/components/Filter/components/Filter/filter.tsx +1 -0
  38. package/src/components/Filter/components/FilterLabel/filterLabel.tsx +16 -10
  39. package/src/components/Index/components/Tabs/common/utils.ts +2 -0
  40. package/src/components/Table/components/TableHead/tableHead.tsx +26 -15
  41. package/src/components/common/Tabs/tabs.tsx +33 -3
  42. package/src/config/entities.ts +10 -1
  43. package/src/hooks/useCategoryFilter.ts +1 -0
  44. package/src/providers/config.tsx +10 -2
  45. package/tests/dataDictionary_utils.test.ts +153 -0
  46. package/types/data-explorer-ui.d.ts +2 -0
@@ -0,0 +1,160 @@
1
+ import {
2
+ Attribute,
3
+ Class,
4
+ DataDictionary,
5
+ DataDictionaryAnnotation,
6
+ } from "../../../common/entities";
7
+ import { CategoryGroupConfig, SiteConfig } from "../../../config/entities";
8
+
9
+ /**
10
+ * Annotate each entity column configuration with data dictionary values. Specifically,
11
+ * look up label and description for each column key.
12
+ * @param siteConfig - Site configuration to annotate.
13
+ * @param annotationsByKey - Data dictionary annotations keyed by key.
14
+ */
15
+ export function annotateColumnConfig(
16
+ siteConfig: SiteConfig,
17
+ annotationsByKey: Record<string, DataDictionaryAnnotation>
18
+ ): void {
19
+ // Annotate every column in every entity.
20
+ siteConfig.entities.forEach((entity) => {
21
+ entity.list.columns.forEach((columnConfig) => {
22
+ // Find the annotation for the column key.
23
+ const annotation = annotationsByKey[columnConfig.id];
24
+ if (!annotation) {
25
+ return;
26
+ }
27
+
28
+ if (!columnConfig.meta) {
29
+ columnConfig.meta = {};
30
+ }
31
+ columnConfig.meta.annotation = annotation;
32
+ });
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Annotate filter and colummn configuration with data dictionary values. Note this
38
+ * functionality mutates the site config. A possible future improvement would be to
39
+ * create either a specific "raw" or "annotated" type to indicate clearly the point
40
+ * at which the config has been annotated.
41
+ * @param siteConfig - The site configuration to annotate.
42
+ */
43
+ export function annotateSiteConfig(siteConfig: SiteConfig): void {
44
+ // Build and map data dictionary annotations by key.
45
+ const { dataDictionary } = siteConfig;
46
+ if (!dataDictionary) {
47
+ return;
48
+ }
49
+ const annotationsByKey = keyAnnotationsByKey(dataDictionary);
50
+
51
+ // Annotate elements of site config.
52
+ annotateEntityConfig(siteConfig, annotationsByKey);
53
+ annotateDefaultCategoryConfig(siteConfig, annotationsByKey);
54
+ annotateEntityCategoryConfig(siteConfig, annotationsByKey);
55
+ annotateColumnConfig(siteConfig, annotationsByKey);
56
+ }
57
+
58
+ /**
59
+ * Annotate entity configuration with data dictionary values. Specifically, look
60
+ * up label and description for each entity key.
61
+ * @param siteConfig - The site configuration to annotate.
62
+ * @param annotationsByKey - Data dictionary annotations keyed by key.
63
+ */
64
+ export function annotateEntityConfig(
65
+ siteConfig: SiteConfig,
66
+ annotationsByKey: Record<string, DataDictionaryAnnotation>
67
+ ): void {
68
+ // Annotate every entity.
69
+ siteConfig.entities.forEach((entityConfig) => {
70
+ // Check entity for a data dictionary key.
71
+ const { key } = entityConfig;
72
+ if (!key) {
73
+ return;
74
+ }
75
+
76
+ // Find corresponding annotation for the key and set on entity config.
77
+ entityConfig.annotation = annotationsByKey[key];
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Annotate top-level (app-wide) category config with data dictionary values.
83
+ * Specifically, look up label and description for each filter key.
84
+ * @param siteConfig - Site configuration to annotate.
85
+ * @param annotationsByKey - Data dictionary annotations keyed by key.
86
+ */
87
+ export function annotateDefaultCategoryConfig(
88
+ siteConfig: SiteConfig,
89
+ annotationsByKey: Record<string, DataDictionaryAnnotation>
90
+ ): void {
91
+ const { categoryGroupConfig } = siteConfig;
92
+ if (categoryGroupConfig) {
93
+ annotateCategoryGroupConfig(categoryGroupConfig, annotationsByKey);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Annotate entity-specific category config with data dictionary values. Specifically,
99
+ * look up label and description for each category key.
100
+ * @param siteConfig - Site configuration to annotate.
101
+ * @param annotationsByKey - Data dictionary annotations keyed by key.
102
+ */
103
+ export function annotateEntityCategoryConfig(
104
+ siteConfig: SiteConfig,
105
+ annotationsByKey: Record<string, DataDictionaryAnnotation>
106
+ ): void {
107
+ // Annotate every category in every entity.
108
+ siteConfig.entities.forEach((entityConfig) => {
109
+ const { categoryGroupConfig } = entityConfig;
110
+ if (categoryGroupConfig) {
111
+ annotateCategoryGroupConfig(categoryGroupConfig, annotationsByKey);
112
+ }
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Annonate category group configuration with data dictionary values.
118
+ * @param categoryGroupConfig - Category group to annotate.
119
+ * @param annotationsByKey - Data dictionary annotations keyed by key.
120
+ */
121
+ function annotateCategoryGroupConfig(
122
+ categoryGroupConfig: CategoryGroupConfig,
123
+ annotationsByKey: Record<string, DataDictionaryAnnotation>
124
+ ): void {
125
+ categoryGroupConfig.categoryGroups.forEach((categoryGroup) => {
126
+ categoryGroup.categoryConfigs.forEach((categorConfig) => {
127
+ categorConfig.annotation = annotationsByKey[categorConfig.key];
128
+ });
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Transform a data dictionary into a key-annotation map. Build annotations for both
134
+ * classes and attributes and add to map.
135
+ * @param dataDictionary - Data dictionary to transform into a key-annotation map.
136
+ * @returns Key-annotation map.
137
+ */
138
+ function keyAnnotationsByKey(
139
+ dataDictionary: DataDictionary
140
+ ): Record<string, DataDictionaryAnnotation> {
141
+ return dataDictionary.classes.reduce(
142
+ (acc: Record<string, DataDictionaryAnnotation>, cls: Class) => {
143
+ // Add class to map.
144
+ acc[cls.key] = {
145
+ description: cls.description,
146
+ label: cls.label,
147
+ };
148
+
149
+ // Add each class attribute to the map.
150
+ cls.attributes.forEach((attribute: Attribute) => {
151
+ acc[attribute.key] = {
152
+ description: attribute.description,
153
+ label: attribute.label,
154
+ };
155
+ });
156
+ return acc;
157
+ },
158
+ {} as Record<string, DataDictionaryAnnotation>
159
+ );
160
+ }
@@ -0,0 +1,11 @@
1
+ import { StackProps, TypographyProps } from "@mui/material";
2
+
3
+ export const STACK_PROPS: StackProps = {
4
+ spacing: 2,
5
+ useFlexGap: true,
6
+ };
7
+
8
+ export const TYPOGRAPHY_PROPS: TypographyProps = {
9
+ color: "ink.light",
10
+ component: "div",
11
+ };
@@ -0,0 +1,19 @@
1
+ import { Stack, Typography } from "@mui/material";
2
+ import React from "react";
3
+ import { TEXT_BODY_LARGE_500 } from "../../../../../../theme/common/typography";
4
+ import { BaseComponentProps } from "../../../../../types";
5
+ import { STACK_PROPS, TYPOGRAPHY_PROPS } from "./constants";
6
+ import { TitleProps } from "./types";
7
+
8
+ export const Title = ({
9
+ className,
10
+ description,
11
+ title,
12
+ }: BaseComponentProps & Required<TitleProps>): JSX.Element => {
13
+ return (
14
+ <Stack {...STACK_PROPS} className={className}>
15
+ <Typography variant={TEXT_BODY_LARGE_500}>{title}</Typography>
16
+ <Typography {...TYPOGRAPHY_PROPS}>{description}</Typography>
17
+ </Stack>
18
+ );
19
+ };
@@ -0,0 +1,6 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export interface TitleProps {
4
+ description?: ReactNode;
5
+ title: ReactNode;
6
+ }
@@ -0,0 +1,26 @@
1
+ import { TooltipProps } from "@mui/material";
2
+ import { TEXT_BODY_SMALL_400_2_LINES } from "../../../../theme/common/typography";
3
+
4
+ export const TOOLTIP_PROPS: Omit<TooltipProps, "children" | "title"> = {
5
+ arrow: true,
6
+ disableInteractive: true,
7
+ enterNextDelay: 250,
8
+ slotProps: {
9
+ arrow: { sx: (theme) => ({ color: theme.palette.common.white }) },
10
+ popper: {
11
+ modifiers: [
12
+ { name: "offset", options: { offset: [0, 2] } },
13
+ { name: "preventOverflow", options: { padding: 8 } },
14
+ ],
15
+ },
16
+ tooltip: {
17
+ sx: (theme) => ({
18
+ ...theme.typography[TEXT_BODY_SMALL_400_2_LINES],
19
+ backgroundColor: theme.palette.common.white,
20
+ borderRadius: 2,
21
+ color: theme.palette.ink.main,
22
+ padding: 4,
23
+ }),
24
+ },
25
+ },
26
+ };
@@ -0,0 +1,26 @@
1
+ import { Tooltip as MTooltip } from "@mui/material";
2
+ import React from "react";
3
+ import { BaseComponentProps } from "../../../types";
4
+ import { Title } from "./components/Title/title";
5
+ import { TitleProps } from "./components/Title/types";
6
+ import { TOOLTIP_PROPS } from "./constants";
7
+ import { TooltipProps } from "./types";
8
+
9
+ export const Tooltip = ({
10
+ children,
11
+ className,
12
+ description,
13
+ title,
14
+ ...props
15
+ }: BaseComponentProps & TitleProps & TooltipProps): JSX.Element => {
16
+ return (
17
+ <MTooltip
18
+ {...TOOLTIP_PROPS}
19
+ className={className}
20
+ title={description && <Title description={description} title={title} />}
21
+ {...props}
22
+ >
23
+ <span>{children}</span>
24
+ </MTooltip>
25
+ );
26
+ };
@@ -0,0 +1,10 @@
1
+ import { TooltipProps as MTooltipProps } from "@mui/material";
2
+ import { ReactNode } from "react";
3
+
4
+ // `children` is defined as a `ReactNode` instead of an `Element`
5
+ // because the Data Dictionary `Tooltip` component wraps its child in a `span` element.
6
+ // This ensures that `children` can hold a ref properly (see https://mui.com/material-ui/api/tooltip/#props).
7
+
8
+ export type TooltipProps = Omit<MTooltipProps, "children"> & {
9
+ children: ReactNode;
10
+ };
@@ -77,6 +77,7 @@ export const Filter = ({
77
77
  return (
78
78
  <>
79
79
  <FilterLabel
80
+ annotation={categoryView.annotation}
80
81
  count={categoryView.values.length}
81
82
  disabled={categoryView.isDisabled}
82
83
  isOpen={isOpen}
@@ -1,8 +1,11 @@
1
1
  import ArrowDropDownRoundedIcon from "@mui/icons-material/ArrowDropDownRounded";
2
2
  import React, { MouseEvent } from "react";
3
+ import { DataDictionaryAnnotation } from "../../../../common/entities";
4
+ import { Tooltip } from "../../../DataDictionary/components/Tooltip/tooltip";
3
5
  import { FilterLabel as Label } from "./filterLabel.styles";
4
6
 
5
7
  export interface FilterLabelProps {
8
+ annotation?: DataDictionaryAnnotation;
6
9
  count?: number;
7
10
  disabled?: boolean;
8
11
  isOpen: boolean;
@@ -11,6 +14,7 @@ export interface FilterLabelProps {
11
14
  }
12
15
 
13
16
  export const FilterLabel = ({
17
+ annotation,
14
18
  count,
15
19
  disabled = false,
16
20
  isOpen,
@@ -19,15 +23,17 @@ export const FilterLabel = ({
19
23
  }: FilterLabelProps): JSX.Element => {
20
24
  const filterLabel = count ? `${label}\xa0(${count})` : label; // When the count is present, a non-breaking space is used to prevent it from being on its own line
21
25
  return (
22
- <Label
23
- color="inherit"
24
- disabled={disabled}
25
- endIcon={<ArrowDropDownRoundedIcon fontSize="small" />}
26
- fullWidth
27
- isOpen={isOpen}
28
- onClick={onClick}
29
- >
30
- {filterLabel}
31
- </Label>
26
+ <Tooltip description={annotation?.description} title={annotation?.label}>
27
+ <Label
28
+ color="inherit"
29
+ disabled={disabled}
30
+ endIcon={<ArrowDropDownRoundedIcon fontSize="small" />}
31
+ fullWidth
32
+ isOpen={isOpen}
33
+ onClick={onClick}
34
+ >
35
+ {filterLabel}
36
+ </Label>
37
+ </Tooltip>
32
38
  );
33
39
  };
@@ -11,6 +11,7 @@ export function getEntityListTabs(entities: EntityConfig[]): Tab[] {
11
11
  (
12
12
  acc: Tab[],
13
13
  {
14
+ annotation,
14
15
  label,
15
16
  listView: { enableTab = true } = {},
16
17
  route,
@@ -20,6 +21,7 @@ export function getEntityListTabs(entities: EntityConfig[]): Tab[] {
20
21
  ) => {
21
22
  if (enableTab) {
22
23
  acc.push({
24
+ annotation,
23
25
  icon,
24
26
  iconPosition,
25
27
  label,
@@ -7,6 +7,7 @@ import {
7
7
  } from "@mui/material";
8
8
  import { flexRender, RowData } from "@tanstack/react-table";
9
9
  import React, { Fragment } from "react";
10
+ import { Tooltip } from "../../../DataDictionary/components/Tooltip/tooltip";
10
11
  import { ROW_DIRECTION } from "../../common/entities";
11
12
  import {
12
13
  getTableCellAlign,
@@ -28,27 +29,37 @@ export const TableHead = <T extends RowData>({
28
29
  <MTableRow>
29
30
  {headerGroup.headers.map(({ column, getContext, id }) => {
30
31
  const { columnDef, getIsGrouped, getIsSorted } = column;
32
+ const annotation = columnDef.meta?.annotation;
31
33
  return getIsGrouped() ? null : (
32
34
  <TableCell
33
35
  key={id}
34
36
  align={getTableCellAlign(column)}
35
37
  padding={getTableCellPadding(id)}
36
38
  >
37
- {shouldSortColumn(tableInstance, column) ? (
38
- <TableSortLabel
39
- IconComponent={SouthRoundedIcon}
40
- active={Boolean(getIsSorted())}
41
- direction={getIsSorted() || undefined}
42
- disabled={isSortDisabled(tableInstance)}
43
- onClick={(mouseEvent) =>
44
- handleToggleSorting(mouseEvent, tableInstance, column)
45
- }
46
- >
47
- {flexRender(columnDef.header, getContext())}
48
- </TableSortLabel>
49
- ) : (
50
- flexRender(columnDef.header, getContext())
51
- )}
39
+ <Tooltip
40
+ description={annotation?.description}
41
+ title={annotation?.label}
42
+ >
43
+ {shouldSortColumn(tableInstance, column) ? (
44
+ <TableSortLabel
45
+ IconComponent={SouthRoundedIcon}
46
+ active={Boolean(getIsSorted())}
47
+ direction={getIsSorted() || undefined}
48
+ disabled={isSortDisabled(tableInstance)}
49
+ onClick={(mouseEvent) =>
50
+ handleToggleSorting(
51
+ mouseEvent,
52
+ tableInstance,
53
+ column
54
+ )
55
+ }
56
+ >
57
+ {flexRender(columnDef.header, getContext())}
58
+ </TableSortLabel>
59
+ ) : (
60
+ flexRender(columnDef.header, getContext())
61
+ )}
62
+ </Tooltip>
52
63
  </TableCell>
53
64
  );
54
65
  })}
@@ -3,7 +3,9 @@ import {
3
3
  Tabs as MTabs,
4
4
  TabsProps as MTabsProps,
5
5
  } from "@mui/material";
6
- import React, { ReactNode } from "react";
6
+ import React, { ReactElement, ReactNode } from "react";
7
+ import { DataDictionaryAnnotation } from "../../../common/entities";
8
+ import { Tooltip } from "../../DataDictionary/components/Tooltip/tooltip";
7
9
  import { Tab, TabScrollFuzz } from "./tabs.styles";
8
10
 
9
11
  export type TabsValue = MTabsProps["value"]; // any
@@ -11,6 +13,7 @@ export type TabValue = MTabProps["value"]; // any
11
13
  export type OnTabChangeFn = (tabValue: TabValue) => void; // Function invoked when selected tab value changes.
12
14
 
13
15
  export interface Tab {
16
+ annotation?: DataDictionaryAnnotation;
14
17
  count?: string;
15
18
  icon?: MTabProps["icon"]; // element or string
16
19
  iconPosition?: MTabProps["iconPosition"]; // "bottom" or "end" or "start" or "top
@@ -41,14 +44,21 @@ export const Tabs = ({
41
44
  >
42
45
  {tabs.map(
43
46
  (
44
- { count, icon, iconPosition = "start", label, value: tabValue },
47
+ {
48
+ annotation,
49
+ count,
50
+ icon,
51
+ iconPosition = "start",
52
+ label,
53
+ value: tabValue,
54
+ },
45
55
  t
46
56
  ) => (
47
57
  <Tab
48
58
  icon={icon}
49
59
  iconPosition={icon ? iconPosition : undefined}
50
60
  key={`${label}${t}`}
51
- label={count ? `${label} (${count})` : label}
61
+ label={buildTabLabel(label, count, annotation)}
52
62
  value={tabValue}
53
63
  />
54
64
  )
@@ -56,3 +66,23 @@ export const Tabs = ({
56
66
  </MTabs>
57
67
  );
58
68
  };
69
+
70
+ /**
71
+ * Build a tab value from a tab config. Specifically, display the tab label
72
+ * with a tooltip annotation if necessary.
73
+ * @param label - Tab display value.
74
+ * @param count - Optional count to display next to the tab label.
75
+ * @param annotation - Data dictionary annotation.
76
+ * @returns Tab label with optional count and tooltip.
77
+ */
78
+ function buildTabLabel(
79
+ label: ReactNode,
80
+ count?: string,
81
+ annotation?: DataDictionaryAnnotation
82
+ ): ReactElement {
83
+ return (
84
+ <Tooltip description={annotation?.description} title={annotation?.label}>
85
+ <span>{count ? `${label} (${count})` : label}</span>
86
+ </Tooltip>
87
+ );
88
+ }
@@ -10,7 +10,12 @@ import {
10
10
  TableOptions,
11
11
  } from "@tanstack/react-table";
12
12
  import { JSXElementConstructor, ReactNode } from "react";
13
- import { SelectCategoryValueView, SelectedFilter } from "../common/entities";
13
+ import {
14
+ DataDictionary,
15
+ DataDictionaryAnnotation,
16
+ SelectCategoryValueView,
17
+ SelectedFilter,
18
+ } from "../common/entities";
14
19
  import { HeroTitle } from "../components/common/Title/title";
15
20
  import { FooterProps } from "../components/Layout/components/Footer/footer";
16
21
  import { HeaderProps } from "../components/Layout/components/Header/header";
@@ -87,6 +92,7 @@ export interface CategoryGroup {
87
92
  * Model of category configured in site config.
88
93
  */
89
94
  export interface CategoryConfig {
95
+ annotation?: DataDictionaryAnnotation;
90
96
  key: string;
91
97
  label: string;
92
98
  mapSelectCategoryValue?: (
@@ -170,6 +176,7 @@ export type EntityPath = string;
170
176
  */
171
177
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This config model is part of a generic array
172
178
  export interface EntityConfig<T = any, I = any> extends TabConfig {
179
+ annotation?: DataDictionaryAnnotation;
173
180
  apiPath?: EntityPath;
174
181
  categoryGroupConfig?: CategoryGroupConfig;
175
182
  detail: BackPageConfig;
@@ -180,6 +187,7 @@ export interface EntityConfig<T = any, I = any> extends TabConfig {
180
187
  getId?: GetIdFunction<T>;
181
188
  getTitle?: GetTitleFunction<T>;
182
189
  hideTabs?: boolean;
190
+ key?: string; // Optional data dictionary key
183
191
  list: ListConfig<T>;
184
192
  listView?: ListViewConfig;
185
193
  options?: Options;
@@ -366,6 +374,7 @@ export interface SiteConfig {
366
374
  categoryGroupConfig?: CategoryGroupConfig;
367
375
  contentDir?: string;
368
376
  contentThemeOptionsFn?: ThemeOptionsFn;
377
+ dataDictionary?: DataDictionary;
369
378
  dataSource: DataSourceConfig;
370
379
  entities: EntityConfig[];
371
380
  explorerTitle: HeroTitle;
@@ -77,6 +77,7 @@ function buildCategoryView(
77
77
  const mapSelectCategoryValue =
78
78
  categoryConfig?.mapSelectCategoryValue || getSelectCategoryValue;
79
79
  return {
80
+ annotation: categoryConfig?.annotation,
80
81
  isDisabled: false,
81
82
  key: category.key,
82
83
  label: getCategoryLabel(category.key, categoryConfig),
@@ -1,4 +1,5 @@
1
- import React, { createContext, ReactNode } from "react";
1
+ import React, { createContext, ReactNode, useState } from "react";
2
+ import { annotateSiteConfig } from "../components/DataDictionary/common/utils";
2
3
  import { EntityConfig, SiteConfig } from "../config/entities";
3
4
  import {
4
5
  getDefaultConfig,
@@ -31,7 +32,14 @@ export function ConfigProvider({
31
32
  config,
32
33
  entityListType = "",
33
34
  }: ConfigProps): JSX.Element {
34
- const { entities } = config;
35
+ // Annote config on init. Note config is mutated but using state here to
36
+ // ensure annotated config is calculated once and is used rather than the raw config.
37
+ const [annotatedConfig] = useState(() => {
38
+ annotateSiteConfig(config);
39
+ return config;
40
+ });
41
+
42
+ const { entities } = annotatedConfig;
35
43
  const defaultEntityListType = config.redirectRootToPath.slice(1);
36
44
  const entityName = entityListType || defaultEntityListType;
37
45
  const entityConfig = getEntityConfig(entities, entityName);