Skip to content

Commit

Permalink
[Enterprise Search] Migrate shared Indexing Status component (elastic…
Browse files Browse the repository at this point in the history
…#84571)

* Add react-motion package

This is needed to animate the loading progress bar in the Enterprise Search schema views

* Add shared interface

* Migrate IndexingStatusContent component

This is a straight copy/paste with only linting changes and tests added

* Migrate IndexingStatusErrors component

This is a copy/paste with linting changes and tests added. Also changed out the Link component to our EuiLinkTo component for internal routing

* Migrate IndexingStatus component

This is a straight copy/paste with only linting changes and tests added

* Migrate IndexingStatusFetcher component

This is a copy/paste with some modifications. The http/axios code has been removed in favor of the HTTPLogic in Kibana.

This is a WIP that I am merging to master until I can get it working in the UI. Without either Schema component in the UIs for App Search or Workplace Search this is only a POC. Will not backport until this is verified working and have written tests.

* Add i18n

* Revert "Add react-motion package"

This reverts commit 92db929.

* Remove motion dependency

* Update copy

Co-authored-by: Constance <constancecchen@users.noreply.github.com>

* Refactor per code review

- Remove stui classes
- Inline status

Co-authored-by: Constance <constancecchen@users.noreply.github.com>
  • Loading branch information
scottybollinger and Constance committed Dec 1, 2020
1 parent 1fd2533 commit 461ebb1
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';

export const INDEXING_STATUS_PROGRESS_TITLE = i18n.translate(
'xpack.enterpriseSearch.indexingStatus.progress.title',
{
defaultMessage: 'Indexing progress',
}
);

export const INDEXING_STATUS_HAS_ERRORS_TITLE = i18n.translate(
'xpack.enterpriseSearch.indexingStatus.hasErrors.title',
{
defaultMessage: 'Several documents have field conversion errors.',
}
);

export const INDEXING_STATUS_HAS_ERRORS_BUTTON = i18n.translate(
'xpack.enterpriseSearch.indexingStatus.hasErrors.button',
{
defaultMessage: 'View errors',
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { IndexingStatus } from './indexing_status';
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { shallow } from 'enzyme';

import { EuiPanel } from '@elastic/eui';

import { IndexingStatusContent } from './indexing_status_content';
import { IndexingStatusErrors } from './indexing_status_errors';
import { IndexingStatusFetcher } from './indexing_status_fetcher';
import { IndexingStatus } from './indexing_status';

describe('IndexingStatus', () => {
const getItemDetailPath = jest.fn();
const getStatusPath = jest.fn();
const onComplete = jest.fn();
const setGlobalIndexingStatus = jest.fn();

const props = {
percentageComplete: 50,
numDocumentsWithErrors: 1,
activeReindexJobId: 12,
viewLinkPath: '/path',
itemId: '1',
getItemDetailPath,
getStatusPath,
onComplete,
setGlobalIndexingStatus,
};

it('renders', () => {
const wrapper = shallow(<IndexingStatus {...props} />);
const fetcher = wrapper.find(IndexingStatusFetcher).prop('children')(
props.percentageComplete,
props.numDocumentsWithErrors
);

expect(shallow(fetcher).find(EuiPanel)).toHaveLength(1);
expect(shallow(fetcher).find(IndexingStatusContent)).toHaveLength(1);
});

it('renders errors', () => {
const wrapper = shallow(<IndexingStatus {...props} percentageComplete={100} />);
const fetcher = wrapper.find(IndexingStatusFetcher).prop('children')(100, 1);
expect(shallow(fetcher).find(IndexingStatusErrors)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';

import { EuiPanel, EuiSpacer } from '@elastic/eui';

import { IndexingStatusContent } from './indexing_status_content';
import { IndexingStatusErrors } from './indexing_status_errors';
import { IndexingStatusFetcher } from './indexing_status_fetcher';

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

export interface IIndexingStatusProps extends IIndexingStatus {
viewLinkPath: string;
itemId: string;
getItemDetailPath?(itemId: string): string;
getStatusPath(itemId: string, activeReindexJobId: number): string;
onComplete(numDocumentsWithErrors: number): void;
setGlobalIndexingStatus?(activeReindexJob: IIndexingStatus): void;
}

export const IndexingStatus: React.FC<IIndexingStatusProps> = (props) => (
<IndexingStatusFetcher {...props}>
{(percentageComplete, numDocumentsWithErrors) => (
<div>
{percentageComplete < 100 && (
<EuiPanel paddingSize="l" hasShadow>
<IndexingStatusContent percentageComplete={percentageComplete} />
</EuiPanel>
)}
{percentageComplete === 100 && numDocumentsWithErrors > 0 && (
<>
<EuiSpacer />
<IndexingStatusErrors viewLinkPath={props.viewLinkPath} />
</>
)}
</div>
)}
</IndexingStatusFetcher>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { shallow } from 'enzyme';

import { EuiProgress, EuiTitle } from '@elastic/eui';

import { IndexingStatusContent } from './indexing_status_content';

describe('IndexingStatusContent', () => {
it('renders', () => {
const wrapper = shallow(<IndexingStatusContent percentageComplete={50} />);

expect(wrapper.find(EuiTitle)).toHaveLength(1);
expect(wrapper.find(EuiProgress)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';

import { EuiProgress, EuiSpacer, EuiTitle } from '@elastic/eui';

import { INDEXING_STATUS_PROGRESS_TITLE } from './constants';

interface IIndexingStatusContentProps {
percentageComplete: number;
}

export const IndexingStatusContent: React.FC<IIndexingStatusContentProps> = ({
percentageComplete,
}) => (
<div data-test-subj="IndexingStatusProgressMeter">
<EuiTitle size="s">
<h3>{INDEXING_STATUS_PROGRESS_TITLE}</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiProgress color="primary" size="m" value={percentageComplete} max={100} />
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { shallow } from 'enzyme';

import { EuiCallOut, EuiButton } from '@elastic/eui';

import { EuiLinkTo } from '../react_router_helpers';

import { IndexingStatusErrors } from './indexing_status_errors';

describe('IndexingStatusErrors', () => {
it('renders', () => {
const wrapper = shallow(<IndexingStatusErrors viewLinkPath="/path" />);

expect(wrapper.find(EuiButton)).toHaveLength(1);
expect(wrapper.find(EuiCallOut)).toHaveLength(1);
expect(wrapper.find(EuiLinkTo)).toHaveLength(1);
expect(wrapper.find(EuiLinkTo).prop('to')).toEqual('/path');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';

import { EuiButton, EuiCallOut } from '@elastic/eui';

import { EuiLinkTo } from '../react_router_helpers';

import { INDEXING_STATUS_HAS_ERRORS_TITLE, INDEXING_STATUS_HAS_ERRORS_BUTTON } from './constants';

interface IIndexingStatusErrorsProps {
viewLinkPath: string;
}

export const IndexingStatusErrors: React.FC<IIndexingStatusErrorsProps> = ({ viewLinkPath }) => (
<EuiCallOut
color="danger"
iconType="cross"
title="There was an error"
data-test-subj="IndexingStatusErrors"
>
<p>{INDEXING_STATUS_HAS_ERRORS_TITLE}</p>
<EuiButton color="danger" fill={true} size="s" data-test-subj="ViewErrorsButton">
<EuiLinkTo to={viewLinkPath}>{INDEXING_STATUS_HAS_ERRORS_BUTTON}</EuiLinkTo>
</EuiButton>
</EuiCallOut>
);
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;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useEffect, useState, useRef } from 'react';

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

interface IIndexingStatusFetcherProps {
activeReindexJobId: number;
itemId: string;
percentageComplete: number;
numDocumentsWithErrors: number;
onComplete?(numDocumentsWithErrors: number): void;
getStatusPath(itemId: string, activeReindexJobId: number): string;
children(percentageComplete: number, numDocumentsWithErrors: number): JSX.Element;
}

export const IndexingStatusFetcher: React.FC<IIndexingStatusFetcherProps> = ({
activeReindexJobId,
children,
getStatusPath,
itemId,
numDocumentsWithErrors,
onComplete,
percentageComplete = 0,
}) => {
const [indexingStatus, setIndexingStatus] = useState({
numDocumentsWithErrors,
percentageComplete,
});
const pollingInterval = useRef<number>();

useEffect(() => {
pollingInterval.current = window.setInterval(async () => {
try {
const response = await HttpLogic.values.http.get(getStatusPath(itemId, activeReindexJobId));
if (response.percentageComplete >= 100) {
clearInterval(pollingInterval.current);
}
setIndexingStatus({
percentageComplete: response.percentageComplete,
numDocumentsWithErrors: response.numDocumentsWithErrors,
});
if (response.percentageComplete >= 100 && onComplete) {
onComplete(response.numDocumentsWithErrors);
}
} catch (e) {
flashAPIErrors(e);
}
}, 3000);

return () => {
if (pollingInterval.current) {
clearInterval(pollingInterval.current);
}
};
}, []);

return children(indexingStatus.percentageComplete, indexingStatus.numDocumentsWithErrors);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export interface IIndexingStatus {
percentageComplete: number;
numDocumentsWithErrors: number;
activeReindexJobId: number;
}

0 comments on commit 461ebb1

Please sign in to comment.