Skip to content

Commit 4cdf198

Browse files
Add new metadata table to Domain Page Metadata tab (#872)
Add ListTableNested component which adds support for descriptions and key-value groups Remove input params from Extended Domain Info Enabled config resolver Add useConfigValue query to useSuspense queries to also fetch extended domain info config Add Metadata Table Extended config that uses the new ListTableNested component (depending on config value)
1 parent f1167dd commit 4cdf198

15 files changed

+520
-83
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React from 'react';
2+
3+
import { render, screen, within } from '@/test-utils/rtl';
4+
5+
import ListTableNested from '../list-table-nested';
6+
import { type ListTableNestedItem } from '../list-table-nested.types';
7+
8+
const mockListTableNestedItemsConfig: Array<ListTableNestedItem> = [
9+
{
10+
key: 'key1',
11+
label: 'Key 1',
12+
description: 'Description for Key 1',
13+
kind: 'simple',
14+
value: 'mock-value',
15+
},
16+
{
17+
key: 'key2',
18+
label: 'Key 2',
19+
description: 'Description for Key 2',
20+
kind: 'simple',
21+
value: (
22+
<div>
23+
{['mock-value-c1', 'mock-value-c2'].map((val) => (
24+
<div key={val} data-testid={val} />
25+
))}
26+
</div>
27+
),
28+
},
29+
{
30+
key: 'key3',
31+
label: 'Key 3',
32+
description: 'Description for Key 3',
33+
kind: 'group',
34+
items: [
35+
{
36+
key: 'subKey1',
37+
label: 'Sub Key 1',
38+
value: 'mock-value-1',
39+
},
40+
{
41+
key: 'subKey2',
42+
label: 'Sub Key 2',
43+
value: 'mock-value-2',
44+
},
45+
],
46+
},
47+
];
48+
49+
describe(ListTableNested.name, () => {
50+
it('renders simple items with description correctly', () => {
51+
render(<ListTableNested items={[mockListTableNestedItemsConfig[0]]} />);
52+
53+
const key1Row = screen.getByText('Key 1').parentElement?.parentElement;
54+
if (!key1Row) throw new Error('Key 1 row not found');
55+
56+
expect(
57+
within(key1Row).getByText('Description for Key 1')
58+
).toBeInTheDocument();
59+
expect(within(key1Row).getByText('mock-value')).toBeInTheDocument();
60+
});
61+
62+
it('renders simple items with complex values correctly', () => {
63+
render(<ListTableNested items={[mockListTableNestedItemsConfig[1]]} />);
64+
65+
const key2Row = screen.getByText('Key 2').parentElement?.parentElement;
66+
if (!key2Row) throw new Error('Key 2 row not found');
67+
68+
expect(
69+
within(key2Row).getByText('Description for Key 2')
70+
).toBeInTheDocument();
71+
expect(within(key2Row).getByTestId('mock-value-c1')).toBeInTheDocument();
72+
expect(within(key2Row).getByTestId('mock-value-c2')).toBeInTheDocument();
73+
});
74+
75+
it('renders group items with sub-items correctly', () => {
76+
render(<ListTableNested items={[mockListTableNestedItemsConfig[2]]} />);
77+
78+
const key3Row = screen.getByText('Key 3').parentElement?.parentElement;
79+
if (!key3Row) throw new Error('Key 3 row not found');
80+
expect(
81+
within(key3Row).getByText('Description for Key 3')
82+
).toBeInTheDocument();
83+
84+
const sublist = screen.getByText('Sub Key 1:').parentElement?.parentElement;
85+
if (!sublist) throw new Error('Sublist container not found');
86+
87+
const sublistItems = within(sublist)
88+
.getAllByText(/Sub Key/)
89+
.map((label) => label.parentElement);
90+
expect(sublistItems).toHaveLength(2);
91+
92+
const firstSubItem = sublistItems[0];
93+
if (!firstSubItem) throw new Error('First sub-item not found');
94+
expect(within(firstSubItem).getByText('Sub Key 1:')).toBeInTheDocument();
95+
expect(within(firstSubItem).getByText('mock-value-1')).toBeInTheDocument();
96+
97+
const secondSubItem = sublistItems[1];
98+
if (!secondSubItem) throw new Error('Second sub-item not found');
99+
expect(within(secondSubItem).getByText('Sub Key 2:')).toBeInTheDocument();
100+
expect(within(secondSubItem).getByText('mock-value-2')).toBeInTheDocument();
101+
});
102+
});
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { type Theme, styled as createStyled } from 'baseui';
2+
import { type StyleObject } from 'styletron-react';
3+
4+
export const styled = {
5+
Table: createStyled('div', {
6+
display: 'flex',
7+
flexDirection: 'column',
8+
maxWidth: '1050px',
9+
}),
10+
TableRow: createStyled(
11+
'div',
12+
({ $theme }: { $theme: Theme }): StyleObject => ({
13+
':not(:last-child)': {
14+
borderBottom: `1px solid ${$theme.colors.borderOpaque}`,
15+
},
16+
display: 'flex',
17+
[$theme.mediaQuery.medium]: {
18+
flexDirection: 'row',
19+
gap: $theme.sizing.scale1200,
20+
},
21+
flexDirection: 'column',
22+
gap: $theme.sizing.scale500,
23+
paddingTop: $theme.sizing.scale550,
24+
paddingBottom: $theme.sizing.scale550,
25+
})
26+
),
27+
TitleBlock: createStyled(
28+
'div',
29+
({ $theme }: { $theme: Theme }): StyleObject => ({
30+
[$theme.mediaQuery.medium]: {
31+
minWidth: '240px',
32+
maxWidth: '240px',
33+
},
34+
display: 'flex',
35+
flexDirection: 'column',
36+
gap: $theme.sizing.scale100,
37+
})
38+
),
39+
Title: createStyled(
40+
'div',
41+
({ $theme }: { $theme: Theme }): StyleObject => ({
42+
...$theme.typography.LabelSmall,
43+
fontWeight: '700',
44+
})
45+
),
46+
Description: createStyled(
47+
'div',
48+
({ $theme }: { $theme: Theme }): StyleObject => ({
49+
...$theme.typography.ParagraphXSmall,
50+
lineHeight: '16px',
51+
color: $theme.colors.contentTertiary,
52+
})
53+
),
54+
ContentContainer: createStyled(
55+
'div',
56+
({ $theme }: { $theme: Theme }): StyleObject => ({
57+
...$theme.typography.ParagraphSmall,
58+
})
59+
),
60+
Sublist: createStyled(
61+
'div',
62+
({ $theme }: { $theme: Theme }): StyleObject => ({
63+
display: 'flex',
64+
flexDirection: 'column',
65+
alignItems: 'stretch',
66+
flexGrow: 1,
67+
rowGap: $theme.sizing.scale400,
68+
[$theme.mediaQuery.medium]: {
69+
rowGap: 0,
70+
},
71+
})
72+
),
73+
SublistItem: createStyled(
74+
'div',
75+
({ $theme }: { $theme: Theme }): StyleObject => ({
76+
display: 'flex',
77+
[$theme.mediaQuery.medium]: {
78+
flexDirection: 'row',
79+
justifyContent: 'space-between',
80+
alignItems: 'center',
81+
':not(:last-child)': {
82+
borderBottom: `1px solid ${$theme.colors.borderOpaque}`,
83+
},
84+
},
85+
flexDirection: 'column',
86+
alignItems: 'start',
87+
gap: $theme.sizing.scale200,
88+
paddingTop: $theme.sizing.scale100,
89+
paddingBottom: $theme.sizing.scale100,
90+
})
91+
),
92+
SublistItemLabel: createStyled(
93+
'div',
94+
({ $theme }: { $theme: Theme }): StyleObject => ({
95+
...$theme.typography.LabelSmall,
96+
[$theme.mediaQuery.medium]: {
97+
minWidth: '120px',
98+
},
99+
})
100+
),
101+
SublistItemValue: createStyled(
102+
'div',
103+
({ $theme }: { $theme: Theme }): StyleObject => ({
104+
...$theme.typography.ParagraphSmall,
105+
})
106+
),
107+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
3+
import { styled } from './list-table-nested.styles';
4+
import type { Props } from './list-table-nested.types';
5+
6+
/**
7+
* Renders a responsive table for displaying items as label-value pairs, or groups of label-value pairs.
8+
* Suitable for presenting grouped key-value data.
9+
*/
10+
export default function ListTableNested({ items }: Props) {
11+
return (
12+
<styled.Table>
13+
{items.map((item) => (
14+
<styled.TableRow key={item.key}>
15+
<styled.TitleBlock>
16+
<styled.Title>{item.label}</styled.Title>
17+
{item.description && (
18+
<styled.Description>{item.description}</styled.Description>
19+
)}
20+
</styled.TitleBlock>
21+
{item.kind === 'group' ? (
22+
<styled.Sublist>
23+
{item.items.map((sublistItem) => (
24+
<styled.SublistItem key={sublistItem.key}>
25+
<styled.SublistItemLabel>
26+
{sublistItem.label}:
27+
</styled.SublistItemLabel>
28+
<styled.SublistItemValue>
29+
{sublistItem.value}
30+
</styled.SublistItemValue>
31+
</styled.SublistItem>
32+
))}
33+
</styled.Sublist>
34+
) : (
35+
<styled.ContentContainer>{item.value}</styled.ContentContainer>
36+
)}
37+
</styled.TableRow>
38+
))}
39+
</styled.Table>
40+
);
41+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export type ListTableNestedSublistItem = {
2+
key: string;
3+
label: string;
4+
value: React.ReactNode;
5+
};
6+
7+
interface ListTableNestedField {
8+
key: string;
9+
label: string;
10+
description?: string;
11+
kind: 'simple' | 'group';
12+
}
13+
14+
export interface ListTableNestedSimpleItem extends ListTableNestedField {
15+
kind: 'simple';
16+
value: React.ReactNode;
17+
}
18+
19+
export interface ListTableNestedGroup extends ListTableNestedField {
20+
kind: 'group';
21+
items: Array<ListTableNestedSublistItem>;
22+
}
23+
24+
export type ListTableNestedItem =
25+
| ListTableNestedSimpleItem
26+
| ListTableNestedGroup;
27+
28+
export type Props = {
29+
items: Array<ListTableNestedItem>;
30+
};

src/config/dynamic/dynamic.config.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import clustersPublic from './resolvers/clusters-public';
1111
import { type PublicClustersConfigs } from './resolvers/clusters-public.types';
1212
import { type ClustersConfigs } from './resolvers/clusters.types';
1313
import extendedDomainInfoEnabled from './resolvers/extended-domain-info-enabled';
14-
import {
15-
type ExtendedDomainInfoEnabledConfig,
16-
type ExtendedDomainInfoEnabledResolverParams,
17-
} from './resolvers/extended-domain-info-enabled.types';
14+
import { type ExtendedDomainInfoEnabledConfig } from './resolvers/extended-domain-info-enabled.types';
1815
import workflowActionsEnabled from './resolvers/workflow-actions-enabled';
1916
import {
2017
type WorkflowActionsEnabledResolverParams,
@@ -42,7 +39,7 @@ const dynamicConfigs: {
4239
true
4340
>;
4441
EXTENDED_DOMAIN_INFO_ENABLED: ConfigAsyncResolverDefinition<
45-
ExtendedDomainInfoEnabledResolverParams,
42+
undefined,
4643
ExtendedDomainInfoEnabledConfig,
4744
'request',
4845
true
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import {
2-
type ExtendedDomainInfoEnabledConfig,
3-
type ExtendedDomainInfoEnabledResolverParams,
4-
} from './extended-domain-info-enabled.types';
1+
import { type ExtendedDomainInfoEnabledConfig } from './extended-domain-info-enabled.types';
52

6-
export default async function extendedDomainInfoEnabled(
7-
_: ExtendedDomainInfoEnabledResolverParams
8-
): Promise<ExtendedDomainInfoEnabledConfig> {
3+
export default async function extendedDomainInfoEnabled(): Promise<ExtendedDomainInfoEnabledConfig> {
94
return {
10-
metadata: false,
5+
metadata: true,
116
issues: false,
127
};
138
}

src/config/dynamic/resolvers/extended-domain-info-enabled.types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
export type ExtendedDomainInfoEnabledResolverParams = Record<string, any>;
2-
31
export type ExtendedDomainInfoEnabledConfig = {
42
metadata: boolean;
53
issues: boolean;

src/config/dynamic/resolvers/schemas/resolver-schemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const resolverSchemas: ResolverSchemas = {
4343
}),
4444
},
4545
EXTENDED_DOMAIN_INFO_ENABLED: {
46-
args: z.record(z.string(), z.any()),
46+
args: z.undefined(),
4747
returnType: z.object({
4848
metadata: z.boolean(),
4949
issues: z.boolean(),

0 commit comments

Comments
 (0)