Skip to content

Commit

Permalink
RavenDB-17793 Add sharding prefixes step to database creator
Browse files Browse the repository at this point in the history
  • Loading branch information
kalczur committed May 16, 2024
1 parent 95ba888 commit e00f762
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export type CreateDatabaseDto = Pick<
Shards: Record<number, { Members?: string[] }>;
Orchestrator: {
Topology: Pick<Raven.Client.ServerWide.DatabaseTopology, "Members">
}
},
Prefixed?: Raven.Client.ServerWide.Sharding.ShardingConfiguration["Prefixed"]
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function CreateDatabaseFromBackupStepBasicInfo() {
name="basicInfoStep.databaseName"
id="DbName"
placeholder="Database Name"
autoComplete="off"
/>
</Col>
</Row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import StepBasicInfo from "./steps/CreateDatabaseRegularStepBasicInfo";
import StepEncryption from "../../../../../../common/FormEncryption";
import StepReplicationAndSharding from "./steps/CreateDatabaseRegularStepReplicationAndSharding";
import StepNodeSelection from "./steps/CreateDatabaseRegularStepNodeSelection";
import StepShardingPrefixes from "components/pages/resources/databases/partials/create/regular/steps/StepShardingPrefixes";
import StepPath from "../shared/CreateDatabaseStepDataDirectory";
import { databaseSelectors } from "components/common/shell/databaseSliceSelectors";
import { useAppSelector } from "components/store";
Expand Down Expand Up @@ -61,6 +62,8 @@ export default function CreateDatabaseRegular({ closeModal, changeCreateModeToBa
isSharded: data.replicationAndShardingStep.isSharded,
isManualReplication: data.replicationAndShardingStep.isManualReplication,
isEncrypted: data.basicInfoStep.isEncrypted,
isPrefixesForShards: data.replicationAndShardingStep.isPrefixesForShards,
usedPrefixes: data.shardingPrefixesStep.prefixesForShards.map((x) => x.prefix),
},
options
),
Expand Down Expand Up @@ -218,6 +221,12 @@ function getActiveStepsList(
active: formValues.replicationAndShardingStep.isManualReplication,
isInvalid: !!errors.manualNodeSelectionStep,
},
{
id: "shardingPrefixesStep",
label: "Sharding Prefixes",
active: formValues.replicationAndShardingStep.isPrefixesForShards,
isInvalid: !!errors.shardingPrefixesStep,
},
{
id: "dataDirectoryStep",
label: "Data Directory",
Expand Down Expand Up @@ -256,6 +265,7 @@ function getStepViews(
),
replicationAndShardingStep: <StepReplicationAndSharding />,
manualNodeSelectionStep: <StepNodeSelection />,
shardingPrefixesStep: <StepShardingPrefixes />,
dataDirectoryStep: (
<StepPath
isBackupFolder={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@ const getDefaultValues = (allNodeTags: string[]): FormData => {
shardsCount: 1,
isDynamicDistribution: false,
isManualReplication: false,
isPrefixesForShards: false,
},
manualNodeSelectionStep: {
// if there is only one node, it should be selected by default
nodes: allNodeTags.length === 1 ? allNodeTags : [],
shards: [],
},
shardingPrefixesStep: {
prefixesForShards: [
{
prefix: "",
shardNumbers: [],
},
],
},
dataDirectoryStep: {
isDefault: true,
directory: "",
Expand Down Expand Up @@ -71,6 +80,12 @@ function mapToDto(formValues: FormData, allNodeTags: string[]): CreateDatabaseDt
Members: selectedOrchestrators,
},
},
Prefixed: formValues.replicationAndShardingStep.isPrefixesForShards
? formValues.shardingPrefixesStep.prefixesForShards.map((x) => ({
Prefix: x.prefix,
Shards: x.shardNumbers,
}))
: null,
}
: null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const replicationAndShardingStepSchema = yup.object({
}),
isDynamicDistribution: yup.boolean(),
isManualReplication: yup.boolean(),
isPrefixesForShards: yup.boolean(),
});

