Skip to content
Draft
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "cadence-web",
"private": true,
"config": {
"cadence_idl_version": "0e56e57909d9fa738eaa8d7a9561ea16acdf51e4"
"cadence_idl_version": "792058c9dbad5787cf8444c5d4bef0763a311b0c"
},
"scripts": {
"dev": "next dev -p ${CADENCE_WEB_PORT:-8088} -H ${CADENCE_WEB_HOSTNAME:-0.0.0.0} | pino-pretty --messageKey message",
Expand Down
10 changes: 10 additions & 0 deletions src/utils/data-formatters/schema/history-event-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,20 @@ const cronOverlapPolicySchema = z.enum([
CronOverlapPolicy.CRON_OVERLAP_POLICY_BUFFER_ONE,
]);

const clusterAttributeSchema = z.object({
scope: z.string(),
name: z.string(),
});

// TODO @adhitya.mamallan - this needs to be removed as part of active-active's redesign
const activeClusterSelectionStrategySchema = z.enum([
ActiveClusterSelectionStrategy.ACTIVE_CLUSTER_SELECTION_STRATEGY_INVALID,
ActiveClusterSelectionStrategy.ACTIVE_CLUSTER_SELECTION_STRATEGY_REGION_STICKY,
ActiveClusterSelectionStrategy.ACTIVE_CLUSTER_SELECTION_STRATEGY_EXTERNAL_ENTITY,
]);

// TODO @adhitya.mamallan - this needs to be modified as part of active-active's redesign,
// so that we only check for clusterAttributes
const activeClusterSelectionPolicySchema = z.discriminatedUnion(
'strategyConfig',
[
Expand All @@ -157,6 +165,7 @@ const activeClusterSelectionPolicySchema = z.discriminatedUnion(
stickyRegion: z.string(),
}),
activeClusterExternalEntityConfig: z.nullable(z.undefined()),
clusterAttribute: clusterAttributeSchema.nullable(),
}),
z.object({
strategy: activeClusterSelectionStrategySchema,
Expand All @@ -166,6 +175,7 @@ const activeClusterSelectionPolicySchema = z.discriminatedUnion(
externalEntityType: z.string(),
externalEntityKey: z.string(),
}),
clusterAttribute: clusterAttributeSchema.nullable(),
}),
]
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createElement } from 'react';

import isActiveActiveDomain from '@/views/shared/active-active/helpers/is-active-active-domain';

