issue/21-topic-form-make-time-to-retain-customizable

This commit is contained in:
Azat Gataullin 2020-04-09 17:04:23 +03:00
parent c26edd1316
commit 3e174e8a44
5 changed files with 223 additions and 179 deletions

View file

@ -10478,6 +10478,11 @@
"json-parse-better-errors": "^1.0.1" "json-parse-better-errors": "^1.0.1"
} }
}, },
"parse-ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
"integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="
},
"parse5": { "parse5": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
@ -11686,6 +11691,14 @@
"react-is": "^16.8.4" "react-is": "^16.8.4"
} }
}, },
"pretty-ms": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-6.0.1.tgz",
"integrity": "sha512-ke4njoVmlotekHlHyCZ3wI/c5AMT8peuHs8rKJqekj/oR5G8lND2dVpicFlUz5cbZgE290vvkMuDwfj/OcW1kw==",
"requires": {
"parse-ms": "^2.1.0"
}
},
"private": { "private": {
"version": "0.1.8", "version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",

View file

@ -22,6 +22,7 @@
"json-server": "^0.15.1", "json-server": "^0.15.1",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
"pretty-ms": "^6.0.1",
"react": "^16.12.0", "react": "^16.12.0",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-hook-form": "^4.5.5", "react-hook-form": "^4.5.5",

View file

@ -2,12 +2,13 @@ import React from 'react';
import { ClusterName, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces'; import { ClusterName, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb'; import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import { clusterTopicsPath } from 'lib/paths'; import { clusterTopicsPath } from 'lib/paths';
import { useForm, ErrorMessage } from 'react-hook-form'; import { useForm, FormContext, ErrorMessage } from 'react-hook-form';
import { import {
TOPIC_NAME_VALIDATION_PATTERN, TOPIC_NAME_VALIDATION_PATTERN,
MILLISECONDS_IN_DAY,
BYTES_IN_GB, BYTES_IN_GB,
} from 'lib/constants'; } from 'lib/constants';
import TimeToRetain from './TimeToRetain';
interface Props { interface Props {
clusterName: ClusterName; clusterName: ClusterName;
@ -24,17 +25,17 @@ const New: React.FC<Props> = ({
redirectToTopicPath, redirectToTopicPath,
resetUploadedState resetUploadedState
}) => { }) => {
const {register, handleSubmit, errors, getValues} = useForm<TopicFormData>(); const methods = useForm<TopicFormData>();
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false); const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
React.useEffect( React.useEffect(
() => { () => {
if (isSubmitting && isTopicCreated) { if (isSubmitting && isTopicCreated) {
const {name} = getValues(); const {name} = methods.getValues();
redirectToTopicPath(clusterName, name); redirectToTopicPath(clusterName, name);
} }
}, },
[isSubmitting, isTopicCreated, redirectToTopicPath, clusterName, getValues], [isSubmitting, isTopicCreated, redirectToTopicPath, clusterName, methods.getValues],
); );
const onSubmit = async (data: TopicFormData) => { const onSubmit = async (data: TopicFormData) => {
@ -59,192 +60,168 @@ const New: React.FC<Props> = ({
</div> </div>
<div className="box"> <div className="box">
<form onSubmit={handleSubmit(onSubmit)}> <FormContext {...methods}>
<div className="columns"> <form onSubmit={methods.handleSubmit(onSubmit)}>
<div className="column is-three-quarters"> <div className="columns">
<label className="label"> <div className="column is-three-quarters">
Topic Name * <label className="label">
</label> Topic Name *
<input </label>
className="input" <input
placeholder="Topic Name" className="input"
ref={register({ placeholder="Topic Name"
required: 'Topic Name is required.', ref={methods.register({
pattern: { required: 'Topic Name is required.',
value: TOPIC_NAME_VALIDATION_PATTERN, pattern: {
message: 'Only alphanumeric, _, -, and . allowed', value: TOPIC_NAME_VALIDATION_PATTERN,
}, message: 'Only alphanumeric, _, -, and . allowed',
})} },
name="name" })}
autoComplete="off" name="name"
disabled={isSubmitting} autoComplete="off"
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="name"/>
</p>
</div>
<div className="column">
<label className="label">
Number of partitions *
</label>
<input
className="input"
type="number"
placeholder="Number of partitions"
defaultValue="1"
ref={register({required: 'Number of partitions is required.'})}
name="partitions"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="partitions"/>
</p>
</div>
</div>
<div className="columns">
<div className="column">
<label className="label">
Replication Factor *
</label>
<input
className="input"
type="number"
placeholder="Replication Factor"
defaultValue="1"
ref={register({required: 'Replication Factor is required.'})}
name="replicationFactor"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="replicationFactor"/>
</p>
</div>
<div className="column">
<label className="label">
Min In Sync Replicas *
</label>
<input
className="input"
type="number"
placeholder="Replication Factor"
defaultValue="1"
ref={register({required: 'Min In Sync Replicas is required.'})}
name="minInSyncReplicas"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="minInSyncReplicas"/>
</p>
</div>
</div>
<div className="columns">
<div className="column is-one-third">
<label className="label">
Cleanup policy
</label>
<div className="select is-block">
<select
defaultValue={CleanupPolicy.Delete}
name="cleanupPolicy"
ref={register}
disabled={isSubmitting} disabled={isSubmitting}
> />
<option value={CleanupPolicy.Delete}> <p className="help is-danger">
Delete <ErrorMessage errors={methods.errors} name="name"/>
</option> </p>
<option value={CleanupPolicy.Compact}> </div>
Compact
</option> <div className="column">
</select> <label className="label">
Number of partitions *
</label>
<input
className="input"
type="number"
placeholder="Number of partitions"
defaultValue="1"
ref={methods.register({required: 'Number of partitions is required.'})}
name="partitions"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={methods.errors} name="partitions"/>
</p>
</div> </div>
</div> </div>
<div className="column is-one-third"> <div className="columns">
<label className="label"> <div className="column">
Time to retain data <label className="label">
</label> Replication Factor *
<div className="select is-block"> </label>
<select <input
defaultValue={MILLISECONDS_IN_DAY * 7} className="input"
name="retentionMs" type="number"
ref={register} placeholder="Replication Factor"
defaultValue="1"
ref={methods.register({required: 'Replication Factor is required.'})}
name="replicationFactor"
disabled={isSubmitting} disabled={isSubmitting}
> />
<option value={MILLISECONDS_IN_DAY / 2}> <p className="help is-danger">
12 hours <ErrorMessage errors={methods.errors} name="replicationFactor"/>
</option> </p>
<option value={MILLISECONDS_IN_DAY}> </div>
1 day
</option> <div className="column">
<option value={MILLISECONDS_IN_DAY * 2}> <label className="label">
2 days Min In Sync Replicas *
</option> </label>
<option value={MILLISECONDS_IN_DAY * 7}> <input
1 week className="input"
</option> type="number"
<option value={MILLISECONDS_IN_DAY * 7 * 4}> placeholder="Replication Factor"
4 weeks defaultValue="1"
</option> ref={methods.register({required: 'Min In Sync Replicas is required.'})}
</select> name="minInSyncReplicas"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={methods.errors} name="minInSyncReplicas"/>
</p>
</div> </div>
</div> </div>
<div className="column is-one-third"> <div className="columns">
<label className="label"> <div className="column is-one-third">
Max size on disk in GB <label className="label">
</label> Cleanup policy
<div className="select is-block"> </label>
<select <div className="select is-block">
defaultValue={-1} <select
name="retentionBytes" defaultValue={CleanupPolicy.Delete}
ref={register} name="cleanupPolicy"
disabled={isSubmitting} ref={methods.register}
> disabled={isSubmitting}
<option value={-1}> >
Not Set <option value={CleanupPolicy.Delete}>
</option> Delete
<option value={BYTES_IN_GB}> </option>
1 GB <option value={CleanupPolicy.Compact}>
</option> Compact
<option value={BYTES_IN_GB * 10}> </option>
10 GB </select>
</option> </div>
<option value={BYTES_IN_GB * 20}> </div>
20 GB
</option> <div className="column is-one-third">
<option value={BYTES_IN_GB * 50}> <TimeToRetain isSubmitting={isSubmitting} />
50 GB </div>
</option>
</select> <div className="column is-one-third">
<label className="label">
Max size on disk in GB
</label>
<div className="select is-block">
<select
defaultValue={-1}
name="retentionBytes"
ref={methods.register}
disabled={isSubmitting}
>
<option value={-1}>
Not Set
</option>
<option value={BYTES_IN_GB}>
1 GB
</option>
<option value={BYTES_IN_GB * 10}>
10 GB
</option>
<option value={BYTES_IN_GB * 20}>
20 GB
</option>
<option value={BYTES_IN_GB * 50}>
50 GB
</option>
</select>
</div>
</div> </div>
</div> </div>
</div>
<div className="columns"> <div className="columns">
<div className="column"> <div className="column">
<label className="label"> <label className="label">
Maximum message size in bytes * Maximum message size in bytes *
</label> </label>
<input <input
className="input" className="input"
type="number" type="number"
defaultValue="1000012" defaultValue="1000012"
ref={register({required: 'Maximum message size in bytes is required'})} ref={methods.register({required: 'Maximum message size in bytes is required'})}
name="maxMessageBytes" name="maxMessageBytes"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<p className="help is-danger"> <p className="help is-danger">
<ErrorMessage errors={errors} name="maxMessageBytes"/> <ErrorMessage errors={methods.errors} name="maxMessageBytes"/>
</p> </p>
</div>
</div> </div>
</div>
<input type="submit" className="button is-primary" disabled={isSubmitting}/> <input type="submit" className="button is-primary" disabled={isSubmitting}/>
</form> </form>
</FormContext>
</div> </div>
</div> </div>
); );

View file

@ -0,0 +1,53 @@
import React from 'react';
import prettyMilliseconds from 'pretty-ms';
import { useFormContext, ErrorMessage } from 'react-hook-form';
import { MILLISECONDS_IN_WEEK } from 'lib/constants';
const MILLISECONDS_IN_SECOND = 1000;
interface Props {
isSubmitting: boolean;
}
const TimeToRetain: React.FC<Props> = ({
isSubmitting,
}) => {
const { register, errors, watch } = useFormContext();
const defaultValue = MILLISECONDS_IN_WEEK;
const name: string = 'retentionMs';
const watchedValue: any = watch(name, defaultValue.toString());
const valueHint = React.useMemo(() => {
const value = parseInt(watchedValue, 10);
return value >= MILLISECONDS_IN_SECOND ? prettyMilliseconds(value) : false;
}, [watchedValue])
return (
<>
<label className="label">
Time to retain data (in ms)
</label>
<input
className="input"
type="number"
defaultValue={defaultValue}
name={name}
ref={register(
{ min: { value: -1, message: 'must be greater than or equal to -1' }}
)}
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name={name}/>
</p>
{
valueHint &&
<p className="help is-info">
{valueHint}
</p>
}
</>
);
}
export default TimeToRetain;

View file

@ -10,6 +10,6 @@ export const BASE_URL = process.env.REACT_APP_API_URL;
export const TOPIC_NAME_VALIDATION_PATTERN = RegExp(/^[.,A-Za-z0-9_-]+$/); export const TOPIC_NAME_VALIDATION_PATTERN = RegExp(/^[.,A-Za-z0-9_-]+$/);
export const MILLISECONDS_IN_DAY = 86_400_000; export const MILLISECONDS_IN_WEEK = 604_800_000;
export const BYTES_IN_GB = 1_073_741_824; export const BYTES_IN_GB = 1_073_741_824;