Implement deleting schemas (#366)

This commit is contained in:
Alexander Krivonosov 2021-04-13 13:30:43 +03:00 committed by GitHub
parent d471759b79
commit 3945845c37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 134 additions and 6 deletions

View file

@ -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>

View file

@ -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(

View file

@ -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();
}); });

View file

@ -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"
> >

View file

@ -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({}),
]);
}
});
});
}); });

View file

@ -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(

View file

@ -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 }));
}
};

View file

@ -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: [],
});
});
}); });

View file

@ -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;
} }

View file

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