Browse Source

Fix the problem with instant redirect (#390)

* Fix the problem with instant redirect

* Rewrite updateSchema thunk
Alexander Krivonosov 4 years ago
parent
commit
42a1c97686

+ 1 - 0
kafka-ui-react-app/src/components/Schemas/Details/LatestVersionItem.tsx

@@ -33,6 +33,7 @@ const LatestVersionItem: React.FC<LatestVersionProps> = ({
     <div className="tile is-parent">
     <div className="tile is-parent">
       <div className="tile is-child box">
       <div className="tile is-child box">
         <JSONEditor
         <JSONEditor
+          isFixedHeight
           name="schema"
           name="schema"
           value={JSON.stringify(JSON.parse(schema), null, '\t')}
           value={JSON.stringify(JSON.parse(schema), null, '\t')}
           showGutter={false}
           showGutter={false}

+ 1 - 0
kafka-ui-react-app/src/components/Schemas/Details/SchemaVersion.tsx

@@ -15,6 +15,7 @@ const SchemaVersion: React.FC<SchemaVersionProps> = ({
       <td>{id}</td>
       <td>{id}</td>
       <td>
       <td>
         <JSONEditor
         <JSONEditor
+          isFixedHeight
           name="schema"
           name="schema"
           value={JSON.stringify(JSON.parse(schema), null, '\t')}
           value={JSON.stringify(JSON.parse(schema), null, '\t')}
           showGutter={false}
           showGutter={false}

+ 1 - 0
kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/LatestVersionItem.spec.tsx.snap

@@ -49,6 +49,7 @@ exports[`LatestVersionItem matches snapshot 1`] = `
       className="tile is-child box"
       className="tile is-child box"
     >
     >
       <JSONEditor
       <JSONEditor
+        isFixedHeight={true}
         name="schema"
         name="schema"
         readOnly={true}
         readOnly={true}
         showGutter={false}
         showGutter={false}

+ 1 - 0
kafka-ui-react-app/src/components/Schemas/Details/__test__/__snapshots__/SchemaVersion.spec.tsx.snap

@@ -10,6 +10,7 @@ exports[`SchemaVersion matches snapshot 1`] = `
   </td>
   </td>
   <td>
   <td>
     <JSONEditor
     <JSONEditor
+      isFixedHeight={true}
       name="schema"
       name="schema"
       readOnly={true}
       readOnly={true}
       showGutter={false}
       showGutter={false}

+ 26 - 12
kafka-ui-react-app/src/components/Schemas/Edit/Edit.tsx

@@ -43,7 +43,7 @@ const Edit = ({
   const {
   const {
     register,
     register,
     handleSubmit,
     handleSubmit,
-    formState: { isSubmitting },
+    formState: { isSubmitting, isDirty },
     control,
     control,
   } = useForm<NewSchemaSubjectRaw>({ mode: 'onChange' });
   } = useForm<NewSchemaSubjectRaw>({ mode: 'onChange' });
 
 
@@ -62,15 +62,19 @@ const Edit = ({
       compatibilityLevel: CompatibilityLevelCompatibilityEnum;
       compatibilityLevel: CompatibilityLevelCompatibilityEnum;
       newSchema: string;
       newSchema: string;
     }) => {
     }) => {
-      await updateSchema(
-        schema,
-        newSchema,
-        schemaType,
-        compatibilityLevel,
-        clusterName,
-        subject
-      );
-      history.push(clusterSchemaPath(clusterName, subject));
+      try {
+        await updateSchema(
+          schema,
+          newSchema,
+          schemaType,
+          compatibilityLevel,
+          clusterName,
+          subject
+        );
+        history.push(clusterSchemaPath(clusterName, subject));
+      } catch (e) {
+        // do not redirect
+      }
     },
     },
     [schema, register, control, clusterName, subject, updateSchema, history]
     [schema, register, control, clusterName, subject, updateSchema, history]
   );
   );
@@ -96,7 +100,7 @@ const Edit = ({
         </div>
         </div>
       </div>
       </div>
 
 
-      {schemasAreFetched && !isSubmitting ? (
+      {schemasAreFetched ? (
         <div className="box">
         <div className="box">
           <form
           <form
             onSubmit={handleSubmit(onSubmit)}
             onSubmit={handleSubmit(onSubmit)}
@@ -111,6 +115,7 @@ const Edit = ({
                     required: 'Schema Type is required.',
                     required: 'Schema Type is required.',
                   })}
                   })}
                   defaultValue={schema.schemaType}
                   defaultValue={schema.schemaType}
+                  disabled={isSubmitting}
                 >
                 >
                   {Object.keys(SchemaType).map((type: string) => (
                   {Object.keys(SchemaType).map((type: string) => (
                     <option key={type} value={type}>
                     <option key={type} value={type}>
@@ -128,6 +133,7 @@ const Edit = ({
                   name="compatibilityLevel"
                   name="compatibilityLevel"
                   ref={register()}
                   ref={register()}
                   defaultValue={schema.compatibilityLevel}
                   defaultValue={schema.compatibilityLevel}
+                  disabled={isSubmitting}
                 >
                 >
                   {Object.keys(CompatibilityLevelCompatibilityEnum).map(
                   {Object.keys(CompatibilityLevelCompatibilityEnum).map(
                     (level: string) => (
                     (level: string) => (
@@ -143,7 +149,9 @@ const Edit = ({
               <div className="column is-one-half">
               <div className="column is-one-half">
                 <h4 className="title is-5 mb-2">Latest Schema</h4>
                 <h4 className="title is-5 mb-2">Latest Schema</h4>
                 <JSONEditor
                 <JSONEditor
+                  isFixedHeight
                   readOnly
                   readOnly
+                  height="500px"
                   value={getFormattedSchema()}
                   value={getFormattedSchema()}
                   name="latestSchema"
                   name="latestSchema"
                   highlightActiveLine={false}
                   highlightActiveLine={false}
@@ -154,8 +162,10 @@ const Edit = ({
                 <Controller
                 <Controller
                   control={control}
                   control={control}
                   name="newSchema"
                   name="newSchema"
+                  disabled={isSubmitting}
                   render={({ name, onChange }) => (
                   render={({ name, onChange }) => (
                     <JSONEditor
                     <JSONEditor
+                      readOnly={isSubmitting}
                       defaultValue={getFormattedSchema()}
                       defaultValue={getFormattedSchema()}
                       name={name}
                       name={name}
                       onChange={onChange}
                       onChange={onChange}
@@ -164,7 +174,11 @@ const Edit = ({
                 />
                 />
               </div>
               </div>
             </div>
             </div>
-            <button type="submit" className="button is-primary">
+            <button
+              type="submit"
+              className="button is-primary"
+              disabled={!isDirty || isSubmitting}
+            >
               Submit
               Submit
             </button>
             </button>
           </form>
           </form>

+ 6 - 0
kafka-ui-react-app/src/components/Schemas/Edit/__tests__/__snapshots__/Edit.spec.tsx.snap

@@ -48,6 +48,7 @@ exports[`Edit Component when schemas are fetched matches the snapshot 1`] = `
         >
         >
           <select
           <select
             defaultValue="AVRO"
             defaultValue="AVRO"
+            disabled={false}
             name="schemaType"
             name="schemaType"
           >
           >
             <option
             <option
@@ -84,6 +85,7 @@ exports[`Edit Component when schemas are fetched matches the snapshot 1`] = `
         >
         >
           <select
           <select
             defaultValue="BACKWARD"
             defaultValue="BACKWARD"
+            disabled={false}
             name="compatibilityLevel"
             name="compatibilityLevel"
           >
           >
             <option
             <option
@@ -143,7 +145,9 @@ exports[`Edit Component when schemas are fetched matches the snapshot 1`] = `
             Latest Schema
             Latest Schema
           </h4>
           </h4>
           <JSONEditor
           <JSONEditor
+            height="500px"
             highlightActiveLine={false}
             highlightActiveLine={false}
+            isFixedHeight={true}
             name="latestSchema"
             name="latestSchema"
             readOnly={true}
             readOnly={true}
             value="{
             value="{
@@ -261,6 +265,7 @@ exports[`Edit Component when schemas are fetched matches the snapshot 1`] = `
                 "watchInternal": [Function],
                 "watchInternal": [Function],
               }
               }
             }
             }
+            disabled={false}
             name="newSchema"
             name="newSchema"
             render={[Function]}
             render={[Function]}
           />
           />
@@ -268,6 +273,7 @@ exports[`Edit Component when schemas are fetched matches the snapshot 1`] = `
       </div>
       </div>
       <button
       <button
         className="button is-primary"
         className="button is-primary"
+        disabled={true}
         type="submit"
         type="submit"
       >
       >
         Submit
         Submit

+ 1 - 7
kafka-ui-react-app/src/components/Schemas/New/NewContainer.ts

@@ -1,16 +1,10 @@
 import { connect } from 'react-redux';
 import { connect } from 'react-redux';
-import { RootState } from 'redux/interfaces';
 import { createSchema } from 'redux/actions';
 import { createSchema } from 'redux/actions';
-import { getSchemaCreated } from 'redux/reducers/schemas/selectors';
 
 
 import New from './New';
 import New from './New';
 
 
-const mapStateToProps = (state: RootState) => ({
-  isSchemaCreated: getSchemaCreated(state),
-});
-
 const mapDispatchToProps = {
 const mapDispatchToProps = {
   createSchema,
   createSchema,
 };
 };
 
 
-export default connect(mapStateToProps, mapDispatchToProps)(New);
+export default connect(null, mapDispatchToProps)(New);

+ 20 - 10
kafka-ui-react-app/src/components/common/JSONEditor/JSONEditor.tsx

@@ -4,15 +4,25 @@ import 'ace-builds/src-noconflict/mode-json5';
 import 'ace-builds/src-noconflict/theme-textmate';
 import 'ace-builds/src-noconflict/theme-textmate';
 import React from 'react';
 import React from 'react';
 
 
-const JSONEditor: React.FC<IAceEditorProps> = (props) => (
-  <AceEditor
-    mode="json5"
-    theme="textmate"
-    tabSize={2}
-    width="100%"
-    wrapEnabled
-    {...props}
-  />
-);
+interface JSONEditorProps extends IAceEditorProps {
+  isFixedHeight?: boolean;
+}
+
+const JSONEditor: React.FC<JSONEditorProps> = (props) => {
+  const { isFixedHeight, value } = props;
+  return (
+    <AceEditor
+      mode="json5"
+      theme="textmate"
+      tabSize={2}
+      width="100%"
+      height={
+        isFixedHeight ? `${(value?.split('\n').length || 32) * 16}px` : '500px'
+      }
+      wrapEnabled
+      {...props}
+    />
+  );
+};
 
 
 export default JSONEditor;
 export default JSONEditor;

+ 13 - 60
kafka-ui-react-app/src/redux/actions/__test__/thunks/schemas.spec.ts

@@ -122,53 +122,22 @@ describe('Thunks', () => {
         expect(error.status).toEqual(404);
         expect(error.status).toEqual(404);
         expect(store.getActions()).toEqual([
         expect(store.getActions()).toEqual([
           actions.createSchemaAction.request(),
           actions.createSchemaAction.request(),
-          actions.createSchemaAction.failure({}),
+          actions.createSchemaAction.failure({
+            alert: {
+              response: {
+                body: undefined,
+                status: 404,
+                statusText: 'Not Found',
+              },
+              subject: 'schema-NewSchema',
+              title: 'Schema NewSchema',
+            },
+          }),
         ]);
         ]);
       }
       }
     });
     });
   });
   });
 
 
-  describe('updateSchemaCompatibilityLevel', () => {
-    it('creates UPDATE_SCHEMA__SUCCESS when patching a schema', async () => {
-      fetchMock.putOnce(
-        `/api/clusters/${clusterName}/schemas/${subject}/compatibility`,
-        200
-      );
-      await store.dispatch(
-        thunks.updateSchemaCompatibilityLevel(
-          clusterName,
-          subject,
-          CompatibilityLevelCompatibilityEnum.BACKWARD
-        )
-      );
-      expect(store.getActions()).toEqual([
-        actions.updateSchemaCompatibilityLevelAction.request(),
-        actions.updateSchemaCompatibilityLevelAction.success(),
-      ]);
-    });
-
-    it('creates UPDATE_SCHEMA__SUCCESS when failing to patch a schema', async () => {
-      fetchMock.putOnce(
-        `/api/clusters/${clusterName}/schemas/${subject}/compatibility`,
-        404
-      );
-      try {
-        await store.dispatch(
-          thunks.updateSchemaCompatibilityLevel(
-            clusterName,
-            subject,
-            CompatibilityLevelCompatibilityEnum.BACKWARD
-          )
-        );
-      } catch (error) {
-        expect(error.status).toEqual(404);
-        expect(store.getActions()).toEqual([
-          actions.updateSchemaCompatibilityLevelAction.request(),
-          actions.updateSchemaCompatibilityLevelAction.failure({}),
-        ]);
-      }
-    });
-  });
   describe('deleteSchema', () => {
   describe('deleteSchema', () => {
     it('fires DELETE_SCHEMA__SUCCESS on success', async () => {
     it('fires DELETE_SCHEMA__SUCCESS on success', async () => {
       fetchMock.deleteOnce(
       fetchMock.deleteOnce(
@@ -203,7 +172,7 @@ describe('Thunks', () => {
   });
   });
 
 
   describe('updateSchema', () => {
   describe('updateSchema', () => {
-    it('calls createSchema', () => {
+    it('calls PATCH_SCHEMA__REQUEST', () => {
       store.dispatch(
       store.dispatch(
         thunks.updateSchema(
         thunks.updateSchema(
           fixtures.schema,
           fixtures.schema,
@@ -215,23 +184,7 @@ describe('Thunks', () => {
         )
         )
       );
       );
       expect(store.getActions()).toEqual([
       expect(store.getActions()).toEqual([
-        actions.createSchemaAction.request(),
-      ]);
-    });
-
-    it('calls updateSchema and does not call createSchema when schema does not change', () => {
-      store.dispatch(
-        thunks.updateSchema(
-          fixtures.schema,
-          fixtures.schema.schema,
-          SchemaType.JSON,
-          CompatibilityLevelCompatibilityEnum.FORWARD,
-          clusterName,
-          subject
-        )
-      );
-      expect(store.getActions()).toEqual([
-        actions.updateSchemaCompatibilityLevelAction.request(),
+        actions.updateSchemaAction.request(),
       ]);
       ]);
     });
     });
   });
   });

+ 5 - 5
kafka-ui-react-app/src/redux/actions/actions.ts

@@ -130,11 +130,11 @@ export const createSchemaAction = createAsyncAction(
   'POST_SCHEMA__FAILURE'
   'POST_SCHEMA__FAILURE'
 )<undefined, SchemaSubject, { alert?: FailurePayload }>();
 )<undefined, SchemaSubject, { alert?: FailurePayload }>();
 
 
-export const updateSchemaCompatibilityLevelAction = createAsyncAction(
-  'PATCH_SCHEMA_COMPATIBILITY__REQUEST',
-  'PATCH_SCHEMA_COMPATIBILITY__SUCCESS',
-  'PATCH_SCHEMA_COMPATIBILITY__FAILURE'
-)<undefined, undefined, { alert?: FailurePayload }>();
+export const updateSchemaAction = createAsyncAction(
+  'PATCH_SCHEMA__REQUEST',
+  'PATCH_SCHEMA__SUCCESS',
+  'PATCH_SCHEMA__FAILURE'
+)<undefined, SchemaSubject, { alert?: FailurePayload }>();
 
 
 export const deleteSchemaAction = createAsyncAction(
 export const deleteSchemaAction = createAsyncAction(
   'DELETE_SCHEMA__REQUEST',
   'DELETE_SCHEMA__REQUEST',

+ 35 - 45
kafka-ui-react-app/src/redux/actions/thunks/schemas.ts

@@ -68,32 +68,7 @@ export const createSchema = (
       response,
       response,
     };
     };
     dispatch(actions.createSchemaAction.failure({ alert }));
     dispatch(actions.createSchemaAction.failure({ alert }));
-  }
-};
-
-export const updateSchemaCompatibilityLevel = (
-  clusterName: ClusterName,
-  subject: string,
-  compatibilityLevel: CompatibilityLevelCompatibilityEnum
-): PromiseThunkResult => async (dispatch) => {
-  dispatch(actions.updateSchemaCompatibilityLevelAction.request());
-  try {
-    await schemasApiClient.updateSchemaCompatibilityLevel({
-      clusterName,
-      subject,
-      compatibilityLevel: {
-        compatibility: compatibilityLevel,
-      },
-    });
-    dispatch(actions.updateSchemaCompatibilityLevelAction.success());
-  } catch (error) {
-    const response = await getResponse(error);
-    const alert: FailurePayload = {
-      subject: 'compatibilityLevel',
-      title: `Compatibility level ${subject}`,
-      response,
-    };
-    dispatch(actions.updateSchemaCompatibilityLevelAction.failure({ alert }));
+    throw error;
   }
   }
 };
 };
 
 
@@ -105,27 +80,42 @@ export const updateSchema = (
   clusterName: string,
   clusterName: string,
   subject: string
   subject: string
 ): PromiseThunkResult => async (dispatch) => {
 ): PromiseThunkResult => async (dispatch) => {
-  if (
-    (newSchema &&
-      !isEqual(JSON.parse(latestSchema.schema), JSON.parse(newSchema))) ||
-    newSchemaType !== latestSchema.schemaType
-  ) {
-    await dispatch(
-      createSchema(clusterName, {
-        ...latestSchema,
-        schema: newSchema || latestSchema.schema,
-        schemaType: newSchemaType || latestSchema.schemaType,
-      })
-    );
-  }
-  if (newCompatibilityLevel !== latestSchema.compatibilityLevel) {
-    await dispatch(
-      updateSchemaCompatibilityLevel(
+  dispatch(actions.updateSchemaAction.request());
+  try {
+    let schema: SchemaSubject = latestSchema;
+    if (
+      (newSchema &&
+        !isEqual(JSON.parse(latestSchema.schema), JSON.parse(newSchema))) ||
+      newSchemaType !== latestSchema.schemaType
+    ) {
+      schema = await schemasApiClient.createNewSchema({
+        clusterName,
+        newSchemaSubject: {
+          ...latestSchema,
+          schema: newSchema || latestSchema.schema,
+          schemaType: newSchemaType || latestSchema.schemaType,
+        },
+      });
+    }
+    if (newCompatibilityLevel !== latestSchema.compatibilityLevel) {
+      await schemasApiClient.updateSchemaCompatibilityLevel({
         clusterName,
         clusterName,
         subject,
         subject,
-        newCompatibilityLevel
-      )
-    );
+        compatibilityLevel: {
+          compatibility: newCompatibilityLevel,
+        },
+      });
+    }
+    actions.updateSchemaAction.success(schema);
+  } catch (e) {
+    const response = await getResponse(e);
+    const alert: FailurePayload = {
+      subject: ['schema', subject].join('-'),
+      title: `Schema ${subject}`,
+      response,
+    };
+    dispatch(actions.updateSchemaAction.failure({ alert }));
+    throw e;
   }
   }
 };
 };
 export const deleteSchema = (
 export const deleteSchema = (

+ 2 - 0
kafka-ui-react-app/src/redux/reducers/schemas/reducer.ts

@@ -66,6 +66,8 @@ const reducer = (state = initialState, action: Action): SchemasState => {
       return { ...state, currentSchemaVersions: action.payload };
       return { ...state, currentSchemaVersions: action.payload };
     case 'POST_SCHEMA__SUCCESS':
     case 'POST_SCHEMA__SUCCESS':
       return addToSchemaList(state, action.payload);
       return addToSchemaList(state, action.payload);
+    case 'PATCH_SCHEMA__SUCCESS':
+      return addToSchemaList(state, action.payload);
     case getType(actions.deleteSchemaAction.success):
     case getType(actions.deleteSchemaAction.success):
       return deleteFromSchemaList(state, action.payload);
       return deleteFromSchemaList(state, action.payload);
     default:
     default: