Skip to content

Commit

Permalink
[ML] Persisted URL state for Data Frame Analytics Exploration page (#…
Browse files Browse the repository at this point in the history
…84499) (#84563)

* [ML] store query string in the URL state

* [ML] query state for the config step

* [ML] pagination in the URL state

* [ML] persisted URL state for outlier results exploration

* [ML] update URL generator

* [ML] do not update the url state when query string hasn't been changed

* [ML] store expandable panels state in the URL

* [ML] fix TS issue

* [ML] fix TS issue
  • Loading branch information
darnautov authored Nov 30, 2020
1 parent 550a294 commit b899d4b
Show file tree
Hide file tree
Showing 25 changed files with 333 additions and 148 deletions.
10 changes: 6 additions & 4 deletions x-pack/plugins/ml/common/constants/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
export const ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE = 500;
export const ANOMALIES_TABLE_DEFAULT_QUERY_SIZE = 500;

export enum SEARCH_QUERY_LANGUAGE {
KUERY = 'kuery',
LUCENE = 'lucene',
}
export const SEARCH_QUERY_LANGUAGE = {
KUERY: 'kuery',
LUCENE: 'lucene',
} as const;

export type SearchQueryLanguage = typeof SEARCH_QUERY_LANGUAGE[keyof typeof SEARCH_QUERY_LANGUAGE];

export interface ErrorMessage {
query: string;
Expand Down
12 changes: 11 additions & 1 deletion x-pack/plugins/ml/common/types/ml_url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { RefreshInterval, TimeRange } from '../../../../../src/plugins/data/comm
import { JobId } from './anomaly_detection_jobs/job';
import { ML_PAGES } from '../constants/ml_url_generator';
import { DataFrameAnalysisConfigType } from './data_frame_analytics';
import { SearchQueryLanguage } from '../constants/search';
import { ListingPageUrlState } from './common';

type OptionalPageState = object | undefined;

Expand Down Expand Up @@ -182,7 +184,7 @@ export type DataFrameAnalyticsExplorationUrlState = MLPageState<
jobId: JobId;
analysisType: DataFrameAnalysisConfigType;
globalState?: MlCommonGlobalState;
defaultIsTraining?: boolean;
queryText?: string;
modelId?: string;
}
>;
Expand All @@ -203,6 +205,14 @@ export type FilterEditUrlState = MLPageState<
}
>;

export type ExpandablePanels = 'analysis' | 'evaluation' | 'feature_importance' | 'results';

export type ExplorationPageUrlState = {
queryText: string;
queryLanguage: SearchQueryLanguage;
} & Pick<ListingPageUrlState, 'pageIndex' | 'pageSize'> &
{ [key in ExpandablePanels]: boolean };

