From 82d81dd847214de41d7cc822b50f3cb224a0647d Mon Sep 17 00:00:00 2001 From: Oleg Shuralev Date: Fri, 17 Jan 2020 23:53:46 +0300 Subject: [PATCH] [UI] Create Topic action --- frontend/src/components/Topics/New/New.tsx | 45 +++++++++++++++---- .../src/components/Topics/New/NewContainer.ts | 18 +++++++- frontend/src/lib/api/topics.ts | 31 +++++++++++++ .../src/redux/reducers/topics/actionType.ts | 4 ++ frontend/src/redux/reducers/topics/actions.ts | 6 +++ .../src/redux/reducers/topics/selectors.ts | 6 +++ frontend/src/redux/reducers/topics/thunks.ts | 15 ++++++- 7 files changed, 114 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Topics/New/New.tsx b/frontend/src/components/Topics/New/New.tsx index c89bd8f031..2941061359 100644 --- a/frontend/src/components/Topics/New/New.tsx +++ b/frontend/src/components/Topics/New/New.tsx @@ -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 = ({ clusterId, + isTopicCreated, + createTopic, + redirectToTopicPath, }) => { - const { register, handleSubmit, errors } = useForm(); // initialise the hook + const { register, handleSubmit, errors, getValues } = useForm(); + const [isSubmitting, setIsSubmitting] = React.useState(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 (
@@ -37,7 +55,6 @@ const New: React.FC = ({
-
- +
diff --git a/frontend/src/components/Topics/New/NewContainer.ts b/frontend/src/components/Topics/New/NewContainer.ts index 9dadbc0ee3..8c247b3a93 100644 --- a/frontend/src/components/Topics/New/NewContainer.ts +++ b/frontend/src/components/Topics/New/NewContainer.ts @@ -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 { } const mapStateToProps = (state: RootState, { match: { params: { clusterId } } }: OwnProps) => ({ clusterId, + isTopicCreated: getTopicCreated(state), +}); + +const mapDispatchToProps = (dispatch: ThunkDispatch, { 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) ); diff --git a/frontend/src/lib/api/topics.ts b/frontend/src/lib/api/topics.ts index 2cd0ac4cd5..b5bc4901d4 100644 --- a/frontend/src/lib/api/topics.ts +++ b/frontend/src/lib/api/topics.ts @@ -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 => fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS }) .then(res => res.json()); + +export const postTopic = (clusterId: ClusterId, form: TopicFormData): Promise => { + 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, + }); +} diff --git a/frontend/src/redux/reducers/topics/actionType.ts b/frontend/src/redux/reducers/topics/actionType.ts index dff0cf167b..29250575c2 100644 --- a/frontend/src/redux/reducers/topics/actionType.ts +++ b/frontend/src/redux/reducers/topics/actionType.ts @@ -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; diff --git a/frontend/src/redux/reducers/topics/actions.ts b/frontend/src/redux/reducers/topics/actions.ts index 82f689ba84..8bbd8c374d 100644 --- a/frontend/src/redux/reducers/topics/actions.ts +++ b/frontend/src/redux/reducers/topics/actions.ts @@ -19,3 +19,9 @@ export const fetchTopicConfigAction = createAsyncAction( ActionType.GET_TOPIC_CONFIG__SUCCESS, ActionType.GET_TOPIC_CONFIG__FAILURE, )(); + +export const createTopicAction = createAsyncAction( + ActionType.POST_TOPIC__REQUEST, + ActionType.POST_TOPIC__SUCCESS, + ActionType.POST_TOPIC__FAILURE, +)(); diff --git a/frontend/src/redux/reducers/topics/selectors.ts b/frontend/src/redux/reducers/topics/selectors.ts index 815c59bd2c..bab0c3a75d 100644 --- a/frontend/src/redux/reducers/topics/selectors.ts +++ b/frontend/src/redux/reducers/topics/selectors.ts @@ -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, diff --git a/frontend/src/redux/reducers/topics/thunks.ts b/frontend/src/redux/reducers/topics/thunks.ts index 9564a8f821..d57472a16d 100644 --- a/frontend/src/redux/reducers/topics/thunks.ts +++ b/frontend/src/redux/reducers/topics/thunks.ts @@ -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 => 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 => async (dispatch) => { + dispatch(createTopicAction.request()); + + try { + await postTopic(clusterId, form); + dispatch(createTopicAction.success()); + } catch (e) { + dispatch(createTopicAction.failure()); + } +}