Skip to content

Commit 0094783

Browse files
authored
feat(compass-global-writes): invalid and mismatching shard key state COMPASS-8278 (#6371)
1 parent ef3ebe9 commit 0094783

File tree

11 files changed

+577
-49
lines changed

11 files changed

+577
-49
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import { ShardingStatuses } from '../store/reducer';
1212
import UnshardedState from './states/unsharded';
1313
import ShardingState from './states/sharding';
1414
import ShardKeyCorrect from './states/shard-key-correct';
15+
import ShardKeyInvalid from './states/shard-key-invalid';
16+
import ShardKeyMismatch from './states/shard-key-mismatch';
1517

1618
const containerStyles = css({
1719
paddingLeft: spacing[400],
1820
paddingRight: spacing[400],
1921
display: 'flex',
2022
width: '100%',
2123
height: '100%',
24+
maxWidth: '700px',
2225
});
2326

2427
const workspaceContentStyles = css({
@@ -70,6 +73,17 @@ function ShardingStateView({
7073
return <ShardKeyCorrect />;
7174
}
7275

76+
if (shardingStatus === ShardingStatuses.SHARD_KEY_INVALID) {
77+
return <ShardKeyInvalid />;
78+
}
79+
80+
if (
81+
shardingStatus === ShardingStatuses.SHARD_KEY_MISMATCH ||
82+
shardingStatus === ShardingStatuses.UNMANAGING_NAMESPACE_MISMATCH
83+
) {
84+
return <ShardKeyMismatch />;
85+
}
86+
7387
return null;
7488
}
7589

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Body, Code, css, spacing } from '@mongodb-js/compass-components';
2+
import React from 'react';
3+
import type { ShardKey } from '../store/reducer';
4+
5+
const codeBlockContainerStyles = css({
6+
display: 'flex',
7+
flexDirection: 'column',
8+
gap: spacing[100],
9+
});
10+
11+
interface ShardKeyMarkupProps {
12+
shardKey: ShardKey;
13+
namespace: string;
14+
showMetaData?: boolean;
15+
type?: 'requested' | 'existing';
16+
}
17+
18+
export function ShardKeyMarkup({
19+
namespace,
20+
shardKey,
21+
showMetaData,
22+
type = 'existing',
23+
}: ShardKeyMarkupProps) {
24+
let markup = shardKey.fields
25+
.map(
26+
(field) =>
27+
`"${field.name}"` +
28+
(showMetaData ? ` (${field.type.toLowerCase()})` : '')
29+
)
30+
.join(', ');
31+
if (showMetaData) {
32+
markup += ` - unique: ${String(shardKey.isUnique)}`;
33+
}
34+
return (
35+
<div className={codeBlockContainerStyles}>
36+
<Body data-testid={`${type}-shardkey-description-title`}>
37+
{type === 'existing' ? (
38+
<>
39+
<strong>{namespace}</strong> is configured with the following shard
40+
key:
41+
</>
42+
) : (
43+
<>You requested to use the shard key:</>
44+
)}
45+
</Body>
46+
<Code language="js" data-testid={`${type}-shardkey-description-content`}>
47+
{markup}
48+
</Code>
49+
</div>
50+
);
51+
}
52+
53+
export default ShardKeyMarkup;

packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ describe('Compass GlobalWrites Plugin', function () {
2828
namespace: 'db1.coll1',
2929
shardKey: {
3030
fields: [
31-
{ type: 'HASHED', name: 'location' },
32-
{ type: 'RANGE', name: 'secondary' },
31+
{ type: 'RANGE', name: 'location' },
32+
{ type: 'HASHED', name: 'secondary' },
3333
],
3434
isUnique: false,
3535
},
@@ -66,7 +66,7 @@ describe('Compass GlobalWrites Plugin', function () {
6666
await renderWithProps({ onUnmanageNamespace, isUnmanagingNamespace: true });
6767

6868
const btn = await screen.findByTestId<HTMLButtonElement>(
69-
'shard-collection-button'
69+
'unmanage-collection-button'
7070
);
7171
expect(btn).to.be.visible;
7272
expect(btn.getAttribute('aria-disabled')).to.equal('true');
@@ -103,12 +103,16 @@ describe('Compass GlobalWrites Plugin', function () {
103103
it('Describes the shardKey', async function () {
104104
await renderWithProps();
105105

106-
const title = await screen.findByTestId('shardkey-description-title');
106+
const title = await screen.findByTestId(
107+
'existing-shardkey-description-title'
108+
);
107109
expect(title).to.be.visible;
108110
expect(title.textContent).to.equal(
109111
`${baseProps.namespace} is configured with the following shard key:`
110112
);
111-
const list = await screen.findByTestId('shardkey-description-content');
113+
const list = await screen.findByTestId(
114+
'existing-shardkey-description-content'
115+
);
112116
expect(list).to.be.visible;
113117
expect(list.textContent).to.contain(`"location", "secondary"`);
114118
});

packages/compass-global-writes/src/components/states/shard-key-correct.tsx

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Subtitle,
1111
Label,
1212
Button,
13+
ButtonVariant,
1314
} from '@mongodb-js/compass-components';
1415
import { connect } from 'react-redux';
1516
import {
@@ -22,6 +23,7 @@ import {
2223
import toNS from 'mongodb-ns';
2324
import { ShardZonesTable } from '../shard-zones-table';
2425
import { useConnectionInfo } from '@mongodb-js/compass-connections/provider';
26+
import ShardKeyMarkup from '../shard-key-markup';
2527

2628
const nbsp = '\u00a0';
2729

@@ -47,7 +49,7 @@ const paragraphStyles = css({
4749

4850
export type ShardKeyCorrectProps = {
4951
namespace: string;
50-
shardKey?: ShardKey;
52+
shardKey: ShardKey;
5153
shardZones: ShardZoneData[];
5254
isUnmanagingNamespace: boolean;
5355
onUnmanageNamespace: () => void;
@@ -60,10 +62,6 @@ export function ShardKeyCorrect({
6062
isUnmanagingNamespace,
6163
onUnmanageNamespace,
6264
}: ShardKeyCorrectProps) {
63-
if (!shardKey) {
64-
throw new Error('Shard key not found in ShardKeyCorrect');
65-
}
66-
6765
const customShardKeyField = useMemo(() => {
6866
return shardKey.fields[1].name;
6967
}, [shardKey]);
@@ -92,17 +90,7 @@ export function ShardKeyCorrect({
9290
</strong>
9391
{nbsp}We have included a table for reference below.
9492
</Banner>
95-
96-
<div className={codeBlockContainerStyles}>
97-
<Body data-testid="shardkey-description-title">
98-
<strong>{namespace}</strong> is configured with the following shard
99-
key:
100-
</Body>
101-
<Code language="js" data-testid="shardkey-description-content">
102-
{shardKey.fields.map((field) => `"${field.name}"`).join(', ')}
103-
</Code>
104-
</div>
105-
93+
<ShardKeyMarkup namespace={namespace} shardKey={shardKey} />
10694
<Subtitle>Example commands</Subtitle>
10795
<div className={paragraphStyles}>
10896
<Body>
@@ -184,9 +172,9 @@ export function ShardKeyCorrect({
184172
</Body>
185173
<div>
186174
<Button
187-
data-testid="shard-collection-button"
175+
data-testid="unmanage-collection-button"
188176
onClick={onUnmanageNamespace}
189-
variant="primary"
177+
variant={ButtonVariant.Primary}
190178
isLoading={isUnmanagingNamespace}
191179
>
192180
Unmanage collection
@@ -197,13 +185,18 @@ export function ShardKeyCorrect({
197185
}
198186

199187
export default connect(
200-
(state: RootState) => ({
201-
namespace: state.namespace,
202-
shardKey: state.shardKey,
203-
shardZones: state.shardZones,
204-
isUnmanagingNamespace:
205-
state.status === ShardingStatuses.UNMANAGING_NAMESPACE,
206-
}),
188+
(state: RootState) => {
189+
if (!state.shardKey) {
190+
throw new Error('Shard key not found in ShardKeyCorrect');
191+
}
192+
return {
193+
namespace: state.namespace,
194+
shardKey: state.shardKey,
195+
shardZones: state.shardZones,
196+
isUnmanagingNamespace:
197+
state.status === ShardingStatuses.UNMANAGING_NAMESPACE,
198+
};
199+
},
207200
{
208201
onUnmanageNamespace: unmanageNamespace,
209202
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { screen } from '@mongodb-js/testing-library-compass';
4+
import {
5+
ShardKeyInvalid,
6+
type ShardKeyInvalidProps,
7+
} from './shard-key-invalid';
8+
import { renderWithStore } from '../../../tests/create-store';
9+
10+
describe('Compass GlobalWrites Plugin', function () {
11+
const baseProps: ShardKeyInvalidProps = {
12+
namespace: 'db1.coll1',
13+
shardKey: {
14+
fields: [
15+
{ type: 'HASHED', name: 'not-location' },
16+
{ type: 'RANGE', name: 'secondary' },
17+
],
18+
isUnique: false,
19+
},
20+
};
21+
22+
function renderWithProps(
23+
props?: Partial<ShardKeyInvalidProps>,
24+
options?: Parameters<typeof renderWithStore>[1]
25+
) {
26+
return renderWithStore(
27+
<ShardKeyInvalid {...baseProps} {...props} />,
28+
options
29+
);
30+
}
31+
32+
it('Describes next steps', async function () {
33+
await renderWithProps();
34+
35+
expect(screen.findByText(/Please migrate the data in this collection/)).to
36+
.exist;
37+
});
38+
39+
it('Describes the shardKey (with metadata)', async function () {
40+
await renderWithProps();
41+
42+
const title = await screen.findByTestId(
43+
'existing-shardkey-description-title'
44+
);
45+
expect(title).to.be.visible;
46+
expect(title.textContent).to.equal(
47+
`${baseProps.namespace} is configured with the following shard key:`
48+
);
49+
const list = await screen.findByTestId(
50+
'existing-shardkey-description-content'
51+
);
52+
expect(list).to.be.visible;
53+
expect(list.textContent).to.contain(
54+
`"not-location" (hashed), "secondary" (range)`
55+
);
56+
expect(list.textContent).to.contain(`unique: false`);
57+
});
58+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {
2+
Banner,
3+
BannerVariant,
4+
spacing,
5+
css,
6+
} from '@mongodb-js/compass-components';
7+
import React from 'react';
8+
import ShardKeyMarkup from '../shard-key-markup';
9+
import type { RootState, ShardKey } from '../../store/reducer';
10+
import { connect } from 'react-redux';
11+
12+
const containerStyles = css({
13+
display: 'flex',
14+
flexDirection: 'column',
15+
gap: spacing[400],
16+
marginBottom: spacing[400],
17+
});
18+
19+
const paragraphStyles = css({
20+
display: 'flex',
21+
flexDirection: 'column',
22+
gap: spacing[100],
23+
});
24+
25+
export interface ShardKeyInvalidProps {
26+
shardKey: ShardKey;
27+
namespace: string;
28+
}
29+
30+
export function ShardKeyInvalid({ shardKey, namespace }: ShardKeyInvalidProps) {
31+
return (
32+
<div className={containerStyles}>
33+
<Banner variant={BannerVariant.Danger}>
34+
<strong>
35+
To configure Global Writes, the first shard key of this collection
36+
must be &quot;location&quot; with ranged sharding and you must also
37+
specify a second shard key.
38+
</strong>{' '}
39+
Please migrate the data in this collection to a new collection and
40+
reshard it using a valid compound shard key.
41+
</Banner>
42+
<ShardKeyMarkup
43+
namespace={namespace}
44+
shardKey={shardKey}
45+
showMetaData={true}
46+
/>
47+
<div className={paragraphStyles}>
48+
Documents in this collection will be distributed across your shards
49+
without being mapped to specific zones.
50+
</div>
51+
</div>
52+
);
53+
}
54+
55+
export default connect((state: RootState) => {
56+
if (!state.shardKey) {
57+
throw new Error('Shard key not found in ShardKeyInvalid');
58+
}
59+
return {
60+
namespace: state.namespace,
61+
shardKey: state.shardKey,
62+
};
63+
})(ShardKeyInvalid);

0 commit comments

Comments
 (0)