[ML] AIOps: Fix to not run log rate analysis twice when no spike/dip detected. (#180980)

## Summary

Part of #172981.

This fixes to not run log rate analysis twice when no spike/dip detected
and a user needs to adapt the initial selection. When a user clicks in
an area of the histogram chart that's not a highlighted change point,
the click will just trigger the baseline/deviation time range selection,
but it will not automatically run the analysis. Instead, an updated
prompt is shown below the chart that explains that the
baseline/deviation can be adjusted via dragging and the analysis can be
run via the button below that description.

Initial view after loading the page:

<img width="1040" alt="image"
src="90e8c390-af2a-45e2-8d11-cfd42285200b">

User clicked in an area that's not covered by the highlighted change
point:

<img width="1026" alt="image"
src="050a07e0-c5e6-4639-a854-83fae10b125b">


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Walter Rafelsberger 2024-04-18 11:35:16 +02:00 committed by GitHub
parent 87c0451f91
commit 6fdcd8d7b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 312 additions and 81 deletions

View file

@ -9,7 +9,15 @@ export { DualBrush, DualBrushAnnotation } from './src/dual_brush';
export { ProgressControls } from './src/progress_controls';
export {
DocumentCountChart,
DocumentCountChartWithAutoAnalysisStart,
type BrushSettings,
type BrushSelectionUpdateHandler,
} from './src/document_count_chart';
export type { DocumentCountChartProps } from './src/document_count_chart';
export {
useLogRateAnalysisStateContext,
LogRateAnalysisStateProvider,
type GroupTableItem,
type GroupTableItemGroup,
type TableItemAction,
} from './src/log_rate_analysis_state_provider';

View file

@ -40,6 +40,8 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { DualBrush, DualBrushAnnotation } from '../..';
import { useLogRateAnalysisStateContext } from '../log_rate_analysis_state_provider';
import { BrushBadge } from './brush_badge';
declare global {
@ -87,6 +89,11 @@ export type BrushSelectionUpdateHandler = (
logRateAnalysisType: LogRateAnalysisType
) => void;
/**
* Callback to set the autoRunAnalysis flag
*/
type SetAutoRunAnalysisFn = (isAutoRun: boolean) => void;
/**
* Props for document count chart
*/
@ -118,9 +125,11 @@ export interface DocumentCountChartProps {
chartPointsSplitLabel: string;
/** Whether or not brush has been reset */
isBrushCleared: boolean;
/** Callback to set the autoRunAnalysis flag */
setAutoRunAnalysis?: SetAutoRunAnalysisFn;
/** Timestamp for start of initial analysis */
autoAnalysisStart?: number | WindowParameters;
/** Optional style to override bar chart */
/** Optional style to override bar chart */
barStyleAccessor?: BarStyleAccessor;
/** Optional color override for the default bar color for charts */
barColorOverride?: string;
@ -181,6 +190,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
interval,
chartPointsSplitLabel,
isBrushCleared,
setAutoRunAnalysis,
autoAnalysisStart,
barColorOverride,
barStyleAccessor,
@ -305,6 +315,17 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
windowParameters === undefined &&
adjustedChartPoints !== undefined
) {
if (setAutoRunAnalysis) {
const autoRun =
typeof startRange !== 'number' ||
(typeof startRange === 'number' &&
changePoint !== undefined &&
startRange >= changePoint.startTs &&
startRange <= changePoint.endTs);
setAutoRunAnalysis(autoRun);
}
const wp = getWindowParametersForTrigger(
startRange,
interval,
@ -333,6 +354,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
timeRangeLatest,
snapTimestamps,
originalWindowParameters,
setAutoRunAnalysis,
setWindowParameters,
brushSelectionUpdateHandler,
adjustedChartPoints,
@ -535,3 +557,24 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
</>
);
};
/**
* Functional component that renders a `DocumentCountChart` with additional properties
* managed by the log rate analysis state. It leverages the `useLogRateAnalysisStateContext`
* to acquire state variables like `initialAnalysisStart` and functions such as
* `setAutoRunAnalysis`. These values are then passed as props to the `DocumentCountChart`.
*
* @param props - The properties passed to the DocumentCountChart component.
* @returns The DocumentCountChart component enhanced with automatic analysis start capabilities.
*/
export const DocumentCountChartWithAutoAnalysisStart: FC<DocumentCountChartProps> = (props) => {
const { initialAnalysisStart, setAutoRunAnalysis } = useLogRateAnalysisStateContext();
return (
<DocumentCountChart
{...props}
autoAnalysisStart={initialAnalysisStart}
setAutoRunAnalysis={setAutoRunAnalysis}
/>
);
};

View file

@ -5,7 +5,10 @@
* 2.0.
*/
export { DocumentCountChart } from './document_count_chart';
export {
DocumentCountChart,
DocumentCountChartWithAutoAnalysisStart,
} from './document_count_chart';
export type {
BrushSelectionUpdateHandler,
BrushSettings,

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export {
useLogRateAnalysisStateContext,
LogRateAnalysisStateProvider,
} from './log_rate_analysis_state_provider';
export type { GroupTableItem, GroupTableItemGroup, TableItemAction } from './types';

View file

@ -16,13 +16,19 @@ import React, {
} from 'react';
import type { SignificantItem } from '@kbn/ml-agg-utils';
import type { WindowParameters } from '@kbn/aiops-log-rate-analysis';
import type { GroupTableItem } from './types';
type InitialAnalysisStart = number | WindowParameters | undefined;
type SignificantItemOrNull = SignificantItem | null;
type GroupOrNull = GroupTableItem | null;
interface LogRateAnalysisResultsTableRow {
interface LogRateAnalysisState {
autoRunAnalysis: boolean;
setAutoRunAnalysis: Dispatch<SetStateAction<boolean>>;
initialAnalysisStart: InitialAnalysisStart;
setInitialAnalysisStart: Dispatch<SetStateAction<InitialAnalysisStart>>;
pinnedSignificantItem: SignificantItemOrNull;
setPinnedSignificantItem: Dispatch<SetStateAction<SignificantItemOrNull>>;
pinnedGroup: GroupOrNull;
@ -36,12 +42,38 @@ interface LogRateAnalysisResultsTableRow {
clearAllRowState: () => void;
}
export const logRateAnalysisResultsTableRowContext = createContext<
LogRateAnalysisResultsTableRow | undefined
>(undefined);
const LogRateAnalysisStateContext = createContext<LogRateAnalysisState | undefined>(undefined);
export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) => {
// State that will be shared with all components
/**
* Props for LogRateAnalysisStateProvider.
*/
interface LogRateAnalysisStateProviderProps {
/** The parameters to be used to trigger an analysis. */
initialAnalysisStart?: InitialAnalysisStart;
}
/**
* Context provider component that manages and provides global state for Log Rate Analysis.
* This provider handles several pieces of state important for controlling and displaying
* log rate analysis data, such as the control of automatic analysis runs, and the management
* of both pinned and selected significant items and groups.
*
* The state includes mechanisms for setting initial analysis parameters, toggling analysis,
* and managing the current selection and pinned state of significant items and groups.
*
* @param props - Props object containing initial settings for the analysis,
* including children components to be wrapped by the Provider.
* @returns A context provider wrapping children with access to log rate analysis state.
*/
export const LogRateAnalysisStateProvider: FC<LogRateAnalysisStateProviderProps> = (props) => {
const { children, initialAnalysisStart: incomingInitialAnalysisStart } = props;
const [autoRunAnalysis, setAutoRunAnalysis] = useState(true);
const [initialAnalysisStart, setInitialAnalysisStart] = useState<
number | WindowParameters | undefined
>(incomingInitialAnalysisStart);
// Row state that will be shared with all components
const [pinnedSignificantItem, setPinnedSignificantItem] = useState<SignificantItemOrNull>(null);
const [pinnedGroup, setPinnedGroup] = useState<GroupOrNull>(null);
const [selectedSignificantItem, setSelectedSignificantItem] =
@ -66,8 +98,12 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) =>
}
}, [selectedGroup, pinnedGroup]);
const contextValue: LogRateAnalysisResultsTableRow = useMemo(
const contextValue: LogRateAnalysisState = useMemo(
() => ({
autoRunAnalysis,
setAutoRunAnalysis,
initialAnalysisStart,
setInitialAnalysisStart,
pinnedSignificantItem,
setPinnedSignificantItem,
pinnedGroup,
@ -86,6 +122,10 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) =>
},
}),
[
autoRunAnalysis,
setAutoRunAnalysis,
initialAnalysisStart,
setInitialAnalysisStart,
pinnedSignificantItem,
setPinnedSignificantItem,
pinnedGroup,
@ -101,19 +141,26 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) =>
return (
// Provider managing the state
<logRateAnalysisResultsTableRowContext.Provider value={contextValue}>
<LogRateAnalysisStateContext.Provider value={contextValue}>
{children}
</logRateAnalysisResultsTableRowContext.Provider>
</LogRateAnalysisStateContext.Provider>
);
};
export const useLogRateAnalysisResultsTableRowContext = () => {
const logRateAnalysisResultsTableRow = useContext(logRateAnalysisResultsTableRowContext);
/**
* Custom hook for accessing the state of log rate analysis from the LogRateAnalysisStateContext.
* This hook must be used within a component that is a descendant of the LogRateAnalysisStateContext provider.
*
* @returns The current state of the log rate analysis.
* @throws Throws an error if the hook is used outside of its Provider context.
*/
export const useLogRateAnalysisStateContext = () => {
const logRateAnalysisState = useContext(LogRateAnalysisStateContext);
// If `undefined`, throw an error.
if (logRateAnalysisResultsTableRow === undefined) {
throw new Error('useLogRateAnalysisResultsTableRowContext was used outside of its Provider');
if (logRateAnalysisState === undefined) {
throw new Error('useLogRateAnalysisStateContext was used outside of its Provider');
}
return logRateAnalysisResultsTableRow;
return logRateAnalysisState;
};

View file

@ -9,20 +9,36 @@ import type { EuiTableActionsColumnType } from '@elastic/eui';
import type { SignificantItem, SignificantItemGroupItem } from '@kbn/ml-agg-utils';
/**
* Type for defining attributes picked from
* SignificantItemGroupItem used in the grouped table.
*/
export type GroupTableItemGroup = Pick<
SignificantItemGroupItem,
'key' | 'type' | 'fieldName' | 'fieldValue' | 'docCount' | 'pValue' | 'duplicate'
>;
/**
* Represents a single item in the group table.
*/
export interface GroupTableItem {
/** Unique identifier for the group table item. */
id: string;
/** Document count associated with the item. */
docCount: number;
/** Statistical p-value indicating the significance of the item, nullable. */
pValue: number | null;
/** Count of unique items within the group. */
uniqueItemsCount: number;
/** Array of items within the group, sorted by uniqueness. */
groupItemsSortedByUniqueness: GroupTableItemGroup[];
/** Histogram data for the significant item. */
histogram: SignificantItem['histogram'];
}
/**
* Type for action columns in a table that involves SignificantItem or GroupTableItem.
*/
export type TableItemAction = EuiTableActionsColumnType<
SignificantItem | GroupTableItem
>['actions'][number];

View file

@ -27,6 +27,7 @@
"@kbn/field-formats-plugin",
"@kbn/visualization-utils",
"@kbn/aiops-log-rate-analysis",
"@kbn/ml-agg-utils",
],
"exclude": [
"target/**/*",

View file

@ -33,8 +33,14 @@ export const journey = new Journey({
await page.waitForSelector(subj('aiopsNoWindowParametersEmptyPrompt'));
})
.step('Run AIOps Log Rate Analysis', async ({ page }) => {
// Select the chart and click in the area where the spike is located to trigger log rate analysis.
// Select the chart and click in the area where the spike is located.
const chart = await page.locator(subj('aiopsDocumentCountChart'));
await chart.click({ position: { x: 710, y: 50 } });
// Click the "Run analysis" button.
await page.waitForSelector(subj('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton'));
await page.click(subj('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton'));
// Wait for the analysis to complete.
await page.waitForSelector(subj('aiopsAnalysisComplete'), { timeout: 120000 });
});

View file

@ -6,8 +6,7 @@
*/
import type { SignificantItem } from '@kbn/ml-agg-utils';
import type { GroupTableItem } from '../../components/log_rate_analysis_results_table/types';
import type { GroupTableItem } from '@kbn/aiops-components';
import { buildExtendedBaseFilterCriteria } from './build_extended_base_filter_criteria';

View file

@ -14,8 +14,7 @@ import type { Query } from '@kbn/es-query';
import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils';
import { buildBaseFilterCriteria } from '@kbn/ml-query-utils';
import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query';
import type { GroupTableItem } from '../../components/log_rate_analysis_results_table/types';
import type { GroupTableItem } from '@kbn/aiops-components';
/*
* Contains utility functions for building and processing queries.

View file

@ -14,7 +14,10 @@ import type {
} from '@elastic/charts/dist/chart_types/xy_chart/utils/specs';
import type { LogRateHistogramItem, WindowParameters } from '@kbn/aiops-log-rate-analysis';
import { DocumentCountChart, type BrushSelectionUpdateHandler } from '@kbn/aiops-components';
import {
DocumentCountChartWithAutoAnalysisStart,
type BrushSelectionUpdateHandler,
} from '@kbn/aiops-components';
import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context';
import type { DocumentCountStats } from '../../../get_document_stats';
@ -29,13 +32,11 @@ export interface DocumentCountContentProps {
isBrushCleared: boolean;
totalCount: number;
sampleProbability: number;
initialAnalysisStart?: number | WindowParameters;
/** Optional color override for the default bar color for charts */
barColorOverride?: string;
/** Optional color override for the highlighted bar color for charts */
barHighlightColorOverride?: string;
windowParameters?: WindowParameters;
incomingInitialAnalysisStart?: number | WindowParameters;
baselineLabel?: string;
deviationLabel?: string;
barStyleAccessor?: BarStyleAccessor;
@ -51,11 +52,9 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
isBrushCleared,
totalCount,
sampleProbability,
initialAnalysisStart,
barColorOverride,
barHighlightColorOverride,
windowParameters,
incomingInitialAnalysisStart,
...docCountChartProps
}) => {
const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext();
@ -100,7 +99,7 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
</EuiFlexItem>
{documentCountStats.interval !== undefined && (
<EuiFlexItem>
<DocumentCountChart
<DocumentCountChartWithAutoAnalysisStart
dependencies={{ data, uiSettings, fieldFormats, charts }}
brushSelectionUpdateHandler={brushSelectionUpdateHandler}
chartPoints={chartPoints}
@ -110,7 +109,6 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
interval={documentCountStats.interval}
chartPointsSplitLabel={documentCountStatsSplitLabel}
isBrushCleared={isBrushCleared}
autoAnalysisStart={initialAnalysisStart}
barColorOverride={barColorOverride}
barHighlightColorOverride={barHighlightColorOverride}
changePoint={documentCountStats.changePoint}

View file

@ -17,13 +17,12 @@ import { Storage } from '@kbn/kibana-utils-plugin/public';
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { LogRateAnalysisStateProvider } from '@kbn/aiops-components';
import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
import { AiopsAppContext } from '../../hooks/use_aiops_app_context';
import { DataSourceContext } from '../../hooks/use_data_source';
import { AIOPS_STORAGE_KEYS } from '../../types/storage';
import { LogRateAnalysisResultsTableRowStateProvider } from '../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
import { LogRateAnalysisPage } from './log_rate_analysis_page';
import { timeSeriesDataViewWarning } from '../../application/utils/time_series_dataview_check';
@ -70,13 +69,13 @@ export const LogRateAnalysisAppState: FC<LogRateAnalysisAppStateProps> = ({
<AiopsAppContext.Provider value={appDependencies}>
<UrlStateProvider>
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
<LogRateAnalysisResultsTableRowStateProvider>
<LogRateAnalysisStateProvider>
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
<DatePickerContextProvider {...datePickerDeps}>
<LogRateAnalysisPage stickyHistogram={stickyHistogram} />
</DatePickerContextProvider>
</StorageContextProvider>
</LogRateAnalysisResultsTableRowStateProvider>
</LogRateAnalysisStateProvider>
</DataSourceContext.Provider>
</UrlStateProvider>
</AiopsAppContext.Provider>

View file

@ -26,6 +26,7 @@ import {
type WindowParameters,
} from '@kbn/aiops-log-rate-analysis';
import type { SignificantItem } from '@kbn/ml-agg-utils';
import { useLogRateAnalysisStateContext, type GroupTableItem } from '@kbn/aiops-components';
import { useData } from '../../../hooks/use_data';
@ -34,8 +35,6 @@ import {
LogRateAnalysisResults,
type LogRateAnalysisResultsData,
} from '../log_rate_analysis_results';
import type { GroupTableItem } from '../../log_rate_analysis_results_table/types';
import { useLogRateAnalysisResultsTableRowContext } from '../../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
const DEFAULT_SEARCH_QUERY: estypes.QueryDslQueryContainer = { match_all: {} };
const DEFAULT_SEARCH_BAR_QUERY: estypes.QueryDslQueryContainer = {
@ -66,8 +65,6 @@ export function getDocumentCountStatsSplitLabel(
export interface LogRateAnalysisContentProps {
/** The data view to analyze. */
dataView: DataView;
/** Timestamp for the start of the range for initial analysis */
initialAnalysisStart?: number | WindowParameters;
timeRange?: { min: Moment; max: Moment };
/** Elasticsearch query to pass to analysis endpoint */
esSearchQuery?: estypes.QueryDslQueryContainer;
@ -87,7 +84,6 @@ export interface LogRateAnalysisContentProps {
export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
dataView,
initialAnalysisStart: incomingInitialAnalysisStart,
timeRange,
esSearchQuery = DEFAULT_SEARCH_QUERY,
stickyHistogram,
@ -98,9 +94,6 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
embeddingOrigin,
}) => {
const [windowParameters, setWindowParameters] = useState<WindowParameters | undefined>();
const [initialAnalysisStart, setInitialAnalysisStart] = useState<
number | WindowParameters | undefined
>(incomingInitialAnalysisStart);
const [isBrushCleared, setIsBrushCleared] = useState(true);
const [logRateAnalysisType, setLogRateAnalysisType] = useState<LogRateAnalysisType>(
LOG_RATE_ANALYSIS_TYPE.SPIKE
@ -140,13 +133,16 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
);
const {
autoRunAnalysis,
currentSelectedSignificantItem,
currentSelectedGroup,
setAutoRunAnalysis,
setInitialAnalysisStart,
setPinnedSignificantItem,
setPinnedGroup,
setSelectedSignificantItem,
setSelectedGroup,
} = useLogRateAnalysisResultsTableRowContext();
} = useLogRateAnalysisStateContext();
const { documentStats, earliest, latest } = useData(
dataView,
@ -206,7 +202,11 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
}
: undefined;
const triggerAnalysis = useCallback(() => {
const triggerAnalysisForManualSelection = useCallback(() => {
setAutoRunAnalysis(true);
}, [setAutoRunAnalysis]);
const triggerAnalysisForChangePoint = useCallback(() => {
if (documentCountStats) {
const { interval, timeRangeEarliest, timeRangeLatest, changePoint } = documentCountStats;
@ -222,14 +222,37 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
const snapTimestamps = getSnappedTimestamps(timeRangeEarliest, timeRangeLatest, interval);
const wpSnap = getSnappedWindowParameters(wp, snapTimestamps);
triggerAnalysisForManualSelection();
setInitialAnalysisStart(wpSnap);
}
}
}, [documentCountStats]);
}, [documentCountStats, setInitialAnalysisStart, triggerAnalysisForManualSelection]);
const showDocumentCountContent = documentCountStats !== undefined;
const showLogRateAnalysisResults =
autoRunAnalysis &&
earliest !== undefined &&
latest !== undefined &&
windowParameters !== undefined;
const showNoAutoRunEmptyPrompt =
!autoRunAnalysis &&
earliest !== undefined &&
latest !== undefined &&
windowParameters !== undefined;
const showSpikeDetectedEmptyPrompt =
windowParameters === undefined && documentCountStats?.changePoint;
const showDefaultEmptyPrompt =
windowParameters === undefined && documentCountStats?.changePoint === undefined;
const changePointType = documentCountStats?.changePoint?.type;
return (
<EuiPanel hasBorder={false} hasShadow={false}>
{documentCountStats !== undefined && (
{showDocumentCountContent && (
<DocumentCountContent
brushSelectionUpdateHandler={brushSelectionUpdate}
documentCountStats={documentCountStats}
@ -241,14 +264,13 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
isBrushCleared={isBrushCleared}
totalCount={totalCount}
sampleProbability={sampleProbability}
initialAnalysisStart={initialAnalysisStart}
barColorOverride={barColorOverride}
barHighlightColorOverride={barHighlightColorOverride}
barStyleAccessor={barStyleAccessor}
/>
)}
<EuiHorizontalRule />
{earliest !== undefined && latest !== undefined && windowParameters !== undefined && (
{showLogRateAnalysisResults && (
<LogRateAnalysisResults
dataView={dataView}
analysisType={logRateAnalysisType}
@ -266,7 +288,47 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
embeddingOrigin={embeddingOrigin}
/>
)}
{windowParameters === undefined && documentCountStats?.changePoint && (
{showNoAutoRunEmptyPrompt && (
<EuiEmptyPrompt
color="subdued"
hasShadow={false}
hasBorder={false}
css={{ minWidth: '100%' }}
title={undefined}
titleSize="xs"
body={
<>
<p>
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.noAutoRunPromptBody"
defaultMessage="Next you can fine tune the time ranges for baseline and deviation by dragging the handles of the brushes. Once you're ready, click the button 'Run analysis' below."
/>
</p>
<EuiButton
data-test-subj="aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton"
onClick={triggerAnalysisForManualSelection}
>
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.noAutoRunPromptRunAnalysisButton"
defaultMessage="Run analysis"
/>
</EuiButton>{' '}
<EuiButton
data-test-subj="aiopsClearSelectionBadge"
onClick={() => clearSelection()}
color="text"
>
<FormattedMessage
id="xpack.aiops.clearSelectionLabel"
defaultMessage="Clear selection"
/>
</EuiButton>
</>
}
data-test-subj="aiopsChangePointDetectedPrompt"
/>
)}
{showSpikeDetectedEmptyPrompt && (
<EuiEmptyPrompt
color="subdued"
hasShadow={false}
@ -274,20 +336,20 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
css={{ minWidth: '100%' }}
title={
<h2>
{documentCountStats?.changePoint.type === LOG_RATE_ANALYSIS_TYPE.SPIKE && (
{changePointType === LOG_RATE_ANALYSIS_TYPE.SPIKE && (
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.changePointSpikePromptTitle"
defaultMessage="Log rate spike detected"
/>
)}
{documentCountStats?.changePoint.type === LOG_RATE_ANALYSIS_TYPE.DIP && (
{changePointType === LOG_RATE_ANALYSIS_TYPE.DIP && (
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.changePointDipPromptTitle"
defaultMessage="Log rate dip detected"
/>
)}
{documentCountStats?.changePoint.type !== LOG_RATE_ANALYSIS_TYPE.SPIKE &&
documentCountStats?.changePoint.type !== LOG_RATE_ANALYSIS_TYPE.DIP && (
{changePointType !== LOG_RATE_ANALYSIS_TYPE.SPIKE &&
changePointType !== LOG_RATE_ANALYSIS_TYPE.DIP && (
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.changePointOtherPromptTitle"
defaultMessage="Log rate change point detected"
@ -301,12 +363,12 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
<p>
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.changePointPromptBody"
defaultMessage="The log rate analysis feature identifies statistically significant field/value combinations that contribute to a log rate spike or dip."
defaultMessage="The log rate analysis feature identifies statistically significant field/value combinations that contribute to a log rate spike or dip. To analyse the area highlighted in the chart, click the button below. For custom analysis of other areas, start by clicking on any of the non-highlighted bars in the histogram chart."
/>
</p>
<EuiButton
data-test-subj="aiopsLogRateAnalysisContentRunAnalysisButton"
onClick={triggerAnalysis}
onClick={triggerAnalysisForChangePoint}
>
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.changePointPromptRunAnalysisButton"
@ -318,7 +380,7 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
data-test-subj="aiopsChangePointDetectedPrompt"
/>
)}
{windowParameters === undefined && documentCountStats?.changePoint === undefined && (
{showDefaultEmptyPrompt && (
<EuiEmptyPrompt
color="subdued"
hasShadow={false}
@ -328,7 +390,7 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
<h2>
<FormattedMessage
id="xpack.aiops.logRateAnalysis.page.emptyPromptTitle"
defaultMessage="Click a spike or dip in the histogram chart to start the analysis."
defaultMessage="Start by clicking a spike or dip in the histogram chart."
/>
</h2>
}

View file

@ -18,13 +18,13 @@ import { UrlStateProvider } from '@kbn/ml-url-state';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { LogRateAnalysisStateProvider } from '@kbn/aiops-components';
import { timeSeriesDataViewWarning } from '../../../application/utils/time_series_dataview_check';
import { AiopsAppContext, type AiopsAppDependencies } from '../../../hooks/use_aiops_app_context';
import { DataSourceContext } from '../../../hooks/use_data_source';
import { AIOPS_STORAGE_KEYS } from '../../../types/storage';
import { LogRateAnalysisResultsTableRowStateProvider } from '../../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
import { LogRateAnalysisContent } from './log_rate_analysis_content';
import type { LogRateAnalysisResultsData } from '../log_rate_analysis_results';
@ -92,12 +92,11 @@ export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProp
<AiopsAppContext.Provider value={appDependencies}>
<UrlStateProvider>
<DataSourceContext.Provider value={{ dataView, savedSearch: null }}>
<LogRateAnalysisResultsTableRowStateProvider>
<LogRateAnalysisStateProvider initialAnalysisStart={initialAnalysisStart}>
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
<DatePickerContextProvider {...datePickerDeps}>
<LogRateAnalysisContent
dataView={dataView}
initialAnalysisStart={initialAnalysisStart}
timeRange={timeRange}
esSearchQuery={esSearchQuery}
stickyHistogram={stickyHistogram}
@ -108,7 +107,7 @@ export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProp
/>
</DatePickerContextProvider>
</StorageContextProvider>
</LogRateAnalysisResultsTableRowStateProvider>
</LogRateAnalysisStateProvider>
</DataSourceContext.Provider>
</UrlStateProvider>
</AiopsAppContext.Provider>

View file

@ -18,6 +18,7 @@ import { useUrlState, usePageUrlState } from '@kbn/ml-url-state';
import type { SearchQueryLanguage } from '@kbn/ml-query-utils';
import type { WindowParameters } from '@kbn/aiops-log-rate-analysis';
import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants';
import { useLogRateAnalysisStateContext } from '@kbn/aiops-components';
import { useDataSource } from '../../hooks/use_data_source';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
@ -31,7 +32,6 @@ import {
} from '../../application/url_state/log_rate_analysis';
import { SearchPanel } from '../search_panel';
import { useLogRateAnalysisResultsTableRowContext } from '../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
import { PageHeader } from '../page_header';
import { LogRateAnalysisContent } from './log_rate_analysis_content/log_rate_analysis_content';
@ -43,8 +43,8 @@ export const LogRateAnalysisPage: FC<Props> = ({ stickyHistogram }) => {
const { data: dataService } = useAiopsAppContext();
const { dataView, savedSearch } = useDataSource();
const { currentSelectedSignificantItem, currentSelectedGroup } =
useLogRateAnalysisResultsTableRowContext();
const { currentSelectedSignificantItem, currentSelectedGroup, setInitialAnalysisStart } =
useLogRateAnalysisStateContext();
const [stateFromUrl, setUrlState] = usePageUrlState<LogRateAnalysisPageUrlState>(
'logRateAnalysis',
@ -142,6 +142,14 @@ export const LogRateAnalysisPage: FC<Props> = ({ stickyHistogram }) => {
});
}, [dataService, searchQueryLanguage, searchString]);
useEffect(
() => {
setInitialAnalysisStart(appStateToWindowParameters(stateFromUrl.wp));
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const onWindowParametersHandler = (wp?: WindowParameters, replace = false) => {
if (!isEqual(windowParametersToAppState(wp), stateFromUrl.wp)) {
setUrlState(
@ -169,7 +177,6 @@ export const LogRateAnalysisPage: FC<Props> = ({ stickyHistogram }) => {
/>
</EuiFlexItem>
<LogRateAnalysisContent
initialAnalysisStart={appStateToWindowParameters(stateFromUrl.wp)}
dataView={dataView}
embeddingOrigin={AIOPS_TELEMETRY_ID.AIOPS_DEFAULT_SOURCE}
esSearchQuery={searchQuery}

View file

@ -38,6 +38,7 @@ import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants';
import { initialState, streamReducer } from '@kbn/aiops-log-rate-analysis/api/stream_reducer';
import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-log-rate-analysis/api/schema';
import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-log-rate-analysis/api/schema_v2';
import { useLogRateAnalysisStateContext } from '@kbn/aiops-components';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import {
@ -45,7 +46,6 @@ import {
LogRateAnalysisResultsTable,
LogRateAnalysisResultsGroupsTable,
} from '../log_rate_analysis_results_table';
import { useLogRateAnalysisResultsTableRowContext } from '../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
import { FieldFilterPopover } from './field_filter_popover';
import { LogRateAnalysisTypeCallOut } from './log_rate_analysis_type_callout';
@ -144,7 +144,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
// to be able to track it across rerenders.
const analysisStartTime = useRef<number | undefined>(window.performance.now());
const { clearAllRowState } = useLogRateAnalysisResultsTableRowContext();
const { clearAllRowState } = useLogRateAnalysisStateContext();
const [currentAnalysisType, setCurrentAnalysisType] = useState<LogRateAnalysisType | undefined>();
const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState<

View file

@ -8,8 +8,7 @@
import { sortBy } from 'lodash';
import type { SignificantItemGroup } from '@kbn/ml-agg-utils';
import type { GroupTableItem, GroupTableItemGroup } from './types';
import type { GroupTableItem, GroupTableItemGroup } from '@kbn/aiops-components';
export function getGroupTableItems(
significantItemsGroups: SignificantItemGroup[]

View file

@ -7,8 +7,7 @@
import { escapeKuery, escapeQuotes } from '@kbn/es-query';
import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils';
import type { GroupTableItem } from './types';
import type { GroupTableItem } from '@kbn/aiops-components';
export const getTableItemAsKQL = (tableItem: GroupTableItem | SignificantItem) => {
if (isSignificantItem(tableItem)) {

View file

@ -32,13 +32,13 @@ import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query';
import { useLogRateAnalysisStateContext } from '@kbn/aiops-components';
import { useEuiTheme } from '../../hooks/use_eui_theme';
import { MiniHistogram } from '../mini_histogram';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label';
import { useLogRateAnalysisResultsTableRowContext } from './log_rate_analysis_results_table_row_provider';
import { FieldStatsPopover } from '../field_stats_popover';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
import { useViewInDiscoverAction } from './use_view_in_discover_action';
@ -93,7 +93,7 @@ export const LogRateAnalysisResultsTable: FC<LogRateAnalysisResultsTableProps> =
selectedSignificantItem,
setPinnedSignificantItem,
setSelectedSignificantItem,
} = useLogRateAnalysisResultsTableRowContext();
} = useLogRateAnalysisStateContext();
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);

View file

@ -32,13 +32,12 @@ import type { SignificantItem } from '@kbn/ml-agg-utils';
import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import type { DataView } from '@kbn/data-views-plugin/public';
import { stringHash } from '@kbn/ml-string-hash';
import { useLogRateAnalysisStateContext, type GroupTableItem } from '@kbn/aiops-components';
import { MiniHistogram } from '../mini_histogram';
import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label';
import { LogRateAnalysisResultsTable } from './log_rate_analysis_results_table';
import { useLogRateAnalysisResultsTableRowContext } from './log_rate_analysis_results_table_row_provider';
import type { GroupTableItem } from './types';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
import { useViewInDiscoverAction } from './use_view_in_discover_action';
import { useViewInLogPatternAnalysisAction } from './use_view_in_log_pattern_analysis_action';
@ -97,7 +96,7 @@ export const LogRateAnalysisResultsGroupsTable: FC<LogRateAnalysisResultsTablePr
const primaryBackgroundColor = useEuiBackgroundColor('primary');
const { pinnedGroup, selectedGroup, setPinnedGroup, setSelectedGroup } =
useLogRateAnalysisResultsTableRowContext();
useLogRateAnalysisStateContext();
const dataViewId = dataView.id;
const toggleDetails = (item: GroupTableItem) => {

View file

@ -14,10 +14,10 @@ import type { SignificantItem } from '@kbn/ml-agg-utils';
import { finalSignificantItemGroups } from '@kbn/aiops-test-utils/artificial_logs/final_significant_item_groups';
import { significantTerms } from '@kbn/aiops-test-utils/artificial_logs/significant_terms';
import type { GroupTableItem } from '@kbn/aiops-components';
import { getGroupTableItems } from './get_group_table_items';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
import type { GroupTableItem } from './types';
interface Action {
render: (tableItem: SignificantItem | GroupTableItem) => ReactElement;

View file

@ -11,10 +11,10 @@ import { EuiCopy, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils';
import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components';
import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
import type { GroupTableItem, TableItemAction } from './types';
const copyToClipboardButtonLabel = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel',

View file

@ -9,13 +9,13 @@ import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import type { SignificantItem } from '@kbn/ml-agg-utils';
import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils';
import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
import type { GroupTableItem, TableItemAction } from './types';
const viewInDiscoverMessage = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover',

View file

@ -11,13 +11,13 @@ import type { SerializableRecord } from '@kbn/utility-types';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { isSignificantItem, type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils';
import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components';
import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
import type { GroupTableItem, TableItemAction } from './types';
const isLogPattern = (tableItem: SignificantItem | GroupTableItem) =>
isSignificantItem(tableItem) && tableItem.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN;

View file

@ -18,9 +18,9 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { SignificantItem } from '@kbn/ml-agg-utils';
import type { Query } from '@kbn/es-query';
import type { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils';
import type { GroupTableItem } from '@kbn/aiops-components';
import { buildExtendedBaseFilterCriteria } from './application/utils/build_extended_base_filter_criteria';
import type { GroupTableItem } from './components/log_rate_analysis_results_table/types';
export interface DocumentCountStats {
interval?: number;

View file

@ -18,9 +18,9 @@ import type { Dictionary } from '@kbn/ml-url-state';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import { useTimeBuckets } from '@kbn/ml-time-buckets';
import { AIOPS_PLUGIN_ID } from '@kbn/aiops-common/constants';
import type { GroupTableItem } from '@kbn/aiops-components';
import type { DocumentStatsSearchStrategyParams } from '../get_document_stats';
import type { GroupTableItem } from '../components/log_rate_analysis_results_table/types';
import { useAiopsAppContext } from './use_aiops_app_context';

View file

@ -80,6 +80,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('clicks the document count chart to start analysis');
await aiops.logRateAnalysisPage.clickDocumentCountChart(testData.chartClickCoordinates);
if (!testData.autoRun) {
await aiops.logRateAnalysisPage.assertNoAutoRunButtonExists();
await aiops.logRateAnalysisPage.clickNoAutoRunButton();
}
await aiops.logRateAnalysisPage.assertAnalysisSectionExists();
if (testData.brushDeviationTargetTimestamp) {

View file

@ -34,12 +34,14 @@ interface GetArtificialLogDataViewTestDataOptions {
analysisType: LogRateAnalysisType;
textField: boolean;
zeroDocsFallback: boolean;
autoRun: boolean;
}
export const getArtificialLogDataViewTestData = ({
analysisType,
textField,
zeroDocsFallback,
autoRun,
}: GetArtificialLogDataViewTestDataOptions): TestData => {
function getAnalysisGroupsTable() {
if (zeroDocsFallback) {
@ -133,6 +135,7 @@ export const getArtificialLogDataViewTestData = ({
return {
suiteTitle: getSuiteTitle(),
analysisType,
autoRun,
dataGenerator: getDataGenerator(),
isSavedSearch: false,
sourceIndexOrSavedSearch: getDataGenerator(),

View file

@ -12,6 +12,7 @@ import type { TestData } from '../../types';
export const farequoteDataViewTestData: TestData = {
suiteTitle: 'farequote with spike',
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
autoRun: false,
dataGenerator: 'farequote_with_spike',
isSavedSearch: false,
sourceIndexOrSavedSearch: 'ft_farequote',

View file

@ -12,6 +12,7 @@ import type { TestData } from '../../types';
export const farequoteDataViewTestDataWithQuery: TestData = {
suiteTitle: 'farequote with spike',
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
autoRun: false,
dataGenerator: 'farequote_with_spike',
isSavedSearch: false,
sourceIndexOrSavedSearch: 'ft_farequote',

View file

@ -13,6 +13,7 @@ import type { TestData } from '../../types';
export const kibanaLogsDataViewTestData: TestData = {
suiteTitle: 'kibana sample data logs',
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
autoRun: true,
dataGenerator: 'kibana_sample_data_logs',
isSavedSearch: false,
sourceIndexOrSavedSearch: 'kibana_sample_data_logstsdb',

View file

@ -22,40 +22,48 @@ export const logRateAnalysisTestData: TestData[] = [
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: false,
zeroDocsFallback: false,
autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: true,
zeroDocsFallback: false,
autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: false,
zeroDocsFallback: false,
autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: true,
zeroDocsFallback: false,
autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: true,
zeroDocsFallback: true,
autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: false,
zeroDocsFallback: true,
autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: true,
zeroDocsFallback: true,
autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: false,
zeroDocsFallback: true,
autoRun: false,
}),
];

View file

@ -54,6 +54,7 @@ interface TestDataExpectedWithoutSampleProbability {
export interface TestData {
suiteTitle: string;
analysisType: LogRateAnalysisType;
autoRun: boolean;
dataGenerator: LogRateAnalysisDataGenerator;
isSavedSearch?: boolean;
sourceIndexOrSavedSearch: string;

View file

@ -133,6 +133,16 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr
await this.assertHistogramBrushesExist();
},
async clickNoAutoRunButton() {
await testSubjects.clickWhenNotDisabledWithoutRetry(
'aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton'
);
await retry.tryForTime(30 * 1000, async () => {
await testSubjects.missingOrFail('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton');
});
},
async clickRerunAnalysisButton(shouldRerun: boolean) {
await testSubjects.clickWhenNotDisabledWithoutRetry(
`aiopsRerunAnalysisButton${shouldRerun ? ' shouldRerun' : ''}`
@ -250,6 +260,10 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr
);
},
async assertNoAutoRunButtonExists() {
await testSubjects.existOrFail('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton');
},
async assertProgressTitle(expectedProgressTitle: string) {
await retry.tryForTime(30 * 1000, async () => {
await testSubjects.existOrFail('aiopProgressTitle');