Skip to content

Commit 3c135eb

Browse files
committed
feat(compass-global-writes): sharding error state
1 parent ef3ebe9 commit 3c135eb

File tree

6 files changed

+468
-310
lines changed

6 files changed

+468
-310
lines changed
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
import React, { useCallback, useState } from 'react';
2+
import {
3+
Accordion,
4+
Body,
5+
Button,
6+
Checkbox,
7+
ComboboxOption,
8+
ComboboxWithCustomOption,
9+
css,
10+
cx,
11+
InlineInfoLink,
12+
Label,
13+
Link,
14+
Radio,
15+
RadioGroup,
16+
spacing,
17+
Subtitle,
18+
TextInput,
19+
} from '@mongodb-js/compass-components';
20+
import type { CreateShardKeyData } from '../store/reducer';
21+
import { useAutocompleteFields } from '@mongodb-js/compass-field-store';
22+
23+
const contentStyles = css({
24+
display: 'flex',
25+
flexDirection: 'column',
26+
gap: spacing[200],
27+
});
28+
29+
const listStyles = css({
30+
listStyle: 'disc',
31+
paddingLeft: 'auto',
32+
marginTop: 0,
33+
});
34+
35+
const shardKeyFormFieldsStyles = css({
36+
display: 'flex',
37+
flexDirection: 'row',
38+
gap: spacing[400],
39+
});
40+
41+
const secondShardKeyStyles = css({
42+
width: '300px',
43+
});
44+
45+
const hasedIndexOptionsStyles = css({
46+
marginLeft: spacing[1200], // This aligns it with the radio button text
47+
marginTop: spacing[400],
48+
});
49+
50+
const advanceOptionsGroupStyles = css({
51+
paddingLeft: spacing[500], // Avoid visual cutoff
52+
});
53+
54+
const chunksInputStyles = css({
55+
display: 'flex',
56+
alignItems: 'center',
57+
gap: spacing[100],
58+
});
59+
60+
const nbsp = '\u00a0';
61+
62+
type ShardingAdvancedOption = 'default' | 'unique-index' | 'hashed-index';
63+
64+
function CreateShardKeyDescription() {
65+
return (
66+
<div className={contentStyles} data-testid="unsharded-text-description">
67+
<Subtitle>Configure compound shard key</Subtitle>
68+
<Body>
69+
To properly configure Global Writes, your collections must be sharded
70+
using a compound shard key made up of a ‘location’ field and a second
71+
field of your choosing.
72+
</Body>
73+
74+
<Body>
75+
All documents in your collection should contain both the ‘location’
76+
field and your chosen second field.
77+
</Body>
78+
79+
<ul className={listStyles}>
80+
<li>
81+
<Body>
82+
The second field should represent a well-distributed and immutable
83+
value to ensure that data is equally distributed across shards in a
84+
particular zone.{nbsp}
85+
<strong>
86+
Note that the value of this field cannot be an array.
87+
</strong>
88+
{nbsp}
89+
For more information, read our documentation on{' '}
90+
<Link
91+
hideExternalIcon
92+
href="https://www.mongodb.com/docs/manual/core/sharding-shard-key/#choosing-a-shard-key"
93+
>
94+
selecting a shard key
95+
</Link>
96+
.
97+
</Body>
98+
</li>
99+
</ul>
100+
101+
<Body weight="medium">
102+
Once you shard your collection, it cannot be unsharded.
103+
</Body>
104+
</div>
105+
);
106+
}
107+
108+
export type CreateShardKeyFormProps = {
109+
namespace: string;
110+
isSubmittingForSharding: boolean;
111+
onCreateShardKey: (data: CreateShardKeyData) => void;
112+
};
113+
114+
function CreateShardKeyForm({
115+
namespace,
116+
isSubmittingForSharding,
117+
onCreateShardKey,
118+
}: CreateShardKeyFormProps) {
119+
const [isAdvancedOptionsOpen, setIsAdvancedOptionsOpen] = useState(false);
120+
const [selectedAdvancedOption, setSelectedAdvancedOption] =
121+
useState<ShardingAdvancedOption>('default');
122+
const fields = useAutocompleteFields(namespace);
123+
124+
const [secondShardKey, setSecondShardKey] = useState<string | null>(null);
125+
const [numInitialChunks, setNumInitialChunks] = useState<
126+
string | undefined
127+
>();
128+
const [isPreSplitData, setIsPreSplitData] = useState(false);
129+
130+
const onSubmit = useCallback(() => {
131+
if (!secondShardKey) {
132+
return;
133+
}
134+
const isCustomShardKeyHashed = selectedAdvancedOption === 'hashed-index';
135+
const presplitHashedZones = isCustomShardKeyHashed && isPreSplitData;
136+
137+
const data: CreateShardKeyData = {
138+
customShardKey: secondShardKey,
139+
isShardKeyUnique: selectedAdvancedOption === 'unique-index',
140+
isCustomShardKeyHashed,
141+
presplitHashedZones,
142+
numInitialChunks:
143+
presplitHashedZones && numInitialChunks
144+
? Number(numInitialChunks)
145+
: null,
146+
};
147+
148+
onCreateShardKey(data);
149+
}, [
150+
isPreSplitData,
151+
numInitialChunks,
152+
secondShardKey,
153+
selectedAdvancedOption,
154+
onCreateShardKey,
155+
]);
156+
157+
return (
158+
<>
159+
<CreateShardKeyDescription />
160+
<div className={contentStyles} data-testid="shard-collection-form">
161+
<div className={shardKeyFormFieldsStyles}>
162+
<div>
163+
<Label htmlFor="first-shard-key">
164+
First shard key field
165+
<InlineInfoLink
166+
aria-label="Connection String Documentation"
167+
data-testid="connectionStringDocsButton"
168+
href="https://docs.mongodb.com/manual/core/sharding-shard-key"
169+
/>
170+
</Label>
171+
<TextInput
172+
id="first-shard-key"
173+
aria-labelledby="First shard key field"
174+
placeholder="location"
175+
disabled
176+
/>
177+
</div>
178+
<div>
179+
<Label htmlFor="second-shard-key">
180+
Second shard key field
181+
<InlineInfoLink
182+
aria-label="Connection String Documentation"
183+
data-testid="connectionStringDocsButton"
184+
href="https://docs.mongodb.com/manual/core/zone-sharding/#shard-key"
185+
/>
186+
</Label>
187+
<ComboboxWithCustomOption
188+
id="second-shard-key"
189+
aria-label="Second shard key field"
190+
placeholder="Second shard key field"
191+
size="default"
192+
clearable={false}
193+
overflow="scroll-x"
194+
onChange={setSecondShardKey}
195+
options={fields.map(({ value }) => ({ value }))}
196+
className={secondShardKeyStyles}
197+
value={secondShardKey}
198+
searchEmptyMessage="No fields found. Please enter a valid field name."
199+
renderOption={(option, index, isCustom) => {
200+
return (
201+
<ComboboxOption
202+
key={`field-option-${index}`}
203+
value={option.value}
204+
displayName={
205+
isCustom ? `Field: "${option.value}"` : option.value
206+
}
207+
/>
208+
);
209+
}}
210+
/>
211+
</div>
212+
</div>
213+
<Accordion
214+
data-testid="advanced-shard-key-configuration"
215+
text="Advanced Shard Key Configuration"
216+
open={isAdvancedOptionsOpen}
217+
setOpen={setIsAdvancedOptionsOpen}
218+
className={css({ paddingLeft: spacing[400] })}
219+
>
220+
<RadioGroup
221+
className={advanceOptionsGroupStyles}
222+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
223+
setSelectedAdvancedOption(
224+
event.target.value as ShardingAdvancedOption
225+
);
226+
}}
227+
>
228+
<Radio
229+
value="default"
230+
checked={selectedAdvancedOption === 'default'}
231+
>
232+
Default
233+
</Radio>
234+
<Radio
235+
id="unique-index"
236+
value="unique-index"
237+
checked={selectedAdvancedOption === 'unique-index'}
238+
>
239+
<div>
240+
<Label htmlFor="unique-index">
241+
Use unique index as the shard key
242+
</Label>
243+
<Body>
244+
Enforce a uniqueness constraint on the shard key of this
245+
Global Collection.{' '}
246+
<Link href="https://docs.atlas.mongodb.com/data-explorer/global-writes/#optional-expand-advanced-shard-key-configuration-section-to-specify-how-to-shard-the-collection">
247+
Learn more
248+
</Link>
249+
</Body>
250+
</div>
251+
</Radio>
252+
<Radio
253+
id="hashed-index"
254+
value="hashed-index"
255+
checked={selectedAdvancedOption === 'hashed-index'}
256+
>
257+
<div>
258+
<Label htmlFor="hashed-index">
259+
Use hashed index as the shard key
260+
</Label>
261+
<Body>
262+
Improve even distribution of the sharded data by hashing the
263+
second field of the shard key.{' '}
264+
<Link href="https://docs.atlas.mongodb.com/data-explorer/global-writes/#optional-expand-advanced-shard-key-configuration-section-to-specify-how-to-shard-the-collection">
265+
Learn more
266+
</Link>
267+
</Body>
268+
</div>
269+
</Radio>
270+
</RadioGroup>
271+
{selectedAdvancedOption === 'hashed-index' && (
272+
<div className={cx(contentStyles, hasedIndexOptionsStyles)}>
273+
<Checkbox
274+
data-testid="presplit-data-checkbox"
275+
onChange={() => setIsPreSplitData(!isPreSplitData)}
276+
label="Pre-split data for even distribution."
277+
checked={isPreSplitData}
278+
/>
279+
<div className={chunksInputStyles}>
280+
<TextInput
281+
data-testid="chunks-per-shard-input"
282+
id="chunks-per-shard"
283+
aria-labelledby="Chunks per shard"
284+
disabled={!isPreSplitData}
285+
type="number"
286+
placeholder="Chunks"
287+
min={0}
288+
value={numInitialChunks}
289+
onChange={(event) => setNumInitialChunks(event.target.value)}
290+
/>
291+
<Body>chunks per shard.</Body>
292+
</div>
293+
</div>
294+
)}
295+
</Accordion>
296+
<div>
297+
<Button
298+
data-testid="shard-collection-button"
299+
onClick={onSubmit}
300+
disabled={!secondShardKey || isSubmittingForSharding}
301+
variant="primary"
302+
isLoading={isSubmittingForSharding}
303+
>
304+
Shard Collection
305+
</Button>
306+
</div>
307+
</div>
308+
</>
309+
);
310+
}
311+
312+
export default CreateShardKeyForm;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ 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 ShardingError from './states/sharding-error';
1516

1617
const containerStyles = css({
1718
paddingLeft: spacing[400],
@@ -63,6 +64,14 @@ function ShardingStateView({
6364
return <ShardingState />;
6465
}
6566

67+
if (
68+
shardingStatus === ShardingStatuses.SHARDING_ERROR ||
69+
shardingStatus === ShardingStatuses.CANCELLING_SHARDING_ERROR ||
70+
shardingStatus === ShardingStatuses.SUBMITTING_FOR_SHARDING_ERROR
71+
) {
72+
return <ShardingError />;
73+
}
74+
6675
if (
6776
shardingStatus === ShardingStatuses.SHARD_KEY_CORRECT ||
6877
shardingStatus === ShardingStatuses.UNMANAGING_NAMESPACE

0 commit comments

Comments
 (0)