/**
* Union type of ML URL state based on page
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ import { fetchExplainData } from '../shared';
import { useIndexData } from '../../hooks';
import { ExplorationQueryBar } from '../../../analytics_exploration/components/exploration_query_bar';
import { useSavedSearch } from './use_saved_search';
import { SEARCH_QUERY_LANGUAGE } from '../../../../../../../common/constants/search';
import { ExplorationQueryBarProps } from '../../../analytics_exploration/components/exploration_query_bar/exploration_query_bar';
import { Query } from '../../../../../../../../../../src/plugins/data/common/query';

const requiredFieldsErrorText = i18n.translate(
'xpack.ml.dataframe.analytics.createWizard.requiredFieldsErrorMessage',
Expand Down Expand Up @@ -93,11 +96,18 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
trainingPercent,
useEstimatedMml,
} = form;
const [query, setQuery] = useState<Query>({
query: jobConfigQueryString ?? '',
language: SEARCH_QUERY_LANGUAGE.KUERY,
});

const toastNotifications = getToastNotifications();

const setJobConfigQuery = ({ query, queryString }: { query: any; queryString: string }) => {
setFormState({ jobConfigQuery: query, jobConfigQueryString: queryString });
const setJobConfigQuery: ExplorationQueryBarProps['setSearchQuery'] = (update) => {
if (update.query) {
setFormState({ jobConfigQuery: update.query, jobConfigQueryString: update.queryString });
}
setQuery({ query: update.queryString, language: update.language });
};

const indexData = useIndexData(
Expand Down Expand Up @@ -305,10 +315,8 @@ export const ConfigurationStepForm: FC<CreateAnalyticsStepProps> = ({
>
<ExplorationQueryBar
indexPattern={currentIndexPattern}
// @ts-ignore
setSearchQuery={setJobConfigQuery}
includeQueryString
defaultQueryString={jobConfigQueryString}
query={query}
/>
</EuiFormRow>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function useSavedSearch() {

if (currentSavedSearch !== null) {
const { query } = getQueryFromSavedSearch(currentSavedSearch);
const queryLanguage = query.language as SEARCH_QUERY_LANGUAGE;
const queryLanguage = query.language;
qryString = query.query;

if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import { i18n } from '@kbn/i18n';
import { ExplorationPageWrapper } from '../exploration_page_wrapper';
import { EvaluatePanel } from './evaluate_panel';
import { FeatureImportanceSummaryPanel } from '../total_feature_importance_summary/feature_importance_summary';

interface Props {
jobId: string;
defaultIsTraining?: boolean;
}

export const ClassificationExploration: FC<Props> = ({ jobId, defaultIsTraining }) => (
export const ClassificationExploration: FC<Props> = ({ jobId }) => (
<div className="mlDataFrameAnalyticsClassification">
<ExplorationPageWrapper
jobId={jobId}
Expand All @@ -29,7 +29,6 @@ export const ClassificationExploration: FC<Props> = ({ jobId, defaultIsTraining
)}
EvaluatePanel={EvaluatePanel}
FeatureImportanceSummaryPanel={FeatureImportanceSummaryPanel}
defaultIsTraining={defaultIsTraining}
/>
</div>
);
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ export const EvaluatePanel: FC<EvaluatePanelProps> = ({ jobConfig, jobStatus, se
return (
<>
<ExpandableSection
urlStateKey={'evaluation'}
dataTestId="ClassificationEvaluation"
title={
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import './expandable_section.scss';

import React, { useState, FC, ReactNode } from 'react';
import React, { FC, ReactNode, useCallback } from 'react';

import {
EuiBadge,
Expand All @@ -17,6 +17,11 @@ import {
EuiPanel,
EuiText,
} from '@elastic/eui';
import {
getDefaultExplorationPageUrlState,
useExplorationUrlState,
} from '../../hooks/use_exploration_url_state';
import { ExpandablePanels } from '../../../../../../../common/types/ml_url_generator';

interface HeaderItem {
// id is used as the React key and to construct a data-test-subj
Expand All @@ -39,25 +44,30 @@ export interface ExpandableSectionProps {
isExpanded?: boolean;
dataTestId: string;
title: ReactNode;
urlStateKey: ExpandablePanels;
}

export const ExpandableSection: FC<ExpandableSectionProps> = ({
headerItems,
// For now we don't have a need for complete external control
// and just want to pass in a default value. If we wanted
// full external control we'd also need to add a onToggleExpanded()
// callback.
isExpanded: isExpandedDefault = true,
content,
isExpanded: isExpandedDefault,
contentPadding = false,
dataTestId,
title,
docsLink,
urlStateKey,
}) => {
const [isExpanded, setIsExpanded] = useState(isExpandedDefault);
const toggleExpanded = () => {
setIsExpanded(!isExpanded);
};
const [pageUrlState, setPageUrlState] = useExplorationUrlState();

const isExpanded =
isExpandedDefault !== undefined &&
pageUrlState[urlStateKey] === getDefaultExplorationPageUrlState()[urlStateKey]
? isExpandedDefault
: pageUrlState[urlStateKey];

const toggleExpanded = useCallback(() => {
setPageUrlState({ [urlStateKey]: !isExpanded });
}, [isExpanded, setPageUrlState, urlStateKey]);

return (
<EuiPanel paddingSize="none" data-test-subj={`mlDFExpandableSection-${dataTestId}`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const ExpandableSectionAnalytics: FC<ExpandableSectionAnalyticsProps> = (
dataTestId="analysis"
content={analyticsSectionContent}
headerItems={analyticsSectionHeaderItems}
isExpanded={false}
urlStateKey={'analysis'}
title={
<FormattedMessage
id="xpack.ml.dataframe.analytics.exploration.analysisSectionTitle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export const ExpandableSectionResults: FC<ExpandableSectionResultsProps> = ({
return (
<>
<ExpandableSection
urlStateKey={'results'}
dataTestId="results"
content={resultsSectionContent}
headerItems={resultsSectionHeaderItems}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FC, useEffect, useState } from 'react';
import React, { FC, useCallback, useState } from 'react';

import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { useUrlState } from '../../../../../util/url_state';

import {
defaultSearchQuery,
getDefaultTrainingFilterQuery,
useResultsViewConfig,
DataFrameAnalyticsConfig,
} from '../../../../common';
Expand All @@ -27,6 +24,8 @@ import { ExplorationQueryBar } from '../exploration_query_bar';
import { JobConfigErrorCallout } from '../job_config_error_callout';
import { LoadingPanel } from '../loading_panel';
import { FeatureImportanceSummaryPanelProps } from '../total_feature_importance_summary/feature_importance_summary';
import { useExplorationUrlState } from '../../hooks/use_exploration_url_state';
import { ExplorationQueryBarProps } from '../exploration_query_bar/exploration_query_bar';

const filters = {
options: [
Expand Down Expand Up @@ -58,15 +57,13 @@ interface Props {
title: string;
EvaluatePanel: FC<EvaluatePanelProps>;
FeatureImportanceSummaryPanel: FC<FeatureImportanceSummaryPanelProps>;
defaultIsTraining?: boolean;
}

export const ExplorationPageWrapper: FC<Props> = ({
jobId,
title,
EvaluatePanel,
FeatureImportanceSummaryPanel,
defaultIsTraining,
}) => {
const {
indexPattern,
Expand All @@ -81,24 +78,26 @@ export const ExplorationPageWrapper: FC<Props> = ({
totalFeatureImportance,
} = useResultsViewConfig(jobId);

const [pageUrlState, setPageUrlState] = useExplorationUrlState();

const [searchQuery, setSearchQuery] = useState<ResultsSearchQuery>(defaultSearchQuery);
const [globalState, setGlobalState] = useUrlState('_g');
const [defaultQueryString, setDefaultQueryString] = useState<string | undefined>();

useEffect(() => {
if (defaultIsTraining !== undefined && jobConfig !== undefined) {
// Apply defaultIsTraining filter
setSearchQuery(
getDefaultTrainingFilterQuery(jobConfig.dest.results_field, defaultIsTraining)
);
setDefaultQueryString(`${jobConfig.dest.results_field}.is_training : ${defaultIsTraining}`);
// Clear defaultIsTraining from url
setGlobalState('ml', {
analysisType: globalState.ml.analysisType,
jobId: globalState.ml.jobId,
});
}
}, [jobConfig?.dest.results_field]);

const searchQueryUpdateHandler: ExplorationQueryBarProps['setSearchQuery'] = useCallback(
(update) => {
if (update.query) {
setSearchQuery(update.query);
}
if (update.queryString !== pageUrlState.queryText) {
setPageUrlState({ queryText: update.queryString, queryLanguage: update.language });
}
},
[pageUrlState, setPageUrlState]
);

const query: ExplorationQueryBarProps['query'] = {
query: pageUrlState.queryText,
language: pageUrlState.queryLanguage,
};

if (indexPatternErrorMessage !== undefined) {
return (
Expand Down Expand Up @@ -144,8 +143,8 @@ export const ExplorationPageWrapper: FC<Props> = ({
<EuiFlexItem>
<ExplorationQueryBar
indexPattern={indexPattern}
setSearchQuery={setSearchQuery}
defaultQueryString={defaultQueryString}
setSearchQuery={searchQueryUpdateHandler}
query={query}
filters={filters}
/>
</EuiFlexItem>
Expand Down
Loading

0 comments on commit b899d4b

Please sign in to comment.