Fix the problem with instant redirect (#390)

* Fix the problem with instant redirect

* Rewrite updateSchema thunk
This commit is contained in:
Alexander Krivonosov 2021-05-01 09:08:32 +03:00 committed by GitHub
parent f3c0866940
commit 42a1c97686
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 112 additions and 139 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,15 +4,25 @@ import 'ace-builds/src-noconflict/mode-json5';
import 'ace-builds/src-noconflict/theme-textmate';
import React from 'react';
const JSONEditor: React.FC<IAceEditorProps> = (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;

View file

@ -122,53 +122,22 @@ describe('Thunks', () => {
expect(error.status).toEqual(404);
expect(store.getActions()).toEqual([
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', () => {
it('fires DELETE_SCHEMA__SUCCESS on success', async () => {
fetchMock.deleteOnce(
@ -203,7 +172,7 @@ describe('Thunks', () => {
});
describe('updateSchema', () => {
it('calls createSchema', () => {
it('calls PATCH_SCHEMA__REQUEST', () => {
store.dispatch(
thunks.updateSchema(
fixtures.schema,
@ -215,23 +184,7 @@ describe('Thunks', () => {
)
);
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(),
]);
});
});

View file

@ -130,11 +130,11 @@ export const createSchemaAction = createAsyncAction(
'POST_SCHEMA__FAILURE'
)<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(
'DELETE_SCHEMA__REQUEST',

View file

@ -68,32 +68,7 @@ export const createSchema = (
response,
};
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,
subject: string
): PromiseThunkResult => async (dispatch) => {
dispatch(actions.updateSchemaAction.request());
try {
let schema: SchemaSubject = latestSchema;
if (
(newSchema &&
!isEqual(JSON.parse(latestSchema.schema), JSON.parse(newSchema))) ||
newSchemaType !== latestSchema.schemaType
) {
await dispatch(
createSchema(clusterName, {
schema = await schemasApiClient.createNewSchema({
clusterName,
newSchemaSubject: {
...latestSchema,
schema: newSchema || latestSchema.schema,
schemaType: newSchemaType || latestSchema.schemaType,
})
);
},
});
}
if (newCompatibilityLevel !== latestSchema.compatibilityLevel) {
await dispatch(
updateSchemaCompatibilityLevel(
await schemasApiClient.updateSchemaCompatibilityLevel({
clusterName,
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 = (

View file

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