Skip to content

Commit

Permalink
Implement updating registry compatibility level (provectus#391)
Browse files Browse the repository at this point in the history
* Implement updating registry compatibility level
  • Loading branch information
GneyHabub committed May 1, 2021
1 parent 4451b66 commit 2a2e81d
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
import PageLoader from 'components/common/PageLoader/PageLoader';
import { CompatibilityLevelCompatibilityEnum } from 'generated-sources';
import React from 'react';
import { useForm } from 'react-hook-form';
import { useParams } from 'react-router-dom';
import { ClusterName } from 'redux/interfaces';

export interface GlobalSchemaSelectorProps {
globalSchemaCompatibilityLevel?: CompatibilityLevelCompatibilityEnum;
updateGlobalSchemaCompatibilityLevel: (
clusterName: ClusterName,
compatibilityLevel: CompatibilityLevelCompatibilityEnum
) => Promise<void>;
}

const GlobalSchemaSelector: React.FC<GlobalSchemaSelectorProps> = ({
globalSchemaCompatibilityLevel,
updateGlobalSchemaCompatibilityLevel,
}) => {
const { clusterName } = useParams<{ clusterName: string }>();

const {
register,
handleSubmit,
formState: { isSubmitting },
} = useForm();

const [
isUpdateCompatibilityConfirmationVisible,
setUpdateCompatibilityConfirmationVisible,
] = React.useState(false);

const onCompatibilityLevelUpdate = async ({
compatibilityLevel,
}: {
compatibilityLevel: CompatibilityLevelCompatibilityEnum;
}) => {
await updateGlobalSchemaCompatibilityLevel(clusterName, compatibilityLevel);
setUpdateCompatibilityConfirmationVisible(false);
};

return (
<div className="level-item">
<h5 className="is-5 mr-2">Global Compatibility Level: </h5>
<div className="select mr-2">
<select
name="compatibilityLevel"
defaultValue={globalSchemaCompatibilityLevel}
ref={register()}
onChange={() => setUpdateCompatibilityConfirmationVisible(true)}
>
{Object.keys(CompatibilityLevelCompatibilityEnum).map(
(level: string) => (
<option key={level} value={level}>
{level}
</option>
)
)}
</select>
</div>
<ConfirmationModal
isOpen={isUpdateCompatibilityConfirmationVisible}
onCancel={() => setUpdateCompatibilityConfirmationVisible(false)}
onConfirm={handleSubmit(onCompatibilityLevelUpdate)}
>
{isSubmitting ? (
<PageLoader />
) : (
`Are you sure you want to update the global compatibility level?
This may affect the compatibility levels of the schemas.`
)}
</ConfirmationModal>
</div>
);
};

export default GlobalSchemaSelector;
28 changes: 26 additions & 2 deletions kafka-ui-react-app/src/components/Schemas/List/List.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React from 'react';
import { SchemaSubject } from 'generated-sources';
import {
CompatibilityLevelCompatibilityEnum,
SchemaSubject,
} from 'generated-sources';
import { Link, useParams } from 'react-router-dom';
import { clusterSchemaNewPath } from 'lib/paths';
import { ClusterName } from 'redux/interfaces';
Expand All @@ -8,32 +11,53 @@ import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import ClusterContext from 'components/contexts/ClusterContext';

import ListItem from './ListItem';
import GlobalSchemaSelector from './GlobalSchemaSelector';

export interface ListProps {
schemas: SchemaSubject[];
isFetching: boolean;
isGlobalSchemaCompatibilityLevelFetched: boolean;
globalSchemaCompatibilityLevel?: CompatibilityLevelCompatibilityEnum;
fetchSchemasByClusterName: (clusterName: ClusterName) => void;
fetchGlobalSchemaCompatibilityLevel: (
clusterName: ClusterName
) => Promise<void>;
updateGlobalSchemaCompatibilityLevel: (
clusterName: ClusterName,
compatibilityLevel: CompatibilityLevelCompatibilityEnum
) => Promise<void>;
}

const List: React.FC<ListProps> = ({
schemas,
isFetching,
globalSchemaCompatibilityLevel,
isGlobalSchemaCompatibilityLevelFetched,
fetchSchemasByClusterName,
fetchGlobalSchemaCompatibilityLevel,
updateGlobalSchemaCompatibilityLevel,
}) => {
const { isReadOnly } = React.useContext(ClusterContext);
const { clusterName } = useParams<{ clusterName: string }>();

React.useEffect(() => {
fetchSchemasByClusterName(clusterName);
fetchGlobalSchemaCompatibilityLevel(clusterName);
}, [fetchSchemasByClusterName, clusterName]);

return (
<div className="section">
<Breadcrumb>Schema Registry</Breadcrumb>
<div className="box">
<div className="level">
{!isReadOnly && (
{!isReadOnly && isGlobalSchemaCompatibilityLevelFetched && (
<div className="level-item level-right">
<GlobalSchemaSelector
globalSchemaCompatibilityLevel={globalSchemaCompatibilityLevel}
updateGlobalSchemaCompatibilityLevel={
updateGlobalSchemaCompatibilityLevel
}
/>
<Link
className="button is-primary"
to={clusterSchemaNewPath(clusterName)}
Expand Down
14 changes: 13 additions & 1 deletion kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { connect } from 'react-redux';
import { RootState } from 'redux/interfaces';
import { fetchSchemasByClusterName } from 'redux/actions';
import {
fetchSchemasByClusterName,
fetchGlobalSchemaCompatibilityLevel,
updateGlobalSchemaCompatibilityLevel,
} from 'redux/actions';
import {
getIsSchemaListFetching,
getSchemaList,
getGlobalSchemaCompatibilityLevel,
getGlobalSchemaCompatibilityLevelFetched,
} from 'redux/reducers/schemas/selectors';

import List from './List';

const mapStateToProps = (state: RootState) => ({
isFetching: getIsSchemaListFetching(state),
schemas: getSchemaList(state),
globalSchemaCompatibilityLevel: getGlobalSchemaCompatibilityLevel(state),
isGlobalSchemaCompatibilityLevelFetched: getGlobalSchemaCompatibilityLevelFetched(
state
),
});

const mapDispatchToProps = {
fetchGlobalSchemaCompatibilityLevel,
fetchSchemasByClusterName,
updateGlobalSchemaCompatibilityLevel,
};

export default connect(mapStateToProps, mapDispatchToProps)(List);
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ describe('List', () => {
<List
isFetching
fetchSchemasByClusterName={jest.fn()}
isGlobalSchemaCompatibilityLevelFetched
fetchGlobalSchemaCompatibilityLevel={jest.fn()}
updateGlobalSchemaCompatibilityLevel={jest.fn()}
schemas={[]}
{...props}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,101 @@ describe('Thunks', () => {
]);
});
});

describe('fetchGlobalSchemaCompatibilityLevel', () => {
it('calls GET_GLOBAL_SCHEMA_COMPATIBILITY__REQUEST on the fucntion call', () => {
store.dispatch(thunks.fetchGlobalSchemaCompatibilityLevel(clusterName));
expect(store.getActions()).toEqual([
actions.fetchGlobalSchemaCompatibilityLevelAction.request(),
]);
});

it('calls GET_GLOBAL_SCHEMA_COMPATIBILITY__SUCCESS on a successful API call', async () => {
fetchMock.getOnce(`/api/clusters/${clusterName}/schemas/compatibility`, {
compatibility: CompatibilityLevelCompatibilityEnum.FORWARD,
});
await store.dispatch(
thunks.fetchGlobalSchemaCompatibilityLevel(clusterName)
);
expect(store.getActions()).toEqual([
actions.fetchGlobalSchemaCompatibilityLevelAction.request(),
actions.fetchGlobalSchemaCompatibilityLevelAction.success(
CompatibilityLevelCompatibilityEnum.FORWARD
),
]);
});

it('calls GET_GLOBAL_SCHEMA_COMPATIBILITY__FAILURE on an unsuccessful API call', async () => {
fetchMock.getOnce(
`/api/clusters/${clusterName}/schemas/compatibility`,
404
);
try {
await store.dispatch(
thunks.fetchGlobalSchemaCompatibilityLevel(clusterName)
);
} catch (error) {
expect(error.status).toEqual(404);
expect(store.getActions()).toEqual([
actions.fetchGlobalSchemaCompatibilityLevelAction.request(),
actions.fetchGlobalSchemaCompatibilityLevelAction.failure(),
]);
}
});
});

describe('updateGlobalSchemaCompatibilityLevel', () => {
const compatibilityLevel = CompatibilityLevelCompatibilityEnum.FORWARD;
it('calls POST_GLOBAL_SCHEMA_COMPATIBILITY__REQUEST on the fucntion call', () => {
store.dispatch(
thunks.updateGlobalSchemaCompatibilityLevel(
clusterName,
compatibilityLevel
)
);
expect(store.getActions()).toEqual([
actions.updateGlobalSchemaCompatibilityLevelAction.request(),
]);
});

it('calls POST_GLOBAL_SCHEMA_COMPATIBILITY__SUCCESS on a successful API call', async () => {
fetchMock.putOnce(
`/api/clusters/${clusterName}/schemas/compatibility`,
200
);
await store.dispatch(
thunks.updateGlobalSchemaCompatibilityLevel(
clusterName,
compatibilityLevel
)
);
expect(store.getActions()).toEqual([
actions.updateGlobalSchemaCompatibilityLevelAction.request(),
actions.updateGlobalSchemaCompatibilityLevelAction.success(
CompatibilityLevelCompatibilityEnum.FORWARD
),
]);
});

it('calls POST_GLOBAL_SCHEMA_COMPATIBILITY__FAILURE on an unsuccessful API call', async () => {
fetchMock.putOnce(
`/api/clusters/${clusterName}/schemas/compatibility`,
404
);
try {
await store.dispatch(
thunks.updateGlobalSchemaCompatibilityLevel(
clusterName,
compatibilityLevel
)
);
} catch (error) {
expect(error.status).toEqual(404);
expect(store.getActions()).toEqual([
actions.updateGlobalSchemaCompatibilityLevelAction.request(),
actions.updateGlobalSchemaCompatibilityLevelAction.failure(),
]);
}
});
});
});
13 changes: 13 additions & 0 deletions kafka-ui-react-app/src/redux/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ConsumerGroup,
ConsumerGroupDetails,
SchemaSubject,
CompatibilityLevelCompatibilityEnum,
} from 'generated-sources';

export const fetchClusterStatsAction = createAsyncAction(
Expand Down Expand Up @@ -118,6 +119,18 @@ export const fetchSchemasByClusterNameAction = createAsyncAction(
'GET_CLUSTER_SCHEMAS__FAILURE'
)<undefined, SchemaSubject[], undefined>();

export const fetchGlobalSchemaCompatibilityLevelAction = createAsyncAction(
'GET_GLOBAL_SCHEMA_COMPATIBILITY__REQUEST',
'GET_GLOBAL_SCHEMA_COMPATIBILITY__SUCCESS',
'GET_GLOBAL_SCHEMA_COMPATIBILITY__FAILURE'
)<undefined, CompatibilityLevelCompatibilityEnum, undefined>();

export const updateGlobalSchemaCompatibilityLevelAction = createAsyncAction(
'PUT_GLOBAL_SCHEMA_COMPATIBILITY__REQUEST',
'PUT_GLOBAL_SCHEMA_COMPATIBILITY__SUCCESS',
'PUT_GLOBAL_SCHEMA_COMPATIBILITY__FAILURE'
)<undefined, CompatibilityLevelCompatibilityEnum, undefined>();

export const fetchSchemaVersionsAction = createAsyncAction(
'GET_SCHEMA_VERSIONS__REQUEST',
'GET_SCHEMA_VERSIONS__SUCCESS',
Expand Down
38 changes: 38 additions & 0 deletions kafka-ui-react-app/src/redux/actions/thunks/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,44 @@ export const fetchSchemaVersions = (
}
};

export const fetchGlobalSchemaCompatibilityLevel = (
clusterName: ClusterName
): PromiseThunkResult<void> => async (dispatch) => {
dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request());
try {
const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({
clusterName,
});
dispatch(
actions.fetchGlobalSchemaCompatibilityLevelAction.success(
result.compatibility
)
);
} catch (e) {
dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.failure());
}
};

export const updateGlobalSchemaCompatibilityLevel = (
clusterName: ClusterName,
compatibilityLevel: CompatibilityLevelCompatibilityEnum
): PromiseThunkResult<void> => async (dispatch) => {
dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request());
try {
await schemasApiClient.updateGlobalSchemaCompatibilityLevel({
clusterName,
compatibilityLevel: { compatibility: compatibilityLevel },
});
dispatch(
actions.updateGlobalSchemaCompatibilityLevelAction.success(
compatibilityLevel
)
);
} catch (e) {
dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.failure());
}
};

export const createSchema = (
clusterName: ClusterName,
newSchemaSubject: NewSchemaSubject
Expand Down
7 changes: 6 additions & 1 deletion kafka-ui-react-app/src/redux/interfaces/schema.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { NewSchemaSubject, SchemaSubject } from 'generated-sources';
import {
CompatibilityLevelCompatibilityEnum,
NewSchemaSubject,
SchemaSubject,
} from 'generated-sources';

export type SchemaName = string;

export interface SchemasState {
byName: { [subject: string]: SchemaSubject };
allNames: SchemaName[];
currentSchemaVersions: SchemaSubject[];
globalSchemaCompatibilityLevel?: CompatibilityLevelCompatibilityEnum;
}

export interface NewSchemaSubjectRaw extends NewSchemaSubject {
Expand Down
Loading

0 comments on commit 2a2e81d

Please sign in to comment.