SendMessage.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import React from 'react';
  2. import { useForm, Controller } from 'react-hook-form';
  3. import { RouteParamsClusterTopic } from 'lib/paths';
  4. import { Button } from 'components/common/Button/Button';
  5. import Editor from 'components/common/Editor/Editor';
  6. import Select, { SelectOption } from 'components/common/Select/Select';
  7. import Switch from 'components/common/Switch/Switch';
  8. import useAppParams from 'lib/hooks/useAppParams';
  9. import { showAlert } from 'lib/errorHandling';
  10. import { useSendMessage, useTopicDetails } from 'lib/hooks/api/topics';
  11. import { InputLabel } from 'components/common/Input/InputLabel.styled';
  12. import { useSerdes } from 'lib/hooks/api/topicMessages';
  13. import { SerdeUsage } from 'generated-sources';
  14. import * as S from './SendMessage.styled';
  15. import {
  16. getDefaultValues,
  17. getPartitionOptions,
  18. getSerdeOptions,
  19. validateBySchema,
  20. } from './utils';
  21. interface FormType {
  22. key: string;
  23. content: string;
  24. headers: string;
  25. partition: number;
  26. keySerde: string;
  27. valueSerde: string;
  28. keepContents: boolean;
  29. }
  30. const SendMessage: React.FC<{ closeSidebar: () => void }> = ({
  31. closeSidebar,
  32. }) => {
  33. const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
  34. const { data: topic } = useTopicDetails({ clusterName, topicName });
  35. const { data: serdes = {} } = useSerdes({
  36. clusterName,
  37. topicName,
  38. use: SerdeUsage.SERIALIZE,
  39. });
  40. const sendMessage = useSendMessage({ clusterName, topicName });
  41. const defaultValues = React.useMemo(() => getDefaultValues(serdes), [serdes]);
  42. const partitionOptions: SelectOption[] = React.useMemo(
  43. () => getPartitionOptions(topic?.partitions || []),
  44. [topic]
  45. );
  46. const {
  47. handleSubmit,
  48. formState: { isSubmitting },
  49. control,
  50. setValue,
  51. } = useForm<FormType>({
  52. mode: 'onChange',
  53. defaultValues: {
  54. ...defaultValues,
  55. partition: Number(partitionOptions[0].value),
  56. keepContents: false,
  57. },
  58. });
  59. const submit = async ({
  60. keySerde,
  61. valueSerde,
  62. key,
  63. content,
  64. headers,
  65. partition,
  66. keepContents,
  67. }: FormType) => {
  68. let errors: string[] = [];
  69. if (keySerde) {
  70. const selectedKeySerde = serdes.key?.find((k) => k.name === keySerde);
  71. errors = validateBySchema(key, selectedKeySerde?.schema, 'key');
  72. }
  73. if (valueSerde) {
  74. const selectedValue = serdes.value?.find((v) => v.name === valueSerde);
  75. errors = [
  76. ...errors,
  77. ...validateBySchema(content, selectedValue?.schema, 'content'),
  78. ];
  79. }
  80. let parsedHeaders;
  81. if (headers) {
  82. try {
  83. parsedHeaders = JSON.parse(headers);
  84. } catch (error) {
  85. errors.push('Wrong header format');
  86. }
  87. }
  88. if (errors.length > 0) {
  89. showAlert('error', {
  90. id: `${clusterName}-${topicName}-createTopicMessageError`,
  91. title: 'Validation Error',
  92. message: (
  93. <ul>
  94. {errors.map((e) => (
  95. <li key={e}>{e}</li>
  96. ))}
  97. </ul>
  98. ),
  99. });
  100. return;
  101. }
  102. try {
  103. await sendMessage.mutateAsync({
  104. key: key || null,
  105. content: content || null,
  106. headers: parsedHeaders,
  107. partition: partition || 0,
  108. keySerde,
  109. valueSerde,
  110. });
  111. if (!keepContents) {
  112. setValue('key', '');
  113. setValue('content', '');
  114. closeSidebar();
  115. }
  116. } catch (e) {
  117. // do nothing
  118. }
  119. };
  120. return (
  121. <S.Wrapper>
  122. <form onSubmit={handleSubmit(submit)}>
  123. <S.Columns>
  124. <S.FlexItem>
  125. <InputLabel>Partition</InputLabel>
  126. <Controller
  127. control={control}
  128. name="partition"
  129. render={({ field: { name, onChange, value } }) => (
  130. <Select
  131. id="selectPartitionOptions"
  132. aria-labelledby="selectPartitionOptions"
  133. name={name}
  134. onChange={onChange}
  135. minWidth="100%"
  136. options={partitionOptions}
  137. value={value}
  138. />
  139. )}
  140. />
  141. </S.FlexItem>
  142. <S.Flex>
  143. <S.FlexItem>
  144. <InputLabel>Key Serde</InputLabel>
  145. <Controller
  146. control={control}
  147. name="keySerde"
  148. render={({ field: { name, onChange, value } }) => (
  149. <Select
  150. id="selectKeySerdeOptions"
  151. aria-labelledby="selectKeySerdeOptions"
  152. name={name}
  153. onChange={onChange}
  154. minWidth="100%"
  155. options={getSerdeOptions(serdes.key || [])}
  156. value={value}
  157. />
  158. )}
  159. />
  160. </S.FlexItem>
  161. <S.FlexItem>
  162. <InputLabel>Value Serde</InputLabel>
  163. <Controller
  164. control={control}
  165. name="valueSerde"
  166. render={({ field: { name, onChange, value } }) => (
  167. <Select
  168. id="selectValueSerdeOptions"
  169. aria-labelledby="selectValueSerdeOptions"
  170. name={name}
  171. onChange={onChange}
  172. minWidth="100%"
  173. options={getSerdeOptions(serdes.value || [])}
  174. value={value}
  175. />
  176. )}
  177. />
  178. </S.FlexItem>
  179. </S.Flex>
  180. <div>
  181. <Controller
  182. control={control}
  183. name="keepContents"
  184. render={({ field: { name, onChange, value } }) => (
  185. <Switch name={name} onChange={onChange} checked={value} />
  186. )}
  187. />
  188. <InputLabel>Keep contents</InputLabel>
  189. </div>
  190. </S.Columns>
  191. <S.Columns>
  192. <div>
  193. <InputLabel>Key</InputLabel>
  194. <Controller
  195. control={control}
  196. name="key"
  197. render={({ field: { name, onChange, value } }) => (
  198. <Editor
  199. readOnly={isSubmitting}
  200. name={name}
  201. onChange={onChange}
  202. value={value}
  203. />
  204. )}
  205. />
  206. </div>
  207. <div>
  208. <InputLabel>Value</InputLabel>
  209. <Controller
  210. control={control}
  211. name="content"
  212. render={({ field: { name, onChange, value } }) => (
  213. <Editor
  214. readOnly={isSubmitting}
  215. name={name}
  216. onChange={onChange}
  217. value={value}
  218. />
  219. )}
  220. />
  221. </div>
  222. </S.Columns>
  223. <S.Columns>
  224. <div>
  225. <InputLabel>Headers</InputLabel>
  226. <Controller
  227. control={control}
  228. name="headers"
  229. render={({ field: { name, onChange } }) => (
  230. <Editor
  231. readOnly={isSubmitting}
  232. defaultValue="{}"
  233. name={name}
  234. onChange={onChange}
  235. height="200px"
  236. />
  237. )}
  238. />
  239. </div>
  240. </S.Columns>
  241. <Button
  242. buttonSize="M"
  243. buttonType="primary"
  244. type="submit"
  245. disabled={isSubmitting}
  246. >
  247. Produce Message
  248. </Button>
  249. </form>
  250. </S.Wrapper>
  251. );
  252. };
  253. export default SendMessage;