SendMessage.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import React, { useEffect } from 'react';
  2. import { useForm, Controller } from 'react-hook-form';
  3. import { RouteParamsClusterTopic } from 'lib/paths';
  4. import jsf from 'json-schema-faker';
  5. import { Button } from 'components/common/Button/Button';
  6. import Editor from 'components/common/Editor/Editor';
  7. import Select, { SelectOption } from 'components/common/Select/Select';
  8. import useAppParams from 'lib/hooks/useAppParams';
  9. import { showAlert } from 'lib/errorHandling';
  10. import {
  11. useSendMessage,
  12. useTopicDetails,
  13. useTopicMessageSchema,
  14. } from 'lib/hooks/api/topics';
  15. import { InputLabel } from 'components/common/Input/InputLabel.styled';
  16. import validateMessage from './validateMessage';
  17. import * as S from './SendMessage.styled';
  18. type FieldValues = Partial<{
  19. key: string;
  20. content: string;
  21. headers: string;
  22. partition: number | string;
  23. }>;
  24. const SendMessage: React.FC<{ onSubmit: () => void }> = ({ onSubmit }) => {
  25. const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
  26. const { data: topic } = useTopicDetails({ clusterName, topicName });
  27. const { data: messageSchema } = useTopicMessageSchema({
  28. clusterName,
  29. topicName,
  30. });
  31. const sendMessage = useSendMessage({ clusterName, topicName });
  32. jsf.option('fillProperties', false);
  33. jsf.option('alwaysFakeOptionals', true);
  34. const partitions = topic?.partitions || [];
  35. const selectPartitionOptions: Array<SelectOption> = partitions.map((p) => {
  36. const value = String(p.partition);
  37. return { value, label: value };
  38. });
  39. const keyDefaultValue = React.useMemo(() => {
  40. if (!messageSchema) {
  41. return undefined;
  42. }
  43. return JSON.stringify(
  44. jsf.generate(JSON.parse(messageSchema.key.schema)),
  45. null,
  46. '\t'
  47. );
  48. }, [messageSchema]);
  49. const contentDefaultValue = React.useMemo(() => {
  50. if (!messageSchema) {
  51. return undefined;
  52. }
  53. return JSON.stringify(
  54. jsf.generate(JSON.parse(messageSchema.value.schema)),
  55. null,
  56. '\t'
  57. );
  58. }, [messageSchema]);
  59. const {
  60. handleSubmit,
  61. formState: { isSubmitting, isDirty },
  62. control,
  63. reset,
  64. } = useForm<FieldValues>({
  65. mode: 'onChange',
  66. defaultValues: {
  67. key: keyDefaultValue,
  68. content: contentDefaultValue,
  69. headers: undefined,
  70. partition: undefined,
  71. },
  72. });
  73. useEffect(() => {
  74. reset({
  75. key: keyDefaultValue,
  76. content: contentDefaultValue,
  77. });
  78. }, [keyDefaultValue, contentDefaultValue, reset]);
  79. const submit = async (data: {
  80. key: string;
  81. content: string;
  82. headers: string;
  83. partition: number;
  84. }) => {
  85. if (messageSchema) {
  86. const { partition, key, content } = data;
  87. const errors = validateMessage(key, content, messageSchema);
  88. if (data.headers) {
  89. try {
  90. JSON.parse(data.headers);
  91. } catch (error) {
  92. errors.push('Wrong header format');
  93. }
  94. }
  95. if (errors.length > 0) {
  96. showAlert('error', {
  97. id: `${clusterName}-${topicName}-createTopicMessageError`,
  98. title: 'Validation Error',
  99. message: (
  100. <ul>
  101. {errors.map((e) => (
  102. <li key={e}>{e}</li>
  103. ))}
  104. </ul>
  105. ),
  106. });
  107. return;
  108. }
  109. const headers = data.headers ? JSON.parse(data.headers) : undefined;
  110. await sendMessage.mutateAsync({
  111. key: !key ? null : key,
  112. content: !content ? null : content,
  113. headers,
  114. partition: !partition ? 0 : partition,
  115. });
  116. onSubmit();
  117. }
  118. };
  119. return (
  120. <S.Wrapper>
  121. <form onSubmit={handleSubmit(submit)}>
  122. <S.Columns>
  123. <S.Column>
  124. <InputLabel>Partition</InputLabel>
  125. <Controller
  126. control={control}
  127. name="partition"
  128. defaultValue={selectPartitionOptions[0].value}
  129. render={({ field: { name, onChange } }) => (
  130. <Select
  131. id="selectPartitionOptions"
  132. aria-labelledby="selectPartitionOptions"
  133. name={name}
  134. onChange={onChange}
  135. minWidth="100px"
  136. options={selectPartitionOptions}
  137. value={selectPartitionOptions[0].value}
  138. />
  139. )}
  140. />
  141. </S.Column>
  142. </S.Columns>
  143. <S.Columns>
  144. <S.Column>
  145. <InputLabel>Key</InputLabel>
  146. <Controller
  147. control={control}
  148. name="key"
  149. render={({ field: { name, onChange, value } }) => (
  150. <Editor
  151. readOnly={isSubmitting}
  152. name={name}
  153. onChange={onChange}
  154. value={value}
  155. />
  156. )}
  157. />
  158. </S.Column>
  159. <S.Column>
  160. <InputLabel>Content</InputLabel>
  161. <Controller
  162. control={control}
  163. name="content"
  164. render={({ field: { name, onChange, value } }) => (
  165. <Editor
  166. readOnly={isSubmitting}
  167. name={name}
  168. onChange={onChange}
  169. value={value}
  170. />
  171. )}
  172. />
  173. </S.Column>
  174. </S.Columns>
  175. <S.Columns>
  176. <S.Column>
  177. <InputLabel>Headers</InputLabel>
  178. <Controller
  179. control={control}
  180. name="headers"
  181. render={({ field: { name, onChange } }) => (
  182. <Editor
  183. readOnly={isSubmitting}
  184. defaultValue="{}"
  185. name={name}
  186. onChange={onChange}
  187. height="200px"
  188. />
  189. )}
  190. />
  191. </S.Column>
  192. </S.Columns>
  193. <Button
  194. buttonSize="M"
  195. buttonType="primary"
  196. type="submit"
  197. disabled={!isDirty || isSubmitting}
  198. >
  199. Produce Message
  200. </Button>
  201. </form>
  202. </S.Wrapper>
  203. );
  204. };
  205. export default SendMessage;