Skip to content

Commit

Permalink
[ML] DF Analytics Outlier detection results - add search bar (#51235)
Browse files Browse the repository at this point in the history
* add search bar to outlierDetection results table

* show empty results error message in table so user can retry query

* remove unused translation

* type updates after branch update
  • Loading branch information
alvarezmelissa87 authored Nov 22, 2019
1 parent 8acd526 commit af19e9d
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ interface RegressionAnalysis {

export const SEARCH_SIZE = 1000;

export const defaultSearchQuery = {
match_all: {},
};

export interface SearchQuery {
track_total_hits?: boolean;
query: SavedSearchQuery;
sort?: any;
}

export enum INDEX_STATUS {
UNUSED,
LOADING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export {
getPredictedFieldName,
INDEX_STATUS,
SEARCH_SIZE,
defaultSearchQuery,
SearchQuery,
} from './analytics';

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FC, useEffect, useState } from 'react';
import React, { FC, Fragment, useEffect, useState } from 'react';
import moment from 'moment-timezone';

import { i18n } from '@kbn/i18n';
Expand All @@ -18,13 +18,16 @@ import {
EuiCheckbox,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiPanel,
EuiPopover,
EuiPopoverTitle,
EuiProgress,
EuiSpacer,
EuiText,
EuiTitle,
EuiToolTip,
Query,
} from '@elastic/eui';

import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json';
Expand All @@ -51,12 +54,18 @@ import {
EsDoc,
MAX_COLUMNS,
INDEX_STATUS,
SEARCH_SIZE,
defaultSearchQuery,
} from '../../../../common';

import { getOutlierScoreFieldName } from './common';
import { useExploreData } from './use_explore_data';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import {
DATA_FRAME_TASK_STATE,
Query as QueryType,
} from '../../../analytics_management/components/analytics_list/common';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns';
import { SavedSearchQuery } from '../../../../../contexts/kibana';

const customColorScaleFactory = (n: number) => (t: number) => {
if (t < 1 / n) {
Expand Down Expand Up @@ -99,6 +108,10 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(25);

const [searchQuery, setSearchQuery] = useState<SavedSearchQuery>(defaultSearchQuery);
const [searchError, setSearchError] = useState<any>(undefined);
const [searchString, setSearchString] = useState<string | undefined>(undefined);

useEffect(() => {
(async function() {
const analyticsConfigs: GetDataFrameAnalyticsResponse = await ml.dataFrameAnalytics.getDataFrameAnalytics(
Expand All @@ -119,23 +132,9 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
? euiThemeDark
: euiThemeLight;

const [clearTable, setClearTable] = useState(false);

const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]);
const [isColumnsPopoverVisible, setColumnsPopoverVisible] = useState(false);

// EuiInMemoryTable has an issue with dynamic sortable columns
// and will trigger a full page Kibana error in such a case.
// The following is a workaround until this is solved upstream:
// - If the sortable/columns config changes,
// the table will be unmounted/not rendered.
// This is what setClearTable(true) in toggleColumn() does.
// - After that on next render it gets re-enabled. To make sure React
// doesn't consolidate the state updates, setTimeout is used.
if (clearTable) {
setTimeout(() => setClearTable(false), 0);
}

function toggleColumnsPopover() {
setColumnsPopoverVisible(!isColumnsPopoverVisible);
}
Expand All @@ -146,7 +145,6 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {

function toggleColumn(column: EsFieldName) {
if (tableItems.length > 0 && jobConfig !== undefined) {
setClearTable(true);
// spread to a new array otherwise the component wouldn't re-render
setSelectedFields([...toggleSelectedField(selectedFields, column)]);
}
Expand Down Expand Up @@ -309,6 +307,17 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
);
}

useEffect(() => {
if (jobConfig !== undefined) {
const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig);
const outlierScoreFieldSelected = selectedFields.includes(outlierScoreFieldName);

const field = outlierScoreFieldSelected ? outlierScoreFieldName : selectedFields[0];
const direction = outlierScoreFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
loadExploreData({ field, direction, searchQuery });
}
}, [JSON.stringify(searchQuery)]);

useEffect(() => {
// by default set the sorting to descending on the `outlier_score` field.
// if that's not available sort ascending on the first column.
Expand All @@ -319,7 +328,7 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {

const field = outlierScoreFieldSelected ? outlierScoreFieldName : selectedFields[0];
const direction = outlierScoreFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
loadExploreData({ field, direction });
loadExploreData({ field, direction, searchQuery });
return;
}
}, [jobConfig, columns.length, sortField, sortDirection, tableItems.length]);
Expand All @@ -344,8 +353,7 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
setPageSize(size);

if (sort.field !== sortField || sort.direction !== sortDirection) {
setClearTable(true);
loadExploreData(sort);
loadExploreData({ ...sort, searchQuery });
}
};
}
Expand All @@ -358,11 +366,37 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
hidePerPageOptions: false,
};

const onQueryChange = ({ query, error }: { query: QueryType; error: any }) => {
if (error) {
setSearchError(error.message);
} else {
try {
const esQueryDsl = Query.toESQuery(query);
setSearchQuery(esQueryDsl);
setSearchString(query.text);
setSearchError(undefined);
} catch (e) {
setSearchError(e.toString());
}
}
};

const search = {
onChange: onQueryChange,
defaultQuery: searchString,
box: {
incremental: false,
placeholder: i18n.translate('xpack.ml.dataframe.analytics.exploration.searchBoxPlaceholder', {
defaultMessage: 'E.g. avg>0.5',
}),
},
};

if (jobConfig === undefined) {
return null;
}

if (status === INDEX_STATUS.ERROR) {
// if it's a searchBar syntax error leave the table visible so they can try again
if (status === INDEX_STATUS.ERROR && !errorMessage.includes('parsing_exception')) {
return (
<EuiPanel grow={false}>
<ExplorationTitle jobId={jobConfig.id} />
Expand All @@ -379,32 +413,16 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
);
}

if (status === INDEX_STATUS.LOADED && tableItems.length === 0) {
return (
<EuiPanel grow={false}>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<ExplorationTitle jobId={jobConfig.id} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span>{getTaskStateBadge(jobStatus)}</span>
</EuiFlexItem>
</EuiFlexGroup>
<EuiCallOut
title={i18n.translate('xpack.ml.dataframe.analytics.exploration.noDataCalloutTitle', {
defaultMessage: 'Empty index query result.',
})}
color="primary"
>
<p>
{i18n.translate('xpack.ml.dataframe.analytics.exploration.noDataCalloutBody', {
defaultMessage:
'The query for the index returned no results. Please make sure the index contains documents and your query is not too restrictive.',
})}
</p>
</EuiCallOut>
</EuiPanel>
);
let tableError =
status === INDEX_STATUS.ERROR && errorMessage.includes('parsing_exception')
? errorMessage
: searchError;

if (status === INDEX_STATUS.LOADED && tableItems.length === 0 && tableError === undefined) {
tableError = i18n.translate('xpack.ml.dataframe.analytics.exploration.noDataCalloutBody', {
defaultMessage:
'The query for the index returned no results. Please make sure the index contains documents and your query is not too restrictive.',
});
}

return (
Expand Down Expand Up @@ -483,20 +501,38 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
{status !== INDEX_STATUS.LOADING && (
<EuiProgress size="xs" color="accent" max={1} value={0} />
)}
{clearTable === false && columns.length > 0 && sortField !== '' && (
<MlInMemoryTableBasic
allowNeutralSort={false}
className="mlDataFrameAnalyticsExploration"
columns={columns}
compressed
hasActions={false}
isSelectable={false}
items={tableItems}
onTableChange={onTableChange}
pagination={pagination}
responsive={false}
sorting={sorting}
/>
{(columns.length > 0 || searchQuery !== defaultSearchQuery) && sortField !== '' && (
<Fragment>
{tableItems.length === SEARCH_SIZE && (
<EuiFormRow
helpText={i18n.translate(
'xpack.ml.dataframe.analytics.exploration.documentsShownHelpText',
{
defaultMessage: 'Showing first {searchSize} documents',
values: { searchSize: SEARCH_SIZE },
}
)}
>
<Fragment />
</EuiFormRow>
)}
<EuiSpacer />
<MlInMemoryTableBasic
allowNeutralSort={false}
className="mlDataFrameAnalyticsExploration"
columns={columns}
compressed
hasActions={false}
isSelectable={false}
items={tableItems}
onTableChange={onTableChange}
pagination={pagination}
responsive={false}
sorting={sorting}
search={search}
error={tableError}
/>
</Fragment>
)}
</EuiPanel>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,21 @@ import {
EsFieldName,
INDEX_STATUS,
SEARCH_SIZE,
defaultSearchQuery,
SearchQuery,
} from '../../../../common';

import { getOutlierScoreFieldName } from './common';
import { SavedSearchQuery } from '../../../../../contexts/kibana';

type TableItem = Record<string, any>;

interface LoadExploreDataArg {
field: string;
direction: SortDirection;
searchQuery: SavedSearchQuery;
}

export interface UseExploreDataReturnType {
errorMessage: string;
loadExploreData: (arg: LoadExploreDataArg) => void;
Expand All @@ -50,27 +55,32 @@ export const useExploreData = (
const [sortField, setSortField] = useState<string>('');
const [sortDirection, setSortDirection] = useState<SortDirection>(SORT_DIRECTION.ASC);

const loadExploreData = async ({ field, direction }: LoadExploreDataArg) => {
const loadExploreData = async ({ field, direction, searchQuery }: LoadExploreDataArg) => {
if (jobConfig !== undefined) {
setErrorMessage('');
setStatus(INDEX_STATUS.LOADING);

try {
const resultsField = jobConfig.dest.results_field;

const body: SearchQuery = {
query: searchQuery,
};

if (field !== undefined) {
body.sort = [
{
[field]: {
order: direction,
},
},
];
}

const resp: SearchResponse<any> = await ml.esSearch({
index: jobConfig.dest.index,
size: SEARCH_SIZE,
body: {
query: { match_all: {} },
sort: [
{
[field]: {
order: direction,
},
},
],
},
body,
});

setSortField(field);
Expand Down Expand Up @@ -135,6 +145,7 @@ export const useExploreData = (
loadExploreData({
field: getOutlierScoreFieldName(jobConfig),
direction: SORT_DIRECTION.DESC,
searchQuery: defaultSearchQuery,
});
}
}, [jobConfig && jobConfig.id]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import {
getEvalQueryBody,
isRegressionResultsSearchBoolQuery,
RegressionResultsSearchQuery,
SearchQuery,
} from '../../../../common/analytics';
import { SearchQuery } from './use_explore_data';

interface Props {
jobConfig: DataFrameAnalyticsConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import { DataFrameAnalyticsConfig } from '../../../../common';
import { EvaluatePanel } from './evaluate_panel';
import { ResultsTable } from './results_table';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
import { defaultSearchQuery } from './use_explore_data';
import { RegressionResultsSearchQuery } from '../../../../common/analytics';
import { RegressionResultsSearchQuery, defaultSearchQuery } from '../../../../common/analytics';

interface GetDataFrameAnalyticsResponse {
count: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ import {
getPredictedFieldName,
INDEX_STATUS,
SEARCH_SIZE,
defaultSearchQuery,
} from '../../../../common';
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns';
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';

import { useExploreData, defaultSearchQuery } from './use_explore_data';
import { useExploreData } from './use_explore_data';
import { ExplorationTitle } from './regression_exploration';

const PAGE_SIZE_OPTIONS = [5, 10, 25, 50];
Expand Down
Loading

0 comments on commit af19e9d

Please sign in to comment.