Skip to content

Commit bc3b054

Browse files
committed
Add grouped, navigable topic Configuration tab behind enableNewTopicPage
Behind the existing new-topic-page flag, render topic configs grouped by category with a sidebar that filters to a category, an All/Modified scope toggle (URL-backed) with a clear-able search, click-to-reveal descriptions, and reset moved into the edit dialog. Show a modified-count badge on the Configuration tab title and per category. Default enum/boolean editors to their first option when a config has no value.
1 parent 50ec785 commit bc3b054

4 files changed

Lines changed: 478 additions & 75 deletions

File tree

Lines changed: 111 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render, screen } from '@testing-library/react';
1+
import { render, screen, within } from '@testing-library/react';
22
import { vi } from 'vitest';
33

44
import ConfigurationEditor from './topic-configuration';
@@ -12,62 +12,118 @@ vi.mock('@tanstack/react-router', async (importOriginal) => {
1212
};
1313
});
1414

15+
const mockIsFeatureFlagEnabled = vi.fn<(flag: string) => boolean>();
16+
vi.mock('../../../config', async (importOriginal) => {
17+
const actual = await importOriginal<typeof import('../../../config')>();
18+
return {
19+
...actual,
20+
isServerless: () => false,
21+
isFeatureFlagEnabled: (flag: string) => mockIsFeatureFlagEnabled(flag),
22+
};
23+
});
24+
1525
import type { ConfigEntryExtended } from '../../../state/rest-interfaces';
1626

27+
const makeEntry = (overrides: Partial<ConfigEntryExtended> & { category: string }): ConfigEntryExtended => ({
28+
name: 'test.option',
29+
value: '',
30+
source: '',
31+
type: 'STRING',
32+
isExplicitlySet: false,
33+
isDefaultValue: false,
34+
isReadOnly: false,
35+
isSensitive: false,
36+
synonyms: [],
37+
currentValue: '',
38+
...overrides,
39+
});
40+
1741
describe('TopicConfiguration', () => {
18-
test('renders groups in the correct order', () => {
19-
// Generate an out of order set of test options
20-
const entries: ConfigEntryExtended[] = [
21-
'Retention',
22-
'Tiered Storage',
23-
'Storage Internals',
24-
'Compression',
25-
'Compaction',
26-
'Replication',
27-
'Iceberg',
28-
'', // unknown options should appear at the end as 'Other'
29-
'Message Handling',
30-
'Write Caching',
31-
'Schema Registry and Validation',
32-
].map((category) => ({
33-
name: 'test.option',
34-
category,
35-
value: '',
36-
source: '',
37-
type: 'STRING',
38-
isExplicitlySet: false,
39-
isDefaultValue: false,
40-
isReadOnly: false,
41-
isSensitive: false,
42-
synonyms: [],
43-
currentValue: '',
44-
}));
45-
46-
const { container } = render(
47-
<ConfigurationEditor
48-
entries={entries}
49-
onForceRefresh={() => {
50-
// no op - test callback
51-
}}
52-
targetTopic=""
53-
/>
54-
);
55-
expect(screen.getByTestId('config-group-table')).toBeVisible();
56-
57-
const groups = container.querySelectorAll('.configGroupTitle');
58-
59-
expect(Array.from(groups).map((g) => g.textContent)).toEqual([
60-
'Retention',
61-
'Compaction',
62-
'Replication',
63-
'Tiered Storage',
64-
'Write Caching',
65-
'Iceberg',
66-
'Schema Registry and Validation',
67-
'Message Handling',
68-
'Compression',
69-
'Storage Internals',
70-
'Other',
71-
]);
42+
describe('legacy layout (enableNewTopicPage off)', () => {
43+
beforeEach(() => mockIsFeatureFlagEnabled.mockReturnValue(false));
44+
45+
test('renders groups in the correct order', () => {
46+
// Generate an out of order set of test options
47+
const entries: ConfigEntryExtended[] = [
48+
'Retention',
49+
'Tiered Storage',
50+
'Storage Internals',
51+
'Compression',
52+
'Compaction',
53+
'Replication',
54+
'Iceberg',
55+
'', // unknown options should appear at the end as 'Other'
56+
'Message Handling',
57+
'Write Caching',
58+
'Schema Registry and Validation',
59+
].map((category) => makeEntry({ category }));
60+
61+
const { container } = render(
62+
<ConfigurationEditor
63+
entries={entries}
64+
onForceRefresh={() => {
65+
// no op - test callback
66+
}}
67+
targetTopic=""
68+
/>
69+
);
70+
expect(screen.getByTestId('config-group-table')).toBeVisible();
71+
72+
const groups = container.querySelectorAll('.configGroupTitle');
73+
74+
expect(Array.from(groups).map((g) => g.textContent)).toEqual([
75+
'Retention',
76+
'Compaction',
77+
'Replication',
78+
'Tiered Storage',
79+
'Write Caching',
80+
'Iceberg',
81+
'Schema Registry and Validation',
82+
'Message Handling',
83+
'Compression',
84+
'Storage Internals',
85+
'Other',
86+
]);
87+
});
88+
});
89+
90+
describe('grouped layout (enableNewTopicPage on)', () => {
91+
beforeEach(() => mockIsFeatureFlagEnabled.mockReturnValue(true));
92+
93+
test('renders a sidebar and titled sections, collapsing unmapped categories into Other', () => {
94+
const entries: ConfigEntryExtended[] = [
95+
makeEntry({ name: 'retention.ms', category: 'Retention', isExplicitlySet: true }),
96+
makeEntry({ name: 'cleanup.policy', category: 'Compaction' }),
97+
makeEntry({ name: 'redpanda.iceberg.mode', category: 'Iceberg' }),
98+
];
99+
100+
render(
101+
<ConfigurationEditor
102+
entries={entries}
103+
onForceRefresh={() => {
104+
// no op - test callback
105+
}}
106+
targetTopic="my-topic"
107+
/>
108+
);
109+
110+
// Sidebar lists each visible category; unmapped 'Iceberg' collapses into 'Other'.
111+
const nav = screen.getByRole('navigation', { name: 'Configuration categories' });
112+
expect(within(nav).getByText('Retention')).toBeVisible();
113+
expect(within(nav).getByText('Compaction')).toBeVisible();
114+
expect(within(nav).getByText('Other')).toBeVisible();
115+
expect(within(nav).queryByText('Iceberg')).not.toBeInTheDocument();
116+
117+
// Each visible category renders as a titled section.
118+
const retentionSection = screen.getByRole('heading', { name: 'Retention' }).closest('section') as HTMLElement;
119+
expect(retentionSection).toBeVisible();
120+
121+
// Only modified rows get a badge; the explicitly-set retention.ms row shows 'Modified',
122+
// and the default cleanup.policy row shows no badge.
123+
expect(within(retentionSection).getByText('Modified')).toBeVisible();
124+
const compactionSection = screen.getByRole('heading', { name: 'Compaction' }).closest('section') as HTMLElement;
125+
expect(within(compactionSection).queryByText('Modified')).not.toBeInTheDocument();
126+
expect(within(compactionSection).queryByText('Default')).not.toBeInTheDocument();
127+
});
72128
});
73129
});

0 commit comments

Comments
 (0)