-
+ {/**
+ * We need position:relative on this parent container of the BrushBadges,
+ * because of the absolute positioning of the BrushBadges. Without it, the
+ * BrushBadges would not be positioned correctly when used in embedded panels.
+ */}
+
= (props) => {
const d3BrushContainer = useRef(null);
const brushes = useRef([]);
+ // id to prefix html ids for the brushes since this component can be used
+ // multiple times within dashboard and embedded charts.
+ const htmlId = useMemo(() => htmlIdGenerator()(), []);
+
// We need to pass props to refs here because the d3-brush code doesn't consider
// native React prop changes. The brush code does its own check whether these props changed then.
// The initialized brushes might otherwise act on stale data.
@@ -135,10 +141,10 @@ export const DualBrush: FC = (props) => {
const xMax = x(maxRef.current) ?? 0;
const minExtentPx = Math.round((xMax - xMin) / 100);
- const baselineBrush = d3.select('#aiops-brush-baseline');
+ const baselineBrush = d3.select(`#aiops-brush-baseline-${htmlId}`);
const baselineSelection = d3.brushSelection(baselineBrush.node() as SVGGElement);
- const deviationBrush = d3.select('#aiops-brush-deviation');
+ const deviationBrush = d3.select(`#aiops-brush-deviation-${htmlId}`);
const deviationSelection = d3.brushSelection(deviationBrush.node() as SVGGElement);
if (!isBrushXSelection(deviationSelection) || !isBrushXSelection(baselineSelection)) {
@@ -260,7 +266,7 @@ export const DualBrush: FC = (props) => {
.insert('g', '.brush')
.attr('class', 'brush')
.attr('id', (b: DualBrush) => {
- return 'aiops-brush-' + b.id;
+ return `aiops-brush-${b.id}-${htmlId}`;
})
.attr('data-test-subj', (b: DualBrush) => {
// Uppercase the first character of the `id` so we get aiopsBrushBaseline/aiopsBrushDeviation.
@@ -339,6 +345,7 @@ export const DualBrush: FC = (props) => {
drawBrushes();
}
}, [
+ htmlId,
min,
max,
width,
diff --git a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx
index 098f7038f82c8..173f33e08f0b4 100644
--- a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx
+++ b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx
@@ -70,7 +70,6 @@ export const ProgressControls: FC> = (pr
const { euiTheme } = useEuiTheme();
const runningProgressBarStyles = useAnimatedProgressBarBackground(euiTheme.colors.success);
- const analysisCompleteStyle = { display: 'none' };
return (
@@ -144,32 +143,30 @@ export const ProgressControls: FC> = (pr
) : null}
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+ ) : null}
{children}
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts b/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts
index 054bb876a4f7a..a9812a7507441 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/constants.ts
@@ -33,3 +33,9 @@ export const RANDOM_SAMPLER_SEED = 3867412;
/** Highlighting color for charts */
export const LOG_RATE_ANALYSIS_HIGHLIGHT_COLOR = 'orange';
+
+/** */
+export const EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE = 'aiopsLogRateAnalysisEmbeddable' as const;
+
+/** */
+export const LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME = 'aiopsLogRateAnalysisEmbeddableDataViewId';
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts
index 4652d604c5d61..d02a3bea22bf3 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts
@@ -6,10 +6,9 @@
*/
import type { TypedUseSelectorHook } from 'react-redux';
-import { useDispatch, useSelector, useStore } from 'react-redux';
-import type { AppDispatch, AppStore, RootState } from './store';
+import { useDispatch, useSelector } from 'react-redux';
+import type { AppDispatch, RootState } from './store';
// Improves TypeScript support compared to plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook = useSelector;
-export const useAppStore: () => AppStore = useStore;
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts
index 785bb02c24f31..7f7710ec23f3b 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts
@@ -11,6 +11,7 @@ export {
setAnalysisType,
setAutoRunAnalysis,
setDocumentCountChartData,
+ setGroupResults,
setInitialAnalysisStart,
setIsBrushCleared,
setStickyHistogram,
@@ -23,9 +24,10 @@ export {
setPinnedSignificantItem,
setSelectedGroup,
setSelectedSignificantItem,
-} from './log_rate_analysis_table_row_slice';
+ setSkippedColumns,
+} from './log_rate_analysis_table_slice';
export { LogRateAnalysisReduxProvider } from './store';
-export { useAppDispatch, useAppSelector, useAppStore } from './hooks';
+export { useAppDispatch, useAppSelector } from './hooks';
export { useCurrentSelectedGroup } from './use_current_selected_group';
export { useCurrentSelectedSignificantItem } from './use_current_selected_significant_item';
export type { GroupTableItem, GroupTableItemGroup, TableItemAction } from './types';
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts
index 4f829b0e0bf5a..5b4946dc2eb2b 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.test.ts
@@ -9,14 +9,16 @@ import { httpServiceMock } from '@kbn/core/public/mocks';
import type { FetchFieldCandidatesResponse } from '../queries/fetch_field_candidates';
-import { fetchFieldCandidates } from './log_rate_analysis_field_candidates_slice';
+import { fetchFieldCandidates, getDefaultState } from './log_rate_analysis_field_candidates_slice';
const mockHttp = httpServiceMock.createStartContract();
describe('fetchFieldCandidates', () => {
it('dispatches field candidates', async () => {
const mockDispatch = jest.fn();
- const mockGetState = jest.fn();
+ const mockGetState = jest.fn().mockReturnValue({
+ logRateAnalysisFieldCandidates: getDefaultState(),
+ });
const mockResponse: FetchFieldCandidatesResponse = {
isECS: false,
@@ -60,7 +62,12 @@ describe('fetchFieldCandidates', () => {
payload: {
fieldSelectionMessage:
'2 out of 5 fields were preselected for the analysis. Use the "Fields" dropdown to adjust the selection.',
- fieldFilterSkippedItems: [
+ initialFieldFilterSkippedItems: [
+ 'another-keyword-field',
+ 'another-text-field',
+ 'yet-another-text-field',
+ ],
+ currentFieldFilterSkippedItems: [
'another-keyword-field',
'another-text-field',
'yet-another-text-field',
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts
index aa5cb969e5401..07b1cd6fee402 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_field_candidates_slice.ts
@@ -90,10 +90,14 @@ export const fetchFieldCandidates = createAsyncThunk(
...selectedKeywordFieldCandidates,
...selectedTextFieldCandidates,
];
- const fieldFilterSkippedItems = fieldFilterUniqueItems.filter(
+ const initialFieldFilterSkippedItems = fieldFilterUniqueItems.filter(
(d) => !fieldFilterUniqueSelectedItems.includes(d)
);
+ const currentFieldFilterSkippedItems = (
+ thunkApi.getState() as { logRateAnalysisFieldCandidates: FieldCandidatesState }
+ ).logRateAnalysisFieldCandidates.currentFieldFilterSkippedItems;
+
thunkApi.dispatch(
setAllFieldCandidates({
fieldSelectionMessage: getFieldSelectionMessage(
@@ -102,7 +106,13 @@ export const fetchFieldCandidates = createAsyncThunk(
fieldFilterUniqueSelectedItems.length
),
fieldFilterUniqueItems,
- fieldFilterSkippedItems,
+ initialFieldFilterSkippedItems,
+ // If the currentFieldFilterSkippedItems is null, we're on the first load,
+ // only then we set the current skipped fields to the initial skipped fields.
+ currentFieldFilterSkippedItems:
+ currentFieldFilterSkippedItems === null
+ ? initialFieldFilterSkippedItems
+ : currentFieldFilterSkippedItems,
keywordFieldCandidates,
textFieldCandidates,
selectedKeywordFieldCandidates,
@@ -116,18 +126,20 @@ export interface FieldCandidatesState {
isLoading: boolean;
fieldSelectionMessage?: string;
fieldFilterUniqueItems: string[];
- fieldFilterSkippedItems: string[];
+ initialFieldFilterSkippedItems: string[];
+ currentFieldFilterSkippedItems: string[] | null;
keywordFieldCandidates: string[];
textFieldCandidates: string[];
selectedKeywordFieldCandidates: string[];
selectedTextFieldCandidates: string[];
}
-function getDefaultState(): FieldCandidatesState {
+export function getDefaultState(): FieldCandidatesState {
return {
isLoading: false,
fieldFilterUniqueItems: [],
- fieldFilterSkippedItems: [],
+ initialFieldFilterSkippedItems: [],
+ currentFieldFilterSkippedItems: null,
keywordFieldCandidates: [],
textFieldCandidates: [],
selectedKeywordFieldCandidates: [],
@@ -145,6 +157,12 @@ export const logRateAnalysisFieldCandidatesSlice = createSlice({
) => {
return { ...state, ...action.payload };
},
+ setCurrentFieldFilterSkippedItems: (
+ state: FieldCandidatesState,
+ action: PayloadAction
+ ) => {
+ return { ...state, currentFieldFilterSkippedItems: action.payload };
+ },
},
extraReducers: (builder) => {
builder.addCase(fetchFieldCandidates.pending, (state) => {
@@ -157,4 +175,5 @@ export const logRateAnalysisFieldCandidatesSlice = createSlice({
});
// Action creators are generated for each case reducer function
-export const { setAllFieldCandidates } = logRateAnalysisFieldCandidatesSlice.actions;
+export const { setAllFieldCandidates, setCurrentFieldFilterSkippedItems } =
+ logRateAnalysisFieldCandidatesSlice.actions;
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts
index 251f0d3263800..8399e896900c6 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts
@@ -34,6 +34,7 @@ export interface LogRateAnalysisState {
autoRunAnalysis: boolean;
initialAnalysisStart: InitialAnalysisStart;
isBrushCleared: boolean;
+ groupResults: boolean;
stickyHistogram: boolean;
chartWindowParameters?: WindowParameters;
earliest?: number;
@@ -48,6 +49,7 @@ function getDefaultState(): LogRateAnalysisState {
autoRunAnalysis: true,
initialAnalysisStart: undefined,
isBrushCleared: true,
+ groupResults: false,
documentStats: {
sampleProbability: 1,
totalCount: 0,
@@ -98,6 +100,9 @@ export const logRateAnalysisSlice = createSlice({
state.intervalMs = action.payload.intervalMs;
state.documentStats = action.payload.documentStats;
},
+ setGroupResults: (state: LogRateAnalysisState, action: PayloadAction) => {
+ state.groupResults = action.payload;
+ },
setInitialAnalysisStart: (
state: LogRateAnalysisState,
action: PayloadAction
@@ -127,6 +132,7 @@ export const {
setAnalysisType,
setAutoRunAnalysis,
setDocumentCountChartData,
+ setGroupResults,
setInitialAnalysisStart,
setIsBrushCleared,
setStickyHistogram,
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts
deleted file mode 100644
index 3da98e4cc80ff..0000000000000
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.
- */
-
-import type { PayloadAction } from '@reduxjs/toolkit';
-import { createSlice } from '@reduxjs/toolkit';
-
-import type { SignificantItem } from '@kbn/ml-agg-utils';
-
-import type { GroupTableItem } from './types';
-
-type SignificantItemOrNull = SignificantItem | null;
-type GroupOrNull = GroupTableItem | null;
-
-export interface LogRateAnalysisTableRowState {
- pinnedGroup: GroupOrNull;
- pinnedSignificantItem: SignificantItemOrNull;
- selectedGroup: GroupOrNull;
- selectedSignificantItem: SignificantItemOrNull;
-}
-
-function getDefaultState(): LogRateAnalysisTableRowState {
- return {
- pinnedGroup: null,
- pinnedSignificantItem: null,
- selectedGroup: null,
- selectedSignificantItem: null,
- };
-}
-
-export const logRateAnalysisTableRowSlice = createSlice({
- name: 'logRateAnalysisTableRow',
- initialState: getDefaultState(),
- reducers: {
- clearAllRowState: (state: LogRateAnalysisTableRowState) => {
- state.pinnedGroup = null;
- state.pinnedSignificantItem = null;
- state.selectedGroup = null;
- state.selectedSignificantItem = null;
- },
- setPinnedGroup: (state: LogRateAnalysisTableRowState, action: PayloadAction) => {
- state.pinnedGroup = action.payload;
- },
- setPinnedSignificantItem: (
- state: LogRateAnalysisTableRowState,
- action: PayloadAction
- ) => {
- state.pinnedSignificantItem = action.payload;
- },
- setSelectedGroup: (state: LogRateAnalysisTableRowState, action: PayloadAction) => {
- state.selectedGroup = action.payload;
- },
- setSelectedSignificantItem: (
- state: LogRateAnalysisTableRowState,
- action: PayloadAction
- ) => {
- state.selectedSignificantItem = action.payload;
- },
- },
-});
-
-// Action creators are generated for each case reducer function
-export const {
- clearAllRowState,
- setPinnedGroup,
- setPinnedSignificantItem,
- setSelectedGroup,
- setSelectedSignificantItem,
-} = logRateAnalysisTableRowSlice.actions;
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.test.ts
new file mode 100644
index 0000000000000..498ada00654f0
--- /dev/null
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.test.ts
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+import { configureStore } from '@reduxjs/toolkit';
+import {
+ logRateAnalysisTableSlice,
+ localStorageListenerMiddleware,
+ setSkippedColumns,
+ getPreloadedState,
+ AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS,
+ type LogRateAnalysisResultsTableColumnName,
+} from './log_rate_analysis_table_slice';
+
+describe('getPreloadedState', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ it('should return default state when localStorage is empty', () => {
+ const state = getPreloadedState();
+ expect(state).toEqual({
+ skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'],
+ pinnedGroup: null,
+ pinnedSignificantItem: null,
+ selectedGroup: null,
+ selectedSignificantItem: null,
+ });
+ });
+
+ it('should return state with skippedColumns from localStorage', () => {
+ const skippedColumns = ['Log rate', 'Doc count'];
+ localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, JSON.stringify(skippedColumns));
+
+ const state = getPreloadedState();
+ expect(state.skippedColumns).toEqual(skippedColumns);
+ });
+
+ it('should return default state when localStorage contains invalid JSON', () => {
+ localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, 'invalid-json');
+
+ const state = getPreloadedState();
+ expect(state).toEqual({
+ skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'],
+ pinnedGroup: null,
+ pinnedSignificantItem: null,
+ selectedGroup: null,
+ selectedSignificantItem: null,
+ });
+ });
+
+ it('should return default state when localStorage does not contain skippedColumns', () => {
+ localStorage.setItem('someOtherKey', JSON.stringify(['someValue']));
+
+ const state = getPreloadedState();
+ expect(state).toEqual({
+ skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'],
+ pinnedGroup: null,
+ pinnedSignificantItem: null,
+ selectedGroup: null,
+ selectedSignificantItem: null,
+ });
+ });
+});
+
+type Store = ReturnType;
+
+describe('localStorageListenerMiddleware', () => {
+ let store: Store;
+
+ beforeEach(() => {
+ localStorage.clear();
+ store = configureStore({
+ reducer: {
+ logRateAnalysisTable: logRateAnalysisTableSlice.reducer,
+ },
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware().prepend(localStorageListenerMiddleware.middleware),
+ }) as Store;
+ });
+
+ it('should save skippedColumns to localStorage when setSkippedColumns is dispatched', () => {
+ const skippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate', 'Doc count'];
+ store.dispatch(setSkippedColumns(skippedColumns));
+
+ const storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS);
+ expect(storedSkippedColumns).toEqual(JSON.stringify(skippedColumns));
+ });
+
+ it('should handle invalid JSON in localStorage gracefully', () => {
+ localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, 'invalid-json');
+ const skippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate', 'Doc count'];
+ store.dispatch(setSkippedColumns(skippedColumns));
+
+ const storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS);
+ expect(storedSkippedColumns).toEqual(JSON.stringify(skippedColumns));
+ });
+
+ it('should not overwrite other localStorage keys', () => {
+ const otherKey = 'someOtherKey';
+ const otherValue = ['someValue'];
+ localStorage.setItem(otherKey, JSON.stringify(otherValue));
+
+ const skippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate', 'Doc count'];
+ store.dispatch(setSkippedColumns(skippedColumns));
+
+ const storedOtherValue = localStorage.getItem(otherKey);
+ expect(storedOtherValue).toEqual(JSON.stringify(otherValue));
+ });
+
+ it('should update localStorage when skippedColumns are updated multiple times', () => {
+ const initialSkippedColumns: LogRateAnalysisResultsTableColumnName[] = ['Log rate'];
+ store.dispatch(setSkippedColumns(initialSkippedColumns));
+
+ let storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS);
+ expect(storedSkippedColumns).toEqual(JSON.stringify(initialSkippedColumns));
+
+ const updatedSkippedColumns: LogRateAnalysisResultsTableColumnName[] = [
+ 'Log rate',
+ 'Doc count',
+ ];
+ store.dispatch(setSkippedColumns(updatedSkippedColumns));
+
+ storedSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS);
+ expect(storedSkippedColumns).toEqual(JSON.stringify(updatedSkippedColumns));
+ });
+});
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts
new file mode 100644
index 0000000000000..1d9c83dea98a6
--- /dev/null
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_slice.ts
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+import type { PayloadAction } from '@reduxjs/toolkit';
+import { createSlice, createListenerMiddleware } from '@reduxjs/toolkit';
+
+import { i18n } from '@kbn/i18n';
+import type { SignificantItem } from '@kbn/ml-agg-utils';
+
+import type { GroupTableItem } from './types';
+
+export const AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS = 'aiops.logRateAnalysisResultColumns';
+
+export const commonColumns = {
+ ['Log rate']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.logRateColumnTitle', {
+ defaultMessage: 'Log rate',
+ }),
+ ['Doc count']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.docCountColumnTitle', {
+ defaultMessage: 'Doc count',
+ }),
+ ['p-value']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.pValueColumnTitle', {
+ defaultMessage: 'p-value',
+ }),
+ ['Impact']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.impactColumnTitle', {
+ defaultMessage: 'Impact',
+ }),
+ ['Baseline rate']: i18n.translate(
+ 'xpack.aiops.logRateAnalysis.resultsTable.baselineRateColumnTitle',
+ {
+ defaultMessage: 'Baseline rate',
+ }
+ ),
+ ['Deviation rate']: i18n.translate(
+ 'xpack.aiops.logRateAnalysis.resultsTable.deviationRateColumnTitle',
+ {
+ defaultMessage: 'Deviation rate',
+ }
+ ),
+ ['Log rate change']: i18n.translate(
+ 'xpack.aiops.logRateAnalysis.resultsTable.logRateChangeColumnTitle',
+ {
+ defaultMessage: 'Log rate change',
+ }
+ ),
+ ['Actions']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.actionsColumnTitle', {
+ defaultMessage: 'Actions',
+ }),
+};
+
+export const significantItemColumns = {
+ ['Field name']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.fieldNameColumnTitle', {
+ defaultMessage: 'Field name',
+ }),
+ ['Field value']: i18n.translate(
+ 'xpack.aiops.logRateAnalysis.resultsTable.fieldValueColumnTitle',
+ {
+ defaultMessage: 'Field value',
+ }
+ ),
+ ...commonColumns,
+} as const;
+
+export type LogRateAnalysisResultsTableColumnName = keyof typeof significantItemColumns | 'unique';
+
+type SignificantItemOrNull = SignificantItem | null;
+type GroupOrNull = GroupTableItem | null;
+
+export interface LogRateAnalysisTableState {
+ skippedColumns: LogRateAnalysisResultsTableColumnName[];
+ pinnedGroup: GroupOrNull;
+ pinnedSignificantItem: SignificantItemOrNull;
+ selectedGroup: GroupOrNull;
+ selectedSignificantItem: SignificantItemOrNull;
+}
+
+function getDefaultState(): LogRateAnalysisTableState {
+ return {
+ skippedColumns: ['p-value', 'Baseline rate', 'Deviation rate'],
+ pinnedGroup: null,
+ pinnedSignificantItem: null,
+ selectedGroup: null,
+ selectedSignificantItem: null,
+ };
+}
+
+export function getPreloadedState(): LogRateAnalysisTableState {
+ const defaultState = getDefaultState();
+
+ const localStorageSkippedColumns = localStorage.getItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS);
+
+ if (localStorageSkippedColumns === null) {
+ return defaultState;
+ }
+
+ try {
+ defaultState.skippedColumns = JSON.parse(localStorageSkippedColumns);
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.warn('Failed to parse skipped columns from local storage:', err);
+ }
+
+ return defaultState;
+}
+
+export const logRateAnalysisTableSlice = createSlice({
+ name: 'logRateAnalysisTable',
+ initialState: getDefaultState(),
+ reducers: {
+ clearAllRowState: (state: LogRateAnalysisTableState) => {
+ state.pinnedGroup = null;
+ state.pinnedSignificantItem = null;
+ state.selectedGroup = null;
+ state.selectedSignificantItem = null;
+ },
+ setPinnedGroup: (state: LogRateAnalysisTableState, action: PayloadAction) => {
+ state.pinnedGroup = action.payload;
+ },
+ setPinnedSignificantItem: (
+ state: LogRateAnalysisTableState,
+ action: PayloadAction
+ ) => {
+ state.pinnedSignificantItem = action.payload;
+ },
+ setSelectedGroup: (state: LogRateAnalysisTableState, action: PayloadAction) => {
+ state.selectedGroup = action.payload;
+ },
+ setSelectedSignificantItem: (
+ state: LogRateAnalysisTableState,
+ action: PayloadAction
+ ) => {
+ state.selectedSignificantItem = action.payload;
+ },
+ setSkippedColumns: (
+ state: LogRateAnalysisTableState,
+ action: PayloadAction
+ ) => {
+ state.skippedColumns = action.payload;
+ },
+ },
+});
+
+// Action creators are generated for each case reducer function
+export const {
+ clearAllRowState,
+ setPinnedGroup,
+ setPinnedSignificantItem,
+ setSelectedGroup,
+ setSelectedSignificantItem,
+ setSkippedColumns,
+} = logRateAnalysisTableSlice.actions;
+
+// Create listener middleware
+export const localStorageListenerMiddleware = createListenerMiddleware();
+
+// Add a listener to save skippedColumns to localStorage whenever it changes
+localStorageListenerMiddleware.startListening({
+ actionCreator: setSkippedColumns,
+ effect: (action, listenerApi) => {
+ const state = listenerApi.getState() as { logRateAnalysisTable: LogRateAnalysisTableState };
+ try {
+ const serializedState = JSON.stringify(state.logRateAnalysisTable.skippedColumns);
+ localStorage.setItem(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, serializedState);
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.warn('Failed to save state to localStorage:', err);
+ }
+ },
+});
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx
index 1589b27348d89..9fd8e8240dde3 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx
@@ -15,12 +15,19 @@ import { streamSlice } from '@kbn/ml-response-stream/client';
import { logRateAnalysisResultsSlice } from '../api/stream_reducer';
import { logRateAnalysisSlice } from './log_rate_analysis_slice';
-import { logRateAnalysisTableRowSlice } from './log_rate_analysis_table_row_slice';
+import {
+ logRateAnalysisTableSlice,
+ getPreloadedState,
+ localStorageListenerMiddleware,
+} from './log_rate_analysis_table_slice';
import { logRateAnalysisFieldCandidatesSlice } from './log_rate_analysis_field_candidates_slice';
import type { InitialAnalysisStart } from './log_rate_analysis_slice';
const getReduxStore = () =>
configureStore({
+ preloadedState: {
+ logRateAnalysisTable: getPreloadedState(),
+ },
reducer: {
// General page state
logRateAnalysis: logRateAnalysisSlice.reducer,
@@ -28,11 +35,13 @@ const getReduxStore = () =>
logRateAnalysisFieldCandidates: logRateAnalysisFieldCandidatesSlice.reducer,
// Analysis results
logRateAnalysisResults: logRateAnalysisResultsSlice.reducer,
- // Handles running the analysis
- logRateAnalysisStream: streamSlice.reducer,
- // Handles hovering and pinning table rows
- logRateAnalysisTableRow: logRateAnalysisTableRowSlice.reducer,
+ // Handles running the analysis, needs to be "stream" for the async thunk to work properly.
+ stream: streamSlice.reducer,
+ // Handles hovering and pinning table rows and column selection
+ logRateAnalysisTable: logRateAnalysisTableSlice.reducer,
},
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware().prepend(localStorageListenerMiddleware.middleware),
});
interface LogRateAnalysisReduxProviderProps {
@@ -54,6 +63,6 @@ export const LogRateAnalysisReduxProvider: FC<
};
// Infer the `RootState` and `AppDispatch` types from the store itself
-export type AppStore = ReturnType;
+type AppStore = ReturnType;
export type RootState = ReturnType;
export type AppDispatch = AppStore['dispatch'];
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts
index 9653691d3efd4..a19bd3e18a735 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts
@@ -10,8 +10,8 @@ import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from './store';
import { useAppSelector } from './hooks';
-const selectSelectedGroup = (s: RootState) => s.logRateAnalysisTableRow.selectedGroup;
-const selectPinnedGroup = (s: RootState) => s.logRateAnalysisTableRow.pinnedGroup;
+const selectSelectedGroup = (s: RootState) => s.logRateAnalysisTable.selectedGroup;
+const selectPinnedGroup = (s: RootState) => s.logRateAnalysisTable.pinnedGroup;
const selectCurrentSelectedGroup = createSelector(
selectSelectedGroup,
selectPinnedGroup,
diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts
index d189d16fc2fa0..f7327d3033df0 100644
--- a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts
+++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts
@@ -11,9 +11,8 @@ import type { RootState } from './store';
import { useAppSelector } from './hooks';
const selectSelectedSignificantItem = (s: RootState) =>
- s.logRateAnalysisTableRow.selectedSignificantItem;
-const selectPinnedSignificantItem = (s: RootState) =>
- s.logRateAnalysisTableRow.pinnedSignificantItem;
+ s.logRateAnalysisTable.selectedSignificantItem;
+const selectPinnedSignificantItem = (s: RootState) => s.logRateAnalysisTable.pinnedSignificantItem;
const selectCurrentSelectedSignificantItem = createSelector(
selectSelectedSignificantItem,
selectPinnedSignificantItem,
diff --git a/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx b/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx
index 678dec7d36f42..4e7a501140b01 100644
--- a/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx
+++ b/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx
@@ -7,9 +7,8 @@
import type { PropsWithChildren, FC } from 'react';
import React, { useCallback, useState } from 'react';
-import type { CoreStart } from '@kbn/core/public';
+import type { ThemeServiceStart } from '@kbn/core-theme-browser';
import type { DataView } from '@kbn/data-plugin/common';
-import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats';
import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import type { FieldStatsProps } from '@kbn/unified-field-list/src/components/field_stats';
@@ -18,32 +17,18 @@ import { getProcessedFields } from '@kbn/ml-data-grid';
import { stringHash } from '@kbn/ml-string-hash';
import { lastValueFrom } from 'rxjs';
import { useRef } from 'react';
-import { useKibana } from '@kbn/kibana-react-plugin/public';
import { getMergedSampleDocsForPopulatedFieldsQuery } from './populated_fields/get_merged_populated_fields_query';
import { FieldStatsFlyout } from './field_stats_flyout';
import { MLFieldStatsFlyoutContext } from './use_field_stats_flyout_context';
import { PopulatedFieldsCacheManager } from './populated_fields/populated_fields_cache_manager';
-type Services = CoreStart & {
- data: DataPublicPluginStart;
-};
-
-function useDataSearch() {
- const { data } = useKibana().services;
-
- if (!data) {
- throw new Error('Kibana data service not available.');
- }
-
- return data.search;
-}
-
/**
* Props for the FieldStatsFlyoutProvider component.
*
* @typedef {Object} FieldStatsFlyoutProviderProps
* @property dataView - The data view object.
* @property fieldStatsServices - Services required for field statistics.
+ * @property theme - The EUI theme service.
* @property [timeRangeMs] - Optional time range in milliseconds.
* @property [dslQuery] - Optional DSL query for filtering field statistics.
* @property [disablePopulatedFields] - Optional flag to disable populated fields.
@@ -51,6 +36,7 @@ function useDataSearch() {
export type FieldStatsFlyoutProviderProps = PropsWithChildren<{
dataView: DataView;
fieldStatsServices: FieldStatsServices;
+ theme: ThemeServiceStart;
timeRangeMs?: TimeRangeMs;
dslQuery?: FieldStatsProps['dslQuery'];
disablePopulatedFields?: boolean;
@@ -79,12 +65,13 @@ export const FieldStatsFlyoutProvider: FC = (prop
const {
dataView,
fieldStatsServices,
+ theme,
timeRangeMs,
dslQuery,
disablePopulatedFields = false,
children,
} = props;
- const search = useDataSearch();
+ const { search } = fieldStatsServices.data;
const [isFieldStatsFlyoutVisible, setFieldStatsIsFlyoutVisible] = useState(false);
const [fieldName, setFieldName] = useState();
const [fieldValue, setFieldValue] = useState();
@@ -187,6 +174,7 @@ export const FieldStatsFlyoutProvider: FC = (prop
fieldValue,
timeRangeMs,
populatedFields,
+ theme,
}}
>
= (props) => {
const { field, label, onButtonClick, disabled, isEmpty, hideTrigger } = props;
- const themeVars = useThemeVars();
+ const theme = useFieldStatsFlyoutThemeVars();
+ const themeVars = useCurrentEuiThemeVars(theme);
+
const emptyFieldMessage = isEmpty
? ' ' +
i18n.translate('xpack.ml.newJob.wizard.fieldContextPopover.emptyFieldInSampleDocsMsg', {
diff --git a/x-pack/packages/ml/field_stats_flyout/tsconfig.json b/x-pack/packages/ml/field_stats_flyout/tsconfig.json
index 0010d79432e34..df70aa27788b8 100644
--- a/x-pack/packages/ml/field_stats_flyout/tsconfig.json
+++ b/x-pack/packages/ml/field_stats_flyout/tsconfig.json
@@ -23,9 +23,7 @@
"@kbn/i18n",
"@kbn/react-field",
"@kbn/ml-anomaly-utils",
- "@kbn/kibana-react-plugin",
"@kbn/ml-kibana-theme",
- "@kbn/core",
"@kbn/ml-data-grid",
"@kbn/ml-string-hash",
"@kbn/ml-is-populated-object",
@@ -33,5 +31,6 @@
"@kbn/ml-is-defined",
"@kbn/field-types",
"@kbn/ui-theme",
+ "@kbn/core-theme-browser",
]
}
diff --git a/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts b/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts
index ec6c28873011c..121426352e6e4 100644
--- a/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts
+++ b/x-pack/packages/ml/field_stats_flyout/use_field_stats_flyout_context.ts
@@ -7,6 +7,7 @@
import { createContext, useContext } from 'react';
import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
+import type { ThemeServiceStart } from '@kbn/core-theme-browser';
/**
* Represents the properties for the MLJobWizardFieldStatsFlyout component.
@@ -21,6 +22,7 @@ interface MLJobWizardFieldStatsFlyoutProps {
fieldValue?: string | number;
timeRangeMs?: TimeRangeMs;
populatedFields?: Set;
+ theme?: ThemeServiceStart;
}
/**
@@ -34,6 +36,7 @@ export const MLFieldStatsFlyoutContext = createContext {},
timeRangeMs: undefined,
populatedFields: undefined,
+ theme: undefined,
});
/**
@@ -41,5 +44,25 @@ export const MLFieldStatsFlyoutContext = createContext() {
populatedFields,
};
}
+
+export type UseFieldStatsTrigger = typeof useFieldStatsTrigger;
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
index aef12da303bcc..3328bd5f8585a 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
@@ -5,7 +5,10 @@
* 2.0.
*/
-import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import {
+ AggregationsCategorizeTextAnalyzer,
+ QueryDslQueryContainer,
+} from '@elastic/elasticsearch/lib/api/types';
import { calculateAuto } from '@kbn/calculate-auto';
import { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils';
import moment from 'moment';
@@ -109,9 +112,7 @@ export const createCategorizationRequestParams = ({
categorize_text: {
field: messageField,
size: maxCategoriesCount,
- categorization_analyzer: {
- tokenizer: 'standard',
- },
+ categorization_analyzer: categorizationAnalyzerConfig,
...(minDocsPerCategory > 0 ? { min_doc_count: minDocsPerCategory } : {}),
},
aggs: {
@@ -149,3 +150,38 @@ export const createCategoryQuery =
},
},
});
+
+// This emulates the behavior of the `ml_standard` tokenizer in the ML plugin in
+// regard to the hexadecimal and numeric tokens. The other parts pertaining to
+// infix punctuation and file paths are not easily emulated this way.
+// https://github.com/elastic/elasticsearch/blob/becd08da24df2af93eee28053d32929298cdccbd/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/categorization/MlStandardTokenizer.java#L35-L146
+// We don't use the `ml_standard` tokenizer directly because it produces tokens
+// that are different from the ones produced by the `standard` tokenizer upon
+// indexing.
+const categorizationAnalyzerConfig: AggregationsCategorizeTextAnalyzer = {
+ tokenizer: 'standard',
+ char_filter: [
+ 'first_line_with_letters',
+ // This ignores tokens that are hexadecimal numbers
+ // @ts-expect-error the official types don't support inline char filters
+ {
+ type: 'pattern_replace',
+ pattern: '\\b[a-fA-F][a-fA-F0-9]+\\b',
+ replacement: '',
+ },
+ // This ignore tokens that start with a digit
+ // @ts-expect-error the official types don't support inline char filters
+ {
+ type: 'pattern_replace',
+ pattern: '\\b\\d\\w*\\b',
+ replacement: '',
+ },
+ ],
+ filter: [
+ // @ts-expect-error the official types don't support inline token filters
+ {
+ type: 'limit',
+ max_token_count: '100',
+ },
+ ],
+};
diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts
index 3421e381d07b6..99bcbf15ecfb9 100644
--- a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts
+++ b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts
@@ -575,8 +575,6 @@ describe('create()', () => {
allowedHosts: ['*'],
preconfiguredAlertHistoryEsIndex: false,
preconfigured: {},
- proxyRejectUnauthorizedCertificates: true, // legacy
- rejectUnauthorized: true, // legacy
proxyBypassHosts: undefined,
proxyOnlyHosts: undefined,
maxResponseContentLength: new ByteSizeValue(1000000),
diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts
index 2b5c4efc283b6..ce16aca508af6 100644
--- a/x-pack/plugins/actions/server/actions_config.test.ts
+++ b/x-pack/plugins/actions/server/actions_config.test.ts
@@ -30,8 +30,6 @@ const defaultActionsConfig: ActionsConfig = {
enabledActionTypes: [],
preconfiguredAlertHistoryEsIndex: false,
preconfigured: {},
- proxyRejectUnauthorizedCertificates: true, // legacy
- rejectUnauthorized: true, // legacy
maxResponseContentLength: new ByteSizeValue(1000000),
responseTimeout: moment.duration(60000),
ssl: {
@@ -318,25 +316,6 @@ describe('getProxySettings', () => {
expect(proxySettings?.proxyUrl).toBe(config.proxyUrl);
});
- test('returns proper verificationMode values, beased on the legacy config option proxyRejectUnauthorizedCertificates', () => {
- const configTrue: ActionsConfig = {
- ...defaultActionsConfig,
- proxyUrl: 'https://proxy.elastic.co',
- proxyRejectUnauthorizedCertificates: true,
- };
- let proxySettings = getActionsConfigurationUtilities(configTrue).getProxySettings();
- expect(proxySettings?.proxySSLSettings.verificationMode).toBe('full');
-
- const configFalse: ActionsConfig = {
- ...defaultActionsConfig,
- proxyUrl: 'https://proxy.elastic.co',
- proxyRejectUnauthorizedCertificates: false,
- ssl: {},
- };
- proxySettings = getActionsConfigurationUtilities(configFalse).getProxySettings();
- expect(proxySettings?.proxySSLSettings.verificationMode).toBe('none');
- });
-
test('returns proper verificationMode value, based on the SSL proxy configuration', () => {
const configTrue: ActionsConfig = {
...defaultActionsConfig,
diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts
index e77c0528d16a1..30c96b4b6a998 100644
--- a/x-pack/plugins/actions/server/actions_config.ts
+++ b/x-pack/plugins/actions/server/actions_config.ts
@@ -122,10 +122,7 @@ function getProxySettingsFromConfig(config: ActionsConfig): undefined | ProxySet
proxyBypassHosts: arrayAsSet(config.proxyBypassHosts),
proxyOnlyHosts: arrayAsSet(config.proxyOnlyHosts),
proxyHeaders: config.proxyHeaders,
- proxySSLSettings: getSSLSettingsFromConfig(
- config.ssl?.proxyVerificationMode,
- config.proxyRejectUnauthorizedCertificates
- ),
+ proxySSLSettings: getSSLSettingsFromConfig(config.ssl?.proxyVerificationMode),
};
}
@@ -200,8 +197,7 @@ export function getActionsConfigurationUtilities(
isActionTypeEnabled,
getProxySettings: () => getProxySettingsFromConfig(config),
getResponseSettings: () => getResponseSettingsFromConfig(config),
- getSSLSettings: () =>
- getSSLSettingsFromConfig(config.ssl?.verificationMode, config.rejectUnauthorized),
+ getSSLSettings: () => getSSLSettingsFromConfig(config.ssl?.verificationMode),
ensureUriAllowed(uri: string) {
if (!isUriAllowed(uri)) {
throw new Error(allowListErrorMessage(AllowListingField.URL, uri));
diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts
index 4034fc5cb50b5..01bcd867fda53 100644
--- a/x-pack/plugins/actions/server/config.test.ts
+++ b/x-pack/plugins/actions/server/config.test.ts
@@ -35,8 +35,6 @@ describe('config validation', () => {
"microsoftGraphApiUrl": "https://graph.microsoft.com/v1.0",
"preconfigured": Object {},
"preconfiguredAlertHistoryEsIndex": false,
- "proxyRejectUnauthorizedCertificates": true,
- "rejectUnauthorized": true,
"responseTimeout": "PT1M",
"usage": Object {
"url": "https://usage-api.usage-api/api/v1/usage",
@@ -56,8 +54,6 @@ describe('config validation', () => {
},
},
},
- proxyRejectUnauthorizedCertificates: false,
- rejectUnauthorized: false,
};
expect(configSchema.validate(config)).toMatchInlineSnapshot(`
Object {
@@ -85,8 +81,6 @@ describe('config validation', () => {
},
},
"preconfiguredAlertHistoryEsIndex": false,
- "proxyRejectUnauthorizedCertificates": false,
- "rejectUnauthorized": false,
"responseTimeout": "PT1M",
"usage": Object {
"url": "https://usage-api.usage-api/api/v1/usage",
@@ -224,8 +218,6 @@ describe('config validation', () => {
"microsoftGraphApiUrl": "https://graph.microsoft.com/v1.0",
"preconfigured": Object {},
"preconfiguredAlertHistoryEsIndex": false,
- "proxyRejectUnauthorizedCertificates": true,
- "rejectUnauthorized": true,
"responseTimeout": "PT1M",
"ssl": Object {
"proxyVerificationMode": "none",
diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts
index f475c05424df4..f16a9830678cd 100644
--- a/x-pack/plugins/actions/server/config.ts
+++ b/x-pack/plugins/actions/server/config.ts
@@ -44,10 +44,6 @@ const customHostSettingsSchema = schema.object({
),
ssl: schema.maybe(
schema.object({
- /**
- * @deprecated in favor of `verificationMode`
- **/
- rejectUnauthorized: schema.maybe(schema.boolean()),
verificationMode: schema.maybe(
schema.oneOf(
[schema.literal('none'), schema.literal('certificate'), schema.literal('full')],
@@ -98,16 +94,8 @@ export const configSchema = schema.object({
}),
proxyUrl: schema.maybe(schema.string()),
proxyHeaders: schema.maybe(schema.recordOf(schema.string(), schema.string())),
- /**
- * @deprecated in favor of `ssl.proxyVerificationMode`
- **/
- proxyRejectUnauthorizedCertificates: schema.boolean({ defaultValue: true }),
proxyBypassHosts: schema.maybe(schema.arrayOf(schema.string({ hostname: true }))),
proxyOnlyHosts: schema.maybe(schema.arrayOf(schema.string({ hostname: true }))),
- /**
- * @deprecated in favor of `ssl.verificationMode`
- **/
- rejectUnauthorized: schema.boolean({ defaultValue: true }),
ssl: schema.maybe(
schema.object({
verificationMode: schema.maybe(
diff --git a/x-pack/plugins/actions/server/index.test.ts b/x-pack/plugins/actions/server/index.test.ts
deleted file mode 100644
index 43f69da15de2f..0000000000000
--- a/x-pack/plugins/actions/server/index.test.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.
- */
-import { config } from '.';
-import { applyDeprecations, configDeprecationFactory } from '@kbn/config';
-import { configDeprecationsMock } from '@kbn/core/server/mocks';
-
-const CONFIG_PATH = 'xpack.actions';
-const applyStackAlertDeprecations = (settings: Record = {}) => {
- const deprecations = config.deprecations!(configDeprecationFactory);
- const deprecationMessages: string[] = [];
- const _config = {
- [CONFIG_PATH]: settings,
- };
- const { config: migrated, changedPaths } = applyDeprecations(
- _config,
- deprecations.map((deprecation) => ({
- deprecation,
- path: CONFIG_PATH,
- context: configDeprecationsMock.createContext(),
- })),
- () =>
- ({ message }) =>
- deprecationMessages.push(message)
- );
- return {
- messages: deprecationMessages,
- migrated,
- changedPaths,
- };
-};
-
-describe('index', () => {
- describe('deprecations', () => {
- it('should properly unset deprecated configs', () => {
- const { messages, changedPaths } = applyStackAlertDeprecations({
- customHostSettings: [{ ssl: { rejectUnauthorized: false } }],
- rejectUnauthorized: false,
- proxyRejectUnauthorizedCertificates: false,
- });
- expect(changedPaths.unset).toStrictEqual([
- 'xpack.actions.customHostSettings.ssl.rejectUnauthorized',
- 'xpack.actions.rejectUnauthorized',
- 'xpack.actions.proxyRejectUnauthorizedCertificates',
- ]);
- expect(messages.length).toBe(3);
- expect(messages[0]).toBe(
- '"xpack.actions.customHostSettings[].ssl.rejectUnauthorized" is deprecated.Use "xpack.actions.customHostSettings[].ssl.verificationMode" instead, with the setting "verificationMode:full" eql to "rejectUnauthorized:true", and "verificationMode:none" eql to "rejectUnauthorized:false".'
- );
- expect(messages[1]).toBe(
- '"xpack.actions.rejectUnauthorized" is deprecated. Use "xpack.actions.ssl.verificationMode" instead, with the setting "verificationMode:full" eql to "rejectUnauthorized:true", and "verificationMode:none" eql to "rejectUnauthorized:false".'
- );
- expect(messages[2]).toBe(
- '"xpack.actions.proxyRejectUnauthorizedCertificates" is deprecated. Use "xpack.actions.ssl.proxyVerificationMode" instead, with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".'
- );
- });
- });
-});
diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts
index 1d5aa22ba07cf..d391911ff0fc3 100644
--- a/x-pack/plugins/actions/server/index.ts
+++ b/x-pack/plugins/actions/server/index.ts
@@ -4,10 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { get } from 'lodash';
import type { PublicMethodsOf } from '@kbn/utility-types';
import { PluginInitializerContext, PluginConfigDescriptor } from '@kbn/core/server';
-import { configSchema, ActionsConfig, CustomHostSettings } from './config';
+import { configSchema, ActionsConfig } from './config';
import { ActionsClient as ActionsClientClass } from './actions_client';
import { ActionsAuthorization as ActionsAuthorizationClass } from './authorization/actions_authorization';
@@ -51,103 +50,6 @@ export const config: PluginConfigDescriptor = {
exposeToBrowser: {
email: { domain_allowlist: true },
},
- deprecations: ({ renameFromRoot, unused }) => [
- renameFromRoot('xpack.actions.whitelistedHosts', 'xpack.actions.allowedHosts', {
- level: 'warning',
- }),
- (settings, fromPath, addDeprecation) => {
- const actions = get(settings, fromPath);
- const customHostSettings = actions?.customHostSettings ?? [];
- if (
- customHostSettings.find(
- (customHostSchema: CustomHostSettings) =>
- Object.hasOwn(customHostSchema, 'ssl') &&
- Object.hasOwn(customHostSchema.ssl ?? {}, 'rejectUnauthorized')
- )
- ) {
- addDeprecation({
- level: 'warning',
- configPath: 'xpack.actions.customHostSettings.ssl.rejectUnauthorized',
- message:
- `"xpack.actions.customHostSettings[].ssl.rejectUnauthorized" is deprecated.` +
- `Use "xpack.actions.customHostSettings[].ssl.verificationMode" instead, ` +
- `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
- `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
- correctiveActions: {
- manualSteps: [
- `Remove "xpack.actions.customHostSettings[].ssl.rejectUnauthorized" from your kibana configs.`,
- `Use "xpack.actions.customHostSettings[].ssl.verificationMode" ` +
- `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
- `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
- ],
- },
- });
- return {
- unset: [
- {
- path: `xpack.actions.customHostSettings.ssl.rejectUnauthorized`,
- },
- ],
- };
- }
- },
- (settings, fromPath, addDeprecation) => {
- const actions = get(settings, fromPath);
- if (Object.hasOwn(actions ?? {}, 'rejectUnauthorized')) {
- addDeprecation({
- level: 'warning',
- configPath: `${fromPath}.rejectUnauthorized`,
- message:
- `"xpack.actions.rejectUnauthorized" is deprecated. Use "xpack.actions.ssl.verificationMode" instead, ` +
- `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
- `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
- correctiveActions: {
- manualSteps: [
- `Remove "xpack.actions.rejectUnauthorized" from your kibana configs.`,
- `Use "xpack.actions.ssl.verificationMode" ` +
- `with the setting "verificationMode:full" eql to "rejectUnauthorized:true", ` +
- `and "verificationMode:none" eql to "rejectUnauthorized:false".`,
- ],
- },
- });
- return {
- unset: [
- {
- path: `xpack.actions.rejectUnauthorized`,
- },
- ],
- };
- }
- },
- (settings, fromPath, addDeprecation) => {
- const actions = get(settings, fromPath);
- if (Object.hasOwn(actions ?? {}, 'proxyRejectUnauthorizedCertificates')) {
- addDeprecation({
- level: 'warning',
- configPath: `${fromPath}.proxyRejectUnauthorizedCertificates`,
- message:
- `"xpack.actions.proxyRejectUnauthorizedCertificates" is deprecated. Use "xpack.actions.ssl.proxyVerificationMode" instead, ` +
- `with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",` +
- `and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".`,
- correctiveActions: {
- manualSteps: [
- `Remove "xpack.actions.proxyRejectUnauthorizedCertificates" from your kibana configs.`,
- `Use "xpack.actions.ssl.proxyVerificationMode" ` +
- `with the setting "proxyVerificationMode:full" eql to "rejectUnauthorized:true",` +
- `and "proxyVerificationMode:none" eql to "rejectUnauthorized:false".`,
- ],
- },
- });
- return {
- unset: [
- {
- path: `xpack.actions.proxyRejectUnauthorizedCertificates`,
- },
- ],
- };
- }
- },
- ],
};
export { urlAllowListValidator } from './sub_action_framework/helpers';
diff --git a/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts b/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts
index 3a4101bb9f152..a0454cb2bbc18 100644
--- a/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts
+++ b/x-pack/plugins/actions/server/integration_tests/axios_utils_connection.test.ts
@@ -461,7 +461,6 @@ async function rejectUnauthorizedTargetProxyTest(opts: RunTestOptions) {
await runWithSetup(opts, async (target, proxyInstance, axiosDefaults) => {
const acu = getACUfromConfig({
proxyUrl: proxyInstance.url,
- rejectUnauthorized: false,
customHostSettings: [{ url: target.url, ssl: { verificationMode: 'none' } }],
});
@@ -676,14 +675,12 @@ const BaseActionsConfig: ActionsConfig = {
preconfigured: {},
proxyUrl: undefined,
proxyHeaders: undefined,
- proxyRejectUnauthorizedCertificates: true,
ssl: {
proxyVerificationMode: 'full',
verificationMode: 'full',
},
proxyBypassHosts: undefined,
proxyOnlyHosts: undefined,
- rejectUnauthorized: true,
maxResponseContentLength: ByteSizeValue.parse('1mb'),
responseTimeout: momentDuration(1000 * 30),
customHostSettings: undefined,
diff --git a/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts b/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts
index 1c1d411111253..97a917d6b6893 100644
--- a/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts
+++ b/x-pack/plugins/actions/server/integration_tests/axios_utils_proxy.test.ts
@@ -366,7 +366,6 @@ async function rejectUnauthorizedTargetProxyTest(opts: RunTestOptions) {
await runWithSetup(opts, async (target, proxyInstance, axiosDefaults) => {
const acu = getACUfromConfig({
proxyUrl: proxyInstance.url,
- rejectUnauthorized: false,
customHostSettings: [{ url: target.url, ssl: { verificationMode: 'none' } }],
});
@@ -582,14 +581,12 @@ const BaseActionsConfig: ActionsConfig = {
preconfigured: {},
proxyUrl: undefined,
proxyHeaders: undefined,
- proxyRejectUnauthorizedCertificates: true,
ssl: {
proxyVerificationMode: 'full',
verificationMode: 'full',
},
proxyBypassHosts: undefined,
proxyOnlyHosts: undefined,
- rejectUnauthorized: true,
maxResponseContentLength: ByteSizeValue.parse('1mb'),
responseTimeout: momentDuration(1000 * 30),
customHostSettings: undefined,
diff --git a/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts
index 0a9d9c6df31e7..f9173f5e007d0 100644
--- a/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts
+++ b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts
@@ -74,8 +74,6 @@ describe('custom_host_settings', () => {
enabledActionTypes: [],
preconfiguredAlertHistoryEsIndex: false,
preconfigured: {},
- proxyRejectUnauthorizedCertificates: true,
- rejectUnauthorized: true,
maxResponseContentLength: new ByteSizeValue(1000000),
responseTimeout: moment.duration(60000),
enableFooterInEmail: true,
@@ -119,14 +117,12 @@ describe('custom_host_settings', () => {
url: 'https://elastic.co:443',
ssl: {
certificateAuthoritiesData: 'xyz',
- rejectUnauthorized: false,
},
},
{
url: 'smtp://mail.elastic.com:25',
ssl: {
certificateAuthoritiesData: 'abc',
- rejectUnauthorized: true,
},
smtp: {
ignoreTLS: true,
@@ -473,15 +469,9 @@ describe('custom_host_settings', () => {
customHostSettings: [
{
url: 'https://almost.purrfect.com/',
- ssl: {
- rejectUnauthorized: true,
- },
},
{
url: 'https://almost.purrfect.com:443',
- ssl: {
- rejectUnauthorized: false,
- },
},
],
};
@@ -491,9 +481,6 @@ describe('custom_host_settings', () => {
customHostSettings: [
{
url: 'https://almost.purrfect.com:443',
- ssl: {
- rejectUnauthorized: true,
- },
},
],
};
diff --git a/x-pack/plugins/actions/server/lib/get_custom_agents.ts b/x-pack/plugins/actions/server/lib/get_custom_agents.ts
index 26b1495902eb1..c433bec18a54b 100644
--- a/x-pack/plugins/actions/server/lib/get_custom_agents.ts
+++ b/x-pack/plugins/actions/server/lib/get_custom_agents.ts
@@ -59,10 +59,7 @@ export function getCustomAgents(
agentOptions.ca = sslSettings.certificateAuthoritiesData;
}
- const sslSettingsFromConfig = getSSLSettingsFromConfig(
- sslSettings.verificationMode,
- sslSettings.rejectUnauthorized
- );
+ const sslSettingsFromConfig = getSSLSettingsFromConfig(sslSettings.verificationMode);
// see: src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts
// This is where the global rejectUnauthorized is overridden by a custom host
const customHostNodeSSLOptions = getNodeSSLOptions(
diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts
index 4ff87aa0459ef..f5de52810b162 100644
--- a/x-pack/plugins/actions/server/plugin.test.ts
+++ b/x-pack/plugins/actions/server/plugin.test.ts
@@ -50,10 +50,8 @@ function getConfig(overrides = {}) {
secrets: {},
},
},
- proxyRejectUnauthorizedCertificates: true,
proxyBypassHosts: undefined,
proxyOnlyHosts: undefined,
- rejectUnauthorized: true,
maxResponseContentLength: new ByteSizeValue(1000000),
responseTimeout: moment.duration('60s'),
enableFooterInEmail: true,
@@ -80,8 +78,6 @@ describe('Actions Plugin', () => {
allowedHosts: ['*'],
preconfiguredAlertHistoryEsIndex: false,
preconfigured: {},
- proxyRejectUnauthorizedCertificates: true,
- rejectUnauthorized: true,
maxResponseContentLength: new ByteSizeValue(1000000),
responseTimeout: moment.duration(60000),
enableFooterInEmail: true,
@@ -587,8 +583,6 @@ describe('Actions Plugin', () => {
secrets: {},
},
},
- proxyRejectUnauthorizedCertificates: true,
- rejectUnauthorized: true,
maxResponseContentLength: new ByteSizeValue(1000000),
responseTimeout: moment.duration(60000),
enableFooterInEmail: true,
diff --git a/x-pack/plugins/actions/tsconfig.json b/x-pack/plugins/actions/tsconfig.json
index 384aba6a6b014..8a3c56a472064 100644
--- a/x-pack/plugins/actions/tsconfig.json
+++ b/x-pack/plugins/actions/tsconfig.json
@@ -24,7 +24,6 @@
"@kbn/i18n",
"@kbn/utility-types",
"@kbn/config-schema",
- "@kbn/config",
"@kbn/core-saved-objects-server",
"@kbn/es-query",
"@kbn/apm-utils",
diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx
index b1c0e3d89f35a..f967fffd45647 100644
--- a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx
+++ b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx
@@ -638,7 +638,7 @@ export const FieldsControls: FC> = ({
}) => {
const { splitFieldsOptions, combinedQuery } = useChangePointDetectionContext();
const { dataView } = useDataSource();
- const { data, uiSettings, fieldFormats, charts, fieldStats } = useAiopsAppContext();
+ const { data, uiSettings, fieldFormats, charts, fieldStats, theme } = useAiopsAppContext();
const timefilter = useTimefilter();
// required in order to trigger state updates
useTimeRangeUpdates();
@@ -677,6 +677,7 @@ export const FieldsControls: FC> = ({
}
: undefined
}
+ theme={theme}
>
diff --git a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx
index 23caf21c39ee3..4dbf021e3b10b 100644
--- a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx
+++ b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx
@@ -15,6 +15,7 @@ import type {
import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state';
import { DocumentCountChartRedux } from '@kbn/aiops-components';
+import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants';
import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context';
@@ -37,17 +38,29 @@ export const DocumentCountContent: FC = ({
barHighlightColorOverride,
...docCountChartProps
}) => {
- const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext();
+ const { data, uiSettings, fieldFormats, charts, embeddingOrigin } = useAiopsAppContext();
const { documentStats } = useAppSelector((s) => s.logRateAnalysis);
const { sampleProbability, totalCount, documentCountStats } = documentStats;
if (documentCountStats === undefined) {
- return totalCount !== undefined ? (
+ return totalCount !== undefined && embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD ? (
) : null;
}
+ if (embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD) {
+ return (
+
+ );
+ }
+
return (
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx
index 7bf43037f45c0..2821b59353b52 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx
@@ -7,7 +7,7 @@
import { isEqual } from 'lodash';
import React, { useCallback, useEffect, useMemo, useRef, type FC } from 'react';
-import { EuiButton, EuiEmptyPrompt, EuiHorizontalRule, EuiPanel } from '@elastic/eui';
+import { EuiButton, EuiEmptyPrompt, EuiSpacer, EuiPanel } from '@elastic/eui';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { BarStyleAccessor } from '@elastic/charts/dist/chart_types/xy_chart/utils/specs';
@@ -28,13 +28,16 @@ import {
setInitialAnalysisStart,
useAppDispatch,
useAppSelector,
+ setGroupResults,
} from '@kbn/aiops-log-rate-analysis/state';
+import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants';
import { DocumentCountContent } from '../../document_count_content/document_count_content';
import {
LogRateAnalysisResults,
type LogRateAnalysisResultsData,
} from '../log_rate_analysis_results';
+import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context';
export const DEFAULT_SEARCH_QUERY: estypes.QueryDslQueryContainer = { match_all: {} };
const DEFAULT_SEARCH_BAR_QUERY: estypes.QueryDslQueryContainer = {
@@ -69,9 +72,11 @@ export const LogRateAnalysisContent: FC = ({
onAnalysisCompleted,
onWindowParametersChange,
}) => {
+ const { embeddingOrigin } = useAiopsAppContext();
+
const dispatch = useAppDispatch();
- const isRunning = useAppSelector((s) => s.logRateAnalysisStream.isRunning);
+ const isRunning = useAppSelector((s) => s.stream.isRunning);
const significantItems = useAppSelector((s) => s.logRateAnalysisResults.significantItems);
const significantItemsGroups = useAppSelector(
(s) => s.logRateAnalysisResults.significantItemsGroups
@@ -116,6 +121,7 @@ export const LogRateAnalysisContent: FC = ({
const { documentCountStats } = documentStats;
function clearSelectionHandler() {
+ dispatch(setGroupResults(false));
dispatch(clearSelection());
dispatch(clearAllRowState());
}
@@ -200,7 +206,11 @@ export const LogRateAnalysisContent: FC = ({
const changePointType = documentCountStats?.changePoint?.type;
return (
-
+
{showDocumentCountContent && (
= ({
barStyleAccessor={barStyleAccessor}
/>
)}
-
+
{showLogRateAnalysisResults && (
= ({
+ timeRange,
+}) => {
+ const { uiSettings } = useAiopsAppContext();
+ const { dataView } = useDataSource();
+ const { filters, query } = useFilterQueryUpdates();
+ const appState = getDefaultLogRateAnalysisAppState({
+ searchQuery: buildEsQuery(
+ dataView,
+ query ?? [],
+ filters ?? [],
+ uiSettings ? getEsQueryConfig(uiSettings) : undefined
+ ),
+ filters,
+ });
+ const { searchQuery } = useSearch({ dataView, savedSearch: null }, appState, true);
+
+ const timeRangeParsed = useMemo(() => {
+ if (timeRange) {
+ const min = datemath.parse(timeRange.from);
+ const max = datemath.parse(timeRange.to);
+ if (min && max) {
+ return { min, max };
+ }
+ }
+ }, [timeRange]);
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx
new file mode 100644
index 0000000000000..0aba8e2d763d4
--- /dev/null
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_options.tsx
@@ -0,0 +1,190 @@
+/*
+ * 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.
+ */
+
+import type { FC } from 'react';
+import React from 'react';
+
+import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+import {
+ clearAllRowState,
+ setGroupResults,
+ useAppDispatch,
+ useAppSelector,
+} from '@kbn/aiops-log-rate-analysis/state';
+import {
+ commonColumns,
+ significantItemColumns,
+ setSkippedColumns,
+ type LogRateAnalysisResultsTableColumnName,
+} from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice';
+import { setCurrentFieldFilterSkippedItems } from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_field_candidates_slice';
+
+import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover';
+import { ItemFilterPopover as ColumnFilterPopover } from './item_filter_popover';
+
+const groupResultsMessage = i18n.translate(
+ 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResults',
+ {
+ defaultMessage: 'Smart grouping',
+ }
+);
+const fieldFilterHelpText = i18n.translate('xpack.aiops.logRateAnalysis.page.fieldFilterHelpText', {
+ defaultMessage:
+ 'Deselect non-relevant fields to remove them from the analysis and click the Apply button to rerun the analysis. Use the search bar to filter the list, then select/deselect multiple fields with the actions below.',
+});
+const columnsFilterHelpText = i18n.translate(
+ 'xpack.aiops.logRateAnalysis.page.columnsFilterHelpText',
+ {
+ defaultMessage: 'Configure visible columns.',
+ }
+);
+const disabledFieldFilterApplyButtonTooltipContent = i18n.translate(
+ 'xpack.aiops.analysis.fieldSelectorNotEnoughFieldsSelected',
+ {
+ defaultMessage: 'Grouping requires at least 2 fields to be selected.',
+ }
+);
+const disabledColumnFilterApplyButtonTooltipContent = i18n.translate(
+ 'xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected',
+ {
+ defaultMessage: 'At least one column must be selected.',
+ }
+);
+const columnSearchAriaLabel = i18n.translate('xpack.aiops.analysis.columnSelectorAriaLabel', {
+ defaultMessage: 'Filter columns',
+});
+const columnsButton = i18n.translate('xpack.aiops.logRateAnalysis.page.columnsFilterButtonLabel', {
+ defaultMessage: 'Columns',
+});
+const fieldsButton = i18n.translate('xpack.aiops.analysis.fieldsButtonLabel', {
+ defaultMessage: 'Fields',
+});
+const groupResultsOffMessage = i18n.translate(
+ 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOff',
+ {
+ defaultMessage: 'Off',
+ }
+);
+const groupResultsOnMessage = i18n.translate(
+ 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOn',
+ {
+ defaultMessage: 'On',
+ }
+);
+const resultsGroupedOffId = 'aiopsLogRateAnalysisGroupingOff';
+const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn';
+
+export interface LogRateAnalysisOptionsProps {
+ foundGroups: boolean;
+ growFirstItem?: boolean;
+}
+
+export const LogRateAnalysisOptions: FC = ({
+ foundGroups,
+ growFirstItem = false,
+}) => {
+ const dispatch = useAppDispatch();
+
+ const { groupResults } = useAppSelector((s) => s.logRateAnalysis);
+ const { isRunning } = useAppSelector((s) => s.stream);
+ const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates);
+ const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable);
+ const { fieldFilterUniqueItems, initialFieldFilterSkippedItems } = fieldCandidates;
+ const fieldFilterButtonDisabled =
+ isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0;
+ const toggleIdSelected = groupResults ? resultsGroupedOnId : resultsGroupedOffId;
+
+ const onGroupResultsToggle = (optionId: string) => {
+ dispatch(setGroupResults(optionId === resultsGroupedOnId));
+ // When toggling the group switch, clear all row selections
+ dispatch(clearAllRowState());
+ };
+
+ const onVisibleColumnsChange = (columns: LogRateAnalysisResultsTableColumnName[]) => {
+ dispatch(setSkippedColumns(columns));
+ };
+
+ const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => {
+ dispatch(setCurrentFieldFilterSkippedItems(skippedFieldsUpdate));
+ };
+
+ // Disable the grouping switch toggle only if no groups were found,
+ // the toggle wasn't enabled already and no fields were selected to be skipped.
+ const disabledGroupResultsSwitch = !foundGroups && !groupResults;
+
+ const toggleButtons = [
+ {
+ id: resultsGroupedOffId,
+ label: groupResultsOffMessage,
+ 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOff',
+ },
+ {
+ id: resultsGroupedOnId,
+ label: groupResultsOnMessage,
+ 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOn',
+ },
+ ];
+
+ return (
+ <>
+
+
+
+ {groupResultsMessage}
+
+
+
+
+
+
+
+
+
+
+ void}
+ />
+
+ >
+ );
+};
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx
index b97019c4b4d29..1eb4f8fd0af0d 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx
@@ -11,12 +11,13 @@ import { isEqual } from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
+ EuiButtonIcon,
EuiButton,
- EuiButtonGroup,
EuiCallOut,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
+ EuiToolTip,
EuiSpacer,
EuiText,
} from '@elastic/eui';
@@ -26,6 +27,7 @@ import { ProgressControls } from '@kbn/aiops-components';
import { cancelStream, startStream } from '@kbn/ml-response-stream/client';
import {
clearAllRowState,
+ setGroupResults,
useAppDispatch,
useAppSelector,
} from '@kbn/aiops-log-rate-analysis/state';
@@ -37,8 +39,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils';
-import { useStorage } from '@kbn/ml-local-storage';
-import { AIOPS_ANALYSIS_RUN_ORIGIN } from '@kbn/aiops-common/constants';
+import { AIOPS_ANALYSIS_RUN_ORIGIN, AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants';
import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-log-rate-analysis/api/schema';
import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-log-rate-analysis/api/schema_v3';
import {
@@ -50,15 +51,6 @@ import { fetchFieldCandidates } from '@kbn/aiops-log-rate-analysis/state/log_rat
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { useDataSource } from '../../hooks/use_data_source';
-import {
- commonColumns,
- significantItemColumns,
-} from '../log_rate_analysis_results_table/use_columns';
-import {
- AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS,
- type AiOpsKey,
- type AiOpsStorageMapped,
-} from '../../types/storage';
import {
getGroupTableItems,
@@ -66,68 +58,15 @@ import {
LogRateAnalysisResultsGroupsTable,
} from '../log_rate_analysis_results_table';
-import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover';
-import { ItemFilterPopover as ColumnFilterPopover } from './item_filter_popover';
import { LogRateAnalysisInfoPopover } from './log_rate_analysis_info_popover';
-import type { ColumnNames } from '../log_rate_analysis_results_table';
+import { LogRateAnalysisOptions } from './log_rate_analysis_options';
-const groupResultsMessage = i18n.translate(
- 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResults',
- {
- defaultMessage: 'Smart grouping',
- }
-);
const groupResultsHelpMessage = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsHelpMessage',
{
defaultMessage: 'Items which are unique to a group are marked by an asterisk (*).',
}
);
-const groupResultsOffMessage = i18n.translate(
- 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOff',
- {
- defaultMessage: 'Off',
- }
-);
-const groupResultsOnMessage = i18n.translate(
- 'xpack.aiops.logRateAnalysis.resultsTable.groupedSwitchLabel.groupResultsOn',
- {
- defaultMessage: 'On',
- }
-);
-const resultsGroupedOffId = 'aiopsLogRateAnalysisGroupingOff';
-const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn';
-const fieldFilterHelpText = i18n.translate('xpack.aiops.logRateAnalysis.page.fieldFilterHelpText', {
- defaultMessage:
- 'Deselect non-relevant fields to remove them from the analysis and click the Apply button to rerun the analysis. Use the search bar to filter the list, then select/deselect multiple fields with the actions below.',
-});
-const columnsFilterHelpText = i18n.translate(
- 'xpack.aiops.logRateAnalysis.page.columnsFilterHelpText',
- {
- defaultMessage: 'Configure visible columns.',
- }
-);
-const disabledFieldFilterApplyButtonTooltipContent = i18n.translate(
- 'xpack.aiops.analysis.fieldSelectorNotEnoughFieldsSelected',
- {
- defaultMessage: 'Grouping requires at least 2 fields to be selected.',
- }
-);
-const disabledColumnFilterApplyButtonTooltipContent = i18n.translate(
- 'xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected',
- {
- defaultMessage: 'At least one column must be selected.',
- }
-);
-const columnSearchAriaLabel = i18n.translate('xpack.aiops.analysis.columnSelectorAriaLabel', {
- defaultMessage: 'Filter columns',
-});
-const columnsButton = i18n.translate('xpack.aiops.logRateAnalysis.page.columnsFilterButtonLabel', {
- defaultMessage: 'Columns',
-});
-const fieldsButton = i18n.translate('xpack.aiops.analysis.fieldsButtonLabel', {
- defaultMessage: 'Fields',
-});
/**
* Interface for log rate analysis results data.
@@ -173,72 +112,51 @@ export const LogRateAnalysisResults: FC = ({
documentStats: { sampleProbability },
stickyHistogram,
isBrushCleared,
+ groupResults,
} = useAppSelector((s) => s.logRateAnalysis);
- const { isRunning, errors: streamErrors } = useAppSelector((s) => s.logRateAnalysisStream);
+ const { isRunning, errors: streamErrors } = useAppSelector((s) => s.stream);
const data = useAppSelector((s) => s.logRateAnalysisResults);
const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates);
+ const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable);
const { currentAnalysisWindowParameters } = data;
// Store the performance metric's start time using a ref
// to be able to track it across rerenders.
const analysisStartTime = useRef(window.performance.now());
const abortCtrl = useRef(new AbortController());
+ const previousSearchQuery = useRef(searchQuery);
- const [groupResults, setGroupResults] = useState(false);
const [overrides, setOverrides] = useState(
undefined
);
const [shouldStart, setShouldStart] = useState(false);
- const [toggleIdSelected, setToggleIdSelected] = useState(resultsGroupedOffId);
- const [skippedColumns, setSkippedColumns] = useStorage<
- AiOpsKey,
- AiOpsStorageMapped
- >(AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS, ['p-value', 'Baseline rate', 'Deviation rate']);
- // null is used as the uninitialized state to identify the first load.
- const [skippedFields, setSkippedFields] = useState(null);
-
- const onGroupResultsToggle = (optionId: string) => {
- setToggleIdSelected(optionId);
- setGroupResults(optionId === resultsGroupedOnId);
-
- // When toggling the group switch, clear all row selections
- dispatch(clearAllRowState());
+ const [embeddableOptionsVisible, setEmbeddableOptionsVisible] = useState(false);
+
+ const onEmbeddableOptionsClickHandler = () => {
+ setEmbeddableOptionsVisible((s) => !s);
};
- const {
- fieldFilterUniqueItems,
- fieldFilterSkippedItems,
- keywordFieldCandidates,
- textFieldCandidates,
- } = fieldCandidates;
- const fieldFilterButtonDisabled =
- isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0;
-
- // Set skipped fields only on first load, otherwise we'd overwrite the user's selection.
+ const { currentFieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates } =
+ fieldCandidates;
+
useEffect(() => {
- if (skippedFields === null && fieldFilterSkippedItems.length > 0)
- setSkippedFields(fieldFilterSkippedItems);
- }, [fieldFilterSkippedItems, skippedFields]);
+ if (currentFieldFilterSkippedItems === null) return;
- const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => {
dispatch(resetResults());
- setSkippedFields(skippedFieldsUpdate);
setOverrides({
loaded: 0,
remainingKeywordFieldCandidates: keywordFieldCandidates.filter(
- (d) => !skippedFieldsUpdate.includes(d)
+ (d) => !currentFieldFilterSkippedItems.includes(d)
),
remainingTextFieldCandidates: textFieldCandidates.filter(
- (d) => !skippedFieldsUpdate.includes(d)
+ (d) => !currentFieldFilterSkippedItems.includes(d)
),
regroupOnly: false,
});
startHandler(true, false);
- };
-
- const onVisibleColumnsChange = (columns: ColumnNames[]) => {
- setSkippedColumns(columns);
- };
+ // custom check to trigger on currentFieldFilterSkippedItems change
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [currentFieldFilterSkippedItems]);
function cancelHandler() {
abortCtrl.current.abort();
@@ -298,18 +216,20 @@ export const LogRateAnalysisResults: FC = ({
dispatch(resetResults());
setOverrides({
remainingKeywordFieldCandidates: keywordFieldCandidates.filter(
- (d) => skippedFields === null || !skippedFields.includes(d)
+ (d) =>
+ currentFieldFilterSkippedItems === null || !currentFieldFilterSkippedItems.includes(d)
),
remainingTextFieldCandidates: textFieldCandidates.filter(
- (d) => skippedFields === null || !skippedFields.includes(d)
+ (d) =>
+ currentFieldFilterSkippedItems === null || !currentFieldFilterSkippedItems.includes(d)
),
});
}
// Reset grouping to false and clear all row selections when restarting the analysis.
if (resetGroupButton) {
- setGroupResults(false);
- setToggleIdSelected(resultsGroupedOffId);
+ dispatch(setGroupResults(false));
+ // When toggling the group switch, clear all row selections
dispatch(clearAllRowState());
}
@@ -371,12 +291,13 @@ export const LogRateAnalysisResults: FC = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [shouldStart]);
+ // On mount, fetch field candidates first. Once they are populated,
+ // the actual analysis will be triggered.
useEffect(() => {
if (startParams) {
dispatch(fetchFieldCandidates(startParams));
dispatch(setCurrentAnalysisType(analysisType));
dispatch(setCurrentAnalysisWindowParameters(chartWindowParameters));
- dispatch(startStream(startParams));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -386,6 +307,19 @@ export const LogRateAnalysisResults: FC = ({
[data.significantItemsGroups]
);
+ const searchQueryUpdated = useMemo(() => {
+ let searchQueryChanged = false;
+ if (
+ !isRunning &&
+ previousSearchQuery.current !== undefined &&
+ !isEqual(previousSearchQuery.current, searchQuery)
+ ) {
+ searchQueryChanged = true;
+ }
+ previousSearchQuery.current = searchQuery;
+ return searchQueryChanged;
+ }, [searchQuery, isRunning]);
+
const shouldRerunAnalysis = useMemo(
() =>
currentAnalysisWindowParameters !== undefined &&
@@ -399,23 +333,6 @@ export const LogRateAnalysisResults: FC = ({
}, 0);
const foundGroups = groupTableItems.length > 0 && groupItemCount > 0;
- // Disable the grouping switch toggle only if no groups were found,
- // the toggle wasn't enabled already and no fields were selected to be skipped.
- const disabledGroupResultsSwitch = !foundGroups && !groupResults;
-
- const toggleButtons = [
- {
- id: resultsGroupedOffId,
- label: groupResultsOffMessage,
- 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOff',
- },
- {
- id: resultsGroupedOnId,
- label: groupResultsOnMessage,
- 'data-test-subj': 'aiopsLogRateAnalysisGroupSwitchOn',
- },
- ];
-
return (
= ({
onRefresh={() => startHandler(false)}
onCancel={cancelHandler}
onReset={onReset}
- shouldRerunAnalysis={shouldRerunAnalysis}
+ shouldRerunAnalysis={shouldRerunAnalysis || searchQueryUpdated}
analysisInfo={}
>
-
-
-
- {groupResultsMessage}
-
+ <>
+ {embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && (
+
+ )}
+ {embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && (
-
+
+
+
-
-
-
-
-
-
- void}
- />
-
+ )}
+ >
+ {embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && embeddableOptionsVisible && (
+ <>
+
+
+
+
+ >
+ )}
+
{errors.length > 0 ? (
<>
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts
index 6813e71704918..c5112723e2784 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/index.ts
@@ -8,4 +8,3 @@
export { getGroupTableItems } from './get_group_table_items';
export { LogRateAnalysisResultsTable } from './log_rate_analysis_results_table';
export { LogRateAnalysisResultsGroupsTable } from './log_rate_analysis_results_table_groups';
-export type { ColumnNames } from './use_columns';
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx
index 83be306e93f50..e9072c2929f14 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx
@@ -83,13 +83,11 @@ export const LogRateAnalysisResultsTable: FC
=
}, [allSignificantItems, groupFilter]);
const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback);
- const pinnedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.pinnedGroup);
- const selectedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.selectedGroup);
- const pinnedSignificantItem = useAppSelector(
- (s) => s.logRateAnalysisTableRow.pinnedSignificantItem
- );
+ const pinnedGroup = useAppSelector((s) => s.logRateAnalysisTable.pinnedGroup);
+ const selectedGroup = useAppSelector((s) => s.logRateAnalysisTable.selectedGroup);
+ const pinnedSignificantItem = useAppSelector((s) => s.logRateAnalysisTable.pinnedSignificantItem);
const selectedSignificantItem = useAppSelector(
- (s) => s.logRateAnalysisTableRow.selectedSignificantItem
+ (s) => s.logRateAnalysisTable.selectedSignificantItem
);
const dispatch = useAppDispatch();
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx
index d69a0fec7200f..6bd0a5e4ce213 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx
@@ -91,8 +91,8 @@ export const LogRateAnalysisResultsGroupsTable: FC s.logRateAnalysisTableRow.pinnedGroup);
- const selectedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.selectedGroup);
+ const pinnedGroup = useAppSelector((s) => s.logRateAnalysisTable.pinnedGroup);
+ const selectedGroup = useAppSelector((s) => s.logRateAnalysisTable.selectedGroup);
const dispatch = useAppDispatch();
const isMounted = useMountedState();
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx
index f3b8195767101..c5b7a83e33641 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx
@@ -21,6 +21,11 @@ import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils';
import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query';
import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats';
import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state';
+import {
+ commonColumns,
+ significantItemColumns,
+ type LogRateAnalysisResultsTableColumnName,
+} from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice';
import {
getBaselineAndDeviationRates,
getLogRateChange,
@@ -40,55 +45,6 @@ const TRUNCATE_TEXT_LINES = 3;
const UNIQUE_COLUMN_WIDTH = '40px';
const NOT_AVAILABLE = '--';
-export const commonColumns = {
- ['Log rate']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.logRateColumnTitle', {
- defaultMessage: 'Log rate',
- }),
- ['Doc count']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.docCountColumnTitle', {
- defaultMessage: 'Doc count',
- }),
- ['p-value']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.pValueColumnTitle', {
- defaultMessage: 'p-value',
- }),
- ['Impact']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.impactColumnTitle', {
- defaultMessage: 'Impact',
- }),
- ['Baseline rate']: i18n.translate(
- 'xpack.aiops.logRateAnalysis.resultsTable.baselineRateColumnTitle',
- {
- defaultMessage: 'Baseline rate',
- }
- ),
- ['Deviation rate']: i18n.translate(
- 'xpack.aiops.logRateAnalysis.resultsTable.deviationRateColumnTitle',
- {
- defaultMessage: 'Deviation rate',
- }
- ),
- ['Log rate change']: i18n.translate(
- 'xpack.aiops.logRateAnalysis.resultsTable.logRateChangeColumnTitle',
- {
- defaultMessage: 'Log rate change',
- }
- ),
- ['Actions']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.actionsColumnTitle', {
- defaultMessage: 'Actions',
- }),
-};
-
-export const significantItemColumns = {
- ['Field name']: i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.fieldNameColumnTitle', {
- defaultMessage: 'Field name',
- }),
- ['Field value']: i18n.translate(
- 'xpack.aiops.logRateAnalysis.resultsTable.fieldValueColumnTitle',
- {
- defaultMessage: 'Field value',
- }
- ),
- ...commonColumns,
-} as const;
-
export const LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE = {
GROUPS: 'groups',
SIGNIFICANT_ITEMS: 'significantItems',
@@ -96,8 +52,6 @@ export const LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE = {
export type LogRateAnalysisResultsTableType =
(typeof LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE)[keyof typeof LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE];
-export type ColumnNames = keyof typeof significantItemColumns | 'unique';
-
const logRateHelpMessage = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTable.logRateColumnTooltip',
{
@@ -213,7 +167,7 @@ export const useColumns = (
const { earliest, latest } = useAppSelector((s) => s.logRateAnalysis);
const timeRangeMs = { from: earliest ?? 0, to: latest ?? 0 };
- const loading = useAppSelector((s) => s.logRateAnalysisStream.isRunning);
+ const loading = useAppSelector((s) => s.stream.isRunning);
const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback);
const {
documentStats: { documentCountStats },
@@ -271,7 +225,10 @@ export const useColumns = (
[currentAnalysisType, buckets]
);
- const columnsMap: Record> = useMemo(
+ const columnsMap: Record<
+ LogRateAnalysisResultsTableColumnName,
+ EuiBasicTableColumn
+ > = useMemo(
() => ({
['Field name']: {
'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnFieldName',
@@ -615,20 +572,21 @@ export const useColumns = (
);
const columns = useMemo(() => {
- const columnNamesToReturn: Partial> = isGroupsTable
- ? commonColumns
- : significantItemColumns;
+ const columnNamesToReturn: Partial> =
+ isGroupsTable ? commonColumns : significantItemColumns;
const columnsToReturn = [];
for (const columnName in columnNamesToReturn) {
if (
Object.hasOwn(columnNamesToReturn, columnName) === false ||
- skippedColumns.includes(columnNamesToReturn[columnName as ColumnNames] as string) ||
+ skippedColumns.includes(
+ columnNamesToReturn[columnName as LogRateAnalysisResultsTableColumnName] as string
+ ) ||
((columnName === 'p-value' || columnName === 'Impact') && zeroDocsFallback)
)
continue;
- columnsToReturn.push(columnsMap[columnName as ColumnNames]);
+ columnsToReturn.push(columnsMap[columnName as LogRateAnalysisResultsTableColumnName]);
}
if (isExpandedRow === true) {
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx
index ec4284d6452e5..765f1435a93ad 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx
@@ -28,8 +28,8 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction =>
const { application, share, data } = useAiopsAppContext();
const discoverLocator = useMemo(
- () => share.url.locators.get('DISCOVER_APP_LOCATOR'),
- [share.url.locators]
+ () => share?.url.locators.get('DISCOVER_APP_LOCATOR'),
+ [share?.url.locators]
);
const discoverUrlError = useMemo(() => {
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx
index dbac6fbe8c9f3..ec1f6774b6b46 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx
@@ -32,7 +32,7 @@ const viewInLogPatternAnalysisMessage = i18n.translate(
export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableItemAction => {
const { application, share, data } = useAiopsAppContext();
- const mlLocator = useMemo(() => share.url.locators.get('ML_APP_LOCATOR'), [share.url.locators]);
+ const mlLocator = useMemo(() => share?.url.locators.get('ML_APP_LOCATOR'), [share?.url.locators]);
const generateLogPatternAnalysisUrl = async (
groupTableItem: GroupTableItem | SignificantItem
diff --git a/x-pack/plugins/aiops/public/components/time_field_warning.tsx b/x-pack/plugins/aiops/public/components/time_field_warning.tsx
new file mode 100644
index 0000000000000..beb64917af538
--- /dev/null
+++ b/x-pack/plugins/aiops/public/components/time_field_warning.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+import { EuiSpacer, EuiCallOut } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+
+export const TimeFieldWarning = () => {
+ return (
+ <>
+
+
+ {i18n.translate('xpack.aiops.embeddableMenu.timeFieldWarning.title.description', {
+ defaultMessage: 'The analysis can only be run on data views with a time field.',
+ })}
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx
index 08cf6ffd7dcb1..e69511fe45f92 100644
--- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx
+++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx
@@ -18,16 +18,23 @@ import {
EuiHorizontalRule,
EuiTitle,
} from '@elastic/eui';
+import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
+import { UI_SETTINGS } from '@kbn/data-plugin/common';
+import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
+import { FieldStatsFlyoutProvider } from '@kbn/ml-field-stats-flyout';
+import { useTimefilter } from '@kbn/ml-date-picker';
import { pick } from 'lodash';
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import {
+ ChangePointDetectionContextProvider,
ChangePointDetectionControlsContextProvider,
+ useChangePointDetectionContext,
useChangePointDetectionControlsContext,
} from '../../components/change_point_detection/change_point_detection_context';
import { DEFAULT_AGG_FUNCTION } from '../../components/change_point_detection/constants';
@@ -38,7 +45,8 @@ import { PartitionsSelector } from '../../components/change_point_detection/part
import { SplitFieldSelector } from '../../components/change_point_detection/split_field_selector';
import { ViewTypeSelector } from '../../components/change_point_detection/view_type_selector';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
-import { DataSourceContextProvider } from '../../hooks/use_data_source';
+import { useDataSource, DataSourceContextProvider } from '../../hooks/use_data_source';
+import { FilterQueryContextProvider } from '../../hooks/use_filters_query';
import { DEFAULT_SERIES } from './const';
import type { ChangePointEmbeddableRuntimeState } from './types';
@@ -53,11 +61,18 @@ export const ChangePointChartInitializer: FC = ({
onCreate,
onCancel,
}) => {
+ const appContextValue = useAiopsAppContext();
const {
+ data: { dataViews },
unifiedSearch: {
ui: { IndexPatternSelect },
},
- } = useAiopsAppContext();
+ } = appContextValue;
+
+ const datePickerDeps: DatePickerDependencies = {
+ ...pick(appContextValue, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
+ uiSettingsKeys: UI_SETTINGS,
+ };
const [dataViewId, setDataViewId] = useState(initialInput?.dataViewId ?? '');
const [viewType, setViewType] = useState(initialInput?.viewType ?? 'charts');
@@ -135,15 +150,21 @@ export const ChangePointChartInitializer: FC = ({
}}
/>
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -190,7 +211,13 @@ export const FormControls: FC<{
onChange: (update: FormControlsProps) => void;
onValidationChange: (isValid: boolean) => void;
}> = ({ formInput, onChange, onValidationChange }) => {
+ const { charts, data, fieldFormats, theme, uiSettings } = useAiopsAppContext();
+ const { dataView } = useDataSource();
+ const { combinedQuery } = useChangePointDetectionContext();
const { metricFieldOptions, splitFieldsOptions } = useChangePointDetectionControlsContext();
+ const timefilter = useTimefilter();
+ const timefilterActiveBounds = timefilter.getActiveBounds();
+
const prevMetricFieldOptions = usePrevious(metricFieldOptions);
const enableSearch = useMemo(() => {
@@ -238,10 +265,33 @@ export const FormControls: FC<{
[formInput, onChange]
);
+ const fieldStatsServices: FieldStatsServices = useMemo(() => {
+ return {
+ uiSettings,
+ dataViews: data.dataViews,
+ data,
+ fieldFormats,
+ charts,
+ };
+ }, [uiSettings, data, fieldFormats, charts]);
+
if (!isPopulatedObject(formInput)) return null;
return (
- <>
+
updateCallback({ maxSeriesToPlot: v })}
onValidationChange={(result) => onValidationChange(result === null)}
/>
- >
+
);
};
diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx
index 7cf39eb1cf4ae..2ce1a46780db1 100644
--- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx
+++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx
@@ -11,7 +11,6 @@ import {
} from '@kbn/aiops-change-point-detection/constants';
import type { Reference } from '@kbn/content-management-utils';
import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser';
-import { type DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common';
import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
@@ -38,32 +37,8 @@ import type {
ChangePointEmbeddableState,
} from './types';
-export interface EmbeddableChangePointChartStartServices {
- data: DataPublicPluginStart;
-}
-
export type EmbeddableChangePointChartType = typeof EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
-export const getDependencies = async (
- getStartServices: StartServicesAccessor
-) => {
- const [
- { http, uiSettings, notifications, ...startServices },
- { lens, data, usageCollection, fieldFormats },
- ] = await getStartServices();
-
- return {
- http,
- uiSettings,
- data,
- notifications,
- lens,
- usageCollection,
- fieldFormats,
- ...startServices,
- };
-};
-
export const getChangePointChartEmbeddableFactory = (
getStartServices: StartServicesAccessor
) => {
@@ -88,20 +63,6 @@ export const getChangePointChartEmbeddableFactory = (
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
const [coreStart, pluginStart] = await getStartServices();
- const { http, uiSettings, notifications, ...startServices } = coreStart;
- const { lens, data, usageCollection, fieldFormats } = pluginStart;
-
- const deps = {
- http,
- uiSettings,
- data,
- notifications,
- lens,
- usageCollection,
- fieldFormats,
- ...startServices,
- };
-
const {
api: timeRangeApi,
comparators: timeRangeComparators,
@@ -120,7 +81,7 @@ export const getChangePointChartEmbeddableFactory = (
const blockingError = new BehaviorSubject(undefined);
const dataViews$ = new BehaviorSubject([
- await deps.data.dataViews.get(state.dataViewId),
+ await pluginStart.data.dataViews.get(state.dataViewId),
]);
const api = buildApi(
diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts b/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts
index 4a39020a299c9..f86270bdaf19d 100644
--- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts
+++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/types.ts
@@ -15,14 +15,6 @@ import type {
SerializedTimeRange,
SerializedTitles,
} from '@kbn/presentation-publishing';
-import type { FC } from 'react';
-import type { SelectedChangePoint } from '../../components/change_point_detection/change_point_detection_context';
-
-export type ViewComponent = FC<{
- changePoints: SelectedChangePoint[];
- interval: string;
- onRenderComplete?: () => void;
-}>;
export interface ChangePointComponentApi {
viewType: PublishingSubject;
diff --git a/x-pack/plugins/aiops/public/embeddables/index.ts b/x-pack/plugins/aiops/public/embeddables/index.ts
index b7d9ad25951fb..dae1f0eb3eeec 100644
--- a/x-pack/plugins/aiops/public/embeddables/index.ts
+++ b/x-pack/plugins/aiops/public/embeddables/index.ts
@@ -9,6 +9,7 @@ import type { CoreSetup } from '@kbn/core-lifecycle-browser';
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants';
+import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants';
import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types';
export const registerEmbeddables = (
@@ -23,4 +24,8 @@ export const registerEmbeddables = (
const { getPatternAnalysisEmbeddableFactory } = await import('./pattern_analysis');
return getPatternAnalysisEmbeddableFactory(core.getStartServices);
});
+ embeddable.registerReactEmbeddableFactory(EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE, async () => {
+ const { getLogRateAnalysisEmbeddableFactory } = await import('./log_rate_analysis');
+ return getLogRateAnalysisEmbeddableFactory(core.getStartServices);
+ });
};
diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx
new file mode 100644
index 0000000000000..592ec32cef120
--- /dev/null
+++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/embeddable_log_rate_analysis_factory.tsx
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ */
+
+import {
+ EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE,
+ LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME,
+} from '@kbn/aiops-log-rate-analysis/constants';
+import type { Reference } from '@kbn/content-management-utils';
+import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser';
+import type { DataView } from '@kbn/data-views-plugin/common';
+import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common';
+import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
+import { i18n } from '@kbn/i18n';
+import {
+ apiHasExecutionContext,
+ fetch$,
+ initializeTimeRange,
+ initializeTitles,
+ useBatchedPublishingSubjects,
+} from '@kbn/presentation-publishing';
+
+import fastIsEqual from 'fast-deep-equal';
+import { cloneDeep } from 'lodash';
+import React, { useMemo } from 'react';
+import useObservable from 'react-use/lib/useObservable';
+import { BehaviorSubject, distinctUntilChanged, map, skipWhile } from 'rxjs';
+import { getLogRateAnalysisEmbeddableWrapperComponent } from '../../shared_components';
+import type { AiopsPluginStart, AiopsPluginStartDeps } from '../../types';
+import { initializeLogRateAnalysisControls } from './initialize_log_rate_analysis_analysis_controls';
+import type {
+ LogRateAnalysisEmbeddableApi,
+ LogRateAnalysisEmbeddableRuntimeState,
+ LogRateAnalysisEmbeddableState,
+} from './types';
+
+export const getLogRateAnalysisEmbeddableFactory = (
+ getStartServices: StartServicesAccessor
+) => {
+ const factory: ReactEmbeddableFactory<
+ LogRateAnalysisEmbeddableState,
+ LogRateAnalysisEmbeddableRuntimeState,
+ LogRateAnalysisEmbeddableApi
+ > = {
+ type: EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE,
+ deserializeState: (state) => {
+ const serializedState = cloneDeep(state.rawState);
+ // inject the reference
+ const dataViewIdRef = state.references?.find(
+ (ref) => ref.name === LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME
+ );
+ // if the serializedState already contains a dataViewId, we don't want to overwrite it. (Unsaved state can cause this)
+ if (dataViewIdRef && serializedState && !serializedState.dataViewId) {
+ serializedState.dataViewId = dataViewIdRef?.id;
+ }
+ return serializedState;
+ },
+ buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
+ const [coreStart, pluginStart] = await getStartServices();
+
+ const {
+ api: timeRangeApi,
+ comparators: timeRangeComparators,
+ serialize: serializeTimeRange,
+ } = initializeTimeRange(state);
+
+ const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
+
+ const {
+ logRateAnalysisControlsApi,
+ serializeLogRateAnalysisChartState,
+ logRateAnalysisControlsComparators,
+ } = initializeLogRateAnalysisControls(state);
+
+ const dataLoading = new BehaviorSubject(true);
+ const blockingError = new BehaviorSubject(undefined);
+
+ const dataViews$ = new BehaviorSubject([
+ await pluginStart.data.dataViews.get(
+ state.dataViewId ?? (await pluginStart.data.dataViews.getDefaultId())
+ ),
+ ]);
+
+ const api = buildApi(
+ {
+ ...timeRangeApi,
+ ...titlesApi,
+ ...logRateAnalysisControlsApi,
+ getTypeDisplayName: () =>
+ i18n.translate('xpack.aiops.logRateAnalysis.typeDisplayName', {
+ defaultMessage: 'log rate analysis',
+ }),
+ isEditingEnabled: () => true,
+ onEdit: async () => {
+ try {
+ const { resolveEmbeddableLogRateAnalysisUserInput } = await import(
+ './resolve_log_rate_analysis_config_input'
+ );
+
+ const result = await resolveEmbeddableLogRateAnalysisUserInput(
+ coreStart,
+ pluginStart,
+ parentApi,
+ uuid,
+ false,
+ logRateAnalysisControlsApi,
+ undefined,
+ serializeLogRateAnalysisChartState()
+ );
+
+ logRateAnalysisControlsApi.updateUserInput(result);
+ } catch (e) {
+ return Promise.reject();
+ }
+ },
+ dataLoading,
+ blockingError,
+ dataViews: dataViews$,
+ serializeState: () => {
+ const dataViewId = logRateAnalysisControlsApi.dataViewId.getValue();
+ const references: Reference[] = dataViewId
+ ? [
+ {
+ type: DATA_VIEW_SAVED_OBJECT_TYPE,
+ name: LOG_RATE_ANALYSIS_DATA_VIEW_REF_NAME,
+ id: dataViewId,
+ },
+ ]
+ : [];
+ return {
+ rawState: {
+ timeRange: undefined,
+ ...serializeTitles(),
+ ...serializeTimeRange(),
+ ...serializeLogRateAnalysisChartState(),
+ },
+ references,
+ };
+ },
+ },
+ {
+ ...timeRangeComparators,
+ ...titleComparators,
+ ...logRateAnalysisControlsComparators,
+ }
+ );
+
+ const LogRateAnalysisEmbeddableWrapper = getLogRateAnalysisEmbeddableWrapperComponent(
+ coreStart,
+ pluginStart
+ );
+
+ const onLoading = (v: boolean) => dataLoading.next(v);
+ const onRenderComplete = () => dataLoading.next(false);
+ const onError = (error: Error) => blockingError.next(error);
+
+ return {
+ api,
+ Component: () => {
+ if (!apiHasExecutionContext(parentApi)) {
+ throw new Error('Parent API does not have execution context');
+ }
+
+ const [dataViewId] = useBatchedPublishingSubjects(api.dataViewId);
+
+ const reload$ = useMemo(
+ () =>
+ fetch$(api).pipe(
+ skipWhile((fetchContext) => !fetchContext.isReload),
+ map((fetchContext) => Date.now())
+ ),
+ []
+ );
+
+ const timeRange$ = useMemo(
+ () =>
+ fetch$(api).pipe(
+ map((fetchContext) => fetchContext.timeRange),
+ distinctUntilChanged(fastIsEqual)
+ ),
+ []
+ );
+
+ const lastReloadRequestTime = useObservable(reload$, Date.now());
+ const timeRange = useObservable(timeRange$, undefined);
+
+ const embeddingOrigin = apiHasExecutionContext(parentApi)
+ ? parentApi.executionContext.type
+ : undefined;
+
+ return (
+
+ );
+ },
+ };
+ },
+ };
+
+ return factory;
+};
diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/index.ts b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/index.ts
new file mode 100644
index 0000000000000..2203d4c64bc8b
--- /dev/null
+++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 { getLogRateAnalysisEmbeddableFactory } from './embeddable_log_rate_analysis_factory';
diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts
new file mode 100644
index 0000000000000..9d8a49b8f0e9c
--- /dev/null
+++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/initialize_log_rate_analysis_analysis_controls.ts
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+import type { StateComparators } from '@kbn/presentation-publishing';
+import { BehaviorSubject } from 'rxjs';
+import type { LogRateAnalysisComponentApi, LogRateAnalysisEmbeddableState } from './types';
+
+type LogRateAnalysisEmbeddableCustomState = Omit<
+ LogRateAnalysisEmbeddableState,
+ 'timeRange' | 'title' | 'description' | 'hidePanelTitles'
+>;
+
+export const initializeLogRateAnalysisControls = (rawState: LogRateAnalysisEmbeddableState) => {
+ const dataViewId = new BehaviorSubject(rawState.dataViewId);
+
+ const updateUserInput = (update: LogRateAnalysisEmbeddableCustomState) => {
+ dataViewId.next(update.dataViewId);
+ };
+
+ const serializeLogRateAnalysisChartState = (): LogRateAnalysisEmbeddableCustomState => {
+ return {
+ dataViewId: dataViewId.getValue(),
+ };
+ };
+
+ const logRateAnalysisControlsComparators: StateComparators =
+ {
+ dataViewId: [dataViewId, (arg) => dataViewId.next(arg)],
+ };
+
+ return {
+ logRateAnalysisControlsApi: {
+ dataViewId,
+ updateUserInput,
+ } as unknown as LogRateAnalysisComponentApi,
+ serializeLogRateAnalysisChartState,
+ logRateAnalysisControlsComparators,
+ };
+};
diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx
new file mode 100644
index 0000000000000..bf53f07677739
--- /dev/null
+++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/log_rate_analysis_embeddable_initializer.tsx
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+import type { FC } from 'react';
+import React, { useEffect, useMemo, useState, useCallback } from 'react';
+import { pick } from 'lodash';
+import useMountedState from 'react-use/lib/useMountedState';
+
+import {
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiFlyoutBody,
+ EuiForm,
+ EuiFormRow,
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyoutFooter,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import type { IndexPatternSelectProps } from '@kbn/unified-search-plugin/public';
+import { euiThemeVars } from '@kbn/ui-theme';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { isPopulatedObject } from '@kbn/ml-is-populated-object';
+import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
+
+import { TimeFieldWarning } from '../../components/time_field_warning';
+
+import type { LogRateAnalysisEmbeddableRuntimeState } from './types';
+
+export interface LogRateAnalysisEmbeddableInitializerProps {
+ dataViews: DataViewsPublicPluginStart;
+ IndexPatternSelect: React.ComponentType;
+ initialInput?: Partial;
+ onCreate: (props: LogRateAnalysisEmbeddableRuntimeState) => void;
+ onCancel: () => void;
+ onPreview: (update: LogRateAnalysisEmbeddableRuntimeState) => Promise;
+ isNewPanel: boolean;
+}
+
+export const LogRateAnalysisEmbeddableInitializer: FC<
+ LogRateAnalysisEmbeddableInitializerProps
+> = ({
+ dataViews,
+ IndexPatternSelect,
+ initialInput,
+ onCreate,
+ onCancel,
+ onPreview,
+ isNewPanel,
+}) => {
+ const isMounted = useMountedState();
+
+ const [formInput, setFormInput] = useState(
+ pick(initialInput ?? {}, ['dataViewId']) as LogRateAnalysisEmbeddableRuntimeState
+ );
+
+ // State to track if the selected data view is time based, undefined is used
+ // to track that the check is in progress.
+ const [isDataViewTimeBased, setIsDataViewTimeBased] = useState();
+
+ const isFormValid = useMemo(
+ () =>
+ isPopulatedObject(formInput, ['dataViewId']) &&
+ formInput.dataViewId !== '' &&
+ isDataViewTimeBased === true,
+ [formInput, isDataViewTimeBased]
+ );
+
+ const updatedProps = useMemo(() => {
+ return {
+ ...formInput,
+ title: isPopulatedObject(formInput)
+ ? i18n.translate('xpack.aiops.embeddableLogRateAnalysis.attachmentTitle', {
+ defaultMessage: 'Log rate analysis',
+ })
+ : '',
+ };
+ }, [formInput]);
+
+ useEffect(
+ function previewChanges() {
+ if (isFormValid) {
+ onPreview(updatedProps);
+ }
+ },
+ [isFormValid, onPreview, updatedProps, isDataViewTimeBased]
+ );
+
+ const setDataViewId = useCallback(
+ (dataViewId: string | undefined) => {
+ setFormInput({
+ ...formInput,
+ dataViewId: dataViewId ?? '',
+ });
+ setIsDataViewTimeBased(undefined);
+ },
+ [formInput]
+ );
+
+ useEffect(
+ function checkIsDataViewTimeBased() {
+ setIsDataViewTimeBased(undefined);
+
+ const { dataViewId } = formInput;
+
+ if (!dataViewId) {
+ return;
+ }
+
+ dataViews
+ .get(dataViewId)
+ .then((dataView) => {
+ if (!isMounted()) {
+ return;
+ }
+ setIsDataViewTimeBased(dataView.isTimeBased());
+ })
+ .catch(() => {
+ setIsDataViewTimeBased(undefined);
+ });
+ },
+ [dataViews, formInput, isMounted]
+ );
+
+ return (
+ <>
+
+
+
+ {isNewPanel
+ ? i18n.translate('xpack.aiops.embeddableLogRateAnalysis.config.title.new', {
+ defaultMessage: 'Create log rate analysis',
+ })
+ : i18n.translate('xpack.aiops.embeddableLogRateAnalysis.config.title.edit', {
+ defaultMessage: 'Edit log rate analysis',
+ })}
+
+
+
+
+
+
+
+ <>
+ {
+ setDataViewId(newId ?? '');
+ }}
+ data-test-subj="aiopsLogRateAnalysisEmbeddableDataViewSelector"
+ />
+ {isDataViewTimeBased === false && (
+ <>
+
+
+ >
+ )}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx
new file mode 100644
index 0000000000000..a066b5bc722f2
--- /dev/null
+++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input.tsx
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+import type { CoreStart } from '@kbn/core/public';
+import { tracksOverlays } from '@kbn/presentation-containers';
+import { toMountPoint } from '@kbn/react-kibana-mount';
+import React from 'react';
+import type { AiopsPluginStartDeps } from '../../types';
+import { LogRateAnalysisEmbeddableInitializer } from './log_rate_analysis_embeddable_initializer';
+import type { LogRateAnalysisComponentApi, LogRateAnalysisEmbeddableState } from './types';
+
+export async function resolveEmbeddableLogRateAnalysisUserInput(
+ coreStart: CoreStart,
+ pluginStart: AiopsPluginStartDeps,
+ parentApi: unknown,
+ focusedPanelId: string,
+ isNewPanel: boolean,
+ logRateAnalysisControlsApi: LogRateAnalysisComponentApi,
+ deletePanel?: () => void,
+ initialState?: LogRateAnalysisEmbeddableState
+): Promise {
+ const { overlays } = coreStart;
+
+ const overlayTracker = tracksOverlays(parentApi) ? parentApi : undefined;
+
+ let hasChanged = false;
+ return new Promise(async (resolve, reject) => {
+ try {
+ const cancelChanges = () => {
+ if (isNewPanel && deletePanel) {
+ deletePanel();
+ } else if (hasChanged && logRateAnalysisControlsApi && initialState) {
+ // Reset to initialState in case user has changed the preview state
+ logRateAnalysisControlsApi.updateUserInput(initialState);
+ }
+
+ flyoutSession.close();
+ overlayTracker?.clearOverlays();
+ };
+
+ const update = async (nextUpdate: LogRateAnalysisEmbeddableState) => {
+ resolve(nextUpdate);
+ flyoutSession.close();
+ overlayTracker?.clearOverlays();
+ };
+
+ const preview = async (nextUpdate: LogRateAnalysisEmbeddableState) => {
+ if (logRateAnalysisControlsApi) {
+ logRateAnalysisControlsApi.updateUserInput(nextUpdate);
+ hasChanged = true;
+ }
+ };
+
+ const flyoutSession = overlays.openFlyout(
+ toMountPoint(
+ ,
+ coreStart
+ ),
+ {
+ ownFocus: true,
+ size: 's',
+ type: 'push',
+ paddingSize: 'm',
+ hideCloseButton: true,
+ 'data-test-subj': 'aiopsLogRateAnalysisEmbeddableInitializer',
+ 'aria-labelledby': 'logRateAnalysisConfig',
+ onClose: () => {
+ reject();
+ flyoutSession.close();
+ overlayTracker?.clearOverlays();
+ },
+ }
+ );
+
+ if (tracksOverlays(parentApi)) {
+ parentApi.openOverlay(flyoutSession, { focusedPanelId });
+ }
+ } catch (error) {
+ reject(error);
+ }
+ });
+}
diff --git a/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts
new file mode 100644
index 0000000000000..d2255e6dacb87
--- /dev/null
+++ b/x-pack/plugins/aiops/public/embeddables/log_rate_analysis/types.ts
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+import type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public';
+import type {
+ HasEditCapabilities,
+ PublishesDataViews,
+ PublishesTimeRange,
+ PublishingSubject,
+ SerializedTimeRange,
+ SerializedTitles,
+} from '@kbn/presentation-publishing';
+
+export interface LogRateAnalysisComponentApi {
+ dataViewId: PublishingSubject;
+ updateUserInput: (update: LogRateAnalysisEmbeddableState) => void;
+}
+
+export type LogRateAnalysisEmbeddableApi = DefaultEmbeddableApi &
+ HasEditCapabilities &
+ PublishesDataViews &
+ PublishesTimeRange &
+ LogRateAnalysisComponentApi;
+
+export interface LogRateAnalysisEmbeddableState extends SerializedTitles, SerializedTimeRange {
+ dataViewId: string;
+}
+
+export interface LogRateAnalysisEmbeddableInitialState
+ extends SerializedTitles,
+ SerializedTimeRange {
+ dataViewId?: string;
+}
+
+export type LogRateAnalysisEmbeddableRuntimeState = LogRateAnalysisEmbeddableState;
diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx
index e7b1d6da3be61..d84043ea5f637 100644
--- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx
+++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx
@@ -11,7 +11,6 @@ import {
} from '@kbn/aiops-log-pattern-analysis/constants';
import type { Reference } from '@kbn/content-management-utils';
import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser';
-import { type DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common';
import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
@@ -37,32 +36,6 @@ import type {
PatternAnalysisEmbeddableState,
} from './types';
-export interface EmbeddablePatternAnalysisStartServices {
- data: DataPublicPluginStart;
-}
-
-export type EmbeddablePatternAnalysisType = typeof EMBEDDABLE_PATTERN_ANALYSIS_TYPE;
-
-export const getDependencies = async (
- getStartServices: StartServicesAccessor
-) => {
- const [
- { http, uiSettings, notifications, ...startServices },
- { lens, data, usageCollection, fieldFormats },
- ] = await getStartServices();
-
- return {
- http,
- uiSettings,
- data,
- notifications,
- lens,
- usageCollection,
- fieldFormats,
- ...startServices,
- };
-};
-
export const getPatternAnalysisEmbeddableFactory = (
getStartServices: StartServicesAccessor
) => {
@@ -87,20 +60,6 @@ export const getPatternAnalysisEmbeddableFactory = (
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
const [coreStart, pluginStart] = await getStartServices();
- const { http, uiSettings, notifications, ...startServices } = coreStart;
- const { lens, data, usageCollection, fieldFormats } = pluginStart;
-
- const deps = {
- http,
- uiSettings,
- data,
- notifications,
- lens,
- usageCollection,
- fieldFormats,
- ...startServices,
- };
-
const {
api: timeRangeApi,
comparators: timeRangeComparators,
@@ -119,8 +78,8 @@ export const getPatternAnalysisEmbeddableFactory = (
const blockingError = new BehaviorSubject(undefined);
const dataViews$ = new BehaviorSubject([
- await deps.data.dataViews.get(
- state.dataViewId ?? (await deps.data.dataViews.getDefaultId())
+ await pluginStart.data.dataViews.get(
+ state.dataViewId ?? (await pluginStart.data.dataViews.getDefaultId())
),
]);
diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysys_component_wrapper.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_component_wrapper.tsx
similarity index 100%
rename from x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysys_component_wrapper.tsx
rename to x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_component_wrapper.tsx
diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx
index f44fff343fb50..ef185518638b8 100644
--- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx
+++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx
@@ -33,6 +33,7 @@ import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { DataSourceContextProvider } from '../../hooks/use_data_source';
import type { PatternAnalysisEmbeddableRuntimeState } from './types';
import { PatternAnalysisSettings } from '../../components/log_categorization/log_categorization_for_embeddable/embeddable_menu';
+import { TimeFieldWarning } from '../../components/time_field_warning';
import { RandomSampler } from '../../components/log_categorization/sampling_menu';
import {
DEFAULT_PROBABILITY,
@@ -62,6 +63,7 @@ export const PatternAnalysisEmbeddableInitializer: FC {
const {
+ data: { dataViews },
unifiedSearch: {
ui: { IndexPatternSelect },
},
@@ -166,7 +168,7 @@ export const PatternAnalysisEmbeddableInitializer: FC
-
+
{
>
);
};
-
-const TimeFieldWarning = () => {
- return (
- <>
-
-
- {i18n.translate(
- 'xpack.aiops.logCategorization.embeddableMenu.timeFieldWarning.title.description',
- {
- defaultMessage: 'Pattern analysis can only be run on data views with a time field.',
- }
- )}
-
-
-
- >
- );
-};
diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts
index f78934b9075f1..710e18823a2bb 100644
--- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts
+++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts
@@ -14,18 +14,12 @@ import type {
SerializedTimeRange,
SerializedTitles,
} from '@kbn/presentation-publishing';
-import type { FC } from 'react';
import type { MinimumTimeRangeOption } from '../../components/log_categorization/log_categorization_for_embeddable/minimum_time_range';
import type {
RandomSamplerOption,
RandomSamplerProbability,
} from '../../components/log_categorization/sampling_menu/random_sampler';
-export type ViewComponent = FC<{
- interval: string;
- onRenderComplete?: () => void;
-}>;
-
export interface PatternAnalysisComponentApi {
dataViewId: PublishingSubject;
fieldName: PublishingSubject;
diff --git a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts
index 03762a7ba70ba..c240ec90bc1df 100644
--- a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts
+++ b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { createContext, type FC, type PropsWithChildren, useContext } from 'react';
+import { createContext, type FC, useContext } from 'react';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
@@ -24,17 +24,12 @@ import type {
ThemeServiceStart,
} from '@kbn/core/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
-import { type EuiComboBoxProps } from '@elastic/eui/src/components/combo_box/combo_box';
-import { type DataView } from '@kbn/data-views-plugin/common';
-import type {
- FieldStatsProps,
- FieldStatsServices,
-} from '@kbn/unified-field-list/src/components/field_stats';
-import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import type { CasesPublicStart } from '@kbn/cases-plugin/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
+import type { FieldStatsFlyoutProviderProps } from '@kbn/ml-field-stats-flyout/field_stats_flyout_provider';
+import type { UseFieldStatsTrigger } from '@kbn/ml-field-stats-flyout/use_field_stats_trigger';
/**
* AIOps app context value to be provided via React context.
@@ -98,7 +93,7 @@ export interface AiopsAppContextValue {
/**
* Used to create deep links to other plugins.
*/
- share: SharePluginStart;
+ share?: SharePluginStart;
/**
* Used to create lens embeddables.
*/
@@ -115,18 +110,8 @@ export interface AiopsAppContextValue {
* Deps for unified fields stats.
*/
fieldStats?: {
- useFieldStatsTrigger: () => {
- renderOption: EuiComboBoxProps['renderOption'];
- closeFlyout: () => void;
- };
- FieldStatsFlyoutProvider: FC<
- PropsWithChildren<{
- dataView: DataView;
- fieldStatsServices: FieldStatsServices;
- timeRangeMs?: TimeRangeMs;
- dslQuery?: FieldStatsProps['dslQuery'];
- }>
- >;
+ useFieldStatsTrigger: UseFieldStatsTrigger;
+ FieldStatsFlyoutProvider: FC;
};
embeddable?: EmbeddableStart;
cases?: CasesPublicStart;
diff --git a/x-pack/plugins/aiops/public/hooks/use_data_source.tsx b/x-pack/plugins/aiops/public/hooks/use_data_source.tsx
index 081e10a34de65..ef574a348b928 100644
--- a/x-pack/plugins/aiops/public/hooks/use_data_source.tsx
+++ b/x-pack/plugins/aiops/public/hooks/use_data_source.tsx
@@ -8,10 +8,10 @@
import type { FC, PropsWithChildren } from 'react';
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
import type { DataView } from '@kbn/data-views-plugin/common';
+import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
-import { useAiopsAppContext } from './use_aiops_app_context';
export const DataSourceContext = createContext({
get dataView(): never {
@@ -30,6 +30,7 @@ export interface DataViewAndSavedSearch {
}
export interface DataSourceContextProviderProps {
+ dataViews: DataViewsPublicPluginStart;
dataViewId?: string;
savedSearchId?: string;
/** Output resolves data view objects */
@@ -43,20 +44,14 @@ export interface DataSourceContextProviderProps {
* @constructor
*/
export const DataSourceContextProvider: FC> = ({
+ dataViews,
dataViewId,
- savedSearchId,
children,
onChange,
}) => {
const [value, setValue] = useState();
const [error, setError] = useState();
- const {
- data: { dataViews },
- // uiSettings,
- // savedSearch: savedSearchService,
- } = useAiopsAppContext();
-
/**
* Resolve data view or saved search if exists.
*/
diff --git a/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx b/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx
index 9afbd9e1c4c8d..53997219fd639 100644
--- a/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx
+++ b/x-pack/plugins/aiops/public/shared_components/change_point_detection.tsx
@@ -11,7 +11,6 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser';
import { UI_SETTINGS } from '@kbn/data-service';
import type { TimeRange } from '@kbn/es-query';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
-import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { pick } from 'lodash';
import React, { useEffect, useMemo, useState, type FC } from 'react';
import type { Observable } from 'rxjs';
@@ -141,33 +140,34 @@ const ChangePointDetectionWrapper: FC = ({
width: 100%;
`}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/aiops/public/shared_components/index.tsx b/x-pack/plugins/aiops/public/shared_components/index.tsx
index 1c5b85c4b79b6..b347d3ee24cac 100644
--- a/x-pack/plugins/aiops/public/shared_components/index.tsx
+++ b/x-pack/plugins/aiops/public/shared_components/index.tsx
@@ -11,6 +11,7 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser';
import type { AiopsPluginStartDeps } from '../types';
import type { ChangePointDetectionSharedComponent } from './change_point_detection';
import type { PatternAnalysisSharedComponent } from './pattern_analysis';
+import type { LogRateAnalysisEmbeddableWrapper } from './log_rate_analysis_embeddable_wrapper';
const ChangePointDetectionLazy = dynamic(async () => import('./change_point_detection'));
@@ -37,3 +38,24 @@ export const getPatternAnalysisComponent = (
};
export type { PatternAnalysisSharedComponent } from './pattern_analysis';
+
+const LogRateAnalysisEmbeddableWrapperLazy = dynamic(
+ async () => import('./log_rate_analysis_embeddable_wrapper')
+);
+
+export const getLogRateAnalysisEmbeddableWrapperComponent = (
+ coreStart: CoreStart,
+ pluginStart: AiopsPluginStartDeps
+): LogRateAnalysisEmbeddableWrapper => {
+ return React.memo((props) => {
+ return (
+
+ );
+ });
+};
+
+export type { LogRateAnalysisEmbeddableWrapper } from './log_rate_analysis_embeddable_wrapper';
diff --git a/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx
new file mode 100644
index 0000000000000..9f2a88e73461c
--- /dev/null
+++ b/x-pack/plugins/aiops/public/shared_components/log_rate_analysis_embeddable_wrapper.tsx
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+import { pick } from 'lodash';
+import React, { useEffect, useMemo, useState, type FC } from 'react';
+import usePrevious from 'react-use/lib/usePrevious';
+import type { Observable } from 'rxjs';
+import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs';
+import { createBrowserHistory } from 'history';
+
+import { UrlStateProvider } from '@kbn/ml-url-state';
+import { Router } from '@kbn/shared-ux-router';
+import { AIOPS_EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants';
+import type { CoreStart } from '@kbn/core-lifecycle-browser';
+import { UI_SETTINGS } from '@kbn/data-service';
+import { LogRateAnalysisReduxProvider } from '@kbn/aiops-log-rate-analysis/state';
+import type { TimeRange } from '@kbn/es-query';
+import { DatePickerContextProvider } from '@kbn/ml-date-picker';
+import type { SignificantItem } from '@kbn/ml-agg-utils';
+
+import { AiopsAppContext, type AiopsAppContextValue } from '../hooks/use_aiops_app_context';
+import { DataSourceContextProvider } from '../hooks/use_data_source';
+import { ReloadContextProvider } from '../hooks/use_reload';
+import { FilterQueryContextProvider } from '../hooks/use_filters_query';
+import type { AiopsPluginStartDeps } from '../types';
+
+import { LogRateAnalysisForEmbeddable } from '../components/log_rate_analysis/log_rate_analysis_for_embeddable';
+
+/**
+ * Only used to initialize internally
+ */
+export type LogRateAnalysisPropsWithDeps = LogRateAnalysisEmbeddableWrapperProps & {
+ coreStart: CoreStart;
+ pluginStart: AiopsPluginStartDeps;
+};
+
+export type LogRateAnalysisEmbeddableWrapper = FC;
+
+export interface LogRateAnalysisEmbeddableWrapperProps {
+ dataViewId: string;
+ timeRange: TimeRange;
+ /**
+ * Component to render if there are no significant items found
+ */
+ emptyState?: React.ReactElement;
+ /**
+ * Outputs the most recent significant items
+ */
+ onChange?: (significantItems: SignificantItem[]) => void;
+ /**
+ * Last reload request time, can be used for manual reload
+ */
+ lastReloadRequestTime?: number;
+ /** Origin of the embeddable instance */
+ embeddingOrigin?: string;
+ onLoading: (isLoading: boolean) => void;
+ onRenderComplete: () => void;
+ onError: (error: Error) => void;
+}
+
+const LogRateAnalysisEmbeddableWrapperWithDeps: FC = ({
+ // Component dependencies
+ coreStart,
+ pluginStart,
+ // Component props
+ dataViewId,
+ timeRange,
+ embeddingOrigin,
+ lastReloadRequestTime,
+}) => {
+ const deps = useMemo(() => {
+ const { lens, data, usageCollection, fieldFormats, charts, share, storage, unifiedSearch } =
+ pluginStart;
+
+ return {
+ data,
+ lens,
+ usageCollection,
+ fieldFormats,
+ charts,
+ share,
+ storage,
+ unifiedSearch,
+ ...coreStart,
+ };
+ }, [coreStart, pluginStart]);
+
+ const datePickerDeps = {
+ ...pick(deps, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
+ uiSettingsKeys: UI_SETTINGS,
+ };
+
+ const aiopsAppContextValue = useMemo(() => {
+ return {
+ embeddingOrigin: embeddingOrigin ?? AIOPS_EMBEDDABLE_ORIGIN.DEFAULT,
+ ...deps,
+ };
+ }, [deps, embeddingOrigin]);
+
+ const [manualReload$] = useState>(
+ new BehaviorSubject(lastReloadRequestTime ?? Date.now())
+ );
+
+ useEffect(
+ function updateManualReloadSubject() {
+ if (!lastReloadRequestTime) return;
+ manualReload$.next(lastReloadRequestTime);
+ },
+ [lastReloadRequestTime, manualReload$]
+ );
+
+ const resultObservable$ = useMemo>(() => {
+ return combineLatest([manualReload$]).pipe(
+ map(([manualReload]) => Math.max(manualReload)),
+ distinctUntilChanged()
+ );
+ }, [manualReload$]);
+
+ const history = createBrowserHistory();
+
+ // We use the following pattern to track changes of dataViewId, and if there's
+ // a change, we unmount and remount the complete inner component. This makes
+ // sure the component is reinitialized correctly when the options of the
+ // dashboard panel are used to change the data view. This is a bit of a
+ // workaround since originally log rate analysis was developed as a standalone
+ // page with the expectation that the data view is set once and never changes.
+ const prevDataViewId = usePrevious(dataViewId);
+ const [_, setRerenderFlag] = useState(false);
+ useEffect(() => {
+ if (prevDataViewId && prevDataViewId !== dataViewId) {
+ setRerenderFlag((prev) => !prev);
+ }
+ }, [dataViewId, prevDataViewId]);
+ const showComponent = prevDataViewId === undefined || prevDataViewId === dataViewId;
+
+ // TODO: Remove data-shared-item as part of https://github.com/elastic/kibana/issues/179376>
+ return (
+
+ {showComponent && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export default LogRateAnalysisEmbeddableWrapperWithDeps;
diff --git a/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx
index 78261cd1f62f0..f601474a5707f 100644
--- a/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx
+++ b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx
@@ -10,7 +10,6 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser';
import { UI_SETTINGS } from '@kbn/data-service';
import type { TimeRange } from '@kbn/es-query';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
-import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { pick } from 'lodash';
import React, { useEffect, useMemo, useState, type FC } from 'react';
import type { Observable } from 'rxjs';
@@ -20,7 +19,7 @@ import type {
RandomSamplerOption,
RandomSamplerProbability,
} from '../components/log_categorization/sampling_menu/random_sampler';
-import { PatternAnalysisEmbeddableWrapper } from '../embeddables/pattern_analysis/pattern_analysys_component_wrapper';
+import { PatternAnalysisEmbeddableWrapper } from '../embeddables/pattern_analysis/pattern_analysis_component_wrapper';
import { AiopsAppContext, type AiopsAppContextValue } from '../hooks/use_aiops_app_context';
import { DataSourceContextProvider } from '../hooks/use_data_source';
import { FilterQueryContextProvider } from '../hooks/use_filters_query';
@@ -139,31 +138,32 @@ const PatternAnalysisWrapper: FC = ({
padding: '10px',
}}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/aiops/public/types/storage.ts b/x-pack/plugins/aiops/public/types/storage.ts
index a4a29dda2f2c3..ea6fde6b06552 100644
--- a/x-pack/plugins/aiops/public/types/storage.ts
+++ b/x-pack/plugins/aiops/public/types/storage.ts
@@ -19,14 +19,12 @@ export const AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE =
'aiops.randomSamplingProbabilityPreference';
export const AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE =
'aiops.patternAnalysisMinimumTimeRangePreference';
-export const AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS = 'aiops.logRateAnalysisResultColumns';
export type AiOps = Partial<{
[AIOPS_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
[AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE]: RandomSamplerOption;
[AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE]: number;
[AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE]: MinimumTimeRangeOption;
- [AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS]: string[];
}> | null;
export type AiOpsKey = keyof Exclude
;
@@ -39,8 +37,6 @@ export type AiOpsStorageMapped = T extends typeof AIOPS_FROZ
? RandomSamplerProbability
: T extends typeof AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE
? MinimumTimeRangeOption
- : T extends typeof AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS
- ? string[]
: null;
export const AIOPS_STORAGE_KEYS = [
@@ -48,5 +44,4 @@ export const AIOPS_STORAGE_KEYS = [
AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE,
AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE,
AIOPS_PATTERN_ANALYSIS_MINIMUM_TIME_RANGE_PREFERENCE,
- AIOPS_LOG_RATE_ANALYSIS_RESULT_COLUMNS,
] as const;
diff --git a/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx b/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx
index f9078f575818a..4d7ed26e295f5 100644
--- a/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx
+++ b/x-pack/plugins/aiops/public/ui_actions/create_change_point_chart.tsx
@@ -12,7 +12,9 @@ import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import type { CoreStart } from '@kbn/core-lifecycle-browser';
+
import type { AiopsPluginStartDeps } from '../types';
+
import type { ChangePointChartActionContext } from './change_point_action_context';
const parentApiIsCompatible = async (
diff --git a/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx
new file mode 100644
index 0000000000000..588903e96aa16
--- /dev/null
+++ b/x-pack/plugins/aiops/public/ui_actions/create_log_rate_analysis_actions.tsx
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+import { i18n } from '@kbn/i18n';
+import type { PresentationContainer } from '@kbn/presentation-containers';
+import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
+import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
+import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
+import type { CoreStart } from '@kbn/core-lifecycle-browser';
+import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants';
+import { AIOPS_EMBEDDABLE_GROUPING } from '@kbn/aiops-common/constants';
+
+import type {
+ LogRateAnalysisEmbeddableApi,
+ LogRateAnalysisEmbeddableInitialState,
+} from '../embeddables/log_rate_analysis/types';
+import type { AiopsPluginStartDeps } from '../types';
+
+import type { LogRateAnalysisActionContext } from './log_rate_analysis_action_context';
+
+const parentApiIsCompatible = async (
+ parentApi: unknown
+): Promise => {
+ const { apiIsPresentationContainer } = await import('@kbn/presentation-containers');
+ // we cannot have an async type check, so return the casted parentApi rather than a boolean
+ return apiIsPresentationContainer(parentApi) ? (parentApi as PresentationContainer) : undefined;
+};
+
+export function createAddLogRateAnalysisEmbeddableAction(
+ coreStart: CoreStart,
+ pluginStart: AiopsPluginStartDeps
+): UiActionsActionDefinition {
+ return {
+ id: 'create-log-rate-analysis-embeddable',
+ grouping: AIOPS_EMBEDDABLE_GROUPING,
+ getIconType: () => 'logRateAnalysis',
+ getDisplayName: () =>
+ i18n.translate('xpack.aiops.embeddableLogRateAnalysisDisplayName', {
+ defaultMessage: 'Log rate analysis',
+ }),
+ async isCompatible(context: EmbeddableApiContext) {
+ return Boolean(await parentApiIsCompatible(context.embeddable));
+ },
+ async execute(context) {
+ const presentationContainerParent = await parentApiIsCompatible(context.embeddable);
+ if (!presentationContainerParent) throw new IncompatibleActionError();
+
+ try {
+ const { resolveEmbeddableLogRateAnalysisUserInput } = await import(
+ '../embeddables/log_rate_analysis/resolve_log_rate_analysis_config_input'
+ );
+
+ const initialState: LogRateAnalysisEmbeddableInitialState = {
+ dataViewId: undefined,
+ };
+
+ const embeddable = await presentationContainerParent.addNewPanel<
+ object,
+ LogRateAnalysisEmbeddableApi
+ >({
+ panelType: EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE,
+ initialState,
+ });
+
+ if (!embeddable) {
+ return;
+ }
+
+ const deletePanel = () => {
+ presentationContainerParent.removePanel(embeddable.uuid);
+ };
+
+ resolveEmbeddableLogRateAnalysisUserInput(
+ coreStart,
+ pluginStart,
+ context.embeddable,
+ embeddable.uuid,
+ true,
+ embeddable,
+ deletePanel
+ );
+ } catch (e) {
+ return Promise.reject();
+ }
+ },
+ };
+}
diff --git a/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx
index 81127559e4e3d..f840e896abac4 100644
--- a/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx
+++ b/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx
@@ -12,13 +12,16 @@ import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import type { CoreStart } from '@kbn/core-lifecycle-browser';
import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants';
+import { AIOPS_EMBEDDABLE_GROUPING } from '@kbn/aiops-common/constants';
+
import type { AiopsPluginStartDeps } from '../types';
-import type { PatternAnalysisActionContext } from './pattern_analysis_action_context';
import type {
PatternAnalysisEmbeddableApi,
PatternAnalysisEmbeddableInitialState,
} from '../embeddables/pattern_analysis/types';
+import type { PatternAnalysisActionContext } from './pattern_analysis_action_context';
+
const parentApiIsCompatible = async (
parentApi: unknown
): Promise => {
@@ -33,16 +36,7 @@ export function createAddPatternAnalysisEmbeddableAction(
): UiActionsActionDefinition {
return {
id: 'create-pattern-analysis-embeddable',
- grouping: [
- {
- id: 'ml',
- getDisplayName: () =>
- i18n.translate('xpack.aiops.navMenu.mlAppNameText', {
- defaultMessage: 'Machine Learning and Analytics',
- }),
- getIconType: () => 'logPatternAnalysis',
- },
- ],
+ grouping: AIOPS_EMBEDDABLE_GROUPING,
getIconType: () => 'logPatternAnalysis',
getDisplayName: () =>
i18n.translate('xpack.aiops.embeddablePatternAnalysisDisplayName', {
diff --git a/x-pack/plugins/aiops/public/ui_actions/index.ts b/x-pack/plugins/aiops/public/ui_actions/index.ts
index d14856fd28733..6081541c448e7 100644
--- a/x-pack/plugins/aiops/public/ui_actions/index.ts
+++ b/x-pack/plugins/aiops/public/ui_actions/index.ts
@@ -18,6 +18,7 @@ import { createOpenChangePointInMlAppAction } from './open_change_point_ml';
import type { AiopsPluginStartDeps } from '../types';
import { createCategorizeFieldAction } from '../components/log_categorization';
import { createAddPatternAnalysisEmbeddableAction } from './create_pattern_analysis_action';
+import { createAddLogRateAnalysisEmbeddableAction } from './create_log_rate_analysis_actions';
export function registerAiopsUiActions(
uiActions: UiActionsSetup,
@@ -27,6 +28,7 @@ export function registerAiopsUiActions(
const openChangePointInMlAppAction = createOpenChangePointInMlAppAction(coreStart, pluginStart);
const addChangePointChartAction = createAddChangePointChartAction(coreStart, pluginStart);
const addPatternAnalysisAction = createAddPatternAnalysisEmbeddableAction(coreStart, pluginStart);
+ const addLogRateAnalysisAction = createAddLogRateAnalysisEmbeddableAction(coreStart, pluginStart);
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addPatternAnalysisAction);
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addChangePointChartAction);
@@ -39,4 +41,6 @@ export function registerAiopsUiActions(
);
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, openChangePointInMlAppAction);
+
+ uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addLogRateAnalysisAction);
}
diff --git a/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts b/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts
new file mode 100644
index 0000000000000..d0c1ef715b84c
--- /dev/null
+++ b/x-pack/plugins/aiops/public/ui_actions/log_rate_analysis_action_context.ts
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+import { isPopulatedObject } from '@kbn/ml-is-populated-object';
+import { apiIsOfType, type EmbeddableApiContext } from '@kbn/presentation-publishing';
+import { EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis/constants';
+import type { LogRateAnalysisEmbeddableApi } from '../embeddables/log_rate_analysis/types';
+
+export interface LogRateAnalysisActionContext extends EmbeddableApiContext {
+ embeddable: LogRateAnalysisEmbeddableApi;
+}
+
+export function isLogRateAnalysisEmbeddableContext(
+ arg: unknown
+): arg is LogRateAnalysisActionContext {
+ return (
+ isPopulatedObject(arg, ['embeddable']) &&
+ apiIsOfType(arg.embeddable, EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE)
+ );
+}
diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json
index 188f8b275fbac..e8b4f4f3ed972 100644
--- a/x-pack/plugins/aiops/tsconfig.json
+++ b/x-pack/plugins/aiops/tsconfig.json
@@ -63,7 +63,6 @@
"@kbn/presentation-containers",
"@kbn/presentation-publishing",
"@kbn/presentation-util-plugin",
- "@kbn/react-kibana-context-render",
"@kbn/react-kibana-context-theme",
"@kbn/react-kibana-mount",
"@kbn/rison",
@@ -79,6 +78,8 @@
"@kbn/observability-ai-assistant-plugin",
"@kbn/ui-theme",
"@kbn/apm-utils",
+ "@kbn/ml-field-stats-flyout",
+ "@kbn/shared-ux-router",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts
index 2f5cd3994e436..2cb2c212a91ba 100644
--- a/x-pack/plugins/alerting/server/index.ts
+++ b/x-pack/plugins/alerting/server/index.ts
@@ -84,20 +84,6 @@ export const config: PluginConfigDescriptor = {
rules: { run: { alerts: { max: true } } },
},
deprecations: ({ renameFromRoot, deprecate }) => [
- renameFromRoot('xpack.alerts.healthCheck', 'xpack.alerting.healthCheck', { level: 'warning' }),
- renameFromRoot(
- 'xpack.alerts.invalidateApiKeysTask.interval',
- 'xpack.alerting.invalidateApiKeysTask.interval',
- { level: 'warning' }
- ),
- renameFromRoot(
- 'xpack.alerts.invalidateApiKeysTask.removalDelay',
- 'xpack.alerting.invalidateApiKeysTask.removalDelay',
- { level: 'warning' }
- ),
- renameFromRoot('xpack.alerting.defaultRuleTaskTimeout', 'xpack.alerting.rules.run.timeout', {
- level: 'warning',
- }),
deprecate('maxEphemeralActionsPerAlert', 'a future version', {
level: 'warning',
message: `Configuring "xpack.alerting.maxEphemeralActionsPerAlert" is deprecated and will be removed in a future version. Remove this setting to increase action execution resiliency.`,
diff --git a/x-pack/plugins/cases/public/components/case_view/components/user_list.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/user_list.test.tsx
index 7254f7f500f63..b1601e471915f 100644
--- a/x-pack/plugins/cases/public/components/case_view/components/user_list.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/components/user_list.test.tsx
@@ -20,7 +20,8 @@ jest.mock('../../../common/navigation/hooks');
const useCaseViewNavigationMock = useCaseViewNavigation as jest.Mock;
-describe('UserList ', () => {
+// FLAKY: https://github.com/elastic/kibana/issues/192640
+describe.skip('UserList ', () => {
const title = basicCase.title;
const caseLink = 'https://example.com/cases/test';
const user = {
diff --git a/x-pack/plugins/elastic_assistant/server/__mocks__/request.ts b/x-pack/plugins/elastic_assistant/server/__mocks__/request.ts
index 9dc57bab25ef3..f6f3007c8f948 100644
--- a/x-pack/plugins/elastic_assistant/server/__mocks__/request.ts
+++ b/x-pack/plugins/elastic_assistant/server/__mocks__/request.ts
@@ -23,6 +23,7 @@ import {
ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_BY_ID_MESSAGES,
ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND,
ELASTIC_AI_ASSISTANT_EVALUATE_URL,
+ ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_INDICES_URL,
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL,
ELASTIC_AI_ASSISTANT_PROMPTS_URL_BULK_ACTION,
ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND,
@@ -46,6 +47,12 @@ export const requestMock = {
create: httpServerMock.createKibanaRequest,
};
+export const getGetKnowledgeBaseIndicesRequest = () =>
+ requestMock.create({
+ method: 'get',
+ path: ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_INDICES_URL,
+ });
+
export const getGetKnowledgeBaseStatusRequest = (resource?: string) =>
requestMock.create({
method: 'get',
diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts
index f985095661f3e..333fbb796ddd9 100644
--- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts
+++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts
@@ -269,9 +269,11 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
public setupKnowledgeBase = async ({
soClient,
v2KnowledgeBaseEnabled = true,
+ ignoreSecurityLabs = false,
}: {
soClient: SavedObjectsClientContract;
v2KnowledgeBaseEnabled?: boolean;
+ ignoreSecurityLabs?: boolean;
}): Promise => {
if (this.options.getIsKBSetupInProgress()) {
this.options.logger.debug('Knowledge Base setup already in progress');
@@ -366,7 +368,7 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
this.options.logger.debug(`Checking if Knowledge Base docs have been loaded...`);
- if (v2KnowledgeBaseEnabled) {
+ if (v2KnowledgeBaseEnabled && !ignoreSecurityLabs) {
const labsDocsLoaded = await this.isSecurityLabsDocsLoaded();
if (!labsDocsLoaded) {
this.options.logger.debug(`Loading Security Labs KB docs...`);
diff --git a/x-pack/plugins/elastic_assistant/server/routes/index.ts b/x-pack/plugins/elastic_assistant/server/routes/index.ts
index a6d7a4298c2b7..928c3211faa9b 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/index.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/index.ts
@@ -14,6 +14,7 @@ export { getAttackDiscoveryRoute } from './attack_discovery/get/get_attack_disco
// Knowledge Base
export { deleteKnowledgeBaseRoute } from './knowledge_base/delete_knowledge_base';
+export { getKnowledgeBaseIndicesRoute } from './knowledge_base/get_knowledge_base_indices';
export { getKnowledgeBaseStatusRoute } from './knowledge_base/get_knowledge_base_status';
export { postKnowledgeBaseRoute } from './knowledge_base/post_knowledge_base';
diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_indices.test.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_indices.test.ts
new file mode 100644
index 0000000000000..e7eaa75407248
--- /dev/null
+++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_indices.test.ts
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+import { getKnowledgeBaseIndicesRoute } from './get_knowledge_base_indices';
+import { serverMock } from '../../__mocks__/server';
+import { requestContextMock } from '../../__mocks__/request_context';
+import { getGetKnowledgeBaseIndicesRequest } from '../../__mocks__/request';
+
+const mockFieldCaps = {
+ indices: [
+ '.ds-logs-endpoint.alerts-default-2024.10.31-000001',
+ '.ds-metrics-endpoint.metadata-default-2024.10.31-000001',
+ '.internal.alerts-security.alerts-default-000001',
+ 'metrics-endpoint.metadata_current_default',
+ 'semantic-index-1',
+ 'semantic-index-2',
+ ],
+ fields: {
+ content: {
+ unmapped: {
+ type: 'unmapped',
+ metadata_field: false,
+ searchable: false,
+ aggregatable: false,
+ indices: [
+ '.ds-logs-endpoint.alerts-default-2024.10.31-000001',
+ '.ds-metrics-endpoint.metadata-default-2024.10.31-000001',
+ '.internal.alerts-security.alerts-default-000001',
+ 'metrics-endpoint.metadata_current_default',
+ ],
+ },
+ semantic_text: {
+ type: 'semantic_text',
+ metadata_field: false,
+ searchable: true,
+ aggregatable: false,
+ indices: ['semantic-index-1', 'semantic-index-2'],
+ },
+ },
+ },
+};
+
+describe('Get Knowledge Base Status Route', () => {
+ let server: ReturnType;
+
+ let { context } = requestContextMock.createTools();
+
+ beforeEach(() => {
+ server = serverMock.create();
+ ({ context } = requestContextMock.createTools());
+ context.core.elasticsearch.client.asCurrentUser.fieldCaps.mockResponse(mockFieldCaps);
+
+ getKnowledgeBaseIndicesRoute(server.router);
+ });
+
+ describe('Status codes', () => {
+ test('returns 200 and all indices with `semantic_text` type fields', async () => {
+ const response = await server.inject(
+ getGetKnowledgeBaseIndicesRequest(),
+ requestContextMock.convertContext(context)
+ );
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({
+ indices: ['semantic-index-1', 'semantic-index-2'],
+ });
+ expect(context.core.elasticsearch.client.asCurrentUser.fieldCaps).toBeCalledWith({
+ index: '*',
+ fields: '*',
+ types: ['semantic_text'],
+ include_unmapped: true,
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_indices.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_indices.ts
new file mode 100644
index 0000000000000..18191291468de
--- /dev/null
+++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/get_knowledge_base_indices.ts
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+import { transformError } from '@kbn/securitysolution-es-utils';
+
+import {
+ ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION,
+ ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_INDICES_URL,
+ GetKnowledgeBaseIndicesResponse,
+} from '@kbn/elastic-assistant-common';
+import { IKibanaResponse } from '@kbn/core/server';
+import { buildResponse } from '../../lib/build_response';
+import { ElasticAssistantPluginRouter } from '../../types';
+
+/**
+ * Get the indices that have fields of `sematic_text` type
+ *
+ * @param router IRouter for registering routes
+ */
+export const getKnowledgeBaseIndicesRoute = (router: ElasticAssistantPluginRouter) => {
+ router.versioned
+ .get({
+ access: 'internal',
+ path: ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_INDICES_URL,
+ options: {
+ tags: ['access:elasticAssistant'],
+ },
+ })
+ .addVersion(
+ {
+ version: ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION,
+ validate: false,
+ },
+ async (context, _, response): Promise> => {
+ const resp = buildResponse(response);
+ const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
+ const logger = ctx.elasticAssistant.logger;
+ const esClient = ctx.core.elasticsearch.client.asCurrentUser;
+
+ try {
+ const body: GetKnowledgeBaseIndicesResponse = {
+ indices: [],
+ };
+
+ const res = await esClient.fieldCaps({
+ index: '*',
+ fields: '*',
+ types: ['semantic_text'],
+ include_unmapped: true,
+ });
+
+ const indices = res.fields.content?.semantic_text?.indices;
+ if (indices) {
+ body.indices = Array.isArray(indices) ? indices : [indices];
+ }
+
+ return response.ok({ body });
+ } catch (err) {
+ logger.error(err);
+ const error = transformError(err);
+
+ return resp.error({
+ body: error.message,
+ statusCode: error.statusCode,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/post_knowledge_base.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/post_knowledge_base.ts
index 96317da303ac1..23604886e4a52 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/post_knowledge_base.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/post_knowledge_base.ts
@@ -60,6 +60,7 @@ export const postKnowledgeBaseRoute = (router: ElasticAssistantPluginRouter) =>
// Only allow modelId override if FF is enabled as this will re-write the ingest pipeline and break any previous KB entries
// This is only really needed for API integration tests
const modelIdOverride = v2KnowledgeBaseEnabled ? request.query.modelId : undefined;
+ const ignoreSecurityLabs = request.query.ignoreSecurityLabs;
try {
const knowledgeBaseDataClient =
@@ -74,6 +75,7 @@ export const postKnowledgeBaseRoute = (router: ElasticAssistantPluginRouter) =>
await knowledgeBaseDataClient.setupKnowledgeBase({
soClient,
v2KnowledgeBaseEnabled,
+ ignoreSecurityLabs,
});
return response.ok({ body: { success: true } });
diff --git a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts
index 7898629e15b5c..d722e31cb2338 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts
@@ -18,6 +18,7 @@ import { updateConversationRoute } from './user_conversations/update_route';
import { findUserConversationsRoute } from './user_conversations/find_route';
import { bulkActionConversationsRoute } from './user_conversations/bulk_actions_route';
import { appendConversationMessageRoute } from './user_conversations/append_conversation_messages_route';
+import { getKnowledgeBaseIndicesRoute } from './knowledge_base/get_knowledge_base_indices';
import { getKnowledgeBaseStatusRoute } from './knowledge_base/get_knowledge_base_status';
import { postKnowledgeBaseRoute } from './knowledge_base/post_knowledge_base';
import { getEvaluateRoute } from './evaluate/get_evaluate';
@@ -60,6 +61,7 @@ export const registerRoutes = (
findUserConversationsRoute(router);
// Knowledge Base Setup
+ getKnowledgeBaseIndicesRoute(router);
getKnowledgeBaseStatusRoute(router);
postKnowledgeBaseRoute(router);
diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_object_type_definition.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_object_type_definition.ts
index d8ce2daa6efbe..bb07842e2bab5 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_object_type_definition.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_object_type_definition.ts
@@ -16,6 +16,7 @@ export class EncryptedSavedObjectAttributesDefinition {
public readonly attributesToEncrypt: ReadonlySet;
private readonly attributesToIncludeInAAD: ReadonlySet | undefined;
private readonly attributesToStrip: ReadonlySet;
+ public readonly enforceRandomId: boolean;
constructor(typeRegistration: EncryptedSavedObjectTypeRegistration) {
if (typeRegistration.attributesToIncludeInAAD) {
@@ -49,6 +50,8 @@ export class EncryptedSavedObjectAttributesDefinition {
}
}
+ this.enforceRandomId = typeRegistration.enforceRandomId !== false;
+
this.attributesToEncrypt = attributesToEncrypt;
this.attributesToStrip = attributesToStrip;
this.attributesToIncludeInAAD = typeRegistration.attributesToIncludeInAAD;
diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts
index 1691d3f4c0610..67c972ec5f859 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts
@@ -2405,3 +2405,24 @@ describe('#decryptAttributesSync', () => {
});
});
});
+
+describe('#shouldEnforceRandomId', () => {
+ it('defaults to true if enforceRandomId is undefined', () => {
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attr']) });
+ expect(service.shouldEnforceRandomId('known-type-1')).toBe(true);
+ });
+ it('should return the value of enforceRandomId if it is defined', () => {
+ service.registerType({
+ type: 'known-type-1',
+ attributesToEncrypt: new Set(['attr']),
+ enforceRandomId: false,
+ });
+ service.registerType({
+ type: 'known-type-2',
+ attributesToEncrypt: new Set(['attr']),
+ enforceRandomId: true,
+ });
+ expect(service.shouldEnforceRandomId('known-type-1')).toBe(false);
+ expect(service.shouldEnforceRandomId('known-type-2')).toBe(true);
+ });
+});
diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts
index 44072a0828d48..d2c7d9975a9ca 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts
@@ -33,6 +33,7 @@ export interface EncryptedSavedObjectTypeRegistration {
readonly type: string;
readonly attributesToEncrypt: ReadonlySet;
readonly attributesToIncludeInAAD?: ReadonlySet;
+ readonly enforceRandomId?: boolean;
}
/**
@@ -152,6 +153,16 @@ export class EncryptedSavedObjectsService {
return this.typeDefinitions.has(type);
}
+ /**
+ * Checks whether the ESO type has explicitly opted out of enforcing random IDs.
+ * @param type Saved object type.
+ * @returns boolean - true unless explicitly opted out by setting enforceRandomId to false
+ */
+ public shouldEnforceRandomId(type: string) {
+ const typeDefinition = this.typeDefinitions.get(type);
+ return typeDefinition?.enforceRandomId !== false;
+ }
+
/**
* Takes saved object attributes for the specified type and, depending on the type definition,
* either decrypts or strips encrypted attributes (e.g. in case AAD or encryption key has changed
diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts
index 01c35c7403fdf..45e0f6a46c892 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts
@@ -40,6 +40,10 @@ export class SavedObjectsEncryptionExtension implements ISavedObjectsEncryptionE
return this._service.isRegistered(type);
}
+ shouldEnforceRandomId(type: string) {
+ return this._service.shouldEnforceRandomId(type);
+ }
+
async decryptOrStripResponseAttributes>(
response: R,
originalAttributes?: T
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx
index 38217df269fd1..c72f56c656e49 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/create_api_key_flyout.tsx
@@ -210,6 +210,7 @@ export const CreateApiKeyFlyout: React.FC = ({ onClose
defaultMessage: 'Store this API key',
})}
titleSize="xs"
+ role="alert"
>
{i18n.translate('xpack.enterpriseSearch.apiKey.apiKeyStepDescription', {
diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts
index 3aa65dc3adcd4..827130d802f22 100644
--- a/x-pack/plugins/fleet/common/types/models/epm.ts
+++ b/x-pack/plugins/fleet/common/types/models/epm.ts
@@ -124,10 +124,25 @@ export type InstallablePackage = RegistryPackage | ArchivePackage;
export type AssetsMap = Map;
+export interface ArchiveEntry {
+ path: string;
+ buffer?: Buffer;
+}
+
+export interface ArchiveIterator {
+ traverseEntries: (onEntry: (entry: ArchiveEntry) => Promise) => Promise;
+ getPaths: () => Promise;
+}
+
export interface PackageInstallContext {
packageInfo: InstallablePackage;
+ /**
+ * @deprecated Use `archiveIterator` to access the package archive entries
+ * without loading them all into memory at once.
+ */
assetsMap: AssetsMap;
paths: string[];
+ archiveIterator: ArchiveIterator;
}
export type ArchivePackage = PackageSpecManifest &
diff --git a/x-pack/plugins/fleet/cypress/tasks/common.ts b/x-pack/plugins/fleet/cypress/tasks/common.ts
index cf161640bf03f..2bf201b11a498 100644
--- a/x-pack/plugins/fleet/cypress/tasks/common.ts
+++ b/x-pack/plugins/fleet/cypress/tasks/common.ts
@@ -67,7 +67,6 @@ export const internalRequest = ({
const NEW_FEATURES_TOUR_STORAGE_KEYS = {
RULE_MANAGEMENT_PAGE: 'securitySolution.rulesManagementPage.newFeaturesTour.v8.9',
TIMELINES: 'securitySolution.security.timelineFlyoutHeader.saveTimelineTour',
- FLYOUT: 'securitySolution.documentDetails.newFeaturesTour.v8.14',
};
const disableNewFeaturesTours = (window: Window) => {
diff --git a/x-pack/plugins/fleet/server/routes/epm/file_handler.test.ts b/x-pack/plugins/fleet/server/routes/epm/file_handler.test.ts
index 1eb8387f69751..5690c32c2d7fd 100644
--- a/x-pack/plugins/fleet/server/routes/epm/file_handler.test.ts
+++ b/x-pack/plugins/fleet/server/routes/epm/file_handler.test.ts
@@ -15,7 +15,7 @@ import { getBundledPackageByPkgKey } from '../../services/epm/packages/bundled_p
import { getFile, getInstallation } from '../../services/epm/packages/get';
import type { FleetRequestHandlerContext } from '../..';
import { appContextService } from '../../services';
-import { unpackBufferEntries } from '../../services/epm/archive';
+import { unpackArchiveEntriesIntoMemory } from '../../services/epm/archive';
import { getAsset } from '../../services/epm/archive/storage';
import { getFileHandler } from './file_handler';
@@ -29,7 +29,7 @@ jest.mock('../../services/epm/packages/get');
const mockedGetBundledPackageByPkgKey = jest.mocked(getBundledPackageByPkgKey);
const mockedGetInstallation = jest.mocked(getInstallation);
const mockedGetFile = jest.mocked(getFile);
-const mockedUnpackBufferEntries = jest.mocked(unpackBufferEntries);
+const mockedUnpackBufferEntries = jest.mocked(unpackArchiveEntriesIntoMemory);
const mockedGetAsset = jest.mocked(getAsset);
function mockContext() {
diff --git a/x-pack/plugins/fleet/server/routes/epm/file_handler.ts b/x-pack/plugins/fleet/server/routes/epm/file_handler.ts
index 0f22a31c1aa72..994f52a71c224 100644
--- a/x-pack/plugins/fleet/server/routes/epm/file_handler.ts
+++ b/x-pack/plugins/fleet/server/routes/epm/file_handler.ts
@@ -17,7 +17,7 @@ import { defaultFleetErrorHandler } from '../../errors';
import { getAsset } from '../../services/epm/archive/storage';
import { getBundledPackageByPkgKey } from '../../services/epm/packages/bundled_packages';
import { pkgToPkgKey } from '../../services/epm/registry';
-import { unpackBufferEntries } from '../../services/epm/archive';
+import { unpackArchiveEntriesIntoMemory } from '../../services/epm/archive';
const CACHE_CONTROL_10_MINUTES_HEADER: HttpResponseOptions['headers'] = {
'cache-control': 'max-age=600',
@@ -69,7 +69,7 @@ export const getFileHandler: FleetRequestHandler<
pkgToPkgKey({ name: pkgName, version: pkgVersion })
);
if (bundledPackage) {
- const bufferEntries = await unpackBufferEntries(
+ const bufferEntries = await unpackArchiveEntriesIntoMemory(
await bundledPackage.getBuffer(),
'application/zip'
);
diff --git a/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts b/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts
index 8fe83f98669d1..ad0bec6397ee8 100644
--- a/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts
+++ b/x-pack/plugins/fleet/server/routes/epm/kibana_assets_handler.ts
@@ -22,6 +22,7 @@ import type {
FleetRequestHandler,
InstallKibanaAssetsRequestSchema,
} from '../../types';
+import { createArchiveIteratorFromMap } from '../../services/epm/archive/archive_iterator';
export const installPackageKibanaAssetsHandler: FleetRequestHandler<
TypeOf,
@@ -69,6 +70,7 @@ export const installPackageKibanaAssetsHandler: FleetRequestHandler<
packageInfo,
paths: installedPkgWithAssets.paths,
assetsMap: installedPkgWithAssets.assetsMap,
+ archiveIterator: createArchiveIteratorFromMap(installedPkgWithAssets.assetsMap),
},
});
diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts
index f93bf583945a0..cada1c8e64452 100644
--- a/x-pack/plugins/fleet/server/services/agent_policy.ts
+++ b/x-pack/plugins/fleet/server/services/agent_policy.ts
@@ -30,6 +30,8 @@ import { asyncForEach } from '@kbn/std';
import type { SavedObjectError } from '@kbn/core-saved-objects-common';
+import { withSpan } from '@kbn/apm-utils';
+
import {
getAllowedOutputTypeForPolicy,
packageToPackagePolicy,
@@ -170,11 +172,13 @@ class AgentPolicyService {
removeProtection: boolean;
skipValidation: boolean;
returnUpdatedPolicy?: boolean;
+ asyncDeploy?: boolean;
} = {
bumpRevision: true,
removeProtection: false,
skipValidation: false,
returnUpdatedPolicy: true,
+ asyncDeploy: false,
}
): Promise {
const savedObjectType = await getAgentPolicySavedObjectType();
@@ -228,10 +232,19 @@ class AgentPolicyService {
newAgentPolicy!.package_policies = existingAgentPolicy.package_policies;
if (options.bumpRevision || options.removeProtection) {
- await this.triggerAgentPolicyUpdatedEvent(esClient, 'updated', id, {
- spaceId: soClient.getCurrentNamespace(),
- agentPolicy: newAgentPolicy,
- });
+ if (!options.asyncDeploy) {
+ await this.triggerAgentPolicyUpdatedEvent(esClient, 'updated', id, {
+ spaceId: soClient.getCurrentNamespace(),
+ agentPolicy: newAgentPolicy,
+ });
+ } else {
+ await scheduleDeployAgentPoliciesTask(appContextService.getTaskManagerStart()!, [
+ {
+ id,
+ spaceId: soClient.getCurrentNamespace(),
+ },
+ ]);
+ }
}
logger.debug(
`Agent policy ${id} update completed, revision: ${
@@ -878,13 +891,16 @@ class AgentPolicyService {
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
id: string,
- options?: { user?: AuthenticatedUser; removeProtection?: boolean }
+ options?: { user?: AuthenticatedUser; removeProtection?: boolean; asyncDeploy?: boolean }
): Promise {
- await this._update(soClient, esClient, id, {}, options?.user, {
- bumpRevision: true,
- removeProtection: options?.removeProtection ?? false,
- skipValidation: false,
- returnUpdatedPolicy: false,
+ return withSpan('bump_agent_policy_revision', async () => {
+ await this._update(soClient, esClient, id, {}, options?.user, {
+ bumpRevision: true,
+ removeProtection: options?.removeProtection ?? false,
+ skipValidation: false,
+ returnUpdatedPolicy: false,
+ asyncDeploy: options?.asyncDeploy,
+ });
});
}
diff --git a/x-pack/plugins/fleet/server/services/epm/archive/archive_iterator.ts b/x-pack/plugins/fleet/server/services/epm/archive/archive_iterator.ts
new file mode 100644
index 0000000000000..369b32412bd82
--- /dev/null
+++ b/x-pack/plugins/fleet/server/services/epm/archive/archive_iterator.ts
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+import type { AssetsMap, ArchiveIterator, ArchiveEntry } from '../../../../common/types';
+
+import { traverseArchiveEntries } from '.';
+
+/**
+ * Creates an iterator for traversing and extracting paths from an archive
+ * buffer. This iterator is intended to be used for memory efficient traversal
+ * of archive contents without extracting the entire archive into memory.
+ *
+ * @param archiveBuffer - The buffer containing the archive data.
+ * @param contentType - The content type of the archive (e.g.,
+ * 'application/zip').
+ * @returns ArchiveIterator instance.
+ *
+ */
+export const createArchiveIterator = (
+ archiveBuffer: Buffer,
+ contentType: string
+): ArchiveIterator => {
+ const paths: string[] = [];
+
+ const traverseEntries = async (
+ onEntry: (entry: ArchiveEntry) => Promise
+ ): Promise => {
+ await traverseArchiveEntries(archiveBuffer, contentType, async (entry) => {
+ await onEntry(entry);
+ });
+ };
+
+ const getPaths = async (): Promise => {
+ if (paths.length) {
+ return paths;
+ }
+
+ await traverseEntries(async (entry) => {
+ paths.push(entry.path);
+ });
+
+ return paths;
+ };
+
+ return {
+ traverseEntries,
+ getPaths,
+ };
+};
+
+/**
+ * Creates an archive iterator from the assetsMap. This is a stop-gap solution
+ * to provide a uniform interface for traversing assets while assetsMap is still
+ * in use. It works with a map of assets loaded into memory and is not intended
+ * for use with large archives.
+ *
+ * @param assetsMap - A map where the keys are asset paths and the values are
+ * asset buffers.
+ * @returns ArchiveIterator instance.
+ *
+ */
+export const createArchiveIteratorFromMap = (assetsMap: AssetsMap): ArchiveIterator => {
+ const traverseEntries = async (
+ onEntry: (entry: ArchiveEntry) => Promise
+ ): Promise => {
+ for (const [path, buffer] of assetsMap) {
+ await onEntry({ path, buffer });
+ }
+ };
+
+ const getPaths = async (): Promise => {
+ return [...assetsMap.keys()];
+ };
+
+ return {
+ traverseEntries,
+ getPaths,
+ };
+};
diff --git a/x-pack/plugins/fleet/server/services/epm/archive/extract.ts b/x-pack/plugins/fleet/server/services/epm/archive/extract.ts
index 84aa161385cb3..9f5f90959d144 100644
--- a/x-pack/plugins/fleet/server/services/epm/archive/extract.ts
+++ b/x-pack/plugins/fleet/server/services/epm/archive/extract.ts
@@ -11,13 +11,12 @@ import * as tar from 'tar';
import yauzl from 'yauzl';
import { bufferToStream, streamToBuffer } from '../streams';
-
-import type { ArchiveEntry } from '.';
+import type { ArchiveEntry } from '../../../../common/types';
export async function untarBuffer(
buffer: Buffer,
filter = (entry: ArchiveEntry): boolean => true,
- onEntry = (entry: ArchiveEntry): void => {}
+ onEntry = async (entry: ArchiveEntry): Promise => {}
) {
const deflatedStream = bufferToStream(buffer);
// use tar.list vs .extract to avoid writing to disk
@@ -37,7 +36,7 @@ export async function untarBuffer(
export async function unzipBuffer(
buffer: Buffer,
filter = (entry: ArchiveEntry): boolean => true,
- onEntry = (entry: ArchiveEntry): void => {}
+ onEntry = async (entry: ArchiveEntry): Promise => {}
): Promise {
const zipfile = await yauzlFromBuffer(buffer, { lazyEntries: true });
zipfile.readEntry();
@@ -45,9 +44,12 @@ export async function unzipBuffer(
const path = entry.fileName;
if (!filter({ path })) return zipfile.readEntry();
- const entryBuffer = await getZipReadStream(zipfile, entry).then(streamToBuffer);
- onEntry({ buffer: entryBuffer, path });
- zipfile.readEntry();
+ try {
+ const entryBuffer = await getZipReadStream(zipfile, entry).then(streamToBuffer);
+ await onEntry({ buffer: entryBuffer, path });
+ } finally {
+ zipfile.readEntry();
+ }
});
return new Promise((resolve, reject) => zipfile.on('end', resolve).on('error', reject));
}
diff --git a/x-pack/plugins/fleet/server/services/epm/archive/index.ts b/x-pack/plugins/fleet/server/services/epm/archive/index.ts
index 5943f8f838fcb..ed9ff2a5e4b72 100644
--- a/x-pack/plugins/fleet/server/services/epm/archive/index.ts
+++ b/x-pack/plugins/fleet/server/services/epm/archive/index.ts
@@ -5,13 +5,20 @@
* 2.0.
*/
-import type { AssetParts, AssetsMap } from '../../../../common/types';
+import type {
+ ArchiveEntry,
+ ArchiveIterator,
+ AssetParts,
+ AssetsMap,
+} from '../../../../common/types';
import {
PackageInvalidArchiveError,
PackageUnsupportedMediaTypeError,
PackageNotFoundError,
} from '../../../errors';
+import { createArchiveIterator } from './archive_iterator';
+
import { deletePackageInfo } from './cache';
import type { SharedKey } from './cache';
import { getBufferExtractor } from './extract';
@@ -20,66 +27,85 @@ export * from './cache';
export { getBufferExtractor, untarBuffer, unzipBuffer } from './extract';
export { generatePackageInfoFromArchiveBuffer } from './parse';
-export interface ArchiveEntry {
- path: string;
- buffer?: Buffer;
-}
-
export async function unpackBufferToAssetsMap({
- name,
- version,
contentType,
archiveBuffer,
+ useStreaming,
}: {
- name: string;
- version: string;
contentType: string;
archiveBuffer: Buffer;
-}): Promise<{ paths: string[]; assetsMap: AssetsMap }> {
- const assetsMap = new Map();
- const paths: string[] = [];
- const entries = await unpackBufferEntries(archiveBuffer, contentType);
-
- entries.forEach((entry) => {
- const { path, buffer } = entry;
- if (buffer) {
- assetsMap.set(path, buffer);
- paths.push(path);
- }
- });
-
- return { assetsMap, paths };
+ useStreaming: boolean | undefined;
+}): Promise<{ paths: string[]; assetsMap: AssetsMap; archiveIterator: ArchiveIterator }> {
+ const archiveIterator = createArchiveIterator(archiveBuffer, contentType);
+ let paths: string[] = [];
+ let assetsMap: AssetsMap = new Map();
+ if (useStreaming) {
+ paths = await archiveIterator.getPaths();
+ // We keep the assetsMap empty as we don't want to load all the assets in memory
+ assetsMap = new Map();
+ } else {
+ const entries = await unpackArchiveEntriesIntoMemory(archiveBuffer, contentType);
+
+ entries.forEach((entry) => {
+ const { path, buffer } = entry;
+ if (buffer) {
+ assetsMap.set(path, buffer);
+ paths.push(path);
+ }
+ });
+ }
+
+ return { paths, assetsMap, archiveIterator };
}
-export async function unpackBufferEntries(
+/**
+ * This function extracts all archive entries into memory.
+ *
+ * NOTE: This is potentially dangerous for large archives and can cause OOM
+ * errors. Use 'traverseArchiveEntries' instead to iterate over the entries
+ * without storing them all in memory at once.
+ *
+ * @param archiveBuffer
+ * @param contentType
+ * @returns All the entries in the archive buffer
+ */
+export async function unpackArchiveEntriesIntoMemory(
archiveBuffer: Buffer,
contentType: string
): Promise {
+ const entries: ArchiveEntry[] = [];
+ const addToEntries = async (entry: ArchiveEntry) => void entries.push(entry);
+ await traverseArchiveEntries(archiveBuffer, contentType, addToEntries);
+
+ // While unpacking a tar.gz file with unzipBuffer() will result in a thrown
+ // error, unpacking a zip file with untarBuffer() just results in nothing.
+ if (entries.length === 0) {
+ throw new PackageInvalidArchiveError(
+ `Archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.`
+ );
+ }
+ return entries;
+}
+
+export async function traverseArchiveEntries(
+ archiveBuffer: Buffer,
+ contentType: string,
+ onEntry: (entry: ArchiveEntry) => Promise
+) {
const bufferExtractor = getBufferExtractor({ contentType });
if (!bufferExtractor) {
throw new PackageUnsupportedMediaTypeError(
`Unsupported media type ${contentType}. Please use 'application/gzip' or 'application/zip'`
);
}
- const entries: ArchiveEntry[] = [];
try {
const onlyFiles = ({ path }: ArchiveEntry): boolean => !path.endsWith('/');
- const addToEntries = (entry: ArchiveEntry) => entries.push(entry);
- await bufferExtractor(archiveBuffer, onlyFiles, addToEntries);
+ await bufferExtractor(archiveBuffer, onlyFiles, onEntry);
} catch (error) {
throw new PackageInvalidArchiveError(
`Error during extraction of package: ${error}. Assumed content type was ${contentType}, check if this matches the archive type.`
);
}
-
- // While unpacking a tar.gz file with unzipBuffer() will result in a thrown error in the try-catch above,
- // unpacking a zip file with untarBuffer() just results in nothing.
- if (entries.length === 0) {
- throw new PackageInvalidArchiveError(
- `Archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.`
- );
- }
- return entries;
}
export const deletePackageCache = ({ name, version }: SharedKey) => {
diff --git a/x-pack/plugins/fleet/server/services/epm/archive/parse.ts b/x-pack/plugins/fleet/server/services/epm/archive/parse.ts
index 530ca804f24eb..8cccfe9982457 100644
--- a/x-pack/plugins/fleet/server/services/epm/archive/parse.ts
+++ b/x-pack/plugins/fleet/server/services/epm/archive/parse.ts
@@ -40,7 +40,7 @@ import {
import { PackageInvalidArchiveError } from '../../../errors';
import { pkgToPkgKey } from '../registry';
-import { unpackBufferEntries } from '.';
+import { traverseArchiveEntries } from '.';
const readFileAsync = promisify(readFile);
export const MANIFEST_NAME = 'manifest.yml';
@@ -160,9 +160,8 @@ export async function generatePackageInfoFromArchiveBuffer(
contentType: string
): Promise<{ paths: string[]; packageInfo: ArchivePackage }> {
const assetsMap: AssetsBufferMap = {};
- const entries = await unpackBufferEntries(archiveBuffer, contentType);
const paths: string[] = [];
- entries.forEach(({ path: bufferPath, buffer }) => {
+ await traverseArchiveEntries(archiveBuffer, contentType, async ({ path: bufferPath, buffer }) => {
paths.push(bufferPath);
if (buffer && filterAssetPathForParseAndVerifyArchive(bufferPath)) {
assetsMap[bufferPath] = buffer;
diff --git a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts
index dd6321445df75..8f6f151383d5a 100644
--- a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts
+++ b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts
@@ -15,6 +15,7 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import { ASSETS_SAVED_OBJECT_TYPE } from '../../../../common';
import type {
+ ArchiveEntry,
InstallablePackage,
InstallSource,
PackageAssetReference,
@@ -24,7 +25,6 @@ import { PackageInvalidArchiveError, PackageNotFoundError } from '../../../error
import { appContextService } from '../../app_context';
import { setPackageInfo } from '.';
-import type { ArchiveEntry } from '.';
import { filterAssetPathForParseAndVerifyArchive, parseAndVerifyArchive } from './parse';
const ONE_BYTE = 1024 * 1024;
diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts
index a456734747324..5a4672f67fe53 100644
--- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts
@@ -16,14 +16,13 @@ import type {
PackageInfo,
} from '../../../../types';
import { getAssetFromAssetsMap, getPathParts } from '../../archive';
-import type { ArchiveEntry } from '../../archive';
import {
FLEET_FINAL_PIPELINE_CONTENT,
FLEET_FINAL_PIPELINE_ID,
FLEET_FINAL_PIPELINE_VERSION,
} from '../../../../constants';
import { getPipelineNameForDatastream } from '../../../../../common/services';
-import type { PackageInstallContext } from '../../../../../common/types';
+import type { ArchiveEntry, PackageInstallContext } from '../../../../../common/types';
import { appendMetadataToIngestPipeline } from '../meta';
import { retryTransientEsErrors } from '../retry';
diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/mappings.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/mappings.test.ts
index f34015bf77697..de962850fba8c 100644
--- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/mappings.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/mappings.test.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { createArchiveIteratorFromMap } from '../../archive/archive_iterator';
+
import { loadMappingForTransform } from './mappings';
describe('loadMappingForTransform', () => {
@@ -13,6 +15,7 @@ describe('loadMappingForTransform', () => {
{
packageInfo: {} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
},
'test'
@@ -49,6 +52,7 @@ describe('loadMappingForTransform', () => {
),
],
]),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [
'/package/ti_opencti/2.1.0/elasticsearch/transform/latest_ioc/fields/ecs.yml',
'/package/ti_opencti/2.1.0/elasticsearch/transform/latest_ioc/fields/ecs-extra.yml',
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
index 276478099daf8..bf5684f29c205 100644
--- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
@@ -325,16 +325,16 @@ export async function deleteKibanaAssetsAndReferencesForSpace({
await saveKibanaAssetsRefs(savedObjectsClient, pkgName, [], true);
}
+const kibanaAssetTypes = Object.values(KibanaAssetType);
+export const isKibanaAssetType = (path: string) => {
+ const parts = getPathParts(path);
+
+ return parts.service === 'kibana' && (kibanaAssetTypes as string[]).includes(parts.type);
+};
+
export function getKibanaAssets(
packageInstallContext: PackageInstallContext
): Record {
- const kibanaAssetTypes = Object.values(KibanaAssetType);
- const isKibanaAssetType = (path: string) => {
- const parts = getPathParts(path);
-
- return parts.service === 'kibana' && (kibanaAssetTypes as string[]).includes(parts.type);
- };
-
const result = Object.fromEntries(
kibanaAssetTypes.map((type) => [type, []])
) as Record;
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install_with_streaming.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install_with_streaming.ts
new file mode 100644
index 0000000000000..fca6cf27a0cd7
--- /dev/null
+++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install_with_streaming.ts
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+import type { SavedObject, SavedObjectsClientContract } from '@kbn/core/server';
+
+import type { Installation, PackageInstallContext } from '../../../../../common/types';
+import type { KibanaAssetReference, KibanaAssetType } from '../../../../types';
+import { getPathParts } from '../../archive';
+
+import { saveKibanaAssetsRefs } from '../../packages/install';
+
+import type { ArchiveAsset } from './install';
+import {
+ KibanaSavedObjectTypeMapping,
+ createSavedObjectKibanaAsset,
+ isKibanaAssetType,
+ toAssetReference,
+} from './install';
+import { getSpaceAwareSaveobjectsClients } from './saved_objects';
+
+interface InstallKibanaAssetsWithStreamingArgs {
+ pkgName: string;
+ packageInstallContext: PackageInstallContext;
+ spaceId: string;
+ savedObjectsClient: SavedObjectsClientContract;
+ installedPkg?: SavedObject | undefined;
+}
+
+const MAX_ASSETS_TO_INSTALL_IN_PARALLEL = 100;
+
+export async function installKibanaAssetsWithStreaming({
+ spaceId,
+ packageInstallContext,
+ savedObjectsClient,
+ pkgName,
+ installedPkg,
+}: InstallKibanaAssetsWithStreamingArgs): Promise {
+ const { archiveIterator } = packageInstallContext;
+
+ const { savedObjectClientWithSpace } = getSpaceAwareSaveobjectsClients(spaceId);
+
+ const assetRefs: KibanaAssetReference[] = [];
+ let batch: ArchiveAsset[] = [];
+
+ await archiveIterator.traverseEntries(async ({ path, buffer }) => {
+ if (!buffer || !isKibanaAssetType(path)) {
+ return;
+ }
+ const savedObject = JSON.parse(buffer.toString('utf8')) as ArchiveAsset;
+ const assetType = getPathParts(path).type as KibanaAssetType;
+ const soType = KibanaSavedObjectTypeMapping[assetType];
+ if (savedObject.type !== soType) {
+ return;
+ }
+
+ batch.push(savedObject);
+ assetRefs.push(toAssetReference(savedObject));
+
+ if (batch.length >= MAX_ASSETS_TO_INSTALL_IN_PARALLEL) {
+ await bulkCreateSavedObjects({
+ savedObjectsClient: savedObjectClientWithSpace,
+ kibanaAssets: batch,
+ refresh: false,
+ });
+ batch = [];
+ }
+ });
+
+ // install any remaining assets
+ if (batch.length) {
+ await bulkCreateSavedObjects({
+ savedObjectsClient: savedObjectClientWithSpace,
+ kibanaAssets: batch,
+ // Use wait_for with the last batch to ensure all assets are readable once the install is complete
+ refresh: 'wait_for',
+ });
+ }
+
+ // Update the installation saved object with installed kibana assets
+ await saveKibanaAssetsRefs(savedObjectsClient, pkgName, assetRefs);
+
+ return assetRefs;
+}
+
+async function bulkCreateSavedObjects({
+ savedObjectsClient,
+ kibanaAssets,
+ refresh,
+}: {
+ kibanaAssets: ArchiveAsset[];
+ savedObjectsClient: SavedObjectsClientContract;
+ refresh?: boolean | 'wait_for';
+}) {
+ if (!kibanaAssets.length) {
+ return [];
+ }
+
+ const toBeSavedObjects = kibanaAssets.map((asset) => createSavedObjectKibanaAsset(asset));
+
+ const { saved_objects: createdSavedObjects } = await savedObjectsClient.bulkCreate(
+ toBeSavedObjects,
+ {
+ // We only want to install new saved objects without overwriting existing ones
+ overwrite: false,
+ managed: true,
+ refresh,
+ }
+ );
+
+ return createdSavedObjects;
+}
diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts
index 661475dfadc09..a097db584b460 100644
--- a/x-pack/plugins/fleet/server/services/epm/package_service.ts
+++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts
@@ -39,7 +39,10 @@ import type { InstallResult } from '../../../common';
import { appContextService } from '..';
-import type { CustomPackageDatasetConfiguration, EnsurePackageResult } from './packages/install';
+import {
+ type CustomPackageDatasetConfiguration,
+ type EnsurePackageResult,
+} from './packages/install';
import type { FetchFindLatestPackageOptions } from './registry';
import { getPackageFieldsMetadata } from './registry';
@@ -56,6 +59,7 @@ import {
} from './packages';
import { generatePackageInfoFromArchiveBuffer } from './archive';
import { getEsPackage } from './archive/storage';
+import { createArchiveIteratorFromMap } from './archive/archive_iterator';
export type InstalledAssetType = EsAssetReference;
@@ -381,12 +385,14 @@ class PackageClientImpl implements PackageClient {
}
const { assetsMap } = esPackage;
+ const archiveIterator = createArchiveIteratorFromMap(assetsMap);
const { installedTransforms } = await installTransforms({
packageInstallContext: {
assetsMap,
packageInfo,
paths,
+ archiveIterator,
},
esClient: this.internalEsClient,
savedObjectsClient: this.internalSoClient,
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/assets.ts
index a82b5c0d103b2..3bb84c0d23163 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/assets.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/assets.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
+import type { ArchiveEntry } from '../../../../common/types';
import type { AssetsMap, PackageInfo } from '../../../types';
import { getAssetFromAssetsMap } from '../archive';
-import type { ArchiveEntry } from '../archive';
const maybeFilterByDataset =
(packageInfo: Pick, datasetName: string) =>
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
index 2dc295762e33a..5711c8fcccaf4 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
@@ -27,6 +27,8 @@ import { auditLoggingService } from '../../audit_logging';
import * as Registry from '../registry';
+import { createArchiveIteratorFromMap } from '../archive/archive_iterator';
+
import { getInstalledPackages, getPackageInfo, getPackages, getPackageUsageStats } from './get';
jest.mock('../registry');
@@ -915,6 +917,7 @@ owner: elastic`,
MockRegistry.getPackage.mockResolvedValue({
paths: [],
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
packageInfo: {
name: 'my-package',
version: '1.0.0',
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts
index 709e0d84d70fc..6b3a31eda649e 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts
@@ -442,6 +442,24 @@ describe('install', () => {
expect(response.status).toEqual('installed');
});
+
+ it('should use streaming installation for the detection rules package', async () => {
+ jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true);
+
+ const response = await installPackage({
+ spaceId: DEFAULT_SPACE_ID,
+ installSource: 'registry',
+ pkgkey: 'security_detection_engine',
+ savedObjectsClient: savedObjectsClientMock.create(),
+ esClient: {} as ElasticsearchClient,
+ });
+
+ expect(response.error).toBeUndefined();
+
+ expect(installStateMachine._stateMachineInstallPackage).toHaveBeenCalledWith(
+ expect.objectContaining({ useStreaming: true })
+ );
+ });
});
describe('upload', () => {
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts
index 1ea6f29cad839..ebe5acc35178d 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts
@@ -76,6 +76,7 @@ import {
deleteVerificationResult,
unpackBufferToAssetsMap,
} from '../archive';
+import { createArchiveIteratorFromMap } from '../archive/archive_iterator';
import { toAssetReference } from '../kibana/assets/install';
import type { ArchiveAsset } from '../kibana/assets/install';
import type { PackageUpdateEvent } from '../../upgrade_sender';
@@ -107,6 +108,12 @@ import { removeInstallation } from './remove';
export const UPLOAD_RETRY_AFTER_MS = 10000; // 10s
const MAX_ENSURE_INSTALL_TIME = 60 * 1000;
+const PACKAGES_TO_INSTALL_WITH_STREAMING = [
+ // The security_detection_engine package contains a large number of assets and
+ // is not suitable for regular installation as it might cause OOM errors.
+ 'security_detection_engine',
+];
+
export async function isPackageInstalled(options: {
savedObjectsClient: SavedObjectsClientContract;
pkgName: string;
@@ -449,6 +456,7 @@ async function installPackageFromRegistry({
// TODO: change epm API to /packageName/version so we don't need to do this
const { pkgName, pkgVersion: version } = Registry.splitPkgKey(pkgkey);
let pkgVersion = version ?? '';
+ const useStreaming = PACKAGES_TO_INSTALL_WITH_STREAMING.includes(pkgName);
// if an error happens during getInstallType, report that we don't know
let installType: InstallType = 'unknown';
@@ -478,11 +486,12 @@ async function installPackageFromRegistry({
}
// get latest package version and requested version in parallel for performance
- const [latestPackage, { paths, packageInfo, assetsMap, verificationResult }] =
+ const [latestPackage, { paths, packageInfo, assetsMap, archiveIterator, verificationResult }] =
await Promise.all([
latestPkg ? Promise.resolve(latestPkg) : queryLatest(),
Registry.getPackage(pkgName, pkgVersion, {
ignoreUnverified: force && !neverIgnoreVerificationError,
+ useStreaming,
}),
]);
@@ -490,6 +499,7 @@ async function installPackageFromRegistry({
packageInfo,
assetsMap,
paths,
+ archiveIterator,
};
// let the user install if using the force flag or needing to reinstall or install a previous version due to failed update
@@ -542,6 +552,7 @@ async function installPackageFromRegistry({
ignoreMappingUpdateErrors,
skipDataStreamRollover,
retryFromLastState,
+ useStreaming,
});
} catch (e) {
sendEvent({
@@ -580,6 +591,7 @@ async function installPackageWithStateMachine(options: {
ignoreMappingUpdateErrors?: boolean;
skipDataStreamRollover?: boolean;
retryFromLastState?: boolean;
+ useStreaming?: boolean;
}): Promise {
const packageInfo = options.packageInstallContext.packageInfo;
@@ -599,6 +611,7 @@ async function installPackageWithStateMachine(options: {
skipDataStreamRollover,
packageInstallContext,
retryFromLastState,
+ useStreaming,
} = options;
let { telemetryEvent } = options;
const logger = appContextService.getLogger();
@@ -696,6 +709,7 @@ async function installPackageWithStateMachine(options: {
ignoreMappingUpdateErrors,
skipDataStreamRollover,
retryFromLastState,
+ useStreaming,
})
.then(async (assets) => {
logger.debug(`Removing old assets from previous versions of ${pkgName}`);
@@ -785,6 +799,7 @@ async function installPackageByUpload({
}
const { packageInfo } = await generatePackageInfoFromArchiveBuffer(archiveBuffer, contentType);
const pkgName = packageInfo.name;
+ const useStreaming = PACKAGES_TO_INSTALL_WITH_STREAMING.includes(pkgName);
// Allow for overriding the version in the manifest for cases where we install
// stack-aligned bundled packages to support special cases around the
@@ -807,17 +822,17 @@ async function installPackageByUpload({
packageInfo,
});
- const { assetsMap, paths } = await unpackBufferToAssetsMap({
- name: packageInfo.name,
- version: pkgVersion,
+ const { paths, assetsMap, archiveIterator } = await unpackBufferToAssetsMap({
archiveBuffer,
contentType,
+ useStreaming,
});
const packageInstallContext: PackageInstallContext = {
packageInfo: { ...packageInfo, version: pkgVersion },
assetsMap,
paths,
+ archiveIterator,
};
// update the timestamp of latest installation
setLastUploadInstallCache();
@@ -837,6 +852,7 @@ async function installPackageByUpload({
authorizationHeader,
ignoreMappingUpdateErrors,
skipDataStreamRollover,
+ useStreaming,
});
} catch (e) {
return {
@@ -1004,12 +1020,14 @@ export async function installCustomPackage(
acc.set(asset.path, asset.content);
return acc;
}, new Map());
- const paths = [...assetsMap.keys()];
+ const paths = assets.map((asset) => asset.path);
+ const archiveIterator = createArchiveIteratorFromMap(assetsMap);
const packageInstallContext: PackageInstallContext = {
assetsMap,
paths,
packageInfo,
+ archiveIterator,
};
return await installPackageWithStateMachine({
packageInstallContext,
@@ -1341,16 +1359,20 @@ export async function installAssetsForInputPackagePolicy(opts: {
ignoreUnverified: force,
});
+ const archiveIterator = createArchiveIteratorFromMap(pkg.assetsMap);
packageInstallContext = {
assetsMap: pkg.assetsMap,
packageInfo: pkg.packageInfo,
paths: pkg.paths,
+ archiveIterator,
};
} else {
+ const archiveIterator = createArchiveIteratorFromMap(installedPkgWithAssets.assetsMap);
packageInstallContext = {
assetsMap: installedPkgWithAssets.assetsMap,
packageInfo: installedPkgWithAssets.packageInfo,
paths: installedPkgWithAssets.paths,
+ archiveIterator,
};
}
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.test.ts
index 174076a9e9b1b..73b78a6cc4aa0 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.test.ts
@@ -38,6 +38,8 @@ import { updateCurrentWriteIndices } from '../../elasticsearch/template/template
import { installIndexTemplatesAndPipelines } from '../install_index_template_pipeline';
+import { createArchiveIteratorFromMap } from '../../archive/archive_iterator';
+
import { handleState } from './state_machine';
import { _stateMachineInstallPackage } from './_state_machine_package_install';
import { cleanupLatestExecutedState } from './steps';
@@ -110,6 +112,7 @@ describe('_stateMachineInstallPackage', () => {
logger: loggerMock.create(),
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -172,6 +175,7 @@ describe('_stateMachineInstallPackage', () => {
logger: loggerMock.create(),
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -208,6 +212,7 @@ describe('_stateMachineInstallPackage', () => {
logger: loggerMock.create(),
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -257,6 +262,7 @@ describe('_stateMachineInstallPackage', () => {
logger: loggerMock.create(),
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -336,6 +342,7 @@ describe('_stateMachineInstallPackage', () => {
owner: { github: 'elastic/fleet' },
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
},
installType: 'install',
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts
index 1f10d40feba38..c941b6d60d63b 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/_state_machine_package_install.ts
@@ -48,11 +48,13 @@ import {
updateLatestExecutedState,
cleanupLatestExecutedState,
cleanUpKibanaAssetsStep,
+ cleanUpUnusedKibanaAssetsStep,
cleanupILMPoliciesStep,
cleanUpMlModelStep,
cleanupIndexTemplatePipelinesStep,
cleanupTransformsStep,
cleanupArchiveEntriesStep,
+ stepInstallKibanaAssetsWithStreaming,
} from './steps';
import type { StateMachineDefinition, StateMachineStates } from './state_machine';
import { handleState } from './state_machine';
@@ -73,6 +75,7 @@ export interface InstallContext extends StateContext {
skipDataStreamRollover?: boolean;
retryFromLastState?: boolean;
initialState?: INSTALL_STATES;
+ useStreaming?: boolean;
indexTemplates?: IndexTemplateEntry[];
packageAssetRefs?: PackageAssetReference[];
@@ -83,7 +86,7 @@ export interface InstallContext extends StateContext {
/**
* This data structure defines the sequence of the states and the transitions
*/
-const statesDefinition: StateMachineStates = {
+const regularStatesDefinition: StateMachineStates = {
create_restart_installation: {
nextState: INSTALL_STATES.INSTALL_KIBANA_ASSETS,
onTransition: stepCreateRestartInstallation,
@@ -152,6 +155,31 @@ const statesDefinition: StateMachineStates = {
},
};
+const streamingStatesDefinition: StateMachineStates = {
+ create_restart_installation: {
+ nextState: INSTALL_STATES.INSTALL_KIBANA_ASSETS,
+ onTransition: stepCreateRestartInstallation,
+ onPostTransition: updateLatestExecutedState,
+ },
+ install_kibana_assets: {
+ onTransition: stepInstallKibanaAssetsWithStreaming,
+ nextState: INSTALL_STATES.SAVE_ARCHIVE_ENTRIES,
+ onPostTransition: updateLatestExecutedState,
+ },
+ save_archive_entries_from_assets_map: {
+ onPreTransition: cleanupArchiveEntriesStep,
+ onTransition: stepSaveArchiveEntries,
+ nextState: INSTALL_STATES.UPDATE_SO,
+ onPostTransition: updateLatestExecutedState,
+ },
+ update_so: {
+ onPreTransition: cleanUpUnusedKibanaAssetsStep,
+ onTransition: stepSaveSystemObject,
+ nextState: 'end',
+ onPostTransition: updateLatestExecutedState,
+ },
+};
+
/*
* _stateMachineInstallPackage installs packages using the generic state machine in ./state_machine
* installStates is the data structure providing the state machine definition
@@ -166,6 +194,10 @@ export async function _stateMachineInstallPackage(
const logger = appContextService.getLogger();
let initialState = INSTALL_STATES.CREATE_RESTART_INSTALLATION;
+ const statesDefinition = context.useStreaming
+ ? streamingStatesDefinition
+ : regularStatesDefinition;
+
// if retryFromLastState, restart install from last install state
// if force is passed, the install should be executed from the beginning
if (retryFromLastState && !force && installedPkg?.attributes?.latest_executed_state?.name) {
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.test.ts
index 2b653728d6574..e5a7fed55fe87 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.test.ts
@@ -31,6 +31,8 @@ import { auditLoggingService } from '../../../../audit_logging';
import { restartInstallation, createInstallation } from '../../install';
import type { Installation } from '../../../../../../common';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepCreateRestartInstallation } from './step_create_restart_installation';
jest.mock('../../../../audit_logging');
@@ -84,6 +86,7 @@ describe('stepCreateRestartInstallation', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -120,6 +123,7 @@ describe('stepCreateRestartInstallation', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -164,6 +168,7 @@ describe('stepCreateRestartInstallation', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -208,6 +213,7 @@ describe('stepCreateRestartInstallation', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.test.ts
index 7d8a251433bb5..06201770ee2e2 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.test.ts
@@ -24,6 +24,8 @@ import {
deletePreviousPipelines,
} from '../../../elasticsearch/ingest_pipeline';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepDeletePreviousPipelines } from './step_delete_previous_pipelines';
jest.mock('../../../elasticsearch/ingest_pipeline');
@@ -84,6 +86,7 @@ describe('stepDeletePreviousPipelines', () => {
owner: { github: 'elastic/fleet' },
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
appContextService.start(
@@ -276,6 +279,7 @@ describe('stepDeletePreviousPipelines', () => {
owner: { github: 'elastic/fleet' },
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
appContextService.start(
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_ilm_policies.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_ilm_policies.test.ts
index 2cf9b23bb9adb..4c106a0c68f15 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_ilm_policies.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_ilm_policies.test.ts
@@ -24,6 +24,8 @@ import { installIlmForDataStream } from '../../../elasticsearch/datastream_ilm/i
import { ElasticsearchAssetType } from '../../../../../types';
import { deleteILMPolicies, deletePrerequisiteAssets } from '../../remove';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepInstallILMPolicies, cleanupILMPoliciesStep } from './step_install_ilm_policies';
jest.mock('../../../archive/storage');
@@ -56,6 +58,7 @@ const packageInstallContext = {
owner: { github: 'elastic/fleet' },
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
let soClient: jest.Mocked;
@@ -239,6 +242,7 @@ describe('stepInstallILMPolicies', () => {
owner: { github: 'elastic/fleet' },
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
},
installType: 'install',
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.test.ts
index d258747edc6ef..1c368cfd998d3 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.test.ts
@@ -37,6 +37,8 @@ const mockDeletePrerequisiteAssets = deletePrerequisiteAssets as jest.MockedFunc
typeof deletePrerequisiteAssets
>;
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import {
stepInstallIndexTemplatePipelines,
cleanupIndexTemplatePipelinesStep,
@@ -122,6 +124,7 @@ describe('stepInstallIndexTemplatePipelines', () => {
owner: { github: 'elastic/fleet' },
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
appContextService.start(
@@ -281,6 +284,7 @@ describe('stepInstallIndexTemplatePipelines', () => {
],
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
appContextService.start(
@@ -431,6 +435,7 @@ describe('stepInstallIndexTemplatePipelines', () => {
],
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
appContextService.start(
@@ -521,6 +526,7 @@ describe('stepInstallIndexTemplatePipelines', () => {
],
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
appContextService.start(
@@ -574,6 +580,7 @@ describe('stepInstallIndexTemplatePipelines', () => {
owner: { github: 'elastic/fleet' },
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
appContextService.start(
@@ -647,6 +654,7 @@ describe('cleanupIndexTemplatePipelinesStep', () => {
],
} as any,
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
};
const mockInstalledPackageSo: SavedObject = {
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.test.ts
index 52c93c61c16e1..cf9d953868b6a 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.test.ts
@@ -23,8 +23,25 @@ import { deleteKibanaAssets } from '../../remove';
import { KibanaSavedObjectType, type Installation } from '../../../../../types';
-import { stepInstallKibanaAssets, cleanUpKibanaAssetsStep } from './step_install_kibana_assets';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+import {
+ stepInstallKibanaAssets,
+ cleanUpKibanaAssetsStep,
+ stepInstallKibanaAssetsWithStreaming,
+ cleanUpUnusedKibanaAssetsStep,
+} from './step_install_kibana_assets';
+
+jest.mock('../../../kibana/assets/saved_objects', () => {
+ return {
+ getSpaceAwareSaveobjectsClients: jest.fn().mockReturnValue({
+ savedObjectClientWithSpace: jest.fn(),
+ savedObjectsImporter: jest.fn(),
+ savedObjectTagAssignmentService: jest.fn(),
+ savedObjectTagClient: jest.fn(),
+ }),
+ };
+});
jest.mock('../../../kibana/assets/install');
jest.mock('../../remove', () => {
return {
@@ -58,6 +75,7 @@ const packageInstallContext = {
} as any,
paths: ['some/path/1', 'some/path/2'],
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
};
describe('stepInstallKibanaAssets', () => {
@@ -82,6 +100,7 @@ describe('stepInstallKibanaAssets', () => {
logger: loggerMock.create(),
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -102,7 +121,7 @@ describe('stepInstallKibanaAssets', () => {
});
await expect(installationPromise).resolves.not.toThrowError();
- expect(mockedInstallKibanaAssetsAndReferencesMultispace).toBeCalledTimes(1);
+ expect(installKibanaAssetsAndReferencesMultispace).toBeCalledTimes(1);
});
esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
appContextService.start(createAppContextStartContractMock());
@@ -121,6 +140,7 @@ describe('stepInstallKibanaAssets', () => {
logger: loggerMock.create(),
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -144,6 +164,60 @@ describe('stepInstallKibanaAssets', () => {
});
});
+describe('stepInstallKibanaAssetsWithStreaming', () => {
+ beforeEach(async () => {
+ soClient = savedObjectsClientMock.create();
+ esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
+ appContextService.start(createAppContextStartContractMock());
+ });
+
+ it('should rely on archiveIterator instead of in-memory assetsMap', async () => {
+ const assetsMap = new Map();
+ assetsMap.get = jest.fn();
+ assetsMap.set = jest.fn();
+
+ const archiveIterator = {
+ traverseEntries: jest.fn(),
+ getPaths: jest.fn(),
+ };
+
+ const result = await stepInstallKibanaAssetsWithStreaming({
+ savedObjectsClient: soClient,
+ esClient,
+ logger: loggerMock.create(),
+ packageInstallContext: {
+ assetsMap,
+ archiveIterator,
+ paths: [],
+ packageInfo: {
+ title: 'title',
+ name: 'xyz',
+ version: '4.5.6',
+ description: 'test',
+ type: 'integration',
+ categories: ['cloud', 'custom'],
+ format_version: 'string',
+ release: 'experimental',
+ conditions: { kibana: { version: 'x.y.z' } },
+ owner: { github: 'elastic/fleet' },
+ },
+ },
+ installType: 'install',
+ installSource: 'registry',
+ spaceId: DEFAULT_SPACE_ID,
+ });
+
+ expect(result).toEqual({ installedKibanaAssetsRefs: [] });
+
+ // Verify that assetsMap was not used
+ expect(assetsMap.get).not.toBeCalled();
+ expect(assetsMap.set).not.toBeCalled();
+
+ // Verify that archiveIterator was used
+ expect(archiveIterator.traverseEntries).toBeCalled();
+ });
+});
+
describe('cleanUpKibanaAssetsStep', () => {
const mockInstalledPackageSo: SavedObject = {
id: 'mocked-package',
@@ -302,3 +376,84 @@ describe('cleanUpKibanaAssetsStep', () => {
expect(mockedDeleteKibanaAssets).not.toBeCalled();
});
});
+
+describe('cleanUpUnusedKibanaAssetsStep', () => {
+ const mockInstalledPackageSo: SavedObject = {
+ id: 'mocked-package',
+ attributes: {
+ name: 'test-package',
+ version: '1.0.0',
+ install_status: 'installing',
+ install_version: '1.0.0',
+ install_started_at: new Date().toISOString(),
+ install_source: 'registry',
+ verification_status: 'verified',
+ installed_kibana: [] as any,
+ installed_es: [] as any,
+ es_index_patterns: {},
+ },
+ type: PACKAGES_SAVED_OBJECT_TYPE,
+ references: [],
+ };
+
+ const installationContext = {
+ savedObjectsClient: soClient,
+ savedObjectsImporter: jest.fn(),
+ esClient,
+ logger: loggerMock.create(),
+ packageInstallContext,
+ installType: 'install' as const,
+ installSource: 'registry' as const,
+ spaceId: DEFAULT_SPACE_ID,
+ retryFromLastState: true,
+ initialState: 'install_kibana_assets' as any,
+ };
+
+ beforeEach(async () => {
+ soClient = savedObjectsClientMock.create();
+ esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
+ appContextService.start(createAppContextStartContractMock());
+ });
+
+ it('should not clean up assets if they all present in the new package', async () => {
+ const installedAssets = [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }];
+ await cleanUpUnusedKibanaAssetsStep({
+ ...installationContext,
+ installedPkg: {
+ ...mockInstalledPackageSo,
+ attributes: {
+ ...mockInstalledPackageSo.attributes,
+ installed_kibana: installedAssets,
+ },
+ },
+ installedKibanaAssetsRefs: installedAssets,
+ });
+
+ expect(mockedDeleteKibanaAssets).not.toBeCalled();
+ });
+
+ it('should clean up assets that are not present in the new package', async () => {
+ const installedAssets = [
+ { type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' },
+ { type: KibanaSavedObjectType.dashboard, id: 'dashboard-2' },
+ ];
+ const newAssets = [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }];
+ await cleanUpUnusedKibanaAssetsStep({
+ ...installationContext,
+ installedPkg: {
+ ...mockInstalledPackageSo,
+ attributes: {
+ ...mockInstalledPackageSo.attributes,
+ installed_kibana: installedAssets,
+ },
+ },
+ installedKibanaAssetsRefs: newAssets,
+ });
+
+ expect(mockedDeleteKibanaAssets).toBeCalledWith({
+ installedObjects: [installedAssets[1]],
+ spaceId: 'default',
+ packageInfo: packageInstallContext.packageInfo,
+ });
+ });
+});
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts
index b5a1fff91d3b8..aabd23f2eb9cc 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts
@@ -11,7 +11,9 @@ import { withPackageSpan } from '../../utils';
import type { InstallContext } from '../_state_machine_package_install';
import { deleteKibanaAssets } from '../../remove';
+import type { KibanaAssetReference } from '../../../../../../common/types';
import { INSTALL_STATES } from '../../../../../../common/types';
+import { installKibanaAssetsWithStreaming } from '../../../kibana/assets/install_with_streaming';
export async function stepInstallKibanaAssets(context: InstallContext) {
const { savedObjectsClient, logger, installedPkg, packageInstallContext, spaceId } = context;
@@ -37,6 +39,26 @@ export async function stepInstallKibanaAssets(context: InstallContext) {
return { kibanaAssetPromise };
}
+export async function stepInstallKibanaAssetsWithStreaming(context: InstallContext) {
+ const { savedObjectsClient, installedPkg, packageInstallContext, spaceId } = context;
+ const { packageInfo } = packageInstallContext;
+ const { name: pkgName } = packageInfo;
+
+ const installedKibanaAssetsRefs = await withPackageSpan(
+ 'Install Kibana assets with streaming',
+ () =>
+ installKibanaAssetsWithStreaming({
+ savedObjectsClient,
+ pkgName,
+ packageInstallContext,
+ installedPkg,
+ spaceId,
+ })
+ );
+
+ return { installedKibanaAssetsRefs };
+}
+
export async function cleanUpKibanaAssetsStep(context: InstallContext) {
const {
logger,
@@ -65,3 +87,44 @@ export async function cleanUpKibanaAssetsStep(context: InstallContext) {
});
}
}
+
+/**
+ * Cleans up Kibana assets that are no longer in the package. As opposite to
+ * `cleanUpKibanaAssetsStep`, this one is used after the package assets are
+ * installed.
+ *
+ * This function compares the currently installed Kibana assets with the assets
+ * in the previous package and removes any assets that are no longer present in the
+ * new installation.
+ *
+ */
+export async function cleanUpUnusedKibanaAssetsStep(context: InstallContext) {
+ const { logger, installedPkg, packageInstallContext, spaceId, installedKibanaAssetsRefs } =
+ context;
+ const { packageInfo } = packageInstallContext;
+
+ if (!installedKibanaAssetsRefs) {
+ return;
+ }
+
+ logger.debug('Clean up Kibana assets that are no longer in the package');
+
+ // Get the assets installed by the previous package
+ const previousAssetRefs = installedPkg?.attributes.installed_kibana ?? [];
+
+ // Remove any assets that are not in the new package
+ const nextAssetRefKeys = new Set(
+ installedKibanaAssetsRefs.map((asset: KibanaAssetReference) => `${asset.id}-${asset.type}`)
+ );
+ const assetsToRemove = previousAssetRefs.filter(
+ (existingAsset) => !nextAssetRefKeys.has(`${existingAsset.id}-${existingAsset.type}`)
+ );
+
+ if (assetsToRemove.length === 0) {
+ return;
+ }
+
+ await withPackageSpan('Clean up Kibana assets that are no longer in the package', async () => {
+ await deleteKibanaAssets({ installedObjects: assetsToRemove, spaceId, packageInfo });
+ });
+}
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_mlmodel.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_mlmodel.test.ts
index 1afb436eb4361..df939f3a458b6 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_mlmodel.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_mlmodel.test.ts
@@ -22,6 +22,8 @@ import { createAppContextStartContractMock } from '../../../../../mocks';
import { installMlModel } from '../../../elasticsearch/ml_model';
import { deleteMLModels, deletePrerequisiteAssets } from '../../remove';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepInstallMlModel, cleanUpMlModelStep } from './step_install_mlmodel';
jest.mock('../../../elasticsearch/ml_model');
@@ -53,6 +55,7 @@ const packageInstallContext = {
} as any,
paths: ['some/path/1', 'some/path/2'],
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
};
let soClient: jest.Mocked;
let esClient: jest.Mocked;
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_transforms.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_transforms.test.ts
index 1ac2383950b05..3bf07d52c6cbf 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_transforms.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_transforms.test.ts
@@ -22,6 +22,8 @@ import { createAppContextStartContractMock } from '../../../../../mocks';
import { installTransforms } from '../../../elasticsearch/transform/install';
import { cleanupTransforms } from '../../remove';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepInstallTransforms, cleanupTransformsStep } from './step_install_transforms';
jest.mock('../../../elasticsearch/transform/install');
@@ -52,6 +54,7 @@ const packageInstallContext = {
} as any,
paths: ['some/path/1', 'some/path/2'],
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
};
describe('stepInstallTransforms', () => {
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.test.ts
index 39e7159596ba8..7fa00a1c57f57 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.test.ts
@@ -24,6 +24,8 @@ import { appContextService } from '../../../../app_context';
import { createAppContextStartContractMock } from '../../../../../mocks';
import { removeLegacyTemplates } from '../../../elasticsearch/template/remove_legacy';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepRemoveLegacyTemplates } from './step_remove_legacy_templates';
jest.mock('../../../elasticsearch/template/remove_legacy');
@@ -82,6 +84,7 @@ describe('stepRemoveLegacyTemplates', () => {
} as any,
paths: ['some/path/1', 'some/path/2'],
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
};
appContextService.start(
createAppContextStartContractMock({
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.test.ts
index b03c146640488..255572d57cf49 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.test.ts
@@ -21,6 +21,8 @@ import { appContextService } from '../../../../app_context';
import { createAppContextStartContractMock } from '../../../../../mocks';
import { saveArchiveEntriesFromAssetsMap, removeArchiveEntries } from '../../../archive/storage';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepSaveArchiveEntries, cleanupArchiveEntriesStep } from './step_save_archive_entries';
jest.mock('../../../archive/storage', () => {
@@ -60,6 +62,7 @@ const packageInstallContext = {
Buffer.from('{"content": "data"}'),
],
]),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
};
const getMockInstalledPackageSo = (
installedEs: EsAssetReference[] = []
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.ts
index b0d5bb67627a6..7db44bb243f85 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_archive_entries.ts
@@ -14,17 +14,32 @@ import { withPackageSpan } from '../../utils';
import type { InstallContext } from '../_state_machine_package_install';
import { INSTALL_STATES } from '../../../../../../common/types';
+import { MANIFEST_NAME } from '../../../archive/parse';
export async function stepSaveArchiveEntries(context: InstallContext) {
- const { packageInstallContext, savedObjectsClient, installSource } = context;
+ const { packageInstallContext, savedObjectsClient, installSource, useStreaming } = context;
- const { packageInfo } = packageInstallContext;
+ const { packageInfo, archiveIterator } = packageInstallContext;
+
+ let assetsMap = packageInstallContext?.assetsMap;
+ let paths = packageInstallContext?.paths;
+ // For stream based installations, we don't want to save any assets but
+ // manifest.yaml due to the large number of assets in the package.
+ if (useStreaming) {
+ assetsMap = new Map();
+ await archiveIterator.traverseEntries(async (entry) => {
+ if (entry.path.endsWith(MANIFEST_NAME)) {
+ assetsMap.set(entry.path, entry.buffer);
+ }
+ });
+ paths = Array.from(assetsMap.keys());
+ }
const packageAssetResults = await withPackageSpan('Update archive entries', () =>
saveArchiveEntriesFromAssetsMap({
savedObjectsClient,
- assetsMap: packageInstallContext?.assetsMap,
- paths: packageInstallContext?.paths,
+ assetsMap,
+ paths,
packageInfo,
installSource,
})
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_system_object.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_system_object.test.ts
index aecdd0b2552c4..8d80c236aefb0 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_system_object.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_save_system_object.test.ts
@@ -21,6 +21,8 @@ import { createAppContextStartContractMock } from '../../../../../mocks';
import { auditLoggingService } from '../../../../audit_logging';
import { packagePolicyService } from '../../../../package_policy';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepSaveSystemObject } from './step_save_system_object';
jest.mock('../../../../audit_logging');
@@ -67,6 +69,7 @@ describe('updateLatestExecutedState', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -133,6 +136,7 @@ describe('updateLatestExecutedState', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_update_current_write_indices.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_update_current_write_indices.test.ts
index c7f3c040b7966..017805d34efef 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_update_current_write_indices.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_update_current_write_indices.test.ts
@@ -22,6 +22,8 @@ import { appContextService } from '../../../../app_context';
import { createAppContextStartContractMock } from '../../../../../mocks';
import { updateCurrentWriteIndices } from '../../../elasticsearch/template/template';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { stepUpdateCurrentWriteIndices } from './step_update_current_write_indices';
jest.mock('../../../elasticsearch/template/template');
@@ -86,6 +88,7 @@ describe('stepUpdateCurrentWriteIndices', () => {
} as any,
paths: ['some/path/1', 'some/path/2'],
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
};
appContextService.start(
createAppContextStartContractMock({
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.test.ts
index d963e5fea44c9..aea879aba5479 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.test.ts
@@ -32,6 +32,8 @@ import { auditLoggingService } from '../../../../audit_logging';
import type { PackagePolicySOAttributes } from '../../../../../types';
+import { createArchiveIteratorFromMap } from '../../../archive/archive_iterator';
+
import { updateLatestExecutedState } from './update_latest_executed_state';
jest.mock('../../../../audit_logging');
@@ -61,6 +63,7 @@ describe('updateLatestExecutedState', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -116,6 +119,7 @@ describe('updateLatestExecutedState', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -153,6 +157,7 @@ describe('updateLatestExecutedState', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
@@ -198,6 +203,7 @@ describe('updateLatestExecutedState', () => {
logger,
packageInstallContext: {
assetsMap: new Map(),
+ archiveIterator: createArchiveIteratorFromMap(new Map()),
paths: [],
packageInfo: {
title: 'title',
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts
index ac3f5def5d09c..3892eaa951e5f 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts
@@ -148,6 +148,7 @@ export async function deleteKibanaAssets({
const namespace = SavedObjectsUtils.namespaceStringToId(spaceId);
+ // TODO this should be the installed package info, not the package that is being installed
const minKibana = packageInfo.conditions?.kibana?.version
? minVersion(packageInfo.conditions.kibana.version)
: null;
diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts
index bb4d612aa7de3..75b9869d0a7c6 100644
--- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts
+++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts
@@ -54,6 +54,8 @@ import { resolveDataStreamFields, resolveDataStreamsMap, withPackageSpan } from
import { verifyPackageArchiveSignature } from '../packages/package_verification';
+import type { ArchiveIterator } from '../../../../common/types';
+
import { fetchUrl, getResponse, getResponseStream } from './requests';
import { getRegistryUrl } from './registry_url';
@@ -309,11 +311,12 @@ async function getPackageInfoFromArchiveOrCache(
export async function getPackage(
name: string,
version: string,
- options?: { ignoreUnverified?: boolean }
+ options?: { ignoreUnverified?: boolean; useStreaming?: boolean }
): Promise<{
paths: string[];
packageInfo: ArchivePackage;
assetsMap: AssetsMap;
+ archiveIterator: ArchiveIterator;
verificationResult?: PackageVerificationResult;
}> {
const verifyPackage = appContextService.getExperimentalFeatures().packageVerification;
@@ -340,18 +343,18 @@ export async function getPackage(
setVerificationResult({ name, version }, latestVerificationResult);
}
- const { assetsMap, paths } = await unpackBufferToAssetsMap({
- name,
- version,
+ const contentType = ensureContentType(archivePath);
+ const { paths, assetsMap, archiveIterator } = await unpackBufferToAssetsMap({
archiveBuffer,
- contentType: ensureContentType(archivePath),
+ contentType,
+ useStreaming: options?.useStreaming,
});
if (!packageInfo) {
packageInfo = await getPackageInfoFromArchiveOrCache(name, version, archiveBuffer, archivePath);
}
- return { paths, packageInfo, assetsMap, verificationResult };
+ return { paths, packageInfo, assetsMap, archiveIterator, verificationResult };
}
export async function getPackageFieldsMetadata(
@@ -397,7 +400,7 @@ export async function getPackageFieldsMetadata(
}
}
-function ensureContentType(archivePath: string) {
+export function ensureContentType(archivePath: string) {
const contentType = mime.lookup(archivePath);
if (!contentType) {
diff --git a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts
index edf31991634b9..cd1c26942aa0c 100644
--- a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts
+++ b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts
@@ -30,6 +30,7 @@ import {
applyDocOnlyValueToMapping,
forEachMappings,
} from '../experimental_datastream_features_helper';
+import { createArchiveIteratorFromMap } from '../epm/archive/archive_iterator';
export async function handleExperimentalDatastreamFeatureOptIn({
soClient,
@@ -75,6 +76,7 @@ export async function handleExperimentalDatastreamFeatureOptIn({
return prepareTemplate({
packageInstallContext: {
assetsMap,
+ archiveIterator: createArchiveIteratorFromMap(assetsMap),
packageInfo,
paths,
},
diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts
index bc5bce9eea2a3..48dc3956d6984 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.ts
@@ -480,22 +480,21 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
user?: AuthenticatedUser;
bumpRevision?: boolean;
force?: true;
+ asyncDeploy?: boolean;
}
): Promise<{
created: PackagePolicy[];
failed: Array<{ packagePolicy: NewPackagePolicy; error?: Error | SavedObjectError }>;
}> {
- const useSpaceAwareness = await isSpaceAwarenessEnabled();
- const savedObjectType = await getPackagePolicySavedObjectType();
- for (const packagePolicy of packagePolicies) {
+ const [useSpaceAwareness, savedObjectType, packageInfos] = await Promise.all([
+ isSpaceAwarenessEnabled(),
+ getPackagePolicySavedObjectType(),
+ getPackageInfoForPackagePolicies(packagePolicies, soClient),
+ ]);
+
+ await pMap(packagePolicies, async (packagePolicy) => {
const basePkgInfo = packagePolicy.package
- ? await getPackageInfo({
- savedObjectsClient: soClient,
- pkgName: packagePolicy.package.name,
- pkgVersion: packagePolicy.package.version,
- ignoreUnverified: true,
- prerelease: true,
- })
+ ? packageInfos.get(`${packagePolicy.package.name}-${packagePolicy.package.version}`)
: undefined;
if (!packagePolicy.id) {
packagePolicy.id = SavedObjectsUtils.generateId();
@@ -508,7 +507,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
this.keepPolicyIdInSync(packagePolicy);
await preflightCheckPackagePolicy(soClient, packagePolicy, basePkgInfo);
- }
+ });
const agentPolicyIds = new Set(packagePolicies.flatMap((pkgPolicy) => pkgPolicy.policy_ids));
@@ -528,8 +527,6 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
}
- const packageInfos = await getPackageInfoForPackagePolicies(packagePolicies, soClient);
-
const isoDate = new Date().toISOString();
const policiesToCreate: Array> = [];
@@ -665,6 +662,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
for (const agentPolicyId of agentPolicyIds) {
await agentPolicyService.bumpRevision(soClient, esClient, agentPolicyId, {
user: options?.user,
+ asyncDeploy: options?.asyncDeploy,
});
}
}
@@ -1176,7 +1174,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
packagePolicyUpdates: Array,
- options?: { user?: AuthenticatedUser; force?: boolean }
+ options?: { user?: AuthenticatedUser; force?: boolean; asyncDeploy?: boolean }
): Promise<{
updatedPolicies: PackagePolicy[] | null;
failedPolicies: Array<{
@@ -1347,6 +1345,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
await agentPolicyService.bumpRevision(soClient, esClient, agentPolicyId, {
user: options?.user,
removeProtection,
+ asyncDeploy: options?.asyncDeploy,
});
});
@@ -2368,6 +2367,7 @@ class PackagePolicyClientWithAuthz extends PackagePolicyClientImpl {
user?: AuthenticatedUser | undefined;
bumpRevision?: boolean | undefined;
force?: true | undefined;
+ asyncDeploy?: boolean;
}
| undefined
): Promise<{
diff --git a/x-pack/plugins/fleet/server/services/package_policy_service.ts b/x-pack/plugins/fleet/server/services/package_policy_service.ts
index 967efb1055cfb..5a83c2adf97ab 100644
--- a/x-pack/plugins/fleet/server/services/package_policy_service.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy_service.ts
@@ -105,6 +105,7 @@ export interface PackagePolicyClient {
bumpRevision?: boolean;
force?: true;
authorizationHeader?: HTTPAuthorizationHeader | null;
+ asyncDeploy?: boolean;
}
): Promise<{
created: PackagePolicy[];
@@ -115,7 +116,7 @@ export interface PackagePolicyClient {
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
packagePolicyUpdates: UpdatePackagePolicy[],
- options?: { user?: AuthenticatedUser; force?: boolean },
+ options?: { user?: AuthenticatedUser; force?: boolean; asyncDeploy?: boolean },
currentVersion?: string
): Promise<{
updatedPolicies: PackagePolicy[] | null;
@@ -165,6 +166,7 @@ export interface PackagePolicyClient {
user?: AuthenticatedUser;
skipUnassignFromAgentPolicies?: boolean;
force?: boolean;
+ asyncDeploy?: boolean;
},
context?: RequestHandlerContext,
request?: KibanaRequest
diff --git a/x-pack/plugins/inference/README.md b/x-pack/plugins/inference/README.md
index 1807da7f29faa..935ae31bd6bc6 100644
--- a/x-pack/plugins/inference/README.md
+++ b/x-pack/plugins/inference/README.md
@@ -4,13 +4,12 @@ The inference plugin is a central place to handle all interactions with the Elas
external LLM APIs. Its goals are:
- Provide a single place for all interactions with large language models and other generative AI adjacent tasks.
-- Abstract away differences between different LLM providers like OpenAI, Bedrock and Gemini
-- Host commonly used LLM-based tasks like generating ES|QL from natural language and knowledge base recall.
+- Abstract away differences between different LLM providers like OpenAI, Bedrock and Gemini.
- Allow us to move gradually to the \_inference endpoint without disrupting engineers.
## Architecture and examples
-![CleanShot 2024-07-14 at 14 45 27@2x](https://github.com/user-attachments/assets/e65a3e47-bce1-4dcf-bbed-4f8ac12a104f)
+![architecture-schema](https://github.com/user-attachments/assets/e65a3e47-bce1-4dcf-bbed-4f8ac12a104f)
## Terminology
@@ -21,8 +20,22 @@ The following concepts are commonly used throughout the plugin:
- **tools**: a set of tools that the LLM can choose to use when generating the next message. In essence, it allows the consumer of the API to define a schema for structured output instead of plain text, and having the LLM select the most appropriate one.
- **tool call**: when the LLM has chosen a tool (schema) to use for its output, and returns a document that matches the schema, this is referred to as a tool call.
+## Inference connectors
+
+Performing inference, or more globally communicating with the LLM, is done using stack connectors.
+
+The subset of connectors that can be used for inference are called `genAI`, or `inference` connectors.
+Calling any inference APIs with the ID of a connector that is not inference-compatible will result in the API throwing an error.
+
+The list of inference connector types:
+- `.gen-ai`: OpenAI connector
+- `.bedrock`: Bedrock Claude connector
+- `.gemini`: Vertex Gemini connector
+
## Usage examples
+The inference APIs are available via the inference client, which can be created using the inference plugin's start contract:
+
```ts
class MyPlugin {
setup(coreSetup, pluginsSetup) {
@@ -40,9 +53,9 @@ class MyPlugin {
async (context, request, response) => {
const [coreStart, pluginsStart] = await coreSetup.getStartServices();
- const inferenceClient = pluginsSetup.inference.getClient({ request });
+ const inferenceClient = pluginsStart.inference.getClient({ request });
- const chatComplete$ = inferenceClient.chatComplete({
+ const chatResponse = inferenceClient.chatComplete({
connectorId: request.body.connectorId,
system: `Here is my system message`,
messages: [
@@ -53,13 +66,9 @@ class MyPlugin {
],
});
- const message = await lastValueFrom(
- chatComplete$.pipe(withoutTokenCountEvents(), withoutChunkEvents())
- );
-
return response.ok({
body: {
- message,
+ chatResponse,
},
});
}
@@ -68,33 +77,190 @@ class MyPlugin {
}
```
-## Services
+## APIs
-### `chatComplete`:
+### `chatComplete` API:
`chatComplete` generates a response to a prompt or a conversation using the LLM. Here's what is supported:
-- Normalizing request and response formats from different connector types (e.g. OpenAI, Bedrock, Claude, Elastic Inference Service)
+- Normalizing request and response formats from all supported connector types
- Tool calling and validation of tool calls
-- Emits token count events
-- Emits message events, which is the concatenated message based on the response chunks
+- Token usage stats / events
+- Streaming mode to work with chunks in real time instead of waiting for the full response
+
+#### Standard usage
+
+In standard mode, the API returns a promise resolving with the full LLM response once the generation is complete.
+The response will also contain the token count info, if available.
+
+```ts
+const chatResponse = inferenceClient.chatComplete({
+ connectorId: 'some-gen-ai-connector',
+ system: `Here is my system message`,
+ messages: [
+ {
+ role: MessageRole.User,
+ content: 'Do something',
+ },
+ ],
+});
+
+const { content, tokens } = chatResponse;
+// do something with the output
+```
+
+#### Streaming mode
+
+Passing `stream: true` when calling the API enables streaming mode.
+In that mode, the API returns an observable instead of a promise, emitting chunks in real time.
+
+That observable emits three types of events:
+
+- `chunk` the completion chunks, emitted in real time
+- `tokenCount` token count event, containing info about token usages, eventually emitted after the chunks
+- `message` full message event, emitted once the source is done sending chunks
+
+The `@kbn/inference-common` package exposes various utilities to work with this multi-events observable:
+
+- `isChatCompletionChunkEvent`, `isChatCompletionMessageEvent` and `isChatCompletionTokenCountEvent` which are type guard for the corresponding event types
+- `withoutChunkEvents` and `withoutTokenCountEvents`
+
+```ts
+import {
+ isChatCompletionChunkEvent,
+ isChatCompletionMessageEvent,
+ withoutTokenCountEvents,
+ withoutChunkEvents,
+} from '@kbn/inference-common';
-### `output`
+const chatComplete$ = inferenceClient.chatComplete({
+ connectorId: 'some-gen-ai-connector',
+ stream: true,
+ system: `Here is my system message`,
+ messages: [
+ {
+ role: MessageRole.User,
+ content: 'Do something',
+ },
+ ],
+});
-`output` is a wrapper around `chatComplete` that is catered towards a single use case: having the LLM output a structured response, based on a schema. It also drops the token count events to simplify usage.
+// using and filtering the events
+chatComplete$.pipe(withoutTokenCountEvents()).subscribe((event) => {
+ if (isChatCompletionChunkEvent(event)) {
+ // do something with the chunk event
+ } else {
+ // do something with the message event
+ }
+});
+
+// or retrieving the final message
+const message = await lastValueFrom(
+ chatComplete$.pipe(withoutTokenCountEvents(), withoutChunkEvents())
+);
+```
+
+#### Defining and using tools
+
+Tools are defined as a record, with a `description` and optionally a `schema`. The reason why it's a record is because of type-safety.
+This allows us to have fully typed tool calls (e.g. when the name of the tool being called is `x`, its arguments are typed as the schema of `x`).
+
+The description and schema of a tool will be converted and sent to the LLM, so it's important
+to be explicit about what each tool does.
+
+```ts
+const chatResponse = inferenceClient.chatComplete({
+ connectorId: 'some-gen-ai-connector',
+ system: `Here is my system message`,
+ messages: [
+ {
+ role: MessageRole.User,
+ content: 'How much is 4 plus 9?',
+ },
+ ],
+ toolChoice: ToolChoiceType.required, // MUST call a tool
+ tools: {
+ date: {
+ description: 'Call this tool if you need to know the current date'
+ },
+ add: {
+ description: 'This tool can be used to add two numbers',
+ schema: {
+ type: 'object',
+ properties: {
+ a: { type: 'number', description: 'the first number' },
+ b: { type: 'number', description: 'the second number'}
+ },
+ required: ['a', 'b']
+ }
+ }
+ } as const // as const is required to have type inference on the schema
+});
-### Observable event streams
+const { content, toolCalls } = chatResponse;
+const toolCall = toolCalls[0];
+// process the tool call and eventually continue the conversation with the LLM
+```
+
+### `output` API
+
+`output` is a wrapper around the `chatComplete` API that is catered towards a specific use case: having the LLM output a structured response, based on a schema.
+It's basically just making sure that the LLM will call the single tool that is exposed via the provided `schema`.
+It also drops the token count info to simplify usage.
+
+Similar to `chatComplete`, `output` supports two modes: normal full response mode by default, and optional streaming mode by passing the `stream: true` parameter.
+
+```ts
+import { ToolSchema } from '@kbn/inference-common';
-These APIs, both on the client and the server, return Observables that emit events. When converting the Observable into a stream, the following things happen:
+// schema must be defined as full const or using the `satisfies ToolSchema` modifier for TS type inference to work
+const mySchema = {
+ type: 'object',
+ properties: {
+ animals: {
+ description: 'the list of animals that are mentioned in the provided article',
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ vegetables: {
+ description: 'the list of vegetables that are mentioned in the provided article',
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ },
+} as const;
-- Errors are caught and serialized as events sent over the stream (after an error, the stream ends).
-- The response stream outputs data as [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
-- The client that reads the stream, parses the event source as an Observable, and if it encounters a serialized error, it deserializes it and throws an error in the Observable.
+const response = inferenceClient.outputApi({
+ id: 'extract_from_article',
+ connectorId: 'some-gen-ai-connector',
+ schema: mySchema,
+ system:
+ 'You are a helpful assistant and your current task is to extract informations from the provided document',
+ input: `
+ Please find all the animals and vegetables that are mentioned in the following document:
+
+ ## Document
+
+ ${theDoc}
+ `,
+});
+
+// output is properly typed from the provided schema
+const { animals, vegetables } = response.output;
+```
### Errors
-All known errors are instances, and not extensions, from the `InferenceTaskError` base class, which has a `code`, a `message`, and `meta` information about the error. This allows us to serialize and deserialize errors over the wire without a complicated factory pattern.
+All known errors are instances, and not extensions, of the `InferenceTaskError` base class, which has a `code`, a `message`, and `meta` information about the error.
+This allows us to serialize and deserialize errors over the wire without a complicated factory pattern.
-### Tools
+Type guards for each type of error are exposed from the `@kbn/inference-common` package, such as:
-Tools are defined as a record, with a `description` and optionally a `schema`. The reason why it's a record is because of type-safety. This allows us to have fully typed tool calls (e.g. when the name of the tool being called is `x`, its arguments are typed as the schema of `x`).
+- `isInferenceError`
+- `isInferenceInternalError`
+- `isInferenceRequestError`
+- ...`isXXXError`
diff --git a/x-pack/plugins/inference/common/create_output_api.test.ts b/x-pack/plugins/inference/common/create_output_api.test.ts
new file mode 100644
index 0000000000000..b5d380fa9aac6
--- /dev/null
+++ b/x-pack/plugins/inference/common/create_output_api.test.ts
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+import { firstValueFrom, isObservable, of, toArray } from 'rxjs';
+import {
+ ChatCompleteResponse,
+ ChatCompletionEvent,
+ ChatCompletionEventType,
+} from '@kbn/inference-common';
+import { createOutputApi } from './create_output_api';
+
+describe('createOutputApi', () => {
+ let chatComplete: jest.Mock;
+
+ beforeEach(() => {
+ chatComplete = jest.fn();
+ });
+
+ it('calls `chatComplete` with the right parameters', async () => {
+ chatComplete.mockResolvedValue(Promise.resolve({ content: 'content', toolCalls: [] }));
+
+ const output = createOutputApi(chatComplete);
+
+ await output({
+ id: 'id',
+ stream: false,
+ functionCalling: 'native',
+ connectorId: '.my-connector',
+ system: 'system',
+ input: 'input message',
+ });
+
+ expect(chatComplete).toHaveBeenCalledTimes(1);
+ expect(chatComplete).toHaveBeenCalledWith({
+ connectorId: '.my-connector',
+ functionCalling: 'native',
+ stream: false,
+ system: 'system',
+ messages: [
+ {
+ content: 'input message',
+ role: 'user',
+ },
+ ],
+ });
+ });
+
+ it('returns the expected value when stream=false', async () => {
+ const chatCompleteResponse: ChatCompleteResponse = {
+ content: 'content',
+ toolCalls: [{ toolCallId: 'a', function: { name: 'foo', arguments: { arg: 1 } } }],
+ };
+
+ chatComplete.mockResolvedValue(Promise.resolve(chatCompleteResponse));
+
+ const output = createOutputApi(chatComplete);
+
+ const response = await output({
+ id: 'my-id',
+ stream: false,
+ connectorId: '.my-connector',
+ input: 'input message',
+ });
+
+ expect(response).toEqual({
+ id: 'my-id',
+ content: chatCompleteResponse.content,
+ output: chatCompleteResponse.toolCalls[0].function.arguments,
+ });
+ });
+
+ it('returns the expected value when stream=true', async () => {
+ const sourceEvents: ChatCompletionEvent[] = [
+ { type: ChatCompletionEventType.ChatCompletionChunk, content: 'chunk-1', tool_calls: [] },
+ { type: ChatCompletionEventType.ChatCompletionChunk, content: 'chunk-2', tool_calls: [] },
+ {
+ type: ChatCompletionEventType.ChatCompletionMessage,
+ content: 'message',
+ toolCalls: [{ toolCallId: 'a', function: { name: 'foo', arguments: { arg: 1 } } }],
+ },
+ ];
+
+ chatComplete.mockReturnValue(of(...sourceEvents));
+
+ const output = createOutputApi(chatComplete);
+
+ const response$ = await output({
+ id: 'my-id',
+ stream: true,
+ connectorId: '.my-connector',
+ input: 'input message',
+ });
+
+ expect(isObservable(response$)).toEqual(true);
+ const events = await firstValueFrom(response$.pipe(toArray()));
+
+ expect(events).toEqual([
+ {
+ content: 'chunk-1',
+ id: 'my-id',
+ type: 'output',
+ },
+ {
+ content: 'chunk-2',
+ id: 'my-id',
+ type: 'output',
+ },
+ {
+ content: 'message',
+ id: 'my-id',
+ output: {
+ arg: 1,
+ },
+ type: 'complete',
+ },
+ ]);
+ });
+});
diff --git a/x-pack/plugins/inference/common/create_output_api.ts b/x-pack/plugins/inference/common/create_output_api.ts
index 450114c892cba..e5dd2eeda2cbd 100644
--- a/x-pack/plugins/inference/common/create_output_api.ts
+++ b/x-pack/plugins/inference/common/create_output_api.ts
@@ -5,24 +5,36 @@
* 2.0.
*/
-import { map } from 'rxjs';
import {
- OutputAPI,
- OutputEvent,
- OutputEventType,
ChatCompleteAPI,
ChatCompletionEventType,
MessageRole,
+ OutputAPI,
+ OutputEventType,
+ OutputOptions,
+ ToolSchema,
withoutTokenCountEvents,
} from '@kbn/inference-common';
+import { isObservable, map } from 'rxjs';
import { ensureMultiTurn } from './utils/ensure_multi_turn';
-export function createOutputApi(chatCompleteApi: ChatCompleteAPI): OutputAPI {
- return (id, { connectorId, input, schema, system, previousMessages, functionCalling }) => {
- return chatCompleteApi({
+export function createOutputApi(chatCompleteApi: ChatCompleteAPI): OutputAPI;
+export function createOutputApi(chatCompleteApi: ChatCompleteAPI) {
+ return ({
+ id,
+ connectorId,
+ input,
+ schema,
+ system,
+ previousMessages,
+ functionCalling,
+ stream,
+ }: OutputOptions) => {
+ const response = chatCompleteApi({
connectorId,
- system,
+ stream,
functionCalling,
+ system,
messages: ensureMultiTurn([
...(previousMessages || []),
{
@@ -41,27 +53,42 @@ export function createOutputApi(chatCompleteApi: ChatCompleteAPI): OutputAPI {
toolChoice: { function: 'structuredOutput' as const },
}
: {}),
- }).pipe(
- withoutTokenCountEvents(),
- map((event): OutputEvent => {
- if (event.type === ChatCompletionEventType.ChatCompletionChunk) {
+ });
+
+ if (isObservable(response)) {
+ return response.pipe(
+ withoutTokenCountEvents(),
+ map((event) => {
+ if (event.type === ChatCompletionEventType.ChatCompletionChunk) {
+ return {
+ type: OutputEventType.OutputUpdate,
+ id,
+ content: event.content,
+ };
+ }
+
return {
- type: OutputEventType.OutputUpdate,
id,
+ output:
+ event.toolCalls.length && 'arguments' in event.toolCalls[0].function
+ ? event.toolCalls[0].function.arguments
+ : undefined,
content: event.content,
+ type: OutputEventType.OutputComplete,
};
- }
-
+ })
+ );
+ } else {
+ return response.then((chatResponse) => {
return {
id,
+ content: chatResponse.content,
output:
- event.toolCalls.length && 'arguments' in event.toolCalls[0].function
- ? event.toolCalls[0].function.arguments
+ chatResponse.toolCalls.length && 'arguments' in chatResponse.toolCalls[0].function
+ ? chatResponse.toolCalls[0].function.arguments
: undefined,
- content: event.content,
- type: OutputEventType.OutputComplete,
};
- })
- );
+ });
+ }
};
}
diff --git a/x-pack/plugins/inference/public/chat_complete.test.ts b/x-pack/plugins/inference/public/chat_complete.test.ts
new file mode 100644
index 0000000000000..b297db1f2fb2c
--- /dev/null
+++ b/x-pack/plugins/inference/public/chat_complete.test.ts
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+import { omit } from 'lodash';
+import { httpServiceMock } from '@kbn/core/public/mocks';
+import { ChatCompleteAPI, MessageRole, ChatCompleteOptions } from '@kbn/inference-common';
+import { createChatCompleteApi } from './chat_complete';
+
+describe('createChatCompleteApi', () => {
+ let http: ReturnType;
+ let chatComplete: ChatCompleteAPI;
+
+ beforeEach(() => {
+ http = httpServiceMock.createStartContract();
+ chatComplete = createChatCompleteApi({ http });
+ });
+
+ it('calls http.post with the right parameters when stream is not true', async () => {
+ const params = {
+ connectorId: 'my-connector',
+ functionCalling: 'native',
+ system: 'system',
+ messages: [{ role: MessageRole.User, content: 'question' }],
+ };
+ await chatComplete(params as ChatCompleteOptions);
+
+ expect(http.post).toHaveBeenCalledTimes(1);
+ expect(http.post).toHaveBeenCalledWith('/internal/inference/chat_complete', {
+ body: expect.any(String),
+ });
+ const callBody = http.post.mock.lastCall!;
+
+ expect(JSON.parse((callBody as any[])[1].body as string)).toEqual(params);
+ });
+
+ it('calls http.post with the right parameters when stream is true', async () => {
+ http.post.mockResolvedValue({});
+
+ const params = {
+ connectorId: 'my-connector',
+ functionCalling: 'native',
+ stream: true,
+ system: 'system',
+ messages: [{ role: MessageRole.User, content: 'question' }],
+ };
+
+ await chatComplete(params as ChatCompleteOptions);
+
+ expect(http.post).toHaveBeenCalledTimes(1);
+ expect(http.post).toHaveBeenCalledWith('/internal/inference/chat_complete/stream', {
+ asResponse: true,
+ rawResponse: true,
+ body: expect.any(String),
+ });
+ const callBody = http.post.mock.lastCall!;
+
+ expect(JSON.parse((callBody as any[])[1].body as string)).toEqual(omit(params, 'stream'));
+ });
+});
diff --git a/x-pack/plugins/inference/public/chat_complete.ts b/x-pack/plugins/inference/public/chat_complete.ts
index 5319f7c31c381..64cb5533f20be 100644
--- a/x-pack/plugins/inference/public/chat_complete.ts
+++ b/x-pack/plugins/inference/public/chat_complete.ts
@@ -5,14 +5,31 @@
* 2.0.
*/
-import { from } from 'rxjs';
import type { HttpStart } from '@kbn/core/public';
-import type { ChatCompleteAPI } from '@kbn/inference-common';
+import {
+ ChatCompleteAPI,
+ ChatCompleteCompositeResponse,
+ ChatCompleteOptions,
+ ToolOptions,
+} from '@kbn/inference-common';
+import { from } from 'rxjs';
import type { ChatCompleteRequestBody } from '../common/http_apis';
import { httpResponseIntoObservable } from './util/http_response_into_observable';
-export function createChatCompleteApi({ http }: { http: HttpStart }): ChatCompleteAPI {
- return ({ connectorId, messages, system, toolChoice, tools, functionCalling }) => {
+export function createChatCompleteApi({ http }: { http: HttpStart }): ChatCompleteAPI;
+export function createChatCompleteApi({ http }: { http: HttpStart }) {
+ return ({
+ connectorId,
+ messages,
+ system,
+ toolChoice,
+ tools,
+ functionCalling,
+ stream,
+ }: ChatCompleteOptions): ChatCompleteCompositeResponse<
+ ToolOptions,
+ boolean
+ > => {
const body: ChatCompleteRequestBody = {
connectorId,
system,
@@ -22,12 +39,18 @@ export function createChatCompleteApi({ http }: { http: HttpStart }): ChatComple
functionCalling,
};
- return from(
- http.post('/internal/inference/chat_complete', {
- asResponse: true,
- rawResponse: true,
+ if (stream) {
+ return from(
+ http.post('/internal/inference/chat_complete/stream', {
+ asResponse: true,
+ rawResponse: true,
+ body: JSON.stringify(body),
+ })
+ ).pipe(httpResponseIntoObservable());
+ } else {
+ return http.post('/internal/inference/chat_complete', {
body: JSON.stringify(body),
- })
- ).pipe(httpResponseIntoObservable());
+ });
+ }
};
}
diff --git a/x-pack/plugins/inference/scripts/evaluation/evaluation.ts b/x-pack/plugins/inference/scripts/evaluation/evaluation.ts
index 0c70bf81eddad..425b7e334074a 100644
--- a/x-pack/plugins/inference/scripts/evaluation/evaluation.ts
+++ b/x-pack/plugins/inference/scripts/evaluation/evaluation.ts
@@ -89,8 +89,8 @@ function runEvaluations() {
const evaluationClient = createInferenceEvaluationClient({
connectorId: connector.connectorId,
evaluationConnectorId,
- outputApi: (id, parameters) =>
- chatClient.output(id, {
+ outputApi: (parameters) =>
+ chatClient.output({
...parameters,
connectorId: evaluationConnectorId,
}) as any,
diff --git a/x-pack/plugins/inference/scripts/evaluation/evaluation_client.ts b/x-pack/plugins/inference/scripts/evaluation/evaluation_client.ts
index d35c214542255..99eaf02494af7 100644
--- a/x-pack/plugins/inference/scripts/evaluation/evaluation_client.ts
+++ b/x-pack/plugins/inference/scripts/evaluation/evaluation_client.ts
@@ -6,8 +6,7 @@
*/
import { remove } from 'lodash';
-import { lastValueFrom } from 'rxjs';
-import { type OutputAPI, withoutOutputUpdateEvents } from '@kbn/inference-common';
+import type { OutputAPI } from '@kbn/inference-common';
import type { EvaluationResult } from './types';
export interface InferenceEvaluationClient {
@@ -67,11 +66,12 @@ export function createInferenceEvaluationClient({
output: outputApi,
getEvaluationConnectorId: () => evaluationConnectorId,
evaluate: async ({ input, criteria = [], system }) => {
- const evaluation = await lastValueFrom(
- outputApi('evaluate', {
- connectorId,
- system: withAdditionalSystemContext(
- `You are a helpful, respected assistant for evaluating task
+ const evaluation = await outputApi({
+ id: 'evaluate',
+ stream: false,
+ connectorId,
+ system: withAdditionalSystemContext(
+ `You are a helpful, respected assistant for evaluating task
inputs and outputs in the Elastic Platform.
Your goal is to verify whether the output of a task
@@ -84,10 +84,10 @@ export function createInferenceEvaluationClient({
quoting what the assistant did wrong, where it could improve,
and what the root cause was in case of a failure.
`,
- system
- ),
+ system
+ ),
- input: `
+ input: `
## Criteria
${criteria
@@ -99,37 +99,36 @@ export function createInferenceEvaluationClient({
## Input
${input}`,
- schema: {
- type: 'object',
- properties: {
- criteria: {
- type: 'array',
- items: {
- type: 'object',
- properties: {
- index: {
- type: 'number',
- description: 'The number of the criterion',
- },
- score: {
- type: 'number',
- description:
- 'The score you calculated for the criterion, between 0 (criterion fully failed) and 1 (criterion fully succeeded).',
- },
- reasoning: {
- type: 'string',
- description:
- 'Your reasoning for the score. Explain your score by mentioning what you expected to happen and what did happen.',
- },
+ schema: {
+ type: 'object',
+ properties: {
+ criteria: {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ index: {
+ type: 'number',
+ description: 'The number of the criterion',
+ },
+ score: {
+ type: 'number',
+ description:
+ 'The score you calculated for the criterion, between 0 (criterion fully failed) and 1 (criterion fully succeeded).',
+ },
+ reasoning: {
+ type: 'string',
+ description:
+ 'Your reasoning for the score. Explain your score by mentioning what you expected to happen and what did happen.',
},
- required: ['index', 'score', 'reasoning'],
},
+ required: ['index', 'score', 'reasoning'],
},
},
- required: ['criteria'],
- } as const,
- }).pipe(withoutOutputUpdateEvents())
- );
+ },
+ required: ['criteria'],
+ } as const,
+ });
const scoredCriteria = evaluation.output.criteria;
diff --git a/x-pack/plugins/inference/scripts/evaluation/scenarios/esql/index.spec.ts b/x-pack/plugins/inference/scripts/evaluation/scenarios/esql/index.spec.ts
index d9071b3f0ae3f..49a82db8124e9 100644
--- a/x-pack/plugins/inference/scripts/evaluation/scenarios/esql/index.spec.ts
+++ b/x-pack/plugins/inference/scripts/evaluation/scenarios/esql/index.spec.ts
@@ -9,8 +9,7 @@
import expect from '@kbn/expect';
import type { Logger } from '@kbn/logging';
-import { firstValueFrom, lastValueFrom, filter } from 'rxjs';
-import { isOutputCompleteEvent } from '@kbn/inference-common';
+import { lastValueFrom } from 'rxjs';
import { naturalLanguageToEsql } from '../../../../server/tasks/nl_to_esql';
import { chatClient, evaluationClient, logger } from '../../services';
import { EsqlDocumentBase } from '../../../../server/tasks/nl_to_esql/doc_base';
@@ -66,11 +65,10 @@ const retrieveUsedCommands = async ({
answer: string;
esqlDescription: string;
}) => {
- const commandsListOutput = await firstValueFrom(
- evaluationClient
- .output('retrieve_commands', {
- connectorId: evaluationClient.getEvaluationConnectorId(),
- system: `
+ const commandsListOutput = await evaluationClient.output({
+ id: 'retrieve_commands',
+ connectorId: evaluationClient.getEvaluationConnectorId(),
+ system: `
You are a helpful, respected Elastic ES|QL assistant.
Your role is to enumerate the list of ES|QL commands and functions that were used
@@ -82,34 +80,32 @@ const retrieveUsedCommands = async ({
${esqlDescription}
`,
- input: `
+ input: `
# Question
${question}
# Answer
${answer}
`,
- schema: {
- type: 'object',
- properties: {
- commands: {
- description:
- 'The list of commands that were used in the provided ES|QL question and answer',
- type: 'array',
- items: { type: 'string' },
- },
- functions: {
- description:
- 'The list of functions that were used in the provided ES|QL question and answer',
- type: 'array',
- items: { type: 'string' },
- },
- },
- required: ['commands', 'functions'],
- } as const,
- })
- .pipe(filter(isOutputCompleteEvent))
- );
+ schema: {
+ type: 'object',
+ properties: {
+ commands: {
+ description:
+ 'The list of commands that were used in the provided ES|QL question and answer',
+ type: 'array',
+ items: { type: 'string' },
+ },
+ functions: {
+ description:
+ 'The list of functions that were used in the provided ES|QL question and answer',
+ type: 'array',
+ items: { type: 'string' },
+ },
+ },
+ required: ['commands', 'functions'],
+ } as const,
+ });
const output = commandsListOutput.output;
diff --git a/x-pack/plugins/inference/scripts/load_esql_docs/utils/output_executor.ts b/x-pack/plugins/inference/scripts/load_esql_docs/utils/output_executor.ts
index 62cfd8f877e3f..f4014db0e6e8d 100644
--- a/x-pack/plugins/inference/scripts/load_esql_docs/utils/output_executor.ts
+++ b/x-pack/plugins/inference/scripts/load_esql_docs/utils/output_executor.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { lastValueFrom } from 'rxjs';
import type { OutputAPI } from '@kbn/inference-common';
export interface Prompt {
@@ -27,13 +26,13 @@ export type PromptCallerFactory = ({
export const bindOutput: PromptCallerFactory = ({ connectorId, output }) => {
return async ({ input, system }) => {
- const response = await lastValueFrom(
- output('', {
- connectorId,
- input,
- system,
- })
- );
+ const response = await output({
+ id: 'output',
+ connectorId,
+ input,
+ system,
+ });
+
return response.content ?? '';
};
};
diff --git a/x-pack/plugins/inference/scripts/util/kibana_client.ts b/x-pack/plugins/inference/scripts/util/kibana_client.ts
index b599ab81a4af4..ad6c21cf4b248 100644
--- a/x-pack/plugins/inference/scripts/util/kibana_client.ts
+++ b/x-pack/plugins/inference/scripts/util/kibana_client.ts
@@ -15,6 +15,7 @@ import { inspect } from 'util';
import { isReadable } from 'stream';
import {
ChatCompleteAPI,
+ ChatCompleteCompositeResponse,
OutputAPI,
ChatCompletionEvent,
InferenceTaskError,
@@ -22,6 +23,8 @@ import {
InferenceTaskEventType,
createInferenceInternalError,
withoutOutputUpdateEvents,
+ type ToolOptions,
+ ChatCompleteOptions,
} from '@kbn/inference-common';
import type { ChatCompleteRequestBody } from '../../common/http_apis';
import type { InferenceConnector } from '../../common/connectors';
@@ -154,7 +157,7 @@ export class KibanaClient {
}
createInferenceClient({ connectorId }: { connectorId: string }): ScriptInferenceClient {
- function stream(responsePromise: Promise) {
+ function streamResponse(responsePromise: Promise) {
return from(responsePromise).pipe(
switchMap((response) => {
if (isReadable(response.data)) {
@@ -174,14 +177,18 @@ export class KibanaClient {
);
}
- const chatCompleteApi: ChatCompleteAPI = ({
+ const chatCompleteApi: ChatCompleteAPI = <
+ TToolOptions extends ToolOptions = ToolOptions,
+ TStream extends boolean = false
+ >({
connectorId: chatCompleteConnectorId,
messages,
system,
toolChoice,
tools,
functionCalling,
- }) => {
+ stream,
+ }: ChatCompleteOptions) => {
const body: ChatCompleteRequestBody = {
connectorId: chatCompleteConnectorId,
system,
@@ -191,15 +198,29 @@ export class KibanaClient {
functionCalling,
};
- return stream(
- this.axios.post(
- this.getUrl({
- pathname: `/internal/inference/chat_complete`,
- }),
- body,
- { responseType: 'stream', timeout: NaN }
- )
- );
+ if (stream) {
+ return streamResponse(
+ this.axios.post(
+ this.getUrl({
+ pathname: `/internal/inference/chat_complete/stream`,
+ }),
+ body,
+ { responseType: 'stream', timeout: NaN }
+ )
+ ) as ChatCompleteCompositeResponse;
+ } else {
+ return this.axios
+ .post(
+ this.getUrl({
+ pathname: `/internal/inference/chat_complete/stream`,
+ }),
+ body,
+ { responseType: 'stream', timeout: NaN }
+ )
+ .then((response) => {
+ return response.data;
+ }) as ChatCompleteCompositeResponse;
+ }
};
const outputApi: OutputAPI = createOutputApi(chatCompleteApi);
@@ -211,8 +232,13 @@ export class KibanaClient {
...options,
});
},
- output: (id, options) => {
- return outputApi(id, { ...options }).pipe(withoutOutputUpdateEvents());
+ output: (options) => {
+ const response = outputApi({ ...options });
+ if (options.stream) {
+ return (response as any).pipe(withoutOutputUpdateEvents());
+ } else {
+ return response;
+ }
},
};
}
diff --git a/x-pack/plugins/inference/server/chat_complete/api.ts b/x-pack/plugins/inference/server/chat_complete/api.ts
index 62a1ea8b26146..cf325e72ddf3a 100644
--- a/x-pack/plugins/inference/server/chat_complete/api.ts
+++ b/x-pack/plugins/inference/server/chat_complete/api.ts
@@ -11,32 +11,37 @@ import type { Logger } from '@kbn/logging';
import type { KibanaRequest } from '@kbn/core-http-server';
import {
type ChatCompleteAPI,
- type ChatCompletionResponse,
+ type ChatCompleteCompositeResponse,
createInferenceRequestError,
+ type ToolOptions,
+ ChatCompleteOptions,
} from '@kbn/inference-common';
import type { InferenceStartDependencies } from '../types';
import { getConnectorById } from '../util/get_connector_by_id';
import { getInferenceAdapter } from './adapters';
-import { createInferenceExecutor, chunksIntoMessage } from './utils';
+import { createInferenceExecutor, chunksIntoMessage, streamToResponse } from './utils';
-export function createChatCompleteApi({
- request,
- actions,
- logger,
-}: {
+interface CreateChatCompleteApiOptions {
request: KibanaRequest;
actions: InferenceStartDependencies['actions'];
logger: Logger;
-}) {
- const chatCompleteAPI: ChatCompleteAPI = ({
+}
+
+export function createChatCompleteApi(options: CreateChatCompleteApiOptions): ChatCompleteAPI;
+export function createChatCompleteApi({ request, actions, logger }: CreateChatCompleteApiOptions) {
+ return ({
connectorId,
messages,
toolChoice,
tools,
system,
functionCalling,
- }): ChatCompletionResponse => {
- return defer(async () => {
+ stream,
+ }: ChatCompleteOptions): ChatCompleteCompositeResponse<
+ ToolOptions,
+ boolean
+ > => {
+ const obs$ = defer(async () => {
const actionsClient = await actions.getActionsClientWithRequest(request);
const connector = await getConnectorById({ connectorId, actionsClient });
const executor = createInferenceExecutor({ actionsClient, connector });
@@ -73,7 +78,11 @@ export function createChatCompleteApi({
logger,
})
);
- };
- return chatCompleteAPI;
+ if (stream) {
+ return obs$;
+ } else {
+ return streamToResponse(obs$);
+ }
+ };
}
diff --git a/x-pack/plugins/inference/server/chat_complete/utils/index.ts b/x-pack/plugins/inference/server/chat_complete/utils/index.ts
index dea2ac65f4755..d3dc2010cba3a 100644
--- a/x-pack/plugins/inference/server/chat_complete/utils/index.ts
+++ b/x-pack/plugins/inference/server/chat_complete/utils/index.ts
@@ -12,3 +12,4 @@ export {
type InferenceExecutor,
} from './inference_executor';
export { chunksIntoMessage } from './chunks_into_message';
+export { streamToResponse } from './stream_to_response';
diff --git a/x-pack/plugins/inference/server/chat_complete/utils/stream_to_response.test.ts b/x-pack/plugins/inference/server/chat_complete/utils/stream_to_response.test.ts
new file mode 100644
index 0000000000000..939997a5fef15
--- /dev/null
+++ b/x-pack/plugins/inference/server/chat_complete/utils/stream_to_response.test.ts
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+import { of } from 'rxjs';
+import { ChatCompletionEvent } from '@kbn/inference-common';
+import { chunkEvent, tokensEvent, messageEvent } from '../../test_utils/chat_complete_events';
+import { streamToResponse } from './stream_to_response';
+
+describe('streamToResponse', () => {
+ function fromEvents(...events: ChatCompletionEvent[]) {
+ return of(...events);
+ }
+
+ it('returns a response with token count if both message and token events got emitted', async () => {
+ const response = await streamToResponse(
+ fromEvents(
+ chunkEvent('chunk_1'),
+ chunkEvent('chunk_2'),
+ tokensEvent({ prompt: 1, completion: 2, total: 3 }),
+ messageEvent('message')
+ )
+ );
+
+ expect(response).toEqual({
+ content: 'message',
+ tokens: {
+ completion: 2,
+ prompt: 1,
+ total: 3,
+ },
+ toolCalls: [],
+ });
+ });
+
+ it('returns a response with tool calls if present', async () => {
+ const someToolCall = {
+ toolCallId: '42',
+ function: {
+ name: 'my_tool',
+ arguments: {},
+ },
+ };
+ const response = await streamToResponse(
+ fromEvents(chunkEvent('chunk_1'), messageEvent('message', [someToolCall]))
+ );
+
+ expect(response).toEqual({
+ content: 'message',
+ toolCalls: [someToolCall],
+ });
+ });
+
+ it('returns a response without token count if only message got emitted', async () => {
+ const response = await streamToResponse(
+ fromEvents(chunkEvent('chunk_1'), messageEvent('message'))
+ );
+
+ expect(response).toEqual({
+ content: 'message',
+ toolCalls: [],
+ });
+ });
+
+ it('rejects an error if message event is not emitted', async () => {
+ await expect(
+ streamToResponse(fromEvents(chunkEvent('chunk_1'), tokensEvent()))
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`"No message event found"`);
+ });
+});
diff --git a/x-pack/plugins/inference/server/chat_complete/utils/stream_to_response.ts b/x-pack/plugins/inference/server/chat_complete/utils/stream_to_response.ts
new file mode 100644
index 0000000000000..4bae4fda767cb
--- /dev/null
+++ b/x-pack/plugins/inference/server/chat_complete/utils/stream_to_response.ts
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+import { toArray, map, firstValueFrom } from 'rxjs';
+import {
+ ChatCompleteResponse,
+ ChatCompleteStreamResponse,
+ createInferenceInternalError,
+ isChatCompletionMessageEvent,
+ isChatCompletionTokenCountEvent,
+ ToolOptions,
+ withoutChunkEvents,
+} from '@kbn/inference-common';
+
+export const streamToResponse = (
+ streamResponse$: ChatCompleteStreamResponse
+): Promise> => {
+ return firstValueFrom(
+ streamResponse$.pipe(
+ withoutChunkEvents(),
+ toArray(),
+ map((events) => {
+ const messageEvent = events.find(isChatCompletionMessageEvent);
+ const tokenEvent = events.find(isChatCompletionTokenCountEvent);
+
+ if (!messageEvent) {
+ throw createInferenceInternalError('No message event found');
+ }
+
+ return {
+ content: messageEvent.content,
+ toolCalls: messageEvent.toolCalls,
+ tokens: tokenEvent?.tokens,
+ };
+ })
+ )
+ );
+};
diff --git a/x-pack/plugins/inference/server/routes/chat_complete.ts b/x-pack/plugins/inference/server/routes/chat_complete.ts
index d4d0d012a78cd..582d4ceb97d45 100644
--- a/x-pack/plugins/inference/server/routes/chat_complete.ts
+++ b/x-pack/plugins/inference/server/routes/chat_complete.ts
@@ -5,8 +5,14 @@
* 2.0.
*/
-import { schema, Type } from '@kbn/config-schema';
-import type { CoreSetup, IRouter, Logger, RequestHandlerContext } from '@kbn/core/server';
+import { schema, Type, TypeOf } from '@kbn/config-schema';
+import type {
+ CoreSetup,
+ IRouter,
+ Logger,
+ RequestHandlerContext,
+ KibanaRequest,
+} from '@kbn/core/server';
import { MessageRole, ToolCall, ToolChoiceType } from '@kbn/inference-common';
import type { ChatCompleteRequestBody } from '../../common/http_apis';
import { createInferenceClient } from '../inference_client';
@@ -84,6 +90,32 @@ export function registerChatCompleteRoute({
router: IRouter;
logger: Logger;
}) {
+ async function callChatComplete({
+ request,
+ stream,
+ }: {
+ request: KibanaRequest>;
+ stream: T;
+ }) {
+ const actions = await coreSetup
+ .getStartServices()
+ .then(([coreStart, pluginsStart]) => pluginsStart.actions);
+
+ const client = createInferenceClient({ request, actions, logger });
+
+ const { connectorId, messages, system, toolChoice, tools, functionCalling } = request.body;
+
+ return client.chatComplete({
+ connectorId,
+ messages,
+ system,
+ toolChoice,
+ tools,
+ functionCalling,
+ stream,
+ });
+ }
+
router.post(
{
path: '/internal/inference/chat_complete',
@@ -92,23 +124,22 @@ export function registerChatCompleteRoute({
},
},
async (context, request, response) => {
- const actions = await coreSetup
- .getStartServices()
- .then(([coreStart, pluginsStart]) => pluginsStart.actions);
-
- const client = createInferenceClient({ request, actions, logger });
-
- const { connectorId, messages, system, toolChoice, tools, functionCalling } = request.body;
-
- const chatCompleteResponse = client.chatComplete({
- connectorId,
- messages,
- system,
- toolChoice,
- tools,
- functionCalling,
+ const chatCompleteResponse = await callChatComplete({ request, stream: false });
+ return response.ok({
+ body: chatCompleteResponse,
});
+ }
+ );
+ router.post(
+ {
+ path: '/internal/inference/chat_complete/stream',
+ validate: {
+ body: chatCompleteBodySchema,
+ },
+ },
+ async (context, request, response) => {
+ const chatCompleteResponse = await callChatComplete({ request, stream: true });
return response.ok({
body: observableIntoEventSourceStream(chatCompleteResponse, logger),
});
diff --git a/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/generate_esql.ts b/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/generate_esql.ts
index 26a8fb63ce013..3d8701eba72db 100644
--- a/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/generate_esql.ts
+++ b/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/generate_esql.ts
@@ -71,6 +71,7 @@ export const generateEsqlTask = ({
chatCompleteApi({
connectorId,
functionCalling,
+ stream: true,
system: `${systemMessage}
# Current task
diff --git a/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/request_documentation.ts b/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/request_documentation.ts
index aea428208be1d..06e75db09bdc9 100644
--- a/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/request_documentation.ts
+++ b/x-pack/plugins/inference/server/tasks/nl_to_esql/actions/request_documentation.ts
@@ -33,8 +33,10 @@ export const requestDocumentation = ({
}) => {
const hasTools = !isEmpty(tools) && toolChoice !== ToolChoiceType.none;
- return outputApi('request_documentation', {
+ return outputApi({
+ id: 'request_documentation',
connectorId,
+ stream: true,
functionCalling,
system,
previousMessages: messages,
diff --git a/x-pack/plugins/inference/server/test_utils/chat_complete_events.ts b/x-pack/plugins/inference/server/test_utils/chat_complete_events.ts
new file mode 100644
index 0000000000000..4b09ca9c4dc5a
--- /dev/null
+++ b/x-pack/plugins/inference/server/test_utils/chat_complete_events.ts
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+import {
+ ChatCompletionChunkEvent,
+ ChatCompletionEventType,
+ ChatCompletionTokenCountEvent,
+ ChatCompletionMessageEvent,
+ ChatCompletionTokenCount,
+ ToolCall,
+} from '@kbn/inference-common';
+
+export const chunkEvent = (content: string = 'chunk'): ChatCompletionChunkEvent => ({
+ type: ChatCompletionEventType.ChatCompletionChunk,
+ content,
+ tool_calls: [],
+});
+
+export const messageEvent = (
+ content: string = 'message',
+ toolCalls: Array> = []
+): ChatCompletionMessageEvent => ({
+ type: ChatCompletionEventType.ChatCompletionMessage,
+ content,
+ toolCalls,
+});
+
+export const tokensEvent = (tokens?: ChatCompletionTokenCount): ChatCompletionTokenCountEvent => ({
+ type: ChatCompletionEventType.ChatCompletionTokenCount,
+ tokens: {
+ prompt: tokens?.prompt ?? 10,
+ completion: tokens?.completion ?? 20,
+ total: tokens?.total ?? 30,
+ },
+});
diff --git a/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts b/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts
index 04ba66acbebfb..34f05fcc82025 100644
--- a/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts
+++ b/x-pack/plugins/integration_assistant/server/routes/analyze_logs_routes.ts
@@ -36,6 +36,13 @@ export function registerAnalyzeLogsRoutes(
.addVersion(
{
version: '1',
+ security: {
+ authz: {
+ enabled: false,
+ reason:
+ 'This route is opted out from authorization because the privileges are not defined yet.',
+ },
+ },
validate: {
request: {
body: buildRouteValidationWithZod(AnalyzeLogsRequestBody),
diff --git a/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts
index 6d7e5155a3d23..f62d6d55f933d 100644
--- a/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts
+++ b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts
@@ -25,6 +25,13 @@ export function registerIntegrationBuilderRoutes(
.addVersion(
{
version: '1',
+ security: {
+ authz: {
+ enabled: false,
+ reason:
+ 'This route is opted out from authorization because the privileges are not defined yet.',
+ },
+ },
validate: {
request: {
body: buildRouteValidationWithZod(BuildIntegrationRequestBody),
diff --git a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts
index 10d72b92563fe..5f63ed9c7bf3c 100644
--- a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts
+++ b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts
@@ -40,6 +40,13 @@ export function registerCategorizationRoutes(
.addVersion(
{
version: '1',
+ security: {
+ authz: {
+ enabled: false,
+ reason:
+ 'This route is opted out from authorization because the privileges are not defined yet.',
+ },
+ },
validate: {
request: {
body: buildRouteValidationWithZod(CategorizationRequestBody),
diff --git a/x-pack/plugins/integration_assistant/server/routes/cel_routes.ts b/x-pack/plugins/integration_assistant/server/routes/cel_routes.ts
index 5f417bbc0aec9..9ce16c3909119 100644
--- a/x-pack/plugins/integration_assistant/server/routes/cel_routes.ts
+++ b/x-pack/plugins/integration_assistant/server/routes/cel_routes.ts
@@ -32,6 +32,13 @@ export function registerCelInputRoutes(router: IRouter = ({
fieldStatsServices={fieldStatsServices}
timeRangeMs={indexData.timeRangeMs}
dslQuery={jobConfigQuery}
+ theme={services.theme}
>
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx
index 6c4600be5d25e..b44c523bc57cf 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx
@@ -123,6 +123,7 @@ export const WizardSteps: FC = ({ currentStep, setCurrentStep }) => {
fieldStatsServices={fieldStatsServices}
timeRangeMs={timeRangeMs}
dslQuery={jobCreator.query}
+ theme={services.theme}
>
<>
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts
index f21e67fe450f4..3552b8f006091 100644
--- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts
@@ -27,7 +27,6 @@ import type {
JobStats,
Datafeed,
CombinedJob,
- Detector,
AnalysisConfig,
ModelSnapshot,
IndicesOptions,
@@ -350,15 +349,6 @@ export function mlApiProvider(httpService: HttpService) {
});
},
- validateDetector({ detector }: { detector: Detector }) {
- const body = JSON.stringify(detector);
- return httpService.http({
- path: `${ML_INTERNAL_BASE_PATH}/anomaly_detectors/_validate/detector`,
- method: 'POST',
- body,
- });
- },
-
forecast({
jobId,
duration,
diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts
index 93977257cdc22..ca7b36df8f208 100644
--- a/x-pack/plugins/ml/server/lib/ml_client/types.ts
+++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts
@@ -121,8 +121,7 @@ export type MlClientParams =
| Parameters
| Parameters
| Parameters
- | Parameters
- | Parameters;
+ | Parameters;
export type MlGetADParams = Parameters | Parameters;
diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
index 8cd9f45a4217e..4c75b7a85556a 100644
--- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
+++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
@@ -349,37 +349,6 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) {
})
);
- router.versioned
- .post({
- path: `${ML_INTERNAL_BASE_PATH}/anomaly_detectors/_validate/detector`,
- access: 'internal',
- options: {
- tags: ['access:ml:canCreateJob'],
- },
- summary: 'Validates detector',
- description: 'Validates specified detector.',
- })
- .addVersion(
- {
- version: '1',
- validate: {
- request: {
- body: schema.any(),
- },
- },
- },
- routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => {
- try {
- const body = await mlClient.validateDetector({ body: request.body });
- return response.ok({
- body,
- });
- } catch (e) {
- return response.customError(wrapError(e));
- }
- })
- );
-
router.versioned
.delete({
path: `${ML_INTERNAL_BASE_PATH}/anomaly_detectors/{jobId}/_forecast/{forecastId}`,
diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/README.md b/x-pack/plugins/observability_solution/apm/ftr_e2e/README.md
index 8336c037ff21d..ecdb37a5f5229 100644
--- a/x-pack/plugins/observability_solution/apm/ftr_e2e/README.md
+++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/README.md
@@ -1,6 +1,6 @@
# APM E2E
-APM uses [FTR](../../../../../packages/kbn-test/README.md) (functional test runner) and [Cypress](https://www.cypress.io/) to run the e2e tests. The tests are located at `kibana/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/integration`.
+APM uses [FTR](../../../../../packages/kbn-test/README.mdx) (functional test runner) and [Cypress](https://www.cypress.io/) to run the e2e tests. The tests are located at `kibana/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/integration`.
## Tips and best practices
diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts
index 3ae431f5d3299..0fc1b609b14ba 100644
--- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts
+++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts
@@ -16,7 +16,7 @@ const timeRange = {
rangeTo: end,
};
// flaky
-describe.skip('Transaction details', () => {
+describe('Transaction details', () => {
before(() => {
synthtrace.index(
opbeans({
@@ -34,8 +34,7 @@ describe.skip('Transaction details', () => {
cy.loginAsViewerUser();
});
- // skipping this as it´s been failing a lot lately, more information here https://github.com/elastic/kibana/issues/197386
- it.skip('shows transaction name and transaction charts', () => {
+ it('shows transaction name and transaction charts', () => {
cy.intercept('GET', '/internal/apm/services/opbeans-java/transactions/charts/latency?*').as(
'transactionLatencyRequest'
);
@@ -61,7 +60,7 @@ describe.skip('Transaction details', () => {
'@transactionThroughputRequest',
'@transactionFailureRateRequest',
],
- { timeout: 60000 }
+ { timeout: 30000 }
).spread((latencyInterception, throughputInterception, failureRateInterception) => {
expect(latencyInterception.request.query.transactionName).to.be.eql('GET /api/product');
@@ -107,8 +106,7 @@ describe.skip('Transaction details', () => {
);
cy.contains('Create SLO');
});
- // skipping this as it´s been failing a lot lately, more information here https://github.com/elastic/kibana/issues/197386
- it.skip('shows top errors table', () => {
+ it('shows top errors table', () => {
cy.visitKibana(
`/app/apm/services/opbeans-java/transactions/view?${new URLSearchParams({
...timeRange,
@@ -116,7 +114,7 @@ describe.skip('Transaction details', () => {
})}`
);
- cy.contains('Top 5 errors');
+ cy.contains('Top 5 errors', { timeout: 30000 });
cy.getByTestSubj('topErrorsForTransactionTable').contains('a', '[MockError] Foo').click();
cy.url().should('include', 'opbeans-java/errors');
});
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts b/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts
index 1815897cb7c7a..d47cdbca64b92 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts
@@ -24,7 +24,7 @@ export function maybeRedirectToAvailableSpanSample({
page: number;
replace: typeof urlHelpersReplace;
history: History;
- samples: Array<{ spanId: string; traceId: string; transactionId: string }>;
+ samples: Array<{ spanId: string; traceId: string; transactionId?: string }>;
}) {
if (spanFetchStatus !== FETCH_STATUS.SUCCESS) {
// we're still loading, don't do anything
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts b/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts
index 2a5a804d57f04..6066ebda155d5 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/dependencies/get_top_dependency_spans.ts
@@ -41,7 +41,7 @@ export interface DependencySpan {
serviceName: string;
agentName: AgentName;
traceId: string;
- transactionId: string;
+ transactionId?: string;
transactionType?: string;
transactionName?: string;
duration: number;
@@ -72,7 +72,6 @@ export async function getTopDependencySpans({
const topDedsRequiredFields = asMutableArray([
SPAN_ID,
TRACE_ID,
- TRANSACTION_ID,
SPAN_NAME,
SERVICE_NAME,
SERVICE_ENVIRONMENT,
@@ -98,7 +97,6 @@ export async function getTopDependencySpans({
...kqlQuery(kuery),
...termQuery(SPAN_DESTINATION_SERVICE_RESOURCE, dependencyName),
...termQuery(SPAN_NAME, spanName),
- { exists: { field: TRANSACTION_ID } },
...((sampleRangeFrom ?? 0) >= 0 && (sampleRangeTo ?? 0) > 0
? [
{
@@ -119,9 +117,10 @@ export async function getTopDependencySpans({
})
).hits.hits.map((hit) => unflattenKnownApmEventFields(hit.fields, topDedsRequiredFields));
- const transactionIds = spans.map((span) => span.transaction.id);
+ const traceIds = spans.map((span) => span.trace.id);
const txRequiredFields = asMutableArray([
+ TRACE_ID,
TRANSACTION_ID,
TRANSACTION_TYPE,
TRANSACTION_NAME,
@@ -134,10 +133,10 @@ export async function getTopDependencySpans({
},
body: {
track_total_hits: false,
- size: transactionIds.length,
+ size: traceIds.length,
query: {
bool: {
- filter: [...termsQuery(TRANSACTION_ID, ...transactionIds)],
+ filter: [...termsQuery(TRACE_ID, ...traceIds), { exists: { field: TRANSACTION_ID } }],
},
},
fields: txRequiredFields,
@@ -148,10 +147,10 @@ export async function getTopDependencySpans({
})
).hits.hits.map((hit) => unflattenKnownApmEventFields(hit.fields, txRequiredFields));
- const transactionsById = keyBy(transactions, (transaction) => transaction.transaction.id);
+ const transactionsByTraceId = keyBy(transactions, (transaction) => transaction.trace.id);
return spans.map((span): DependencySpan => {
- const transaction = maybe(transactionsById[span.transaction!.id]);
+ const transaction = maybe(transactionsByTraceId[span.trace!.id]);
return {
'@timestamp': new Date(span['@timestamp']).getTime(),
@@ -162,7 +161,7 @@ export async function getTopDependencySpans({
duration: span.span.duration.us,
traceId: span.trace.id,
outcome: (span.event?.outcome || EventOutcome.unknown) as EventOutcome,
- transactionId: span.transaction!.id,
+ transactionId: transaction?.transaction.id,
transactionType: transaction?.transaction.type,
transactionName: transaction?.transaction.name,
};
diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts
index 36433a2bca016..a6cfd30eaa26d 100644
--- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts
+++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/lib/get_filtered_metrics.ts
@@ -18,11 +18,13 @@ export const getFilteredMetrics = (
.filter((data) => data && data.source === 'metrics')
.map((data) => data && data.name);
return requiredMetrics.filter((metric) => {
- const metricModelCreator = metrics.tsvb[metric];
+ const metricModelCreator = metrics?.tsvb[metric] ?? null;
// We just need to get a dummy version of the model so we can filter
// using the `requires` attribute.
- const metricModel = metricModelCreator(TIMESTAMP_FIELD, 'test', '>=1m');
- return metricMetadata.some((m) => m && metricModel.requires.includes(m));
+ const metricModel = metricModelCreator
+ ? metricModelCreator(TIMESTAMP_FIELD, 'test', '>=1m')
+ : { requires: [''] }; // when tsvb is not defined (host & container)
+ return metricMetadata.some((m) => m && metricModel?.requires?.includes(m));
});
};
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/index.ts
index faa848192fd46..44fe7d0cb1a8c 100644
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/index.ts
+++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/index.ts
@@ -10,16 +10,6 @@ import { cpu } from './snapshot/cpu';
import { memory } from './snapshot/memory';
import { rx } from './snapshot/rx';
import { tx } from './snapshot/tx';
-import { containerCpuKernel } from './tsvb/container_cpu_kernel';
-import { containerCpuUsage } from './tsvb/container_cpu_usage';
-import { containerDiskIOOps } from './tsvb/container_diskio_ops';
-import { containerDiskIOBytes } from './tsvb/container_disk_io_bytes';
-import { containerK8sCpuUsage } from './tsvb/container_k8s_cpu_usage';
-import { containerK8sMemoryUsage } from './tsvb/container_k8s_memory_usage';
-import { containerK8sOverview } from './tsvb/container_k8s_overview';
-import { containerMemory } from './tsvb/container_memory';
-import { containerNetworkTraffic } from './tsvb/container_network_traffic';
-import { containerOverview } from './tsvb/container_overview';
import type { ContainerFormulas } from './formulas';
import { ContainerCharts } from './charts';
@@ -30,18 +20,6 @@ export const containerSnapshotMetricTypes = Object.keys(containerSnapshotMetrics
>;
export const metrics: InventoryMetricsWithCharts = {
- tsvb: {
- containerOverview,
- containerCpuUsage,
- containerCpuKernel,
- containerDiskIOOps,
- containerDiskIOBytes,
- containerNetworkTraffic,
- containerMemory,
- containerK8sCpuUsage,
- containerK8sOverview,
- containerK8sMemoryUsage,
- },
snapshot: containerSnapshotMetrics,
getFormulas: async () => await import('./formulas').then(({ formulas }) => formulas),
getCharts: async () => await import('./charts').then(({ charts }) => charts),
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_cpu_kernel.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_cpu_kernel.ts
deleted file mode 100644
index f469a9e86ad49..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_cpu_kernel.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerCpuKernel: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerCpuKernel',
- requires: ['docker.cpu'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'kernel',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.cpu.kernel.pct',
- id: 'avg-cpu-kernel',
- type: 'avg',
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_cpu_usage.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_cpu_usage.ts
deleted file mode 100644
index f4efc7de43663..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_cpu_usage.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerCpuUsage: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerCpuUsage',
- requires: ['docker.cpu'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'cpu',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.cpu.total.pct',
- id: 'avg-cpu-total',
- type: 'avg',
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_disk_io_bytes.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_disk_io_bytes.ts
deleted file mode 100644
index 7320349f92e8d..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_disk_io_bytes.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerDiskIOBytes: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerDiskIOBytes',
- requires: ['docker.diskio'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'read',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.diskio.read.bytes',
- id: 'max-diskio-read-bytes',
- type: 'max',
- },
- {
- field: 'max-diskio-read-bytes',
- id: 'deriv-max-diskio-read-bytes',
- type: 'derivative',
- unit: '1s',
- },
- {
- id: 'posonly-deriv-max-diskio-read-bytes',
- type: 'calculation',
- variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-diskio-read-bytes' }],
- script: 'params.rate > 0.0 ? params.rate : 0.0',
- },
- ],
- },
- {
- id: 'write',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.diskio.write.bytes',
- id: 'max-diskio-write-bytes',
- type: 'max',
- },
- {
- field: 'max-diskio-write-bytes',
- id: 'deriv-max-diskio-write-bytes',
- type: 'derivative',
- unit: '1s',
- },
- {
- id: 'posonly-deriv-max-diskio-write-bytes',
- type: 'calculation',
- variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-diskio-write-bytes' }],
- script: 'params.rate > 0.0 ? params.rate : 0.0',
- },
- {
- id: 'calc-invert-rate',
- script: 'params.rate * -1',
- type: 'calculation',
- variables: [
- {
- field: 'posonly-deriv-max-diskio-write-bytes',
- id: 'var-rate',
- name: 'rate',
- },
- ],
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_diskio_ops.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_diskio_ops.ts
deleted file mode 100644
index a6887fb4e7638..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_diskio_ops.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerDiskIOOps: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerDiskIOOps',
- requires: ['docker.diskio'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'read',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.diskio.read.ops',
- id: 'max-diskio-read-ops',
- type: 'max',
- },
- {
- field: 'max-diskio-read-ops',
- id: 'deriv-max-diskio-read-ops',
- type: 'derivative',
- unit: '1s',
- },
- {
- id: 'posonly-deriv-max-diskio-read-ops',
- type: 'calculation',
- variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-diskio-read-ops' }],
- script: 'params.rate > 0.0 ? params.rate : 0.0',
- },
- ],
- },
- {
- id: 'write',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.diskio.write.ops',
- id: 'max-diskio-write-ops',
- type: 'max',
- },
- {
- field: 'max-diskio-write-ops',
- id: 'deriv-max-diskio-write-ops',
- type: 'derivative',
- unit: '1s',
- },
- {
- id: 'posonly-deriv-max-diskio-write-ops',
- type: 'calculation',
- variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-diskio-write-ops' }],
- script: 'params.rate > 0.0 ? params.rate : 0.0',
- },
- {
- id: 'calc-invert-rate',
- script: 'params.rate * -1',
- type: 'calculation',
- variables: [
- {
- field: 'posonly-deriv-max-diskio-write-ops',
- id: 'var-rate',
- name: 'rate',
- },
- ],
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_cpu_usage.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_cpu_usage.ts
deleted file mode 100644
index 67372f50d750b..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_cpu_usage.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerK8sCpuUsage: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerK8sCpuUsage',
- requires: ['kubernetes.container'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'cpu',
- split_mode: 'everything',
- metrics: [
- {
- field: 'kubernetes.container.cpu.usage.limit.pct',
- id: 'avg-cpu',
- type: 'avg',
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_memory_usage.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_memory_usage.ts
deleted file mode 100644
index 066d993817b75..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_memory_usage.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerK8sMemoryUsage: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerK8sMemoryUsage',
- requires: ['kubernetes.container'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'memory',
- split_mode: 'everything',
- metrics: [
- {
- field: 'kubernetes.container.memory.usage.limit.pct',
- id: 'avg-memory',
- type: 'avg',
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_overview.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_overview.ts
deleted file mode 100644
index 470704140efb4..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_k8s_overview.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerK8sOverview: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerK8sOverview',
- requires: ['kubernetes.container'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'cpu',
- split_mode: 'everything',
- metrics: [
- {
- field: 'kubernetes.container.cpu.usage.limit.pct',
- id: 'avg-cpu-total',
- type: 'avg',
- },
- ],
- },
- {
- id: 'memory',
- split_mode: 'everything',
- metrics: [
- {
- field: 'kubernetes.container.memory.usage.limit.pct',
- id: 'avg-memory-total',
- type: 'avg',
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_memory.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_memory.ts
deleted file mode 100644
index e0572692d60fc..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_memory.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerMemory: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerMemory',
- requires: ['docker.memory'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'memory',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.memory.usage.pct',
- id: 'avg-memory',
- type: 'avg',
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_network_traffic.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_network_traffic.ts
deleted file mode 100644
index d957d51a41648..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_network_traffic.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerNetworkTraffic: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerNetworkTraffic',
- requires: ['docker.network'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'tx',
- metrics: [
- {
- field: 'docker.network.outbound.bytes',
- id: 'max-net-out',
- type: 'max',
- },
- {
- field: 'max-net-out',
- id: 'deriv-max-net-out',
- type: 'derivative',
- unit: '1s',
- },
- {
- id: 'posonly-deriv-max-net-out',
- type: 'calculation',
- variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-net-out' }],
- script: 'params.rate > 0.0 ? params.rate : 0.0',
- },
- {
- function: 'sum',
- id: 'seriesagg-sum',
- type: 'series_agg',
- },
- ],
- split_mode: 'terms',
- terms_field: 'docker.network.interface',
- },
- {
- id: 'rx',
- metrics: [
- {
- field: 'docker.network.inbound.bytes',
- id: 'max-net-in',
- type: 'max',
- },
- {
- field: 'max-net-in',
- id: 'deriv-max-net-in',
- type: 'derivative',
- unit: '1s',
- },
- {
- id: 'posonly-deriv-max-net-in',
- type: 'calculation',
- variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-net-in' }],
- script: 'params.rate > 0.0 ? params.rate : 0.0',
- },
- {
- id: 'calc-invert-rate',
- script: 'params.rate * -1',
- type: 'calculation',
- variables: [
- {
- field: 'posonly-deriv-max-net-in',
- id: 'var-rate',
- name: 'rate',
- },
- ],
- },
- {
- function: 'sum',
- id: 'seriesagg-sum',
- type: 'series_agg',
- },
- ],
- split_mode: 'terms',
- terms_field: 'docker.network.interface',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_overview.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_overview.ts
deleted file mode 100644
index c2d234be2eed7..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/container/metrics/tsvb/container_overview.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const containerOverview: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'containerOverview',
- requires: ['docker'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'cpu',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.cpu.total.pct',
- id: 'avg-cpu-total',
- type: 'avg',
- },
- ],
- },
- {
- id: 'memory',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.memory.usage.pct',
- id: 'avg-memory',
- type: 'avg',
- },
- ],
- },
- {
- id: 'tx',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.network.out.bytes',
- id: 'avg-network-out',
- type: 'avg',
- },
- ],
- },
- {
- id: 'rx',
- split_mode: 'everything',
- metrics: [
- {
- field: 'docker.network.in.bytes',
- id: 'avg-network-in',
- type: 'avg',
- },
- ],
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts
index ce56e7bd59b0b..82408084e9472 100644
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts
+++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
import { snapshot } from './snapshot';
-import { tsvb } from './tsvb';
import { InventoryMetricsWithCharts } from '../../types';
import type { HostFormulas } from './formulas';
import type { HostCharts } from './charts';
@@ -18,7 +17,6 @@ export const hostSnapshotMetricTypes = Object.keys(exposedHostSnapshotMetrics) a
>;
export const metrics: InventoryMetricsWithCharts = {
- tsvb,
snapshot,
getFormulas: async () => await import('./formulas').then(({ formulas }) => formulas),
getCharts: async () => await import('./charts').then(({ charts }) => charts),
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_cpu_usage.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_cpu_usage.ts
deleted file mode 100644
index bcafeb4ebc4cf..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_cpu_usage.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostCpuUsage: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostCpuUsage',
- requires: ['system.cpu'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'user',
- metrics: [
- {
- field: 'system.cpu.user.pct',
- id: 'avg-cpu-user',
- type: 'avg',
- },
- {
- field: 'system.cpu.cores',
- id: 'max-cpu-cores',
- type: 'max',
- },
- {
- id: 'calc-avg-cores',
- script: 'params.avg / params.cores',
- type: 'calculation',
- variables: [
- {
- field: 'max-cpu-cores',
- id: 'var-cores',
- name: 'cores',
- },
- {
- field: 'avg-cpu-user',
- id: 'var-avg',
- name: 'avg',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'system',
- metrics: [
- {
- field: 'system.cpu.system.pct',
- id: 'avg-cpu-system',
- type: 'avg',
- },
- {
- field: 'system.cpu.cores',
- id: 'max-cpu-cores',
- type: 'max',
- },
- {
- id: 'calc-avg-cores',
- script: 'params.avg / params.cores',
- type: 'calculation',
- variables: [
- {
- field: 'max-cpu-cores',
- id: 'var-cores',
- name: 'cores',
- },
- {
- field: 'avg-cpu-system',
- id: 'var-avg',
- name: 'avg',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'steal',
- metrics: [
- {
- field: 'system.cpu.steal.pct',
- id: 'avg-cpu-steal',
- type: 'avg',
- },
- {
- field: 'system.cpu.cores',
- id: 'max-cpu-cores',
- type: 'max',
- },
- {
- id: 'calc-avg-cores',
- script: 'params.avg / params.cores',
- type: 'calculation',
- variables: [
- {
- field: 'avg-cpu-steal',
- id: 'var-avg',
- name: 'avg',
- },
- {
- field: 'max-cpu-cores',
- id: 'var-cores',
- name: 'cores',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'irq',
- metrics: [
- {
- field: 'system.cpu.irq.pct',
- id: 'avg-cpu-irq',
- type: 'avg',
- },
- {
- field: 'system.cpu.cores',
- id: 'max-cpu-cores',
- type: 'max',
- },
- {
- id: 'calc-avg-cores',
- script: 'params.avg / params.cores',
- type: 'calculation',
- variables: [
- {
- field: 'max-cpu-cores',
- id: 'var-cores',
- name: 'cores',
- },
- {
- field: 'avg-cpu-irq',
- id: 'var-avg',
- name: 'avg',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'softirq',
- metrics: [
- {
- field: 'system.cpu.softirq.pct',
- id: 'avg-cpu-softirq',
- type: 'avg',
- },
- {
- field: 'system.cpu.cores',
- id: 'max-cpu-cores',
- type: 'max',
- },
- {
- id: 'calc-avg-cores',
- script: 'params.avg / params.cores',
- type: 'calculation',
- variables: [
- {
- field: 'max-cpu-cores',
- id: 'var-cores',
- name: 'cores',
- },
- {
- field: 'avg-cpu-softirq',
- id: 'var-avg',
- name: 'avg',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'iowait',
- metrics: [
- {
- field: 'system.cpu.iowait.pct',
- id: 'avg-cpu-iowait',
- type: 'avg',
- },
- {
- field: 'system.cpu.cores',
- id: 'max-cpu-cores',
- type: 'max',
- },
- {
- id: 'calc-avg-cores',
- script: 'params.avg / params.cores',
- type: 'calculation',
- variables: [
- {
- field: 'max-cpu-cores',
- id: 'var-cores',
- name: 'cores',
- },
- {
- field: 'avg-cpu-iowait',
- id: 'var-avg',
- name: 'avg',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'nice',
- metrics: [
- {
- field: 'system.cpu.nice.pct',
- id: 'avg-cpu-nice',
- type: 'avg',
- },
- {
- field: 'system.cpu.cores',
- id: 'max-cpu-cores',
- type: 'max',
- },
- {
- id: 'calc-avg-cores',
- script: 'params.avg / params.cores',
- type: 'calculation',
- variables: [
- {
- field: 'max-cpu-cores',
- id: 'var-cores',
- name: 'cores',
- },
- {
- field: 'avg-cpu-nice',
- id: 'var-avg',
- name: 'avg',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_info.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_info.ts
deleted file mode 100644
index 4cc2b574362d7..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_info.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerInfo: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostDockerInfo',
- requires: ['docker.info'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'running',
- metrics: [
- {
- field: 'docker.info.containers.running',
- id: 'max-running',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'paused',
- metrics: [
- {
- field: 'docker.info.containers.paused',
- id: 'max-paused',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'stopped',
- metrics: [
- {
- field: 'docker.info.containers.stopped',
- id: 'max-stopped',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts
deleted file mode 100644
index df56a21dbf5b7..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerOverview: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostDockerOverview',
- requires: ['docker.info'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'top_n',
- series: [
- {
- id: 'total',
- metrics: [
- {
- field: 'docker.info.containers.total',
- id: 'max-total',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'running',
- metrics: [
- {
- field: 'docker.info.containers.running',
- id: 'max-running',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'paused',
- metrics: [
- {
- field: 'docker.info.containers.paused',
- id: 'max-paused',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'stopped',
- metrics: [
- {
- field: 'docker.info.containers.stopped',
- id: 'max-stopped',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_cpu.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_cpu.ts
deleted file mode 100644
index fd9eb97419736..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_cpu.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerTop5ByCpu: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostDockerTop5ByCpu',
- requires: ['docker.cpu'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'avg-cpu',
- metrics: [
- {
- field: 'docker.cpu.total.pct',
- id: 'avg-cpu-metric',
- type: 'avg',
- },
- ],
- split_mode: 'terms',
- terms_field: 'container.name',
- terms_order_by: 'avg-cpu',
- terms_size: 5,
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_memory.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_memory.ts
deleted file mode 100644
index cad828671d232..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_memory.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerTop5ByMemory: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostDockerTop5ByMemory',
- requires: ['docker.memory'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'avg-memory',
- metrics: [
- {
- field: 'docker.memory.usage.pct',
- id: 'avg-memory-metric',
- type: 'avg',
- },
- ],
- split_mode: 'terms',
- terms_field: 'container.name',
- terms_order_by: 'avg-memory',
- terms_size: 5,
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_filesystem.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_filesystem.ts
deleted file mode 100644
index ce284345410fc..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_filesystem.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostFilesystem: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostFilesystem',
- requires: ['system.filesystem'],
- filter: 'system.filesystem.device_name:\\/*',
- index_pattern: indexPattern,
- time_field: timeField,
- interval,
- type: 'timeseries',
- series: [
- {
- id: 'used',
- metrics: [
- {
- field: 'system.filesystem.used.pct',
- id: 'avg-filesystem-used',
- type: 'avg',
- },
- ],
- split_mode: 'terms',
- terms_field: 'system.filesystem.device_name',
- terms_order_by: 'used',
- terms_size: 5,
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_cpu_cap.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_cpu_cap.ts
deleted file mode 100644
index d33e4cdeb34cd..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_cpu_cap.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sCpuCap: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostK8sCpuCap',
- map_field_to: 'kubernetes.node.name',
- requires: ['kubernetes.node'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'capacity',
- metrics: [
- {
- field: 'kubernetes.node.cpu.allocatable.cores',
- id: 'max-cpu-cap',
- type: 'max',
- },
- {
- id: 'calc-nanocores',
- type: 'calculation',
- variables: [
- {
- id: 'var-cores',
- field: 'max-cpu-cap',
- name: 'cores',
- },
- ],
- script: 'params.cores * 1000000000',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'used',
- metrics: [
- {
- field: 'kubernetes.node.cpu.usage.nanocores',
- id: 'avg-cpu-usage',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_disk_cap.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_disk_cap.ts
deleted file mode 100644
index e9e512a136631..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_disk_cap.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-export const hostK8sDiskCap: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostK8sDiskCap',
- map_field_to: 'kubernetes.node.name',
- requires: ['kubernetes.node'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'capacity',
- metrics: [
- {
- field: 'kubernetes.node.fs.capacity.bytes',
- id: 'max-fs-cap',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'used',
- metrics: [
- {
- field: 'kubernetes.node.fs.used.bytes',
- id: 'avg-fs-used',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_memory_cap.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_memory_cap.ts
deleted file mode 100644
index ccc227ef1854e..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_memory_cap.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sMemoryCap: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostK8sMemoryCap',
- map_field_to: 'kubernetes.node.name',
- requires: ['kubernetes.node'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'capacity',
- metrics: [
- {
- field: 'kubernetes.node.memory.allocatable.bytes',
- id: 'max-memory-cap',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'used',
- metrics: [
- {
- field: 'kubernetes.node.memory.usage.bytes',
- id: 'avg-memory-usage',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts
deleted file mode 100644
index 2da74d50dff1b..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sOverview: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostK8sOverview',
- requires: ['kubernetes'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'top_n',
- series: [
- {
- id: 'cpucap',
- split_mode: 'everything',
- metrics: [
- {
- field: 'kubernetes.node.cpu.allocatable.cores',
- id: 'max-cpu-cap',
- type: 'max',
- },
- {
- field: 'kubernetes.node.cpu.usage.nanocores',
- id: 'avg-cpu-usage',
- type: 'avg',
- },
- {
- id: 'calc-used-cap',
- script: 'params.used / (params.cap * 1000000000)',
- type: 'calculation',
- variables: [
- {
- field: 'max-cpu-cap',
- id: 'var-cap',
- name: 'cap',
- },
- {
- field: 'avg-cpu-usage',
- id: 'var-used',
- name: 'used',
- },
- ],
- },
- ],
- },
- {
- id: 'diskcap',
- metrics: [
- {
- field: 'kubernetes.node.fs.capacity.bytes',
- id: 'max-fs-cap',
- type: 'max',
- },
- {
- field: 'kubernetes.node.fs.used.bytes',
- id: 'avg-fs-used',
- type: 'avg',
- },
- {
- id: 'calc-used-cap',
- script: 'params.used / params.cap',
- type: 'calculation',
- variables: [
- {
- field: 'max-fs-cap',
- id: 'var-cap',
- name: 'cap',
- },
- {
- field: 'avg-fs-used',
- id: 'var-used',
- name: 'used',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'memorycap',
- metrics: [
- {
- field: 'kubernetes.node.memory.allocatable.bytes',
- id: 'max-memory-cap',
- type: 'max',
- },
- {
- field: 'kubernetes.node.memory.usage.bytes',
- id: 'avg-memory-usage',
- type: 'avg',
- },
- {
- id: 'calc-used-cap',
- script: 'params.used / params.cap',
- type: 'calculation',
- variables: [
- {
- field: 'max-memory-cap',
- id: 'var-cap',
- name: 'cap',
- },
- {
- field: 'avg-memory-usage',
- id: 'var-used',
- name: 'used',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'podcap',
- metrics: [
- {
- field: 'kubernetes.node.pod.capacity.total',
- id: 'max-pod-cap',
- type: 'max',
- },
- {
- field: 'kubernetes.pod.uid',
- id: 'card-pod-name',
- type: 'cardinality',
- },
- {
- id: 'calc-used-cap',
- script: 'params.used / params.cap',
- type: 'calculation',
- variables: [
- {
- field: 'max-pod-cap',
- id: 'var-cap',
- name: 'cap',
- },
- {
- field: 'card-pod-name',
- id: 'var-used',
- name: 'used',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_pod_cap.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_pod_cap.ts
deleted file mode 100644
index 85cb798eaf9b9..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_k8s_pod_cap.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sPodCap: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostK8sPodCap',
- requires: ['kubernetes.node'],
- map_field_to: 'kubernetes.node.name',
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
-
- series: [
- {
- id: 'capacity',
- metrics: [
- {
- field: 'kubernetes.node.pod.allocatable.total',
- id: 'max-pod-cap',
- type: 'max',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'used',
- metrics: [
- {
- field: 'kubernetes.pod.uid',
- id: 'avg-pod',
- type: 'cardinality',
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_load.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_load.ts
deleted file mode 100644
index bef170c743e6c..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_load.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostLoad: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostLoad',
- requires: ['system.cpu'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'load_1m',
- metrics: [
- {
- field: 'system.load.1',
- id: 'avg-load-1m',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'load_5m',
- metrics: [
- {
- field: 'system.load.5',
- id: 'avg-load-5m',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'load_15m',
- metrics: [
- {
- field: 'system.load.15',
- id: 'avg-load-15m',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_memory_usage.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_memory_usage.ts
deleted file mode 100644
index bda81dea4bd28..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_memory_usage.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostMemoryUsage: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostMemoryUsage',
- requires: ['system.memory'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'free',
- metrics: [
- {
- field: 'system.memory.free',
- id: 'avg-memory-free',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'used',
- metrics: [
- {
- field: 'system.memory.actual.used.bytes',
- id: 'avg-memory-used',
- type: 'avg',
- },
- ],
- split_mode: 'everything',
- },
- {
- id: 'cache',
- metrics: [
- {
- field: 'system.memory.actual.used.bytes',
- id: 'avg-memory-actual-used',
- type: 'avg',
- },
- {
- field: 'system.memory.used.bytes',
- id: 'avg-memory-used',
- type: 'avg',
- },
- {
- id: 'calc-used-actual',
- script: 'params.used - params.actual',
- type: 'calculation',
- variables: [
- {
- field: 'avg-memory-actual-used',
- id: 'var-actual',
- name: 'actual',
- },
- {
- field: 'avg-memory-used',
- id: 'var-used',
- name: 'used',
- },
- ],
- },
- ],
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts
deleted file mode 100644
index b3dfc5e91ba0a..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostNetworkTraffic: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostNetworkTraffic',
- requires: ['system.network'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'timeseries',
- series: [
- {
- id: 'tx',
- metrics: [
- {
- field: 'host.network.egress.bytes',
- id: 'avg-net-out',
- type: 'avg',
- },
- {
- id: 'max-period',
- type: 'max',
- field: 'metricset.period',
- },
- {
- id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
- type: 'calculation',
- variables: [
- {
- id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
- name: 'value',
- field: 'avg-net-out',
- },
- {
- id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
- name: 'period',
- field: 'max-period',
- },
- ],
- script: 'params.value / (params.period / 1000)',
- },
- ],
- filter: {
- language: 'kuery',
- query: 'host.network.egress.bytes : * ',
- },
- split_mode: 'everything',
- },
- {
- id: 'rx',
- metrics: [
- {
- field: 'host.network.ingress.bytes',
- id: 'avg-net-in',
- type: 'avg',
- },
- {
- id: 'calc-invert-rate',
- script: 'params.rate * -1',
- type: 'calculation',
- variables: [
- {
- field: 'avg-net-in',
- id: 'var-rate',
- name: 'rate',
- },
- ],
- },
- {
- id: 'max-period',
- type: 'max',
- field: 'metricset.period',
- },
- {
- id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
- type: 'calculation',
- variables: [
- {
- id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
- name: 'value',
- field: 'calc-invert-rate',
- },
- {
- id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
- name: 'period',
- field: 'max-period',
- },
- ],
- script: 'params.value / (params.period / 1000)',
- },
- ],
- filter: {
- language: 'kuery',
- query: 'host.network.ingress.bytes : * ',
- },
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_system_overview.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_system_overview.ts
deleted file mode 100644
index 69ebd0aa35947..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/host_system_overview.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostSystemOverview: TSVBMetricModelCreator = (
- timeField,
- indexPattern,
- interval
-): TSVBMetricModel => ({
- id: 'hostSystemOverview',
- requires: ['system.cpu', 'system.memory', 'system.load', 'system.network'],
- index_pattern: indexPattern,
- interval,
- time_field: timeField,
- type: 'top_n',
- series: [
- {
- id: 'cpu',
- split_mode: 'everything',
- metrics: [
- {
- field: 'system.cpu.total.norm.pct',
- id: 'avg-cpu-total',
- type: 'avg',
- },
- ],
- },
- {
- id: 'load',
- split_mode: 'everything',
- metrics: [
- {
- field: 'system.load.5',
- id: 'avg-load-5m',
- type: 'avg',
- },
- ],
- },
- {
- id: 'memory',
- split_mode: 'everything',
- metrics: [
- {
- field: 'system.memory.actual.used.pct',
- id: 'avg-memory-actual-used',
- type: 'avg',
- },
- ],
- },
- {
- id: 'rx',
- metrics: [
- {
- field: 'host.network.ingress.bytes',
- id: 'avg-net-in',
- type: 'avg',
- },
- {
- id: 'max-period',
- type: 'max',
- field: 'metricset.period',
- },
- {
- id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
- type: 'calculation',
- variables: [
- {
- id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
- name: 'value',
- field: 'avg-net-in',
- },
- {
- id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
- name: 'period',
- field: 'max-period',
- },
- ],
- script: 'params.value / (params.period / 1000)',
- },
- ],
- filter: {
- language: 'kuery',
- query: 'host.network.ingress.bytes : * ',
- },
- split_mode: 'everything',
- },
- {
- id: 'tx',
- metrics: [
- {
- field: 'host.network.egress.bytes',
- id: 'avg-net-out',
- type: 'avg',
- },
- {
- id: 'max-period',
- type: 'max',
- field: 'metricset.period',
- },
- {
- id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
- type: 'calculation',
- variables: [
- {
- id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
- name: 'value',
- field: 'avg-net-out',
- },
- {
- id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
- name: 'period',
- field: 'max-period',
- },
- ],
- script: 'params.value / (params.period / 1000)',
- },
- ],
- filter: {
- language: 'kuery',
- query: 'host.network.egress.bytes : * ',
- },
- split_mode: 'everything',
- },
- ],
-});
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/index.ts
deleted file mode 100644
index fb91ee9dd8b27..0000000000000
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/tsvb/index.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-
-import { hostSystemOverview } from './host_system_overview';
-import { hostCpuUsage } from './host_cpu_usage';
-import { hostLoad } from './host_load';
-import { hostMemoryUsage } from './host_memory_usage';
-import { hostNetworkTraffic } from './host_network_traffic';
-import { hostFilesystem } from './host_filesystem';
-
-import { hostK8sOverview } from './host_k8s_overview';
-import { hostK8sCpuCap } from './host_k8s_cpu_cap';
-import { hostK8sPodCap } from './host_k8s_pod_cap';
-import { hostK8sDiskCap } from './host_k8s_disk_cap';
-import { hostK8sMemoryCap } from './host_k8s_memory_cap';
-
-import { hostDockerTop5ByMemory } from './host_docker_top_5_by_memory';
-import { hostDockerTop5ByCpu } from './host_docker_top_5_by_cpu';
-import { hostDockerOverview } from './host_docker_overview';
-import { hostDockerInfo } from './host_docker_info';
-
-export const tsvb = {
- hostSystemOverview,
- hostCpuUsage,
- hostLoad,
- hostMemoryUsage,
- hostNetworkTraffic,
- hostFilesystem,
- hostK8sOverview,
- hostK8sCpuCap,
- hostK8sPodCap,
- hostK8sDiskCap,
- hostK8sMemoryCap,
- hostDockerOverview,
- hostDockerInfo,
- hostDockerTop5ByMemory,
- hostDockerTop5ByCpu,
-};
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/metrics.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/metrics.ts
index 00937a064a7b8..510e655c0b8d7 100644
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/metrics.ts
+++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/metrics.ts
@@ -5,10 +5,8 @@
* 2.0.
*/
-import { metrics as hostMetrics } from './host/metrics';
import { metrics as sharedMetrics } from './shared/metrics';
import { metrics as podMetrics } from './kubernetes/pod/metrics';
-import { metrics as containerMetrics } from './container/metrics';
import { metrics as awsEC2Metrics } from './aws_ec2/metrics';
import { metrics as awsS3Metrics } from './aws_s3/metrics';
import { metrics as awsRDSMetrics } from './aws_rds/metrics';
@@ -16,10 +14,8 @@ import { metrics as awsSQSMetrics } from './aws_sqs/metrics';
export const metrics = {
tsvb: {
- ...hostMetrics.tsvb,
...sharedMetrics.tsvb,
...podMetrics.tsvb,
- ...containerMetrics.tsvb,
...awsEC2Metrics.tsvb,
...awsS3Metrics.tsvb,
...awsRDSMetrics.tsvb,
diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts
index f4054f289ae7a..8ad0ff886ebe6 100644
--- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts
+++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts
@@ -46,7 +46,6 @@ export const InventoryMetricRT = rt.keyof({
hostSystemOverview: null,
hostCpuUsageTotal: null,
hostCpuUsage: null,
- hostFilesystem: null,
hostK8sOverview: null,
hostK8sCpuCap: null,
hostK8sDiskCap: null,
@@ -55,17 +54,12 @@ export const InventoryMetricRT = rt.keyof({
hostLoad: null,
hostMemoryUsage: null,
hostNetworkTraffic: null,
- hostDockerOverview: null,
- hostDockerInfo: null,
- hostDockerTop5ByCpu: null,
- hostDockerTop5ByMemory: null,
podOverview: null,
podCpuUsage: null,
podMemoryUsage: null,
podLogUsage: null,
podNetworkTraffic: null,
containerOverview: null,
- containerCpuKernel: null,
containerCpuUsage: null,
containerDiskIOOps: null,
containerDiskIOBytes: null,
@@ -276,7 +270,7 @@ export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys);
export type SnapshotMetricType = rt.TypeOf;
export interface InventoryMetrics {
- tsvb: { [name: string]: TSVBMetricModelCreator };
+ tsvb?: { [name: string]: TSVBMetricModelCreator };
snapshot: { [name: string]: MetricsUIAggregation | undefined };
defaultSnapshot: SnapshotMetricType;
/** This is used by the inventory view to calculate the appropriate amount of time for the metrics detail page. Some metrics like awsS3 require multiple days where others like host only need an hour.*/
diff --git a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/helpers/map_rules_params_with_flyout.ts b/x-pack/plugins/observability_solution/observability/public/components/alert_overview/helpers/map_rules_params_with_flyout.ts
index b5819631406d7..e9e8714bf85b9 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/helpers/map_rules_params_with_flyout.ts
+++ b/x-pack/plugins/observability_solution/observability/public/components/alert_overview/helpers/map_rules_params_with_flyout.ts
@@ -286,7 +286,7 @@ export const mapRuleParamsWithFlyout = (alert: TopAlert): FlyoutThresholdData[]
const { thresholdComparator, threshold } = ruleParams as EsQueryRuleParams;
const ESQueryFlyoutMap = {
observedValue: [alert.fields[ALERT_EVALUATION_VALUE]],
- threshold: threshold.join(' AND '),
+ threshold: [threshold].flat().join(' AND '),
comparator: thresholdComparator,
pctAboveThreshold: getPctAboveThreshold(
threshold,
diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts
index fe5f74529121e..9ed34399e74f2 100644
--- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts
+++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts
@@ -369,7 +369,10 @@ export class SyntheticsPrivateLocation {
return await this.server.fleet.packagePolicyService.bulkCreate(
soClient,
esClient,
- newPolicies
+ newPolicies,
+ {
+ asyncDeploy: true,
+ }
);
}
}
@@ -384,6 +387,7 @@ export class SyntheticsPrivateLocation {
policiesToUpdate,
{
force: true,
+ asyncDeploy: true,
}
);
return failedPolicies;
@@ -401,6 +405,7 @@ export class SyntheticsPrivateLocation {
policyIdsToDelete,
{
force: true,
+ asyncDeploy: true,
}
);
} catch (e) {
@@ -430,6 +435,7 @@ export class SyntheticsPrivateLocation {
policyIdsToDelete,
{
force: true,
+ asyncDeploy: true,
}
);
const failedPolicies = result?.filter((policy) => {
diff --git a/x-pack/plugins/osquery/cypress/tasks/navigation.ts b/x-pack/plugins/osquery/cypress/tasks/navigation.ts
index 72107207d7a8c..a0747706ffc15 100644
--- a/x-pack/plugins/osquery/cypress/tasks/navigation.ts
+++ b/x-pack/plugins/osquery/cypress/tasks/navigation.ts
@@ -45,8 +45,6 @@ export enum NAV_SEARCH_INPUT_OSQUERY_RESULTS {
export const NEW_FEATURES_TOUR_STORAGE_KEYS = {
RULE_MANAGEMENT_PAGE: 'securitySolution.rulesManagementPage.newFeaturesTour.v8.13',
TIMELINES: 'securitySolution.security.timelineFlyoutHeader.saveTimelineTour',
- TIMELINE: 'securitySolution.timeline.newFeaturesTour.v8.12',
- FLYOUT: 'securitySolution.documentDetails.newFeaturesTour.v8.14',
KNOWLEDGE_BASE: 'elasticAssistant.knowledgeBase.newFeaturesTour.v8.16',
};
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index 68aaf7bf9cf04..137afe7ba9112 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -424,7 +424,6 @@ export const RULES_TABLE_MAX_PAGE_SIZE = 100;
export const NEW_FEATURES_TOUR_STORAGE_KEYS = {
RULE_MANAGEMENT_PAGE: 'securitySolution.rulesManagementPage.newFeaturesTour.v8.13',
TIMELINES: 'securitySolution.security.timelineFlyoutHeader.saveTimelineTour',
- FLYOUT: 'securitySolution.documentDetails.newFeaturesTour.v8.14',
};
export const RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY =
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/tour.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/tour.test.tsx
deleted file mode 100644
index bb288b5c7afaa..0000000000000
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/tour.test.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.
- */
-
-import React from 'react';
-import { render, waitFor, fireEvent } from '@testing-library/react';
-import { LeftPanelTour } from './tour';
-import { DocumentDetailsContext } from '../../shared/context';
-import { mockContextValue } from '../../shared/mocks/mock_context';
-import {
- createMockStore,
- createSecuritySolutionStorageMock,
- TestProviders,
-} from '../../../../common/mock';
-import { useKibana as mockUseKibana } from '../../../../common/lib/kibana/__mocks__';
-import { useKibana } from '../../../../common/lib/kibana';
-import { FLYOUT_TOUR_CONFIG_ANCHORS } from '../../shared/utils/tour_step_config';
-import { FLYOUT_TOUR_TEST_ID } from '../../shared/components/test_ids';
-import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
-import { Flyouts } from '../../shared/constants/flyouts';
-
-jest.mock('../../../../common/lib/kibana');
-jest.mock('../../shared/hooks/use_which_flyout');
-
-const mockedUseKibana = mockUseKibana();
-
-const { storage: storageMock } = createSecuritySolutionStorageMock();
-const mockStore = createMockStore(undefined, undefined, undefined, {
- ...storageMock,
-});
-
-const renderLeftPanelTour = (context: DocumentDetailsContext = mockContextValue) =>
- render(
-
-
-
- {Object.values(FLYOUT_TOUR_CONFIG_ANCHORS).map((i, idx) => (
-
- ))}
-
-
- );
-
-describe('', () => {
- beforeEach(() => {
- (useKibana as jest.Mock).mockReturnValue({
- ...mockedUseKibana,
- services: {
- ...mockedUseKibana.services,
- storage: storageMock,
- },
- });
- (useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.securitySolution);
-
- storageMock.clear();
- });
-
- it('should render left panel tour for alerts starting as step 4', async () => {
- storageMock.set('securitySolution.documentDetails.newFeaturesTour.v8.14', {
- currentTourStep: 4,
- isTourActive: true,
- });
-
- const { getByText, getByTestId } = renderLeftPanelTour();
- await waitFor(() => {
- expect(getByTestId(`${FLYOUT_TOUR_TEST_ID}-4`)).toBeVisible();
- });
- fireEvent.click(getByText('Next'));
- await waitFor(() => {
- expect(getByTestId(`${FLYOUT_TOUR_TEST_ID}-5`)).toBeVisible();
- });
- await waitFor(() => {
- expect(getByText('Finish')).toBeVisible();
- });
- });
-
- it('should not render left panel tour for preview', () => {
- storageMock.set('securitySolution.documentDetails.newFeaturesTour.v8.14', {
- currentTourStep: 3,
- isTourActive: true,
- });
-
- const { queryByTestId, queryByText } = renderLeftPanelTour({
- ...mockContextValue,
- isPreview: true,
- });
- expect(queryByTestId(`${FLYOUT_TOUR_TEST_ID}-4`)).not.toBeInTheDocument();
- expect(queryByText('Next')).not.toBeInTheDocument();
- });
-
- it('should not render left panel tour for non-alerts', async () => {
- storageMock.set('securitySolution.documentDetails.newFeaturesTour.v8.14', {
- currentTourStep: 3,
- isTourActive: true,
- });
-
- const { queryByTestId, queryByText } = renderLeftPanelTour({
- ...mockContextValue,
- getFieldsData: () => '',
- });
- expect(queryByTestId(`${FLYOUT_TOUR_TEST_ID}-4`)).not.toBeInTheDocument();
- expect(queryByText('Next')).not.toBeInTheDocument();
- });
-
- it('should not render left panel tour for flyout in timeline', () => {
- (useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.timeline);
- storageMock.set('securitySolution.documentDetails.newFeaturesTour.v8.14', {
- currentTourStep: 3,
- isTourActive: true,
- });
-
- const { queryByTestId, queryByText } = renderLeftPanelTour({
- ...mockContextValue,
- isPreview: true,
- });
- expect(queryByTestId(`${FLYOUT_TOUR_TEST_ID}-4`)).not.toBeInTheDocument();
- expect(queryByText('Next')).not.toBeInTheDocument();
- });
-});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/tour.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/tour.tsx
deleted file mode 100644
index 196d9113457a3..0000000000000
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/tour.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.
- */
-
-import React, { memo, useMemo } from 'react';
-import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
-import { getField } from '../../shared/utils';
-import { EventKind } from '../../shared/constants/event_kinds';
-import { useDocumentDetailsContext } from '../../shared/context';
-import { getLeftSectionTourSteps } from '../../shared/utils/tour_step_config';
-import { Flyouts } from '../../shared/constants/flyouts';
-import { FlyoutTour } from '../../shared/components/flyout_tour';
-
-/**
- * Guided tour for the left panel in details flyout
- */
-export const LeftPanelTour = memo(() => {
- const { getFieldsData, isPreview } = useDocumentDetailsContext();
- const eventKind = getField(getFieldsData('event.kind'));
- const isAlert = eventKind === EventKind.signal;
- const isTimelineFlyoutOpen = useWhichFlyout() === Flyouts.timeline;
- const showTour = isAlert && !isPreview && !isTimelineFlyoutOpen;
-
- const tourStepContent = useMemo(() => getLeftSectionTourSteps(), []);
-
- return showTour ? : null;
-});
-
-LeftPanelTour.displayName = 'LeftPanelTour';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx
index 32b0b10d61ffd..56375426c5f68 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx
@@ -22,7 +22,6 @@ import { getField } from '../shared/utils';
import { EventKind } from '../shared/constants/event_kinds';
import { useDocumentDetailsContext } from '../shared/context';
import type { DocumentDetailsProps } from '../shared/types';
-import { LeftPanelTour } from './components/tour';
export type LeftPanelPaths = 'visualize' | 'insights' | 'investigation' | 'response' | 'notes';
export const LeftPanelVisualizeTab: LeftPanelPaths = 'visualize';
@@ -85,7 +84,6 @@ export const LeftPanel: FC> = memo(({ path }) => {
return (
<>
-
-
-
+
),
'data-test-subj': INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID,
},
@@ -62,12 +58,10 @@ const insightsButtons: EuiButtonGroupOptionProps[] = [
{
id: PREVALENCE_TAB_ID,
label: (
-
-
-
+
),
'data-test-subj': INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID,
},
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts
index 1e99fb63d18a5..eb64c91b2143d 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts
@@ -17,12 +17,8 @@ const INSIGHTS_TAB_TEST_ID = `${PREFIX}InsightsTab` as const;
export const INSIGHTS_TAB_BUTTON_GROUP_TEST_ID = `${INSIGHTS_TAB_TEST_ID}ButtonGroup` as const;
export const INSIGHTS_TAB_ENTITIES_BUTTON_TEST_ID =
`${INSIGHTS_TAB_TEST_ID}EntitiesButton` as const;
-export const INSIGHTS_TAB_ENTITIES_BUTTON_LABEL_TEST_ID =
- `${INSIGHTS_TAB_TEST_ID}Entities` as const;
export const INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON_TEST_ID =
`${INSIGHTS_TAB_TEST_ID}ThreatIntelligenceButton` as const;
-export const INSIGHTS_TAB_PREVALENCE_BUTTON_LABEL_TEST_ID =
- `${INSIGHTS_TAB_TEST_ID}Prevalence` as const;
export const INSIGHTS_TAB_PREVALENCE_BUTTON_TEST_ID =
`${INSIGHTS_TAB_TEST_ID}PrevalenceButton` as const;
export const INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID =
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.test.tsx
deleted file mode 100644
index 20540184156b9..0000000000000
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.test.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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.
- */
-
-import React from 'react';
-import { render, waitFor, fireEvent } from '@testing-library/react';
-import { RightPanelTour } from './tour';
-import { DocumentDetailsContext } from '../../shared/context';
-import { mockContextValue } from '../../shared/mocks/mock_context';
-import {
- createMockStore,
- createSecuritySolutionStorageMock,
- TestProviders,
-} from '../../../../common/mock';
-import { useKibana as mockUseKibana } from '../../../../common/lib/kibana/__mocks__';
-import { useKibana } from '../../../../common/lib/kibana';
-import { FLYOUT_TOUR_CONFIG_ANCHORS } from '../../shared/utils/tour_step_config';
-import { FLYOUT_TOUR_TEST_ID } from '../../shared/components/test_ids';
-import { useTourContext } from '../../../../common/components/guided_onboarding_tour/tour';
-import { casesPluginMock } from '@kbn/cases-plugin/public/mocks';
-import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
-import { Flyouts } from '../../shared/constants/flyouts';
-
-jest.mock('../../../../common/lib/kibana');
-jest.mock('../../shared/hooks/use_which_flyout');
-jest.mock('../../../../common/components/guided_onboarding_tour/tour');
-
-const mockedUseKibana = mockUseKibana();
-
-const { storage: storageMock } = createSecuritySolutionStorageMock();
-const mockStore = createMockStore(undefined, undefined, undefined, {
- ...storageMock,
-});
-const mockCasesContract = casesPluginMock.createStartContract();
-const mockUseIsAddToCaseOpen = mockCasesContract.hooks.useIsAddToCaseOpen as jest.Mock;
-mockUseIsAddToCaseOpen.mockReturnValue(false);
-
-const renderRightPanelTour = (context: DocumentDetailsContext = mockContextValue) =>
- render(
-
-
-
- {Object.values(FLYOUT_TOUR_CONFIG_ANCHORS).map((i, idx) => (
-
- ))}
-
-
- );
-
-describe('
', () => {
- beforeEach(() => {
- (useKibana as jest.Mock).mockReturnValue({
- ...mockedUseKibana,
- services: {
- ...mockedUseKibana.services,
- storage: storageMock,
- cases: mockCasesContract,
- },
- });
- (useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.securitySolution);
- (useTourContext as jest.Mock).mockReturnValue({ isTourShown: jest.fn(() => false) });
- storageMock.clear();
- });
-
- it('should render tour for alerts', async () => {
- const { getByText, getByTestId } = renderRightPanelTour();
- await waitFor(() => {
- expect(getByTestId(`${FLYOUT_TOUR_TEST_ID}-1`)).toBeVisible();
- });
- fireEvent.click(getByText('Next'));
- await waitFor(() => {
- expect(getByTestId(`${FLYOUT_TOUR_TEST_ID}-2`)).toBeVisible();
- });
- fireEvent.click(getByText('Next'));
- await waitFor(() => {
- expect(getByTestId(`${FLYOUT_TOUR_TEST_ID}-3`)).toBeVisible();
- });
- fireEvent.click(getByText('Next'));
- });
-
- it('should not render tour for preview', () => {
- const { queryByTestId, queryByText } = renderRightPanelTour({
- ...mockContextValue,
- isPreview: true,
- });
- expect(queryByTestId(`${FLYOUT_TOUR_TEST_ID}-1`)).not.toBeInTheDocument();
- expect(queryByText('Next')).not.toBeInTheDocument();
- });
-
- it('should not render tour when guided onboarding tour is active', () => {
- (useTourContext as jest.Mock).mockReturnValue({ isTourShown: jest.fn(() => true) });
- const { queryByText, queryByTestId } = renderRightPanelTour({
- ...mockContextValue,
- getFieldsData: () => '',
- });
-
- expect(queryByTestId(`${FLYOUT_TOUR_TEST_ID}-1`)).not.toBeInTheDocument();
- expect(queryByText('Next')).not.toBeInTheDocument();
- });
-
- it('should not render tour when case modal is open', () => {
- mockUseIsAddToCaseOpen.mockReturnValue(true);
- const { queryByText, queryByTestId } = renderRightPanelTour({
- ...mockContextValue,
- getFieldsData: () => '',
- });
-
- expect(queryByTestId(`${FLYOUT_TOUR_TEST_ID}-1`)).not.toBeInTheDocument();
- expect(queryByText('Next')).not.toBeInTheDocument();
- });
-
- it('should not render tour for flyout in timeline', () => {
- (useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.timeline);
- const { queryByText, queryByTestId } = renderRightPanelTour({
- ...mockContextValue,
- getFieldsData: () => '',
- });
-
- expect(queryByTestId(`${FLYOUT_TOUR_TEST_ID}-1`)).not.toBeInTheDocument();
- expect(queryByText('Next')).not.toBeInTheDocument();
- });
-});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.tsx
deleted file mode 100644
index 839b77766886a..0000000000000
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.
- */
-
-import React, { memo, useMemo, useCallback } from 'react';
-import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
-import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
-import { Flyouts } from '../../shared/constants/flyouts';
-import { useDocumentDetailsContext } from '../../shared/context';
-import {
- getRightSectionTourSteps,
- getLeftSectionTourSteps,
-} from '../../shared/utils/tour_step_config';
-import { getField } from '../../shared/utils';
-import {
- DocumentDetailsLeftPanelKey,
- DocumentDetailsRightPanelKey,
-} from '../../shared/constants/panel_keys';
-import { EventKind } from '../../shared/constants/event_kinds';
-import { useTourContext } from '../../../../common/components/guided_onboarding_tour/tour';
-import { SecurityStepId } from '../../../../common/components/guided_onboarding_tour/tour_config';
-import { useKibana } from '../../../../common/lib/kibana';
-import { FlyoutTour } from '../../shared/components/flyout_tour';
-
-/**
- * Guided tour for the right panel in details flyout
- */
-export const RightPanelTour = memo(() => {
- const { useIsAddToCaseOpen } = useKibana().services.cases.hooks;
-
- const casesFlyoutExpanded = useIsAddToCaseOpen();
-
- const { isTourShown: isGuidedOnboardingTourShown } = useTourContext();
-
- const { openLeftPanel, openRightPanel } = useExpandableFlyoutApi();
- const { eventId, indexName, scopeId, isPreview, getFieldsData } = useDocumentDetailsContext();
-
- const eventKind = getField(getFieldsData('event.kind'));
- const isAlert = eventKind === EventKind.signal;
- const isTimelineFlyoutOpen = useWhichFlyout() === Flyouts.timeline;
- const showTour =
- isAlert &&
- !isPreview &&
- !isTimelineFlyoutOpen &&
- !isGuidedOnboardingTourShown(SecurityStepId.alertsCases) &&
- !casesFlyoutExpanded;
-
- const goToLeftPanel = useCallback(() => {
- openLeftPanel({
- id: DocumentDetailsLeftPanelKey,
- params: {
- id: eventId,
- indexName,
- scopeId,
- },
- });
- }, [eventId, indexName, scopeId, openLeftPanel]);
-
- const goToOverviewTab = useCallback(() => {
- openRightPanel({
- id: DocumentDetailsRightPanelKey,
- path: { tab: 'overview' },
- params: {
- id: eventId,
- indexName,
- scopeId,
- },
- });
- }, [eventId, indexName, scopeId, openRightPanel]);
-
- const tourStepContent = useMemo(
- // we append the left tour steps here to support the scenarios where the flyout left section is already expanded when starting the tour
- () => [...getRightSectionTourSteps(), ...getLeftSectionTourSteps()],
- []
- );
-
- return showTour ? (
-
- ) : null;
-});
-
-RightPanelTour.displayName = 'RightPanelTour';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx
index 1f9006b8d04a8..56c24d9562091 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/index.tsx
@@ -17,7 +17,6 @@ import type { DocumentDetailsProps } from '../shared/types';
import { PanelNavigation } from './navigation';
import { PanelHeader } from './header';
import { PanelContent } from './content';
-import { RightPanelTour } from './components/tour';
import type { RightPanelTabType } from './tabs';
import { PanelFooter } from './footer';
import { useFlyoutIsExpandable } from './hooks/use_flyout_is_expandable';
@@ -76,7 +75,6 @@ export const RightPanel: FC
> = memo(({ path }) =>
return (
<>
- {flyoutIsExpandable && }
-
-
+
),
content: