Skip to content

Commit 58fb9f5

Browse files
authored
feat: add sharded state COMPASS-8279 (#6304)
1 parent 2c24180 commit 58fb9f5

File tree

16 files changed

+1314
-231
lines changed

16 files changed

+1314
-231
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-global-writes/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@mongodb-js/compass-logging": "^1.4.8",
5757
"@mongodb-js/compass-telemetry": "^1.2.0",
5858
"hadron-app-registry": "^9.2.7",
59+
"lodash": "^4.17.21",
5960
"@mongodb-js/compass-field-store": "^9.18.1",
6061
"mongodb-ns": "^2.4.2",
6162
"react": "^17.0.2",

packages/compass-global-writes/src/components/index.spec.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@ import { GlobalWrites } from './index';
55
import { renderWithStore } from './../../tests/create-store';
66

77
describe('Compass GlobalWrites Plugin', function () {
8-
it('renders plugin in NOT_READY state', function () {
9-
renderWithStore(<GlobalWrites shardingStatus={'NOT_READY'} />);
8+
it('renders plugin in NOT_READY state', async function () {
9+
await renderWithStore(<GlobalWrites shardingStatus={'NOT_READY'} />);
1010
expect(screen.getByText(/loading/i)).to.exist;
1111
});
1212

13-
it('renders plugin in UNSHARDED state', function () {
14-
renderWithStore(<GlobalWrites shardingStatus={'UNSHARDED'} />);
13+
it('renders plugin in UNSHARDED state', async function () {
14+
await renderWithStore(<GlobalWrites shardingStatus={'UNSHARDED'} />);
1515
expect(screen.getByTestId('shard-collection-button')).to.exist;
1616
});
1717

18-
it('renders plugin in SUBMITTING_FOR_SHARDING state', function () {
19-
renderWithStore(
18+
it('renders plugin in SUBMITTING_FOR_SHARDING state', async function () {
19+
await renderWithStore(
2020
<GlobalWrites shardingStatus={'SUBMITTING_FOR_SHARDING'} />
2121
);
2222
expect(screen.getByTestId('shard-collection-button')).to.exist;
2323
});
2424

25-
it('renders plugin in SHARDING state', function () {
26-
renderWithStore(<GlobalWrites shardingStatus={'SHARDING'} />);
25+
it('renders plugin in SHARDING state', async function () {
26+
await renderWithStore(<GlobalWrites shardingStatus={'SHARDING'} />);
2727
expect(screen.getByText(/sharding your collection/i)).to.exist;
2828
});
2929
});

packages/compass-global-writes/src/components/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { RootState, ShardingStatus } from '../store/reducer';
1010
import { ShardingStatuses } from '../store/reducer';
1111
import UnshardedState from './states/unsharded';
1212
import ShardingState from './states/sharding';
13+
import ShardKeyCorrect from './states/shard-key-correct';
1314

1415
const containerStyles = css({
1516
paddingLeft: spacing[400],
@@ -58,6 +59,13 @@ function ShardingStateView({
5859
return <ShardingState />;
5960
}
6061

62+
if (
63+
shardingStatus === ShardingStatuses.SHARD_KEY_CORRECT ||
64+
shardingStatus === ShardingStatuses.UNMANAGING_NAMESPACE
65+
) {
66+
return <ShardKeyCorrect />;
67+
}
68+
6169
return null;
6270
}
6371

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { render, screen, within } from '@mongodb-js/testing-library-compass';
4+
import { ShardZonesTable } from './shard-zones-table';
5+
import { type ShardZoneData } from '../store/reducer';
6+
7+
describe('Compass GlobalWrites Plugin', function () {
8+
const shardZones: ShardZoneData[] = [
9+
{
10+
zoneId: '45893084',
11+
country: 'Germany',
12+
readableName: 'Germany',
13+
isoCode: 'DE',
14+
typeOneIsoCode: 'DE',
15+
zoneName: 'EMEA',
16+
zoneLocations: ['Frankfurt'],
17+
},
18+
{
19+
zoneId: '43829408',
20+
country: 'Germany',
21+
readableName: 'Germany - Berlin',
22+
isoCode: 'DE-BE',
23+
typeOneIsoCode: 'DE',
24+
zoneName: 'EMEA',
25+
zoneLocations: ['Frankfurt'],
26+
},
27+
];
28+
29+
it('renders the Location name & Zone for all items', function () {
30+
render(<ShardZonesTable shardZones={shardZones} />);
31+
32+
const rows = screen.getAllByRole('row');
33+
expect(rows).to.have.lengthOf(3); // 1 header, 2 items
34+
expect(within(rows[1]).getByText('Germany (DE)')).to.be.visible;
35+
expect(within(rows[1]).getByText('EMEA (Frankfurt)')).to.be.visible;
36+
expect(within(rows[2]).getByText('Germany - Berlin (DE-BE)')).to.be.visible;
37+
expect(within(rows[2]).getByText('EMEA (Frankfurt)')).to.be.visible;
38+
});
39+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import {
3+
Table,
4+
TableBody,
5+
TableHead,
6+
HeaderRow,
7+
HeaderCell,
8+
Row,
9+
Cell,
10+
css,
11+
} from '@mongodb-js/compass-components';
12+
import type { ShardZoneData } from '../store/reducer';
13+
14+
const containerStyles = css({
15+
maxWidth: '700px',
16+
height: '400px',
17+
});
18+
19+
export function ShardZonesTable({
20+
shardZones,
21+
}: {
22+
shardZones: ShardZoneData[];
23+
}) {
24+
return (
25+
// TODO(COMPASS-8336):
26+
// Add search
27+
// group zones by ShardZoneData.typeOneIsoCode
28+
// and display them in a single row that can be expanded
29+
<Table className={containerStyles} title="Zone Mapping">
30+
<TableHead isSticky>
31+
<HeaderRow>
32+
<HeaderCell>Location Name</HeaderCell>
33+
<HeaderCell>Zone</HeaderCell>
34+
</HeaderRow>
35+
</TableHead>
36+
<TableBody>
37+
{shardZones.map(
38+
({ readableName, zoneName, zoneLocations, isoCode }, index) => {
39+
return (
40+
<Row key={index}>
41+
<Cell>
42+
{readableName} ({isoCode})
43+
</Cell>
44+
<Cell>
45+
{zoneName} ({zoneLocations.join(', ')})
46+
</Cell>
47+
</Row>
48+
);
49+
}
50+
)}
51+
</TableBody>
52+
</Table>
53+
);
54+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { screen, userEvent } from '@mongodb-js/testing-library-compass';
4+
import {
5+
ShardKeyCorrect,
6+
type ShardKeyCorrectProps,
7+
} from './shard-key-correct';
8+
import { type ShardZoneData } from '../../store/reducer';
9+
import Sinon from 'sinon';
10+
import { renderWithStore } from '../../../tests/create-store';
11+
import { type ConnectionInfo } from '@mongodb-js/compass-connections/provider';
12+
13+
describe('Compass GlobalWrites Plugin', function () {
14+
const shardZones: ShardZoneData[] = [
15+
{
16+
zoneId: '45893084',
17+
country: 'Germany',
18+
readableName: 'Germany',
19+
isoCode: 'DE',
20+
typeOneIsoCode: 'DE',
21+
zoneName: 'EMEA',
22+
zoneLocations: ['Frankfurt'],
23+
},
24+
];
25+
26+
const baseProps: ShardKeyCorrectProps = {
27+
shardZones,
28+
namespace: 'db1.coll1',
29+
shardKey: {
30+
fields: [
31+
{ type: 'HASHED', name: 'location' },
32+
{ type: 'RANGE', name: 'secondary' },
33+
],
34+
isUnique: false,
35+
},
36+
isUnmanagingNamespace: false,
37+
onUnmanageNamespace: () => {},
38+
};
39+
40+
function renderWithProps(
41+
props?: Partial<ShardKeyCorrectProps>,
42+
options?: Parameters<typeof renderWithStore>[1]
43+
) {
44+
return renderWithStore(
45+
<ShardKeyCorrect {...baseProps} {...props} />,
46+
options
47+
);
48+
}
49+
50+
it('Provides button to unmanage', async function () {
51+
const onUnmanageNamespace = Sinon.spy();
52+
await renderWithProps({ onUnmanageNamespace });
53+
54+
const btn = await screen.findByRole<HTMLButtonElement>('button', {
55+
name: /Unmanage collection/,
56+
});
57+
expect(btn).to.be.visible;
58+
59+
userEvent.click(btn);
60+
61+
expect(onUnmanageNamespace).to.have.been.calledOnce;
62+
});
63+
64+
it('Unmanage btn is disabled when the action is in progress', async function () {
65+
const onUnmanageNamespace = Sinon.spy();
66+
await renderWithProps({ onUnmanageNamespace, isUnmanagingNamespace: true });
67+
68+
const btn = await screen.findByTestId<HTMLButtonElement>(
69+
'shard-collection-button'
70+
);
71+
expect(btn).to.be.visible;
72+
expect(btn.getAttribute('aria-disabled')).to.equal('true');
73+
74+
userEvent.click(btn);
75+
76+
expect(onUnmanageNamespace).not.to.have.been.called;
77+
});
78+
79+
it('Provides link to Edit Configuration', async function () {
80+
const connectionInfo = {
81+
id: 'testConnection',
82+
connectionOptions: {
83+
connectionString: 'mongodb://test',
84+
},
85+
atlasMetadata: {
86+
projectId: 'project1',
87+
clusterName: 'myCluster',
88+
} as ConnectionInfo['atlasMetadata'],
89+
};
90+
await renderWithProps(undefined, {
91+
connectionInfo,
92+
});
93+
94+
const link = await screen.findByRole('link', {
95+
name: /Edit Configuration/,
96+
});
97+
const expectedHref = `/v2/${connectionInfo.atlasMetadata?.projectId}#/clusters/edit/${connectionInfo.atlasMetadata?.clusterName}`;
98+
99+
expect(link).to.be.visible;
100+
expect(link).to.have.attribute('href', expectedHref);
101+
});
102+
103+
it('Describes the shardKey', async function () {
104+
await renderWithProps();
105+
106+
const title = await screen.findByTestId('shardkey-description-title');
107+
expect(title).to.be.visible;
108+
expect(title.textContent).to.equal(
109+
`${baseProps.namespace} is configured with the following shard key:`
110+
);
111+
const list = await screen.findByTestId('shardkey-description-content');
112+
expect(list).to.be.visible;
113+
expect(list.textContent).to.contain(`"location", "secondary"`);
114+
});
115+
116+
it('Contains sample codes', async function () {
117+
await renderWithProps();
118+
119+
const findingDocumentsSample = await screen.findByTestId(
120+
'sample-finding-documents'
121+
);
122+
expect(findingDocumentsSample).to.be.visible;
123+
expect(findingDocumentsSample.textContent).to.contain(
124+
`use db1db["coll1"].find({"location": "US-NY", "secondary": "<id_value>"})`
125+
);
126+
127+
const insertingDocumentsSample = await screen.findByTestId(
128+
'sample-inserting-documents'
129+
);
130+
expect(insertingDocumentsSample).to.be.visible;
131+
expect(insertingDocumentsSample.textContent).to.contain(
132+
`use db1db["coll1"].insertOne({"location": "US-NY", "secondary": "<id_value>",...<other fields>})`
133+
);
134+
});
135+
});

0 commit comments

Comments
 (0)