Browse Source

Implement deleting schemas (#366)

Alexander Krivonosov 4 years ago
parent
commit
3945845c37

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

@@ -3,6 +3,7 @@ import { SchemaSubject } from 'generated-sources';
 import { ClusterName, SchemaName } from 'redux/interfaces';
 import { ClusterName, SchemaName } from 'redux/interfaces';
 import { clusterSchemasPath } from 'lib/paths';
 import { clusterSchemasPath } from 'lib/paths';
 import ClusterContext from 'components/contexts/ClusterContext';
 import ClusterContext from 'components/contexts/ClusterContext';
+import { useHistory } from 'react-router';
 import Breadcrumb from '../../common/Breadcrumb/Breadcrumb';
 import Breadcrumb from '../../common/Breadcrumb/Breadcrumb';
 import SchemaVersion from './SchemaVersion';
 import SchemaVersion from './SchemaVersion';
 import LatestVersionItem from './LatestVersionItem';
 import LatestVersionItem from './LatestVersionItem';
@@ -18,6 +19,7 @@ export interface DetailsProps {
     clusterName: ClusterName,
     clusterName: ClusterName,
     schemaName: SchemaName
     schemaName: SchemaName
   ) => void;
   ) => void;
+  deleteSchema: (clusterName: ClusterName, subject: string) => Promise<void>;
 }
 }
 
 
 const Details: React.FC<DetailsProps> = ({
 const Details: React.FC<DetailsProps> = ({
@@ -25,6 +27,7 @@ const Details: React.FC<DetailsProps> = ({
   schema,
   schema,
   clusterName,
   clusterName,
   fetchSchemaVersions,
   fetchSchemaVersions,
+  deleteSchema,
   versions,
   versions,
   isFetched,
   isFetched,
 }) => {
 }) => {
@@ -33,6 +36,12 @@ const Details: React.FC<DetailsProps> = ({
     fetchSchemaVersions(clusterName, subject);
     fetchSchemaVersions(clusterName, subject);
   }, [fetchSchemaVersions, clusterName]);
   }, [fetchSchemaVersions, clusterName]);
 
 
+  const history = useHistory();
+  const onDelete = async () => {
+    await deleteSchema(clusterName, subject);
+    history.push(clusterSchemasPath(clusterName));
+  };
+
   return (
   return (
     <div className="section">
     <div className="section">
       <div className="level">
       <div className="level">
@@ -75,7 +84,7 @@ const Details: React.FC<DetailsProps> = ({
                     className="button is-danger is-small level-item"
                     className="button is-danger is-small level-item"
                     type="button"
                     type="button"
                     title="in development"
                     title="in development"
-                    disabled
+                    onClick={onDelete}
                   >
                   >
                     Delete
                     Delete
                   </button>
                   </button>

+ 2 - 1
kafka-ui-react-app/src/components/Schemas/Details/DetailsContainer.ts

@@ -6,7 +6,7 @@ import {
   getSchema,
   getSchema,
   getSortedSchemaVersions,
   getSortedSchemaVersions,
 } from 'redux/reducers/schemas/selectors';
 } from 'redux/reducers/schemas/selectors';
-import { fetchSchemaVersions } from 'redux/actions';
+import { fetchSchemaVersions, deleteSchema } from 'redux/actions';
 import Details from './Details';
 import Details from './Details';
 
 
 interface RouteProps {
 interface RouteProps {
@@ -33,6 +33,7 @@ const mapStateToProps = (
 
 
 const mapDispatchToProps = {
 const mapDispatchToProps = {
   fetchSchemaVersions,
   fetchSchemaVersions,
+  deleteSchema,
 };
 };
 
 
 export default withRouter(
 export default withRouter(

+ 12 - 0
kafka-ui-react-app/src/components/Schemas/Details/__test__/Details.spec.tsx

@@ -30,6 +30,7 @@ describe('Details', () => {
         schema={schema}
         schema={schema}
         clusterName="Test cluster"
         clusterName="Test cluster"
         fetchSchemaVersions={jest.fn()}
         fetchSchemaVersions={jest.fn()}
+        deleteSchema={jest.fn()}
         isFetched
         isFetched
         versions={[]}
         versions={[]}
         {...props}
         {...props}
@@ -100,6 +101,17 @@ describe('Details', () => {
           expect(wrapper.find('SchemaVersion').length).toEqual(2);
           expect(wrapper.find('SchemaVersion').length).toEqual(2);
         });
         });
 
 
+        it('calls deleteSchema on button click', () => {
+          const mockDelete = jest.fn();
+          const component = mount(
+            <StaticRouter>
+              {setupWrapper({ versions, deleteSchema: mockDelete })}
+            </StaticRouter>
+          );
+          component.find('button').at(1).simulate('click');
+          expect(mockDelete).toHaveBeenCalledTimes(1);
+        });
+
         it('matches snapshot', () => {
         it('matches snapshot', () => {
           expect(shallow(setupWrapper({ versions }))).toMatchSnapshot();
           expect(shallow(setupWrapper({ versions }))).toMatchSnapshot();
         });
         });

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

@@ -61,7 +61,7 @@ exports[`Details View Initial state matches snapshot 1`] = `
         </button>
         </button>
         <button
         <button
           className="button is-danger is-small level-item"
           className="button is-danger is-small level-item"
-          disabled={true}
+          onClick={[Function]}
           title="in development"
           title="in development"
           type="button"
           type="button"
         >
         >
@@ -196,7 +196,7 @@ exports[`Details View when page with schema versions loaded when schema has vers
         </button>
         </button>
         <button
         <button
           className="button is-danger is-small level-item"
           className="button is-danger is-small level-item"
-          disabled={true}
+          onClick={[Function]}
           title="in development"
           title="in development"
           type="button"
           type="button"
         >
         >
@@ -334,7 +334,7 @@ exports[`Details View when page with schema versions loaded when versions are em
         </button>
         </button>
         <button
         <button
           className="button is-danger is-small level-item"
           className="button is-danger is-small level-item"
-          disabled={true}
+          onClick={[Function]}
           title="in development"
           title="in development"
           type="button"
           type="button"
         >
         >

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

@@ -123,4 +123,37 @@ describe('Thunks', () => {
       }
       }
     });
     });
   });
   });
+
+  describe('deleteSchema', () => {
+    it('fires DELETE_SCHEMA__SUCCESS on success', async () => {
+      fetchMock.deleteOnce(
+        `/api/clusters/${clusterName}/schemas/${subject}`,
+        200
+      );
+
+      await store.dispatch(thunks.deleteSchema(clusterName, subject));
+
+      expect(store.getActions()).toEqual([
+        actions.deleteSchemaAction.request(),
+        actions.deleteSchemaAction.success(subject),
+      ]);
+    });
+
+    it('fires DELETE_SCHEMA__FAILURE on failure', async () => {
+      fetchMock.deleteOnce(
+        `/api/clusters/${clusterName}/schemas/${subject}`,
+        404
+      );
+
+      try {
+        await store.dispatch(thunks.deleteSchema(clusterName, subject));
+      } catch (error) {
+        expect(error.status).toEqual(404);
+        expect(store.getActions()).toEqual([
+          actions.deleteSchemaAction.request(),
+          actions.deleteSchemaAction.failure({}),
+        ]);
+      }
+    });
+  });
 });
 });

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

@@ -124,6 +124,12 @@ export const createSchemaAction = createAsyncAction(
   'POST_SCHEMA__FAILURE'
   'POST_SCHEMA__FAILURE'
 )<undefined, SchemaSubject, { alert?: FailurePayload }>();
 )<undefined, SchemaSubject, { alert?: FailurePayload }>();
 
 
+export const deleteSchemaAction = createAsyncAction(
+  'DELETE_SCHEMA__REQUEST',
+  'DELETE_SCHEMA__SUCCESS',
+  'DELETE_SCHEMA__FAILURE'
+)<undefined, string, { alert?: FailurePayload }>();
+
 export const dismissAlert = createAction('DISMISS_ALERT')<string>();
 export const dismissAlert = createAction('DISMISS_ALERT')<string>();
 
 
 export const fetchConnectsAction = createAsyncAction(
 export const fetchConnectsAction = createAsyncAction(

+ 22 - 0
kafka-ui-react-app/src/redux/actions/thunks/schemas.ts

@@ -68,3 +68,25 @@ export const createSchema = (
     dispatch(actions.createSchemaAction.failure({ alert }));
     dispatch(actions.createSchemaAction.failure({ alert }));
   }
   }
 };
 };
+
+export const deleteSchema = (
+  clusterName: ClusterName,
+  subject: string
+): PromiseThunkResult => async (dispatch) => {
+  dispatch(actions.deleteSchemaAction.request());
+  try {
+    await schemasApiClient.deleteSchema({
+      clusterName,
+      subject,
+    });
+    dispatch(actions.deleteSchemaAction.success(subject));
+  } catch (error) {
+    const response = await getResponse(error);
+    const alert: FailurePayload = {
+      subject: ['schema', subject].join('-'),
+      title: `Schema ${subject}`,
+      response,
+    };
+    dispatch(actions.deleteSchemaAction.failure({ alert }));
+  }
+};

+ 29 - 0
kafka-ui-react-app/src/redux/reducers/schemas/__test__/reducer.spec.ts

@@ -1,5 +1,7 @@
+import { SchemaSubject, SchemaType } from 'generated-sources';
 import {
 import {
   createSchemaAction,
   createSchemaAction,
+  deleteSchemaAction,
   fetchSchemasByClusterNameAction,
   fetchSchemasByClusterNameAction,
   fetchSchemaVersionsAction,
   fetchSchemaVersionsAction,
 } from 'redux/actions';
 } from 'redux/actions';
@@ -46,4 +48,31 @@ describe('Schemas reducer', () => {
       reducer(undefined, createSchemaAction.success(schemaVersionsPayload[0]))
       reducer(undefined, createSchemaAction.success(schemaVersionsPayload[0]))
     ).toMatchSnapshot();
     ).toMatchSnapshot();
   });
   });
+
+  it('deletes the schema from the list on DELETE_SCHEMA__SUCCESS', () => {
+    const schema: SchemaSubject = {
+      subject: 'name',
+      version: '1',
+      id: 1,
+      schema: '{}',
+      compatibilityLevel: 'BACKWARD',
+      schemaType: SchemaType.AVRO,
+    };
+    expect(
+      reducer(
+        {
+          byName: {
+            [schema.subject]: schema,
+          },
+          allNames: [schema.subject],
+          currentSchemaVersions: [],
+        },
+        deleteSchemaAction.success(schema.subject)
+      )
+    ).toEqual({
+      byName: {},
+      allNames: [],
+      currentSchemaVersions: [],
+    });
+  });
 });
 });

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

@@ -1,5 +1,7 @@
 import { SchemaSubject } from 'generated-sources';
 import { SchemaSubject } from 'generated-sources';
 import { Action, SchemasState } from 'redux/interfaces';
 import { Action, SchemasState } from 'redux/interfaces';
+import * as actions from 'redux/actions';
+import { getType } from 'typesafe-actions';
 
 
 export const initialState: SchemasState = {
 export const initialState: SchemasState = {
   byName: {},
   byName: {},
@@ -44,6 +46,18 @@ const addToSchemaList = (
   return newState;
   return newState;
 };
 };
 
 
+const deleteFromSchemaList = (
+  state: SchemasState,
+  payload: string
+): SchemasState => {
+  const newState: SchemasState = {
+    ...state,
+  };
+  delete newState.byName[payload];
+  newState.allNames = newState.allNames.filter((name) => name !== payload);
+  return newState;
+};
+
 const reducer = (state = initialState, action: Action): SchemasState => {
 const reducer = (state = initialState, action: Action): SchemasState => {
   switch (action.type) {
   switch (action.type) {
     case 'GET_CLUSTER_SCHEMAS__SUCCESS':
     case 'GET_CLUSTER_SCHEMAS__SUCCESS':
@@ -52,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 getType(actions.deleteSchemaAction.success):
+      return deleteFromSchemaList(state, action.payload);
     default:
     default:
       return state;
       return state;
   }
   }

+ 1 - 1
kafka-ui-react-app/src/redux/reducers/topics/__tests__/reducer.spec.ts

@@ -11,7 +11,7 @@ describe('topics reducer', () => {
       reducer(
       reducer(
         {
         {
           byName: {
           byName: {
-            topic,
+            [topic.name]: topic,
           },
           },
           allNames: [topic.name],
           allNames: [topic.name],
           messages: [],
           messages: [],