Skip to content

Commit

Permalink
[App Search] Schema reindex job errors view (elastic#99941)
Browse files Browse the repository at this point in the history
* Set up ReindexJobLogic

* Update ReindexJob view with load behavior & shared SchemaErrorsAccordion
  • Loading branch information
Constance authored May 12, 2021
1 parent 537be25 commit ea75823
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,72 @@
* 2.0.
*/

import { setMockValues, setMockActions } from '../../../../__mocks__';
import '../../../../__mocks__/react_router_history.mock';
import '../../../../__mocks__/shallow_useeffect.mock';
import '../../../__mocks__/engine_logic.mock';

import React from 'react';
import { useParams } from 'react-router-dom';

import { shallow } from 'enzyme';

import { Loading } from '../../../../shared/loading';
import { SchemaErrorsAccordion } from '../../../../shared/schema';

import { ReindexJob } from './';

describe('ReindexJob', () => {
const props = {
schemaBreadcrumb: ['Engines', 'some-engine', 'Schema'],
};
const values = {
dataLoading: false,
fieldCoercionErrors: {},
engine: {
schema: {
some_field: 'text',
},
},
};
const actions = {
loadReindexJob: jest.fn(),
};

beforeEach(() => {
(useParams as jest.Mock).mockReturnValueOnce({ reindexJobId: 'abc1234567890' });
setMockValues(values);
setMockActions(actions);
});

it('renders', () => {
const wrapper = shallow(<ReindexJob {...props} />);

expect(wrapper.find(SchemaErrorsAccordion)).toHaveLength(1);
expect(wrapper.find(SchemaErrorsAccordion).prop('generateViewPath')).toHaveLength(1);
});

it('calls loadReindexJob on page load', () => {
shallow(<ReindexJob {...props} />);
// TODO: Check child components

expect(actions.loadReindexJob).toHaveBeenCalledWith('abc1234567890');
});

it('renders a loading state', () => {
setMockValues({ ...values, dataLoading: true });
const wrapper = shallow(<ReindexJob {...props} />);

expect(wrapper.find(Loading)).toHaveLength(1);
});

it('renders schema errors with links to document pages', () => {
const wrapper = shallow(<ReindexJob {...props} />);
const generateViewPath = wrapper
.find(SchemaErrorsAccordion)
.prop('generateViewPath') as Function;

expect(generateViewPath('some-document-id')).toEqual(
'/engines/some-engine/documents/some-document-id'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,42 @@
* 2.0.
*/

import React from 'react';
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';

import { useActions, useValues } from 'kea';

import { EuiPageHeader, EuiPageContentBody } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { FlashMessages } from '../../../../shared/flash_messages';
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs';
import { Loading } from '../../../../shared/loading';
import { SchemaErrorsAccordion } from '../../../../shared/schema';

import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../../routes';
import { EngineLogic, generateEnginePath } from '../../engine';

import { ReindexJobLogic } from './reindex_job_logic';

interface Props {
schemaBreadcrumb: BreadcrumbTrail;
}

export const ReindexJob: React.FC<Props> = ({ schemaBreadcrumb }) => {
const { reindexJobId } = useParams() as { reindexJobId: string };
const { loadReindexJob } = useActions(ReindexJobLogic);
const { dataLoading, fieldCoercionErrors } = useValues(ReindexJobLogic);
const {
engine: { schema },
} = useValues(EngineLogic);

useEffect(() => {
loadReindexJob(reindexJobId);
}, [reindexJobId]);

if (dataLoading) return <Loading />;

return (
<>
Expand All @@ -39,7 +59,15 @@ export const ReindexJob: React.FC<Props> = ({ schemaBreadcrumb }) => {
)}
/>
<FlashMessages />
<EuiPageContentBody>{reindexJobId}</EuiPageContentBody>
<EuiPageContentBody>
<SchemaErrorsAccordion
fieldCoercionErrors={fieldCoercionErrors}
schema={schema!}
generateViewPath={(documentId: string) =>
generateEnginePath(ENGINE_DOCUMENT_DETAIL_PATH, { documentId })
}
/>
</EuiPageContentBody>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../../__mocks__';
import '../../../__mocks__/engine_logic.mock';

import { nextTick } from '@kbn/test/jest';

import { ReindexJobLogic } from './reindex_job_logic';

describe('ReindexJobLogic', () => {
const { mount } = new LogicMounter(ReindexJobLogic);
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;

const MOCK_RESPONSE = {
fieldCoercionErrors: {
some_erroring_field: [
{
external_id: 'document-1',
error: "Value 'some text' cannot be parsed as a number",
},
],
another_erroring_field: [
{
external_id: 'document-2',
error: "Value '123' cannot be parsed as a date",
},
],
},
};

const DEFAULT_VALUES = {
dataLoading: true,
fieldCoercionErrors: {},
};

beforeEach(() => {
jest.clearAllMocks();
});

it('has expected default values', () => {
mount();
expect(ReindexJobLogic.values).toEqual(DEFAULT_VALUES);
});

describe('actions', () => {
describe('onLoadSuccess', () => {
it('stores fieldCoercionErrors state and sets dataLoading to false', () => {
mount({ fieldCoercionErrors: {}, dataLoading: true });

ReindexJobLogic.actions.onLoadSuccess(MOCK_RESPONSE);

expect(ReindexJobLogic.values).toEqual({
...DEFAULT_VALUES,
dataLoading: false,
fieldCoercionErrors: MOCK_RESPONSE.fieldCoercionErrors,
});
});
});

describe('onLoadError', () => {
it('sets dataLoading to false', () => {
mount({ dataLoading: true });

ReindexJobLogic.actions.onLoadError();

expect(ReindexJobLogic.values).toEqual({
...DEFAULT_VALUES,
dataLoading: false,
});
});
});
});

describe('listeners', () => {
describe('loadReindexJob', () => {
it('sets dataLoading to true', () => {
mount({ dataLoading: false });

ReindexJobLogic.actions.loadReindexJob('some-job-id');

expect(ReindexJobLogic.values).toEqual({
...DEFAULT_VALUES,
dataLoading: true,
});
});

it('should make an API call and then set schema state', async () => {
http.get.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE));
mount();
jest.spyOn(ReindexJobLogic.actions, 'onLoadSuccess');

ReindexJobLogic.actions.loadReindexJob('some-job-id');
await nextTick();

expect(http.get).toHaveBeenCalledWith(
'/api/app_search/engines/some-engine/reindex_job/some-job-id'
);
expect(ReindexJobLogic.actions.onLoadSuccess).toHaveBeenCalledWith(MOCK_RESPONSE);
});

it('handles errors', async () => {
http.get.mockReturnValueOnce(Promise.reject('error'));
mount();
jest.spyOn(ReindexJobLogic.actions, 'onLoadError');

ReindexJobLogic.actions.loadReindexJob('some-bad-id');
await nextTick();

expect(flashAPIErrors).toHaveBeenCalledWith('error');
expect(ReindexJobLogic.actions.onLoadError).toHaveBeenCalled();
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { kea, MakeLogicType } from 'kea';

import { flashAPIErrors } from '../../../../shared/flash_messages';
import { HttpLogic } from '../../../../shared/http';
import { EngineLogic } from '../../engine';

import { ReindexJobApiResponse } from '../types';

export interface ReindexJobValues {
dataLoading: boolean;
fieldCoercionErrors: ReindexJobApiResponse['fieldCoercionErrors'];
}

export interface ReindexJobActions {
loadReindexJob(id: string): string;
onLoadSuccess(response: ReindexJobApiResponse): ReindexJobApiResponse;
onLoadError(): void;
}

export const ReindexJobLogic = kea<MakeLogicType<ReindexJobValues, ReindexJobActions>>({
path: ['enterprise_search', 'app_search', 'reindex_job_logic'],
actions: {
loadReindexJob: (id) => id,
onLoadSuccess: (response) => response,
onLoadError: true,
},
reducers: {
dataLoading: [
true,
{
loadReindexJob: () => true,
onLoadSuccess: () => false,
onLoadError: () => false,
},
],
fieldCoercionErrors: [
{},
{
onLoadSuccess: (_, { fieldCoercionErrors }) => fieldCoercionErrors,
},
],
},
listeners: ({ actions }) => ({
loadReindexJob: async (id) => {
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;

try {
const response = await http.get(`/api/app_search/engines/${engineName}/reindex_job/${id}`);
actions.onLoadSuccess(response);
} catch (e) {
flashAPIErrors(e);
actions.onLoadError();
}
},
}),
});

0 comments on commit ea75823

Please sign in to comment.