import DomainPageMetadataClusters from '../domain-page-metadata-clusters/domain-page-metadata-clusters';
import DomainPageMetadataDescription from '../domain-page-metadata-description/domain-page-metadata-description';
import DomainPageMetadataFailoverVersion from '../domain-page-metadata-failover-version/domain-page-metadata-failover-version';
import DomainPageMetadataFailoverVersionActiveActive from '../domain-page-metadata-failover-version-active-active/domain-page-metadata-failover-version-active-active';
import { type MetadataItem } from '../domain-page-metadata-table/domain-page-metadata-table.types';
import DomainPageMetadataViewJson from '../domain-page-metadata-view-json/domain-page-metadata-view-json';
import getClusterOperationMode from '../helpers/get-cluster-operation-mode';
Expand Down Expand Up @@ -55,7 +57,12 @@ const domainPageMetadataExtendedTableConfig = [
description: 'The failover version of the domain',
kind: 'simple',
getValue: ({ domainDescription }) =>
createElement(DomainPageMetadataFailoverVersion, domainDescription),
isActiveActiveDomain(domainDescription)
? createElement(
DomainPageMetadataFailoverVersionActiveActive,
domainDescription
)
: domainDescription.failoverVersion,
},
{
key: 'describeDomainJson',
Expand Down
13 changes: 11 additions & 2 deletions src/views/domain-page/config/domain-page-metadata-table.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { createElement } from 'react';

import type { ListTableItem } from '@/components/list-table/list-table.types';
import isActiveActiveDomain from '@/views/shared/active-active/helpers/is-active-active-domain';

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

Expand Down Expand Up @@ -31,7 +34,13 @@ const domainPageMetadataTableConfig: Array<ListTableItem<DomainDescription>> = [
{
key: 'failoverVersion',
label: 'Failover version',
renderValue: DomainPageMetadataFailoverVersion,
renderValue: (domainDescription: DomainDescription) =>
isActiveActiveDomain(domainDescription)
? createElement(
DomainPageMetadataFailoverVersionActiveActive,
domainDescription
)
: domainDescription.failoverVersion,
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,9 @@ import {
mockDomainDescription,
mockDomainDescriptionSingleCluster,
} from '../../__fixtures__/domain-description';
import * as isActiveClusterModule from '../../helpers/is-active-cluster';
import DomainPageMetadataClusters from '../domain-page-metadata-clusters';

jest.mock('../../helpers/is-active-cluster', () => ({
__esModule: true,
default: jest.fn().mockReturnValue(false),
}));

describe(DomainPageMetadataClusters.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('renders plain text for single cluster', async () => {
render(
<DomainPageMetadataClusters {...mockDomainDescriptionSingleCluster} />
Expand All @@ -27,11 +17,6 @@ describe(DomainPageMetadataClusters.name, () => {
});

it('renders active/passive labels and links for multiple clusters', () => {
jest
.spyOn(isActiveClusterModule, 'default')
.mockReturnValueOnce(true) // cluster_1 is active
.mockReturnValueOnce(false); // cluster_2 is passive

const { container } = render(
<DomainPageMetadataClusters {...mockDomainDescription} />
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React from 'react';
import Link from '@/components/link/link';

import { type DomainDescription } from '../domain-page.types';
import isActiveCluster from '../helpers/is-active-cluster';

import { styled } from './domain-page-metadata-clusters.styles';

Expand All @@ -18,12 +17,10 @@ export default function DomainPageMetadataClusters(
return (
<styled.ClusterTextContainer>
{domainDescription.clusters.map((cluster, index) => {
const replicationStatusLabel = isActiveCluster(
domainDescription,
cluster.clusterName
)
? 'active'
: 'passive';
const replicationStatusLabel =
cluster.clusterName === domainDescription.activeClusterName
? 'active'
: 'passive';

return (
<React.Fragment key={cluster.clusterName}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { render, screen, userEvent } from '@/test-utils/rtl';

import { mockActiveActiveDomain } from '@/views/shared/active-active/__fixtures__/active-active-domain';
import { type ActiveActiveDomain } from '@/views/shared/active-active/active-active.types';

import DomainPageMetadataFailoverVersionActiveActive from '../domain-page-metadata-failover-version-active-active';

describe(DomainPageMetadataFailoverVersionActiveActive.name, () => {
it('renders the table with headers', () => {
setup();

expect(screen.getByText('Scope')).toBeInTheDocument();
expect(screen.getByText('Attribute')).toBeInTheDocument();
expect(screen.getByText('Active Cluster')).toBeInTheDocument();
expect(screen.getByText('Failover Version')).toBeInTheDocument();
});

it('renders default entry with domain data', () => {
setup();

expect(screen.getByText('default')).toBeInTheDocument();
expect(screen.getByText('-')).toBeInTheDocument();
expect(screen.getByText('cluster0')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();
});

it('renders multiple failover version entries', () => {
setup({ attributeCount: 2 });

// Default entry
expect(screen.getByText('default')).toBeInTheDocument();
expect(screen.getByText('cluster0')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();

// Additional entries
expect(screen.getByText('scope1')).toBeInTheDocument();
expect(screen.getByText('attribute1')).toBeInTheDocument();
expect(screen.getByText('cluster_1')).toBeInTheDocument();
expect(screen.getByText('1000')).toBeInTheDocument();

expect(screen.getByText('scope2')).toBeInTheDocument();
expect(screen.getByText('attribute2')).toBeInTheDocument();
expect(screen.getByText('cluster_2')).toBeInTheDocument();
expect(screen.getByText('1001')).toBeInTheDocument();
});

it('does not show "Show all" button when entries are less than or equal to MAX_ATTRIBUTES_COUNT_TRUNCATED', () => {
// 3 additional attributes + 1 default = 4 total (exactly at the limit)
setup({ attributeCount: 3 });

expect(screen.queryByText(/Show all/)).not.toBeInTheDocument();
});

it('shows "Show all" button when entries exceed MAX_ATTRIBUTES_COUNT_TRUNCATED', () => {
// 5 additional attributes + 1 default = 6 total (exceeds limit of 4)
setup({ attributeCount: 5 });

expect(screen.getByText('Show all (6)')).toBeInTheDocument();
});

it('truncates entries when not expanded', () => {
setup({ attributeCount: 5 });

// Should show first 4 entries only (default + first 3 attributes)
expect(screen.getByText('default')).toBeInTheDocument();
expect(screen.getByText('scope1')).toBeInTheDocument();
expect(screen.getByText('scope2')).toBeInTheDocument();
expect(screen.getByText('scope3')).toBeInTheDocument();

// Should not show 5th and 6th entries
expect(screen.queryByText('scope4')).not.toBeInTheDocument();
expect(screen.queryByText('scope5')).not.toBeInTheDocument();
});

it('expands to show all entries when "Show all" button is clicked', async () => {
const { user } = setup({ attributeCount: 5 });

const showAllButton = screen.getByText('Show all (6)');
await user.click(showAllButton);

// Now all entries should be visible
expect(screen.getByText('default')).toBeInTheDocument();
expect(screen.getByText('scope1')).toBeInTheDocument();
expect(screen.getByText('scope2')).toBeInTheDocument();
expect(screen.getByText('scope3')).toBeInTheDocument();
expect(screen.getByText('scope4')).toBeInTheDocument();
expect(screen.getByText('scope5')).toBeInTheDocument();

// Button text should change
expect(screen.getByText('Show less')).toBeInTheDocument();
expect(screen.queryByText('Show all (6)')).not.toBeInTheDocument();
});

it('collapses to show truncated entries when "Show less" button is clicked', async () => {
const { user } = setup({ attributeCount: 5 });

// Expand first
const showAllButton = screen.getByText('Show all (6)');
await user.click(showAllButton);

// Then collapse
const showLessButton = screen.getByText('Show less');
await user.click(showLessButton);

// Should show first 4 entries only
expect(screen.getByText('default')).toBeInTheDocument();
expect(screen.getByText('scope1')).toBeInTheDocument();
expect(screen.getByText('scope2')).toBeInTheDocument();
expect(screen.getByText('scope3')).toBeInTheDocument();

// Should not show 5th and 6th entries
expect(screen.queryByText('scope4')).not.toBeInTheDocument();
expect(screen.queryByText('scope5')).not.toBeInTheDocument();

// Button text should change back
expect(screen.getByText('Show all (6)')).toBeInTheDocument();
expect(screen.queryByText('Show less')).not.toBeInTheDocument();
});
});

function setup({
attributeCount = 0,
}: {
attributeCount?: number;
} = {}) {
const user = userEvent.setup();

const activeClustersByClusterAttribute: Record<string, any> = {};

// Create additional attributes beyond the default one
for (let i = 0; i < attributeCount; i++) {
const scope = `scope${i + 1}`;
activeClustersByClusterAttribute[scope] = {
clusterAttributes: {
[`attribute${i + 1}`]: {
activeClusterName: `cluster_${i + 1}`,
failoverVersion: `${1000 + i}`,
},
},
};
}

const domain: ActiveActiveDomain = {
...mockActiveActiveDomain,
activeClusters: {
regionToCluster: {},
activeClustersByClusterAttribute,
},
};

render(<DomainPageMetadataFailoverVersionActiveActive {...domain} />);

return { user };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAX_ATTRIBUTES_COUNT_TRUNCATED = 4;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { styled as createStyled, type Theme } from 'baseui';
import { type TableOverrides } from 'baseui/table-semantic';
import { type StyleObject } from 'styletron-react';

export const overrides = {
table: {
TableHeadCell: {
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
...$theme.typography.LabelXSmall,
paddingTop: $theme.sizing.scale300,
paddingBottom: $theme.sizing.scale300,
paddingLeft: $theme.sizing.scale500,
paddingRight: $theme.sizing.scale500,
}),
},
TableBodyCell: {
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
...$theme.typography.ParagraphXSmall,
paddingTop: $theme.sizing.scale300,
paddingBottom: $theme.sizing.scale300,
paddingLeft: $theme.sizing.scale500,
paddingRight: $theme.sizing.scale500,
}),
},
} satisfies TableOverrides,
};

export const styled = {
FailoverVersionsContainer: createStyled('div', ({ $theme }) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: $theme.sizing.scale500,
})),
};
Loading