Form.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import React from 'react';
  2. import { useNavigate } from 'react-router-dom';
  3. import { useForm, Controller, FormProvider } from 'react-hook-form';
  4. import {
  5. CompatibilityLevelCompatibilityEnum,
  6. SchemaType,
  7. } from 'generated-sources';
  8. import {
  9. clusterSchemaPath,
  10. clusterSchemasPath,
  11. ClusterSubjectParam,
  12. } from 'lib/paths';
  13. import yup from 'lib/yupExtended';
  14. import { NewSchemaSubjectRaw } from 'redux/interfaces';
  15. import Editor from 'components/common/Editor/Editor';
  16. import Select from 'components/common/Select/Select';
  17. import { Button } from 'components/common/Button/Button';
  18. import { InputLabel } from 'components/common/Input/InputLabel.styled';
  19. import PageHeading from 'components/common/PageHeading/PageHeading';
  20. import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
  21. import useAppParams from 'lib/hooks/useAppParams';
  22. import {
  23. schemaAdded,
  24. getSchemaLatest,
  25. getAreSchemaLatestFulfilled,
  26. schemaUpdated,
  27. getAreSchemaLatestRejected,
  28. } from 'redux/reducers/schemas/schemasSlice';
  29. import PageLoader from 'components/common/PageLoader/PageLoader';
  30. import { schemasApiClient } from 'lib/api';
  31. import { showServerError } from 'lib/errorHandling';
  32. import { yupResolver } from '@hookform/resolvers/yup';
  33. import { FormError } from 'components/common/Input/Input.styled';
  34. import { ErrorMessage } from '@hookform/error-message';
  35. import * as S from './Edit.styled';
  36. const Form: React.FC = () => {
  37. const navigate = useNavigate();
  38. const dispatch = useAppDispatch();
  39. const { clusterName, subject } = useAppParams<ClusterSubjectParam>();
  40. const schema = useAppSelector((state) => getSchemaLatest(state));
  41. const isFetched = useAppSelector(getAreSchemaLatestFulfilled);
  42. const isRejected = useAppSelector(getAreSchemaLatestRejected);
  43. const formatedSchema = React.useMemo(() => {
  44. return schema?.schemaType === SchemaType.PROTOBUF
  45. ? schema?.schema
  46. : JSON.stringify(JSON.parse(schema?.schema || '{}'), null, '\t');
  47. }, [schema]);
  48. const validationSchema = () =>
  49. yup.object().shape({
  50. newSchema:
  51. schema?.schemaType === SchemaType.PROTOBUF
  52. ? yup.string().required()
  53. : yup.string().required().isJsonObject('Schema syntax is not valid'),
  54. });
  55. const methods = useForm<NewSchemaSubjectRaw>({
  56. mode: 'onChange',
  57. resolver: yupResolver(validationSchema()),
  58. defaultValues: {
  59. schemaType: schema?.schemaType,
  60. compatibilityLevel:
  61. schema?.compatibilityLevel as CompatibilityLevelCompatibilityEnum,
  62. newSchema: formatedSchema,
  63. },
  64. });
  65. const {
  66. formState: { isDirty, isSubmitting, dirtyFields, errors },
  67. control,
  68. handleSubmit,
  69. } = methods;
  70. const onSubmit = async (props: NewSchemaSubjectRaw) => {
  71. if (!schema) return;
  72. try {
  73. if (dirtyFields.compatibilityLevel) {
  74. await schemasApiClient.updateSchemaCompatibilityLevel({
  75. clusterName,
  76. subject,
  77. compatibilityLevel: {
  78. compatibility: props.compatibilityLevel,
  79. },
  80. });
  81. dispatch(
  82. schemaUpdated({
  83. ...schema,
  84. compatibilityLevel: props.compatibilityLevel,
  85. })
  86. );
  87. }
  88. if (dirtyFields.newSchema || dirtyFields.schemaType) {
  89. const resp = await schemasApiClient.createNewSchema({
  90. clusterName,
  91. newSchemaSubject: {
  92. ...schema,
  93. schema: props.newSchema || schema.schema,
  94. schemaType: props.schemaType || schema.schemaType,
  95. },
  96. });
  97. dispatch(schemaAdded(resp));
  98. }
  99. navigate(clusterSchemaPath(clusterName, subject));
  100. } catch (e) {
  101. showServerError(e as Response);
  102. }
  103. };
  104. if (isRejected) {
  105. navigate('/404');
  106. }
  107. if (!isFetched || !schema) {
  108. return <PageLoader />;
  109. }
  110. return (
  111. <FormProvider {...methods}>
  112. <PageHeading
  113. text={`${subject} Edit`}
  114. backText="Schema Registry"
  115. backTo={clusterSchemasPath(clusterName)}
  116. />
  117. <S.EditWrapper>
  118. <form onSubmit={handleSubmit(onSubmit)}>
  119. <div>
  120. <div>
  121. <InputLabel>Type</InputLabel>
  122. <Controller
  123. control={control}
  124. rules={{ required: true }}
  125. name="schemaType"
  126. render={({ field: { name, onChange, value } }) => (
  127. <Select
  128. name={name}
  129. value={value}
  130. onChange={onChange}
  131. minWidth="100%"
  132. disabled
  133. options={Object.keys(SchemaType).map((type) => ({
  134. value: type,
  135. label: type,
  136. }))}
  137. />
  138. )}
  139. />
  140. </div>
  141. <div>
  142. <InputLabel>Compatibility level</InputLabel>
  143. <Controller
  144. control={control}
  145. name="compatibilityLevel"
  146. render={({ field: { name, onChange, value } }) => (
  147. <Select
  148. name={name}
  149. value={value}
  150. onChange={onChange}
  151. minWidth="100%"
  152. disabled={isSubmitting}
  153. options={Object.keys(
  154. CompatibilityLevelCompatibilityEnum
  155. ).map((level) => ({ value: level, label: level }))}
  156. />
  157. )}
  158. />
  159. </div>
  160. </div>
  161. <S.EditorsWrapper>
  162. <div>
  163. <S.EditorContainer>
  164. <h4>Latest schema</h4>
  165. <Editor
  166. schemaType={schema?.schemaType}
  167. isFixedHeight
  168. readOnly
  169. height="372px"
  170. value={formatedSchema}
  171. name="latestSchema"
  172. highlightActiveLine={false}
  173. />
  174. </S.EditorContainer>
  175. </div>
  176. <div>
  177. <S.EditorContainer>
  178. <h4>New schema</h4>
  179. <Controller
  180. control={control}
  181. name="newSchema"
  182. render={({ field: { name, onChange, value } }) => (
  183. <Editor
  184. schemaType={schema?.schemaType}
  185. readOnly={isSubmitting}
  186. defaultValue={value}
  187. name={name}
  188. onChange={onChange}
  189. />
  190. )}
  191. />
  192. </S.EditorContainer>
  193. <FormError>
  194. <ErrorMessage errors={errors} name="newSchema" />
  195. </FormError>
  196. <Button
  197. buttonType="primary"
  198. buttonSize="M"
  199. type="submit"
  200. disabled={!isDirty || isSubmitting || !!errors.newSchema}
  201. >
  202. Submit
  203. </Button>
  204. </div>
  205. </S.EditorsWrapper>
  206. </form>
  207. </S.EditWrapper>
  208. </FormProvider>
  209. );
  210. };
  211. export default Form;