[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 React from 'react';
|
||||||
import { ClusterId, CleanupPolicy, TopicFormData } from 'types';
|
import { ClusterId, CleanupPolicy, TopicFormData, TopicName } from 'types';
|
||||||
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, ErrorMessage } from 'react-hook-form';
|
||||||
|
@ -11,16 +11,34 @@ import {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
clusterId: ClusterId;
|
clusterId: ClusterId;
|
||||||
|
isTopicCreated: boolean;
|
||||||
|
createTopic: (clusterId: ClusterId, form: TopicFormData) => void;
|
||||||
|
redirectToTopicPath: (clusterId: ClusterId, topicName: TopicName) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const New: React.FC<Props> = ({
|
const New: React.FC<Props> = ({
|
||||||
clusterId,
|
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) => {
|
React.useEffect(
|
||||||
console.log(data);
|
() => {
|
||||||
};
|
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 (
|
return (
|
||||||
<div className="section">
|
<div className="section">
|
||||||
|
@ -37,7 +55,6 @@ const New: React.FC<Props> = ({
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
|
|
||||||
<div className="column is-three-quarters">
|
<div className="column is-three-quarters">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
Topic Name *
|
Topic Name *
|
||||||
|
@ -54,6 +71,7 @@ const New: React.FC<Props> = ({
|
||||||
})}
|
})}
|
||||||
name="name"
|
name="name"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<p className="help is-danger">
|
<p className="help is-danger">
|
||||||
<ErrorMessage errors={errors} name="name" />
|
<ErrorMessage errors={errors} name="name" />
|
||||||
|
@ -71,6 +89,7 @@ const New: React.FC<Props> = ({
|
||||||
defaultValue="1"
|
defaultValue="1"
|
||||||
ref={register({ required: 'Number of partitions is required.' })}
|
ref={register({ required: 'Number of partitions is required.' })}
|
||||||
name="partitions"
|
name="partitions"
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<p className="help is-danger">
|
<p className="help is-danger">
|
||||||
<ErrorMessage errors={errors} name="partitions" />
|
<ErrorMessage errors={errors} name="partitions" />
|
||||||
|
@ -90,6 +109,7 @@ const New: React.FC<Props> = ({
|
||||||
defaultValue="1"
|
defaultValue="1"
|
||||||
ref={register({ required: 'Replication Factor is required.' })}
|
ref={register({ required: 'Replication Factor is required.' })}
|
||||||
name="replicationFactor"
|
name="replicationFactor"
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<p className="help is-danger">
|
<p className="help is-danger">
|
||||||
<ErrorMessage errors={errors} name="replicationFactor" />
|
<ErrorMessage errors={errors} name="replicationFactor" />
|
||||||
|
@ -107,6 +127,7 @@ const New: React.FC<Props> = ({
|
||||||
defaultValue="1"
|
defaultValue="1"
|
||||||
ref={register({ required: 'Min In Sync Replicas is required.' })}
|
ref={register({ required: 'Min In Sync Replicas is required.' })}
|
||||||
name="minInSyncReplicas"
|
name="minInSyncReplicas"
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<p className="help is-danger">
|
<p className="help is-danger">
|
||||||
<ErrorMessage errors={errors} name="minInSyncReplicas" />
|
<ErrorMessage errors={errors} name="minInSyncReplicas" />
|
||||||
|
@ -120,7 +141,12 @@ const New: React.FC<Props> = ({
|
||||||
Cleanup policy
|
Cleanup policy
|
||||||
</label>
|
</label>
|
||||||
<div className="select is-block">
|
<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}>
|
<option value={CleanupPolicy.Delete}>
|
||||||
Delete
|
Delete
|
||||||
</option>
|
</option>
|
||||||
|
@ -140,6 +166,7 @@ const New: React.FC<Props> = ({
|
||||||
defaultValue={MILLISECONDS_IN_DAY * 7}
|
defaultValue={MILLISECONDS_IN_DAY * 7}
|
||||||
name="retentionMs"
|
name="retentionMs"
|
||||||
ref={register}
|
ref={register}
|
||||||
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
<option value={MILLISECONDS_IN_DAY / 2 }>
|
<option value={MILLISECONDS_IN_DAY / 2 }>
|
||||||
12 hours
|
12 hours
|
||||||
|
@ -169,6 +196,7 @@ const New: React.FC<Props> = ({
|
||||||
defaultValue={-1}
|
defaultValue={-1}
|
||||||
name="retentionBytes"
|
name="retentionBytes"
|
||||||
ref={register}
|
ref={register}
|
||||||
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
<option value={-1}>
|
<option value={-1}>
|
||||||
Not Set
|
Not Set
|
||||||
|
@ -201,6 +229,7 @@ const New: React.FC<Props> = ({
|
||||||
defaultValue="1000012"
|
defaultValue="1000012"
|
||||||
ref={register({ required: 'Maximum message size in bytes is required' })}
|
ref={register({ required: 'Maximum message size in bytes is required' })}
|
||||||
name="maxMessageBytes"
|
name="maxMessageBytes"
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<p className="help is-danger">
|
<p className="help is-danger">
|
||||||
<ErrorMessage errors={errors} name="maxMessageBytes" />
|
<ErrorMessage errors={errors} name="maxMessageBytes" />
|
||||||
|
@ -208,7 +237,7 @@ const New: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="submit" className="button is-primary"/>
|
<input type="submit" className="button is-primary" disabled={isSubmitting} />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { RootState } from 'types';
|
import { RootState, ClusterId, TopicFormData, TopicName, Action } from 'types';
|
||||||
import New from './New';
|
import New from './New';
|
||||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
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 {
|
interface RouteProps {
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
|
@ -11,9 +15,19 @@ interface OwnProps extends RouteComponentProps<RouteProps> { }
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState, { match: { params: { clusterId } } }: OwnProps) => ({
|
const mapStateToProps = (state: RootState, { match: { params: { clusterId } } }: OwnProps) => ({
|
||||||
clusterId,
|
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(
|
export default withRouter(
|
||||||
connect(mapStateToProps)(New)
|
connect(mapStateToProps, mapDispatchToProps)(New)
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
ClusterId,
|
ClusterId,
|
||||||
TopicDetails,
|
TopicDetails,
|
||||||
TopicConfig,
|
TopicConfig,
|
||||||
|
TopicFormData,
|
||||||
} from 'types';
|
} from 'types';
|
||||||
import {
|
import {
|
||||||
BASE_URL,
|
BASE_URL,
|
||||||
|
@ -21,3 +22,33 @@ export const getTopicDetails = (clusterId: ClusterId, topicName: TopicName): Pro
|
||||||
export const getTopics = (clusterId: ClusterId): Promise<Topic[]> =>
|
export const getTopics = (clusterId: ClusterId): Promise<Topic[]> =>
|
||||||
fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS })
|
fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS })
|
||||||
.then(res => res.json());
|
.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__REQUEST = 'GET_TOPIC_CONFIG__REQUEST',
|
||||||
GET_TOPIC_CONFIG__SUCCESS = 'GET_TOPIC_CONFIG__SUCCESS',
|
GET_TOPIC_CONFIG__SUCCESS = 'GET_TOPIC_CONFIG__SUCCESS',
|
||||||
GET_TOPIC_CONFIG__FAILURE = 'GET_TOPIC_CONFIG__FAILURE',
|
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;
|
export default ActionType;
|
||||||
|
|
|
@ -19,3 +19,9 @@ export const fetchTopicConfigAction = createAsyncAction(
|
||||||
ActionType.GET_TOPIC_CONFIG__SUCCESS,
|
ActionType.GET_TOPIC_CONFIG__SUCCESS,
|
||||||
ActionType.GET_TOPIC_CONFIG__FAILURE,
|
ActionType.GET_TOPIC_CONFIG__FAILURE,
|
||||||
)<undefined, { topicName: TopicName, config: TopicConfig[] }, undefined>();
|
)<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 getTopicListFetchingStatus = createFetchingSelector('GET_TOPICS');
|
||||||
const getTopicDetailsFetchingStatus = createFetchingSelector('GET_TOPIC_DETAILS');
|
const getTopicDetailsFetchingStatus = createFetchingSelector('GET_TOPIC_DETAILS');
|
||||||
const getTopicConfigFetchingStatus = createFetchingSelector('GET_TOPIC_CONFIG');
|
const getTopicConfigFetchingStatus = createFetchingSelector('GET_TOPIC_CONFIG');
|
||||||
|
const getTopicCreationStatus = createFetchingSelector('POST_TOPIC');
|
||||||
|
|
||||||
export const getIsTopicListFetched = createSelector(
|
export const getIsTopicListFetched = createSelector(
|
||||||
getTopicListFetchingStatus,
|
getTopicListFetchingStatus,
|
||||||
|
@ -26,6 +27,11 @@ export const getTopicConfigFetched = createSelector(
|
||||||
(status) => status === FetchStatus.fetched,
|
(status) => status === FetchStatus.fetched,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getTopicCreated = createSelector(
|
||||||
|
getTopicCreationStatus,
|
||||||
|
(status) => status === FetchStatus.fetched,
|
||||||
|
);
|
||||||
|
|
||||||
export const getTopicList = createSelector(
|
export const getTopicList = createSelector(
|
||||||
getIsTopicListFetched,
|
getIsTopicListFetched,
|
||||||
getAllNames,
|
getAllNames,
|
||||||
|
|
|
@ -2,13 +2,15 @@ import {
|
||||||
getTopics,
|
getTopics,
|
||||||
getTopicDetails,
|
getTopicDetails,
|
||||||
getTopicConfig,
|
getTopicConfig,
|
||||||
|
postTopic,
|
||||||
} from 'lib/api';
|
} from 'lib/api';
|
||||||
import {
|
import {
|
||||||
fetchTopicListAction,
|
fetchTopicListAction,
|
||||||
fetchTopicDetailsAction,
|
fetchTopicDetailsAction,
|
||||||
fetchTopicConfigAction,
|
fetchTopicConfigAction,
|
||||||
|
createTopicAction,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { PromiseThunk, ClusterId, TopicName } from 'types';
|
import { PromiseThunk, ClusterId, TopicName, TopicFormData } from 'types';
|
||||||
|
|
||||||
export const fetchTopicList = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
|
export const fetchTopicList = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
|
||||||
dispatch(fetchTopicListAction.request());
|
dispatch(fetchTopicListAction.request());
|
||||||
|
@ -39,3 +41,14 @@ export const fetchTopicConfig = (clusterId: ClusterId, topicName: TopicName): Pr
|
||||||
dispatch(fetchTopicConfigAction.failure());
|
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