[UI] Create Topic action

This commit is contained in:
Oleg Shuralev 2020-01-17 23:53:46 +03:00
parent ee92f4482f
commit 82d81dd847
No known key found for this signature in database
GPG key ID: 0459DF80E1A2FD1B
7 changed files with 114 additions and 11 deletions

View file

@ -1,5 +1,5 @@
import React from 'react';
import { ClusterId, CleanupPolicy, TopicFormData } from 'types';
import { ClusterId, CleanupPolicy, TopicFormData, TopicName } from 'types';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import { clusterTopicsPath } from 'lib/paths';
import { useForm, ErrorMessage } from 'react-hook-form';
@ -11,16 +11,34 @@ import {
interface Props {
clusterId: ClusterId;
isTopicCreated: boolean;
createTopic: (clusterId: ClusterId, form: TopicFormData) => void;
redirectToTopicPath: (clusterId: ClusterId, topicName: TopicName) => void;
}
const New: React.FC<Props> = ({
clusterId,
isTopicCreated,
createTopic,
redirectToTopicPath,
}) => {
const { register, handleSubmit, errors } = useForm<TopicFormData>(); // initialise the hook
const { register, handleSubmit, errors, getValues } = useForm<TopicFormData>();
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
const onSubmit = (data: TopicFormData) => {
console.log(data);
};
React.useEffect(
() => {
if (isSubmitting && isTopicCreated) {
const { name } = getValues();
redirectToTopicPath(clusterId, name);
}
},
[isSubmitting, isTopicCreated, redirectToTopicPath, clusterId, getValues],
);
const onSubmit = async (data: TopicFormData) => {
setIsSubmitting(true);
createTopic(clusterId, data);
}
return (
<div className="section">
@ -37,7 +55,6 @@ const New: React.FC<Props> = ({
<div className="box">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="columns">
<div className="column is-three-quarters">
<label className="label">
Topic Name *
@ -54,6 +71,7 @@ const New: React.FC<Props> = ({
})}
name="name"
autoComplete="off"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="name" />
@ -71,6 +89,7 @@ const New: React.FC<Props> = ({
defaultValue="1"
ref={register({ required: 'Number of partitions is required.' })}
name="partitions"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="partitions" />
@ -90,6 +109,7 @@ const New: React.FC<Props> = ({
defaultValue="1"
ref={register({ required: 'Replication Factor is required.' })}
name="replicationFactor"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="replicationFactor" />
@ -107,6 +127,7 @@ const New: React.FC<Props> = ({
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" />
@ -120,7 +141,12 @@ const New: React.FC<Props> = ({
Cleanup policy
</label>
<div className="select is-block">
<select defaultValue={CleanupPolicy.Delete} name="cleanupPolicy">
<select
defaultValue={CleanupPolicy.Delete}
name="cleanupPolicy"
ref={register}
disabled={isSubmitting}
>
<option value={CleanupPolicy.Delete}>
Delete
</option>
@ -140,6 +166,7 @@ const New: React.FC<Props> = ({
defaultValue={MILLISECONDS_IN_DAY * 7}
name="retentionMs"
ref={register}
disabled={isSubmitting}
>
<option value={MILLISECONDS_IN_DAY / 2 }>
12 hours
@ -169,6 +196,7 @@ const New: React.FC<Props> = ({
defaultValue={-1}
name="retentionBytes"
ref={register}
disabled={isSubmitting}
>
<option value={-1}>
Not Set
@ -201,6 +229,7 @@ const New: React.FC<Props> = ({
defaultValue="1000012"
ref={register({ required: 'Maximum message size in bytes is required' })}
name="maxMessageBytes"
disabled={isSubmitting}
/>
<p className="help is-danger">
<ErrorMessage errors={errors} name="maxMessageBytes" />
@ -208,7 +237,7 @@ const New: React.FC<Props> = ({
</div>
</div>
<input type="submit" className="button is-primary"/>
<input type="submit" className="button is-primary" disabled={isSubmitting} />
</form>
</div>
</div>

View file

@ -1,7 +1,11 @@
import { connect } from 'react-redux';
import { RootState } from 'types';
import { RootState, ClusterId, TopicFormData, TopicName, Action } from 'types';
import New from './New';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { createTopic } from 'redux/reducers/topics/thunks';
import { getTopicCreated } from 'redux/reducers/topics/selectors';
import { clusterTopicPath } from 'lib/paths';
import { ThunkDispatch } from 'redux-thunk';
interface RouteProps {
clusterId: string;
@ -11,9 +15,19 @@ interface OwnProps extends RouteComponentProps<RouteProps> { }
const mapStateToProps = (state: RootState, { match: { params: { clusterId } } }: OwnProps) => ({
clusterId,
isTopicCreated: getTopicCreated(state),
});
const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, undefined, Action>, { history }: OwnProps) => ({
createTopic: (clusterId: ClusterId, form: TopicFormData) => {
dispatch(createTopic(clusterId, form))
},
redirectToTopicPath: (clusterId: ClusterId, topicName: TopicName) => {
history.push(clusterTopicPath(clusterId, topicName));
}
});
export default withRouter(
connect(mapStateToProps)(New)
connect(mapStateToProps, mapDispatchToProps)(New)
);

View file

@ -4,6 +4,7 @@ import {
ClusterId,
TopicDetails,
TopicConfig,
TopicFormData,
} from 'types';
import {
BASE_URL,
@ -21,3 +22,33 @@ export const getTopicDetails = (clusterId: ClusterId, topicName: TopicName): Pro
export const getTopics = (clusterId: ClusterId): Promise<Topic[]> =>
fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS })
.then(res => res.json());
export const postTopic = (clusterId: ClusterId, form: TopicFormData): Promise<Response> => {
const {
name,
partitions,
replicationFactor,
cleanupPolicy,
retentionBytes,
retentionMs,
maxMessageBytes,
minInSyncReplicas,
} = form;
const body = JSON.stringify({
name,
partitions,
replicationFactor,
configs: {
'cleanup.policy': cleanupPolicy,
'retention.ms': retentionMs,
'retention.bytes': retentionBytes,
'max.message.bytes': maxMessageBytes,
'min.insync.replicas': minInSyncReplicas,
}
});
return fetch(`${BASE_URL}/clusters/${clusterId}/topics`, {
...BASE_PARAMS,
method: 'POST',
body,
});
}

View file

@ -10,6 +10,10 @@ enum ActionType {
GET_TOPIC_CONFIG__REQUEST = 'GET_TOPIC_CONFIG__REQUEST',
GET_TOPIC_CONFIG__SUCCESS = 'GET_TOPIC_CONFIG__SUCCESS',
GET_TOPIC_CONFIG__FAILURE = 'GET_TOPIC_CONFIG__FAILURE',
POST_TOPIC__REQUEST = 'POST_TOPIC__REQUEST',
POST_TOPIC__SUCCESS = 'POST_TOPIC__SUCCESS',
POST_TOPIC__FAILURE = 'POST_TOPIC__FAILURE',
}
export default ActionType;

View file

@ -19,3 +19,9 @@ export const fetchTopicConfigAction = createAsyncAction(
ActionType.GET_TOPIC_CONFIG__SUCCESS,
ActionType.GET_TOPIC_CONFIG__FAILURE,
)<undefined, { topicName: TopicName, config: TopicConfig[] }, undefined>();
export const createTopicAction = createAsyncAction(
ActionType.POST_TOPIC__REQUEST,
ActionType.POST_TOPIC__SUCCESS,
ActionType.POST_TOPIC__FAILURE,
)<undefined, undefined, undefined>();

View file

@ -10,6 +10,7 @@ const getTopicMap = (state: RootState) => topicsState(state).byName;
const getTopicListFetchingStatus = createFetchingSelector('GET_TOPICS');
const getTopicDetailsFetchingStatus = createFetchingSelector('GET_TOPIC_DETAILS');
const getTopicConfigFetchingStatus = createFetchingSelector('GET_TOPIC_CONFIG');
const getTopicCreationStatus = createFetchingSelector('POST_TOPIC');
export const getIsTopicListFetched = createSelector(
getTopicListFetchingStatus,
@ -26,6 +27,11 @@ export const getTopicConfigFetched = createSelector(
(status) => status === FetchStatus.fetched,
);
export const getTopicCreated = createSelector(
getTopicCreationStatus,
(status) => status === FetchStatus.fetched,
);
export const getTopicList = createSelector(
getIsTopicListFetched,
getAllNames,

View file

@ -2,13 +2,15 @@ import {
getTopics,
getTopicDetails,
getTopicConfig,
postTopic,
} from 'lib/api';
import {
fetchTopicListAction,
fetchTopicDetailsAction,
fetchTopicConfigAction,
createTopicAction,
} from './actions';
import { PromiseThunk, ClusterId, TopicName } from 'types';
import { PromiseThunk, ClusterId, TopicName, TopicFormData } from 'types';
export const fetchTopicList = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
dispatch(fetchTopicListAction.request());
@ -39,3 +41,14 @@ export const fetchTopicConfig = (clusterId: ClusterId, topicName: TopicName): Pr
dispatch(fetchTopicConfigAction.failure());
}
}
export const createTopic = (clusterId: ClusterId, form: TopicFormData): PromiseThunk<void> => async (dispatch) => {
dispatch(createTopicAction.request());
try {
await postTopic(clusterId, form);
dispatch(createTopicAction.success());
} catch (e) {
dispatch(createTopicAction.failure());
}
}