[ML] Improved error handling for data frame pivot wizard (#35490) (#35554)

Improves the error handling of the form elements and requests in the data frame pivot wizard.
This commit is contained in:
Walter Rafelsberger 2019-04-26 16:05:22 +02:00 committed by GitHub
parent 534d8c37ea
commit 5d5bfced0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 527 additions and 116 deletions

View file

@ -94,6 +94,10 @@ export interface DataFrameRequest extends DataFramePreviewRequest {
};
}
export interface DataFrameJobConfig extends DataFrameRequest {
id: string;
}
export const pivotSupportedAggs = [
PIVOT_SUPPORTED_AGGS.AVG,
PIVOT_SUPPORTED_AGGS.CARDINALITY,

View file

@ -4,20 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiInMemoryTable, EuiPanel, EuiProgress, EuiTitle, SortDirection } from '@elastic/eui';
import { ml } from '../../../services/ml_api_service';
import {
getDataFramePreviewRequest,
IndexPatternContext,
OptionsDataElement,
SimpleQuery,
} from '../../common';
EuiCallOut,
EuiInMemoryTable,
EuiPanel,
EuiProgress,
EuiTitle,
SortDirection,
} from '@elastic/eui';
import { IndexPatternContext, OptionsDataElement, SimpleQuery } from '../../common';
import { PIVOT_PREVIEW_STATUS, usePivotPreviewData } from './use_pivot_preview_data';
const PreviewTitle = () => (
<EuiTitle size="xs">
<span>
{i18n.translate('xpack.ml.dataframe.pivotPreview.dataFramePivotPreviewTitle', {
defaultMessage: 'Data frame pivot preview',
})}
</span>
</EuiTitle>
);
interface Props {
aggs: OptionsDataElement[];
@ -32,36 +43,57 @@ export const PivotPreview: React.SFC<Props> = React.memo(({ aggs, groupBy, query
return null;
}
const [loading, setLoading] = useState(false);
const [dataFramePreviewData, setDataFramePreviewData] = useState([]);
useEffect(
() => {
if (aggs.length === 0) {
setDataFramePreviewData([]);
return;
}
setLoading(true);
const request = getDataFramePreviewRequest(indexPattern.title, query, groupBy, aggs);
ml.dataFrame
.getDataFrameTransformsPreview(request)
.then((resp: any) => {
setDataFramePreviewData(resp.preview);
setLoading(false);
})
.catch((resp: any) => {
setDataFramePreviewData([]);
setLoading(false);
});
},
[indexPattern.title, aggs, groupBy, query]
const { dataFramePreviewData, errorMessage, status } = usePivotPreviewData(
indexPattern,
query,
aggs,
groupBy
);
if (status === PIVOT_PREVIEW_STATUS.ERROR) {
return (
<EuiPanel grow={false}>
<PreviewTitle />
<EuiCallOut
title={i18n.translate(
'xpack.ml.dataframe.sourceIndexPreview.dataFramePivotPreviewError',
{
defaultMessage: 'An error occurred loading the pivot preview.',
}
)}
color="danger"
iconType="cross"
>
<p>{errorMessage}</p>
</EuiCallOut>
</EuiPanel>
);
}
if (dataFramePreviewData.length === 0) {
return null;
return (
<EuiPanel grow={false}>
<PreviewTitle />
<EuiCallOut
title={i18n.translate(
'xpack.ml.dataframe.sourceIndexPreview.dataFramePivotPreviewNoDataCalloutTitle',
{
defaultMessage: 'Pivot preview not available',
}
)}
color="primary"
>
<p>
{i18n.translate(
'xpack.ml.dataframe.sourceIndexPreview.dataFramePivotPreviewNoDataCalloutBody',
{
defaultMessage: 'Please choose at least one group-by field and aggregation.',
}
)}
</p>
</EuiCallOut>
</EuiPanel>
);
}
const columnKeys = Object.keys(dataFramePreviewData[0]);
@ -97,15 +129,11 @@ export const PivotPreview: React.SFC<Props> = React.memo(({ aggs, groupBy, query
return (
<EuiPanel>
<EuiTitle size="xs">
<span>
{i18n.translate('xpack.ml.dataframe.pivotPreview.dataFramePivotPreviewTitle', {
defaultMessage: 'Data Frame Pivot Preview',
})}
</span>
</EuiTitle>
{loading && <EuiProgress size="xs" color="accent" />}
{!loading && <EuiProgress size="xs" color="accent" max={1} value={0} />}
<PreviewTitle />
{status === PIVOT_PREVIEW_STATUS.LOADING && <EuiProgress size="xs" color="accent" />}
{status !== PIVOT_PREVIEW_STATUS.LOADING && (
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
{dataFramePreviewData.length > 0 && (
<EuiInMemoryTable
items={dataFramePreviewData}

View file

@ -0,0 +1,74 @@
/*
* 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, { SFC } from 'react';
import ReactDOM from 'react-dom';
import { ml } from '../../../services/ml_api_service';
import { SimpleQuery } from '../../common';
import {
PIVOT_PREVIEW_STATUS,
usePivotPreviewData,
UsePivotPreviewDataReturnType,
} from './use_pivot_preview_data';
jest.mock('../../../services/ml_api_service');
type Callback = () => void;
interface TestHookProps {
callback: Callback;
}
const TestHook: SFC<TestHookProps> = ({ callback }) => {
callback();
return null;
};
const testHook = (callback: Callback) => {
const container = document.createElement('div');
document.body.appendChild(container);
ReactDOM.render(<TestHook callback={callback} />, container);
};
const query: SimpleQuery = {
query_string: {
query: '*',
default_operator: 'AND',
},
};
let pivotPreviewObj: UsePivotPreviewDataReturnType;
describe('usePivotPreviewData', () => {
test('indexPattern not defined', () => {
testHook(() => {
pivotPreviewObj = usePivotPreviewData(null, query, [], []);
});
expect(pivotPreviewObj.errorMessage).toBe('');
expect(pivotPreviewObj.status).toBe(PIVOT_PREVIEW_STATUS.UNUSED);
expect(pivotPreviewObj.dataFramePreviewData).toEqual([]);
expect(ml.dataFrame.getDataFrameTransformsPreview).not.toHaveBeenCalled();
});
test('indexPattern set triggers loading', () => {
testHook(() => {
pivotPreviewObj = usePivotPreviewData({ title: 'lorem', fields: [] }, query, [], []);
});
expect(pivotPreviewObj.errorMessage).toBe('');
// ideally this should be LOADING instead of UNUSED but jest/enzyme/hooks doesn't
// trigger that state upate yet.
expect(pivotPreviewObj.status).toBe(PIVOT_PREVIEW_STATUS.UNUSED);
expect(pivotPreviewObj.dataFramePreviewData).toEqual([]);
// ideally this should be 1 instead of 0 but jest/enzyme/hooks doesn't
// trigger that state upate yet.
expect(ml.dataFrame.getDataFrameTransformsPreview).toHaveBeenCalledTimes(0);
});
// TODO add more tests to check data retrieved via `ml.esSearch()`.
// This needs more investigation in regards to jest/enzyme's React Hooks support.
});

View file

@ -0,0 +1,69 @@
/*
* 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 { useEffect, useState } from 'react';
import { ml } from '../../../services/ml_api_service';
import { Dictionary } from '../../../../common/types/common';
import { getDataFramePreviewRequest, OptionsDataElement, SimpleQuery } from '../../common';
import { IndexPatternContextValue } from '../../common/index_pattern_context';
export enum PIVOT_PREVIEW_STATUS {
UNUSED,
LOADING,
LOADED,
ERROR,
}
export interface UsePivotPreviewDataReturnType {
errorMessage: string;
status: PIVOT_PREVIEW_STATUS;
dataFramePreviewData: Array<Dictionary<any>>;
}
export const usePivotPreviewData = (
indexPattern: IndexPatternContextValue,
query: SimpleQuery,
aggs: OptionsDataElement[],
groupBy: string[]
): UsePivotPreviewDataReturnType => {
const [errorMessage, setErrorMessage] = useState('');
const [status, setStatus] = useState(PIVOT_PREVIEW_STATUS.UNUSED);
const [dataFramePreviewData, setDataFramePreviewData] = useState([]);
if (indexPattern !== null) {
const getDataFramePreviewData = async () => {
if (aggs.length === 0 || groupBy.length === 0) {
setDataFramePreviewData([]);
return;
}
setErrorMessage('');
setStatus(PIVOT_PREVIEW_STATUS.LOADING);
const request = getDataFramePreviewRequest(indexPattern.title, query, groupBy, aggs);
try {
const resp: any = await ml.dataFrame.getDataFrameTransformsPreview(request);
setDataFramePreviewData(resp.preview);
setStatus(PIVOT_PREVIEW_STATUS.LOADED);
} catch (e) {
setErrorMessage(JSON.stringify(e));
setDataFramePreviewData([]);
setStatus(PIVOT_PREVIEW_STATUS.ERROR);
}
};
useEffect(
() => {
getDataFramePreviewData();
},
[indexPattern.title, aggs, groupBy, query]
);
}
return { errorMessage, status, dataFramePreviewData };
};

View file

@ -4,12 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment, SFC, useEffect, useState } from 'react';
import React, { SFC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import { EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui';
import { ml } from '../../../services/ml_api_service';
import { DataFrameJobConfig } from '../../common';
import { JobId, TargetIndex } from './common';
export interface JobDetailsExposedState {
@ -38,21 +42,67 @@ export const JobDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChange
const [jobId, setJobId] = useState(defaults.jobId);
const [targetIndex, setTargetIndex] = useState(defaults.targetIndex);
const [jobIds, setJobIds] = useState([]);
const [indexNames, setIndexNames] = useState([] as string[]);
// fetch existing job IDs and indices once for form validation
useEffect(() => {
// use an IIFE to avoid returning a Promise to useEffect.
(async function() {
try {
setJobIds(
(await ml.dataFrame.getDataFrameTransforms()).transforms.map(
(job: DataFrameJobConfig) => job.id
)
);
} catch (e) {
toastNotifications.addDanger(
i18n.translate('xpack.ml.dataframe.jobDetailsForm.errorGettingDataFrameJobsList', {
defaultMessage: 'An error occurred getting the existing data frame job Ids: {error}',
values: { error: JSON.stringify(e) },
})
);
}
try {
setIndexNames((await ml.getIndices()).map(index => index.name));
} catch (e) {
toastNotifications.addDanger(
i18n.translate('xpack.ml.dataframe.jobDetailsForm.errorGettingDataFrameIndexNames', {
defaultMessage: 'An error occurred getting the existing index names: {error}',
values: { error: JSON.stringify(e) },
})
);
}
})();
}, []);
const jobIdExists = jobIds.some(id => jobId === id);
const indexNameExists = indexNames.some(name => targetIndex === name);
const valid = jobId !== '' && targetIndex !== '' && !jobIdExists && !indexNameExists;
// expose state to wizard
useEffect(
() => {
const valid = jobId !== '' && targetIndex !== '';
onChange({ jobId, targetIndex, touched: true, valid });
},
[jobId, targetIndex]
[jobId, targetIndex, valid]
);
return (
<Fragment>
<EuiForm>
<EuiFormRow
label={i18n.translate('xpack.ml.dataframe.jobDetailsForm.jobIdLabel', {
defaultMessage: 'Job id',
})}
isInvalid={jobIdExists}
error={
jobIdExists && [
i18n.translate('xpack.ml.dataframe.jobDetailsForm.jobIdError', {
defaultMessage: 'A job with this id already exists.',
}),
]
}
>
<EuiFieldText
placeholder="job id"
@ -61,12 +111,21 @@ export const JobDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChange
aria-label={i18n.translate('xpack.ml.dataframe.jobDetailsForm.jobIdInputAriaLabel', {
defaultMessage: 'Choose a unique job id.',
})}
isInvalid={jobIdExists}
/>
</EuiFormRow>
<EuiFormRow
label={i18n.translate('xpack.ml.dataframe.jobDetailsForm.targetIndexLabel', {
defaultMessage: 'Target index',
})}
isInvalid={indexNameExists}
error={
indexNameExists && [
i18n.translate('xpack.ml.dataframe.jobDetailsForm.targetIndexError', {
defaultMessage: 'An index with this name already exists.',
}),
]
}
>
<EuiFieldText
placeholder="target index"
@ -78,8 +137,9 @@ export const JobDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChange
defaultMessage: 'Choose a unique target index name.',
}
)}
isInvalid={indexNameExists}
/>
</EuiFormRow>
</Fragment>
</EuiForm>
);
});

View file

@ -4,17 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FunctionComponent, useContext, useEffect, useState } from 'react';
import React, { FunctionComponent, useContext, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { SearchResponse } from 'elasticsearch';
import {
EuiButtonEmpty,
EuiButtonIcon,
EuiCallOut,
EuiCheckbox,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
@ -36,8 +34,6 @@ interface ExpandableTableProps extends EuiInMemoryTableProps {
const ExpandableTable = (EuiInMemoryTable as any) as FunctionComponent<ExpandableTableProps>;
import { ml } from '../../../services/ml_api_service';
import { Dictionary } from '../../../../common/types/common';
import { IndexPatternContext, SimpleQuery } from '../../common';
@ -45,12 +41,12 @@ import { IndexPatternContext, SimpleQuery } from '../../common';
import {
EsDoc,
EsFieldName,
getDefaultSelectableFields,
getSelectableFields,
MAX_COLUMNS,
toggleSelectedField,
} from './common';
import { ExpandedRow } from './expanded_row';
import { SOURCE_INDEX_STATUS, useSourceIndexData } from './use_source_index_data';
type ItemIdToExpandedRowMap = Dictionary<JSX.Element>;
@ -71,13 +67,25 @@ interface Sorting {
type TableSorting = Sorting | boolean;
interface SourceIndexPreviewTitle {
indexPatternTitle: string;
}
const SourceIndexPreviewTitle: React.SFC<SourceIndexPreviewTitle> = ({ indexPatternTitle }) => (
<EuiTitle size="xs">
<span>
{i18n.translate('xpack.ml.dataframe.sourceIndexPreview.sourceIndexPatternTitle', {
defaultMessage: 'Source index {indexPatternTitle}',
values: { indexPatternTitle },
})}
</span>
</EuiTitle>
);
interface Props {
query: SimpleQuery;
cellClick?(search: string): void;
}
const SEARCH_SIZE = 1000;
export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, query }) => {
const indexPattern = useContext(IndexPatternContext);
@ -85,9 +93,6 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
return null;
}
const [loading, setLoading] = useState(false);
const [tableItems, setTableItems] = useState([] as EsDoc[]);
const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]);
const [isColumnsPopoverVisible, setColumnsPopoverVisible] = useState(false);
@ -104,14 +109,6 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
setSelectedFields([...toggleSelectedField(selectedFields, column)]);
}
let docFields: EsFieldName[] = [];
let docFieldsCount = 0;
if (tableItems.length > 0) {
docFields = getSelectableFields(tableItems);
docFields.sort();
docFieldsCount = docFields.length;
}
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState(
{} as ItemIdToExpandedRowMap
);
@ -126,35 +123,65 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
setItemIdToExpandedRowMap({ ...itemIdToExpandedRowMap });
}
useEffect(
() => {
setLoading(true);
ml.esSearch({
index: indexPattern.title,
rest_total_hits_as_int: true,
size: SEARCH_SIZE,
body: { query },
})
.then((resp: SearchResponse<any>) => {
const docs = resp.hits.hits;
if (selectedFields.length === 0) {
const newSelectedFields = getDefaultSelectableFields(docs);
setSelectedFields(newSelectedFields);
}
setTableItems(docs as EsDoc[]);
setLoading(false);
})
.catch((resp: any) => {
setTableItems([] as EsDoc[]);
setLoading(false);
});
},
[indexPattern.title, query.query_string.query]
const { errorMessage, status, tableItems } = useSourceIndexData(
indexPattern,
query,
selectedFields,
setSelectedFields
);
if (status === SOURCE_INDEX_STATUS.ERROR) {
return (
<EuiPanel grow={false}>
<SourceIndexPreviewTitle indexPatternTitle={indexPattern.title} />
<EuiCallOut
title={i18n.translate('xpack.ml.dataframe.sourceIndexPreview.sourceIndexPatternError', {
defaultMessage: 'An error occurred loading the source index data.',
})}
color="danger"
iconType="cross"
>
<p>{errorMessage}</p>
</EuiCallOut>
</EuiPanel>
);
}
if (status === SOURCE_INDEX_STATUS.LOADED && tableItems.length === 0) {
return (
<EuiPanel grow={false}>
<SourceIndexPreviewTitle indexPatternTitle={indexPattern.title} />
<EuiCallOut
title={i18n.translate(
'xpack.ml.dataframe.sourceIndexPreview.dataFrameSourceIndexNoDataCalloutTitle',
{
defaultMessage: 'Empty source index query result.',
}
)}
color="primary"
>
<p>
{i18n.translate(
'xpack.ml.dataframe.sourceIndexPreview.dataFrameSourceIndexNoDataCalloutBody',
{
defaultMessage:
'The query for the source index returned no results. Please make sure the index contains documents and your query is not too restrictive.',
}
)}
</p>
</EuiCallOut>
</EuiPanel>
);
}
let docFields: EsFieldName[] = [];
let docFieldsCount = 0;
if (tableItems.length > 0) {
docFields = getSelectableFields(tableItems);
docFields.sort();
docFieldsCount = docFields.length;
}
const columns = selectedFields.map(k => {
const column = {
field: `_source.${k}`,
@ -209,24 +236,11 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
});
}
if (!loading && tableItems.length === 0) {
return (
<EuiEmptyPrompt title={<h2>No results</h2>} body={<p>Check the syntax of your query.</p>} />
);
}
return (
<EuiPanel>
<EuiPanel grow={false}>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<span>
{i18n.translate('xpack.ml.dataframe.sourceIndexPreview.sourceIndexPatternTitle', {
defaultMessage: 'Source Index {indexPatternTitle}',
values: { indexPatternTitle: indexPattern.title },
})}
</span>
</EuiTitle>
<SourceIndexPreviewTitle indexPatternTitle={indexPattern.title} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center">
@ -287,8 +301,10 @@ export const SourceIndexPreview: React.SFC<Props> = React.memo(({ cellClick, que
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
{loading && <EuiProgress size="xs" color="accent" />}
{!loading && <EuiProgress size="xs" color="accent" max={1} value={0} />}
{status === SOURCE_INDEX_STATUS.LOADING && <EuiProgress size="xs" color="accent" />}
{status !== SOURCE_INDEX_STATUS.LOADING && (
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
<ExpandableTable
items={tableItems}
columns={columns}

View file

@ -0,0 +1,77 @@
/*
* 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, { SFC } from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { ml } from '../../../services/ml_api_service';
import { SimpleQuery } from '../../common';
import {
SOURCE_INDEX_STATUS,
useSourceIndexData,
UseSourceIndexDataReturnType,
} from './use_source_index_data';
jest.mock('../../../services/ml_api_service');
type Callback = () => void;
interface TestHookProps {
callback: Callback;
}
const TestHook: SFC<TestHookProps> = ({ callback }) => {
callback();
return null;
};
const testHook = (callback: Callback) => {
const container = document.createElement('div');
document.body.appendChild(container);
act(() => {
ReactDOM.render(<TestHook callback={callback} />, container);
});
};
const query: SimpleQuery = {
query_string: {
query: '*',
default_operator: 'AND',
},
};
let sourceIndexObj: UseSourceIndexDataReturnType;
describe('useSourceIndexData', () => {
test('indexPattern not defined', () => {
testHook(() => {
act(() => {
sourceIndexObj = useSourceIndexData(null, query, [], () => {});
});
});
expect(sourceIndexObj.errorMessage).toBe('');
expect(sourceIndexObj.status).toBe(SOURCE_INDEX_STATUS.UNUSED);
expect(sourceIndexObj.tableItems).toEqual([]);
expect(ml.esSearch).not.toHaveBeenCalled();
});
test('indexPattern set triggers loading', () => {
testHook(() => {
act(() => {
sourceIndexObj = useSourceIndexData({ title: 'lorem', fields: [] }, query, [], () => {});
});
});
expect(sourceIndexObj.errorMessage).toBe('');
expect(sourceIndexObj.status).toBe(SOURCE_INDEX_STATUS.LOADING);
expect(sourceIndexObj.tableItems).toEqual([]);
expect(ml.esSearch).toHaveBeenCalledTimes(1);
});
// TODO add more tests to check data retrieved via `ml.esSearch()`.
// This needs more investigation in regards to jest/enzyme's React Hooks support.
});

View file

@ -0,0 +1,78 @@
/*
* 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 } from 'react';
import { SearchResponse } from 'elasticsearch';
import { ml } from '../../../services/ml_api_service';
import { SimpleQuery } from '../../common';
import { IndexPatternContextValue } from '../../common/index_pattern_context';
import { EsDoc, EsFieldName, getDefaultSelectableFields } from './common';
const SEARCH_SIZE = 1000;
export enum SOURCE_INDEX_STATUS {
UNUSED,
LOADING,
LOADED,
ERROR,
}
export interface UseSourceIndexDataReturnType {
errorMessage: string;
status: SOURCE_INDEX_STATUS;
tableItems: EsDoc[];
}
export const useSourceIndexData = (
indexPattern: IndexPatternContextValue,
query: SimpleQuery,
selectedFields: EsFieldName[],
setSelectedFields: React.Dispatch<React.SetStateAction<EsFieldName[]>>
): UseSourceIndexDataReturnType => {
const [errorMessage, setErrorMessage] = useState('');
const [status, setStatus] = useState(SOURCE_INDEX_STATUS.UNUSED);
const [tableItems, setTableItems] = useState([] as EsDoc[]);
if (indexPattern !== null) {
const getSourceIndexData = async function() {
setErrorMessage('');
setStatus(SOURCE_INDEX_STATUS.LOADING);
try {
const resp: SearchResponse<any> = await ml.esSearch({
index: indexPattern.title,
size: SEARCH_SIZE,
body: { query },
});
const docs = resp.hits.hits;
if (selectedFields.length === 0) {
const newSelectedFields = getDefaultSelectableFields(docs);
setSelectedFields(newSelectedFields);
}
setTableItems(docs as EsDoc[]);
setStatus(SOURCE_INDEX_STATUS.LOADED);
} catch (e) {
setErrorMessage(JSON.stringify(e));
setTableItems([] as EsDoc[]);
setStatus(SOURCE_INDEX_STATUS.ERROR);
}
};
useEffect(
() => {
getSourceIndexData();
},
[indexPattern.title, query.query_string.query]
);
}
return { errorMessage, status, tableItems };
};

View file

@ -10,6 +10,10 @@ import { Annotation } from '../../../common/types/annotations';
// It just satisfies needs for other parts of the code area which use
// TypeScript and rely on the methods typed in here.
// This allows the import of `ml` into TypeScript code.
interface EsIndex {
name: string;
}
declare interface Ml {
annotations: {
deleteAnnotation(id: string | undefined): Promise<any>;
@ -26,6 +30,7 @@ declare interface Ml {
stopDataFrameTransformsJob(jobId: string): Promise<any>;
};
esSearch: any;
getIndices(): Promise<EsIndex[]>;
getTimeFieldRange(obj: object): Promise<any>;
}