Merge pull request #20 from provectus/feature/14-add-custom-params-for-topics-creation
Feature/14 add custom params for topics creation
This commit is contained in:
commit
ea9426e8dd
11 changed files with 429 additions and 6 deletions
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import CustomParamButton, { CustomParamButtonType } from './CustomParamButton';
|
||||
import { isFirstParam } from './CustomParams';
|
||||
|
||||
interface Props {
|
||||
index: string;
|
||||
onAdd: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onRemove: (index: string) => void;
|
||||
}
|
||||
|
||||
const CustomParamAction: React.FC<Props> = ({
|
||||
index,
|
||||
onAdd,
|
||||
onRemove,
|
||||
}) => (
|
||||
<>
|
||||
<label className='label'> </label>
|
||||
{
|
||||
isFirstParam(index)
|
||||
? <CustomParamButton className="is-success" type={CustomParamButtonType.plus} onClick={onAdd} />
|
||||
: <CustomParamButton className="is-danger" type={CustomParamButtonType.minus} onClick={() => onRemove(index)} />
|
||||
}
|
||||
</>
|
||||
)
|
||||
|
||||
export default CustomParamAction;
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
|
||||
export enum CustomParamButtonType {
|
||||
plus = 'fa-plus',
|
||||
minus = 'fa-minus',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void,
|
||||
className: string;
|
||||
type: CustomParamButtonType;
|
||||
}
|
||||
|
||||
const CustomParamButton: React.FC<Props> = ({
|
||||
onClick,
|
||||
className,
|
||||
type,
|
||||
}) => (
|
||||
<button className={`button ${className} is-outlined`} onClick={onClick}>
|
||||
<span className="icon">
|
||||
<i className={`fas fa-lg ${type}`}></i>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
export default CustomParamButton;
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import { TopicCustomParamOption } from 'redux/interfaces';
|
||||
import { CUSTOM_PARAMS_OPTIONS } from './customParamsOptions';
|
||||
|
||||
interface Props {};
|
||||
|
||||
const CustomParamOptions: React.FC<Props> = () => (
|
||||
<>
|
||||
<option value=''>Select</option>
|
||||
{
|
||||
Object.values(CUSTOM_PARAMS_OPTIONS).map((opt: TopicCustomParamOption) => (
|
||||
<option key={opt.name} value={opt.name}>
|
||||
{opt.name}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
export default React.memo(CustomParamOptions);
|
|
@ -0,0 +1,67 @@
|
|||
import React from 'react';
|
||||
import { useFormContext, ErrorMessage } from 'react-hook-form';
|
||||
import CustomParamOptions from './CustomParamOptions';
|
||||
import { isFirstParam, INDEX_PREFIX } from './CustomParams';
|
||||
import { TopicFormCustomParam } from 'redux/interfaces';
|
||||
|
||||
interface Props {
|
||||
isDisabled: boolean;
|
||||
index: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const CustomParamSelect: React.FC<Props> = ({
|
||||
isDisabled,
|
||||
index,
|
||||
name,
|
||||
}) => {
|
||||
|
||||
const { register, unregister, errors, getValues, triggerValidation } = useFormContext();
|
||||
const optInputName = `${index}[name]`;
|
||||
|
||||
React.useEffect(
|
||||
() => { if (isFirstParam(index)) { unregister(optInputName) } },
|
||||
);
|
||||
|
||||
const selectedMustBeUniq = (selected: string) => {
|
||||
const values = getValues({ nest: true });
|
||||
const customParamsValues: TopicFormCustomParam = values.customParams;
|
||||
|
||||
let valid = true;
|
||||
|
||||
for (const [key, customParam] of Object.entries(customParamsValues)) {
|
||||
if (`${INDEX_PREFIX}.${key}` === index) { continue; }
|
||||
if (selected === customParam.name) {
|
||||
valid = false;
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
return valid ? true : 'Custom Parameter must be unique';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className="label">Custom Parameter</label>
|
||||
<div className="select is-block">
|
||||
<select
|
||||
name={optInputName}
|
||||
ref={register({
|
||||
required: 'Custom Parameter is required.',
|
||||
validate: { unique: selected => selectedMustBeUniq(selected) },
|
||||
})}
|
||||
onChange={() => triggerValidation(optInputName)}
|
||||
disabled={isDisabled}
|
||||
defaultValue={name}
|
||||
>
|
||||
<CustomParamOptions />
|
||||
</select>
|
||||
<p className="help is-danger">
|
||||
<ErrorMessage errors={errors} name={optInputName}/>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CustomParamSelect);
|
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import { useFormContext, ErrorMessage } from 'react-hook-form';
|
||||
import { CUSTOM_PARAMS_OPTIONS } from './customParamsOptions';
|
||||
import { isFirstParam } from './CustomParams';
|
||||
|
||||
interface Props {
|
||||
isDisabled: boolean;
|
||||
index: string;
|
||||
name: string;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
const CustomParamValue: React.FC<Props> = ({
|
||||
isDisabled,
|
||||
index,
|
||||
name,
|
||||
defaultValue,
|
||||
}) => {
|
||||
|
||||
const { register, unregister, errors, watch, setValue } = useFormContext();
|
||||
const selectInputName: string = `${index}[name]`;
|
||||
const valInputName: string = `${index}[value]`;
|
||||
const selectedParamName = watch(selectInputName, name);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (selectedParamName) {
|
||||
setValue(valInputName, CUSTOM_PARAMS_OPTIONS[selectedParamName].defaultValue, true);
|
||||
}
|
||||
},
|
||||
[selectedParamName],
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() => { if (isFirstParam(index)) { unregister(valInputName) } },
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className="label">Value</label>
|
||||
<input
|
||||
className="input"
|
||||
placeholder="Value"
|
||||
ref={register({
|
||||
required: 'Value is required.',
|
||||
})}
|
||||
name={valInputName}
|
||||
defaultValue={defaultValue}
|
||||
autoComplete="off"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="help is-danger">
|
||||
<ErrorMessage errors={errors} name={valInputName}/>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CustomParamValue);
|
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import { omit, reject } from 'lodash';
|
||||
|
||||
import { TopicFormCustomParams } from 'redux/interfaces';
|
||||
import CustomParamSelect from './CustomParamSelect';
|
||||
import CustomParamValue from './CustomParamValue';
|
||||
import CustomParamAction from './CustomParamAction';
|
||||
|
||||
const DEFAULT_INDEX = 'default';
|
||||
export const INDEX_PREFIX = 'customParams';
|
||||
export const isFirstParam = (index: string) => (index === DEFAULT_INDEX);
|
||||
|
||||
interface Props {
|
||||
isSubmitting: boolean;
|
||||
}
|
||||
|
||||
const CustomParams: React.FC<Props> = ({
|
||||
isSubmitting,
|
||||
}) => {
|
||||
|
||||
const [formCustomParams, setFormCustomParams] = React.useState<TopicFormCustomParams>({
|
||||
byIndex: { [DEFAULT_INDEX]: { name: '', value: '' } },
|
||||
allIndexes: [DEFAULT_INDEX],
|
||||
});
|
||||
|
||||
const onAdd = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const newIndex = `${INDEX_PREFIX}.${new Date().getTime()}`;
|
||||
|
||||
setFormCustomParams({
|
||||
...formCustomParams,
|
||||
byIndex: {
|
||||
...formCustomParams.byIndex,
|
||||
[newIndex]: { name: '', value: '' },
|
||||
},
|
||||
allIndexes: [
|
||||
formCustomParams.allIndexes[0],
|
||||
newIndex,
|
||||
...formCustomParams.allIndexes.slice(1),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const onRemove = (index: string) => {
|
||||
setFormCustomParams({
|
||||
...formCustomParams,
|
||||
byIndex: omit(formCustomParams.byIndex, index),
|
||||
allIndexes: reject(formCustomParams.allIndexes, (i) => (i === index)),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
formCustomParams.allIndexes.map((index) => (
|
||||
<div className="columns is-centered" key={index}>
|
||||
<div className="column">
|
||||
<CustomParamSelect
|
||||
index={index}
|
||||
isDisabled={isFirstParam(index) || isSubmitting}
|
||||
name={formCustomParams.byIndex[index].name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="column">
|
||||
<CustomParamValue
|
||||
index={index}
|
||||
isDisabled={isFirstParam(index) || isSubmitting}
|
||||
name={formCustomParams.byIndex[index].name}
|
||||
defaultValue={formCustomParams.byIndex[index].value}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="column is-narrow">
|
||||
<CustomParamAction
|
||||
index={index}
|
||||
onAdd={onAdd}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomParams;
|
|
@ -0,0 +1,18 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import CustomParams from './CustomParams';
|
||||
|
||||
interface RouteProps {};
|
||||
|
||||
interface OwnProps extends RouteComponentProps<RouteProps> {
|
||||
isSubmitting: boolean;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: RootState, { isSubmitting }: OwnProps) => ({
|
||||
isSubmitting,
|
||||
})
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps)(CustomParams)
|
||||
);
|
|
@ -0,0 +1,96 @@
|
|||
import { TopicCustomParamOption } from 'redux/interfaces';
|
||||
|
||||
interface CustomParamOption {
|
||||
[optionName: string]: TopicCustomParamOption;
|
||||
}
|
||||
|
||||
export const CUSTOM_PARAMS_OPTIONS: CustomParamOption = {
|
||||
"compression.type": {
|
||||
"name": "compression.type",
|
||||
"defaultValue": "producer"
|
||||
},
|
||||
"leader.replication.throttled.replicas": {
|
||||
"name": "leader.replication.throttled.replicas",
|
||||
"defaultValue": ""
|
||||
},
|
||||
"message.downconversion.enable": {
|
||||
"name": "message.downconversion.enable",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
"segment.jitter.ms": {
|
||||
"name": "segment.jitter.ms",
|
||||
"defaultValue": "0"
|
||||
},
|
||||
"flush.ms": {
|
||||
"name": "flush.ms",
|
||||
"defaultValue": "9223372036854775807"
|
||||
},
|
||||
"follower.replication.throttled.replicas": {
|
||||
"name": "follower.replication.throttled.replicas",
|
||||
"defaultValue": ""
|
||||
},
|
||||
"segment.bytes": {
|
||||
"name": "segment.bytes",
|
||||
"defaultValue": "1073741824"
|
||||
},
|
||||
"flush.messages": {
|
||||
"name": "flush.messages",
|
||||
"defaultValue": "9223372036854775807"
|
||||
},
|
||||
"message.format.version": {
|
||||
"name": "message.format.version",
|
||||
"defaultValue": "2.3-IV1"
|
||||
},
|
||||
"file.delete.delay.ms": {
|
||||
"name": "file.delete.delay.ms",
|
||||
"defaultValue": "60000"
|
||||
},
|
||||
"max.compaction.lag.ms": {
|
||||
"name": "max.compaction.lag.ms",
|
||||
"defaultValue": "9223372036854775807"
|
||||
},
|
||||
"min.compaction.lag.ms": {
|
||||
"name": "min.compaction.lag.ms",
|
||||
"defaultValue": "0"
|
||||
},
|
||||
"message.timestamp.type": {
|
||||
"name": "message.timestamp.type",
|
||||
"defaultValue": "CreateTime"
|
||||
},
|
||||
"preallocate": {
|
||||
"name": "preallocate",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"min.cleanable.dirty.ratio": {
|
||||
"name": "min.cleanable.dirty.ratio",
|
||||
"defaultValue": "0.5"
|
||||
},
|
||||
"index.interval.bytes": {
|
||||
"name": "index.interval.bytes",
|
||||
"defaultValue": "4096"
|
||||
},
|
||||
"unclean.leader.election.enable": {
|
||||
"name": "unclean.leader.election.enable",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
"retention.bytes": {
|
||||
"name": "retention.bytes",
|
||||
"defaultValue": "-1"
|
||||
},
|
||||
"delete.retention.ms": {
|
||||
"name": "delete.retention.ms",
|
||||
"defaultValue": "86400000"
|
||||
},
|
||||
"segment.ms": {
|
||||
"name": "segment.ms",
|
||||
"defaultValue": "604800000"
|
||||
},
|
||||
"message.timestamp.difference.max.ms": {
|
||||
"name": "message.timestamp.difference.max.ms",
|
||||
"defaultValue": "9223372036854775807"
|
||||
},
|
||||
"segment.index.bytes": {
|
||||
"name": "segment.index.bytes",
|
||||
"defaultValue": "10485760"
|
||||
}
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import { ClusterName, CleanupPolicy, TopicFormData, TopicName } from 'redux/interfaces';
|
||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||
import { clusterTopicsPath } from 'lib/paths';
|
||||
import { useForm, FormContext, ErrorMessage } from 'react-hook-form';
|
||||
|
||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||
import CustomParamsContainer from "./CustomParams/CustomParamsContainer";
|
||||
import TimeToRetain from './TimeToRetain';
|
||||
import { clusterTopicsPath } from 'lib/paths';
|
||||
import {
|
||||
TOPIC_NAME_VALIDATION_PATTERN,
|
||||
BYTES_IN_GB,
|
||||
} from 'lib/constants';
|
||||
import TimeToRetain from './TimeToRetain';
|
||||
|
||||
|
||||
interface Props {
|
||||
clusterName: ClusterName;
|
||||
|
@ -219,6 +221,8 @@ const New: React.FC<Props> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<CustomParamsContainer isSubmitting={isSubmitting} />
|
||||
|
||||
<input type="submit" className="button is-primary" disabled={isSubmitting}/>
|
||||
</form>
|
||||
</FormContext>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createAsyncAction} from 'typesafe-actions';
|
||||
import { createAsyncAction } from 'typesafe-actions';
|
||||
import { ActionType } from 'redux/actionType';
|
||||
import { ConsumerGroup } from '../interfaces/consumerGroup';
|
||||
import {
|
||||
|
|
|
@ -23,6 +23,11 @@ export interface TopicPartition {
|
|||
replicas: TopicReplica[];
|
||||
}
|
||||
|
||||
export interface TopicCustomParamOption {
|
||||
name: string;
|
||||
defaultValue: string;
|
||||
}
|
||||
|
||||
export interface TopicDetails {
|
||||
partitionCount?: number;
|
||||
replicationFactor?: number;
|
||||
|
@ -39,13 +44,23 @@ export interface Topic {
|
|||
partitions: TopicPartition[];
|
||||
}
|
||||
|
||||
export interface TopicFormCustomParam {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface TopicFormCustomParams {
|
||||
byIndex: { [paramIndex: string]: TopicFormCustomParam };
|
||||
allIndexes: string[];
|
||||
}
|
||||
|
||||
export interface TopicWithDetailedInfo extends Topic, TopicDetails {
|
||||
config?: TopicConfig[];
|
||||
}
|
||||
|
||||
export interface TopicsState {
|
||||
byName: { [topicName: string]: TopicWithDetailedInfo },
|
||||
allNames: TopicName[],
|
||||
byName: { [topicName: string]: TopicWithDetailedInfo };
|
||||
allNames: TopicName[];
|
||||
}
|
||||
|
||||
export interface TopicFormData {
|
||||
|
@ -57,4 +72,7 @@ export interface TopicFormData {
|
|||
retentionMs: number;
|
||||
retentionBytes: number;
|
||||
maxMessageBytes: number;
|
||||
customParams: {
|
||||
[index: string]: TopicFormCustomParam;
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue