Skip to content

Commit d075320

Browse files
Add failover version table for active-active domains
Signed-off-by: Adhitya Mamallan <[email protected]>
1 parent 5f52059 commit d075320

9 files changed

+292
-45
lines changed

src/views/domain-page/config/domain-page-metadata-extended-table.config.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { createElement } from 'react';
22

3+
import isActiveActiveDomain from '@/views/shared/active-active/helpers/is-active-active-domain';
4+
35
import DomainPageMetadataClusters from '../domain-page-metadata-clusters/domain-page-metadata-clusters';
46
import DomainPageMetadataDescription from '../domain-page-metadata-description/domain-page-metadata-description';
5-
import DomainPageMetadataFailoverVersion from '../domain-page-metadata-failover-version/domain-page-metadata-failover-version';
7+
import DomainPageMetadataFailoverVersionActiveActive from '../domain-page-metadata-failover-version-active-active/domain-page-metadata-failover-version-active-active';
68
import { type MetadataItem } from '../domain-page-metadata-table/domain-page-metadata-table.types';
79
import DomainPageMetadataViewJson from '../domain-page-metadata-view-json/domain-page-metadata-view-json';
810
import getClusterOperationMode from '../helpers/get-cluster-operation-mode';
@@ -55,7 +57,12 @@ const domainPageMetadataExtendedTableConfig = [
5557
description: 'The failover version of the domain',
5658
kind: 'simple',
5759
getValue: ({ domainDescription }) =>
58-
createElement(DomainPageMetadataFailoverVersion, domainDescription),
60+
isActiveActiveDomain(domainDescription)
61+
? createElement(
62+
DomainPageMetadataFailoverVersionActiveActive,
63+
domainDescription
64+
)
65+
: domainDescription.failoverVersion,
5966
},
6067
{
6168
key: 'describeDomainJson',

src/views/domain-page/config/domain-page-metadata-table.config.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { createElement } from 'react';
2+
13
import type { ListTableItem } from '@/components/list-table/list-table.types';
4+
import isActiveActiveDomain from '@/views/shared/active-active/helpers/is-active-active-domain';
25

36
import DomainPageMetadataClusters from '../domain-page-metadata-clusters/domain-page-metadata-clusters';
4-
import DomainPageMetadataFailoverVersion from '../domain-page-metadata-failover-version/domain-page-metadata-failover-version';
7+
import DomainPageMetadataFailoverVersionActiveActive from '../domain-page-metadata-failover-version-active-active/domain-page-metadata-failover-version-active-active';
58
import { type DomainDescription } from '../domain-page.types';
69
import getClusterOperationMode from '../helpers/get-cluster-operation-mode';
710

@@ -31,7 +34,13 @@ const domainPageMetadataTableConfig: Array<ListTableItem<DomainDescription>> = [
3134
{
3235
key: 'failoverVersion',
3336
label: 'Failover version',
34-
renderValue: DomainPageMetadataFailoverVersion,
37+
renderValue: (domainDescription: DomainDescription) =>
38+
isActiveActiveDomain(domainDescription)
39+
? createElement(
40+
DomainPageMetadataFailoverVersionActiveActive,
41+
domainDescription
42+
)
43+
: domainDescription.failoverVersion,
3544
},
3645
];
3746

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { render, screen, userEvent } from '@/test-utils/rtl';
2+
3+
import { mockActiveActiveDomain } from '@/views/shared/active-active/__fixtures__/active-active-domain';
4+
import { type ActiveActiveDomain } from '@/views/shared/active-active/active-active.types';
5+
6+
import DomainPageMetadataFailoverVersionActiveActive from '../domain-page-metadata-failover-version-active-active';
7+
8+
describe(DomainPageMetadataFailoverVersionActiveActive.name, () => {
9+
it('renders the table with headers', () => {
10+
setup();
11+
12+
expect(screen.getByText('Scope')).toBeInTheDocument();
13+
expect(screen.getByText('Attribute')).toBeInTheDocument();
14+
expect(screen.getByText('Active Cluster')).toBeInTheDocument();
15+
expect(screen.getByText('Failover Version')).toBeInTheDocument();
16+
});
17+
18+
it('renders default entry with domain data', () => {
19+
setup();
20+
21+
expect(screen.getByText('default')).toBeInTheDocument();
22+
expect(screen.getByText('-')).toBeInTheDocument();
23+
expect(screen.getByText('cluster0')).toBeInTheDocument();
24+
expect(screen.getByText('2')).toBeInTheDocument();
25+
});
26+
27+
it('renders multiple failover version entries', () => {
28+
setup({ attributeCount: 2 });
29+
30+
// Default entry
31+
expect(screen.getByText('default')).toBeInTheDocument();
32+
expect(screen.getByText('cluster0')).toBeInTheDocument();
33+
expect(screen.getByText('2')).toBeInTheDocument();
34+
35+
// Additional entries
36+
expect(screen.getByText('scope1')).toBeInTheDocument();
37+
expect(screen.getByText('attribute1')).toBeInTheDocument();
38+
expect(screen.getByText('cluster_1')).toBeInTheDocument();
39+
expect(screen.getByText('1000')).toBeInTheDocument();
40+
41+
expect(screen.getByText('scope2')).toBeInTheDocument();
42+
expect(screen.getByText('attribute2')).toBeInTheDocument();
43+
expect(screen.getByText('cluster_2')).toBeInTheDocument();
44+
expect(screen.getByText('1001')).toBeInTheDocument();
45+
});
46+
47+
it('does not show "Show all" button when entries are less than or equal to MAX_ATTRIBUTES_COUNT_TRUNCATED', () => {
48+
// 3 additional attributes + 1 default = 4 total (exactly at the limit)
49+
setup({ attributeCount: 3 });
50+
51+
expect(screen.queryByText(/Show all/)).not.toBeInTheDocument();
52+
});
53+
54+
it('shows "Show all" button when entries exceed MAX_ATTRIBUTES_COUNT_TRUNCATED', () => {
55+
// 5 additional attributes + 1 default = 6 total (exceeds limit of 4)
56+
setup({ attributeCount: 5 });
57+
58+
expect(screen.getByText('Show all (6)')).toBeInTheDocument();
59+
});
60+
61+
it('truncates entries when not expanded', () => {
62+
setup({ attributeCount: 5 });
63+
64+
// Should show first 4 entries only (default + first 3 attributes)
65+
expect(screen.getByText('default')).toBeInTheDocument();
66+
expect(screen.getByText('scope1')).toBeInTheDocument();
67+
expect(screen.getByText('scope2')).toBeInTheDocument();
68+
expect(screen.getByText('scope3')).toBeInTheDocument();
69+
70+
// Should not show 5th and 6th entries
71+
expect(screen.queryByText('scope4')).not.toBeInTheDocument();
72+
expect(screen.queryByText('scope5')).not.toBeInTheDocument();
73+
});
74+
75+
it('expands to show all entries when "Show all" button is clicked', async () => {
76+
const { user } = setup({ attributeCount: 5 });
77+
78+
const showAllButton = screen.getByText('Show all (6)');
79+
await user.click(showAllButton);
80+
81+
// Now all entries should be visible
82+
expect(screen.getByText('default')).toBeInTheDocument();
83+
expect(screen.getByText('scope1')).toBeInTheDocument();
84+
expect(screen.getByText('scope2')).toBeInTheDocument();
85+
expect(screen.getByText('scope3')).toBeInTheDocument();
86+
expect(screen.getByText('scope4')).toBeInTheDocument();
87+
expect(screen.getByText('scope5')).toBeInTheDocument();
88+
89+
// Button text should change
90+
expect(screen.getByText('Show less')).toBeInTheDocument();
91+
expect(screen.queryByText('Show all (6)')).not.toBeInTheDocument();
92+
});
93+
94+
it('collapses to show truncated entries when "Show less" button is clicked', async () => {
95+
const { user } = setup({ attributeCount: 5 });
96+
97+
// Expand first
98+
const showAllButton = screen.getByText('Show all (6)');
99+
await user.click(showAllButton);
100+
101+
// Then collapse
102+
const showLessButton = screen.getByText('Show less');
103+
await user.click(showLessButton);
104+
105+
// Should show first 4 entries only
106+
expect(screen.getByText('default')).toBeInTheDocument();
107+
expect(screen.getByText('scope1')).toBeInTheDocument();
108+
expect(screen.getByText('scope2')).toBeInTheDocument();
109+
expect(screen.getByText('scope3')).toBeInTheDocument();
110+
111+
// Should not show 5th and 6th entries
112+
expect(screen.queryByText('scope4')).not.toBeInTheDocument();
113+
expect(screen.queryByText('scope5')).not.toBeInTheDocument();
114+
115+
// Button text should change back
116+
expect(screen.getByText('Show all (6)')).toBeInTheDocument();
117+
expect(screen.queryByText('Show less')).not.toBeInTheDocument();
118+
});
119+
});
120+
121+
function setup({
122+
attributeCount = 0,
123+
}: {
124+
attributeCount?: number;
125+
} = {}) {
126+
const user = userEvent.setup();
127+
128+
const activeClustersByClusterAttribute: Record<string, any> = {};
129+
130+
// Create additional attributes beyond the default one
131+
for (let i = 0; i < attributeCount; i++) {
132+
const scope = `scope${i + 1}`;
133+
activeClustersByClusterAttribute[scope] = {
134+
clusterAttributes: {
135+
[`attribute${i + 1}`]: {
136+
activeClusterName: `cluster_${i + 1}`,
137+
failoverVersion: `${1000 + i}`,
138+
},
139+
},
140+
};
141+
}
142+
143+
const domain: ActiveActiveDomain = {
144+
...mockActiveActiveDomain,
145+
activeClusters: {
146+
regionToCluster: {},
147+
activeClustersByClusterAttribute,
148+
},
149+
};
150+
151+
render(<DomainPageMetadataFailoverVersionActiveActive {...domain} />);
152+
153+
return { user };
154+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const MAX_ATTRIBUTES_COUNT_TRUNCATED = 4;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { styled as createStyled, type Theme } from 'baseui';
2+
import { type TableOverrides } from 'baseui/table-semantic';
3+
import { type StyleObject } from 'styletron-react';
4+
5+
export const overrides = {
6+
table: {
7+
TableHeadCell: {
8+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
9+
...$theme.typography.LabelXSmall,
10+
paddingTop: $theme.sizing.scale300,
11+
paddingBottom: $theme.sizing.scale300,
12+
paddingLeft: $theme.sizing.scale500,
13+
paddingRight: $theme.sizing.scale500,
14+
}),
15+
},
16+
TableBodyCell: {
17+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
18+
...$theme.typography.ParagraphXSmall,
19+
paddingTop: $theme.sizing.scale300,
20+
paddingBottom: $theme.sizing.scale300,
21+
paddingLeft: $theme.sizing.scale500,
22+
paddingRight: $theme.sizing.scale500,
23+
}),
24+
},
25+
} satisfies TableOverrides,
26+
};
27+
28+
export const styled = {
29+
FailoverVersionsContainer: createStyled('div', ({ $theme }) => ({
30+
display: 'flex',
31+
flexDirection: 'column',
32+
alignItems: 'flex-start',
33+
gap: $theme.sizing.scale500,
34+
})),
35+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useMemo, useState } from 'react';
2+
3+
import { Button } from 'baseui/button';
4+
import { Table } from 'baseui/table-semantic';
5+
import { MdAdd, MdHorizontalRule } from 'react-icons/md';
6+
7+
import { type ActiveActiveDomain } from '@/views/shared/active-active/active-active.types';
8+
9+
import { MAX_ATTRIBUTES_COUNT_TRUNCATED } from './domain-page-metadata-failover-version-active-active.constants';
10+
import {
11+
overrides,
12+
styled,
13+
} from './domain-page-metadata-failover-version-active-active.styles';
14+
import { type FailoverVersionEntryActiveActive } from './domain-page-metadata-failover-version-active-active.types';
15+
16+
export default function DomainPageMetadataFailoverVersionActiveActive(
17+
domain: ActiveActiveDomain
18+
) {
19+
const [showAll, setShowAll] = useState(false);
20+
21+
const allEntries = useMemo<Array<FailoverVersionEntryActiveActive>>(() => {
22+
return [
23+
{
24+
scope: 'default',
25+
attribute: '-',
26+
cluster: domain.activeClusterName,
27+
version: domain.failoverVersion,
28+
},
29+
...Object.entries(
30+
domain.activeClusters.activeClustersByClusterAttribute
31+
).flatMap(([scope, { clusterAttributes }]) =>
32+
Object.entries(clusterAttributes).map(
33+
([attribute, activeClusterInfo]) => ({
34+
scope,
35+
attribute,
36+
cluster: activeClusterInfo.activeClusterName,
37+
version: activeClusterInfo.failoverVersion,
38+
})
39+
)
40+
),
41+
];
42+
}, [
43+
domain.activeClusters.activeClustersByClusterAttribute,
44+
domain.activeClusterName,
45+
domain.failoverVersion,
46+
]);
47+
48+
const entriesToShow = useMemo(() => {
49+
return showAll
50+
? allEntries
51+
: allEntries.slice(0, MAX_ATTRIBUTES_COUNT_TRUNCATED);
52+
}, [allEntries, showAll]);
53+
54+
return (
55+
<styled.FailoverVersionsContainer>
56+
<Table
57+
size="compact"
58+
divider="clean"
59+
overrides={overrides.table}
60+
columns={['Scope', 'Attribute', 'Active Cluster', 'Failover Version']}
61+
data={entriesToShow.map(Object.values)}
62+
/>
63+
{allEntries.length > MAX_ATTRIBUTES_COUNT_TRUNCATED && (
64+
<Button
65+
kind="secondary"
66+
size="mini"
67+
shape="pill"
68+
startEnhancer={showAll ? <MdHorizontalRule /> : <MdAdd />}
69+
onClick={() => setShowAll((v) => !v)}
70+
>
71+
{showAll ? 'Show less' : `Show all (${allEntries.length})`}
72+
</Button>
73+
)}
74+
</styled.FailoverVersionsContainer>
75+
);
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type FailoverVersionEntryActiveActive = {
2+
scope: string;
3+
attribute: string;
4+
cluster: string;
5+
version: string;
6+
};

src/views/domain-page/domain-page-metadata-failover-version/__tests__/domain-page-metadata-failover-version.test.tsx

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/views/domain-page/domain-page-metadata-failover-version/domain-page-metadata-failover-version.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)