const manualNodeSelectionStepSchema = yup.object({
Expand Down Expand Up @@ -70,11 +71,50 @@ const manualNodeSelectionStepSchema = yup.object({
),
});

const shardingPrefixesStepSchema = yup.object({
prefixesForShards: yup
.array()
.of(
yup.object({
prefix: yup.string().when("$isPrefixesForShards", {
is: true,
then: (schema) =>
schema
.trim()
.strict()
.required()
.test(
"invalid-ending-character",
"The prefix must end with '/' or '-' characters",
(value) => {
return value.endsWith("/") || value.endsWith("-");
}
)
.test("unique-prefix", "Prefix must be unique", (value, ctx) => {
return ctx.options.context.usedPrefixes.filter((x: string) => x === value).length < 2;
}),
}),
shardNumbers: yup
.array()
.of(yup.number())
.when("$isPrefixesForShards", {
is: true,
then: (schema) => schema.min(1, "Select at least one shard"),
}),
})
)
.when("$isPrefixesForShards", {
is: true,
then: (schema) => schema.min(1),
}),
});

export const createDatabaseRegularSchema = yup.object({
basicInfoStep: basicInfoStepSchema,
encryptionStep: encryptionStepSchema,
replicationAndShardingStep: replicationAndShardingStepSchema,
manualNodeSelectionStep: manualNodeSelectionStepSchema,
shardingPrefixesStep: shardingPrefixesStepSchema,
dataDirectoryStep: dataDirectoryStepSchema,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default function CreateDatabaseRegularStepBasicInfo() {
name="basicInfoStep.databaseName"
placeholder="Database Name"
id="DbName"
autoComplete="off"
/>
</Col>
</Row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default function CreateDatabaseRegularStepReplicationAndSharding() {
</Row>

<Row>
<Col lg={{ offset: 2, size: 8 }}>
<Col lg={{ offset: 1, size: 10 }}>
<Row className="pt-2">
<Col sm="6" className="d-flex gap-1 align-items-center">
<Icon id="ReplicationInfo" icon="info" color="info" margin="m-0" /> Available nodes:{" "}
Expand Down Expand Up @@ -181,7 +181,7 @@ export default function CreateDatabaseRegularStepReplicationAndSharding() {
/>
</Collapse>
</Col>
<Col sm="auto">
<Col sm="6">
<Collapse isOpen={isSharded}>
<InputGroup>
<InputGroupText>Number of shards</InputGroupText>
Expand All @@ -194,6 +194,19 @@ export default function CreateDatabaseRegularStepReplicationAndSharding() {
max="100"
/>
</InputGroup>
<FormSwitch
control={control}
name="replicationAndShardingStep.isPrefixesForShards"
color="primary"
className="mt-3"
>
Add <strong>prefixes</strong> for shards
<br />
<small className="text-muted">
Manage document distribution by defining
<br />a prefix for document names
</small>
</FormSwitch>
</Collapse>
</Col>
</Row>
Expand Down Expand Up @@ -224,7 +237,7 @@ export default function CreateDatabaseRegularStepReplicationAndSharding() {
</Row>

<Row className="mt-4">
<Col>
<Col lg={{ offset: 1, size: 5 }}>
<ConditionalPopover
conditions={{
isActive: !hasDynamicNodesDistribution,
Expand All @@ -249,7 +262,7 @@ export default function CreateDatabaseRegularStepReplicationAndSharding() {
</FormSwitch>
</ConditionalPopover>
</Col>
<Col>
<Col lg={{ size: 5 }}>
<ConditionalPopover
conditions={[
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import React, { useEffect } from "react";
import { CreateDatabaseRegularFormData as FormData } from "../createDatabaseRegularValidation";
import { FieldArrayWithId, useFieldArray, useFormContext, useWatch } from "react-hook-form";
import { Alert, Button, Table, UncontrolledTooltip } from "reactstrap";
import { Icon } from "components/common/Icon";
import { FormInput } from "components/common/Form";
import { Checkbox } from "components/common/Checkbox";

export default function StepShardingPrefixes() {
const { control, trigger } = useFormContext<FormData>();

const {
shardingPrefixesStep: { prefixesForShards },
replicationAndShardingStep: { shardsCount },
} = useWatch({ control });

const { fields, append, remove, update } = useFieldArray({
control,
name: "shardingPrefixesStep.prefixesForShards",
});

const toggleShard = (index: number, shardNumber: number) => {
const field = prefixesForShards[index];

const updatedShardNumbers = field.shardNumbers.includes(shardNumber)
? field.shardNumbers.filter((x) => x !== shardNumber)
: [...field.shardNumbers, shardNumber];

update(index, {
prefix: field.prefix,
shardNumbers: updatedShardNumbers,
});
trigger(`shardingPrefixesStep.prefixesForShards.${index}.shardNumbers`);
};

const allShardNumbers = [...new Array(shardsCount).keys()];

return (
<div className="text-center">
<h2 className="text-center">Sharding Prefixes</h2>

<Table responsive>
<thead>
<tr>
<th></th>
{allShardNumbers.map((shardNumber) => (
<th key={shardNumber} className="px-0">
<Icon icon="shard" color="shard" />
{shardNumber}
</th>
))}
<th></th>
<th className="px-0"></th>
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<PrefixRow
key={field.id}
field={field}
index={index}
allShardNumbers={allShardNumbers}
toggleShard={toggleShard}
remove={remove}
/>
))}
</tbody>
</Table>

<Button
type="button"
color="shard"
outline
className="rounded-pill mt-2"
onClick={() => append({ prefix: "", shardNumbers: [] })}
>
<Icon icon="plus" />
Add prefix
</Button>

<div className="d-flex justify-content-center mt-3">
<Alert color="warning">
Sharding prefixes can be defined only when creating a database, and cannot be modified.
<br />
Make sure everything is set up correctly.
</Alert>
</div>
</div>
);
}

interface PrefixRowProps {
index: number;
field: FieldArrayWithId<FormData, "shardingPrefixesStep.prefixesForShards", "id">;
allShardNumbers: number[];
toggleShard: (index: number, shardNumber: number) => void;
remove: (index: number) => void;
}

function PrefixRow({ index, field, allShardNumbers, toggleShard, remove }: PrefixRowProps) {
const { control, formState, trigger } = useFormContext<FormData>();

const {
shardingPrefixesStep: { prefixesForShards },
} = useWatch({ control });

const prefix = prefixesForShards[index].prefix;

// Trigger validation for all fields when prefix changes (check for duplicates)
useEffect(() => {
if (!prefix) {
return;
}

trigger("shardingPrefixesStep.prefixesForShards");
}, [prefix, trigger]);

const shardsError = formState.errors.shardingPrefixesStep?.prefixesForShards?.[index]?.shardNumbers?.message;

return (
<tr>
<td>
<FormInput
type="text"
control={control}
name={`shardingPrefixesStep.prefixesForShards.${index}.prefix`}
className="form-control"
placeholder="Prefix"
style={{ minWidth: "100px", maxWidth: "300px" }}
/>
</td>
{allShardNumbers.map((shardNumber) => (
<td key={shardNumber} className="px-0 align-middle">
<Checkbox
size="lg"
selected={field.shardNumbers.includes(shardNumber)}
toggleSelection={() => toggleShard(index, shardNumber)}
/>
</td>
))}
<td className="px-0 align-middle">
{index !== 0 && (
<Button type="button" color="danger" outline onClick={() => remove(index)}>
<Icon icon="trash" margin="m-0" />
</Button>
)}
</td>
<td id={"prefixShardsError" + index} className="px-0 align-middle">
{shardsError && (
<>
<Icon icon="warning" color="danger" margin="m-0" />
<UncontrolledTooltip target={"prefixShardsError" + index} placement="left">
{shardsError}
</UncontrolledTooltip>
</>
)}
</td>
</tr>
);
}

0 comments on commit e00f762

Please sign in to comment.