[UI] Create Topic action
This commit is contained in:
parent
ee92f4482f
commit
82d81dd847
7 changed files with 114 additions and 11 deletions
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue