From 87c0451f91aa81c867345318ff994c594668f069 Mon Sep 17 00:00:00 2001
From: Maryam Saeidi
Date: Thu, 18 Apr 2024 11:25:57 +0200
Subject: [PATCH 01/96] [Alert search bar] Fix alert search bar refresh
functionality (#180899)
Fixes #180722
## Summary
This PR fixes the refresh issue by using `isUpdate` prop to decide if
any of the search bar params have changed and if not, it just resubmits
the request without changing calling the parent functions for updating
the params.
### Testing refresh functionality
https://github.com/elastic/kibana/assets/12370520/0e406613-e662-4f62-a229-c1f5cc2bb478
### Testing other alerts search bar functionalities
https://github.com/elastic/kibana/assets/12370520/13021c28-0c68-4f92-94ac-f6a3f6234441
---------
Co-authored-by: Dominique Clarke
---
.../public/search_bar/search_bar.test.tsx | 57 +++++++++++++++++++
.../public/search_bar/search_bar.tsx | 30 ++++++----
.../alert_search_bar/alert_search_bar.tsx | 29 ++++++----
.../alerts_search_bar/alerts_search_bar.tsx | 16 ++++--
.../sections/alerts_search_bar/types.ts | 11 ++--
5 files changed, 112 insertions(+), 31 deletions(-)
diff --git a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx
index e9fce0f749928..005d4926f4709 100644
--- a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx
+++ b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx
@@ -143,6 +143,7 @@ describe('SearchBar', () => {
const QUERY_BAR = '.kbnQueryBar';
const QUERY_INPUT = '[data-test-subj="unifiedQueryInput"]';
const QUERY_MENU_BUTTON = '[data-test-subj="showQueryBarMenu"]';
+ const QUERY_SUBMIT_BUTTON = 'button[data-test-subj="querySubmitButton"]';
const EDITOR = '[data-test-subj="unifiedTextLangEditor"]';
beforeEach(() => {
@@ -308,4 +309,60 @@ describe('SearchBar', () => {
expect(button).toBeDisabled();
});
});
+
+ it('Should call onQuerySubmit with isUpdate prop as false when dateRange is provided', () => {
+ const mockedOnQuerySubmit = jest.fn();
+ const component = mount(
+ wrapSearchBarInContext({
+ indexPatterns: [mockIndexPattern],
+ screenTitle: 'test screen',
+ onQuerySubmit: mockedOnQuerySubmit,
+ query: kqlQuery,
+ showQueryMenu: false,
+ dateRangeTo: 'now',
+ dateRangeFrom: 'now-15m',
+ })
+ );
+
+ const submitButton = component.find(QUERY_SUBMIT_BUTTON);
+ submitButton.simulate('click');
+
+ expect(mockedOnQuerySubmit).toBeCalledTimes(1);
+ expect(mockedOnQuerySubmit).toHaveBeenNthCalledWith(
+ 1,
+ {
+ dateRange: { from: 'now-15m', to: 'now' },
+ query: { language: 'kuery', query: 'response:200' },
+ },
+ false
+ );
+ });
+
+ it('Should call onQuerySubmit with isUpdate prop as true when dateRange is not provided', () => {
+ const mockedOnQuerySubmit = jest.fn();
+ const component = mount(
+ wrapSearchBarInContext({
+ indexPatterns: [mockIndexPattern],
+ screenTitle: 'test screen',
+ onQuerySubmit: mockedOnQuerySubmit,
+ query: kqlQuery,
+ showQueryMenu: false,
+ })
+ );
+
+ const submitButton = component.find(QUERY_SUBMIT_BUTTON);
+ submitButton.simulate('click');
+
+ expect(mockedOnQuerySubmit).toBeCalledTimes(1);
+ expect(mockedOnQuerySubmit).toHaveBeenNthCalledWith(
+ 1,
+ {
+ dateRange: { from: 'now-15m', to: 'now' },
+ query: { language: 'kuery', query: 'response:200' },
+ },
+ // isUpdate is true because the default value in state ({ from: 'now-15m', to: 'now' })
+ // is not equal with props for dateRange which is undefined
+ true
+ );
+ });
});
diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx
index 9bb60972734b0..a2b8ade82b3f7 100644
--- a/src/plugins/unified_search/public/search_bar/search_bar.tsx
+++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx
@@ -413,13 +413,16 @@ class SearchBarUI extends C
},
() => {
if (this.props.onQuerySubmit) {
- this.props.onQuerySubmit({
- query: query as QT,
- dateRange: {
- from: this.state.dateRangeFrom,
- to: this.state.dateRangeTo,
+ this.props.onQuerySubmit(
+ {
+ query: query as QT,
+ dateRange: {
+ from: this.state.dateRangeFrom,
+ to: this.state.dateRangeTo,
+ },
},
- });
+ this.isDirty()
+ );
}
}
);
@@ -437,13 +440,16 @@ class SearchBarUI extends C
},
() => {
if (this.props.onQuerySubmit) {
- this.props.onQuerySubmit({
- query: this.state.query,
- dateRange: {
- from: this.state.dateRangeFrom,
- to: this.state.dateRangeTo,
+ this.props.onQuerySubmit(
+ {
+ query: this.state.query,
+ dateRange: {
+ from: this.state.dateRangeFrom,
+ to: this.state.dateRangeTo,
+ },
},
- });
+ this.isDirty()
+ );
}
this.services.usageCollection?.reportUiCounter(
this.services.appName,
diff --git a/x-pack/plugins/observability_solution/observability/public/components/alert_search_bar/alert_search_bar.tsx b/x-pack/plugins/observability_solution/observability/public/components/alert_search_bar/alert_search_bar.tsx
index ffb8f30dc3918..863855bbbb3b8 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/alert_search_bar/alert_search_bar.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/components/alert_search_bar/alert_search_bar.tsx
@@ -90,7 +90,7 @@ export function ObservabilityAlertSearchBar({
onAlertStatusChange(status);
}, [onAlertStatusChange, status]);
- useEffect(() => {
+ const submitQuery = useCallback(() => {
try {
onEsQueryChange(
buildEsQuery({
@@ -123,21 +123,30 @@ export function ObservabilityAlertSearchBar({
toasts,
]);
- const onSearchBarParamsChange = useCallback<
- (query: {
+ useEffect(() => {
+ submitQuery();
+ }, [submitQuery]);
+
+ const onQuerySubmit = (
+ {
+ dateRange,
+ query,
+ }: {
dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' };
query?: string;
- }) => void
- >(
- ({ dateRange, query }) => {
+ },
+ isUpdate?: boolean
+ ) => {
+ if (isUpdate) {
clearSavedQuery();
onKueryChange(query ?? '');
timeFilterService.setTime(dateRange);
onRangeFromChange(dateRange.from);
onRangeToChange(dateRange.to);
- },
- [onKueryChange, timeFilterService, clearSavedQuery, onRangeFromChange, onRangeToChange]
- );
+ } else {
+ submitQuery();
+ }
+ };
const onFilterUpdated = useCallback<(filters: Filter[]) => void>(
(updatedFilters) => {
@@ -162,7 +171,7 @@ export function ObservabilityAlertSearchBar({
onSavedQueryUpdated={setSavedQuery}
onClearSavedQuery={clearSavedQuery}
query={kuery}
- onQuerySubmit={onSearchBarParamsChange}
+ onQuerySubmit={onQuerySubmit}
/>
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx
index 294985bcf5224..2f4f0492ad73a 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx
@@ -70,11 +70,17 @@ export function AlertsSearchBar({
ruleType.ruleTypesState.data.get(ruleTypeId)?.producer === AlertConsumers.SIEM);
const onSearchQuerySubmit = useCallback(
- ({ dateRange, query: nextQuery }: { dateRange: TimeRange; query?: Query }) => {
- onQuerySubmit({
- dateRange,
- query: typeof nextQuery?.query === 'string' ? nextQuery.query : undefined,
- });
+ (
+ { dateRange, query: nextQuery }: { dateRange: TimeRange; query?: Query },
+ isUpdate?: boolean
+ ) => {
+ onQuerySubmit(
+ {
+ dateRange,
+ query: typeof nextQuery?.query === 'string' ? nextQuery.query : undefined,
+ },
+ isUpdate
+ );
setQueryLanguage((nextQuery?.language ?? 'kuery') as QueryLanguageType);
},
[onQuerySubmit, setQueryLanguage]
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts
index 59b679b4fdee9..b1af2746d6cac 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts
@@ -32,10 +32,13 @@ export interface AlertsSearchBarProps
dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' };
query?: string;
}) => void;
- onQuerySubmit: (query: {
- dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' };
- query?: string;
- }) => void;
+ onQuerySubmit: (
+ query: {
+ dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' };
+ query?: string;
+ },
+ isUpdate?: boolean
+ ) => void;
onFiltersUpdated?: (filters: Filter[]) => void;
filtersForSuggestions?: Filter[];
}
From 6fdcd8d7b80515ce9361666f5264f8966d9b54e7 Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Thu, 18 Apr 2024 11:35:16 +0200
Subject: [PATCH 02/96] [ML] AIOps: Fix to not run log rate analysis twice when
no spike/dip detected. (#180980)
## Summary
Part of #172981.
This fixes to not run log rate analysis twice when no spike/dip detected
and a user needs to adapt the initial selection. When a user clicks in
an area of the histogram chart that's not a highlighted change point,
the click will just trigger the baseline/deviation time range selection,
but it will not automatically run the analysis. Instead, an updated
prompt is shown below the chart that explains that the
baseline/deviation can be adjusted via dragging and the analysis can be
run via the button below that description.
Initial view after loading the page:
User clicked in an area that's not covered by the highlighted change
point:
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
x-pack/packages/ml/aiops_components/index.ts | 8 ++
.../document_count_chart.tsx | 45 +++++++-
.../src/document_count_chart/index.ts | 5 +-
.../log_rate_analysis_state_provider/index.ts | 12 ++
.../log_rate_analysis_state_provider.tsx} | 75 +++++++++---
.../types.ts | 16 +++
.../ml/aiops_components/tsconfig.json | 1 +
.../journeys_e2e/aiops_log_rate_analysis.ts | 8 +-
...uild_extended_base_filter_criteria.test.ts | 3 +-
.../build_extended_base_filter_criteria.ts | 3 +-
.../document_count_content.tsx | 12 +-
.../log_rate_analysis_app_state.tsx | 7 +-
.../log_rate_analysis_content.tsx | 108 ++++++++++++++----
.../log_rate_analysis_content_wrapper.tsx | 7 +-
.../log_rate_analysis_page.tsx | 15 ++-
.../log_rate_analysis_results.tsx | 4 +-
.../get_group_table_items.ts | 3 +-
.../get_table_item_as_kql.ts | 3 +-
.../log_rate_analysis_results_table.tsx | 4 +-
...log_rate_analysis_results_table_groups.tsx | 5 +-
.../use_copy_to_clipboard_action.test.tsx | 2 +-
.../use_copy_to_clipboard_action.tsx | 2 +-
.../use_view_in_discover_action.tsx | 4 +-
...se_view_in_log_pattern_analysis_action.tsx | 2 +-
.../aiops/public/get_document_stats.ts | 2 +-
x-pack/plugins/aiops/public/hooks/use_data.ts | 2 +-
.../apps/aiops/log_rate_analysis.ts | 6 +
.../artificial_log_data_view_test_data.ts | 3 +
.../farequote_data_view_test_data.ts | 1 +
...arequote_data_view_test_data_with_query.ts | 1 +
.../kibana_logs_data_view_test_data.ts | 1 +
.../apps/aiops/log_rate_analysis_test_data.ts | 8 ++
x-pack/test/functional/apps/aiops/types.ts | 1 +
.../services/aiops/log_rate_analysis_page.ts | 14 +++
34 files changed, 312 insertions(+), 81 deletions(-)
create mode 100644 x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/index.ts
rename x-pack/{plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx => packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx} (50%)
rename x-pack/{plugins/aiops/public/components/log_rate_analysis_results_table => packages/ml/aiops_components/src/log_rate_analysis_state_provider}/types.ts (59%)
diff --git a/x-pack/packages/ml/aiops_components/index.ts b/x-pack/packages/ml/aiops_components/index.ts
index c9ca50ad0daff..3a640c1d0cb44 100644
--- a/x-pack/packages/ml/aiops_components/index.ts
+++ b/x-pack/packages/ml/aiops_components/index.ts
@@ -9,7 +9,15 @@ export { DualBrush, DualBrushAnnotation } from './src/dual_brush';
export { ProgressControls } from './src/progress_controls';
export {
DocumentCountChart,
+ DocumentCountChartWithAutoAnalysisStart,
type BrushSettings,
type BrushSelectionUpdateHandler,
} from './src/document_count_chart';
export type { DocumentCountChartProps } from './src/document_count_chart';
+export {
+ useLogRateAnalysisStateContext,
+ LogRateAnalysisStateProvider,
+ type GroupTableItem,
+ type GroupTableItemGroup,
+ type TableItemAction,
+} from './src/log_rate_analysis_state_provider';
diff --git a/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx
index 57139ddd0a1fe..02c532d186704 100644
--- a/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx
+++ b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx
@@ -40,6 +40,8 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { DualBrush, DualBrushAnnotation } from '../..';
+import { useLogRateAnalysisStateContext } from '../log_rate_analysis_state_provider';
+
import { BrushBadge } from './brush_badge';
declare global {
@@ -87,6 +89,11 @@ export type BrushSelectionUpdateHandler = (
logRateAnalysisType: LogRateAnalysisType
) => void;
+/**
+ * Callback to set the autoRunAnalysis flag
+ */
+type SetAutoRunAnalysisFn = (isAutoRun: boolean) => void;
+
/**
* Props for document count chart
*/
@@ -118,9 +125,11 @@ export interface DocumentCountChartProps {
chartPointsSplitLabel: string;
/** Whether or not brush has been reset */
isBrushCleared: boolean;
+ /** Callback to set the autoRunAnalysis flag */
+ setAutoRunAnalysis?: SetAutoRunAnalysisFn;
/** Timestamp for start of initial analysis */
autoAnalysisStart?: number | WindowParameters;
- /** Optional style to override bar chart */
+ /** Optional style to override bar chart */
barStyleAccessor?: BarStyleAccessor;
/** Optional color override for the default bar color for charts */
barColorOverride?: string;
@@ -181,6 +190,7 @@ export const DocumentCountChart: FC = (props) => {
interval,
chartPointsSplitLabel,
isBrushCleared,
+ setAutoRunAnalysis,
autoAnalysisStart,
barColorOverride,
barStyleAccessor,
@@ -305,6 +315,17 @@ export const DocumentCountChart: FC = (props) => {
windowParameters === undefined &&
adjustedChartPoints !== undefined
) {
+ if (setAutoRunAnalysis) {
+ const autoRun =
+ typeof startRange !== 'number' ||
+ (typeof startRange === 'number' &&
+ changePoint !== undefined &&
+ startRange >= changePoint.startTs &&
+ startRange <= changePoint.endTs);
+
+ setAutoRunAnalysis(autoRun);
+ }
+
const wp = getWindowParametersForTrigger(
startRange,
interval,
@@ -333,6 +354,7 @@ export const DocumentCountChart: FC = (props) => {
timeRangeLatest,
snapTimestamps,
originalWindowParameters,
+ setAutoRunAnalysis,
setWindowParameters,
brushSelectionUpdateHandler,
adjustedChartPoints,
@@ -535,3 +557,24 @@ export const DocumentCountChart: FC = (props) => {
>
);
};
+
+/**
+ * Functional component that renders a `DocumentCountChart` with additional properties
+ * managed by the log rate analysis state. It leverages the `useLogRateAnalysisStateContext`
+ * to acquire state variables like `initialAnalysisStart` and functions such as
+ * `setAutoRunAnalysis`. These values are then passed as props to the `DocumentCountChart`.
+ *
+ * @param props - The properties passed to the DocumentCountChart component.
+ * @returns The DocumentCountChart component enhanced with automatic analysis start capabilities.
+ */
+export const DocumentCountChartWithAutoAnalysisStart: FC = (props) => {
+ const { initialAnalysisStart, setAutoRunAnalysis } = useLogRateAnalysisStateContext();
+
+ return (
+
+ );
+};
diff --git a/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts b/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts
index 5b64acedbf5f6..efde8b0a83cba 100644
--- a/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts
+++ b/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts
@@ -5,7 +5,10 @@
* 2.0.
*/
-export { DocumentCountChart } from './document_count_chart';
+export {
+ DocumentCountChart,
+ DocumentCountChartWithAutoAnalysisStart,
+} from './document_count_chart';
export type {
BrushSelectionUpdateHandler,
BrushSettings,
diff --git a/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/index.ts b/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/index.ts
new file mode 100644
index 0000000000000..18453665cb4f2
--- /dev/null
+++ b/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export {
+ useLogRateAnalysisStateContext,
+ LogRateAnalysisStateProvider,
+} from './log_rate_analysis_state_provider';
+export type { GroupTableItem, GroupTableItemGroup, TableItemAction } from './types';
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx b/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx
similarity index 50%
rename from x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx
rename to x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx
index a7b3398e4e7cc..f3aa55bdce771 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx
+++ b/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx
@@ -16,13 +16,19 @@ import React, {
} from 'react';
import type { SignificantItem } from '@kbn/ml-agg-utils';
+import type { WindowParameters } from '@kbn/aiops-log-rate-analysis';
import type { GroupTableItem } from './types';
+type InitialAnalysisStart = number | WindowParameters | undefined;
type SignificantItemOrNull = SignificantItem | null;
type GroupOrNull = GroupTableItem | null;
-interface LogRateAnalysisResultsTableRow {
+interface LogRateAnalysisState {
+ autoRunAnalysis: boolean;
+ setAutoRunAnalysis: Dispatch>;
+ initialAnalysisStart: InitialAnalysisStart;
+ setInitialAnalysisStart: Dispatch>;
pinnedSignificantItem: SignificantItemOrNull;
setPinnedSignificantItem: Dispatch>;
pinnedGroup: GroupOrNull;
@@ -36,12 +42,38 @@ interface LogRateAnalysisResultsTableRow {
clearAllRowState: () => void;
}
-export const logRateAnalysisResultsTableRowContext = createContext<
- LogRateAnalysisResultsTableRow | undefined
->(undefined);
+const LogRateAnalysisStateContext = createContext(undefined);
-export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) => {
- // State that will be shared with all components
+/**
+ * Props for LogRateAnalysisStateProvider.
+ */
+interface LogRateAnalysisStateProviderProps {
+ /** The parameters to be used to trigger an analysis. */
+ initialAnalysisStart?: InitialAnalysisStart;
+}
+
+/**
+ * Context provider component that manages and provides global state for Log Rate Analysis.
+ * This provider handles several pieces of state important for controlling and displaying
+ * log rate analysis data, such as the control of automatic analysis runs, and the management
+ * of both pinned and selected significant items and groups.
+ *
+ * The state includes mechanisms for setting initial analysis parameters, toggling analysis,
+ * and managing the current selection and pinned state of significant items and groups.
+ *
+ * @param props - Props object containing initial settings for the analysis,
+ * including children components to be wrapped by the Provider.
+ * @returns A context provider wrapping children with access to log rate analysis state.
+ */
+export const LogRateAnalysisStateProvider: FC = (props) => {
+ const { children, initialAnalysisStart: incomingInitialAnalysisStart } = props;
+
+ const [autoRunAnalysis, setAutoRunAnalysis] = useState(true);
+ const [initialAnalysisStart, setInitialAnalysisStart] = useState<
+ number | WindowParameters | undefined
+ >(incomingInitialAnalysisStart);
+
+ // Row state that will be shared with all components
const [pinnedSignificantItem, setPinnedSignificantItem] = useState(null);
const [pinnedGroup, setPinnedGroup] = useState(null);
const [selectedSignificantItem, setSelectedSignificantItem] =
@@ -66,8 +98,12 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) =>
}
}, [selectedGroup, pinnedGroup]);
- const contextValue: LogRateAnalysisResultsTableRow = useMemo(
+ const contextValue: LogRateAnalysisState = useMemo(
() => ({
+ autoRunAnalysis,
+ setAutoRunAnalysis,
+ initialAnalysisStart,
+ setInitialAnalysisStart,
pinnedSignificantItem,
setPinnedSignificantItem,
pinnedGroup,
@@ -86,6 +122,10 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) =>
},
}),
[
+ autoRunAnalysis,
+ setAutoRunAnalysis,
+ initialAnalysisStart,
+ setInitialAnalysisStart,
pinnedSignificantItem,
setPinnedSignificantItem,
pinnedGroup,
@@ -101,19 +141,26 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) =>
return (
// Provider managing the state
-
+
{children}
-
+
);
};
-export const useLogRateAnalysisResultsTableRowContext = () => {
- const logRateAnalysisResultsTableRow = useContext(logRateAnalysisResultsTableRowContext);
+/**
+ * Custom hook for accessing the state of log rate analysis from the LogRateAnalysisStateContext.
+ * This hook must be used within a component that is a descendant of the LogRateAnalysisStateContext provider.
+ *
+ * @returns The current state of the log rate analysis.
+ * @throws Throws an error if the hook is used outside of its Provider context.
+ */
+export const useLogRateAnalysisStateContext = () => {
+ const logRateAnalysisState = useContext(LogRateAnalysisStateContext);
// If `undefined`, throw an error.
- if (logRateAnalysisResultsTableRow === undefined) {
- throw new Error('useLogRateAnalysisResultsTableRowContext was used outside of its Provider');
+ if (logRateAnalysisState === undefined) {
+ throw new Error('useLogRateAnalysisStateContext was used outside of its Provider');
}
- return logRateAnalysisResultsTableRow;
+ return logRateAnalysisState;
};
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts b/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts
similarity index 59%
rename from x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts
rename to x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts
index 400e4534b54f1..4c4013e3d4867 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts
+++ b/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts
@@ -9,20 +9,36 @@ import type { EuiTableActionsColumnType } from '@elastic/eui';
import type { SignificantItem, SignificantItemGroupItem } from '@kbn/ml-agg-utils';
+/**
+ * Type for defining attributes picked from
+ * SignificantItemGroupItem used in the grouped table.
+ */
export type GroupTableItemGroup = Pick<
SignificantItemGroupItem,
'key' | 'type' | 'fieldName' | 'fieldValue' | 'docCount' | 'pValue' | 'duplicate'
>;
+/**
+ * Represents a single item in the group table.
+ */
export interface GroupTableItem {
+ /** Unique identifier for the group table item. */
id: string;
+ /** Document count associated with the item. */
docCount: number;
+ /** Statistical p-value indicating the significance of the item, nullable. */
pValue: number | null;
+ /** Count of unique items within the group. */
uniqueItemsCount: number;
+ /** Array of items within the group, sorted by uniqueness. */
groupItemsSortedByUniqueness: GroupTableItemGroup[];
+ /** Histogram data for the significant item. */
histogram: SignificantItem['histogram'];
}
+/**
+ * Type for action columns in a table that involves SignificantItem or GroupTableItem.
+ */
export type TableItemAction = EuiTableActionsColumnType<
SignificantItem | GroupTableItem
>['actions'][number];
diff --git a/x-pack/packages/ml/aiops_components/tsconfig.json b/x-pack/packages/ml/aiops_components/tsconfig.json
index 3fba5d38f9fc1..23cf9e9d60d4b 100644
--- a/x-pack/packages/ml/aiops_components/tsconfig.json
+++ b/x-pack/packages/ml/aiops_components/tsconfig.json
@@ -27,6 +27,7 @@
"@kbn/field-formats-plugin",
"@kbn/visualization-utils",
"@kbn/aiops-log-rate-analysis",
+ "@kbn/ml-agg-utils",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/performance/journeys_e2e/aiops_log_rate_analysis.ts b/x-pack/performance/journeys_e2e/aiops_log_rate_analysis.ts
index 06a5e01a2caf5..888a4802e6a51 100644
--- a/x-pack/performance/journeys_e2e/aiops_log_rate_analysis.ts
+++ b/x-pack/performance/journeys_e2e/aiops_log_rate_analysis.ts
@@ -33,8 +33,14 @@ export const journey = new Journey({
await page.waitForSelector(subj('aiopsNoWindowParametersEmptyPrompt'));
})
.step('Run AIOps Log Rate Analysis', async ({ page }) => {
- // Select the chart and click in the area where the spike is located to trigger log rate analysis.
+ // Select the chart and click in the area where the spike is located.
const chart = await page.locator(subj('aiopsDocumentCountChart'));
await chart.click({ position: { x: 710, y: 50 } });
+
+ // Click the "Run analysis" button.
+ await page.waitForSelector(subj('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton'));
+ await page.click(subj('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton'));
+
+ // Wait for the analysis to complete.
await page.waitForSelector(subj('aiopsAnalysisComplete'), { timeout: 120000 });
});
diff --git a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts
index bdc1336fc9441..6ab69bf7ccd1c 100644
--- a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts
+++ b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts
@@ -6,8 +6,7 @@
*/
import type { SignificantItem } from '@kbn/ml-agg-utils';
-
-import type { GroupTableItem } from '../../components/log_rate_analysis_results_table/types';
+import type { GroupTableItem } from '@kbn/aiops-components';
import { buildExtendedBaseFilterCriteria } from './build_extended_base_filter_criteria';
diff --git a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts
index 91709b9bd91a1..9d77f68cfa15b 100644
--- a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts
+++ b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts
@@ -14,8 +14,7 @@ import type { Query } from '@kbn/es-query';
import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils';
import { buildBaseFilterCriteria } from '@kbn/ml-query-utils';
import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query';
-
-import type { GroupTableItem } from '../../components/log_rate_analysis_results_table/types';
+import type { GroupTableItem } from '@kbn/aiops-components';
/*
* Contains utility functions for building and processing queries.
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 3ccf2bbbbfd2c..6a13d705cc918 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
@@ -14,7 +14,10 @@ import type {
} from '@elastic/charts/dist/chart_types/xy_chart/utils/specs';
import type { LogRateHistogramItem, WindowParameters } from '@kbn/aiops-log-rate-analysis';
-import { DocumentCountChart, type BrushSelectionUpdateHandler } from '@kbn/aiops-components';
+import {
+ DocumentCountChartWithAutoAnalysisStart,
+ type BrushSelectionUpdateHandler,
+} from '@kbn/aiops-components';
import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context';
import type { DocumentCountStats } from '../../../get_document_stats';
@@ -29,13 +32,11 @@ export interface DocumentCountContentProps {
isBrushCleared: boolean;
totalCount: number;
sampleProbability: number;
- initialAnalysisStart?: number | WindowParameters;
/** Optional color override for the default bar color for charts */
barColorOverride?: string;
/** Optional color override for the highlighted bar color for charts */
barHighlightColorOverride?: string;
windowParameters?: WindowParameters;
- incomingInitialAnalysisStart?: number | WindowParameters;
baselineLabel?: string;
deviationLabel?: string;
barStyleAccessor?: BarStyleAccessor;
@@ -51,11 +52,9 @@ export const DocumentCountContent: FC = ({
isBrushCleared,
totalCount,
sampleProbability,
- initialAnalysisStart,
barColorOverride,
barHighlightColorOverride,
windowParameters,
- incomingInitialAnalysisStart,
...docCountChartProps
}) => {
const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext();
@@ -100,7 +99,7 @@ export const DocumentCountContent: FC = ({
{documentCountStats.interval !== undefined && (
- = ({
interval={documentCountStats.interval}
chartPointsSplitLabel={documentCountStatsSplitLabel}
isBrushCleared={isBrushCleared}
- autoAnalysisStart={initialAnalysisStart}
barColorOverride={barColorOverride}
barHighlightColorOverride={barHighlightColorOverride}
changePoint={documentCountStats.changePoint}
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx
index 7de8b5f91d61e..ca06d3c7c9b37 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx
@@ -17,13 +17,12 @@ import { Storage } from '@kbn/kibana-utils-plugin/public';
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
+import { LogRateAnalysisStateProvider } from '@kbn/aiops-components';
import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
import { AiopsAppContext } from '../../hooks/use_aiops_app_context';
import { DataSourceContext } from '../../hooks/use_data_source';
import { AIOPS_STORAGE_KEYS } from '../../types/storage';
-import { LogRateAnalysisResultsTableRowStateProvider } from '../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
-
import { LogRateAnalysisPage } from './log_rate_analysis_page';
import { timeSeriesDataViewWarning } from '../../application/utils/time_series_dataview_check';
@@ -70,13 +69,13 @@ export const LogRateAnalysisAppState: FC = ({
-
+
-
+
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 abcffc3ebe86d..b244cbc324d17 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
@@ -26,6 +26,7 @@ import {
type WindowParameters,
} from '@kbn/aiops-log-rate-analysis';
import type { SignificantItem } from '@kbn/ml-agg-utils';
+import { useLogRateAnalysisStateContext, type GroupTableItem } from '@kbn/aiops-components';
import { useData } from '../../../hooks/use_data';
@@ -34,8 +35,6 @@ import {
LogRateAnalysisResults,
type LogRateAnalysisResultsData,
} from '../log_rate_analysis_results';
-import type { GroupTableItem } from '../../log_rate_analysis_results_table/types';
-import { useLogRateAnalysisResultsTableRowContext } from '../../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
const DEFAULT_SEARCH_QUERY: estypes.QueryDslQueryContainer = { match_all: {} };
const DEFAULT_SEARCH_BAR_QUERY: estypes.QueryDslQueryContainer = {
@@ -66,8 +65,6 @@ export function getDocumentCountStatsSplitLabel(
export interface LogRateAnalysisContentProps {
/** The data view to analyze. */
dataView: DataView;
- /** Timestamp for the start of the range for initial analysis */
- initialAnalysisStart?: number | WindowParameters;
timeRange?: { min: Moment; max: Moment };
/** Elasticsearch query to pass to analysis endpoint */
esSearchQuery?: estypes.QueryDslQueryContainer;
@@ -87,7 +84,6 @@ export interface LogRateAnalysisContentProps {
export const LogRateAnalysisContent: FC = ({
dataView,
- initialAnalysisStart: incomingInitialAnalysisStart,
timeRange,
esSearchQuery = DEFAULT_SEARCH_QUERY,
stickyHistogram,
@@ -98,9 +94,6 @@ export const LogRateAnalysisContent: FC = ({
embeddingOrigin,
}) => {
const [windowParameters, setWindowParameters] = useState();
- const [initialAnalysisStart, setInitialAnalysisStart] = useState<
- number | WindowParameters | undefined
- >(incomingInitialAnalysisStart);
const [isBrushCleared, setIsBrushCleared] = useState(true);
const [logRateAnalysisType, setLogRateAnalysisType] = useState(
LOG_RATE_ANALYSIS_TYPE.SPIKE
@@ -140,13 +133,16 @@ export const LogRateAnalysisContent: FC = ({
);
const {
+ autoRunAnalysis,
currentSelectedSignificantItem,
currentSelectedGroup,
+ setAutoRunAnalysis,
+ setInitialAnalysisStart,
setPinnedSignificantItem,
setPinnedGroup,
setSelectedSignificantItem,
setSelectedGroup,
- } = useLogRateAnalysisResultsTableRowContext();
+ } = useLogRateAnalysisStateContext();
const { documentStats, earliest, latest } = useData(
dataView,
@@ -206,7 +202,11 @@ export const LogRateAnalysisContent: FC = ({
}
: undefined;
- const triggerAnalysis = useCallback(() => {
+ const triggerAnalysisForManualSelection = useCallback(() => {
+ setAutoRunAnalysis(true);
+ }, [setAutoRunAnalysis]);
+
+ const triggerAnalysisForChangePoint = useCallback(() => {
if (documentCountStats) {
const { interval, timeRangeEarliest, timeRangeLatest, changePoint } = documentCountStats;
@@ -222,14 +222,37 @@ export const LogRateAnalysisContent: FC = ({
const snapTimestamps = getSnappedTimestamps(timeRangeEarliest, timeRangeLatest, interval);
const wpSnap = getSnappedWindowParameters(wp, snapTimestamps);
+ triggerAnalysisForManualSelection();
setInitialAnalysisStart(wpSnap);
}
}
- }, [documentCountStats]);
+ }, [documentCountStats, setInitialAnalysisStart, triggerAnalysisForManualSelection]);
+
+ const showDocumentCountContent = documentCountStats !== undefined;
+
+ const showLogRateAnalysisResults =
+ autoRunAnalysis &&
+ earliest !== undefined &&
+ latest !== undefined &&
+ windowParameters !== undefined;
+
+ const showNoAutoRunEmptyPrompt =
+ !autoRunAnalysis &&
+ earliest !== undefined &&
+ latest !== undefined &&
+ windowParameters !== undefined;
+
+ const showSpikeDetectedEmptyPrompt =
+ windowParameters === undefined && documentCountStats?.changePoint;
+
+ const showDefaultEmptyPrompt =
+ windowParameters === undefined && documentCountStats?.changePoint === undefined;
+
+ const changePointType = documentCountStats?.changePoint?.type;
return (
- {documentCountStats !== undefined && (
+ {showDocumentCountContent && (
= ({
isBrushCleared={isBrushCleared}
totalCount={totalCount}
sampleProbability={sampleProbability}
- initialAnalysisStart={initialAnalysisStart}
barColorOverride={barColorOverride}
barHighlightColorOverride={barHighlightColorOverride}
barStyleAccessor={barStyleAccessor}
/>
)}
- {earliest !== undefined && latest !== undefined && windowParameters !== undefined && (
+ {showLogRateAnalysisResults && (
= ({
embeddingOrigin={embeddingOrigin}
/>
)}
- {windowParameters === undefined && documentCountStats?.changePoint && (
+ {showNoAutoRunEmptyPrompt && (
+
+
+
+
+
+
+ {' '}
+ clearSelection()}
+ color="text"
+ >
+
+
+ >
+ }
+ data-test-subj="aiopsChangePointDetectedPrompt"
+ />
+ )}
+ {showSpikeDetectedEmptyPrompt && (
= ({
css={{ minWidth: '100%' }}
title={
- {documentCountStats?.changePoint.type === LOG_RATE_ANALYSIS_TYPE.SPIKE && (
+ {changePointType === LOG_RATE_ANALYSIS_TYPE.SPIKE && (
)}
- {documentCountStats?.changePoint.type === LOG_RATE_ANALYSIS_TYPE.DIP && (
+ {changePointType === LOG_RATE_ANALYSIS_TYPE.DIP && (
)}
- {documentCountStats?.changePoint.type !== LOG_RATE_ANALYSIS_TYPE.SPIKE &&
- documentCountStats?.changePoint.type !== LOG_RATE_ANALYSIS_TYPE.DIP && (
+ {changePointType !== LOG_RATE_ANALYSIS_TYPE.SPIKE &&
+ changePointType !== LOG_RATE_ANALYSIS_TYPE.DIP && (
= ({
= ({
data-test-subj="aiopsChangePointDetectedPrompt"
/>
)}
- {windowParameters === undefined && documentCountStats?.changePoint === undefined && (
+ {showDefaultEmptyPrompt && (
= ({
}
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx
index 8655256b4da8d..524780d16c201 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx
@@ -18,13 +18,13 @@ import { UrlStateProvider } from '@kbn/ml-url-state';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
+import { LogRateAnalysisStateProvider } from '@kbn/aiops-components';
import { timeSeriesDataViewWarning } from '../../../application/utils/time_series_dataview_check';
import { AiopsAppContext, type AiopsAppDependencies } from '../../../hooks/use_aiops_app_context';
import { DataSourceContext } from '../../../hooks/use_data_source';
import { AIOPS_STORAGE_KEYS } from '../../../types/storage';
-import { LogRateAnalysisResultsTableRowStateProvider } from '../../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
import { LogRateAnalysisContent } from './log_rate_analysis_content';
import type { LogRateAnalysisResultsData } from '../log_rate_analysis_results';
@@ -92,12 +92,11 @@ export const LogRateAnalysisContentWrapper: FC
-
+
-
+
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx
index 90227fd7915a0..254d58c93675a 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx
@@ -18,6 +18,7 @@ import { useUrlState, usePageUrlState } from '@kbn/ml-url-state';
import type { SearchQueryLanguage } from '@kbn/ml-query-utils';
import type { WindowParameters } from '@kbn/aiops-log-rate-analysis';
import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants';
+import { useLogRateAnalysisStateContext } from '@kbn/aiops-components';
import { useDataSource } from '../../hooks/use_data_source';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
@@ -31,7 +32,6 @@ import {
} from '../../application/url_state/log_rate_analysis';
import { SearchPanel } from '../search_panel';
-import { useLogRateAnalysisResultsTableRowContext } from '../log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider';
import { PageHeader } from '../page_header';
import { LogRateAnalysisContent } from './log_rate_analysis_content/log_rate_analysis_content';
@@ -43,8 +43,8 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => {
const { data: dataService } = useAiopsAppContext();
const { dataView, savedSearch } = useDataSource();
- const { currentSelectedSignificantItem, currentSelectedGroup } =
- useLogRateAnalysisResultsTableRowContext();
+ const { currentSelectedSignificantItem, currentSelectedGroup, setInitialAnalysisStart } =
+ useLogRateAnalysisStateContext();
const [stateFromUrl, setUrlState] = usePageUrlState(
'logRateAnalysis',
@@ -142,6 +142,14 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => {
});
}, [dataService, searchQueryLanguage, searchString]);
+ useEffect(
+ () => {
+ setInitialAnalysisStart(appStateToWindowParameters(stateFromUrl.wp));
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ );
+
const onWindowParametersHandler = (wp?: WindowParameters, replace = false) => {
if (!isEqual(windowParametersToAppState(wp), stateFromUrl.wp)) {
setUrlState(
@@ -169,7 +177,6 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => {
/>
= ({
// to be able to track it across rerenders.
const analysisStartTime = useRef(window.performance.now());
- const { clearAllRowState } = useLogRateAnalysisResultsTableRowContext();
+ const { clearAllRowState } = useLogRateAnalysisStateContext();
const [currentAnalysisType, setCurrentAnalysisType] = useState();
const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState<
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts
index 6767b82444946..c33713e111f02 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts
@@ -8,8 +8,7 @@
import { sortBy } from 'lodash';
import type { SignificantItemGroup } from '@kbn/ml-agg-utils';
-
-import type { GroupTableItem, GroupTableItemGroup } from './types';
+import type { GroupTableItem, GroupTableItemGroup } from '@kbn/aiops-components';
export function getGroupTableItems(
significantItemsGroups: SignificantItemGroup[]
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts
index 97717d43cb121..6ac66468b658e 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts
@@ -7,8 +7,7 @@
import { escapeKuery, escapeQuotes } from '@kbn/es-query';
import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils';
-
-import type { GroupTableItem } from './types';
+import type { GroupTableItem } from '@kbn/aiops-components';
export const getTableItemAsKQL = (tableItem: GroupTableItem | SignificantItem) => {
if (isSignificantItem(tableItem)) {
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 e19663e064d63..ff683855f6d38 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
@@ -32,13 +32,13 @@ import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query';
+import { useLogRateAnalysisStateContext } from '@kbn/aiops-components';
import { useEuiTheme } from '../../hooks/use_eui_theme';
import { MiniHistogram } from '../mini_histogram';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label';
-import { useLogRateAnalysisResultsTableRowContext } from './log_rate_analysis_results_table_row_provider';
import { FieldStatsPopover } from '../field_stats_popover';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
import { useViewInDiscoverAction } from './use_view_in_discover_action';
@@ -93,7 +93,7 @@ export const LogRateAnalysisResultsTable: FC =
selectedSignificantItem,
setPinnedSignificantItem,
setSelectedSignificantItem,
- } = useLogRateAnalysisResultsTableRowContext();
+ } = useLogRateAnalysisStateContext();
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
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 cf3afc2720665..aefe170fc4b5b 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
@@ -32,13 +32,12 @@ import type { SignificantItem } from '@kbn/ml-agg-utils';
import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import type { DataView } from '@kbn/data-views-plugin/public';
import { stringHash } from '@kbn/ml-string-hash';
+import { useLogRateAnalysisStateContext, type GroupTableItem } from '@kbn/aiops-components';
import { MiniHistogram } from '../mini_histogram';
import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label';
import { LogRateAnalysisResultsTable } from './log_rate_analysis_results_table';
-import { useLogRateAnalysisResultsTableRowContext } from './log_rate_analysis_results_table_row_provider';
-import type { GroupTableItem } from './types';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
import { useViewInDiscoverAction } from './use_view_in_discover_action';
import { useViewInLogPatternAnalysisAction } from './use_view_in_log_pattern_analysis_action';
@@ -97,7 +96,7 @@ export const LogRateAnalysisResultsGroupsTable: FC {
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx
index 8e3b68f4ec279..c110e8b2d56c8 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx
@@ -14,10 +14,10 @@ import type { SignificantItem } from '@kbn/ml-agg-utils';
import { finalSignificantItemGroups } from '@kbn/aiops-test-utils/artificial_logs/final_significant_item_groups';
import { significantTerms } from '@kbn/aiops-test-utils/artificial_logs/significant_terms';
+import type { GroupTableItem } from '@kbn/aiops-components';
import { getGroupTableItems } from './get_group_table_items';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
-import type { GroupTableItem } from './types';
interface Action {
render: (tableItem: SignificantItem | GroupTableItem) => ReactElement;
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx
index b7dc328f1a468..403adfdbf070a 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx
@@ -11,10 +11,10 @@ import { EuiCopy, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils';
+import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components';
import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
-import type { GroupTableItem, TableItemAction } from './types';
const copyToClipboardButtonLabel = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel',
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 7f467ded4c24b..7f657617517af 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
@@ -9,13 +9,13 @@ import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import type { SignificantItem } from '@kbn/ml-agg-utils';
-
import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils';
+import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components';
+
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
-import type { GroupTableItem, TableItemAction } from './types';
const viewInDiscoverMessage = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover',
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 3f75654d4280e..6c29ee4607d60 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
@@ -11,13 +11,13 @@ import type { SerializableRecord } from '@kbn/utility-types';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { isSignificantItem, type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils';
+import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components';
import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { TableActionButton } from './table_action_button';
import { getTableItemAsKQL } from './get_table_item_as_kql';
-import type { GroupTableItem, TableItemAction } from './types';
const isLogPattern = (tableItem: SignificantItem | GroupTableItem) =>
isSignificantItem(tableItem) && tableItem.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN;
diff --git a/x-pack/plugins/aiops/public/get_document_stats.ts b/x-pack/plugins/aiops/public/get_document_stats.ts
index f693dc8f67517..0846565cc1890 100644
--- a/x-pack/plugins/aiops/public/get_document_stats.ts
+++ b/x-pack/plugins/aiops/public/get_document_stats.ts
@@ -18,9 +18,9 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { SignificantItem } from '@kbn/ml-agg-utils';
import type { Query } from '@kbn/es-query';
import type { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils';
+import type { GroupTableItem } from '@kbn/aiops-components';
import { buildExtendedBaseFilterCriteria } from './application/utils/build_extended_base_filter_criteria';
-import type { GroupTableItem } from './components/log_rate_analysis_results_table/types';
export interface DocumentCountStats {
interval?: number;
diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts
index 64cb69f2dcdd9..9986a4d65dd70 100644
--- a/x-pack/plugins/aiops/public/hooks/use_data.ts
+++ b/x-pack/plugins/aiops/public/hooks/use_data.ts
@@ -18,9 +18,9 @@ import type { Dictionary } from '@kbn/ml-url-state';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import { useTimeBuckets } from '@kbn/ml-time-buckets';
import { AIOPS_PLUGIN_ID } from '@kbn/aiops-common/constants';
+import type { GroupTableItem } from '@kbn/aiops-components';
import type { DocumentStatsSearchStrategyParams } from '../get_document_stats';
-import type { GroupTableItem } from '../components/log_rate_analysis_results_table/types';
import { useAiopsAppContext } from './use_aiops_app_context';
diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts
index 3d3a629599aec..7308d561109be 100644
--- a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts
+++ b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts
@@ -80,6 +80,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('clicks the document count chart to start analysis');
await aiops.logRateAnalysisPage.clickDocumentCountChart(testData.chartClickCoordinates);
+
+ if (!testData.autoRun) {
+ await aiops.logRateAnalysisPage.assertNoAutoRunButtonExists();
+ await aiops.logRateAnalysisPage.clickNoAutoRunButton();
+ }
+
await aiops.logRateAnalysisPage.assertAnalysisSectionExists();
if (testData.brushDeviationTargetTimestamp) {
diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts
index a04d6aea22ba1..ff6bfd4f9f19f 100644
--- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts
+++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/artificial_log_data_view_test_data.ts
@@ -34,12 +34,14 @@ interface GetArtificialLogDataViewTestDataOptions {
analysisType: LogRateAnalysisType;
textField: boolean;
zeroDocsFallback: boolean;
+ autoRun: boolean;
}
export const getArtificialLogDataViewTestData = ({
analysisType,
textField,
zeroDocsFallback,
+ autoRun,
}: GetArtificialLogDataViewTestDataOptions): TestData => {
function getAnalysisGroupsTable() {
if (zeroDocsFallback) {
@@ -133,6 +135,7 @@ export const getArtificialLogDataViewTestData = ({
return {
suiteTitle: getSuiteTitle(),
analysisType,
+ autoRun,
dataGenerator: getDataGenerator(),
isSavedSearch: false,
sourceIndexOrSavedSearch: getDataGenerator(),
diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts
index a1e860e55a493..07b116e544e20 100644
--- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts
+++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data.ts
@@ -12,6 +12,7 @@ import type { TestData } from '../../types';
export const farequoteDataViewTestData: TestData = {
suiteTitle: 'farequote with spike',
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
+ autoRun: false,
dataGenerator: 'farequote_with_spike',
isSavedSearch: false,
sourceIndexOrSavedSearch: 'ft_farequote',
diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts
index 29d49bbdf8ace..14e754550f608 100644
--- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts
+++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/farequote_data_view_test_data_with_query.ts
@@ -12,6 +12,7 @@ import type { TestData } from '../../types';
export const farequoteDataViewTestDataWithQuery: TestData = {
suiteTitle: 'farequote with spike',
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
+ autoRun: false,
dataGenerator: 'farequote_with_spike',
isSavedSearch: false,
sourceIndexOrSavedSearch: 'ft_farequote',
diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts
index 1e4f019ed901f..4a5f9dddb6f1f 100644
--- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts
+++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/kibana_logs_data_view_test_data.ts
@@ -13,6 +13,7 @@ import type { TestData } from '../../types';
export const kibanaLogsDataViewTestData: TestData = {
suiteTitle: 'kibana sample data logs',
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
+ autoRun: true,
dataGenerator: 'kibana_sample_data_logs',
isSavedSearch: false,
sourceIndexOrSavedSearch: 'kibana_sample_data_logstsdb',
diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis_test_data.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis_test_data.ts
index 2b5ca3e37861e..8fd0be5fa2b5b 100644
--- a/x-pack/test/functional/apps/aiops/log_rate_analysis_test_data.ts
+++ b/x-pack/test/functional/apps/aiops/log_rate_analysis_test_data.ts
@@ -22,40 +22,48 @@ export const logRateAnalysisTestData: TestData[] = [
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: false,
zeroDocsFallback: false,
+ autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: true,
zeroDocsFallback: false,
+ autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: false,
zeroDocsFallback: false,
+ autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: true,
zeroDocsFallback: false,
+ autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: true,
zeroDocsFallback: true,
+ autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
textField: false,
zeroDocsFallback: true,
+ autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: true,
zeroDocsFallback: true,
+ autoRun: false,
}),
getArtificialLogDataViewTestData({
analysisType: LOG_RATE_ANALYSIS_TYPE.DIP,
textField: false,
zeroDocsFallback: true,
+ autoRun: false,
}),
];
diff --git a/x-pack/test/functional/apps/aiops/types.ts b/x-pack/test/functional/apps/aiops/types.ts
index cb4005dcacde3..0f8f4a4d07d22 100644
--- a/x-pack/test/functional/apps/aiops/types.ts
+++ b/x-pack/test/functional/apps/aiops/types.ts
@@ -54,6 +54,7 @@ interface TestDataExpectedWithoutSampleProbability {
export interface TestData {
suiteTitle: string;
analysisType: LogRateAnalysisType;
+ autoRun: boolean;
dataGenerator: LogRateAnalysisDataGenerator;
isSavedSearch?: boolean;
sourceIndexOrSavedSearch: string;
diff --git a/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts b/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts
index dde260c408258..1a33ede2e700c 100644
--- a/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts
+++ b/x-pack/test/functional/services/aiops/log_rate_analysis_page.ts
@@ -133,6 +133,16 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr
await this.assertHistogramBrushesExist();
},
+ async clickNoAutoRunButton() {
+ await testSubjects.clickWhenNotDisabledWithoutRetry(
+ 'aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton'
+ );
+
+ await retry.tryForTime(30 * 1000, async () => {
+ await testSubjects.missingOrFail('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton');
+ });
+ },
+
async clickRerunAnalysisButton(shouldRerun: boolean) {
await testSubjects.clickWhenNotDisabledWithoutRetry(
`aiopsRerunAnalysisButton${shouldRerun ? ' shouldRerun' : ''}`
@@ -250,6 +260,10 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr
);
},
+ async assertNoAutoRunButtonExists() {
+ await testSubjects.existOrFail('aiopsLogRateAnalysisNoAutoRunContentRunAnalysisButton');
+ },
+
async assertProgressTitle(expectedProgressTitle: string) {
await retry.tryForTime(30 * 1000, async () => {
await testSubjects.existOrFail('aiopProgressTitle');
From fc78d14fe902beb4f96a323ea95450bd0b88fa86 Mon Sep 17 00:00:00 2001
From: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com>
Date: Thu, 18 Apr 2024 10:52:43 +0100
Subject: [PATCH 03/96] [Console Migration] Use dev config for migration of
embeddable console (#180848)
## Summary
This PR migrates the embeddable Console to Monaco based on the
`enableMonaco` dev config.
---
.../application/containers/embeddable/console_wrapper.tsx | 5 ++++-
.../application/containers/embeddable/embeddable_console.tsx | 5 ++++-
src/plugins/console/public/plugin.ts | 2 ++
src/plugins/console/public/types/embeddable_console.ts | 1 +
4 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx b/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx
index e01df7ed7b658..6429d8894d33c 100644
--- a/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx
+++ b/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx
@@ -113,7 +113,7 @@ interface ConsoleWrapperProps
export const ConsoleWrapper = (props: ConsoleWrapperProps) => {
const [dependencies, setDependencies] = useState(null);
- const { core, usageCollection, onKeyDown } = props;
+ const { core, usageCollection, onKeyDown, isMonacoEnabled } = props;
useEffect(() => {
if (dependencies === null) {
@@ -165,6 +165,9 @@ export const ConsoleWrapper = (props: ConsoleWrapperProps) => {
autocompleteInfo,
},
theme$,
+ config: {
+ isMonacoEnabled,
+ },
}}
>
diff --git a/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx b/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx
index 651182f83a404..218496b9d81ab 100644
--- a/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx
+++ b/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx
@@ -48,6 +48,7 @@ export const EmbeddableConsole = ({
usageCollection,
setDispatch,
alternateView,
+ isMonacoEnabled,
}: EmbeddableConsoleProps & EmbeddableConsoleDependencies) => {
const [consoleState, consoleDispatch] = useReducer(
store.reducer,
@@ -149,7 +150,9 @@ export const EmbeddableConsole = ({
)}
- {showConsole ? : null}
+ {showConsole ? (
+
+ ) : null}
{showAlternateView ? (
diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts
index 9feb04b8f6663..43cedf1fa4bb0 100644
--- a/src/plugins/console/public/plugin.ts
+++ b/src/plugins/console/public/plugin.ts
@@ -112,6 +112,7 @@ export class ConsoleUIPlugin
public start(core: CoreStart, deps: AppStartUIPluginDependencies): ConsolePluginStart {
const {
ui: { enabled: isConsoleUiEnabled, embeddedEnabled: isEmbeddedConsoleEnabled },
+ dev: { enableMonaco: isMonacoEnabled },
} = this.ctx.config.get
();
const consoleStart: ConsolePluginStart = {};
@@ -134,6 +135,7 @@ export class ConsoleUIPlugin
this._embeddableConsole.setDispatch(d);
},
alternateView: this._embeddableConsole.alternateView,
+ isMonacoEnabled,
});
};
consoleStart.isEmbeddedConsoleAvailable = () =>
diff --git a/src/plugins/console/public/types/embeddable_console.ts b/src/plugins/console/public/types/embeddable_console.ts
index da1e99f5e64c2..07a801c40287b 100644
--- a/src/plugins/console/public/types/embeddable_console.ts
+++ b/src/plugins/console/public/types/embeddable_console.ts
@@ -25,6 +25,7 @@ export interface EmbeddableConsoleDependencies {
usageCollection?: UsageCollectionStart;
setDispatch: (dispatch: Dispatch | null) => void;
alternateView?: EmbeddedConsoleView;
+ isMonacoEnabled: boolean;
}
export type EmbeddedConsoleAction =
From ec5a53848da71b0935664ed7cb336549dfede73f Mon Sep 17 00:00:00 2001
From: Konrad Szwarc
Date: Thu, 18 Apr 2024 12:43:43 +0200
Subject: [PATCH 04/96] [MKI][EDR Workflows] Switch from placeholder explore to
edr workflows tests (#181123)
We need to update the current quality gate configuration for the EDR
Workflows team. It's using a placeholder called
`mki_security_solution_explore` , and we want to replace it with the
actual target EDR Workflows configuration.
This is a prerequisite for https://github.com/elastic/kibana/pull/181080
---
...rverless-security-solution-quality-gate-defend-workflows.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.buildkite/pipeline-resource-definitions/security-solution-quality-gate/kibana-serverless-security-solution-quality-gate-defend-workflows.yml b/.buildkite/pipeline-resource-definitions/security-solution-quality-gate/kibana-serverless-security-solution-quality-gate-defend-workflows.yml
index f3e9358fa4f30..32cd883223c18 100644
--- a/.buildkite/pipeline-resource-definitions/security-solution-quality-gate/kibana-serverless-security-solution-quality-gate-defend-workflows.yml
+++ b/.buildkite/pipeline-resource-definitions/security-solution-quality-gate/kibana-serverless-security-solution-quality-gate-defend-workflows.yml
@@ -16,7 +16,7 @@ spec:
description: "[MKI] Executes Cypress tests for the Defend Workflows team"
spec:
repository: elastic/kibana
- pipeline_file: .buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml
+ pipeline_file: .buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml
provider_settings:
build_branches: false
build_pull_requests: false
From 4127163bd913dccfbd17badbb9ce1a7a5b2afadc Mon Sep 17 00:00:00 2001
From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com>
Date: Thu, 18 Apr 2024 13:01:18 +0100
Subject: [PATCH 05/96] revert change to shared UX markdown component for
dashboard vis (#180906)
## Summary
This PR reverts the change to the shared UX markdown component that
happened here https://github.com/elastic/kibana/pull/176478. The
aforementioned PR introduced couple of visual issues that were uncaught
during the migration.
The known issues that have been brought up that informed the decision to
revert this change, are itemised below;
- https://github.com/elastic/kibana/issues/180576
- https://github.com/elastic/kibana/issues/180452
- https://github.com/elastic/sdh-kibana/issues/4608
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
### For maintainers
- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
src/plugins/vis_type_markdown/kibana.jsonc | 1 +
.../public/markdown_vis_controller.test.tsx | 5 +----
.../vis_type_markdown/public/markdown_vis_controller.tsx | 5 ++---
src/plugins/vis_type_markdown/tsconfig.json | 2 +-
.../apps/dashboard_elements/markdown/_markdown_vis.ts | 7 +++++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/plugins/vis_type_markdown/kibana.jsonc b/src/plugins/vis_type_markdown/kibana.jsonc
index d3d1d3bd9f29b..476dcc0605ad4 100644
--- a/src/plugins/vis_type_markdown/kibana.jsonc
+++ b/src/plugins/vis_type_markdown/kibana.jsonc
@@ -13,6 +13,7 @@
],
"requiredBundles": [
"expressions",
+ "kibanaReact",
"visDefaultEditor",
"visualizations"
]
diff --git a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
index 04f86e815e841..963f3b0ecbbb2 100644
--- a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
+++ b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx
@@ -29,9 +29,7 @@ describe('markdown vis controller', () => {
expect(getByText('markdown')).toMatchInlineSnapshot(`
markdown
@@ -55,8 +53,7 @@ describe('markdown vis controller', () => {
expect(getByText(/testing/i)).toMatchInlineSnapshot(`
- Testing
- html
+ Testing <a>html</a>
`);
});
diff --git a/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx b/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx
index 7c0b44d24860d..d941adf573591 100644
--- a/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx
+++ b/src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx
@@ -7,7 +7,7 @@
*/
import React, { useEffect } from 'react';
-import { Markdown } from '@kbn/shared-ux-markdown';
+import { Markdown } from '@kbn/kibana-react-plugin/public';
import { MarkdownVisParams } from './types';
import './markdown_vis.scss';
@@ -29,8 +29,7 @@ const MarkdownVisComponent = ({
);
diff --git a/src/plugins/vis_type_markdown/tsconfig.json b/src/plugins/vis_type_markdown/tsconfig.json
index 833dc7404cc0f..5b9639787e12c 100644
--- a/src/plugins/vis_type_markdown/tsconfig.json
+++ b/src/plugins/vis_type_markdown/tsconfig.json
@@ -16,7 +16,7 @@
"@kbn/i18n",
"@kbn/i18n-react",
"@kbn/config-schema",
- "@kbn/shared-ux-markdown",
+ "@kbn/kibana-react-plugin",
],
"exclude": [
"target/**/*",
diff --git a/test/functional/apps/dashboard_elements/markdown/_markdown_vis.ts b/test/functional/apps/dashboard_elements/markdown/_markdown_vis.ts
index d524fd23e0393..e440cdddc77c7 100644
--- a/test/functional/apps/dashboard_elements/markdown/_markdown_vis.ts
+++ b/test/functional/apps/dashboard_elements/markdown/_markdown_vis.ts
@@ -39,9 +39,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(h1Txt).to.equal('Heading 1');
});
- it('should not render html in markdown', async function () {
+ it('should not render html in markdown as html', async function () {
const actual = await PageObjects.visChart.getMarkdownText();
- expect(actual).to.equal('Heading 1');
+
+ expect(actual).to.equal(
+ 'Heading 1\nInline HTML that should not be rendered as html
'
+ );
});
it('should auto apply changes if auto mode is turned on', async function () {
From ef5af172336d76cc553ae72cbcf289327c2ea428 Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Thu, 18 Apr 2024 06:03:43 -0700
Subject: [PATCH 06/96] [Security] Remove usage of deprecated React rendering
utilities (#180517)
## Summary
Partially addresses https://github.com/elastic/kibana-team/issues/805
Follows https://github.com/elastic/kibana/pull/180003
These changes come up from searching in the code and finding where
certain kinds of deprecated AppEx-SharedUX modules are imported.
**Reviewers: Please interact with critical paths through the UI
components touched in this PR, ESPECIALLY in terms of testing dark mode
and i18n.**
This focuses on code within Security.
Note: this also makes inclusion of `i18n` and `analytics` dependencies
consistent. Analytics is an optional dependency for the SharedUX
modules, which wrap `KibanaErrorBoundaryProvider` and is designed to
capture telemetry about errors that are caught in the error boundary.
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../src/services.tsx | 4 +-
.../public/cluster_address_form.test.tsx | 8 +-
.../cluster_configuration_form.test.tsx | 10 +-
.../public/enrollment_token_form.test.tsx | 8 +-
.../interactive_setup/public/plugin.tsx | 26 ++---
.../interactive_setup/public/theme/index.tsx | 24 ----
.../public/verification_code_form.test.tsx | 8 +-
src/plugins/interactive_setup/tsconfig.json | 3 +-
.../account_management_app.tsx | 47 ++++----
.../access_agreement_app.test.ts | 4 +-
.../access_agreement/access_agreement_app.ts | 6 +-
.../access_agreement_page.tsx | 16 ++-
.../logged_out/logged_out_app.test.ts | 4 +-
.../logged_out/logged_out_app.ts | 6 +-
.../logged_out/logged_out_page.tsx | 22 ++--
.../authentication/login/login_app.test.ts | 4 +-
.../public/authentication/login/login_app.ts | 6 +-
.../authentication/login/login_page.tsx | 16 ++-
.../overwritten_session_app.test.ts | 4 +-
.../overwritten_session_app.ts | 6 +-
.../overwritten_session_page.tsx | 17 ++-
x-pack/plugins/security/public/index.ts | 5 +-
.../api_keys_grid/api_keys_grid_page.test.tsx | 15 ++-
.../api_keys/api_keys_management_app.tsx | 44 +++----
.../role_mappings_management_app.tsx | 85 +++++++-------
.../roles/edit_role/edit_role_page.test.tsx | 5 +-
.../roles/edit_role/edit_role_page.tsx | 21 ++--
.../confirm_delete/confirm_delete.tsx | 13 +--
.../roles/roles_grid/roles_grid_page.tsx | 20 ++--
.../roles/roles_management_app.test.tsx | 110 ++++++++++++++++--
.../management/roles/roles_management_app.tsx | 96 +++++++--------
.../users/edit_user/create_user_page.test.tsx | 9 +-
.../users/edit_user/edit_user_page.test.tsx | 13 +--
.../management/users/users_management_app.tsx | 32 +++--
.../nav_control/nav_control_service.tsx | 39 +++----
x-pack/plugins/security/public/plugin.tsx | 2 +-
.../session/session_expiration_toast.test.tsx | 4 +-
.../session/session_expiration_toast.tsx | 7 +-
.../public/session/session_timeout.test.ts | 5 +-
.../public/session/session_timeout.ts | 9 +-
x-pack/plugins/security/tsconfig.json | 4 +-
.../management/spaces_management_app.tsx | 47 ++++----
.../spaces/public/nav_control/nav_control.tsx | 30 +++--
.../public/space_selector/space_selector.tsx | 14 +--
.../space_selector/space_selector_app.tsx | 6 +-
x-pack/plugins/spaces/tsconfig.json | 1 +
46 files changed, 435 insertions(+), 450 deletions(-)
delete mode 100644 src/plugins/interactive_setup/public/theme/index.tsx
diff --git a/packages/kbn-user-profile-components/src/services.tsx b/packages/kbn-user-profile-components/src/services.tsx
index 683b9ac0ce01e..71ce5a0ff844f 100644
--- a/packages/kbn-user-profile-components/src/services.tsx
+++ b/packages/kbn-user-profile-components/src/services.tsx
@@ -48,10 +48,10 @@ export interface UserProfilesKibanaDependencies {
userProfiles: UserProfileAPIClient;
};
/**
- * Handler from the '@kbn/kibana-react-plugin/public' Plugin
+ * Handler from the '@kbn/react-kibana-mount' Package
*
* ```
- * import { toMountPoint } from '@kbn/kibana-react-plugin/public';
+ * import { toMountPoint } from '@kbn/react-kibana-mount';
* ```
*/
toMountPoint: typeof toMountPoint;
diff --git a/src/plugins/interactive_setup/public/cluster_address_form.test.tsx b/src/plugins/interactive_setup/public/cluster_address_form.test.tsx
index 51ca8dd830c3f..a532a81deac34 100644
--- a/src/plugins/interactive_setup/public/cluster_address_form.test.tsx
+++ b/src/plugins/interactive_setup/public/cluster_address_form.test.tsx
@@ -9,7 +9,7 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
-import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
+import { coreMock } from '@kbn/core/public/mocks';
import { ClusterAddressForm } from './cluster_address_form';
import { Providers } from './plugin';
@@ -21,8 +21,6 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
describe('ClusterAddressForm', () => {
jest.setTimeout(20_000);
- const theme$ = themeServiceMock.createTheme$();
-
it('calls enrollment API when submitting form', async () => {
const coreStart = coreMock.createStart();
coreStart.http.post.mockResolvedValue({});
@@ -30,7 +28,7 @@ describe('ClusterAddressForm', () => {
const onSuccess = jest.fn();
const { findByRole, findByLabelText } = render(
-
+
);
@@ -54,7 +52,7 @@ describe('ClusterAddressForm', () => {
const onSuccess = jest.fn();
const { findAllByText, findByRole, findByLabelText } = render(
-
+
);
diff --git a/src/plugins/interactive_setup/public/cluster_configuration_form.test.tsx b/src/plugins/interactive_setup/public/cluster_configuration_form.test.tsx
index 2e66d9b4f125e..01512cee06cf2 100644
--- a/src/plugins/interactive_setup/public/cluster_configuration_form.test.tsx
+++ b/src/plugins/interactive_setup/public/cluster_configuration_form.test.tsx
@@ -9,7 +9,7 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
-import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
+import { coreMock } from '@kbn/core/public/mocks';
import { ClusterConfigurationForm } from './cluster_configuration_form';
import { Providers } from './plugin';
@@ -21,8 +21,6 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
describe('ClusterConfigurationForm', () => {
jest.setTimeout(20_000);
- const theme$ = themeServiceMock.createTheme$();
-
it('calls enrollment API for https addresses when submitting form', async () => {
const coreStart = coreMock.createStart();
coreStart.http.post.mockResolvedValue({});
@@ -30,7 +28,7 @@ describe('ClusterConfigurationForm', () => {
const onSuccess = jest.fn();
const { findByRole, findByLabelText } = render(
-
+
{
const onSuccess = jest.fn();
const { findByRole } = render(
-
+
{
const onSuccess = jest.fn();
const { findAllByText, findByRole, findByLabelText } = render(
-
+
{
jest.setTimeout(20_000);
- const theme$ = themeServiceMock.createTheme$();
-
it('calls enrollment API when submitting form', async () => {
const coreStart = coreMock.createStart();
coreStart.http.post.mockResolvedValue({});
@@ -38,7 +36,7 @@ describe('EnrollmentTokenForm', () => {
const onSuccess = jest.fn();
const { findByRole, findByLabelText } = render(
-
+
);
@@ -64,7 +62,7 @@ describe('EnrollmentTokenForm', () => {
const onSuccess = jest.fn();
const { findAllByText, findByRole, findByLabelText } = render(
-
+
);
diff --git a/src/plugins/interactive_setup/public/plugin.tsx b/src/plugins/interactive_setup/public/plugin.tsx
index aa688cd0e53b5..84c581fe44a9a 100644
--- a/src/plugins/interactive_setup/public/plugin.tsx
+++ b/src/plugins/interactive_setup/public/plugin.tsx
@@ -9,13 +9,11 @@
import type { FunctionComponent } from 'react';
import React from 'react';
import ReactDOM from 'react-dom';
-import type { Observable } from 'rxjs';
-import type { CoreSetup, CoreStart, CoreTheme, Plugin } from '@kbn/core/public';
-import { I18nProvider } from '@kbn/i18n-react';
+import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { App } from './app';
-import { KibanaThemeProvider } from './theme'; // TODO: replace this with the one exported from `kibana_react` after https://github.com/elastic/kibana/issues/119204 is implemented.
import { KibanaProvider } from './use_kibana';
import { VerificationProvider } from './use_verification';
@@ -26,7 +24,7 @@ export class InteractiveSetupPlugin implements Plugin {
title: 'Configure Elastic to get started',
appRoute: '/',
chromeless: true,
- mount: async ({ element, theme$ }) => {
+ mount: async ({ element }) => {
const url = new URL(window.location.href);
const defaultCode = url.searchParams.get('code') || undefined;
const onSuccess = () => {
@@ -36,7 +34,7 @@ export class InteractiveSetupPlugin implements Plugin {
const [services] = await core.getStartServices();
ReactDOM.render(
-
+
,
element
@@ -46,26 +44,22 @@ export class InteractiveSetupPlugin implements Plugin {
});
}
- public start(core: CoreStart) {}
+ public start(_core: CoreStart) {}
}
export interface ProvidersProps {
services: CoreStart;
- theme$: Observable;
defaultCode?: string;
}
export const Providers: FunctionComponent = ({
defaultCode,
services,
- theme$,
children,
}) => (
-
-
-
- {children}
-
-
-
+
+
+ {children}
+
+
);
diff --git a/src/plugins/interactive_setup/public/theme/index.tsx b/src/plugins/interactive_setup/public/theme/index.tsx
deleted file mode 100644
index 8007d392a071d..0000000000000
--- a/src/plugins/interactive_setup/public/theme/index.tsx
+++ /dev/null
@@ -1,24 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import type { EuiProviderProps } from '@elastic/eui';
-import React, { type FC } from 'react';
-import type { Observable } from 'rxjs';
-
-import type { CoreTheme } from '@kbn/core-theme-browser';
-import { KibanaThemeProvider as KbnThemeProvider } from '@kbn/react-kibana-context-theme';
-
-export interface KibanaThemeProviderProps {
- theme$: Observable;
- modify?: EuiProviderProps<{}>['modify'];
-}
-
-/** @deprecated use `KibanaThemeProvider` from `@kbn/react-kibana-context-theme */
-export const KibanaThemeProvider: FC = ({ theme$, modify, children }) => (
- {children}
-);
diff --git a/src/plugins/interactive_setup/public/verification_code_form.test.tsx b/src/plugins/interactive_setup/public/verification_code_form.test.tsx
index efa5721c8d5b8..c7329396c0cd1 100644
--- a/src/plugins/interactive_setup/public/verification_code_form.test.tsx
+++ b/src/plugins/interactive_setup/public/verification_code_form.test.tsx
@@ -9,7 +9,7 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
-import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
+import { coreMock } from '@kbn/core/public/mocks';
import { Providers } from './plugin';
import { VerificationCodeForm } from './verification_code_form';
@@ -21,8 +21,6 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
describe('VerificationCodeForm', () => {
jest.setTimeout(20_000);
- const theme$ = themeServiceMock.createTheme$();
-
it('calls enrollment API when submitting form', async () => {
const coreStart = coreMock.createStart();
coreStart.http.post.mockResolvedValue({});
@@ -30,7 +28,7 @@ describe('VerificationCodeForm', () => {
const onSuccess = jest.fn();
const { findByRole, findByLabelText } = render(
-
+
);
@@ -67,7 +65,7 @@ describe('VerificationCodeForm', () => {
const onSuccess = jest.fn();
const { findAllByText, findByRole, findByLabelText } = render(
-
+
);
diff --git a/src/plugins/interactive_setup/tsconfig.json b/src/plugins/interactive_setup/tsconfig.json
index c8ae9dc8fdfb6..51fff541980cc 100644
--- a/src/plugins/interactive_setup/tsconfig.json
+++ b/src/plugins/interactive_setup/tsconfig.json
@@ -23,9 +23,8 @@
"@kbn/utils",
"@kbn/core-logging-server-mocks",
"@kbn/core-preboot-server",
- "@kbn/react-kibana-context-theme",
- "@kbn/core-theme-browser",
"@kbn/security-hardening",
+ "@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/security/public/account_management/account_management_app.tsx b/x-pack/plugins/security/public/account_management/account_management_app.tsx
index e7a41e49f3cb5..e648e6409536a 100644
--- a/x-pack/plugins/security/public/account_management/account_management_app.tsx
+++ b/x-pack/plugins/security/public/account_management/account_management_app.tsx
@@ -17,9 +17,8 @@ import type {
StartServicesAccessor,
} from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
-import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
-import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { AuthenticationServiceSetup } from '@kbn/security-plugin-types-public';
import { Router } from '@kbn/shared-ux-router';
@@ -87,27 +86,25 @@ export const Providers: FunctionComponent = ({
onChange,
children,
}) => (
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
);
diff --git a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts
index 40f667996e590..eb008293c0f1d 100644
--- a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts
+++ b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.test.ts
@@ -57,8 +57,8 @@ describe('accessAgreementApp', () => {
const mockRenderApp = jest.requireMock('./access_agreement_page').renderAccessAgreementPage;
expect(mockRenderApp).toHaveBeenCalledTimes(1);
expect(mockRenderApp).toHaveBeenCalledWith(
- coreStartMock.i18n,
- { element: appMountParams.element, theme$: appMountParams.theme$ },
+ coreStartMock,
+ { element: appMountParams.element },
{
http: coreStartMock.http,
notifications: coreStartMock.notifications,
diff --git a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.ts b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.ts
index b32056094cd7f..f429b5782a816 100644
--- a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.ts
+++ b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_app.ts
@@ -23,14 +23,14 @@ export const accessAgreementApp = Object.freeze({
}),
chromeless: true,
appRoute: '/security/access_agreement',
- async mount({ element, theme$ }: AppMountParameters) {
+ async mount({ element }: AppMountParameters) {
const [[coreStart], { renderAccessAgreementPage }] = await Promise.all([
getStartServices(),
import('./access_agreement_page'),
]);
return renderAccessAgreementPage(
- coreStart.i18n,
- { element, theme$ },
+ coreStart,
+ { element },
{
http: coreStart.http,
notifications: coreStart.notifications,
diff --git a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx
index ba96ee16a03bd..c4c3c51d94d6f 100644
--- a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx
+++ b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.tsx
@@ -23,15 +23,15 @@ import ReactMarkdown from 'react-markdown';
import type {
AppMountParameters,
- CoreStart,
FatalErrorsStart,
HttpStart,
NotificationsStart,
} from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
+import type { StartServices } from '../..';
import { parseNext } from '../../../common/parse_next';
import { AuthenticationStatePage } from '../components';
@@ -128,16 +128,14 @@ export function AccessAgreementPage({ http, fatalErrors, notifications }: Props)
}
export function renderAccessAgreementPage(
- i18nStart: CoreStart['i18n'],
- { element, theme$ }: Pick,
+ services: StartServices,
+ { element }: Pick,
props: Props
) {
ReactDOM.render(
-
-
-
-
- ,
+
+
+ ,
element
);
diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts b/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts
index 966efccf0c3ba..0b68d63985652 100644
--- a/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts
+++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.test.ts
@@ -53,8 +53,8 @@ describe('loggedOutApp', () => {
const mockRenderApp = jest.requireMock('./logged_out_page').renderLoggedOutPage;
expect(mockRenderApp).toHaveBeenCalledTimes(1);
expect(mockRenderApp).toHaveBeenCalledWith(
- coreStartMock.i18n,
- { element: appMountParams.element, theme$: appMountParams.theme$ },
+ coreStartMock,
+ { element: appMountParams.element },
{ basePath: coreStartMock.http.basePath, customBranding: coreStartMock.customBranding }
);
});
diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.ts b/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.ts
index 1141a9b3ffaca..291630c634cf2 100644
--- a/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.ts
+++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_app.ts
@@ -28,14 +28,14 @@ export const loggedOutApp = Object.freeze({
title: i18n.translate('xpack.security.loggedOutAppTitle', { defaultMessage: 'Logged out' }),
chromeless: true,
appRoute: '/security/logged_out',
- async mount({ element, theme$ }: AppMountParameters) {
+ async mount({ element }: AppMountParameters) {
const [[coreStart], { renderLoggedOutPage }] = await Promise.all([
getStartServices(),
import('./logged_out_page'),
]);
return renderLoggedOutPage(
- coreStart.i18n,
- { element, theme$ },
+ coreStart,
+ { element },
{ basePath: coreStart.http.basePath, customBranding: coreStart.customBranding }
);
},
diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx
index 7b077f60cc1e0..c380bfa11c2b9 100644
--- a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx
+++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx
@@ -10,15 +10,11 @@ import React from 'react';
import ReactDOM from 'react-dom';
import useObservable from 'react-use/lib/useObservable';
-import type {
- AppMountParameters,
- CoreStart,
- CustomBrandingStart,
- IBasePath,
-} from '@kbn/core/public';
+import type { AppMountParameters, CustomBrandingStart, IBasePath } from '@kbn/core/public';
import { FormattedMessage } from '@kbn/i18n-react';
-import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
+import type { StartServices } from '../..';
import { parseNext } from '../../../common/parse_next';
import { AuthenticationStatePage } from '../components';
@@ -47,16 +43,14 @@ export function LoggedOutPage({ basePath, customBranding }: Props) {
}
export function renderLoggedOutPage(
- i18nStart: CoreStart['i18n'],
- { element, theme$ }: Pick,
+ services: StartServices,
+ { element }: Pick,
props: Props
) {
ReactDOM.render(
-
-
-
-
- ,
+
+
+ ,
element
);
diff --git a/x-pack/plugins/security/public/authentication/login/login_app.test.ts b/x-pack/plugins/security/public/authentication/login/login_app.test.ts
index fd62978aa98b1..dee1086c35aa3 100644
--- a/x-pack/plugins/security/public/authentication/login/login_app.test.ts
+++ b/x-pack/plugins/security/public/authentication/login/login_app.test.ts
@@ -60,8 +60,8 @@ describe('loginApp', () => {
const mockRenderApp = jest.requireMock('./login_page').renderLoginPage;
expect(mockRenderApp).toHaveBeenCalledTimes(1);
expect(mockRenderApp).toHaveBeenCalledWith(
- coreStartMock.i18n,
- { element: appMountParams.element, theme$: appMountParams.theme$ },
+ coreStartMock,
+ { element: appMountParams.element },
{
http: coreStartMock.http,
customBranding: coreStartMock.customBranding,
diff --git a/x-pack/plugins/security/public/authentication/login/login_app.ts b/x-pack/plugins/security/public/authentication/login/login_app.ts
index df944a259e4d7..495bd8d1037c8 100644
--- a/x-pack/plugins/security/public/authentication/login/login_app.ts
+++ b/x-pack/plugins/security/public/authentication/login/login_app.ts
@@ -31,14 +31,14 @@ export const loginApp = Object.freeze({
title: i18n.translate('xpack.security.loginAppTitle', { defaultMessage: 'Login' }),
chromeless: true,
appRoute: '/login',
- async mount({ element, theme$ }: AppMountParameters) {
+ async mount({ element }: AppMountParameters) {
const [[coreStart], { renderLoginPage }] = await Promise.all([
getStartServices(),
import('./login_page'),
]);
return renderLoginPage(
- coreStart.i18n,
- { element, theme$ },
+ coreStart,
+ { element },
{
customBranding: coreStart.customBranding,
http: coreStart.http,
diff --git a/x-pack/plugins/security/public/authentication/login/login_page.tsx b/x-pack/plugins/security/public/authentication/login/login_page.tsx
index a645ab70a540f..741f61016928c 100644
--- a/x-pack/plugins/security/public/authentication/login/login_page.tsx
+++ b/x-pack/plugins/security/public/authentication/login/login_page.tsx
@@ -25,7 +25,6 @@ import { BehaviorSubject } from 'rxjs';
import type {
AppMountParameters,
- CoreStart,
CustomBrandingStart,
FatalErrorsStart,
HttpStart,
@@ -34,10 +33,11 @@ import type {
import type { CustomBranding } from '@kbn/core-custom-branding-common';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { LoginFormProps } from './components';
import { DisabledLoginForm, LoginForm, LoginFormMessageType } from './components';
+import type { StartServices } from '../..';
import {
AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER,
LOGOUT_REASON_QUERY_STRING_PARAMETER,
@@ -368,16 +368,14 @@ export class LoginPage extends Component {
}
export function renderLoginPage(
- i18nStart: CoreStart['i18n'],
- { element, theme$ }: Pick,
+ services: StartServices,
+ { element }: Pick,
props: Props
) {
ReactDOM.render(
-
-
-
-
- ,
+
+
+ ,
element
);
diff --git a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts
index dee4149b08507..f81dc8f4f27ca 100644
--- a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts
+++ b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts
@@ -63,8 +63,8 @@ describe('overwrittenSessionApp', () => {
).renderOverwrittenSessionPage;
expect(mockRenderApp).toHaveBeenCalledTimes(1);
expect(mockRenderApp).toHaveBeenCalledWith(
- coreStartMock.i18n,
- { element: appMountParams.element, theme$: appMountParams.theme$ },
+ coreStartMock,
+ { element: appMountParams.element },
{ authc: authcMock, basePath: coreStartMock.http.basePath }
);
});
diff --git a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.ts b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.ts
index b0f3f1059dfb4..a121377a5f3fc 100644
--- a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.ts
+++ b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.ts
@@ -25,14 +25,14 @@ export const overwrittenSessionApp = Object.freeze({
}),
chromeless: true,
appRoute: '/security/overwritten_session',
- async mount({ element, theme$ }: AppMountParameters) {
+ async mount({ element }: AppMountParameters) {
const [[coreStart], { renderOverwrittenSessionPage }] = await Promise.all([
getStartServices(),
import('./overwritten_session_page'),
]);
return renderOverwrittenSessionPage(
- coreStart.i18n,
- { element, theme$ },
+ coreStart,
+ { element },
{
authc,
basePath: coreStart.http.basePath,
diff --git a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.tsx b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.tsx
index 6f39a2608e1cd..42ef361bc1fd9 100644
--- a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.tsx
+++ b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.tsx
@@ -9,11 +9,12 @@ import { EuiButton } from '@elastic/eui';
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
-import type { AppMountParameters, CoreStart, IBasePath } from '@kbn/core/public';
+import type { AppMountParameters, IBasePath } from '@kbn/core/public';
import { FormattedMessage } from '@kbn/i18n-react';
-import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { AuthenticationServiceSetup } from '@kbn/security-plugin-types-public';
+import type { StartServices } from '../..';
import { parseNext } from '../../../common/parse_next';
import { AuthenticationStatePage } from '../components';
@@ -53,16 +54,14 @@ export function OverwrittenSessionPage({ authc, basePath }: Props) {
}
export function renderOverwrittenSessionPage(
- i18nStart: CoreStart['i18n'],
- { element, theme$ }: Pick,
+ services: StartServices,
+ { element }: Pick,
props: Props
) {
ReactDOM.render(
-
-
-
-
- ,
+
+
+ ,
element
);
diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts
index 66f0c08521d52..7db535b030f3b 100644
--- a/x-pack/plugins/security/public/index.ts
+++ b/x-pack/plugins/security/public/index.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import type { PluginInitializer, PluginInitializerContext } from '@kbn/core/public';
+import type { CoreStart, PluginInitializer, PluginInitializerContext } from '@kbn/core/public';
import type { SecurityPluginSetup } from '@kbn/security-plugin-types-public';
import type {
@@ -40,3 +40,6 @@ export const plugin: PluginInitializer<
PluginSetupDependencies,
PluginStartDependencies
> = (initializerContext: PluginInitializerContext) => new SecurityPlugin(initializerContext);
+
+// services needed for rendering React using shared modules
+export type StartServices = Pick;
diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx
index b10a1eaeaacda..159495b67400c 100644
--- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx
+++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx
@@ -9,7 +9,7 @@ import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
-import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
+import { coreMock } from '@kbn/core/public/mocks';
import { APIKeysGridPage } from './api_keys_grid_page';
import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock';
@@ -33,7 +33,6 @@ describe('APIKeysGridPage', () => {
const consoleWarnMock = jest.spyOn(console, 'error').mockImplementation();
let coreStart: ReturnType;
- const theme$ = themeServiceMock.createTheme$();
const { authc } = securityMock.createSetup();
beforeEach(() => {
@@ -102,12 +101,12 @@ describe('APIKeysGridPage', () => {
};
const { findByText, queryByTestId, getByText } = render(
-
+
);
- expect(await queryByTestId('apiKeysCreateTableButton')).not.toBeInTheDocument();
+ expect(queryByTestId('apiKeysCreateTableButton')).not.toBeInTheDocument();
expect(await findByText(/Loading API keys/)).not.toBeInTheDocument();
@@ -134,7 +133,7 @@ describe('APIKeysGridPage', () => {
};
const { findByText } = render(
-
+
);
@@ -158,7 +157,7 @@ describe('APIKeysGridPage', () => {
};
const { findByText } = render(
-
+
);
@@ -185,7 +184,7 @@ describe('APIKeysGridPage', () => {
};
const { findByText } = render(
-
+
);
@@ -215,7 +214,7 @@ describe('APIKeysGridPage', () => {
};
const { findByText, queryByText } = render(
-
+
);
diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx
index c15ea9d9e731e..08fcbaac520e3 100644
--- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx
+++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx
@@ -9,13 +9,12 @@ import type { History } from 'history';
import type { FunctionComponent } from 'react';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
-import type { Observable } from 'rxjs';
-import type { CoreStart, CoreTheme, StartServicesAccessor } from '@kbn/core/public';
+import type { CoreStart, StartServicesAccessor } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
-import { I18nProvider } from '@kbn/i18n-react';
-import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { RegisterManagementAppArgs } from '@kbn/management-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { AuthenticationServiceSetup } from '@kbn/security-plugin-types-public';
import { Router } from '@kbn/shared-ux-router';
@@ -43,7 +42,7 @@ export const apiKeysManagementApp = Object.freeze({
title: i18n.translate('xpack.security.management.apiKeysTitle', {
defaultMessage: 'API keys',
}),
- async mount({ element, theme$, setBreadcrumbs, history }) {
+ async mount({ element, setBreadcrumbs, history }) {
const [[coreStart], { APIKeysGridPage }] = await Promise.all([
getStartServices(),
import('./api_keys_grid'),
@@ -52,7 +51,6 @@ export const apiKeysManagementApp = Object.freeze({
render(
;
history: History;
authc: AuthenticationServiceSetup;
onChange?: BreadcrumbsChangeHandler;
@@ -87,27 +84,24 @@ export interface ProvidersProps {
export const Providers: FunctionComponent = ({
services,
- theme$,
history,
authc,
onChange,
children,
}) => (
-
-
-
-
-
-
- {children}
-
-
-
-
-
+
+
+
+
+
+ {children}
+
+
+
+
);
diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx
index c44ddbdc8ac83..b3fc8ec71b890 100644
--- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx
+++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx
@@ -11,8 +11,9 @@ import { useParams } from 'react-router-dom';
import type { StartServicesAccessor } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
-import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { RegisterManagementAppArgs } from '@kbn/management-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { Route, Router } from '@kbn/shared-ux-router';
import {
@@ -39,7 +40,7 @@ export const roleMappingsManagementApp = Object.freeze({
id: this.id,
order: 40,
title,
- async mount({ element, theme$, setBreadcrumbs, history }) {
+ async mount({ element, setBreadcrumbs, history }) {
const [
[core],
{ RoleMappingsGridPage },
@@ -91,47 +92,45 @@ export const roleMappingsManagementApp = Object.freeze({
};
render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
element
);
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
index 846818e540dcc..622d10a4faab3 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
@@ -12,6 +12,7 @@ import React from 'react';
import type { BuildFlavor } from '@kbn/config';
import type { Capabilities } from '@kbn/core/public';
import { coreMock, scopedHistoryMock } from '@kbn/core/public/mocks';
+import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks';
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
@@ -192,6 +193,7 @@ function getProps({
return [];
}
});
+ const analyticsMock = analyticsServiceMock.createAnalyticsServiceStart();
const i18nMock = i18nServiceMock.createStartContract();
const themeMock = themeServiceMock.createStartContract();
return {
@@ -213,7 +215,8 @@ function getProps({
spacesApiUi,
buildFlavor,
theme: themeMock,
- i18nStart: i18nMock,
+ i18n: i18nMock,
+ analytics: analyticsMock,
};
}
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
index 8e0cb5f0cf065..745cb2d68ab07 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
@@ -30,10 +30,8 @@ import type {
DocLinksStart,
FatalErrorsSetup,
HttpStart,
- I18nStart,
NotificationsStart,
ScopedHistory,
- ThemeServiceStart,
} from '@kbn/core/public';
import type { IHttpFetchError } from '@kbn/core-http-browser';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
@@ -53,6 +51,7 @@ import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges';
import { ReservedRoleBadge } from './reserved_role_badge';
import type { RoleValidationResult } from './validate_role';
import { RoleValidator } from './validate_role';
+import type { StartServices } from '../../..';
import type {
BuiltinESPrivileges,
RawKibanaPrivileges,
@@ -76,7 +75,7 @@ import { KibanaPrivileges } from '../model';
import type { PrivilegesAPIClient } from '../privileges_api_client';
import type { RolesAPIClient } from '../roles_api_client';
-interface Props {
+export interface Props extends StartServices {
action: 'edit' | 'clone';
roleName?: string;
dataViews?: DataViewsContract;
@@ -94,8 +93,6 @@ interface Props {
history: ScopedHistory;
spacesApiUi?: SpacesApiUi;
buildFlavor: BuildFlavor;
- i18nStart: I18nStart;
- theme: ThemeServiceStart;
cloudOrgUrl?: string;
}
@@ -336,9 +333,8 @@ export const EditRolePage: FunctionComponent = ({
history,
spacesApiUi,
buildFlavor,
- i18nStart,
- theme,
cloudOrgUrl,
+ ...startServices
}) => {
const isDarkMode = useDarkMode();
@@ -402,7 +398,7 @@ export const EditRolePage: FunctionComponent = ({
const [kibanaPrivileges, builtInESPrivileges] = privileges;
const getFormTitle = () => {
- let titleText;
+ let titleText: JSX.Element;
const props: HTMLProps = {
tabIndex: 0,
};
@@ -505,7 +501,7 @@ export const EditRolePage: FunctionComponent = ({
name: e.target.value,
});
- const onNameBlur = (e: FocusEvent) => {
+ const onNameBlur = (_e: FocusEvent) => {
if (!isEditingExistingRole && previousName !== role.name) {
setPreviousName(role.name);
doesRoleExist().then((roleExists) => {
@@ -676,7 +672,7 @@ export const EditRolePage: FunctionComponent = ({
>,
- { i18n: i18nStart, theme }
+ startServices
),
});
} else {
@@ -738,10 +734,7 @@ export const EditRolePage: FunctionComponent = ({
>,
- {
- i18n: i18nStart,
- theme,
- }
+ startServices
),
});
} else {
diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx
index 35aea7c9b99ca..bc434f61dab0d 100644
--- a/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx
+++ b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx
@@ -20,24 +20,22 @@ import {
import React, { Component, Fragment } from 'react';
import type { BuildFlavor } from '@kbn/config';
-import type { I18nStart, NotificationsStart } from '@kbn/core/public';
+import type { NotificationsStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import type { ThemeServiceStart } from '@kbn/react-kibana-context-common';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { PublicMethodsOf } from '@kbn/utility-types';
+import type { StartServices } from '../../../..';
import type { RolesAPIClient } from '../../roles_api_client';
-interface Props {
+interface Props extends StartServices {
rolesToDelete: string[];
callback: (rolesToDelete: string[], errors: string[]) => void;
onCancel: () => void;
notifications: NotificationsStart;
rolesAPIClient: PublicMethodsOf;
buildFlavor: BuildFlavor;
- theme: ThemeServiceStart;
- i18nStart: I18nStart;
cloudOrgUrl?: string;
}
@@ -213,10 +211,7 @@ export class ConfirmDelete extends Component {
>,
- {
- i18n: this.props.i18nStart,
- theme: this.props.theme,
- }
+ this.props
),
});
}
diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx
index 2dc2628d23030..4273a31e207c9 100644
--- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx
+++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx
@@ -23,15 +23,15 @@ import _ from 'lodash';
import React, { Component } from 'react';
import type { BuildFlavor } from '@kbn/config';
-import type { I18nStart, NotificationsStart, ScopedHistory } from '@kbn/core/public';
+import type { NotificationsStart, ScopedHistory } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public';
-import type { ThemeServiceStart } from '@kbn/react-kibana-context-common';
import type { PublicMethodsOf } from '@kbn/utility-types';
import { ConfirmDelete } from './confirm_delete';
import { PermissionDenied } from './permission_denied';
+import type { StartServices } from '../../..';
import type { Role } from '../../../../common';
import {
getExtendedRoleDeprecationNotice,
@@ -44,14 +44,12 @@ import { DeprecatedBadge, DisabledBadge, ReservedBadge } from '../../badges';
import { ActionsEuiTableFormatting } from '../../table_utils';
import type { RolesAPIClient } from '../roles_api_client';
-interface Props {
+export interface Props extends StartServices {
notifications: NotificationsStart;
rolesAPIClient: PublicMethodsOf;
history: ScopedHistory;
readOnly?: boolean;
buildFlavor: BuildFlavor;
- theme: ThemeServiceStart;
- i18nStart: I18nStart;
cloudOrgUrl?: string;
}
@@ -180,12 +178,8 @@ export class RolesGridPage extends Component {
onCancel={this.onCancelDelete}
rolesToDelete={this.state.selection.map((role) => role.name)}
callback={this.handleDelete}
- notifications={this.props.notifications}
- rolesAPIClient={this.props.rolesAPIClient}
- buildFlavor={this.props.buildFlavor}
- theme={this.props.theme}
- i18nStart={this.props.i18nStart}
cloudOrgUrl={this.props.cloudOrgUrl}
+ {...this.props}
/>
) : null}
@@ -237,7 +231,7 @@ export class RolesGridPage extends Component {
direction: 'asc',
},
}}
- rowProps={(role: Role) => {
+ rowProps={(_role: Role) => {
return {
'data-test-subj': `roleRow`,
};
@@ -257,7 +251,7 @@ export class RolesGridPage extends Component {
defaultMessage: 'Role',
}),
sortable: true,
- render: (name: string, record: Role) => {
+ render: (name: string, _record: Role) => {
return (
{
defaultMessage: 'Status',
}),
sortable: (role: Role) => isRoleEnabled(role) && !isRoleDeprecated(role),
- render: (metadata: Role['metadata'], record: Role) => {
+ render: (_metadata: Role['metadata'], record: Role) => {
return this.getRoleStatusBadges(record);
},
});
diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx
index 19bdb6d41c2ad..1c417a977b856 100644
--- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx
+++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx
@@ -13,17 +13,55 @@ import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/
import { featuresPluginMock } from '@kbn/features-plugin/public/mocks';
import type { Unmount } from '@kbn/management-plugin/public/types';
+import type { Props as EditRolePageProps } from './edit_role/edit_role_page';
+import type { Props as RolesGridPageProps } from './roles_grid/roles_grid_page';
import { rolesManagementApp } from './roles_management_app';
import { licenseMock } from '../../../common/licensing/index.mock';
jest.mock('./roles_grid', () => ({
- RolesGridPage: (props: any) => `Roles Page: ${JSON.stringify(props)}`,
+ RolesGridPage: ({
+ // props object is too big to include into test snapshot, so we just check for existence of fields we care about
+ buildFlavor,
+ cloudOrgUrl,
+ readOnly,
+ rolesAPIClient,
+ }: RolesGridPageProps) =>
+ `Roles Page: ${JSON.stringify(
+ {
+ buildFlavor,
+ cloudOrgUrl,
+ readOnly,
+ rolesAPIClient: rolesAPIClient ? 'rolesAPIClient' : undefined,
+ },
+ null,
+ ' '
+ )}`,
}));
jest.mock('./edit_role', () => ({
- // `docLinks` object is too big to include into test snapshot, so we just check its existence.
- EditRolePage: (props: any) =>
- `Role Edit Page: ${JSON.stringify({ ...props, docLinks: props.docLinks ? {} : undefined })}`,
+ EditRolePage: ({
+ // props object is too big to include into test snapshot, so we just check for existence of fields we care about
+ buildFlavor,
+ cloudOrgUrl,
+ roleName,
+ indicesAPIClient,
+ privilegesAPIClient,
+ rolesAPIClient,
+ userAPIClient,
+ }: EditRolePageProps) =>
+ `Role Edit Page: ${JSON.stringify(
+ {
+ buildFlavor,
+ cloudOrgUrl,
+ roleName,
+ indicesAPIClient: indicesAPIClient ? 'indicesAPIClient' : undefined,
+ privilegesAPIClient: privilegesAPIClient ? 'privilegesAPIClient' : undefined,
+ rolesAPIClient: rolesAPIClient ? 'rolesAPIClient' : undefined,
+ userAPIClient: userAPIClient ? 'userAPIClient' : undefined,
+ },
+ null,
+ ' '
+ )}`,
}));
async function mountApp(basePath: string, pathname: string, buildFlavor?: BuildFlavor) {
@@ -94,7 +132,11 @@ describe('rolesManagementApp', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"readOnly":false,"buildFlavor":"traditional","i18nStart":{},"theme":{"theme$":{}}}
+ Roles Page: {
+ "buildFlavor": "traditional",
+ "readOnly": false,
+ "rolesAPIClient": "rolesAPIClient"
+ }
`);
@@ -116,7 +158,13 @@ describe('rolesManagementApp', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{},"roles":{"save":true}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}},"buildFlavor":"traditional","i18nStart":{},"theme":{"theme$":{}}}
+ Role Edit Page: {
+ "buildFlavor": "traditional",
+ "indicesAPIClient": "indicesAPIClient",
+ "privilegesAPIClient": "privilegesAPIClient",
+ "rolesAPIClient": "rolesAPIClient",
+ "userAPIClient": "userAPIClient"
+ }
`);
@@ -143,7 +191,14 @@ describe('rolesManagementApp', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","roleName":"role@name","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{},"roles":{"save":true}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/role@name","search":"","hash":""}},"buildFlavor":"traditional","i18nStart":{},"theme":{"theme$":{}}}
+ Role Edit Page: {
+ "buildFlavor": "traditional",
+ "roleName": "role@name",
+ "indicesAPIClient": "indicesAPIClient",
+ "privilegesAPIClient": "privilegesAPIClient",
+ "rolesAPIClient": "rolesAPIClient",
+ "userAPIClient": "userAPIClient"
+ }
`);
@@ -170,7 +225,14 @@ describe('rolesManagementApp', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{},"roles":{"save":true}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}},"buildFlavor":"traditional","i18nStart":{},"theme":{"theme$":{}}}
+ Role Edit Page: {
+ "buildFlavor": "traditional",
+ "roleName": "someRoleName",
+ "indicesAPIClient": "indicesAPIClient",
+ "privilegesAPIClient": "privilegesAPIClient",
+ "rolesAPIClient": "rolesAPIClient",
+ "userAPIClient": "userAPIClient"
+ }
`);
@@ -228,7 +290,11 @@ describe('rolesManagementApp - serverless', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"readOnly":false,"buildFlavor":"serverless","i18nStart":{},"theme":{"theme$":{}}}
+ Roles Page: {
+ "buildFlavor": "serverless",
+ "readOnly": false,
+ "rolesAPIClient": "rolesAPIClient"
+ }
`);
@@ -257,7 +323,13 @@ describe('rolesManagementApp - serverless', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{},"roles":{"save":true}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}},"buildFlavor":"serverless","i18nStart":{},"theme":{"theme$":{}}}
+ Role Edit Page: {
+ "buildFlavor": "serverless",
+ "indicesAPIClient": "indicesAPIClient",
+ "privilegesAPIClient": "privilegesAPIClient",
+ "rolesAPIClient": "rolesAPIClient",
+ "userAPIClient": "userAPIClient"
+ }
`);
@@ -288,7 +360,14 @@ describe('rolesManagementApp - serverless', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","roleName":"role@name","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{},"roles":{"save":true}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/role@name","search":"","hash":""}},"buildFlavor":"serverless","i18nStart":{},"theme":{"theme$":{}}}
+ Role Edit Page: {
+ "buildFlavor": "serverless",
+ "roleName": "role@name",
+ "indicesAPIClient": "indicesAPIClient",
+ "privilegesAPIClient": "privilegesAPIClient",
+ "rolesAPIClient": "rolesAPIClient",
+ "userAPIClient": "userAPIClient"
+ }
`);
@@ -319,7 +398,14 @@ describe('rolesManagementApp - serverless', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}}},"http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{},"roles":{"save":true}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}},"buildFlavor":"serverless","i18nStart":{},"theme":{"theme$":{}}}
+ Role Edit Page: {
+ "buildFlavor": "serverless",
+ "roleName": "someRoleName",
+ "indicesAPIClient": "indicesAPIClient",
+ "privilegesAPIClient": "privilegesAPIClient",
+ "rolesAPIClient": "rolesAPIClient",
+ "userAPIClient": "userAPIClient"
+ }
`);
diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx
index f82548f89b352..3a5d9f57b432a 100644
--- a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx
+++ b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx
@@ -12,8 +12,9 @@ import { useParams } from 'react-router-dom';
import type { BuildFlavor } from '@kbn/config';
import type { FatalErrorsSetup, StartServicesAccessor } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
-import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { RegisterManagementAppArgs } from '@kbn/management-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { Route, Router } from '@kbn/shared-ux-router';
import type { SecurityLicense } from '../../../common';
@@ -35,7 +36,7 @@ interface CreateParams {
export const rolesManagementApp = Object.freeze({
id: 'roles',
- create({ license, fatalErrors, getStartServices, buildFlavor }: CreateParams) {
+ create({ license, getStartServices, buildFlavor }: CreateParams) {
const title =
buildFlavor === 'serverless'
? i18n.translate('xpack.security.management.rolesTitleServerless', {
@@ -48,7 +49,7 @@ export const rolesManagementApp = Object.freeze({
id: this.id,
order: 20,
title,
- async mount({ element, theme$, setBreadcrumbs, history }) {
+ async mount({ element, setBreadcrumbs, history }) {
const [
[startServices, { dataViews, features, spaces, cloud }],
{ RolesGridPage },
@@ -67,15 +68,7 @@ export const rolesManagementApp = Object.freeze({
import('../users'),
]);
- const {
- application,
- docLinks,
- http,
- i18n: i18nStart,
- notifications,
- chrome,
- theme: themeServiceStart,
- } = startServices;
+ const { application, http, chrome } = startServices;
chrome.docTitle.change(title);
@@ -109,63 +102,54 @@ export const rolesManagementApp = Object.freeze({
indicesAPIClient={new IndicesAPIClient(http)}
privilegesAPIClient={new PrivilegesAPIClient(http)}
getFeatures={features.getFeatures}
- http={http}
- notifications={notifications}
- fatalErrors={fatalErrors}
license={license}
- docLinks={docLinks}
uiCapabilities={application.capabilities}
dataViews={dataViews}
history={history}
spacesApiUi={spacesApiUi}
buildFlavor={buildFlavor}
- i18nStart={i18nStart}
- theme={themeServiceStart}
cloudOrgUrl={cloud?.organizationUrl}
+ {...startServices}
/>
);
};
render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
element
);
diff --git a/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx
index ef41ba92c7850..7771560826f6f 100644
--- a/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx
+++ b/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx
@@ -9,7 +9,7 @@ import { fireEvent, render, waitFor, within } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
-import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
+import { coreMock } from '@kbn/core/public/mocks';
import { CreateUserPage } from './create_user_page';
import { securityMock } from '../../../mocks';
@@ -23,7 +23,6 @@ describe('CreateUserPage', () => {
jest.setTimeout(15_000);
const coreStart = coreMock.createStart();
- const theme$ = themeServiceMock.createTheme$();
let history = createMemoryHistory({ initialEntries: ['/create'] });
const authc = securityMock.createSetup().authc;
@@ -45,7 +44,7 @@ describe('CreateUserPage', () => {
coreStart.http.post.mockResolvedValue({});
const { findByRole, findByLabelText } = render(
-
+
);
@@ -80,7 +79,7 @@ describe('CreateUserPage', () => {
};
render(
-
+
);
@@ -103,7 +102,7 @@ describe('CreateUserPage', () => {
]);
const { findAllByText, findByRole, findByLabelText } = render(
-
+
);
diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx
index 673fd2e89599a..badc5990d91b2 100644
--- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx
+++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx
@@ -9,7 +9,7 @@ import { fireEvent, render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
-import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
+import { coreMock } from '@kbn/core/public/mocks';
import { EditUserPage } from './edit_user_page';
import { securityMock } from '../../../mocks';
@@ -25,7 +25,6 @@ const userMock = {
describe('EditUserPage', () => {
const coreStart = coreMock.createStart();
- const theme$ = themeServiceMock.createTheme$();
let history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] });
const authc = securityMock.createSetup().authc;
@@ -53,7 +52,7 @@ describe('EditUserPage', () => {
coreStart.http.get.mockResolvedValueOnce([]);
const { findByText } = render(
-
+
);
@@ -73,7 +72,7 @@ describe('EditUserPage', () => {
coreStart.http.get.mockResolvedValueOnce([]);
const { findByRole, findByText } = render(
-
+
);
@@ -94,7 +93,7 @@ describe('EditUserPage', () => {
coreStart.http.get.mockResolvedValueOnce([]);
const { findByRole, findByText } = render(
-
+
);
@@ -124,7 +123,7 @@ describe('EditUserPage', () => {
]);
const { findByText } = render(
-
+
);
@@ -143,7 +142,7 @@ describe('EditUserPage', () => {
};
const { findByRole, findAllByRole } = render(
-
+
);
diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx
index ffb7d1e5618d9..4935db6273bb6 100644
--- a/x-pack/plugins/security/public/management/users/users_management_app.tsx
+++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx
@@ -10,13 +10,12 @@ import type { FunctionComponent } from 'react';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Redirect } from 'react-router-dom';
-import type { Observable } from 'rxjs';
-import type { CoreStart, CoreTheme, StartServicesAccessor } from '@kbn/core/public';
+import type { CoreStart, StartServicesAccessor } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
-import { I18nProvider } from '@kbn/i18n-react';
-import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { RegisterManagementAppArgs } from '@kbn/management-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { AuthenticationServiceSetup } from '@kbn/security-plugin-types-public';
import { Route, Router, Routes } from '@kbn/shared-ux-router';
@@ -46,7 +45,7 @@ export const usersManagementApp = Object.freeze({
id: this.id,
order: 10,
title,
- async mount({ element, theme$, setBreadcrumbs, history }) {
+ async mount({ element, setBreadcrumbs, history }) {
const [
[coreStart],
{ UsersGridPage },
@@ -64,7 +63,6 @@ export const usersManagementApp = Object.freeze({
render(
;
history: History;
authc: AuthenticationServiceSetup;
onChange?: BreadcrumbsChangeHandler;
@@ -142,21 +139,18 @@ export interface ProvidersProps {
export const Providers: FunctionComponent = ({
services,
- theme$,
history,
authc,
onChange,
children,
}) => (
-
-
-
-
-
- {children}
-
-
-
-
-
+
+
+
+
+ {children}
+
+
+
+
);
diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx
index a9098396bd189..ebfe41aeafc0c 100644
--- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx
+++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx
@@ -9,13 +9,13 @@ import { sortBy } from 'lodash';
import type { FunctionComponent } from 'react';
import React from 'react';
import ReactDOM from 'react-dom';
-import type { Observable, Subscription } from 'rxjs';
+import type { Subscription } from 'rxjs';
import { BehaviorSubject, map, ReplaySubject, takeUntil } from 'rxjs';
import type { BuildFlavor } from '@kbn/config/src/types';
-import type { CoreStart, CoreTheme } from '@kbn/core/public';
-import { I18nProvider } from '@kbn/i18n-react';
-import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import type { CoreStart } from '@kbn/core/public';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type {
AuthenticationServiceSetup,
SecurityNavControlServiceStart,
@@ -110,18 +110,11 @@ export class SecurityNavControlService {
}
private registerSecurityNavControl(core: CoreStart, authc: AuthenticationServiceSetup) {
- const { theme$ } = core.theme;
-
core.chrome.navControls.registerRight({
order: 4000,
mount: (element: HTMLElement) => {
ReactDOM.render(
-
+
;
}
export const Providers: FunctionComponent = ({
authc,
services,
- theme$,
securityApiClients,
children,
}) => (
-
-
-
-
-
- {children}
-
-
-
-
-
+
+
+
+
+ {children}
+
+
+
+
);
diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx
index 1aaf7a882d6a2..333f8736731ac 100644
--- a/x-pack/plugins/security/public/plugin.tsx
+++ b/x-pack/plugins/security/public/plugin.tsx
@@ -196,7 +196,7 @@ export class SecurityPlugin
const sessionExpired = new SessionExpired(application, logoutUrl, tenant);
http.intercept(new UnauthorizedResponseHttpInterceptor(sessionExpired, anonymousPaths));
- this.sessionTimeout = new SessionTimeout(notifications, sessionExpired, http, tenant);
+ this.sessionTimeout = new SessionTimeout(core, notifications, sessionExpired, http, tenant);
this.sessionTimeout.start();
this.securityCheckupService.start({ http, notifications, docLinks });
diff --git a/x-pack/plugins/security/public/session/session_expiration_toast.test.tsx b/x-pack/plugins/security/public/session/session_expiration_toast.test.tsx
index 0cec3b1848008..f2f1f6ff92f79 100644
--- a/x-pack/plugins/security/public/session/session_expiration_toast.test.tsx
+++ b/x-pack/plugins/security/public/session/session_expiration_toast.test.tsx
@@ -8,6 +8,7 @@ import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { of } from 'rxjs';
+import { coreMock } from '@kbn/core/public/mocks';
import { I18nProvider } from '@kbn/i18n-react';
import { createSessionExpirationToast, SessionExpirationToast } from './session_expiration_toast';
@@ -15,6 +16,7 @@ import type { SessionState } from './session_timeout';
describe('createSessionExpirationToast', () => {
it('creates a toast', () => {
+ const coreStart = coreMock.createStart();
const sessionState$ = of({
lastExtensionTime: Date.now(),
expiresInMs: 60 * 1000,
@@ -22,7 +24,7 @@ describe('createSessionExpirationToast', () => {
});
const onExtend = jest.fn();
const onClose = jest.fn();
- const toast = createSessionExpirationToast(sessionState$, onExtend, onClose);
+ const toast = createSessionExpirationToast(coreStart, sessionState$, onExtend, onClose);
expect(toast).toEqual(
expect.objectContaining({
diff --git a/x-pack/plugins/security/public/session/session_expiration_toast.tsx b/x-pack/plugins/security/public/session/session_expiration_toast.tsx
index b5aa984a5ce1f..03e82d06b64da 100644
--- a/x-pack/plugins/security/public/session/session_expiration_toast.tsx
+++ b/x-pack/plugins/security/public/session/session_expiration_toast.tsx
@@ -15,9 +15,10 @@ import type { Observable } from 'rxjs';
import type { ToastInput } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react';
-import { toMountPoint } from '@kbn/kibana-react-plugin/public';
+import { toMountPoint } from '@kbn/react-kibana-mount';
import type { SessionState } from './session_timeout';
+import type { StartServices } from '..';
import { SESSION_GRACE_PERIOD_MS } from '../../common/constants';
export interface SessionExpirationToastProps {
@@ -74,6 +75,7 @@ export const SessionExpirationToast: FunctionComponent,
onExtend: () => Promise,
onClose: () => void
@@ -85,7 +87,8 @@ export const createSessionExpirationToast = (
defaultMessage: 'Session timeout',
}),
text: toMountPoint(
-
+ ,
+ services
),
onClose,
toastLifeTimeMs: 0x7fffffff, // Toast is hidden based on observable so using maximum possible timeout
diff --git a/x-pack/plugins/security/public/session/session_timeout.test.ts b/x-pack/plugins/security/public/session/session_timeout.test.ts
index fa0bcffc66b01..71180a83a8cbe 100644
--- a/x-pack/plugins/security/public/session/session_timeout.test.ts
+++ b/x-pack/plugins/security/public/session/session_timeout.test.ts
@@ -38,11 +38,12 @@ const visibilityStateMock = jest.spyOn(document, 'visibilityState', 'get');
function createSessionTimeout(expiresInMs: number | null = 60 * 60 * 1000, canBeExtended = true) {
const { notifications, http } = coreMock.createSetup();
+ const coreStart = coreMock.createStart();
const toast = Symbol();
notifications.toasts.add.mockReturnValue(toast as any);
const sessionExpired = createSessionExpiredMock();
const tenant = 'test';
- const sessionTimeout = new SessionTimeout(notifications, sessionExpired, http, tenant);
+ const sessionTimeout = new SessionTimeout(coreStart, notifications, sessionExpired, http, tenant);
http.fetch.mockResolvedValue({
expiresInMs,
@@ -294,7 +295,7 @@ describe('SessionTimeout', () => {
const [toast] = notifications.toasts.add.mock.calls[0] as [ToastInputFields];
- await toast.onClose!();
+ toast.onClose!();
expect(http.fetch).toHaveBeenCalledTimes(3);
expect(http.fetch).toHaveBeenLastCalledWith(
diff --git a/x-pack/plugins/security/public/session/session_timeout.ts b/x-pack/plugins/security/public/session/session_timeout.ts
index 34255657857c9..e119e43db1b72 100644
--- a/x-pack/plugins/security/public/session/session_timeout.ts
+++ b/x-pack/plugins/security/public/session/session_timeout.ts
@@ -17,6 +17,7 @@ import type {
import { createSessionExpirationToast } from './session_expiration_toast';
import type { SessionExpired } from './session_expired';
+import type { StartServices } from '..';
import {
SESSION_CHECK_MS,
SESSION_EXPIRATION_WARNING_MS,
@@ -57,6 +58,7 @@ export class SessionTimeout {
private stopLogoutTimer?: Function;
constructor(
+ private startServices: StartServices,
private notifications: NotificationsSetup,
private sessionExpired: Pick,
private http: HttpSetup,
@@ -265,7 +267,12 @@ export class SessionTimeout {
this.hideWarning(true);
return onExtend();
};
- const toast = createSessionExpirationToast(this.sessionState$, onExtend, onClose);
+ const toast = createSessionExpirationToast(
+ this.startServices,
+ this.sessionState$,
+ onExtend,
+ onClose
+ );
this.warningToast = this.notifications.toasts.add(toast);
}
};
diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json
index 2e0430de3b549..3dde17effc710 100644
--- a/x-pack/plugins/security/tsconfig.json
+++ b/x-pack/plugins/security/tsconfig.json
@@ -77,11 +77,11 @@
"@kbn/core-security-server",
"@kbn/core-http-router-server-internal",
"@kbn/react-kibana-mount",
- "@kbn/react-kibana-context-theme",
"@kbn/core-security-common",
- "@kbn/react-kibana-context-common",
+ "@kbn/react-kibana-context-render",
"@kbn/core-i18n-browser-mocks",
"@kbn/core-theme-browser-mocks",
+ "@kbn/core-analytics-browser-mocks",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
index ba63168875d75..1819b22cb7f3b 100644
--- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
+++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
@@ -11,8 +11,9 @@ import { useParams } from 'react-router-dom';
import type { StartServicesAccessor } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
-import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { RegisterManagementAppArgs } from '@kbn/management-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { Route, Router, Routes } from '@kbn/shared-ux-router';
@@ -39,7 +40,7 @@ export const spacesManagementApp = Object.freeze({
order: 2,
title,
- async mount({ element, theme$, setBreadcrumbs, history }) {
+ async mount({ element, setBreadcrumbs, history }) {
const [[coreStart, { features }], { SpacesGridPage }, { ManageSpacePage }] =
await Promise.all([getStartServices(), import('./spaces_grid'), import('./edit_space')]);
@@ -47,7 +48,7 @@ export const spacesManagementApp = Object.freeze({
text: title,
href: `/`,
};
- const { notifications, i18n: i18nStart, application, chrome } = coreStart;
+ const { notifications, application, chrome } = coreStart;
chrome.docTitle.change(title);
@@ -115,27 +116,25 @@ export const spacesManagementApp = Object.freeze({
};
render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
element
);
diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx
index 80b08bdd5f8f8..732be89eacd7b 100644
--- a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx
+++ b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx
@@ -10,13 +10,11 @@ import React, { lazy, Suspense } from 'react';
import ReactDOM from 'react-dom';
import type { CoreStart } from '@kbn/core/public';
-import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { SpacesManager } from '../spaces_manager';
export function initSpacesNavControl(spacesManager: SpacesManager, core: CoreStart) {
- const I18nContext = core.i18n.Context;
- const { theme$ } = core.theme;
core.chrome.navControls.registerLeft({
order: 1000,
mount(targetDomElement: HTMLElement) {
@@ -31,20 +29,18 @@ export function initSpacesNavControl(spacesManager: SpacesManager, core: CoreSta
);
ReactDOM.render(
-
-
- }>
-
-
-
- ,
+
+ }>
+
+
+ ,
targetDomElement
);
diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx
index ee7d320ead7cc..e90d1c4a10954 100644
--- a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx
+++ b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx
@@ -26,7 +26,7 @@ import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import type { CustomBranding } from '@kbn/core-custom-branding-common';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaSolutionAvatar } from '@kbn/shared-ux-avatar-solution';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
@@ -250,16 +250,14 @@ export class SpaceSelector extends Component {
}
export const renderSpaceSelectorApp = (
- i18nStart: CoreStart['i18n'],
- { element, theme$ }: Pick,
+ services: Pick,
+ { element }: Pick,
props: Props
) => {
ReactDOM.render(
-
-
-
-
- ,
+
+
+ ,
element
);
return () => ReactDOM.unmountComponentAtNode(element);
diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx
index 45fb334c2b864..61f19595e3177 100644
--- a/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx
+++ b/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx
@@ -26,14 +26,14 @@ export const spaceSelectorApp = Object.freeze({
}),
chromeless: true,
appRoute: '/spaces/space_selector',
- mount: async ({ element, theme$ }: AppMountParameters) => {
+ mount: async ({ element }: AppMountParameters) => {
const [[coreStart], { renderSpaceSelectorApp }] = await Promise.all([
getStartServices(),
import('./space_selector'),
]);
return renderSpaceSelectorApp(
- coreStart.i18n,
- { element, theme$ },
+ coreStart,
+ { element },
{
spacesManager,
serverBasePath: coreStart.http.basePath.serverBasePath,
diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json
index 9e89c8c2055ee..863027b382510 100644
--- a/x-pack/plugins/spaces/tsconfig.json
+++ b/x-pack/plugins/spaces/tsconfig.json
@@ -33,6 +33,7 @@
"@kbn/config",
"@kbn/shared-ux-avatar-solution",
"@kbn/core-http-server",
+ "@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*",
From d3207ec1ec0fb9b6cc43dedf0544e85bf4f90a48 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Thu, 18 Apr 2024 15:46:28 +0200
Subject: [PATCH 07/96] [SLOs] Add/edit form show tags suggestions (#181075)
## Summary
Show tags as suggestions from existing SLOs
---
.../src/rest_specs/routes/get_suggestions.ts | 22 +++++++++
.../src/rest_specs/routes/index.ts | 1 +
.../slo_edit_form_description_section.tsx | 6 ++-
.../slo_edit/hooks/use_fetch_suggestions.ts | 39 +++++++++++++++
.../slo/server/routes/slo/route.ts | 17 +++++++
.../server/services/get_slo_suggestions.ts | 47 +++++++++++++++++++
6 files changed, 130 insertions(+), 2 deletions(-)
create mode 100644 x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get_suggestions.ts
create mode 100644 x-pack/plugins/observability_solution/slo/public/pages/slo_edit/hooks/use_fetch_suggestions.ts
create mode 100644 x-pack/plugins/observability_solution/slo/server/services/get_slo_suggestions.ts
diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get_suggestions.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get_suggestions.ts
new file mode 100644
index 0000000000000..b67fa9aba17f1
--- /dev/null
+++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get_suggestions.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 * as t from 'io-ts';
+
+const getSLOSuggestionsResponseSchema = t.type({
+ tags: t.array(
+ t.type({
+ label: t.string,
+ value: t.string,
+ count: t.number,
+ })
+ ),
+});
+
+type GetSLOSuggestionsResponse = t.OutputOf;
+
+export { getSLOSuggestionsResponseSchema };
+export type { GetSLOSuggestionsResponse };
diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/index.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/index.ts
index afa90877253e3..57058d03790bb 100644
--- a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/index.ts
+++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/index.ts
@@ -20,3 +20,4 @@ export * from './manage';
export * from './delete_instance';
export * from './fetch_historical_summary';
export * from './put_settings';
+export * from './get_suggestions';
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/slo_edit_form_description_section.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/slo_edit_form_description_section.tsx
index 0e9af3602b9db..a210021674f6b 100644
--- a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/slo_edit_form_description_section.tsx
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/slo_edit_form_description_section.tsx
@@ -19,6 +19,7 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
+import { useFetchSLOSuggestions } from '../hooks/use_fetch_suggestions';
import { OptionalText } from './common/optional_text';
import { CreateSLOForm } from '../types';
import { maxWidth } from './slo_edit_form';
@@ -29,6 +30,8 @@ export function SloEditFormDescriptionSection() {
const descriptionId = useGeneratedHtmlId({ prefix: 'sloDescription' });
const tagsId = useGeneratedHtmlId({ prefix: 'tags' });
+ const { suggestions } = useFetchSLOSuggestions();
+
return (
{
if (selected.length) {
diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/hooks/use_fetch_suggestions.ts b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/hooks/use_fetch_suggestions.ts
new file mode 100644
index 0000000000000..7e66bc954f23c
--- /dev/null
+++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/hooks/use_fetch_suggestions.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 { useQuery } from '@tanstack/react-query';
+import { GetSLOSuggestionsResponse } from '@kbn/slo-schema';
+import { useKibana } from '../../../utils/kibana_react';
+
+export function useFetchSLOSuggestions() {
+ const { http } = useKibana().services;
+
+ const { isLoading, isError, isSuccess, data } = useQuery({
+ queryKey: ['fetchSLOSuggestions'],
+ queryFn: async ({ signal }) => {
+ try {
+ return await http.get(
+ '/internal/api/observability/slos/suggestions',
+ {
+ signal,
+ }
+ );
+ } catch (error) {
+ // ignore error
+ }
+ },
+ refetchOnWindowFocus: false,
+ keepPreviousData: true,
+ });
+
+ return {
+ suggestions: data,
+ isLoading,
+ isSuccess,
+ isError,
+ };
+}
diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts
index 10b9ebe0d0ae6..80a9a18542da9 100644
--- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts
+++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts
@@ -24,6 +24,7 @@ import {
resetSLOParamsSchema,
updateSLOParamsSchema,
} from '@kbn/slo-schema';
+import { GetSLOSuggestions } from '../../services/get_slo_suggestions';
import type { IndicatorTypes } from '../../domain/models';
import {
CreateSLO,
@@ -445,6 +446,21 @@ const findSLOGroupsRoute = createSloServerRoute({
},
});
+const getSLOSuggestionsRoute = createSloServerRoute({
+ endpoint: 'GET /internal/api/observability/slos/suggestions',
+ options: {
+ tags: ['access:slo_read'],
+ access: 'internal',
+ },
+ handler: async ({ context }) => {
+ await assertPlatinumLicense(context);
+
+ const soClient = (await context.core).savedObjects.client;
+ const getSLOSuggestions = new GetSLOSuggestions(soClient);
+ return await getSLOSuggestions.execute();
+ },
+});
+
const deleteSloInstancesRoute = createSloServerRoute({
endpoint: 'POST /api/observability/slos/_delete_instances 2023-10-31',
options: {
@@ -642,4 +658,5 @@ export const sloRouteRepository = {
...getSLOInstancesRoute,
...resetSLORoute,
...findSLOGroupsRoute,
+ ...getSLOSuggestionsRoute,
};
diff --git a/x-pack/plugins/observability_solution/slo/server/services/get_slo_suggestions.ts b/x-pack/plugins/observability_solution/slo/server/services/get_slo_suggestions.ts
new file mode 100644
index 0000000000000..a9dfef5a14aa1
--- /dev/null
+++ b/x-pack/plugins/observability_solution/slo/server/services/get_slo_suggestions.ts
@@ -0,0 +1,47 @@
+/*
+ * 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 { SavedObjectsClientContract } from '@kbn/core/server';
+import { GetSLOSuggestionsResponse } from '@kbn/slo-schema';
+import { SO_SLO_TYPE } from '../saved_objects';
+type Buckets = Array<{
+ key: string;
+ doc_count: number;
+}>;
+
+interface AggsResponse {
+ tagsAggs: {
+ buckets: Buckets;
+ };
+}
+export class GetSLOSuggestions {
+ constructor(private soClient: SavedObjectsClientContract) {}
+
+ public async execute(): Promise {
+ const findResponse = await this.soClient.find({
+ type: SO_SLO_TYPE,
+ perPage: 0,
+ aggs: {
+ tagsAggs: {
+ terms: {
+ field: `${SO_SLO_TYPE}.attributes.tags`,
+ size: 10000,
+ },
+ },
+ },
+ });
+ const { tagsAggs } = (findResponse?.aggregations as AggsResponse) ?? {};
+
+ return {
+ tags:
+ tagsAggs?.buckets?.map(({ key, doc_count: count }) => ({
+ label: key,
+ value: key,
+ count,
+ })) ?? [],
+ };
+ }
+}
From 168e4c3a7c9283befde6b211d2aa7b30b3bd5365 Mon Sep 17 00:00:00 2001
From: Joe McElroy
Date: Thu, 18 Apr 2024 15:36:52 +0100
Subject: [PATCH 08/96] [Search] [Playground] fix citations bug (#181134)
Fixes citations bug where cannot see the citation text.
![image](https://github.com/elastic/kibana/assets/49480/98398ccb-133a-4ee8-b403-6eaf73c12a60)
---
.../message_list/citations_table.test.tsx | 43 +++++++++++++++++++
.../message_list/citations_table.tsx | 10 ++++-
2 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 x-pack/plugins/search_playground/public/components/message_list/citations_table.test.tsx
diff --git a/x-pack/plugins/search_playground/public/components/message_list/citations_table.test.tsx b/x-pack/plugins/search_playground/public/components/message_list/citations_table.test.tsx
new file mode 100644
index 0000000000000..06363c61f7ade
--- /dev/null
+++ b/x-pack/plugins/search_playground/public/components/message_list/citations_table.test.tsx
@@ -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 React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import { CitationsTable } from './citations_table';
+
+describe('CitationsTable component', () => {
+ const citationsMock = [
+ {
+ metadata: {
+ _id: '1',
+ _score: 0.5,
+ _index: 'index1',
+ },
+ content: 'Lorem ipsum dolor sit amet.',
+ },
+ {
+ metadata: {
+ _id: '2',
+ _score: 0.5,
+ _index: 'index1',
+ },
+ content: 'Consectetur adipiscing elit.',
+ },
+ ];
+
+ it('should expand row on snippet button click', () => {
+ const { getByTestId, getByText, queryByText } = render(
+
+ );
+
+ expect(queryByText('Lorem ipsum dolor sit amet.')).not.toBeInTheDocument();
+
+ fireEvent.click(getByTestId('expandButton-1'));
+
+ expect(getByText('Lorem ipsum dolor sit amet.')).toBeInTheDocument();
+ });
+});
diff --git a/x-pack/plugins/search_playground/public/components/message_list/citations_table.tsx b/x-pack/plugins/search_playground/public/components/message_list/citations_table.tsx
index 6de4ec26485d7..dbe89d489ef27 100644
--- a/x-pack/plugins/search_playground/public/components/message_list/citations_table.tsx
+++ b/x-pack/plugins/search_playground/public/components/message_list/citations_table.tsx
@@ -16,6 +16,13 @@ export const CitationsTable: React.FC = ({ citations }) =>
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<
Record
>({});
+
+ // Add an ID to each citation to use for expanding the row
+ const citationsWithId = citations.map((citation) => ({
+ ...citation,
+ id: citation.metadata._id,
+ }));
+
const toggleDetails = (citation: Doc) => {
const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
@@ -51,6 +58,7 @@ export const CitationsTable: React.FC = ({ citations }) =>
toggleDetails(citation)}
iconType={
itemIdToExpandedRowMapValues[citation.metadata._id] ? 'arrowDown' : 'arrowRight'
@@ -64,7 +72,7 @@ export const CitationsTable: React.FC = ({ citations }) =>
},
},
]}
- items={citations}
+ items={citationsWithId}
itemId="id"
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
isExpandable
From dfa001a7255dad7b598c8010ab8280fbded04248 Mon Sep 17 00:00:00 2001
From: Zacqary Adam Xeper
Date: Thu, 18 Apr 2024 10:03:02 -0500
Subject: [PATCH 09/96] [RAM] [Rule Form v2] Hide categories if there is only
one producer (#180629)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Part of #179963
On Observability serverless, the producer for every rule type is
Observability, so we want to hide the category facet:
This PR is agnostic to whether it's being deployed in stateful or
serverless, it simply calculates whether all the rule types received
from the server have the same producer. It does this BEFORE filtering by
search string, so on stateful, categories won't disappear if the user
has filtered down to a single category.
### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
---------
Co-authored-by: Faisal Kanout
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../src/rule_type_modal/components/index.tsx | 10 +++++
.../components/rule_type_list.tsx | 44 +++++++++++--------
.../components/rule_type_modal.tsx | 3 ++
3 files changed, 39 insertions(+), 18 deletions(-)
diff --git a/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/index.tsx b/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/index.tsx
index e7d89c76e6650..8ae14f462f772 100644
--- a/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/index.tsx
+++ b/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/index.tsx
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { countBy } from 'lodash';
import React, { useMemo, useState } from 'react';
import type { HttpStart } from '@kbn/core-http-browser';
import type { ToastsStart } from '@kbn/core-notifications-browser';
@@ -43,6 +44,14 @@ export const RuleTypeModalComponent: React.FC = ({
registeredRuleTypes,
});
+ // Count producers before filtering. This is used to determine if we should show the categories,
+ // and categories should only be hidden if there is only one producer BEFORE filters are applied,
+ // e.g. on oblt serverless
+ const hasOnlyOneProducer = useMemo(() => {
+ const producerCount = countBy([...ruleTypeIndex.values()], 'producer');
+ return Object.keys(producerCount).length === 1;
+ }, [ruleTypeIndex]);
+
const [ruleTypes, ruleTypeCountsByProducer] = useMemo(
() => filterAndCountRuleTypes(ruleTypeIndex, selectedProducer, searchString),
[ruleTypeIndex, searchString, selectedProducer]
@@ -58,6 +67,7 @@ export const RuleTypeModalComponent: React.FC = ({
onFilterByProducer={setSelectedProducer}
selectedProducer={selectedProducer}
searchString={searchString}
+ showCategories={!hasOnlyOneProducer}
/>
);
};
diff --git a/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/rule_type_list.tsx b/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/rule_type_list.tsx
index 7eb1ead13c727..8bb47f145de9a 100644
--- a/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/rule_type_list.tsx
+++ b/packages/kbn-alerts-ui-shared/src/rule_type_modal/components/rule_type_list.tsx
@@ -31,6 +31,7 @@ interface RuleTypeListProps {
selectedProducer: string | null;
ruleTypeCountsByProducer: RuleTypeCountsByProducer;
onClearFilters: () => void;
+ showCategories: boolean;
}
const producerToDisplayName = (producer: string) => {
@@ -44,6 +45,7 @@ export const RuleTypeList: React.FC = ({
selectedProducer,
ruleTypeCountsByProducer,
onClearFilters,
+ showCategories = true,
}) => {
const ruleTypesList = [...ruleTypes].sort((a, b) => a.name.localeCompare(b.name));
const { euiTheme } = useEuiTheme();
@@ -66,30 +68,36 @@ export const RuleTypeList: React.FC = ({
[ruleTypeCountsByProducer, onFilterByProducer, selectedProducer]
);
+ const onClickAll = useCallback(() => onFilterByProducer(null), [onFilterByProducer]);
+
return (
-
-
- onFilterByProducer(null), [onFilterByProducer])}
- isSelected={!selectedProducer}
- >
- All
-
- {facetList}
-
-
+ {showCategories && (
+
+
+
+ {i18n.translate('alertsUIShared.components.ruleTypeModal.allRuleTypes', {
+ defaultMessage: 'All',
+ })}
+
+ {facetList}
+
+
+ )}
void;
searchString: string;
selectedProducer: string | null;
+ showCategories: boolean;
}
export interface RuleTypeModalState {
@@ -64,6 +65,7 @@ export const RuleTypeModal: React.FC =
ruleTypeCountsByProducer,
searchString,
selectedProducer,
+ showCategories,
}) => {
const { euiTheme } = useEuiTheme();
const currentBreakpoint = useCurrentEuiBreakpoint() ?? 'm';
@@ -131,6 +133,7 @@ export const RuleTypeModal: React.FC =
onFilterByProducer={onFilterByProducer}
selectedProducer={selectedProducer}
onClearFilters={onClearFilters}
+ showCategories={showCategories}
/>
)}
From 0956f539ac21a2cf66453b1bfd89e589c0133aad Mon Sep 17 00:00:00 2001
From: Ash <1849116+ashokaditya@users.noreply.github.com>
Date: Thu, 18 Apr 2024 17:04:52 +0200
Subject: [PATCH 10/96] [8.15][Endpoint][Security Solution] Add an agent status
client (#180513)
## Summary
Adds an agent status service and client for fetching endpoint and third
party agent statuses. This PR is a smaller chunk of
elastic/kibana/pull/178625.
With `responseActionsSentinelOneV2Enabled` enabled, we use the new agent
status client to get agent status and pending actions.
- [x] Updates code that deals with sentinel one agent status
- [x] pending actions badge for sentinel one alerts and response console
- [x] adds a new feature flag `responseActionsSentinelOneV2Enabled`.
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
---
.../agent/get_agent_status_route.test.ts | 4 +-
.../common/endpoint/constants.ts | 2 +-
.../common/endpoint/types/agents.ts | 30 ++--
.../common/experimental_features.ts | 7 +
.../sentinel_one_agent_status.tsx | 6 +-
.../use_host_isolation_action.test.tsx | 149 ++++++++++--------
.../use_host_isolation_action.tsx | 39 +++--
.../use_sentinelone_host_isolation.tsx | 53 +++++--
.../highlighted_fields_cell.test.tsx | 66 +++++---
.../sentinel_one/header_sentinel_one_info.tsx | 5 +-
.../components/offline_callout.test.tsx | 135 +++++++++-------
.../components/offline_callout.tsx | 5 +-
.../routes/agent/agent_status_handler.test.ts | 35 ++--
.../routes/agent/agent_status_handler.ts | 47 +++---
.../services/actions/clients/errors.ts | 2 +-
.../get_response_actions_client.test.ts | 3 +-
.../clients/get_response_actions_client.ts | 10 +-
.../services/agent/agent_status.test.ts | 10 +-
.../endpoint/services/agent/agent_status.ts | 10 +-
.../endpoint/endpoint_agent_status_client.ts | 70 ++++++++
.../endpoint/services/agent/clients/errors.ts | 51 ++++++
.../agent/clients/get_agent_status_client.ts | 35 ++++
.../endpoint/services/agent/clients/index.ts | 12 ++
.../clients/lib/base_agent_status_client.ts | 33 ++++
.../services/agent/clients/lib/types.ts | 12 ++
.../sentinel_one_agent_status_client.ts | 133 ++++++++++++++++
.../agent/clients/sentinel_one/types.ts | 36 +++++
.../server/endpoint/services/agent/index.ts | 8 +
.../server/endpoint/services/index.ts | 1 +
29 files changed, 765 insertions(+), 244 deletions(-)
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/errors.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/index.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/base_agent_status_client.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/types.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/sentinel_one_agent_status_client.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/types.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/services/agent/index.ts
diff --git a/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts b/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts
index f9362aa6847a9..a0091e52b7652 100644
--- a/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts
+++ b/x-pack/plugins/security_solution/common/api/endpoint/agent/get_agent_status_route.test.ts
@@ -22,7 +22,7 @@ describe('Agent status api route schema', () => {
agentIds: '1',
agentType: 'foo',
})
- ).toThrow(/\[agentType\]: types that failed validation/);
+ ).toThrow(/\[agentType]: types that failed validation/);
});
it.each([
@@ -37,7 +37,7 @@ describe('Agent status api route schema', () => {
],
])('should error if %s are used for `agentIds`', (_, validateOptions) => {
expect(() => EndpointAgentStatusRequestSchema.query.validate(validateOptions)).toThrow(
- /\[agentIds\]:/
+ /\[agentIds]:/
);
});
diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts
index 7f41e36382137..a4dd0b2b44518 100644
--- a/x-pack/plugins/security_solution/common/endpoint/constants.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts
@@ -104,7 +104,7 @@ export const ACTION_AGENT_FILE_DOWNLOAD_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/{
export const ACTION_STATE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/state`;
/** Endpoint Agent Routes */
-export const ENDPOINT_AGENT_STATUS_ROUTE = `/internal${BASE_ENDPOINT_ROUTE}/agent_status`;
+export const AGENT_STATUS_ROUTE = `/internal${BASE_ENDPOINT_ROUTE}/agent_status`;
export const failedFleetActionErrorCode = '424';
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/agents.ts b/x-pack/plugins/security_solution/common/endpoint/types/agents.ts
index 99ba35f504d3e..646bb944fce19 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/agents.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/agents.ts
@@ -7,22 +7,26 @@
import type { HostStatus } from '.';
import type {
- ResponseActionsApiCommandNames,
ResponseActionAgentType,
+ ResponseActionsApiCommandNames,
} from '../service/response_actions/constants';
-export interface AgentStatusInfo {
- id: string;
- agentType: ResponseActionAgentType;
- found: boolean;
- isolated: boolean;
- isPendingUninstall: boolean;
- isUninstalled: boolean;
- lastSeen: string; // ISO date
- pendingActions: Record;
- status: HostStatus;
+export interface AgentStatusRecords {
+ [agentId: string]: {
+ agentId: string;
+ agentType: ResponseActionAgentType;
+ found: boolean;
+ isolated: boolean;
+ lastSeen: string; // ISO date
+ pendingActions: Record;
+ status: HostStatus;
+ };
}
-export interface AgentStatusApiResponse {
- data: Record;
+// TODO: 8.15 remove when `agentStatusClientEnabled` is enabled/removed
+export interface AgentStatusInfo {
+ [agentId: string]: AgentStatusRecords[string] & {
+ isPendingUninstall: boolean;
+ isUninstalled: boolean;
+ };
}
diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts
index 918d734561fa5..7bd1834513e30 100644
--- a/x-pack/plugins/security_solution/common/experimental_features.ts
+++ b/x-pack/plugins/security_solution/common/experimental_features.ts
@@ -85,6 +85,13 @@ export const allowedExperimentalValues = Object.freeze({
*/
responseActionsSentinelOneV2Enabled: false,
+ /**
+ * 8.15
+ * Enables use of agent status service to get agent status information
+ * for endpoint and third-party agents.
+ */
+ agentStatusClientEnabled: false,
+
/**
* Enables top charts on Alerts Page
*/
diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx
index 1936fb065d40e..98d5161843a5b 100644
--- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/sentinel_one_agent_status.tsx
@@ -11,7 +11,7 @@ import styled from 'styled-components';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { getAgentStatusText } from '../../../common/components/endpoint/agent_status_text';
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants';
-import { useGetSentinelOneAgentStatus } from './use_sentinelone_host_isolation';
+import { useAgentStatusHook } from './use_sentinelone_host_isolation';
import {
ISOLATED_LABEL,
ISOLATING_LABEL,
@@ -33,11 +33,13 @@ const EuiFlexGroupStyled = styled(EuiFlexGroup)`
export const SentinelOneAgentStatus = React.memo(
({ agentId, 'data-test-subj': dataTestSubj }: { agentId: string; 'data-test-subj'?: string }) => {
+ const useAgentStatus = useAgentStatusHook();
+
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
'sentinelOneManualHostActionsEnabled'
);
- const { data, isLoading, isFetched } = useGetSentinelOneAgentStatus([agentId], {
+ const { data, isLoading, isFetched } = useAgentStatus([agentId], 'sentinel_one', {
enabled: sentinelOneManualHostActionsEnabled,
});
const agentStatus = data?.[`${agentId}`];
diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.test.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.test.tsx
index 369e8d373560f..2c6d1f4122f7e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.test.tsx
@@ -8,93 +8,106 @@
import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { useHostIsolationAction } from './use_host_isolation_action';
-import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { useGetSentinelOneAgentStatus } from './use_sentinelone_host_isolation';
+import {
+ useAgentStatusHook,
+ useGetAgentStatus,
+ useGetSentinelOneAgentStatus,
+} from './use_sentinelone_host_isolation';
+import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
jest.mock('./use_sentinelone_host_isolation');
jest.mock('../../../common/hooks/use_experimental_features');
+
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
const useGetSentinelOneAgentStatusMock = useGetSentinelOneAgentStatus as jest.Mock;
+const useGetAgentStatusMock = useGetAgentStatus as jest.Mock;
+const useAgentStatusHookMock = useAgentStatusHook as jest.Mock;
describe('useHostIsolationAction', () => {
- const createReactQueryWrapper = () => {
- const queryClient = new QueryClient();
- const wrapper: React.FC = ({ children }) => (
- {children}
- );
- return wrapper;
- };
+ describe.each([
+ ['useGetSentinelOneAgentStatus', useGetSentinelOneAgentStatusMock],
+ ['useGetAgentStatus', useGetAgentStatusMock],
+ ])('works with %s hook', (name, hook) => {
+ const createReactQueryWrapper = () => {
+ const queryClient = new QueryClient();
+ const wrapper: React.FC = ({ children }) => (
+ {children}
+ );
+ return wrapper;
+ };
- const render = (isSentinelAlert: boolean = true) =>
- renderHook(
- () =>
- useHostIsolationAction({
- closePopover: jest.fn(),
- detailsData: isSentinelAlert
- ? [
- {
- category: 'event',
- field: 'event.module',
- values: ['sentinel_one'],
- originalValue: ['sentinel_one'],
- isObjectArray: false,
- },
- {
- category: 'observer',
- field: 'observer.serial_number',
- values: ['some-agent-id'],
- originalValue: ['some-agent-id'],
- isObjectArray: false,
- },
- ]
- : [
- {
- category: 'agent',
- field: 'agent.id',
- values: ['some-agent-id'],
- originalValue: ['some-agent-id'],
- isObjectArray: false,
- },
- ],
- isHostIsolationPanelOpen: false,
- onAddIsolationStatusClick: jest.fn(),
- }),
- {
- wrapper: createReactQueryWrapper(),
- }
- );
+ const render = (isSentinelAlert: boolean = true) =>
+ renderHook(
+ () =>
+ useHostIsolationAction({
+ closePopover: jest.fn(),
+ detailsData: isSentinelAlert
+ ? [
+ {
+ category: 'event',
+ field: 'event.module',
+ values: ['sentinel_one'],
+ originalValue: ['sentinel_one'],
+ isObjectArray: false,
+ },
+ {
+ category: 'observer',
+ field: 'observer.serial_number',
+ values: ['some-agent-id'],
+ originalValue: ['some-agent-id'],
+ isObjectArray: false,
+ },
+ ]
+ : [
+ {
+ category: 'agent',
+ field: 'agent.id',
+ values: ['some-agent-id'],
+ originalValue: ['some-agent-id'],
+ isObjectArray: false,
+ },
+ ],
+ isHostIsolationPanelOpen: false,
+ onAddIsolationStatusClick: jest.fn(),
+ }),
+ {
+ wrapper: createReactQueryWrapper(),
+ }
+ );
- beforeEach(() => {
- useIsExperimentalFeatureEnabledMock.mockReturnValue(true);
- });
+ beforeEach(() => {
+ useIsExperimentalFeatureEnabledMock.mockReturnValue(true);
+ useAgentStatusHookMock.mockImplementation(() => hook);
+ });
- afterEach(() => {
- jest.clearAllMocks();
- });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
- it('`useGetSentinelOneAgentStatusMock` is invoked as `enabled` when SentinelOne alert and FF enabled', () => {
- render();
+ it(`${name} is invoked as 'enabled' when SentinelOne alert and FF enabled`, () => {
+ render();
- expect(useGetSentinelOneAgentStatusMock).toHaveBeenCalledWith(['some-agent-id'], {
- enabled: true,
+ expect(hook).toHaveBeenCalledWith(['some-agent-id'], 'sentinel_one', {
+ enabled: true,
+ });
});
- });
- it('`useGetSentinelOneAgentStatusMock` is invoked as `disabled` when SentinelOne alert and FF disabled', () => {
- useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
- render();
+ it(`${name} is invoked as 'disabled' when SentinelOne alert and FF disabled`, () => {
+ useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
+ render();
- expect(useGetSentinelOneAgentStatusMock).toHaveBeenCalledWith(['some-agent-id'], {
- enabled: false,
+ expect(hook).toHaveBeenCalledWith(['some-agent-id'], 'sentinel_one', {
+ enabled: false,
+ });
});
- });
- it('`useGetSentinelOneAgentStatusMock` is invoked as `disabled` when non-SentinelOne alert', () => {
- render(false);
+ it(`${name} is invoked as 'disabled' when non-SentinelOne alert`, () => {
+ render(false);
- expect(useGetSentinelOneAgentStatusMock).toHaveBeenCalledWith([''], {
- enabled: false,
+ expect(hook).toHaveBeenCalledWith([''], 'sentinel_one', {
+ enabled: false,
+ });
});
});
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx
index 1732e429269a6..90dce4985cb84 100644
--- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx
@@ -5,14 +5,15 @@
* 2.0.
*/
import { useCallback, useMemo } from 'react';
+import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { useKibana } from '../../../common/lib/kibana/kibana_react';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import {
getSentinelOneAgentId,
isAlertFromSentinelOneEvent,
} from '../../../common/utils/sentinelone_alert_check';
-import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import { isIsolationSupported } from '../../../../common/endpoint/service/host_isolation/utils';
+import type { AgentStatusInfo } from '../../../../common/endpoint/types';
import { HostStatus } from '../../../../common/endpoint/types';
import { isAlertFromEndpointEvent } from '../../../common/utils/endpoint_alert_check';
import { useEndpointHostIsolationStatus } from '../../containers/detection_engine/alerts/use_host_isolation_status';
@@ -20,7 +21,7 @@ import { ISOLATE_HOST, UNISOLATE_HOST } from './translations';
import { getFieldValue } from './helpers';
import { useUserPrivileges } from '../../../common/components/user_privileges';
import type { AlertTableContextMenuItem } from '../alerts_table/types';
-import { useGetSentinelOneAgentStatus } from './use_sentinelone_host_isolation';
+import { useAgentStatusHook } from './use_sentinelone_host_isolation';
interface UseHostIsolationActionProps {
closePopover: () => void;
@@ -35,11 +36,15 @@ export const useHostIsolationAction = ({
isHostIsolationPanelOpen,
onAddIsolationStatusClick,
}: UseHostIsolationActionProps): AlertTableContextMenuItem[] => {
+ const useAgentStatus = useAgentStatusHook();
+
const hasActionsAllPrivileges = useKibana().services.application?.capabilities?.actions?.save;
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
'sentinelOneManualHostActionsEnabled'
);
+
+ const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
const { canIsolateHost, canUnIsolateHost } = useUserPrivileges().endpointPrivileges;
const isEndpointAlert = useMemo(
@@ -79,9 +84,13 @@ export const useHostIsolationAction = ({
agentType: sentinelOneAgentId ? 'sentinel_one' : 'endpoint',
});
- const { data: sentinelOneAgentData } = useGetSentinelOneAgentStatus([sentinelOneAgentId || ''], {
- enabled: !!sentinelOneAgentId && sentinelOneManualHostActionsEnabled,
- });
+ const { data: sentinelOneAgentData } = useAgentStatus(
+ [sentinelOneAgentId || ''],
+ 'sentinel_one',
+ {
+ enabled: !!sentinelOneAgentId && sentinelOneManualHostActionsEnabled,
+ }
+ );
const sentinelOneAgentStatus = sentinelOneAgentData?.[`${sentinelOneAgentId}`];
const isHostIsolated = useMemo(() => {
@@ -131,16 +140,26 @@ export const useHostIsolationAction = ({
const isIsolationActionDisabled = useMemo(() => {
if (sentinelOneManualHostActionsEnabled && isSentinelOneAlert) {
- return (
- !sentinelOneAgentStatus ||
- sentinelOneAgentStatus?.isUninstalled ||
- sentinelOneAgentStatus?.isPendingUninstall
- );
+ // 8.15 use FF for computing if action is enabled
+ if (agentStatusClientEnabled) {
+ return sentinelOneAgentStatus?.status === HostStatus.UNENROLLED;
+ }
+
+ // else use the old way
+ if (!sentinelOneAgentStatus) {
+ return true;
+ }
+
+ const { isUninstalled, isPendingUninstall } =
+ sentinelOneAgentStatus as AgentStatusInfo[string];
+
+ return isUninstalled || isPendingUninstall;
}
return agentStatus === HostStatus.UNENROLLED;
}, [
agentStatus,
+ agentStatusClientEnabled,
isSentinelOneAlert,
sentinelOneAgentStatus,
sentinelOneManualHostActionsEnabled,
diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx
index c41c7fc0e1724..d62d68a2534ff 100644
--- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_sentinelone_host_isolation.tsx
@@ -10,8 +10,9 @@ import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import type { IHttpFetchError } from '@kbn/core-http-browser';
import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common';
-import { ENDPOINT_AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants';
-import type { AgentStatusApiResponse } from '../../../../common/endpoint/types';
+import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
+import { AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants';
+import type { AgentStatusInfo, AgentStatusRecords } from '../../../../common/endpoint/types';
import { useHttp } from '../../../common/lib/kibana';
interface ErrorType {
@@ -20,27 +21,61 @@ interface ErrorType {
meta: ActionTypeExecutorResult;
}
+// TODO: 8.15: Remove `useGetSentinelOneAgentStatus` function when `agentStatusClientEnabled` is enabled/removed
export const useGetSentinelOneAgentStatus = (
agentIds: string[],
- options: UseQueryOptions> = {}
-): UseQueryResult> => {
+ agentType?: string,
+ options: UseQueryOptions> = {}
+): UseQueryResult> => {
const http = useHttp();
- return useQuery>({
+ return useQuery>({
queryKey: ['get-agent-status', agentIds],
+ refetchInterval: 5000,
...options,
- // TODO: update this to use a function instead of a number
- refetchInterval: 2000,
queryFn: () =>
http
- .get<{ data: AgentStatusApiResponse['data'] }>(ENDPOINT_AGENT_STATUS_ROUTE, {
+ .get<{ data: AgentStatusInfo }>(AGENT_STATUS_ROUTE, {
version: '1',
query: {
agentIds,
// 8.13 sentinel_one support via internal API
- agentType: 'sentinel_one',
+ agentType: agentType ? agentType : 'sentinel_one',
},
})
.then((response) => response.data),
});
};
+
+export const useGetAgentStatus = (
+ agentIds: string[],
+ agentType: string,
+ options: UseQueryOptions> = {}
+): UseQueryResult> => {
+ const http = useHttp();
+
+ return useQuery>({
+ queryKey: ['get-agent-status', agentIds],
+ // TODO: remove this refetchInterval and instead override it where called, via options.
+ refetchInterval: 5000,
+ ...options,
+ queryFn: () =>
+ http
+ .get<{ data: AgentStatusRecords }>(AGENT_STATUS_ROUTE, {
+ version: '1',
+ query: {
+ agentIds: agentIds.filter((agentId) => agentId.trim().length),
+ agentType,
+ },
+ })
+ .then((response) => response.data),
+ });
+};
+
+export const useAgentStatusHook = ():
+ | typeof useGetAgentStatus
+ | typeof useGetSentinelOneAgentStatus => {
+ const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
+ // 8.15 use agent status client hook if `agentStatusClientEnabled` FF enabled
+ return !agentStatusClientEnabled ? useGetSentinelOneAgentStatus : useGetAgentStatus;
+};
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx
index 562de8574146f..baa98fd757054 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx
@@ -14,12 +14,16 @@ import {
} from './test_ids';
import { HighlightedFieldsCell } from './highlighted_fields_cell';
import { RightPanelContext } from '../context';
-import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left';
+import { DocumentDetailsLeftPanelKey, LeftPanelInsightsTab } from '../../left';
import { TestProviders } from '../../../../common/mock';
import { ENTITIES_TAB_ID } from '../../left/components/entities_details';
import { useGetEndpointDetails } from '../../../../management/hooks';
-import { useGetSentinelOneAgentStatus } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
-import { useExpandableFlyoutApi, type ExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import {
+ useAgentStatusHook,
+ useGetAgentStatus,
+ useGetSentinelOneAgentStatus,
+} from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
+import { type ExpandableFlyoutApi, useExpandableFlyoutApi } from '@kbn/expandable-flyout';
jest.mock('../../../../management/hooks');
jest.mock('../../../../detections/components/host_isolation/use_sentinelone_host_isolation');
@@ -29,6 +33,14 @@ jest.mock('@kbn/expandable-flyout', () => ({
ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}>,
}));
+const useGetSentinelOneAgentStatusMock = useGetSentinelOneAgentStatus as jest.Mock;
+const useGetAgentStatusMock = useGetAgentStatus as jest.Mock;
+const useAgentStatusHookMock = useAgentStatusHook as jest.Mock;
+const hooksToMock: Record = {
+ useGetSentinelOneAgentStatus: useGetSentinelOneAgentStatusMock,
+ useGetAgentStatus: useGetAgentStatusMock,
+};
+
const flyoutContextValue = {
openLeftPanel: jest.fn(),
} as unknown as ExpandableFlyoutApi;
@@ -57,13 +69,13 @@ describe('', () => {
expect(getByTestId(HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID)).toBeInTheDocument();
});
- it('should render a link cell if field is host.name', () => {
+ it('should render a link cell if field is `host.name`', () => {
const { getByTestId } = renderHighlightedFieldsCell(['value'], 'host.name');
expect(getByTestId(HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID)).toBeInTheDocument();
});
- it('should render a link cell if field is user.name', () => {
+ it('should render a link cell if field is `user.name`', () => {
const { getByTestId } = renderHighlightedFieldsCell(['value'], 'user.name');
expect(getByTestId(HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID)).toBeInTheDocument();
@@ -84,7 +96,7 @@ describe('', () => {
});
});
- it('should render agent status cell if field is agent.status', () => {
+ it('should render agent status cell if field is `agent.status`', () => {
(useGetEndpointDetails as jest.Mock).mockReturnValue({});
const { getByTestId } = render(
@@ -95,23 +107,31 @@ describe('', () => {
expect(getByTestId(HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID)).toBeInTheDocument();
});
- it('should render sentinelone agent status cell if field is agent.status and origialField is observer.serial_number', () => {
- (useGetSentinelOneAgentStatus as jest.Mock).mockReturnValue({
- isFetched: true,
- isLoading: false,
- });
- const { getByTestId } = render(
-
-
-
- );
-
- expect(getByTestId(HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID)).toBeInTheDocument();
- });
+ // TODO: 8.15 simplify when `agentStatusClientEnabled` FF is enabled/removed
+ it.each(Object.keys(hooksToMock))(
+ 'should render SentinelOne agent status cell if field is agent.status and `origialField` is `observer.serial_number` with %s hook',
+ (hookName) => {
+ const hook = hooksToMock[hookName];
+ useAgentStatusHookMock.mockImplementation(() => hook);
+
+ (hook as jest.Mock).mockReturnValue({
+ isFetched: true,
+ isLoading: false,
+ });
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId(HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID)).toBeInTheDocument();
+ }
+ );
it('should not render if values is null', () => {
const { container } = render();
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx
index da556b549f153..6a369abb05f56 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx
@@ -7,7 +7,7 @@
import React, { memo } from 'react';
import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features';
-import { useGetSentinelOneAgentStatus } from '../../../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
+import { useAgentStatusHook } from '../../../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
import { SentinelOneAgentStatus } from '../../../../../../detections/components/host_isolation/sentinel_one_agent_status';
import type { ThirdPartyAgentInfo } from '../../../../../../../common/types';
import { HeaderAgentInfo } from '../header_agent_info';
@@ -24,7 +24,8 @@ export const HeaderSentinelOneInfo = memo(
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
'sentinelOneManualHostActionsEnabled'
);
- const { data } = useGetSentinelOneAgentStatus([agentId], { enabled: isSentinelOneV1Enabled });
+ const getAgentStatus = useAgentStatusHook();
+ const { data } = getAgentStatus([agentId], 'sentinel_one', { enabled: isSentinelOneV1Enabled });
const agentStatus = data?.[agentId];
const lastCheckin = agentStatus ? agentStatus.lastSeen : '';
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.test.tsx
index 3bce57829151b..12593f6320eec 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.test.tsx
@@ -11,7 +11,11 @@ import type { HostInfo } from '../../../../../common/endpoint/types';
import { HostStatus } from '../../../../../common/endpoint/types';
import type { AppContextTestRender } from '../../../../common/mock/endpoint';
import { createAppRootMockRenderer } from '../../../../common/mock/endpoint';
-import { useGetSentinelOneAgentStatus } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
+import {
+ useAgentStatusHook,
+ useGetAgentStatus,
+ useGetSentinelOneAgentStatus,
+} from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
import { useGetEndpointDetails } from '../../../hooks/endpoint/use_get_endpoint_details';
import { mockEndpointDetailsApiResult } from '../../../pages/endpoint_hosts/store/mock_endpoint_result_list';
import { OfflineCallout } from './offline_callout';
@@ -21,74 +25,83 @@ jest.mock('../../../../detections/components/host_isolation/use_sentinelone_host
const getEndpointDetails = useGetEndpointDetails as jest.Mock;
const getSentinelOneAgentStatus = useGetSentinelOneAgentStatus as jest.Mock;
+const getAgentStatus = useGetAgentStatus as jest.Mock;
+const useAgentStatusHookMock = useAgentStatusHook as jest.Mock;
describe('Responder offline callout', () => {
- let render: (agentType?: ResponseActionAgentType) => ReturnType;
- let renderResult: ReturnType;
- let mockedContext: AppContextTestRender;
- let endpointDetails: HostInfo;
+ // TODO: 8.15 remove the sentinelOneAgentStatus hook when `agentStatusClientEnabled` is enabled and removed
+ describe.each([
+ [useGetSentinelOneAgentStatus, getSentinelOneAgentStatus],
+ [useGetAgentStatus, getAgentStatus],
+ ])('works with %s hook', (hook, mockHook) => {
+ let render: (agentType?: ResponseActionAgentType) => ReturnType;
+ let renderResult: ReturnType;
+ let mockedContext: AppContextTestRender;
+ let endpointDetails: HostInfo;
- beforeEach(() => {
- mockedContext = createAppRootMockRenderer();
- render = (agentType?: ResponseActionAgentType) =>
- (renderResult = mockedContext.render(
-
- ));
- endpointDetails = mockEndpointDetailsApiResult();
- getEndpointDetails.mockReturnValue({ data: endpointDetails });
- getSentinelOneAgentStatus.mockReturnValue({ data: {} });
- render();
- });
+ beforeEach(() => {
+ mockedContext = createAppRootMockRenderer();
+ render = (agentType?: ResponseActionAgentType) =>
+ (renderResult = mockedContext.render(
+
+ ));
+ endpointDetails = mockEndpointDetailsApiResult();
+ getEndpointDetails.mockReturnValue({ data: endpointDetails });
+ mockHook.mockReturnValue({ data: {} });
+ useAgentStatusHookMock.mockImplementation(() => hook);
+ render();
+ });
- afterEach(() => {
- jest.clearAllMocks();
- });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
- it.each(['endpoint', 'sentinel_one'] as ResponseActionAgentType[])(
- 'should be visible when agent type is %s and host is offline',
- (agentType) => {
- if (agentType === 'endpoint') {
- getEndpointDetails.mockReturnValue({
- data: { ...endpointDetails, host_status: HostStatus.OFFLINE },
- });
- } else if (agentType === 'sentinel_one') {
- getSentinelOneAgentStatus.mockReturnValue({
- data: {
- '1234': {
- status: HostStatus.OFFLINE,
+ it.each(['endpoint', 'sentinel_one'] as ResponseActionAgentType[])(
+ 'should be visible when agent type is %s and host is offline',
+ (agentType) => {
+ if (agentType === 'endpoint') {
+ getEndpointDetails.mockReturnValue({
+ data: { ...endpointDetails, host_status: HostStatus.OFFLINE },
+ });
+ } else if (agentType === 'sentinel_one') {
+ mockHook.mockReturnValue({
+ data: {
+ '1234': {
+ status: HostStatus.OFFLINE,
+ },
},
- },
- });
+ });
+ }
+ render(agentType);
+ const callout = renderResult.queryByTestId('offlineCallout');
+ expect(callout).toBeTruthy();
}
- render(agentType);
- const callout = renderResult.queryByTestId('offlineCallout');
- expect(callout).toBeTruthy();
- }
- );
+ );
- it.each(['endpoint', 'sentinel_one'] as ResponseActionAgentType[])(
- 'should not be visible when agent type is %s and host is online',
- (agentType) => {
- if (agentType === 'endpoint') {
- getEndpointDetails.mockReturnValue({
- data: { ...endpointDetails, host_status: HostStatus.HEALTHY },
- });
- } else if (agentType === 'sentinel_one') {
- getSentinelOneAgentStatus.mockReturnValue({
- data: {
- '1234': {
- status: HostStatus.HEALTHY,
+ it.each(['endpoint', 'sentinel_one'] as ResponseActionAgentType[])(
+ 'should not be visible when agent type is %s and host is online',
+ (agentType) => {
+ if (agentType === 'endpoint') {
+ getEndpointDetails.mockReturnValue({
+ data: { ...endpointDetails, host_status: HostStatus.HEALTHY },
+ });
+ } else if (agentType === 'sentinel_one') {
+ mockHook.mockReturnValue({
+ data: {
+ '1234': {
+ status: HostStatus.HEALTHY,
+ },
},
- },
- });
+ });
+ }
+ render(agentType);
+ const callout = renderResult.queryByTestId('offlineCallout');
+ expect(callout).toBeFalsy();
}
- render(agentType);
- const callout = renderResult.queryByTestId('offlineCallout');
- expect(callout).toBeFalsy();
- }
- );
+ );
+ });
});
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx
index 3644415b0adae..cae6396885c12 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx
@@ -10,7 +10,7 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
-import { useGetSentinelOneAgentStatus } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
+import { useAgentStatusHook } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants';
import { useGetEndpointDetails } from '../../../hooks';
import { HostStatus } from '../../../../../common/endpoint/types';
@@ -24,6 +24,7 @@ interface OfflineCalloutProps {
export const OfflineCallout = memo(({ agentType, endpointId, hostName }) => {
const isEndpointAgent = agentType === 'endpoint';
const isSentinelOneAgent = agentType === 'sentinel_one';
+ const getAgentStatus = useAgentStatusHook();
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
'responseActionsSentinelOneV1Enabled'
);
@@ -37,7 +38,7 @@ export const OfflineCallout = memo(({ agentType, endpointId
enabled: isEndpointAgent,
});
- const { data } = useGetSentinelOneAgentStatus([endpointId], {
+ const { data } = getAgentStatus([endpointId], agentType, {
enabled: sentinelOneManualHostActionsEnabled && isSentinelOneAgent,
});
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts
index 7ab7cd3b3c8d4..de16e0003fe63 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts
@@ -9,9 +9,10 @@ import type { HttpApiTestSetupMock } from '../../mocks';
import { createHttpApiTestSetupMock } from '../../mocks';
import { sentinelOneMock } from '../../services/actions/clients/sentinelone/mocks';
import { registerAgentStatusRoute } from './agent_status_handler';
-import { ENDPOINT_AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants';
+import { AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants';
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
import type { EndpointAgentStatusRequestQueryParams } from '../../../../common/api/endpoint/agent/get_agent_status_route';
+import { RESPONSE_ACTION_AGENT_TYPE } from '../../../../common/endpoint/service/response_actions/constants';
describe('Agent Status API route handler', () => {
let apiTestSetup: HttpApiTestSetupMock;
@@ -42,6 +43,7 @@ describe('Agent Status API route handler', () => {
apiTestSetup.endpointAppContextMock.experimentalFeatures = {
...apiTestSetup.endpointAppContextMock.experimentalFeatures,
responseActionsSentinelOneV1Enabled: true,
+ agentStatusClientEnabled: false,
};
registerAgentStatusRoute(apiTestSetup.routerMock, apiTestSetup.endpointAppContextMock);
@@ -54,7 +56,7 @@ describe('Agent Status API route handler', () => {
};
await apiTestSetup
- .getRegisteredVersionedRoute('get', ENDPOINT_AGENT_STATUS_ROUTE, '1')
+ .getRegisteredVersionedRoute('get', AGENT_STATUS_ROUTE, '1')
.routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock);
expect(httpResponseMock.customError).toHaveBeenCalledWith({
@@ -63,22 +65,33 @@ describe('Agent Status API route handler', () => {
});
});
- it('should only (v8.13) accept agent type of sentinel_one', async () => {
+ it.each(RESPONSE_ACTION_AGENT_TYPE)('should accept agent type of %s', async (agentType) => {
+ // @ts-expect-error `query.*` is not mutable
+ httpRequestMock.query.agentType = agentType;
+ await apiTestSetup
+ .getRegisteredVersionedRoute('get', AGENT_STATUS_ROUTE, '1')
+ .routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock);
+
+ expect(httpResponseMock.ok).toHaveBeenCalled();
+ });
+
+ it('should accept agent type of `endpoint` when FF is disabled', async () => {
+ apiTestSetup.endpointAppContextMock.experimentalFeatures = {
+ ...apiTestSetup.endpointAppContextMock.experimentalFeatures,
+ responseActionsSentinelOneV1Enabled: false,
+ };
// @ts-expect-error `query.*` is not mutable
httpRequestMock.query.agentType = 'endpoint';
await apiTestSetup
- .getRegisteredVersionedRoute('get', ENDPOINT_AGENT_STATUS_ROUTE, '1')
+ .getRegisteredVersionedRoute('get', AGENT_STATUS_ROUTE, '1')
.routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock);
- expect(httpResponseMock.customError).toHaveBeenCalledWith({
- statusCode: 400,
- body: expect.any(CustomHttpRequestError),
- });
+ expect(httpResponseMock.ok).toHaveBeenCalled();
});
it('should return status code 200 with expected payload', async () => {
await apiTestSetup
- .getRegisteredVersionedRoute('get', ENDPOINT_AGENT_STATUS_ROUTE, '1')
+ .getRegisteredVersionedRoute('get', AGENT_STATUS_ROUTE, '1')
.routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock);
expect(httpResponseMock.ok).toHaveBeenCalledWith({
@@ -87,7 +100,7 @@ describe('Agent Status API route handler', () => {
one: {
agentType: 'sentinel_one',
found: false,
- id: 'one',
+ agentId: 'one',
isUninstalled: false,
isPendingUninstall: false,
isolated: false,
@@ -107,7 +120,7 @@ describe('Agent Status API route handler', () => {
two: {
agentType: 'sentinel_one',
found: false,
- id: 'two',
+ agentId: 'two',
isUninstalled: false,
isPendingUninstall: false,
isolated: false,
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts
index 2bf78bbb73b99..231eb3cccaf05 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts
@@ -10,7 +10,7 @@ import { getAgentStatus } from '../../services/agent/agent_status';
import { errorHandler } from '../error_handler';
import type { EndpointAgentStatusRequestQueryParams } from '../../../../common/api/endpoint/agent/get_agent_status_route';
import { EndpointAgentStatusRequestSchema } from '../../../../common/api/endpoint/agent/get_agent_status_route';
-import { ENDPOINT_AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants';
+import { AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants';
import type {
SecuritySolutionPluginRouter,
SecuritySolutionRequestHandlerContext,
@@ -18,6 +18,7 @@ import type {
import type { EndpointAppContext } from '../../types';
import { withEndpointAuthz } from '../with_endpoint_authz';
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
+import { getAgentStatusClient } from '../../services';
export const registerAgentStatusRoute = (
router: SecuritySolutionPluginRouter,
@@ -26,7 +27,7 @@ export const registerAgentStatusRoute = (
router.versioned
.get({
access: 'internal',
- path: ENDPOINT_AGENT_STATUS_ROUTE,
+ path: AGENT_STATUS_ROUTE,
options: { authRequired: true, tags: ['access:securitySolution'] },
})
.addVersion(
@@ -38,7 +39,7 @@ export const registerAgentStatusRoute = (
},
withEndpointAuthz(
{ all: ['canReadSecuritySolution'] },
- endpointContext.logFactory.get('actionStatusRoute'),
+ endpointContext.logFactory.get('agentStatusRoute'),
getAgentStatusRouteHandler(endpointContext)
)
);
@@ -52,13 +53,13 @@ export const getAgentStatusRouteHandler = (
unknown,
SecuritySolutionRequestHandlerContext
> => {
- const logger = endpointContext.logFactory.get('agentStatus');
+ const logger = endpointContext.logFactory.get('agentStatusRoute');
return async (context, request, response) => {
const { agentType = 'endpoint', agentIds: _agentIds } = request.query;
const agentIds = Array.isArray(_agentIds) ? _agentIds : [_agentIds];
- // Note: because our API schemas are defined as module static variables (as opposed to a
+ // Note: because our API schemas are defined as module static variables (as opposed to a
// `getter` function), we need to include this additional validation here, since
// `agent_type` is included in the schema independent of the feature flag
if (
@@ -72,18 +73,23 @@ export const getAgentStatusRouteHandler = (
);
}
- // TEMPORARY:
- // For v8.13 we only support SentinelOne on this API due to time constraints
- if (agentType !== 'sentinel_one') {
- return errorHandler(
- logger,
- response,
- new CustomHttpRequestError(
- `[${agentType}] agent type is not currently supported by this API`,
- 400
- )
- );
- }
+ const esClient = (await context.core).elasticsearch.client.asInternalUser;
+ const soClient = (await context.core).savedObjects.client;
+ const agentStatusClient = getAgentStatusClient(agentType, {
+ esClient,
+ soClient,
+ endpointService: endpointContext.service,
+ });
+
+ // 8.15: use the new `agentStatusClientEnabled` FF enabled
+ const getAgentStatusPromise = endpointContext.experimentalFeatures.agentStatusClientEnabled
+ ? agentStatusClient.getAgentStatuses(agentIds)
+ : getAgentStatus({
+ agentType,
+ agentIds,
+ logger,
+ connectorActionsClient: (await context.actions).getActionsClient(),
+ });
logger.debug(
`Retrieving status for: agentType [${agentType}], agentIds: [${agentIds.join(', ')}]`
@@ -92,12 +98,7 @@ export const getAgentStatusRouteHandler = (
try {
return response.ok({
body: {
- data: await getAgentStatus({
- agentType,
- agentIds,
- logger,
- connectorActionsClient: (await context.actions).getActionsClient(),
- }),
+ data: await getAgentStatusPromise,
},
});
} catch (e) {
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/errors.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/errors.ts
index d79986e899fe0..3c0190a459e35 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/errors.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/errors.ts
@@ -25,7 +25,7 @@ export class ResponseActionsClientError extends CustomHttpRequestError {
}
toString() {
- return JSON.stringify(stringify(this.toJSON()), null, 2);
+ return stringify(this.toJSON());
}
}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts
index 70f7d6d2d45cd..6149cdead82dd 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.test.ts
@@ -6,9 +6,9 @@
*/
import type { GetResponseActionsClientConstructorOptions } from '../..';
+import { getResponseActionsClient } from '../..';
import { responseActionsClientMock } from './mocks';
import { RESPONSE_ACTION_AGENT_TYPE } from '../../../../../common/endpoint/service/response_actions/constants';
-import { getResponseActionsClient } from '../..';
import { ResponseActionsClientImpl } from './lib/base_response_actions_client';
import { UnsupportedResponseActionsAgentTypeError } from './errors';
@@ -32,6 +32,7 @@ describe('getResponseActionsClient()', () => {
);
it(`should throw error if agentType is not supported`, () => {
+ // @ts-expect-error Argument of type "foo" is not assignable to ResponseActionAgentType
expect(() => getResponseActionsClient('foo', options)).toThrow(
UnsupportedResponseActionsAgentTypeError
);
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.ts
index 26ea73ac07dce..9e170572b26bf 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/get_response_actions_client.ts
@@ -24,7 +24,7 @@ export type GetResponseActionsClientConstructorOptions = ResponseActionsClientOp
* @throws UnsupportedResponseActionsAgentTypeError
*/
export const getResponseActionsClient = (
- agentType: string | ResponseActionAgentType,
+ agentType: ResponseActionAgentType,
constructorOptions: GetResponseActionsClientConstructorOptions
): ResponseActionsClient => {
switch (agentType) {
@@ -32,9 +32,9 @@ export const getResponseActionsClient = (
return new EndpointActionsClient(constructorOptions);
case 'sentinel_one':
return new SentinelOneActionsClient(constructorOptions);
+ default:
+ throw new UnsupportedResponseActionsAgentTypeError(
+ `Agent type [${agentType}] does not support response actions`
+ );
}
-
- throw new UnsupportedResponseActionsAgentTypeError(
- `Agent type [${agentType}] does not support response actions`
- );
};
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts
index 8037590d323c2..2f89b218f51e9 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.test.ts
@@ -6,9 +6,9 @@
*/
import type { GetAgentStatusOptions } from './agent_status';
+import { getAgentStatus, SENTINEL_ONE_NETWORK_STATUS } from './agent_status';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { sentinelOneMock } from '../actions/clients/sentinelone/mocks';
-import { getAgentStatus, SENTINEL_ONE_NETWORK_STATUS } from './agent_status';
import { responseActionsClientMock } from '../actions/clients/mocks';
describe('Endpoint Get Agent Status service', () => {
@@ -102,7 +102,7 @@ describe('Endpoint Get Agent Status service', () => {
aaa: {
agentType: 'sentinel_one',
found: true,
- id: 'aaa',
+ agentId: 'aaa',
isUninstalled: false,
isPendingUninstall: false,
isolated: true,
@@ -122,7 +122,7 @@ describe('Endpoint Get Agent Status service', () => {
bbb: {
agentType: 'sentinel_one',
found: true,
- id: 'bbb',
+ agentId: 'bbb',
isUninstalled: false,
isPendingUninstall: false,
isolated: false,
@@ -142,7 +142,7 @@ describe('Endpoint Get Agent Status service', () => {
ccc: {
agentType: 'sentinel_one',
found: true,
- id: 'ccc',
+ agentId: 'ccc',
isUninstalled: false,
isPendingUninstall: false,
isolated: false,
@@ -162,7 +162,7 @@ describe('Endpoint Get Agent Status service', () => {
invalid: {
agentType: 'sentinel_one',
found: false,
- id: 'invalid',
+ agentId: 'invalid',
isUninstalled: false,
isPendingUninstall: false,
isolated: false,
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts
index f91b4238979dc..d7268bb494943 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/agent_status.ts
@@ -17,13 +17,13 @@ import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common';
import type { SentinelOneGetAgentsResponse } from '@kbn/stack-connectors-plugin/common/sentinelone/types';
import { stringify } from '../../utils/stringify';
import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants';
-import type { AgentStatusApiResponse } from '../../../../common/endpoint/types';
+import type { AgentStatusInfo } from '../../../../common/endpoint/types';
import { HostStatus } from '../../../../common/endpoint/types';
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
export interface GetAgentStatusOptions {
// NOTE: only sentinel_one currently supported
- agentType: ResponseActionAgentType & 'sentinel_one';
+ agentType: ResponseActionAgentType;
agentIds: string[];
connectorActionsClient: ActionsClient;
logger: Logger;
@@ -34,7 +34,7 @@ export const getAgentStatus = async ({
agentIds,
connectorActionsClient,
logger,
-}: GetAgentStatusOptions): Promise => {
+}: GetAgentStatusOptions): Promise => {
let connectorList: ConnectorWithExtraFindData[] = [];
try {
@@ -84,11 +84,11 @@ export const getAgentStatus = async ({
logger.debug(`Response from SentinelOne API:\n${stringify(agentDetailsById)}`);
- return agentIds.reduce((acc, agentId) => {
+ return agentIds.reduce((acc, agentId) => {
const thisAgentDetails = agentDetailsById[agentId];
const thisAgentStatus = {
agentType,
- id: agentId,
+ agentId,
found: false,
isolated: false,
isPendingUninstall: false,
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts
new file mode 100644
index 0000000000000..c7b060833a64b
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts
@@ -0,0 +1,70 @@
+/*
+ * 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 { catchAndWrapError } from '../../../../utils';
+import { type AgentStatusRecords, HostStatus } from '../../../../../../common/endpoint/types';
+import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants';
+import { AgentStatusClient } from '../lib/base_agent_status_client';
+import { getPendingActionsSummary } from '../../../actions';
+import { AgentStatusClientError } from '../errors';
+
+export class EndpointAgentStatusClient extends AgentStatusClient {
+ protected readonly agentType: ResponseActionAgentType = 'endpoint';
+
+ async getAgentStatuses(agentIds: string[]): Promise {
+ const metadataService = this.options.endpointService.getEndpointMetadataService();
+ const esClient = this.options.esClient;
+ const soClient = this.options.soClient;
+
+ try {
+ const agentIdsKql = agentIds.map((agentId) => `agent.id: ${agentId}`).join(' or ');
+ const [{ data: hostInfoForAgents }, allPendingActions] = await Promise.all([
+ metadataService.getHostMetadataList(
+ esClient,
+ soClient,
+ this.options.endpointService.getInternalFleetServices(),
+ {
+ page: 0,
+ pageSize: 1000,
+ kuery: agentIdsKql,
+ }
+ ),
+ getPendingActionsSummary(esClient, metadataService, this.log, agentIds),
+ ]).catch(catchAndWrapError);
+
+ return agentIds.reduce((acc, agentId) => {
+ const agentMetadata = hostInfoForAgents.find(
+ (hostInfo) => hostInfo.metadata.agent.id === agentId
+ );
+
+ const pendingActions = allPendingActions.find(
+ (agentPendingActions) => agentPendingActions.agent_id === agentId
+ );
+
+ acc[agentId] = {
+ agentId,
+ agentType: this.agentType,
+ found: agentMetadata !== undefined,
+ isolated: Boolean(agentMetadata?.metadata.Endpoint.state?.isolation),
+ lastSeen: agentMetadata?.last_checkin || '',
+ pendingActions: pendingActions?.pending_actions ?? {},
+ status: agentMetadata?.host_status || HostStatus.OFFLINE,
+ };
+
+ return acc;
+ }, {});
+ } catch (err) {
+ const error = new AgentStatusClientError(
+ `Failed to fetch endpoint agent statuses for agentIds: [${agentIds}], failed with: ${err.message}`,
+ 500,
+ err
+ );
+ this.log.error(error);
+ throw error;
+ }
+ }
+}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/errors.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/errors.ts
new file mode 100644
index 0000000000000..5622cb7ff3578
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/errors.ts
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable max-classes-per-file */
+
+import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants';
+import { stringify } from '../../../utils/stringify';
+import { CustomHttpRequestError } from '../../../../utils/custom_http_request_error';
+
+/**
+ * Errors associated with Agent Status clients
+ */
+export class AgentStatusClientError extends CustomHttpRequestError {
+ toJSON() {
+ return {
+ message: this.message,
+ statusCode: this.statusCode,
+ meta: this.meta,
+ stack: this.stack,
+ };
+ }
+
+ toString() {
+ return stringify(this.toJSON());
+ }
+}
+
+export class UnsupportedAgentTypeError extends AgentStatusClientError {
+ constructor(message: string, statusCode = 501, meta?: unknown) {
+ super(message, statusCode, meta);
+ }
+}
+
+export class AgentStatusNotSupportedError extends AgentStatusClientError {
+ constructor(
+ agentIds: string[],
+ agentType: ResponseActionAgentType,
+ statusCode: number = 405,
+ meta?: unknown
+ ) {
+ super(
+ `Agent status is not available for ${`[agentIds: ${agentIds} and agentType: ${agentType}]`} not supported`,
+ statusCode,
+ meta
+ );
+ }
+}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts
new file mode 100644
index 0000000000000..0290542a5387f
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 { SentinelOneAgentStatusClient } from './sentinel_one/sentinel_one_agent_status_client';
+import type { AgentStatusClientInterface } from './lib/types';
+import type { AgentStatusClientOptions } from './lib/base_agent_status_client';
+import { EndpointAgentStatusClient } from './endpoint/endpoint_agent_status_client';
+import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants';
+import { UnsupportedAgentTypeError } from './errors';
+
+/**
+ * Retrieve a agent status client for an agent type
+ * @param agentType
+ * @param constructorOptions
+ *
+ */
+export const getAgentStatusClient = (
+ agentType: ResponseActionAgentType,
+ constructorOptions: AgentStatusClientOptions
+): AgentStatusClientInterface => {
+ switch (agentType) {
+ case 'endpoint':
+ return new EndpointAgentStatusClient(constructorOptions);
+ case 'sentinel_one':
+ return new SentinelOneAgentStatusClient(constructorOptions);
+ default:
+ throw new UnsupportedAgentTypeError(
+ `Agent type [${agentType}] does not support agent status`
+ );
+ }
+};
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/index.ts
new file mode 100644
index 0000000000000..b21f3b952a4e8
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './endpoint/endpoint_agent_status_client';
+export * from './sentinel_one/sentinel_one_agent_status_client';
+export * from './get_agent_status_client';
+
+export * from './lib/types';
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/base_agent_status_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/base_agent_status_client.ts
new file mode 100644
index 0000000000000..7722427fc18c8
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/base_agent_status_client.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
+import type { Logger } from '@kbn/logging';
+import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
+import type { AgentStatusRecords } from '../../../../../../common/endpoint/types/agents';
+import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants';
+import type { EndpointAppContextService } from '../../../../endpoint_app_context_services';
+import type { AgentStatusClientInterface } from './types';
+import { AgentStatusNotSupportedError } from '../errors';
+
+export interface AgentStatusClientOptions {
+ endpointService: EndpointAppContextService;
+ esClient: ElasticsearchClient;
+ soClient: SavedObjectsClientContract;
+}
+
+export abstract class AgentStatusClient implements AgentStatusClientInterface {
+ protected readonly log: Logger;
+ protected abstract readonly agentType: ResponseActionAgentType;
+
+ constructor(protected readonly options: AgentStatusClientOptions) {
+ this.log = options.endpointService.createLogger(this.constructor.name ?? 'AgentStatusClient');
+ }
+
+ public async getAgentStatuses(agentIds: string[]): Promise {
+ throw new AgentStatusNotSupportedError(agentIds, this.agentType);
+ }
+}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/types.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/types.ts
new file mode 100644
index 0000000000000..34afc1ec3f599
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/lib/types.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { AgentStatusRecords } from '../../../../../../common/endpoint/types';
+
+export interface AgentStatusClientInterface {
+ getAgentStatuses: (agentIds: string[]) => Promise;
+}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/sentinel_one_agent_status_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/sentinel_one_agent_status_client.ts
new file mode 100644
index 0000000000000..0fdf57d873443
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/sentinel_one_agent_status_client.ts
@@ -0,0 +1,133 @@
+/*
+ * 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 { catchAndWrapError } from '../../../../utils';
+import { getPendingActionsSummary } from '../../..';
+import type { RawSentinelOneInfo } from './types';
+import { type AgentStatusRecords, HostStatus } from '../../../../../../common/endpoint/types';
+import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants';
+import { AgentStatusClient } from '../lib/base_agent_status_client';
+import { AgentStatusClientError } from '../errors';
+
+const SENTINEL_ONE_AGENT_INDEX = `logs-sentinel_one.agent-default`;
+
+enum SENTINEL_ONE_NETWORK_STATUS {
+ CONNECTING = 'connecting',
+ CONNECTED = 'connected',
+ DISCONNECTING = 'disconnecting',
+ DISCONNECTED = 'disconnected',
+}
+
+export class SentinelOneAgentStatusClient extends AgentStatusClient {
+ protected readonly agentType: ResponseActionAgentType = 'sentinel_one';
+
+ async getAgentStatuses(agentIds: string[]): Promise {
+ const esClient = this.options.esClient;
+ const metadataService = this.options.endpointService.getEndpointMetadataService();
+ const sortField = 'sentinel_one.agent.last_active_date';
+
+ const query = {
+ bool: {
+ must: [
+ {
+ bool: {
+ filter: [
+ {
+ terms: {
+ 'sentinel_one.agent.uuid': agentIds,
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ };
+
+ try {
+ const [searchResponse, allPendingActions] = await Promise.all([
+ esClient.search(
+ {
+ index: SENTINEL_ONE_AGENT_INDEX,
+ from: 0,
+ size: 10000,
+ query,
+ collapse: {
+ field: 'sentinel_one.agent.uuid',
+ inner_hits: {
+ name: 'most_recent',
+ size: 1,
+ sort: [
+ {
+ [sortField]: {
+ order: 'desc',
+ },
+ },
+ ],
+ },
+ },
+ sort: [
+ {
+ [sortField]: {
+ order: 'desc',
+ },
+ },
+ ],
+ _source: false,
+ },
+ { ignore: [404] }
+ ),
+
+ getPendingActionsSummary(esClient, metadataService, this.log, agentIds),
+ ]).catch(catchAndWrapError);
+
+ const mostRecentAgentInfosByAgentId = searchResponse?.hits?.hits?.reduce<
+ Record
+ >((acc, hit) => {
+ if (hit.fields?.['sentinel_one.agent.uuid'][0]) {
+ acc[hit.fields?.['sentinel_one.agent.uuid'][0]] =
+ hit.inner_hits?.most_recent.hits.hits[0]._source;
+ }
+
+ return acc;
+ }, {});
+
+ return agentIds.reduce((acc, agentId) => {
+ const agentInfo = mostRecentAgentInfosByAgentId[agentId].sentinel_one.agent;
+
+ const pendingActions = allPendingActions.find(
+ (agentPendingActions) => agentPendingActions.agent_id === agentId
+ );
+
+ acc[agentId] = {
+ agentId,
+ agentType: this.agentType,
+ found: agentInfo?.uuid === agentId,
+ isolated: agentInfo?.network_status === SENTINEL_ONE_NETWORK_STATUS.DISCONNECTED,
+ lastSeen: agentInfo?.last_active_date || '',
+ status: agentInfo?.is_active
+ ? HostStatus.HEALTHY
+ : // If the agent is pending uninstall or uninstalled, we consider it unenrolled
+ agentInfo?.is_pending_uninstall || agentInfo?.is_uninstalled
+ ? HostStatus.UNENROLLED
+ : HostStatus.OFFLINE,
+ pendingActions: pendingActions?.pending_actions ?? {},
+ };
+
+ return acc;
+ }, {});
+ } catch (err) {
+ const error = new AgentStatusClientError(
+ `Failed to fetch sentinel one agent status for agentIds: [${agentIds}], failed with: ${err.message}`,
+ 500,
+ err
+ );
+ this.log.error(error);
+ throw error;
+ }
+ }
+}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/types.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/types.ts
new file mode 100644
index 0000000000000..002a561e7db47
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/sentinel_one/types.ts
@@ -0,0 +1,36 @@
+/*
+ * 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 SearchHitsMetadata,
+ type SearchResponseBody,
+} from '@elastic/elasticsearch/lib/api/types';
+
+export interface RawSentinelOneInfo {
+ sentinel_one: {
+ agent: {
+ uuid: string;
+ last_active_date: string;
+ network_status: string;
+ is_active: boolean;
+ is_pending_uninstall: boolean;
+ is_uninstalled: boolean;
+ };
+ };
+}
+
+export type SentinelOneSearchResponse = SearchResponseBody & {
+ hits: SearchResponseBody['hits'] & {
+ hits: Array & {
+ inner_hits: {
+ most_recent: {
+ hits: SearchHitsMetadata;
+ };
+ };
+ };
+ };
+};
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/index.ts
new file mode 100644
index 0000000000000..3b6ed23908dda
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/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 * from './clients';
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/index.ts
index fdb08a80589f3..242639818566e 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/index.ts
@@ -7,5 +7,6 @@
export * from './artifacts';
export * from './actions';
+export * from './agent';
export * from './artifacts_exception_list';
export type { FeatureKeys } from './feature_usage';
From 5be49cac3986b7f9dfcb2e51487cec194b743faf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Thu, 18 Apr 2024 16:12:16 +0100
Subject: [PATCH 11/96] [Stateful sidenav] Remove analytics IA group (#181132)
---
.github/CODEOWNERS | 1 -
package.json | 1 -
packages/solution-nav/analytics/README.md | 3 -
packages/solution-nav/analytics/definition.ts | 185 ------------------
packages/solution-nav/analytics/index.ts | 9 -
.../solution-nav/analytics/jest.config.js | 13 --
packages/solution-nav/analytics/kibana.jsonc | 10 -
packages/solution-nav/analytics/package.json | 6 -
packages/solution-nav/analytics/tsconfig.json | 22 ---
src/plugins/navigation/public/plugin.test.ts | 2 +-
src/plugins/navigation/public/plugin.tsx | 5 -
src/plugins/navigation/tsconfig.json | 1 -
tsconfig.base.json | 2 -
yarn.lock | 4 -
14 files changed, 1 insertion(+), 263 deletions(-)
delete mode 100644 packages/solution-nav/analytics/README.md
delete mode 100644 packages/solution-nav/analytics/definition.ts
delete mode 100644 packages/solution-nav/analytics/index.ts
delete mode 100644 packages/solution-nav/analytics/jest.config.js
delete mode 100644 packages/solution-nav/analytics/kibana.jsonc
delete mode 100644 packages/solution-nav/analytics/package.json
delete mode 100644 packages/solution-nav/analytics/tsconfig.json
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index e60f6f108c473..53f1de1f863d1 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -803,7 +803,6 @@ packages/kbn-shared-ux-utility @elastic/appex-sharedux
x-pack/plugins/observability_solution/slo @elastic/obs-ux-management-team
x-pack/packages/kbn-slo-schema @elastic/obs-ux-management-team
x-pack/plugins/snapshot_restore @elastic/kibana-management
-packages/solution-nav/analytics @elastic/appex-sharedux @elastic/kibana-data-discovery @elastic/kibana-presentation @elastic/kibana-visualizations
packages/solution-nav/es @elastic/appex-sharedux @elastic/enterprise-search-frontend
packages/solution-nav/oblt @elastic/appex-sharedux @elastic/obs-ux-management-team
packages/kbn-some-dev-log @elastic/kibana-operations
diff --git a/package.json b/package.json
index f4365eb0e5b56..19f84d4203033 100644
--- a/package.json
+++ b/package.json
@@ -806,7 +806,6 @@
"@kbn/slo-plugin": "link:x-pack/plugins/observability_solution/slo",
"@kbn/slo-schema": "link:x-pack/packages/kbn-slo-schema",
"@kbn/snapshot-restore-plugin": "link:x-pack/plugins/snapshot_restore",
- "@kbn/solution-nav-analytics": "link:packages/solution-nav/analytics",
"@kbn/solution-nav-es": "link:packages/solution-nav/es",
"@kbn/solution-nav-oblt": "link:packages/solution-nav/oblt",
"@kbn/sort-predicates": "link:packages/kbn-sort-predicates",
diff --git a/packages/solution-nav/analytics/README.md b/packages/solution-nav/analytics/README.md
deleted file mode 100644
index 91ea50c5e256a..0000000000000
--- a/packages/solution-nav/analytics/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# @kbn/solution-nav-analytics
-
-## This package contains the navigation definition for the Analytics in Kibana stateful.
diff --git a/packages/solution-nav/analytics/definition.ts b/packages/solution-nav/analytics/definition.ts
deleted file mode 100644
index 51b480c8c6c82..0000000000000
--- a/packages/solution-nav/analytics/definition.ts
+++ /dev/null
@@ -1,185 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { i18n } from '@kbn/i18n';
-import type {
- SolutionNavigationDefinition,
- NavigationTreeDefinition,
-} from '@kbn/core-chrome-browser';
-import { of } from 'rxjs';
-
-const title = i18n.translate(
- 'navigation.analyticsNav.headerSolutionSwitcher.analyticsSolutionTitle',
- {
- defaultMessage: 'Analytics',
- }
-);
-const icon = 'visualizeApp';
-
-const navTree: NavigationTreeDefinition = {
- body: [
- { type: 'recentlyAccessed' },
- {
- type: 'navGroup',
- id: 'analytics_project_nav',
- title,
- icon,
- defaultIsCollapsed: false,
- isCollapsible: false,
- breadcrumbStatus: 'hidden',
- children: [
- { link: 'discover' },
- {
- link: 'dashboards',
- getIsActive: ({ pathNameSerialized, prepend }) => {
- return pathNameSerialized.startsWith(prepend('/app/dashboards'));
- },
- },
- {
- title: i18n.translate('navigation.analyticsNav.visualizationLinkTitle', {
- defaultMessage: 'Visualizations',
- }),
- link: 'visualize',
- getIsActive: ({ pathNameSerialized, prepend }) => {
- return (
- pathNameSerialized.startsWith(prepend('/app/visualize')) ||
- pathNameSerialized.startsWith(prepend('/app/lens'))
- );
- },
- },
- {
- id: 'moreEditorsGroup',
- title: i18n.translate('navigation.analyticsNav.moreEditorsGroupTitle', {
- defaultMessage: 'More editors...',
- }),
- renderAs: 'accordion',
- spaceBefore: null,
- children: [
- {
- link: 'canvas',
- },
- {
- link: 'graph',
- },
- {
- link: 'maps',
- },
- ],
- },
- ],
- },
- ],
- footer: [
- {
- title: i18n.translate('navigation.analyticsNav.management.getStarted', {
- defaultMessage: 'Get started',
- }),
- icon: 'launch',
- type: 'navItem',
- link: 'home',
- },
- {
- type: 'navItem',
- id: 'devTools',
- title: i18n.translate('navigation.obltNav.devTools', {
- defaultMessage: 'Developer tools',
- }),
- link: 'dev_tools:console',
- icon: 'editorCodeBlock',
- getIsActive: ({ pathNameSerialized, prepend }) => {
- return pathNameSerialized.startsWith(prepend('/app/dev_tools'));
- },
- },
- {
- type: 'navGroup',
- id: 'project_settings_project_nav',
- title: i18n.translate('navigation.analyticsNav.management', {
- defaultMessage: 'Management',
- }),
- icon: 'gear',
- breadcrumbStatus: 'hidden',
- children: [
- {
- link: 'management',
- title: i18n.translate('navigation.analyticsNav.mngt', {
- defaultMessage: 'Stack Management',
- }),
- spaceBefore: null,
- renderAs: 'panelOpener',
- children: [
- {
- title: 'Ingest',
- children: [{ link: 'management:ingest_pipelines' }, { link: 'management:pipelines' }],
- },
- {
- title: 'Data',
- children: [
- { link: 'management:index_management' },
- { link: 'management:index_lifecycle_management' },
- { link: 'management:snapshot_restore' },
- { link: 'management:rollup_jobs' },
- { link: 'management:transform' },
- { link: 'management:cross_cluster_replication' },
- { link: 'management:remote_clusters' },
- { link: 'management:migrate_data' },
- ],
- },
- {
- title: 'Alerts and Insights',
- children: [
- { link: 'management:triggersActions' },
- { link: 'management:cases' },
- { link: 'management:triggersActionsConnectors' },
- { link: 'management:reporting' },
- { link: 'management:jobsListLink' },
- { link: 'management:watcher' },
- { link: 'management:maintenanceWindows' },
- ],
- },
- {
- title: 'Security',
- children: [
- { link: 'management:users' },
- { link: 'management:roles' },
- { link: 'management:api_keys' },
- { link: 'management:role_mappings' },
- ],
- },
- {
- title: 'Kibana',
- children: [
- { link: 'management:dataViews' },
- { link: 'management:filesManagement' },
- { link: 'management:objects' },
- { link: 'management:tags' },
- { link: 'management:search_sessions' },
- { link: 'management:spaces' },
- { link: 'management:settings' },
- ],
- },
- {
- title: 'Stack',
- children: [
- { link: 'management:license_management' },
- { link: 'management:upgrade_assistant' },
- ],
- },
- ],
- },
- ],
- },
- ],
-};
-
-export const definition: SolutionNavigationDefinition = {
- id: 'analytics',
- title,
- icon,
- homePage: 'home',
- navigationTree$: of(navTree),
-};
diff --git a/packages/solution-nav/analytics/index.ts b/packages/solution-nav/analytics/index.ts
deleted file mode 100644
index 8024948fb3cd1..0000000000000
--- a/packages/solution-nav/analytics/index.ts
+++ /dev/null
@@ -1,9 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-export { definition } from './definition';
diff --git a/packages/solution-nav/analytics/jest.config.js b/packages/solution-nav/analytics/jest.config.js
deleted file mode 100644
index 5e34a4721ae8e..0000000000000
--- a/packages/solution-nav/analytics/jest.config.js
+++ /dev/null
@@ -1,13 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-module.exports = {
- preset: '@kbn/test',
- rootDir: '../../..',
- roots: ['/packages/solution-nav/analytics'],
-};
diff --git a/packages/solution-nav/analytics/kibana.jsonc b/packages/solution-nav/analytics/kibana.jsonc
deleted file mode 100644
index 97f0ce68b571f..0000000000000
--- a/packages/solution-nav/analytics/kibana.jsonc
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "type": "shared-common",
- "id": "@kbn/solution-nav-analytics",
- "owner": [
- "@elastic/appex-sharedux",
- "@elastic/kibana-data-discovery",
- "@elastic/kibana-presentation",
- "@elastic/kibana-visualizations"
- ]
-}
diff --git a/packages/solution-nav/analytics/package.json b/packages/solution-nav/analytics/package.json
deleted file mode 100644
index 2e11112c8cc16..0000000000000
--- a/packages/solution-nav/analytics/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "@kbn/solution-nav-analytics",
- "private": true,
- "version": "1.0.0",
- "license": "SSPL-1.0 OR Elastic License 2.0"
-}
\ No newline at end of file
diff --git a/packages/solution-nav/analytics/tsconfig.json b/packages/solution-nav/analytics/tsconfig.json
deleted file mode 100644
index 8173aa127f832..0000000000000
--- a/packages/solution-nav/analytics/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "outDir": "target/types",
- "types": [
- "jest",
- "node",
- "react"
- ]
- },
- "include": [
- "**/*.ts",
- "**/*.tsx",
- ],
- "exclude": [
- "target/**/*"
- ],
- "kbn_references": [
- "@kbn/i18n",
- "@kbn/core-chrome-browser",
- ]
-}
diff --git a/src/plugins/navigation/public/plugin.test.ts b/src/plugins/navigation/public/plugin.test.ts
index 84dadf234c21b..1aad72a3344f3 100644
--- a/src/plugins/navigation/public/plugin.test.ts
+++ b/src/plugins/navigation/public/plugin.test.ts
@@ -139,7 +139,7 @@ describe('Navigation Plugin', () => {
expect(coreStart.chrome.project.updateSolutionNavigations).toHaveBeenCalled();
const [arg] = coreStart.chrome.project.updateSolutionNavigations.mock.calls[0];
- expect(Object.keys(arg)).toEqual(['es', 'oblt', 'analytics']);
+ expect(Object.keys(arg)).toEqual(['es', 'oblt']);
expect(coreStart.chrome.project.changeActiveSolutionNavigation).toHaveBeenCalledWith(null);
});
diff --git a/src/plugins/navigation/public/plugin.tsx b/src/plugins/navigation/public/plugin.tsx
index f70e20cce8bbe..79f7a032daf72 100644
--- a/src/plugins/navigation/public/plugin.tsx
+++ b/src/plugins/navigation/public/plugin.tsx
@@ -31,7 +31,6 @@ import type {
import { InternalChromeStart } from '@kbn/core-chrome-browser-internal';
import { definition as esDefinition } from '@kbn/solution-nav-es';
import { definition as obltDefinition } from '@kbn/solution-nav-oblt';
-import { definition as analyticsDefinition } from '@kbn/solution-nav-analytics';
import type { PanelContentProvider } from '@kbn/shared-ux-chrome-navigation';
import { UserProfileData } from '@kbn/user-profile-components';
import { ENABLE_SOLUTION_NAV_UI_SETTING_ID, SOLUTION_NAV_FEATURE_FLAG_NAME } from '../common';
@@ -283,10 +282,6 @@ export class NavigationPublicPlugin
...obltDefinition,
sideNavComponent: this.getSideNavComponent({ dataTestSubj: 'observabilitySideNav' }),
},
- analytics: {
- ...analyticsDefinition,
- sideNavComponent: this.getSideNavComponent({ dataTestSubj: 'analyticsSideNav' }),
- },
};
chrome.project.updateSolutionNavigations(solutionNavs, true);
}
diff --git a/src/plugins/navigation/tsconfig.json b/src/plugins/navigation/tsconfig.json
index 4a9c4832ea42d..10818f045be04 100644
--- a/src/plugins/navigation/tsconfig.json
+++ b/src/plugins/navigation/tsconfig.json
@@ -27,7 +27,6 @@
"@kbn/solution-nav-es",
"@kbn/solution-nav-oblt",
"@kbn/config",
- "@kbn/solution-nav-analytics",
"@kbn/security-plugin",
"@kbn/user-profile-components",
"@kbn/core-lifecycle-browser",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index e7ece2e877697..aab754a9364fd 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1600,8 +1600,6 @@
"@kbn/slo-schema/*": ["x-pack/packages/kbn-slo-schema/*"],
"@kbn/snapshot-restore-plugin": ["x-pack/plugins/snapshot_restore"],
"@kbn/snapshot-restore-plugin/*": ["x-pack/plugins/snapshot_restore/*"],
- "@kbn/solution-nav-analytics": ["packages/solution-nav/analytics"],
- "@kbn/solution-nav-analytics/*": ["packages/solution-nav/analytics/*"],
"@kbn/solution-nav-es": ["packages/solution-nav/es"],
"@kbn/solution-nav-es/*": ["packages/solution-nav/es/*"],
"@kbn/solution-nav-oblt": ["packages/solution-nav/oblt"],
diff --git a/yarn.lock b/yarn.lock
index 6d4131ea777b9..d0e2a6e1736a1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6252,10 +6252,6 @@
version "0.0.0"
uid ""
-"@kbn/solution-nav-analytics@link:packages/solution-nav/analytics":
- version "0.0.0"
- uid ""
-
"@kbn/solution-nav-es@link:packages/solution-nav/es":
version "0.0.0"
uid ""
From 48e61e8fa16da7aa2b81260536fa12fabbac0e56 Mon Sep 17 00:00:00 2001
From: Alexi Doak <109488926+doakalexi@users.noreply.github.com>
Date: Thu, 18 Apr 2024 08:44:16 -0700
Subject: [PATCH 12/96] Onboard Transaction Error Rate rule type with FAAD
(#179496)
Towards: https://github.com/elastic/kibana/issues/169867
This PR onboards the Transaction Error Rate rule type with FAAD.
### To verify
1. Run the following script to generate APM data:
```
node scripts/synthtrace many_errors.ts --local --live
```
2. Create a transaction error rate rule.
Example:
```
POST kbn:/api/alerting/rule
{
"params": {
"threshold": 0,
"windowSize": 5,
"windowUnit": "m",
"environment": "ENVIRONMENT_ALL"
},
"consumer": "alerts",
"schedule": {
"interval": "1m"
},
"tags": [],
"name": "test",
"rule_type_id": "apm.transaction_error_rate",
"notify_when": "onActionGroupChange",
"actions": []
}
```
3. Your rule should create an alert and should saved it in
`.internal.alerts-observability.apm.alerts-default-000001`
Example:
```
GET .internal.alerts-*/_search
```
4. Recover the alert by setting `threshold: 200`
5. The alert should be recovered and the AAD in the above index should
be updated `kibana.alert.status: recovered`.
---
.../routes/alerts/register_apm_rule_types.ts | 6 +-
.../anomaly/register_anomaly_rule_type.ts | 6 +-
.../register_error_count_rule_type.ts | 6 +-
...register_transaction_duration_rule_type.ts | 6 +-
...r_transaction_error_rate_rule_type.test.ts | 283 ++++++++----
...gister_transaction_error_rate_rule_type.ts | 407 ++++++++++--------
.../trial/__snapshots__/create_rule.snap | 182 --------
.../spaces_only/tests/trial/create_rule.ts | 318 --------------
.../spaces_only/tests/trial/index.ts | 1 -
9 files changed, 422 insertions(+), 793 deletions(-)
delete mode 100644 x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap
delete mode 100644 x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/register_apm_rule_types.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/register_apm_rule_types.ts
index 4aa4aef900edb..4c57ef4a10e1d 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/register_apm_rule_types.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/register_apm_rule_types.ts
@@ -15,7 +15,7 @@ import {
import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server';
import { IRuleDataClient } from '@kbn/rule-registry-plugin/server';
import { MlPluginSetup } from '@kbn/ml-plugin/server';
-import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils';
+import { legacyExperimentalFieldMap, ObservabilityApmAlert } from '@kbn/alerts-as-data-utils';
import type { APMIndices } from '@kbn/apm-data-access-plugin/server';
import {
AGENT_NAME,
@@ -92,11 +92,11 @@ export const apmRuleTypeAlertFieldMap = {
};
// Defines which alerts-as-data index alerts will use
-export const ApmRuleTypeAlertDefinition: IRuleTypeAlerts = {
+export const ApmRuleTypeAlertDefinition: IRuleTypeAlerts = {
context: APM_RULE_TYPE_ALERT_CONTEXT,
mappings: { fieldMap: apmRuleTypeAlertFieldMap },
useLegacyAlerts: true,
- shouldWrite: false,
+ shouldWrite: true,
};
export interface RegisterRuleDependencies {
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts
index e7ceb547cd39d..4678622a7d122 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts
@@ -14,7 +14,6 @@ import {
RuleTypeState,
RuleExecutorOptions,
AlertsClientError,
- IRuleTypeAlerts,
} from '@kbn/alerting-plugin/server';
import { KibanaRequest, DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import datemath from '@kbn/datemath';
@@ -359,10 +358,7 @@ export function registerAnomalyRuleType({
return { state: {} };
},
- alerts: {
- ...ApmRuleTypeAlertDefinition,
- shouldWrite: true,
- } as IRuleTypeAlerts,
+ alerts: ApmRuleTypeAlertDefinition,
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts
index 255c9b77aecad..d90aa0e143a14 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts
@@ -14,7 +14,6 @@ import {
RuleTypeState,
RuleExecutorOptions,
AlertsClientError,
- IRuleTypeAlerts,
} from '@kbn/alerting-plugin/server';
import {
formatDurationFromTimeUnitChar,
@@ -280,10 +279,7 @@ export function registerErrorCountRuleType({
return { state: {} };
},
- alerts: {
- ...ApmRuleTypeAlertDefinition,
- shouldWrite: true,
- } as IRuleTypeAlerts,
+ alerts: ApmRuleTypeAlertDefinition,
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts
index 4412e949cb325..299615e7663ef 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts
@@ -15,7 +15,6 @@ import {
AlertInstanceState as AlertState,
RuleTypeState,
RuleExecutorOptions,
- IRuleTypeAlerts,
} from '@kbn/alerting-plugin/server';
import {
asDuration,
@@ -328,10 +327,7 @@ export function registerTransactionDurationRuleType({
return { state: {} };
},
- alerts: {
- ...ApmRuleTypeAlertDefinition,
- shouldWrite: true,
- } as IRuleTypeAlerts,
+ alerts: ApmRuleTypeAlertDefinition,
getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
observabilityPaths.ruleDetails(rule.id),
});
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts
index 0f3e29201762c..382cc9f53767e 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.test.ts
@@ -42,11 +42,11 @@ describe('Transaction error rate alert', () => {
});
await executor({ params });
- expect(services.alertFactory.create).not.toBeCalled();
+ expect(services.alertsClient.report).not.toBeCalled();
});
it('sends alerts for services that exceeded the threshold', async () => {
- const { services, dependencies, executor, scheduleActions } = createRuleTypeMocks();
+ const { services, dependencies, executor } = createRuleTypeMocks();
registerTransactionErrorRateRuleType({
...dependencies,
@@ -106,6 +106,8 @@ describe('Transaction error rate alert', () => {
},
});
+ services.alertsClient.report.mockReturnValue({ uuid: 'test-uuid' });
+
const params = {
threshold: 10,
windowSize: 5,
@@ -114,28 +116,49 @@ describe('Transaction error rate alert', () => {
await executor({ params });
- expect(services.alertFactory.create).toHaveBeenCalledTimes(1);
+ expect(services.alertsClient.report).toHaveBeenCalledTimes(1);
- expect(services.alertFactory.create).toHaveBeenCalledWith('foo_env-foo_type-foo');
- expect(services.alertFactory.create).not.toHaveBeenCalledWith('bar_env-bar_type-bar');
+ expect(services.alertsClient.report).toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'foo_env-foo_type-foo',
+ });
+ expect(services.alertsClient.report).not.toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'bar_env-bar_type-bar',
+ });
- expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
- serviceName: 'foo',
- transactionType: 'type-foo',
- environment: 'env-foo',
- reason:
- 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.',
- threshold: 10,
- triggerValue: '10',
- interval: '5 mins',
- viewInAppUrl:
- 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo',
- alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ expect(services.alertsClient.setAlertData).toHaveBeenCalledWith({
+ context: {
+ alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ environment: 'env-foo',
+ interval: '5 mins',
+ reason:
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.',
+ serviceName: 'foo',
+ threshold: 10,
+ transactionName: undefined,
+ transactionType: 'type-foo',
+ triggerValue: '10',
+ viewInAppUrl:
+ 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo',
+ },
+ id: 'foo_env-foo_type-foo',
+ payload: {
+ 'kibana.alert.evaluation.threshold': 10,
+ 'kibana.alert.evaluation.value': 10,
+ 'kibana.alert.reason':
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.',
+ 'processor.event': 'transaction',
+ 'service.environment': 'env-foo',
+ 'service.name': 'foo',
+ 'transaction.name': undefined,
+ 'transaction.type': 'type-foo',
+ },
});
});
it('sends alert when rule is configured with group by on transaction.name', async () => {
- const { services, dependencies, executor, scheduleActions } = createRuleTypeMocks();
+ const { services, dependencies, executor } = createRuleTypeMocks();
registerTransactionErrorRateRuleType({
...dependencies,
@@ -195,6 +218,8 @@ describe('Transaction error rate alert', () => {
},
});
+ services.alertsClient.report.mockReturnValue({ uuid: 'test-uuid' });
+
const params = {
threshold: 10,
windowSize: 5,
@@ -204,31 +229,49 @@ describe('Transaction error rate alert', () => {
await executor({ params });
- expect(services.alertFactory.create).toHaveBeenCalledTimes(1);
+ expect(services.alertsClient.report).toHaveBeenCalledTimes(1);
- expect(services.alertFactory.create).toHaveBeenCalledWith('foo_env-foo_type-foo_tx-name-foo');
- expect(services.alertFactory.create).not.toHaveBeenCalledWith(
- 'bar_env-bar_type-bar_tx-name-bar'
- );
+ expect(services.alertsClient.report).toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'foo_env-foo_type-foo_tx-name-foo',
+ });
+ expect(services.alertsClient.report).not.toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'bar_env-bar_type-bar_tx-name-bar',
+ });
- expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
- serviceName: 'foo',
- transactionType: 'type-foo',
- environment: 'env-foo',
- reason:
- 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo, name: tx-name-foo. Alert when > 10%.',
- threshold: 10,
- triggerValue: '10',
- interval: '5 mins',
- viewInAppUrl:
- 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo',
- transactionName: 'tx-name-foo',
- alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ expect(services.alertsClient.setAlertData).toHaveBeenCalledWith({
+ context: {
+ alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ environment: 'env-foo',
+ interval: '5 mins',
+ reason:
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo, name: tx-name-foo. Alert when > 10%.',
+ serviceName: 'foo',
+ threshold: 10,
+ transactionName: 'tx-name-foo',
+ transactionType: 'type-foo',
+ triggerValue: '10',
+ viewInAppUrl:
+ 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo',
+ },
+ id: 'foo_env-foo_type-foo_tx-name-foo',
+ payload: {
+ 'kibana.alert.evaluation.threshold': 10,
+ 'kibana.alert.evaluation.value': 10,
+ 'kibana.alert.reason':
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo, name: tx-name-foo. Alert when > 10%.',
+ 'processor.event': 'transaction',
+ 'service.environment': 'env-foo',
+ 'service.name': 'foo',
+ 'transaction.name': 'tx-name-foo',
+ 'transaction.type': 'type-foo',
+ },
});
});
it('sends alert when rule is configured with preselected group by', async () => {
- const { services, dependencies, executor, scheduleActions } = createRuleTypeMocks();
+ const { services, dependencies, executor } = createRuleTypeMocks();
registerTransactionErrorRateRuleType({
...dependencies,
@@ -295,30 +338,53 @@ describe('Transaction error rate alert', () => {
groupBy: ['service.name', 'service.environment', 'transaction.type'],
};
+ services.alertsClient.report.mockReturnValue({ uuid: 'test-uuid' });
+
await executor({ params });
- expect(services.alertFactory.create).toHaveBeenCalledTimes(1);
+ expect(services.alertsClient.report).toHaveBeenCalledTimes(1);
- expect(services.alertFactory.create).toHaveBeenCalledWith('foo_env-foo_type-foo');
- expect(services.alertFactory.create).not.toHaveBeenCalledWith('bar_env-bar_type-bar');
+ expect(services.alertsClient.report).toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'foo_env-foo_type-foo',
+ });
+ expect(services.alertsClient.report).not.toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'bar_env-bar_type-bar',
+ });
- expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
- serviceName: 'foo',
- transactionType: 'type-foo',
- environment: 'env-foo',
- reason:
- 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.',
- threshold: 10,
- triggerValue: '10',
- interval: '5 mins',
- viewInAppUrl:
- 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo',
- alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ expect(services.alertsClient.setAlertData).toHaveBeenCalledWith({
+ context: {
+ alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ environment: 'env-foo',
+ interval: '5 mins',
+ reason:
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.',
+ serviceName: 'foo',
+ threshold: 10,
+ transactionName: undefined,
+ transactionType: 'type-foo',
+ triggerValue: '10',
+ viewInAppUrl:
+ 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=env-foo',
+ },
+ id: 'foo_env-foo_type-foo',
+ payload: {
+ 'kibana.alert.evaluation.threshold': 10,
+ 'kibana.alert.evaluation.value': 10,
+ 'kibana.alert.reason':
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: env-foo, type: type-foo. Alert when > 10%.',
+ 'processor.event': 'transaction',
+ 'service.environment': 'env-foo',
+ 'service.name': 'foo',
+ 'transaction.name': undefined,
+ 'transaction.type': 'type-foo',
+ },
});
});
it('sends alert when service.environment field does not exist in the source', async () => {
- const { services, dependencies, executor, scheduleActions } = createRuleTypeMocks();
+ const { services, dependencies, executor } = createRuleTypeMocks();
registerTransactionErrorRateRuleType({
...dependencies,
@@ -378,6 +444,8 @@ describe('Transaction error rate alert', () => {
},
});
+ services.alertsClient.report.mockReturnValue({ uuid: 'test-uuid' });
+
const params = {
threshold: 10,
windowSize: 5,
@@ -387,32 +455,49 @@ describe('Transaction error rate alert', () => {
await executor({ params });
- expect(services.alertFactory.create).toHaveBeenCalledTimes(1);
-
- expect(services.alertFactory.create).toHaveBeenCalledWith(
- 'foo_ENVIRONMENT_NOT_DEFINED_type-foo'
- );
- expect(services.alertFactory.create).not.toHaveBeenCalledWith(
- 'bar_ENVIRONMENT_NOT_DEFINED_type-bar'
- );
-
- expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
- serviceName: 'foo',
- transactionType: 'type-foo',
- environment: 'Not defined',
- reason:
- 'Failed transactions is 10% in the last 5 mins for service: foo, env: Not defined, type: type-foo. Alert when > 10%.',
- threshold: 10,
- triggerValue: '10',
- interval: '5 mins',
- viewInAppUrl:
- 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=ENVIRONMENT_ALL',
- alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ expect(services.alertsClient.report).toHaveBeenCalledTimes(1);
+
+ expect(services.alertsClient.report).toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'foo_ENVIRONMENT_NOT_DEFINED_type-foo',
+ });
+ expect(services.alertsClient.report).not.toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'bar_ENVIRONMENT_NOT_DEFINED_type-bar',
+ });
+
+ expect(services.alertsClient.setAlertData).toHaveBeenCalledWith({
+ context: {
+ alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ environment: 'Not defined',
+ interval: '5 mins',
+ reason:
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: Not defined, type: type-foo. Alert when > 10%.',
+ serviceName: 'foo',
+ threshold: 10,
+ transactionName: undefined,
+ transactionType: 'type-foo',
+ triggerValue: '10',
+ viewInAppUrl:
+ 'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=ENVIRONMENT_ALL',
+ },
+ id: 'foo_ENVIRONMENT_NOT_DEFINED_type-foo',
+ payload: {
+ 'kibana.alert.evaluation.threshold': 10,
+ 'kibana.alert.evaluation.value': 10,
+ 'kibana.alert.reason':
+ 'Failed transactions is 10% in the last 5 mins for service: foo, env: Not defined, type: type-foo. Alert when > 10%.',
+ 'processor.event': 'transaction',
+ 'service.environment': 'ENVIRONMENT_NOT_DEFINED',
+ 'service.name': 'foo',
+ 'transaction.name': undefined,
+ 'transaction.type': 'type-foo',
+ },
});
});
it('sends alert when rule is configured with a filter query', async () => {
- const { services, dependencies, executor, scheduleActions } = createRuleTypeMocks();
+ const { services, dependencies, executor } = createRuleTypeMocks();
registerTransactionErrorRateRuleType({
...dependencies,
@@ -457,6 +542,8 @@ describe('Transaction error rate alert', () => {
},
});
+ services.alertsClient.report.mockReturnValue({ uuid: 'test-uuid' });
+
const params = {
threshold: 10,
windowSize: 5,
@@ -473,22 +560,40 @@ describe('Transaction error rate alert', () => {
await executor({ params });
- expect(services.alertFactory.create).toHaveBeenCalledTimes(1);
+ expect(services.alertsClient.report).toHaveBeenCalledTimes(1);
- expect(services.alertFactory.create).toHaveBeenCalledWith('bar_env-bar_type-bar');
+ expect(services.alertsClient.report).toHaveBeenCalledWith({
+ actionGroup: 'threshold_met',
+ id: 'bar_env-bar_type-bar',
+ });
- expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
- serviceName: 'bar',
- transactionType: 'type-bar',
- environment: 'env-bar',
- reason:
- 'Failed transactions is 10% in the last 5 mins for service: bar, env: env-bar, type: type-bar. Alert when > 10%.',
- threshold: 10,
- triggerValue: '10',
- interval: '5 mins',
- viewInAppUrl:
- 'http://localhost:5601/eyr/app/apm/services/bar?transactionType=type-bar&environment=env-bar',
- alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ expect(services.alertsClient.setAlertData).toHaveBeenCalledWith({
+ context: {
+ alertDetailsUrl: 'mockedAlertsLocator > getLocation',
+ environment: 'env-bar',
+ interval: '5 mins',
+ reason:
+ 'Failed transactions is 10% in the last 5 mins for service: bar, env: env-bar, type: type-bar. Alert when > 10%.',
+ serviceName: 'bar',
+ threshold: 10,
+ transactionName: undefined,
+ transactionType: 'type-bar',
+ triggerValue: '10',
+ viewInAppUrl:
+ 'http://localhost:5601/eyr/app/apm/services/bar?transactionType=type-bar&environment=env-bar',
+ },
+ id: 'bar_env-bar_type-bar',
+ payload: {
+ 'kibana.alert.evaluation.threshold': 10,
+ 'kibana.alert.evaluation.value': 10,
+ 'kibana.alert.reason':
+ 'Failed transactions is 10% in the last 5 mins for service: bar, env: env-bar, type: type-bar. Alert when > 10%.',
+ 'processor.event': 'transaction',
+ 'service.environment': 'env-bar',
+ 'service.name': 'bar',
+ 'transaction.name': undefined,
+ 'transaction.type': 'type-bar',
+ },
});
});
});
diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts
index 94c5738532127..81b4612244b1b 100644
--- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts
+++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts
@@ -6,7 +6,15 @@
*/
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
-import { GetViewInAppRelativeUrlFnOpts } from '@kbn/alerting-plugin/server';
+import {
+ GetViewInAppRelativeUrlFnOpts,
+ ActionGroupIdsOf,
+ AlertInstanceContext as AlertContext,
+ AlertInstanceState as AlertState,
+ RuleTypeState,
+ RuleExecutorOptions,
+ AlertsClientError,
+} from '@kbn/alerting-plugin/server';
import {
formatDurationFromTimeUnitChar,
getAlertUrl,
@@ -22,7 +30,7 @@ import {
ALERT_REASON,
ApmRuleType,
} from '@kbn/rule-data-utils';
-import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server';
+import { ObservabilityApmAlert } from '@kbn/alerts-as-data-utils';
import { addSpaceIdToPath } from '@kbn/spaces-plugin/common';
import { asyncForEach } from '@kbn/std';
import { SearchAggregatedTransactionSetting } from '../../../../../common/aggregated_transactions';
@@ -40,8 +48,12 @@ import {
APM_SERVER_FEATURE_ID,
formatTransactionErrorRateReason,
RULE_TYPES_CONFIG,
+ THRESHOLD_MET_GROUP,
} from '../../../../../common/rules/apm_rule_types';
-import { transactionErrorRateParamsSchema } from '../../../../../common/rules/schema';
+import {
+ transactionErrorRateParamsSchema,
+ ApmRuleParamsType,
+} from '../../../../../common/rules/schema';
import { environmentQuery } from '../../../../../common/utils/environment_query';
import { asDecimalOrInteger, getAlertUrlTransaction } from '../../../../../common/utils/formatters';
import { getBackwardCompatibleDocumentTypeFilter } from '../../../../lib/helpers/transactions';
@@ -74,6 +86,13 @@ export const transactionErrorRateActionVariables = [
apmActionVariables.viewInAppUrl,
];
+type TransactionErrorRateRuleTypeParams = ApmRuleParamsType[ApmRuleType.TransactionErrorRate];
+type TransactionErrorRateActionGroups = ActionGroupIdsOf;
+type TransactionErrorRateRuleTypeState = RuleTypeState;
+type TransactionErrorRateAlertState = AlertState;
+type TransactionErrorRateAlertContext = AlertContext;
+type TransactionErrorRateAlert = ObservabilityApmAlert;
+
export function registerTransactionErrorRateRuleType({
alerting,
alertsLocator,
@@ -83,218 +102,236 @@ export function registerTransactionErrorRateRuleType({
logger,
ruleDataClient,
}: RegisterRuleDependencies) {
- const createLifecycleRuleType = createLifecycleRuleTypeFactory({
- ruleDataClient,
- logger,
- });
+ if (!alerting) {
+ throw new Error(
+ 'Cannot register the transaction error rate rule type. The alerting plugin need to be enabled.'
+ );
+ }
- alerting.registerType(
- createLifecycleRuleType({
- id: ApmRuleType.TransactionErrorRate,
- name: ruleTypeConfig.name,
- actionGroups: ruleTypeConfig.actionGroups,
- defaultActionGroupId: ruleTypeConfig.defaultActionGroupId,
- validate: { params: transactionErrorRateParamsSchema },
- schemas: {
- params: {
- type: 'config-schema',
- schema: transactionErrorRateParamsSchema,
- },
+ alerting.registerType({
+ id: ApmRuleType.TransactionErrorRate,
+ name: ruleTypeConfig.name,
+ actionGroups: ruleTypeConfig.actionGroups,
+ defaultActionGroupId: ruleTypeConfig.defaultActionGroupId,
+ validate: { params: transactionErrorRateParamsSchema },
+ schemas: {
+ params: {
+ type: 'config-schema',
+ schema: transactionErrorRateParamsSchema,
},
- actionVariables: {
- context: transactionErrorRateActionVariables,
- },
- category: DEFAULT_APP_CATEGORIES.observability.id,
- producer: APM_SERVER_FEATURE_ID,
- minimumLicenseRequired: 'basic',
- isExportable: true,
- executor: async ({ services, spaceId, params: ruleParams, startedAt, getTimeRange }) => {
- const allGroupByFields = getAllGroupByFields(
- ApmRuleType.TransactionErrorRate,
- ruleParams.groupBy
- );
+ },
+ actionVariables: {
+ context: transactionErrorRateActionVariables,
+ },
+ category: DEFAULT_APP_CATEGORIES.observability.id,
+ producer: APM_SERVER_FEATURE_ID,
+ minimumLicenseRequired: 'basic',
+ isExportable: true,
+ executor: async (
+ options: RuleExecutorOptions<
+ TransactionErrorRateRuleTypeParams,
+ TransactionErrorRateRuleTypeState,
+ TransactionErrorRateAlertState,
+ TransactionErrorRateAlertContext,
+ TransactionErrorRateActionGroups,
+ TransactionErrorRateAlert
+ >
+ ) => {
+ const { services, spaceId, params: ruleParams, startedAt, getTimeRange } = options;
+ const { alertsClient, savedObjectsClient, scopedClusterClient } = services;
+ if (!alertsClient) {
+ throw new AlertsClientError();
+ }
- const { getAlertUuid, getAlertStartedDate, savedObjectsClient, scopedClusterClient } =
- services;
+ const allGroupByFields = getAllGroupByFields(
+ ApmRuleType.TransactionErrorRate,
+ ruleParams.groupBy
+ );
- const indices = await getApmIndices(savedObjectsClient);
+ const indices = await getApmIndices(savedObjectsClient);
- // only query transaction events when set to 'never',
- // to prevent (likely) unnecessary blocking request
- // in rule execution
- const searchAggregatedTransactions =
- apmConfig.searchAggregatedTransactions !== SearchAggregatedTransactionSetting.never;
+ // only query transaction events when set to 'never',
+ // to prevent (likely) unnecessary blocking request
+ // in rule execution
+ const searchAggregatedTransactions =
+ apmConfig.searchAggregatedTransactions !== SearchAggregatedTransactionSetting.never;
- const index = searchAggregatedTransactions ? indices.metric : indices.transaction;
+ const index = searchAggregatedTransactions ? indices.metric : indices.transaction;
- const termFilterQuery = !ruleParams.searchConfiguration?.query?.query
- ? [
- ...termQuery(SERVICE_NAME, ruleParams.serviceName, {
- queryEmptyString: false,
- }),
- ...termQuery(TRANSACTION_TYPE, ruleParams.transactionType, {
- queryEmptyString: false,
- }),
- ...termQuery(TRANSACTION_NAME, ruleParams.transactionName, {
- queryEmptyString: false,
- }),
- ...environmentQuery(ruleParams.environment),
- ]
- : [];
+ const termFilterQuery = !ruleParams.searchConfiguration?.query?.query
+ ? [
+ ...termQuery(SERVICE_NAME, ruleParams.serviceName, {
+ queryEmptyString: false,
+ }),
+ ...termQuery(TRANSACTION_TYPE, ruleParams.transactionType, {
+ queryEmptyString: false,
+ }),
+ ...termQuery(TRANSACTION_NAME, ruleParams.transactionName, {
+ queryEmptyString: false,
+ }),
+ ...environmentQuery(ruleParams.environment),
+ ]
+ : [];
- const { dateStart } = getTimeRange(`${ruleParams.windowSize}${ruleParams.windowUnit}`);
+ const { dateStart } = getTimeRange(`${ruleParams.windowSize}${ruleParams.windowUnit}`);
- const searchParams = {
- index,
- body: {
- track_total_hits: false,
- size: 0,
- query: {
- bool: {
- filter: [
- {
- range: {
- '@timestamp': {
- gte: dateStart,
- },
+ const searchParams = {
+ index,
+ body: {
+ track_total_hits: false,
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ {
+ range: {
+ '@timestamp': {
+ gte: dateStart,
},
},
- ...getBackwardCompatibleDocumentTypeFilter(searchAggregatedTransactions),
- {
- terms: {
- [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success],
- },
+ },
+ ...getBackwardCompatibleDocumentTypeFilter(searchAggregatedTransactions),
+ {
+ terms: {
+ [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success],
},
- ...termFilterQuery,
- ...getParsedFilterQuery(ruleParams.searchConfiguration?.query?.query as string),
- ],
- },
- },
- aggs: {
- series: {
- multi_terms: {
- terms: [...getGroupByTerms(allGroupByFields)],
- size: 1000,
- order: { _count: 'desc' as const },
},
- aggs: {
- outcomes: {
- terms: {
- field: EVENT_OUTCOME,
- },
- aggs: getApmAlertSourceFieldsAgg(),
+ ...termFilterQuery,
+ ...getParsedFilterQuery(ruleParams.searchConfiguration?.query?.query as string),
+ ],
+ },
+ },
+ aggs: {
+ series: {
+ multi_terms: {
+ terms: [...getGroupByTerms(allGroupByFields)],
+ size: 1000,
+ order: { _count: 'desc' as const },
+ },
+ aggs: {
+ outcomes: {
+ terms: {
+ field: EVENT_OUTCOME,
},
+ aggs: getApmAlertSourceFieldsAgg(),
},
},
},
},
- };
-
- const response = await alertingEsClient({
- scopedClusterClient,
- params: searchParams,
- });
+ },
+ };
- if (!response.aggregations) {
- return { state: {} };
- }
+ const response = await alertingEsClient({
+ scopedClusterClient,
+ params: searchParams,
+ });
- const results = [];
+ if (!response.aggregations) {
+ return { state: {} };
+ }
- for (const bucket of response.aggregations.series.buckets) {
- const groupByFields = bucket.key.reduce((obj, bucketKey, bucketIndex) => {
- obj[allGroupByFields[bucketIndex]] = bucketKey;
- return obj;
- }, {} as Record);
+ const results = [];
- const bucketKey = bucket.key;
+ for (const bucket of response.aggregations.series.buckets) {
+ const groupByFields = bucket.key.reduce((obj, bucketKey, bucketIndex) => {
+ obj[allGroupByFields[bucketIndex]] = bucketKey;
+ return obj;
+ }, {} as Record);
- const failedOutcomeBucket = bucket.outcomes.buckets.find(
- (outcomeBucket) => outcomeBucket.key === EventOutcome.failure
- );
- const failed = failedOutcomeBucket?.doc_count ?? 0;
- const succesful =
- bucket.outcomes.buckets.find(
- (outcomeBucket) => outcomeBucket.key === EventOutcome.success
- )?.doc_count ?? 0;
- const errorRate = (failed / (failed + succesful)) * 100;
+ const bucketKey = bucket.key;
- if (errorRate >= ruleParams.threshold) {
- results.push({
- errorRate,
- sourceFields: getApmAlertSourceFields(failedOutcomeBucket),
- groupByFields,
- bucketKey,
- });
- }
- }
+ const failedOutcomeBucket = bucket.outcomes.buckets.find(
+ (outcomeBucket) => outcomeBucket.key === EventOutcome.failure
+ );
+ const failed = failedOutcomeBucket?.doc_count ?? 0;
+ const succesful =
+ bucket.outcomes.buckets.find(
+ (outcomeBucket) => outcomeBucket.key === EventOutcome.success
+ )?.doc_count ?? 0;
+ const errorRate = (failed / (failed + succesful)) * 100;
- await asyncForEach(results, async (result) => {
- const { errorRate, sourceFields, groupByFields, bucketKey } = result;
- const alertId = bucketKey.join('_');
- const reasonMessage = formatTransactionErrorRateReason({
- threshold: ruleParams.threshold,
- measured: errorRate,
- asPercent,
- windowSize: ruleParams.windowSize,
- windowUnit: ruleParams.windowUnit,
+ if (errorRate >= ruleParams.threshold) {
+ results.push({
+ errorRate,
+ sourceFields: getApmAlertSourceFields(failedOutcomeBucket),
groupByFields,
+ bucketKey,
});
+ }
+ }
- const alert = services.alertWithLifecycle({
- id: alertId,
- fields: {
- [TRANSACTION_NAME]: ruleParams.transactionName,
- [PROCESSOR_EVENT]: ProcessorEvent.transaction,
- [ALERT_EVALUATION_VALUE]: errorRate,
- [ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold,
- [ALERT_REASON]: reasonMessage,
- ...sourceFields,
- ...groupByFields,
- },
- });
+ await asyncForEach(results, async (result) => {
+ const { errorRate, sourceFields, groupByFields, bucketKey } = result;
+ const alertId = bucketKey.join('_');
+ const reasonMessage = formatTransactionErrorRateReason({
+ threshold: ruleParams.threshold,
+ measured: errorRate,
+ asPercent,
+ windowSize: ruleParams.windowSize,
+ windowUnit: ruleParams.windowUnit,
+ groupByFields,
+ });
- const relativeViewInAppUrl = getAlertUrlTransaction(
- groupByFields[SERVICE_NAME],
- getEnvironmentEsField(groupByFields[SERVICE_ENVIRONMENT])?.[SERVICE_ENVIRONMENT],
- groupByFields[TRANSACTION_TYPE]
- );
- const viewInAppUrl = addSpaceIdToPath(
- basePath.publicBaseUrl,
- spaceId,
- relativeViewInAppUrl
- );
- const indexedStartedAt = getAlertStartedDate(alertId) ?? startedAt.toISOString();
- const alertUuid = getAlertUuid(alertId);
- const alertDetailsUrl = await getAlertUrl(
- alertUuid,
- spaceId,
- indexedStartedAt,
- alertsLocator,
- basePath.publicBaseUrl
- );
- const groupByActionVariables = getGroupByActionVariables(groupByFields);
+ const { uuid, start } = alertsClient.report({
+ id: alertId,
+ actionGroup: ruleTypeConfig.defaultActionGroupId,
+ });
+ const indexedStartedAt = start ?? startedAt.toISOString();
- alert.scheduleActions(ruleTypeConfig.defaultActionGroupId, {
- alertDetailsUrl,
- interval: formatDurationFromTimeUnitChar(
- ruleParams.windowSize,
- ruleParams.windowUnit as TimeUnitChar
- ),
- reason: reasonMessage,
- threshold: ruleParams.threshold,
- transactionName: ruleParams.transactionName,
- triggerValue: asDecimalOrInteger(errorRate),
- viewInAppUrl,
- ...groupByActionVariables,
- });
+ const relativeViewInAppUrl = getAlertUrlTransaction(
+ groupByFields[SERVICE_NAME],
+ getEnvironmentEsField(groupByFields[SERVICE_ENVIRONMENT])?.[SERVICE_ENVIRONMENT],
+ groupByFields[TRANSACTION_TYPE]
+ );
+ const viewInAppUrl = addSpaceIdToPath(
+ basePath.publicBaseUrl,
+ spaceId,
+ relativeViewInAppUrl
+ );
+ const alertDetailsUrl = await getAlertUrl(
+ uuid,
+ spaceId,
+ indexedStartedAt,
+ alertsLocator,
+ basePath.publicBaseUrl
+ );
+ const groupByActionVariables = getGroupByActionVariables(groupByFields);
+
+ const payload = {
+ [TRANSACTION_NAME]: ruleParams.transactionName,
+ [PROCESSOR_EVENT]: ProcessorEvent.transaction,
+ [ALERT_EVALUATION_VALUE]: errorRate,
+ [ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold,
+ [ALERT_REASON]: reasonMessage,
+ ...sourceFields,
+ ...groupByFields,
+ };
+
+ const context = {
+ alertDetailsUrl,
+ interval: formatDurationFromTimeUnitChar(
+ ruleParams.windowSize,
+ ruleParams.windowUnit as TimeUnitChar
+ ),
+ reason: reasonMessage,
+ threshold: ruleParams.threshold,
+ transactionName: ruleParams.transactionName,
+ triggerValue: asDecimalOrInteger(errorRate),
+ viewInAppUrl,
+ ...groupByActionVariables,
+ };
+
+ alertsClient.setAlertData({
+ id: alertId,
+ payload,
+ context,
});
+ });
- return { state: {} };
- },
- alerts: ApmRuleTypeAlertDefinition,
- getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
- observabilityPaths.ruleDetails(rule.id),
- })
- );
+ return { state: {} };
+ },
+ alerts: ApmRuleTypeAlertDefinition,
+ getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) =>
+ observabilityPaths.ruleDetails(rule.id),
+ });
}
diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap b/x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap
deleted file mode 100644
index 54ad6fe9f9847..0000000000000
--- a/x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap
+++ /dev/null
@@ -1,182 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`rule registry spaces only: trial Rule Registry API with write permissions when creating a rule writes alerts data to the alert indices 1`] = `
-Object {
- "event.action": Array [
- "open",
- ],
- "event.kind": Array [
- "signal",
- ],
- "kibana.alert.consecutive_matches": Array [
- 1,
- ],
- "kibana.alert.duration.us": Array [
- 0,
- ],
- "kibana.alert.evaluation.threshold": Array [
- 30,
- ],
- "kibana.alert.evaluation.value": Array [
- 50,
- ],
- "kibana.alert.flapping": Array [
- false,
- ],
- "kibana.alert.instance.id": Array [
- "opbeans-go_ENVIRONMENT_NOT_DEFINED_request",
- ],
- "kibana.alert.reason": Array [
- "Failed transactions is 50% in the last 5 mins for service: opbeans-go, env: Not defined, type: request. Alert when > 30%.",
- ],
- "kibana.alert.reason.text": Array [
- "Failed transactions is 50% in the last 5 mins for service: opbeans-go, env: Not defined, type: request. Alert when > 30%.",
- ],
- "kibana.alert.rule.category": Array [
- "Failed transaction rate threshold",
- ],
- "kibana.alert.rule.consumer": Array [
- "apm",
- ],
- "kibana.alert.rule.name": Array [
- "Failed transaction rate threshold | opbeans-go",
- ],
- "kibana.alert.rule.parameters": Array [
- Object {
- "environment": "ENVIRONMENT_ALL",
- "serviceName": "opbeans-go",
- "threshold": 30,
- "transactionType": "request",
- "windowSize": 5,
- "windowUnit": "m",
- },
- ],
- "kibana.alert.rule.producer": Array [
- "apm",
- ],
- "kibana.alert.rule.revision": Array [
- 0,
- ],
- "kibana.alert.rule.rule_type_id": Array [
- "apm.transaction_error_rate",
- ],
- "kibana.alert.rule.tags": Array [
- "apm",
- "service.name:opbeans-go",
- ],
- "kibana.alert.status": Array [
- "active",
- ],
- "kibana.alert.workflow_status": Array [
- "open",
- ],
- "kibana.space_ids": Array [
- "space1",
- ],
- "processor.event": Array [
- "transaction",
- ],
- "service.environment": Array [
- "ENVIRONMENT_NOT_DEFINED",
- ],
- "service.name": Array [
- "opbeans-go",
- ],
- "tags": Array [
- "apm",
- "service.name:opbeans-go",
- ],
- "transaction.type": Array [
- "request",
- ],
-}
-`;
-
-exports[`rule registry spaces only: trial Rule Registry API with write permissions when creating a rule writes alerts data to the alert indices 2`] = `
-Object {
- "event.action": Array [
- "close",
- ],
- "event.kind": Array [
- "signal",
- ],
- "kibana.alert.consecutive_matches": Array [
- 0,
- ],
- "kibana.alert.evaluation.threshold": Array [
- 30,
- ],
- "kibana.alert.evaluation.value": Array [
- 50,
- ],
- "kibana.alert.flapping": Array [
- false,
- ],
- "kibana.alert.instance.id": Array [
- "opbeans-go_ENVIRONMENT_NOT_DEFINED_request",
- ],
- "kibana.alert.reason": Array [
- "Failed transactions is 50% in the last 5 mins for service: opbeans-go, env: Not defined, type: request. Alert when > 30%.",
- ],
- "kibana.alert.reason.text": Array [
- "Failed transactions is 50% in the last 5 mins for service: opbeans-go, env: Not defined, type: request. Alert when > 30%.",
- ],
- "kibana.alert.rule.category": Array [
- "Failed transaction rate threshold",
- ],
- "kibana.alert.rule.consumer": Array [
- "apm",
- ],
- "kibana.alert.rule.name": Array [
- "Failed transaction rate threshold | opbeans-go",
- ],
- "kibana.alert.rule.parameters": Array [
- Object {
- "environment": "ENVIRONMENT_ALL",
- "serviceName": "opbeans-go",
- "threshold": 30,
- "transactionType": "request",
- "windowSize": 5,
- "windowUnit": "m",
- },
- ],
- "kibana.alert.rule.producer": Array [
- "apm",
- ],
- "kibana.alert.rule.revision": Array [
- 0,
- ],
- "kibana.alert.rule.rule_type_id": Array [
- "apm.transaction_error_rate",
- ],
- "kibana.alert.rule.tags": Array [
- "apm",
- "service.name:opbeans-go",
- ],
- "kibana.alert.status": Array [
- "recovered",
- ],
- "kibana.alert.workflow_status": Array [
- "open",
- ],
- "kibana.space_ids": Array [
- "space1",
- ],
- "processor.event": Array [
- "transaction",
- ],
- "service.environment": Array [
- "ENVIRONMENT_NOT_DEFINED",
- ],
- "service.name": Array [
- "opbeans-go",
- ],
- "tags": Array [
- "apm",
- "service.name:opbeans-go",
- ],
- "transaction.type": Array [
- "request",
- ],
-}
-`;
diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts
deleted file mode 100644
index 453ca383008d4..0000000000000
--- a/x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts
+++ /dev/null
@@ -1,318 +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 expect from '@kbn/expect';
-import {
- ALERT_DURATION,
- ALERT_END,
- ALERT_INSTANCE_ID,
- ALERT_RULE_EXECUTION_UUID,
- ALERT_RULE_UUID,
- ALERT_START,
- ALERT_STATUS,
- ALERT_TIME_RANGE,
- ALERT_UUID,
- EVENT_KIND,
- VERSION,
-} from '@kbn/rule-data-utils';
-import { omit } from 'lodash';
-import { Rule } from '@kbn/alerting-plugin/common';
-import { SerializedConcreteTaskInstance } from '@kbn/task-manager-plugin/server/task';
-import type { RuleTaskState } from '@kbn/alerting-state-types';
-import type { FtrProviderContext } from '../../../common/ftr_provider_context';
-import {
- getAlertsTargetIndices,
- createApmMetricIndex,
- createAlert,
- waitUntilNextExecution,
- createTransactionMetric,
- cleanupTargetIndices,
- deleteAlert,
-} from '../../../common/lib/helpers';
-import { AlertDef, AlertParams } from '../../../common/types';
-import { APM_METRIC_INDEX_NAME } from '../../../common/constants';
-import { obsOnly } from '../../../common/lib/authentication/users';
-import { getEventLog } from '../../../../alerting_api_integration/common/lib/get_event_log';
-
-const SPACE_ID = 'space1';
-
-// eslint-disable-next-line import/no-default-export
-export default function registryRulesApiTest({ getService }: FtrProviderContext) {
- const es = getService('es');
-
- describe('Rule Registry API', async () => {
- describe('with write permissions', () => {
- describe('when creating a rule', () => {
- let createResponse: {
- alert: Rule;
- status: number;
- };
- before(async () => {
- await createApmMetricIndex(getService);
- const alertDef: AlertDef = {
- params: {
- threshold: 30,
- windowSize: 5,
- windowUnit: 'm',
- transactionType: 'request',
- environment: 'ENVIRONMENT_ALL',
- serviceName: 'opbeans-go',
- },
- consumer: 'apm',
- alertTypeId: 'apm.transaction_error_rate',
- schedule: { interval: '5s' },
- actions: [],
- tags: ['apm', 'service.name:opbeans-go'],
- notifyWhen: 'onActionGroupChange',
- name: 'Failed transaction rate threshold | opbeans-go',
- };
- createResponse = await createAlert(getService, obsOnly, SPACE_ID, alertDef);
- });
- after(async () => {
- await deleteAlert(getService, obsOnly, SPACE_ID, createResponse.alert.id);
- await cleanupTargetIndices(getService, obsOnly, SPACE_ID);
- });
-
- it('writes alerts data to the alert indices', async () => {
- expect(createResponse.status).to.be.below(299);
-
- expect(createResponse.alert).not.to.be(undefined);
- let alert = await waitUntilNextExecution(
- getService,
- obsOnly,
- createResponse.alert,
- SPACE_ID
- );
-
- const { body: targetIndices } = await getAlertsTargetIndices(
- getService,
- obsOnly,
- SPACE_ID
- );
-
- try {
- const res = await es.search({
- index: targetIndices[0],
- body: {
- query: {
- term: {
- [EVENT_KIND]: 'signal',
- },
- },
- size: 1,
- sort: {
- '@timestamp': 'desc',
- },
- },
- });
- expect(res.hits.hits).to.be.empty();
- } catch (exc) {
- expect(exc.message).contain('index_not_found_exception');
- }
-
- await es.index({
- index: APM_METRIC_INDEX_NAME,
- body: createTransactionMetric({
- event: {
- outcome: 'success',
- },
- }),
- refresh: true,
- });
-
- alert = await waitUntilNextExecution(getService, obsOnly, alert, SPACE_ID);
-
- try {
- const res = await es.search({
- index: targetIndices[0],
- body: {
- query: {
- term: {
- [EVENT_KIND]: 'signal',
- },
- },
- size: 1,
- sort: {
- '@timestamp': 'desc',
- },
- },
- });
- expect(res.hits.hits).to.be.empty();
- } catch (exc) {
- expect(exc.message).contain('index_not_found_exception');
- }
-
- await es.index({
- index: APM_METRIC_INDEX_NAME,
- body: createTransactionMetric({
- event: {
- outcome: 'failure',
- },
- }),
- refresh: true,
- });
-
- alert = await waitUntilNextExecution(getService, obsOnly, alert, SPACE_ID);
-
- const afterViolatingDataResponse = await es.search({
- index: targetIndices[0],
- body: {
- query: {
- term: {
- [EVENT_KIND]: 'signal',
- },
- },
- size: 1,
- sort: {
- '@timestamp': 'desc',
- },
- _source: false,
- fields: [{ field: '*', include_unmapped: true }],
- },
- });
-
- expect(afterViolatingDataResponse.hits.hits.length).to.be(1);
-
- const alertEvent = afterViolatingDataResponse.hits.hits[0].fields as Record;
-
- const exclude = [
- '@timestamp',
- ALERT_START,
- ALERT_UUID,
- ALERT_RULE_EXECUTION_UUID,
- ALERT_RULE_UUID,
- ALERT_TIME_RANGE,
- VERSION,
- ];
-
- const alertInstanceId = alertEvent[ALERT_INSTANCE_ID]?.[0];
- const alertUuid = alertEvent[ALERT_UUID]?.[0];
- const executionUuid = alertEvent[ALERT_RULE_EXECUTION_UUID]?.[0];
- expect(typeof alertUuid).to.be('string');
- expect(typeof executionUuid).to.be('string');
-
- await checkEventLogAlertUuids(
- getService,
- SPACE_ID,
- createResponse.alert.id,
- alertInstanceId,
- alertUuid,
- executionUuid
- );
-
- const toCompare = omit(alertEvent, exclude);
-
- expectSnapshot(toCompare).toMatch();
-
- await es.bulk({
- index: APM_METRIC_INDEX_NAME,
- body: [
- { index: {} },
- createTransactionMetric({
- event: {
- outcome: 'success',
- },
- }),
- { index: {} },
- createTransactionMetric({
- event: {
- outcome: 'success',
- },
- }),
- ],
- refresh: true,
- });
-
- alert = await waitUntilNextExecution(getService, obsOnly, alert, SPACE_ID);
-
- const afterRecoveryResponse = await es.search({
- index: targetIndices[0],
- body: {
- query: {
- term: {
- [EVENT_KIND]: 'signal',
- },
- },
- size: 1,
- sort: {
- '@timestamp': 'desc',
- },
- _source: false,
- fields: [{ field: '*', include_unmapped: true }],
- },
- });
-
- expect(afterRecoveryResponse.hits.hits.length).to.be(1);
-
- const recoveredAlertEvent = afterRecoveryResponse.hits.hits[0].fields as Record<
- string,
- any
- >;
-
- expect(recoveredAlertEvent[ALERT_STATUS]?.[0]).to.eql('recovered');
- expect(recoveredAlertEvent[ALERT_DURATION]?.[0]).to.be.greaterThan(0);
- expect(new Date(recoveredAlertEvent[ALERT_END]?.[0]).getTime()).to.be.greaterThan(0);
-
- expectSnapshot(
- omit(recoveredAlertEvent, exclude.concat([ALERT_DURATION, ALERT_END]))
- ).toMatch();
- });
- });
- });
- });
-}
-
-async function checkEventLogAlertUuids(
- getService: FtrProviderContext['getService'],
- spaceId: string,
- ruleId: string,
- alertInstanceId: string,
- alertUuid: string,
- executionUuid: string
-) {
- const es = getService('es');
- const retry = getService('retry');
-
- const docs: Awaited> = [];
- await retry.waitFor('getting event log docs', async () => {
- docs.push(...(await getEventLogDocs()));
- return docs.length > 0;
- });
-
- expect(docs.length).to.be.greaterThan(0);
- for (const doc of docs) {
- expect(doc?.kibana?.alert?.uuid).to.be(alertUuid);
- }
-
- // check that the task doc has the same UUID
- const taskDoc = await es.get<{ task: SerializedConcreteTaskInstance }>({
- index: '.kibana_task_manager',
- id: `task:${ruleId}`,
- });
-
- const ruleStateString = taskDoc._source?.task.state || 'task-state-is-missing';
- const ruleState: RuleTaskState = JSON.parse(ruleStateString);
- if (ruleState.alertInstances?.[alertInstanceId]) {
- expect(ruleState.alertInstances[alertInstanceId].meta?.uuid).to.be(alertUuid);
- } else if (ruleState.alertRecoveredInstances?.[alertInstanceId]) {
- expect(ruleState.alertRecoveredInstances[alertInstanceId].meta?.uuid).to.be(alertUuid);
- } else {
- expect(false).to.be('alert instance not found in task doc');
- }
-
- function getEventLogDocs() {
- return getEventLog({
- getService,
- spaceId,
- type: 'alert',
- id: ruleId,
- provider: 'alerting',
- actions: new Map([['active-instance', { equal: 1 }]]),
- filter: `kibana.alert.rule.execution.uuid: ${executionUuid}`,
- });
- }
-}
diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/index.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/index.ts
index d519dd16dab45..23638d10f664f 100644
--- a/x-pack/test/rule_registry/spaces_only/tests/trial/index.ts
+++ b/x-pack/test/rule_registry/spaces_only/tests/trial/index.ts
@@ -22,7 +22,6 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => {
// Trial
loadTestFile(require.resolve('./get_alert_by_id'));
loadTestFile(require.resolve('./update_alert'));
- loadTestFile(require.resolve('./create_rule'));
loadTestFile(require.resolve('./lifecycle_executor'));
});
};
From d84c5877fcd76257848d3f8b734a0cc18eee0927 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Thu, 18 Apr 2024 16:54:55 +0100
Subject: [PATCH 13/96] skip flaky suite (#169159)
---
.../spaces_only/tests/actions/migrations.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts
index 541f83fc8d412..92e43bbd83dc9 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts
@@ -15,7 +15,8 @@ export default function createGetTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
- describe('migrations', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/169159
+ describe.skip('migrations', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/actions');
});
From abc91f2fcdb017a7f7788d4161583d34cf0555ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?=
Date: Thu, 18 Apr 2024 17:59:40 +0200
Subject: [PATCH 14/96] [Search] Remove beta callouts from sync rules (#181157)
## Summary
Promote Sync Rules to GA, by removing the beta callout and labels.
Also fixes a few links that didn't set up as external.
### Checklist
Delete any items that are not applicable to this PR.
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
---
.../connector/sync_rules/connector_rules.tsx | 9 ++-------
.../connector/sync_rules/edit_sync_rules_flyout.tsx | 12 ------------
.../sync_rules/editable_basic_rules_table.tsx | 2 +-
x-pack/plugins/translations/translations/fr-FR.json | 1 -
x-pack/plugins/translations/translations/ja-JP.json | 1 -
x-pack/plugins/translations/translations/zh-CN.json | 1 -
6 files changed, 3 insertions(+), 23 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/connector_rules.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/connector_rules.tsx
index 73894c1391941..1a8b01fef8cd9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/connector_rules.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/connector_rules.tsx
@@ -24,8 +24,6 @@ import {
import { i18n } from '@kbn/i18n';
-import { BetaBadge } from '../../../../../shared/beta/beta_badge';
-
import { docLinks } from '../../../../../shared/doc_links';
import { ConnectorViewLogic } from '../../../connector_detail/connector_view_logic';
@@ -80,9 +78,6 @@ export const ConnectorSyncRules: React.FC = () => {
-
-
-
@@ -97,7 +92,7 @@ export const ConnectorSyncRules: React.FC = () => {
})}
-
+
{i18n.translate(
'xpack.enterpriseSearch.index.connector.syncRules.syncRulesLabel',
{
@@ -191,7 +186,7 @@ export const ConnectorSyncRules: React.FC = () => {
)}
-
+
{i18n.translate(
'xpack.enterpriseSearch.content.index.connector.syncRules.advancedFiltersLinkTitle',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/edit_sync_rules_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/edit_sync_rules_flyout.tsx
index 779c3ec9e8bee..d1e10d21a5421 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/edit_sync_rules_flyout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/edit_sync_rules_flyout.tsx
@@ -25,8 +25,6 @@ import { i18n } from '@kbn/i18n';
import { FilteringValidation } from '@kbn/search-connectors';
-import { BetaCallOut } from '../../../../../shared/beta/beta_callout';
-
import { AdvancedSyncRules } from './advanced_sync_rules';
import { EditSyncRulesTab } from './edit_sync_rules_tab';
import { SyncRulesTable } from './editable_basic_rules_table';
@@ -106,16 +104,6 @@ export const EditSyncRulesFlyout: React.FC = ({
-
-
{i18n.translate(
'xpack.enterpriseSearch.content.index.connector.syncRules.flyout.description',
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/editable_basic_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/editable_basic_rules_table.tsx
index 7aebd9de94df4..5cd4dc00ee916 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/editable_basic_rules_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/sync_rules/editable_basic_rules_table.tsx
@@ -80,7 +80,7 @@ export const SyncRulesTable: React.FC = () => {
values: { indexName },
})}
-
+
{i18n.translate('xpack.enterpriseSearch.content.index.connector.syncRules.link', {
defaultMessage: 'Learn more about customizing your sync rules.',
})}
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 95c9f5501e39a..dde0b0f63fd56 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -15267,7 +15267,6 @@
"xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesDescription": "Ces règles s'appliquent aux documents pendant la phrase de filtrage de l'intégration.",
"xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesTitle": "Règles de base",
"xpack.enterpriseSearch.content.index.connector.syncRules.basicTabTitle": "Règles de base",
- "xpack.enterpriseSearch.content.index.connector.syncRules.flyout.betaDescription": "Les règles de synchronisation sont une fonctionnalité en version bêta. Les fonctionnalités bêta sont susceptibles d'être modifiées et ne sont pas couvertes par l'accord de niveau de service (SLA) des fonctionnalités de la version générale (GA). Elastic prévoit de faire passer cette fonctionnalité en disponibilité générale dans une prochaine version.",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.description": "Planifiez et modifiez les règles ici avant de les appliquer à la prochaine synchronisation.",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.revertButtonTitle": "Revenir aux règles actives",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.title": "Ébauches de règles",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c12da993ba5ed..4705c43ac0166 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -15245,7 +15245,6 @@
"xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesDescription": "これらのルールは、統合フィルタリングフェーズ中にドキュメントに適用されます。",
"xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesTitle": "基本ルール",
"xpack.enterpriseSearch.content.index.connector.syncRules.basicTabTitle": "基本ルール",
- "xpack.enterpriseSearch.content.index.connector.syncRules.flyout.betaDescription": "同期ルールはベータ機能です。ベータ版の機能は変更される可能性があり、一般リリース(GA)機能のサポートSLAではカバーされません。Elasticは将来のリリースでこの機能をGAに昇格させる予定です。",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.description": "次回の同期に適用する前に、ここでルールを計画して編集してください。",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.revertButtonTitle": "アクティブなルールに戻す",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.title": "ドラフトルール",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 4921de9a5b61c..af6c1e6110591 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -15272,7 +15272,6 @@
"xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesDescription": "在集成筛选阶段,这些规则适用于文档。",
"xpack.enterpriseSearch.content.index.connector.syncRules.basicRulesTitle": "基本规则",
"xpack.enterpriseSearch.content.index.connector.syncRules.basicTabTitle": "基本规则",
- "xpack.enterpriseSearch.content.index.connector.syncRules.flyout.betaDescription": "同步规则为公测版功能。公测版功能可能会有所更改,并且不受公开发行版 (GA) 功能支持 SLA 的约束。Elastic 计划在未来版本中将此功能提升到 GA 版本。",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.description": "在此规划和编辑规则,然后将其应用到下次同步。",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.revertButtonTitle": "恢复为活动规则",
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.title": "起草规则",
From 720fb66b398c8d74d5fb9f3a2e21cd84e952935b Mon Sep 17 00:00:00 2001
From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com>
Date: Thu, 18 Apr 2024 18:27:01 +0200
Subject: [PATCH 15/96] Use recoveredCurrent and activeCurrent to determine how
to update old alerts (#180934)
Fixes: #180797
This PR removes execution.uuid from the alert update payload, so the
recovered alerts from previous executions would not be reported again
after their initial execution.
## To verify
Create a rule and let it run at least one time.
Then make one of the alerts recovered and let it run for at least 2
times,
the recovered alert should not be in the message after the first
notification.
would be good to test the scenario in the following issue as well:
https://github.com/elastic/kibana/issues/164739
---
.../server/alerts_client/alerts_client.test.ts | 2 +-
.../server/alerts_client/alerts_client.ts | 8 ++++----
.../lib/build_updated_recovered_alert.test.ts | 3 +++
.../lib/build_updated_recovered_alert.ts | 14 ++++++++++++--
.../alerts_client/lib/format_alert.test.ts | 16 ++++++++++++++++
.../server/alerts_client/lib/format_alert.ts | 8 +++++---
.../group4/alerts_as_data/alerts_as_data.ts | 8 ++++++--
7 files changed, 47 insertions(+), 12 deletions(-)
diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts
index 60e0889d9aec6..36dc3761185ff 100644
--- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts
+++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts
@@ -764,7 +764,7 @@ describe('Alerts Client', () => {
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenNthCalledWith(1, 'active');
- expect(spy).toHaveBeenNthCalledWith(2, 'recovered');
+ expect(spy).toHaveBeenNthCalledWith(2, 'recoveredCurrent');
expect(logger.error).toHaveBeenCalledWith(
"Error writing alert(2) to .alerts-test.alerts-default - alert(2) doesn't exist in active alerts"
diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts
index 4fab0825354b7..ff656f64be4e4 100644
--- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts
+++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts
@@ -413,7 +413,7 @@ export class AlertsClient<
this.legacyAlertsClient.getAlertsToSerialize(false);
const activeAlerts = this.legacyAlertsClient.getProcessedAlerts('active');
- const recoveredAlerts = this.legacyAlertsClient.getProcessedAlerts('recovered');
+ const currentRecoveredAlerts = this.legacyAlertsClient.getProcessedAlerts('recoveredCurrent');
// TODO - Lifecycle alerts set some other fields based on alert status
// Example: workflow status - default to 'open' if not set
@@ -478,7 +478,7 @@ export class AlertsClient<
// If there is not, log an error because there should be
if (this.fetchedAlerts.data.hasOwnProperty(id)) {
recoveredAlertsToIndex.push(
- recoveredAlerts[id]
+ currentRecoveredAlerts[id]
? buildRecoveredAlert<
AlertData,
LegacyState,
@@ -487,7 +487,7 @@ export class AlertsClient<
RecoveryActionGroupId
>({
alert: this.fetchedAlerts.data[id],
- legacyAlert: recoveredAlerts[id],
+ legacyAlert: currentRecoveredAlerts[id],
rule: this.rule,
timestamp: currentTime,
payload: this.reportedAlerts[id],
@@ -503,7 +503,7 @@ export class AlertsClient<
);
} else {
this.options.logger.debug(
- `Could not find alert document to update for recovered alert with id ${id} and uuid ${recoveredAlerts[
+ `Could not find alert document to update for recovered alert with id ${id} and uuid ${currentRecoveredAlerts[
id
].getUuid()}`
);
diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.test.ts b/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.test.ts
index 1c646f08d7828..93ea493bee9cc 100644
--- a/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.test.ts
+++ b/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.test.ts
@@ -25,6 +25,7 @@ import {
VERSION,
ALERT_TIME_RANGE,
ALERT_END,
+ ALERT_CONSECUTIVE_MATCHES,
} from '@kbn/rule-data-utils';
import {
alertRule,
@@ -76,6 +77,7 @@ describe('buildUpdatedRecoveredAlert', () => {
[SPACE_IDS]: ['default'],
[VERSION]: '8.8.1',
[TAGS]: ['rule-', '-tags'],
+ [ALERT_CONSECUTIVE_MATCHES]: 0,
});
});
@@ -125,6 +127,7 @@ describe('buildUpdatedRecoveredAlert', () => {
lte: '2023-03-30T12:27:28.159Z',
},
uuid: 'abcdefg',
+ consecutive_matches: 0,
},
version: '8.8.1',
},
diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.ts b/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.ts
index 59c9a5cb35874..e02999eac950d 100644
--- a/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.ts
+++ b/x-pack/plugins/alerting/server/alerts_client/lib/build_updated_recovered_alert.ts
@@ -7,8 +7,14 @@
import deepmerge from 'deepmerge';
import type { Alert } from '@kbn/alerts-as-data-utils';
-import { ALERT_FLAPPING, ALERT_FLAPPING_HISTORY, TIMESTAMP } from '@kbn/rule-data-utils';
+import {
+ ALERT_FLAPPING,
+ ALERT_FLAPPING_HISTORY,
+ ALERT_RULE_EXECUTION_UUID,
+ TIMESTAMP,
+} from '@kbn/rule-data-utils';
import { RawAlertInstance } from '@kbn/alerting-state-types';
+import { get } from 'lodash';
import { RuleAlertData } from '../../types';
import { AlertRule } from '../types';
import { removeUnflattenedFieldsFromAlert, replaceRefreshableAlertFields } from './format_alert';
@@ -31,7 +37,7 @@ export const buildUpdatedRecoveredAlert = ({
rule,
timestamp,
}: BuildUpdatedRecoveredAlertOpts): Alert & AlertData => {
- // Make sure that any alert fields that are updateable are flattened.
+ // Make sure that any alert fields that are updatable are flattened.
const refreshableAlertFields = replaceRefreshableAlertFields(alert);
const alertUpdates = {
@@ -43,6 +49,10 @@ export const buildUpdatedRecoveredAlert = ({
[ALERT_FLAPPING]: legacyRawAlert.meta?.flapping,
// Set latest flapping history
[ALERT_FLAPPING_HISTORY]: legacyRawAlert.meta?.flappingHistory,
+ // For an "ongoing recovered" alert, we do not want to update the execution UUID to the current one so it does
+ // not get returned for summary alerts. In the future, we may want to restore this and add another field to the
+ // alert doc indicating that this is an ongoing recovered alert that can be used for querying.
+ [ALERT_RULE_EXECUTION_UUID]: get(alert, ALERT_RULE_EXECUTION_UUID),
};
// Clean the existing alert document so any nested fields that will be updated
diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.test.ts b/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.test.ts
index a96cfb715d559..b39dc744d41a7 100644
--- a/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.test.ts
+++ b/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.test.ts
@@ -254,4 +254,20 @@ describe('compactObject', () => {
expect(
compactObject({ 'kibana.alert.rule.execution': {}, 'kibana.alert.nested_field': ['a', 'b'] })
).toEqual({ 'kibana.alert.nested_field': ['a', 'b'] });
+
+ test('should not filter out the fileds with primitive values', () => {
+ expect(
+ compactObject({
+ 'kibana.alert.rule.execution': 1,
+ 'kibana.alert.rule.zero': 0,
+ 'kibana.alert.bool_field': false,
+ 'kibana.alert.null_field': null,
+ })
+ ).toEqual({
+ 'kibana.alert.rule.execution': 1,
+ 'kibana.alert.rule.zero': 0,
+ 'kibana.alert.bool_field': false,
+ 'kibana.alert.null_field': null,
+ });
+ });
});
diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.ts b/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.ts
index a81bdb35ce175..45043bf334be6 100644
--- a/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.ts
+++ b/x-pack/plugins/alerting/server/alerts_client/lib/format_alert.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { cloneDeep, get, isEmpty, merge, omit } from 'lodash';
+import { cloneDeep, get, isEmpty, isNull, isUndefined, merge, omit } from 'lodash';
import type { Alert } from '@kbn/alerts-as-data-utils';
import { RuleAlertData } from '../../types';
import { REFRESH_FIELDS_ALL } from './alert_conflict_resolver';
@@ -35,15 +35,17 @@ export const compactObject = (obj: Obj) => {
// just filter out empty objects
// keep any primitives or arrays, even empty arrays
return (
- !!obj[key] &&
+ !isUndefined(obj[key]) &&
(Array.isArray(obj[key]) ||
typeof obj[key] !== 'object' ||
- (typeof obj[key] === 'object' && !isEmpty(obj[key])))
+ (typeof obj[key] === 'object' && (!isEmpty(obj[key]) || obj[key] === null)))
);
})
.reduce((acc, curr) => {
if (typeof obj[curr] !== 'object' || Array.isArray(obj[curr])) {
acc[curr] = obj[curr];
+ } else if (isNull(obj[curr])) {
+ acc[curr] = null;
} else {
const compacted = compactObject(obj[curr] as Obj);
if (!isEmpty(compacted)) {
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts
index bd79f1dc4a569..7221e8bb4190a 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data.ts
@@ -395,8 +395,12 @@ export default function createAlertsAsDataInstallResourcesTest({ getService }: F
expect(omit(alertBDocRun3, fieldsToOmitInComparison)).to.eql(
omit(alertBDocRun2, fieldsToOmitInComparison)
);
- // execution uuid should be current one
- expect(alertBDocRun3[ALERT_RULE_EXECUTION_UUID]).to.equal(executionUuid);
+
+ // execution uuid should be overwritten
+ expect(alertBDocRun3[ALERT_RULE_EXECUTION_UUID]).to.eql(
+ alertBDocRun2[ALERT_RULE_EXECUTION_UUID]
+ );
+
// flapping history should be history from prior run with additional entry
expect(alertBDocRun3[ALERT_FLAPPING_HISTORY]).to.eql([
...alertBDocRun2[ALERT_FLAPPING_HISTORY]!,
From e2fc61501181d5f7a96f916548fd3d80d14f2c46 Mon Sep 17 00:00:00 2001
From: Joe McElroy
Date: Thu, 18 Apr 2024 17:27:40 +0100
Subject: [PATCH 16/96] [Search] [Playground] fix code formatting (#181142)
Fixing the code formatting in the view code section. Also labelling the
examples as OpenAI as we now introduced more model options (Azure and
Bedrock + more) which we dont have support for yet in view_code.
In future these code examples will be moved to notebooks in
elasticsearch-labs so they can inherit the formatting / automated test
coverage / updates and no longer managed in strings.
![image](https://github.com/elastic/kibana/assets/49480/67865378-61c5-4b40-9985-fbc419f82578)
---
.../py_lang_client.test.tsx.snap | 48 +++++++++----------
.../py_langchain_python.test.tsx.snap | 23 +++++----
.../view_code/examples/py_lang_client.tsx | 46 +++++++-----------
.../examples/py_langchain_python.tsx | 33 +++++--------
.../components/view_code/examples/utils.ts | 21 ++++++++
.../components/view_code/view_code_flyout.tsx | 6 +--
6 files changed, 90 insertions(+), 87 deletions(-)
create mode 100644 x-pack/plugins/search_playground/public/components/view_code/examples/utils.ts
diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap b/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap
index 25d1f6cce4b61..8506071050eb8 100644
--- a/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap
+++ b/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap
@@ -10,7 +10,7 @@ from openai import OpenAI
es_client = Elasticsearch(
- http://my-local-cloud-instance,
+ \\"http://my-local-cloud-instance\\",
api_key=os.environ[\\"ES_API_KEY\\"]
)
@@ -19,16 +19,7 @@ openai_client = OpenAI(
api_key=os.environ[\\"OPENAI_API_KEY\\"],
)
-def get_elasticsearch_results(query):
- es_query = {}
-
- result = es.search(index=\\"index1,index2\\", query=es_query, size=10)
- return result[\\"hits\\"][\\"hits\\"]
-
-def create_openai_prompt(question, results):
-
- context = \\"\\"
- index_source_fields = {
+index_source_fields = {
\\"index1\\": [
\\"field1\\"
],
@@ -36,11 +27,22 @@ def create_openai_prompt(question, results):
\\"field2\\"
]
}
+
+def get_elasticsearch_results(query):
+ es_query = {
+ \\"query\\": {},
+ \\"size\\": 10
+ }
+
+ result = es.search(index=\\"index1,index2\\", body=es_query)
+ return result[\\"hits\\"][\\"hits\\"]
+
+def create_openai_prompt(question, results):
+ context = \\"\\"
for hit in results:
source_field = index_source_fields.get(hit[\\"_index\\"])[0]
hit_context = hit[\\"_source\\"][source_field]
- context += f\\"{hit_context}
-\\"
+ context += f\\"{hit_context}\\\\n\\"
prompt = f\\"\\"\\"
Instructions:
@@ -64,25 +66,21 @@ def create_openai_prompt(question, results):
def generate_openai_completion(user_prompt):
response = openai_client.chat.completions.create(
- model=\\"Your-new-model\\",
+ model=\\"gpt-3.5-turbo\\",
messages=[
- {\\"role\\": \\"system\\", \\"content\\": \\"You are an assistant for question-answering tasks.\\"},
- {\\"role\\": \\"user\\", \\"content\\": user_prompt},
+ {\\"role\\": \\"system\\", \\"content\\": \\"You are an assistant for question-answering tasks.\\"},
+ {\\"role\\": \\"user\\", \\"content\\": user_prompt},
]
)
return response.choices[0].message.content
if __name__ == \\"__main__\\":
- question = \\"my question\\"
-
- elasticsearch_results = get_elasticsearch_results(question)
-
- context_prompt = create_openai_prompt(question, elasticsearch_results)
-
- openai_completion = generate_openai_completion(context_prompt)
-
- print(openai_completion)
+ question = \\"my question\\"
+ elasticsearch_results = get_elasticsearch_results(question)
+ context_prompt = create_openai_prompt(question, elasticsearch_results)
+ openai_completion = generate_openai_completion(context_prompt)
+ print(openai_completion)
"
`;
diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_langchain_python.test.tsx.snap b/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_langchain_python.test.tsx.snap
index 7474cbe8b64d4..e6735a416e52c 100644
--- a/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_langchain_python.test.tsx.snap
+++ b/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_langchain_python.test.tsx.snap
@@ -13,30 +13,37 @@ from langchain_core.prompts import format_document
from langchain.prompts.prompt import PromptTemplate
import os
-
es_client = Elasticsearch(
- http://my-local-cloud-instance,
+ \\"http://my-local-cloud-instance\\",
api_key=os.environ[\\"ES_API_KEY\\"]
)
def build_query(query):
return {
- \\"query\\": {}
+ \\"query\\": {}
+ }
+
+index_source_fields = {
+ \\"index1\\": [
+ \\"field1\\"
+ ],
+ \\"index2\\": [
+ \\"field2\\"
+ ]
}
retriever = ElasticsearchRetriever(
- index_name=\\"{formValues.indices.join(',')}\\",
+ index_name=\\"index1,index2\\",
body_func=build_query,
- content_field=\\"text\\",
+ content_field=index_source_fields,
es_client=es_client
)
-model = ChatOpenAI(openai_api_key=os.environ[\\"OPENAI_API_KEY\\"], model_name=\\"Your-new-model\\")
-
+model = ChatOpenAI(openai_api_key=os.environ[\\"OPENAI_API_KEY\\"], model_name=\\"gpt-3.5-turbo\\")
ANSWER_PROMPT = ChatPromptTemplate.from_template(
- f\\"\\"\\"
+ f\\"\\"\\"
Instructions:
- Your prompt
diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx b/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx
index 4ade9c59724ae..6f78f72795299 100644
--- a/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx
+++ b/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx
@@ -9,16 +9,7 @@ import { EuiCodeBlock } from '@elastic/eui';
import React from 'react';
import { ChatForm } from '../../../types';
import { Prompt } from '../../../../common/prompt';
-
-const getESQuery = (query: any) => {
- try {
- return JSON.stringify(query, null, 2).replace('"${query}"', 'f"${query}"');
- } catch (e) {
- // eslint-disable-next-line no-console
- console.error('Error parsing ES query', e);
- return '{}';
- }
-};
+import { getESQuery } from './utils';
export const PY_LANG_CLIENT = (formValues: ChatForm, clientDetails: string) => (
@@ -35,22 +26,23 @@ openai_client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
)
+index_source_fields = ${JSON.stringify(formValues.source_fields, null, 2)}
+
def get_elasticsearch_results(query):
- es_query = ${getESQuery(formValues.elasticsearch_query.query)}
+ es_query = ${getESQuery({
+ ...formValues.elasticsearch_query,
+ size: formValues.doc_size,
+ })}
- result = es.search(index="${formValues.indices.join(',')}", query=es_query, size=${
- formValues.doc_size
- })
+ result = es.search(index="${formValues.indices.join(',')}", body=es_query)
return result["hits"]["hits"]
def create_openai_prompt(question, results):
-
context = ""
- index_source_fields = ${JSON.stringify(formValues.source_fields, null, 2)}
for hit in results:
source_field = index_source_fields.get(hit["_index"])[0]
hit_context = hit["_source"][source_field]
- context += f"{hit_context}\n"
+ context += f"{hit_context}\\n"
prompt = f"""${Prompt(formValues.prompt, {
context: true,
@@ -62,25 +54,21 @@ def create_openai_prompt(question, results):
def generate_openai_completion(user_prompt):
response = openai_client.chat.completions.create(
- model="${formValues.summarization_model}",
+ model="gpt-3.5-turbo",
messages=[
- {"role": "system", "content": "You are an assistant for question-answering tasks."},
- {"role": "user", "content": user_prompt},
+ {"role": "system", "content": "You are an assistant for question-answering tasks."},
+ {"role": "user", "content": user_prompt},
]
)
return response.choices[0].message.content
if __name__ == "__main__":
- question = "my question"
-
- elasticsearch_results = get_elasticsearch_results(question)
-
- context_prompt = create_openai_prompt(question, elasticsearch_results)
-
- openai_completion = generate_openai_completion(context_prompt)
-
- print(openai_completion)
+ question = "my question"
+ elasticsearch_results = get_elasticsearch_results(question)
+ context_prompt = create_openai_prompt(question, elasticsearch_results)
+ openai_completion = generate_openai_completion(context_prompt)
+ print(openai_completion)
`}
diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/py_langchain_python.tsx b/x-pack/plugins/search_playground/public/components/view_code/examples/py_langchain_python.tsx
index cd02395c9c165..127d3786bcaf5 100644
--- a/x-pack/plugins/search_playground/public/components/view_code/examples/py_langchain_python.tsx
+++ b/x-pack/plugins/search_playground/public/components/view_code/examples/py_langchain_python.tsx
@@ -9,16 +9,7 @@ import { EuiCodeBlock } from '@elastic/eui';
import React from 'react';
import { ChatForm } from '../../../types';
import { Prompt } from '../../../../common/prompt';
-
-const getESQuery = (query: any) => {
- try {
- return JSON.stringify(query, null, 2).replace('"${query}"', 'f"${query}"');
- } catch (e) {
- // eslint-disable-next-line no-console
- console.error('Error parsing ES query', e);
- return '{}';
- }
-};
+import { getESQuery } from './utils';
export const LANGCHAIN_PYTHON = (formValues: ChatForm, clientDetails: string) => (
@@ -33,30 +24,28 @@ from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import format_document
from langchain.prompts.prompt import PromptTemplate
import os
-
${clientDetails}
def build_query(query):
return ${getESQuery(formValues.elasticsearch_query)}
+index_source_fields = ${JSON.stringify(formValues.source_fields, null, 2)}
+
retriever = ElasticsearchRetriever(
- index_name="{formValues.indices.join(',')}",
+ index_name="${formValues.indices.join(',')}",
body_func=build_query,
- content_field="text",
+ content_field=index_source_fields,
es_client=es_client
)
-model = ChatOpenAI(openai_api_key=os.environ["OPENAI_API_KEY"], model_name="${
- formValues.summarization_model
- }")
-
+model = ChatOpenAI(openai_api_key=os.environ["OPENAI_API_KEY"], model_name="gpt-3.5-turbo")
ANSWER_PROMPT = ChatPromptTemplate.from_template(
- f"""${Prompt(formValues.prompt, {
- context: true,
- citations: formValues.citations,
- type: 'openai',
- })}"""
+ f"""${Prompt(formValues.prompt, {
+ context: true,
+ citations: formValues.citations,
+ type: 'openai',
+ })}"""
)
DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template(template="{page_content}")
diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/utils.ts b/x-pack/plugins/search_playground/public/components/view_code/examples/utils.ts
new file mode 100644
index 0000000000000..6d44442f588a2
--- /dev/null
+++ b/x-pack/plugins/search_playground/public/components/view_code/examples/utils.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 const getESQuery = (query: any) => {
+ try {
+ return JSON.stringify(query, null, 2)
+ .replace('"{query}"', 'query')
+ .split('\n')
+ .map((line) => ` ${line}`)
+ .join('\n')
+ .trim();
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error('Error parsing ES query', e);
+ return '{}';
+ }
+};
diff --git a/x-pack/plugins/search_playground/public/components/view_code/view_code_flyout.tsx b/x-pack/plugins/search_playground/public/components/view_code/view_code_flyout.tsx
index a914da997d276..9b65da674f3c3 100644
--- a/x-pack/plugins/search_playground/public/components/view_code/view_code_flyout.tsx
+++ b/x-pack/plugins/search_playground/public/components/view_code/view_code_flyout.tsx
@@ -35,7 +35,7 @@ export const ES_CLIENT_DETAILS = (cloud: CloudSetup | undefined) => {
if (cloud) {
return `
es_client = Elasticsearch(
- ${cloud.elasticsearchUrl},
+ "${cloud.elasticsearchUrl}",
api_key=os.environ["ES_API_KEY"]
)
`;
@@ -91,8 +91,8 @@ export const ViewCodeFlyout: React.FC = ({ onClose }) => {
setSelectedLanguage(e.target.value)}
value={selectedLanguage}
From aef197b243a7f604e963a07ab9124608ff0d1402 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Thu, 18 Apr 2024 18:35:47 +0100
Subject: [PATCH 17/96] fix(NA): do not change default encoding when using vfs
to copy files (#181174)
Closes https://github.com/elastic/kibana/issues/180067
When we upgraded into vinyl-fs@4.0.0 there was a missing breaking change
into their changelog related to encodings that was only added last
february. Because of that we missed a couple places where file
encondings were being changed from the default ones and in some cases
corrupting those files.
This PR fixes those problems.
---
packages/kbn-plugin-generator/src/render_template.ts | 1 +
.../kbn-plugin-helpers/src/tasks/brotli_compress_bundles.ts | 2 +-
packages/kbn-plugin-helpers/src/tasks/write_public_assets.ts | 1 +
packages/kbn-plugin-helpers/src/tasks/write_server_files.ts | 1 +
4 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/kbn-plugin-generator/src/render_template.ts b/packages/kbn-plugin-generator/src/render_template.ts
index f2cb8c18a88d4..ad9b4cbc8d845 100644
--- a/packages/kbn-plugin-generator/src/render_template.ts
+++ b/packages/kbn-plugin-generator/src/render_template.ts
@@ -79,6 +79,7 @@ export async function renderTemplates({
buffer: true,
nodir: true,
cwd: Path.resolve(__dirname, '../template'),
+ encoding: false,
}),
// exclude files from the template based on selected options, patterns
diff --git a/packages/kbn-plugin-helpers/src/tasks/brotli_compress_bundles.ts b/packages/kbn-plugin-helpers/src/tasks/brotli_compress_bundles.ts
index ef03cb86c7aa0..ef9d3ac7d71f8 100644
--- a/packages/kbn-plugin-helpers/src/tasks/brotli_compress_bundles.ts
+++ b/packages/kbn-plugin-helpers/src/tasks/brotli_compress_bundles.ts
@@ -34,7 +34,7 @@ export async function brotliCompressBundles({ buildDir, log, plugin }: TaskConte
try {
await del(['**/*.br'], { cwd: compressDir });
await asyncPipeline(
- vfs.src(['**/*.{js,css}'], { cwd: compressDir }),
+ vfs.src(['**/*.{js,css}'], { cwd: compressDir, encoding: false }),
gulpBrotli({
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
diff --git a/packages/kbn-plugin-helpers/src/tasks/write_public_assets.ts b/packages/kbn-plugin-helpers/src/tasks/write_public_assets.ts
index 7fa6d157f4639..d43062018626f 100644
--- a/packages/kbn-plugin-helpers/src/tasks/write_public_assets.ts
+++ b/packages/kbn-plugin-helpers/src/tasks/write_public_assets.ts
@@ -28,6 +28,7 @@ export async function writePublicAssets({ log, plugin, sourceDir, buildDir }: Ta
base: sourceDir,
buffer: true,
allowEmpty: true,
+ encoding: false,
}),
vfs.dest(buildDir)
);
diff --git a/packages/kbn-plugin-helpers/src/tasks/write_server_files.ts b/packages/kbn-plugin-helpers/src/tasks/write_server_files.ts
index 2ba2686796487..362ef171cd9da 100644
--- a/packages/kbn-plugin-helpers/src/tasks/write_server_files.ts
+++ b/packages/kbn-plugin-helpers/src/tasks/write_server_files.ts
@@ -53,6 +53,7 @@ export async function writeServerFiles({
'**/*.{test,test.mocks,mock,mocks}.*',
],
allowEmpty: true,
+ encoding: false,
}
),
From d61b53fea522c648f4e023e643b52cf6ae6e930d Mon Sep 17 00:00:00 2001
From: Alexi Doak <109488926+doakalexi@users.noreply.github.com>
Date: Thu, 18 Apr 2024 10:48:46 -0700
Subject: [PATCH 18/96] [Response Ops][Task Manager] TM status affected by
capacity estimation which is based on a best estimate of number of Kibana
instances (#179982)
Resolves https://github.com/elastic/kibana/issues/176152
## Summary
This PR sets task manager status to `OK` if the required throughput is
greater than the capacity, but logs a warning to help indicate that Task
Manager can not keep up with the workload.
We decided to do this bc the `assumedKibanaInstances` metric is a best
estimate that could potentially set the task manager status to degraded
.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../monitoring/capacity_estimation.test.ts | 19 ++-
.../server/monitoring/capacity_estimation.ts | 16 ++-
.../task_manager/server/routes/health.test.ts | 136 +++++++++++++++---
3 files changed, 141 insertions(+), 30 deletions(-)
diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts
index a171b06903bbc..263f2e9987b7c 100644
--- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts
@@ -12,7 +12,7 @@ import { mockLogger } from '../test_utils';
describe('estimateCapacity', () => {
const logger = mockLogger();
- beforeAll(() => {
+ beforeEach(() => {
jest.resetAllMocks();
});
@@ -568,6 +568,9 @@ describe('estimateCapacity', () => {
timestamp: expect.any(String),
value: expect.any(Object),
});
+ expect(logger.debug).toHaveBeenCalledWith(
+ 'Task Manager is healthy, the assumedRequiredThroughputPerMinutePerKibana (190) < capacityPerMinutePerKibana (200)'
+ );
});
test('marks estimated capacity as Warning state when capacity is insufficient for recent spikes of non-recurring workload, but sufficient for the recurring workload', async () => {
@@ -626,10 +629,13 @@ describe('estimateCapacity', () => {
)
)
).toMatchObject({
- status: 'warn',
+ status: 'OK',
timestamp: expect.any(String),
value: expect.any(Object),
});
+ expect(logger.warn).toHaveBeenCalledWith(
+ 'Task Manager is unhealthy, the assumedAverageRecurringRequiredThroughputPerMinutePerKibana (175) < capacityPerMinutePerKibana (200)'
+ );
});
test('marks estimated capacity as Error state when workload and load suggest capacity is insufficient', async () => {
@@ -688,10 +694,13 @@ describe('estimateCapacity', () => {
)
)
).toMatchObject({
- status: 'error',
+ status: 'OK',
timestamp: expect.any(String),
value: expect.any(Object),
});
+ expect(logger.warn).toHaveBeenCalledWith(
+ 'Task Manager is unhealthy, the assumedRequiredThroughputPerMinutePerKibana (250) >= capacityPerMinutePerKibana (200) AND assumedAverageRecurringRequiredThroughputPerMinutePerKibana (210) >= capacityPerMinutePerKibana (200)'
+ );
});
test('recommmends a 20% increase in kibana when a spike in non-recurring tasks forces recurring task capacity to zero', async () => {
@@ -749,7 +758,7 @@ describe('estimateCapacity', () => {
)
)
).toMatchObject({
- status: 'warn',
+ status: 'OK',
timestamp: expect.any(String),
value: {
observed: {
@@ -825,7 +834,7 @@ describe('estimateCapacity', () => {
)
)
).toMatchObject({
- status: 'error',
+ status: 'OK',
timestamp: expect.any(String),
value: {
observed: {
diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
index e03a9c6337090..b12382f16e27b 100644
--- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
@@ -242,18 +242,20 @@ function getHealthStatus(
capacityPerMinutePerKibana,
} = params;
if (assumedRequiredThroughputPerMinutePerKibana < capacityPerMinutePerKibana) {
- return { status: HealthStatus.OK };
+ const reason = `Task Manager is healthy, the assumedRequiredThroughputPerMinutePerKibana (${assumedRequiredThroughputPerMinutePerKibana}) < capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`;
+ logger.debug(reason);
+ return { status: HealthStatus.OK, reason };
}
if (assumedAverageRecurringRequiredThroughputPerMinutePerKibana < capacityPerMinutePerKibana) {
- const reason = `setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) < capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`;
- logger.debug(reason);
- return { status: HealthStatus.Warning, reason };
+ const reason = `Task Manager is unhealthy, the assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) < capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`;
+ logger.warn(reason);
+ return { status: HealthStatus.OK, reason };
}
- const reason = `setting HealthStatus.Error because assumedRequiredThroughputPerMinutePerKibana (${assumedRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana}) AND assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`;
- logger.debug(reason);
- return { status: HealthStatus.Error, reason };
+ const reason = `Task Manager is unhealthy, the assumedRequiredThroughputPerMinutePerKibana (${assumedRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana}) AND assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`;
+ logger.warn(reason);
+ return { status: HealthStatus.OK, reason };
}
export function withCapacityEstimate(
diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts
index de84a2bd4aa1c..a97d99079bc58 100644
--- a/x-pack/plugins/task_manager/server/routes/health.test.ts
+++ b/x-pack/plugins/task_manager/server/routes/health.test.ts
@@ -14,15 +14,25 @@ import { mockHandlerArguments } from './_mock_handler_arguments';
import { sleep } from '../test_utils';
import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
-import { MonitoringStats, RawMonitoringStats, summarizeMonitoringStats } from '../monitoring';
-import { ServiceStatusLevels, Logger } from '@kbn/core/server';
+import { MonitoringStats, RawMonitoringStats } from '../monitoring';
+import { ServiceStatusLevels } from '@kbn/core/server';
import { configSchema, TaskManagerConfig } from '../config';
import { FillPoolResult } from '../lib/fill_pool';
+jest.mock('../monitoring', () => {
+ const monitoring = jest.requireActual('../monitoring');
+ return {
+ ...monitoring,
+ summarizeMonitoringStats: jest.fn(),
+ };
+});
+
jest.mock('../lib/log_health_metrics', () => ({
logHealthMetrics: jest.fn(),
}));
+const { summarizeMonitoringStats } = jest.requireMock('../monitoring');
+
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
@@ -38,12 +48,29 @@ const createMockClusterClient = (response: any) => {
return { mockClusterClient, mockScopedClusterClient };
};
+const timestamp = new Date().toISOString();
+
describe('healthRoute', () => {
const logger = loggingSystemMock.create().get();
const docLinks = docLinksServiceMock.create().setup();
-
beforeEach(() => {
jest.resetAllMocks();
+
+ summarizeMonitoringStats.mockReturnValue({
+ last_update: timestamp,
+ stats: {
+ workload: {
+ timestamp,
+ value: {},
+ status: 'OK',
+ },
+ capacity_estimation: {
+ status: 'OK',
+ timestamp,
+ value: {},
+ },
+ },
+ });
});
it('registers the route', async () => {
@@ -245,6 +272,24 @@ describe('healthRoute', () => {
it(`logs at a warn level if the status is warning`, async () => {
const router = httpServiceMock.createRouter();
const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
+ const reason =
+ 'setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana (78.28472222222223) < capacityPerMinutePerKibana (200)';
+ summarizeMonitoringStats.mockReturnValue({
+ last_update: timestamp,
+ stats: {
+ workload: {
+ timestamp,
+ value: {},
+ status: 'OK',
+ },
+ capacity_estimation: {
+ status: 'warn',
+ reason,
+ timestamp,
+ value: {},
+ },
+ },
+ });
const warnRuntimeStat = mockHealthStats();
const warnConfigurationStat = mockHealthStats();
@@ -288,8 +333,7 @@ describe('healthRoute', () => {
expect(await serviceStatus).toMatchObject({
level: ServiceStatusLevels.degraded,
- summary:
- 'Task Manager is unhealthy - Reason: setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana (78.28472222222223) < capacityPerMinutePerKibana (200)',
+ summary: `Task Manager is unhealthy - Reason: ${reason}`,
});
expect(logHealthMetrics).toBeCalledTimes(4);
@@ -330,6 +374,24 @@ describe('healthRoute', () => {
it(`logs at an error level if the status is error`, async () => {
const router = httpServiceMock.createRouter();
const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
+ const reason =
+ 'setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana (78.28472222222223) < capacityPerMinutePerKibana (200)';
+ summarizeMonitoringStats.mockReturnValue({
+ last_update: timestamp,
+ stats: {
+ workload: {
+ timestamp,
+ value: {},
+ status: 'OK',
+ },
+ capacity_estimation: {
+ status: 'error',
+ reason,
+ timestamp,
+ value: {},
+ },
+ },
+ });
const errorRuntimeStat = mockHealthStats();
const errorConfigurationStat = mockHealthStats();
@@ -373,8 +435,7 @@ describe('healthRoute', () => {
expect(await serviceStatus).toMatchObject({
level: ServiceStatusLevels.degraded,
- summary:
- 'Task Manager is unhealthy - Reason: setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana (78.28472222222223) < capacityPerMinutePerKibana (200)',
+ summary: `Task Manager is unhealthy - Reason: ${reason}`,
});
expect(logHealthMetrics).toBeCalledTimes(4);
@@ -414,6 +475,22 @@ describe('healthRoute', () => {
it('returns a error status if the overall stats have not been updated within the required hot freshness', async () => {
const router = httpServiceMock.createRouter();
+ const coldTimestamp = new Date(Date.now() - 3001).toISOString();
+ summarizeMonitoringStats.mockReturnValue({
+ last_update: coldTimestamp,
+ stats: {
+ workload: {
+ timestamp: coldTimestamp,
+ value: {},
+ status: 'OK',
+ },
+ capacity_estimation: {
+ status: 'OK',
+ timestamp: coldTimestamp,
+ value: {},
+ },
+ },
+ });
const stats$ = new Subject();
@@ -444,7 +521,7 @@ describe('healthRoute', () => {
stats$.next(
mockHealthStats({
- last_update: new Date(Date.now() - 3001).toISOString(),
+ last_update: coldTimestamp,
})
);
@@ -487,17 +564,26 @@ describe('healthRoute', () => {
summary:
'Task Manager is unhealthy - Reason: setting HealthStatus.Error because of expired hot timestamps',
});
- const warnCalls = (logger as jest.Mocked).debug.mock.calls as string[][];
- const warnMessage =
- /^setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana/;
- const found = warnCalls
- .map((arr) => arr[0])
- .find((message) => message.match(warnMessage) != null);
- expect(found).toMatch(warnMessage);
});
it('returns a error status if the workload stats have not been updated within the required cold freshness', async () => {
+ const coldTimestamp = new Date(Date.now() - 120000).toISOString();
const router = httpServiceMock.createRouter();
+ summarizeMonitoringStats.mockReturnValue({
+ last_update: timestamp,
+ stats: {
+ workload: {
+ timestamp: coldTimestamp,
+ value: {},
+ status: 'OK',
+ },
+ capacity_estimation: {
+ status: 'OK',
+ timestamp,
+ value: {},
+ },
+ },
+ });
const stats$ = new Subject();
@@ -522,12 +608,11 @@ describe('healthRoute', () => {
await sleep(0);
- const lastUpdateOfWorkload = new Date(Date.now() - 120000).toISOString();
stats$.next(
mockHealthStats({
stats: {
workload: {
- timestamp: lastUpdateOfWorkload,
+ timestamp: coldTimestamp,
},
},
})
@@ -663,7 +748,23 @@ describe('healthRoute', () => {
});
it('returns a OK status for empty if shouldRunTasks is false', async () => {
+ const lastUpdate = new Date().toISOString();
const router = httpServiceMock.createRouter();
+ summarizeMonitoringStats.mockReturnValue({
+ last_update: lastUpdate,
+ stats: {
+ workload: {
+ timestamp: lastUpdate,
+ value: {},
+ status: 'OK',
+ },
+ capacity_estimation: {
+ status: 'OK',
+ timestamp: lastUpdate,
+ value: {},
+ },
+ },
+ });
const stats$ = new Subject();
const { serviceStatus$ } = healthRoute({
@@ -685,7 +786,6 @@ describe('healthRoute', () => {
const serviceStatus = firstValueFrom(serviceStatus$);
await sleep(0);
- const lastUpdate = new Date().toISOString();
stats$.next({
last_update: lastUpdate,
stats: {},
From cfee0fafb6e4bf272e547a3fa569ac390038fff3 Mon Sep 17 00:00:00 2001
From: Philippe Oberti
Date: Thu, 18 Apr 2024 13:18:17 -0500
Subject: [PATCH 19/96] [Security Solution][Alert details] - update telemetry
property to better reflect where the flyout is opened from (#180984)
---
x-pack/plugins/security_solution/public/cases/pages/index.tsx | 2 +-
.../common/components/control_columns/row_action/index.tsx | 2 +-
.../common/lib/telemetry/events/document_details/index.ts | 4 ++--
.../common/lib/telemetry/events/document_details/types.ts | 4 ++--
.../public/flyout/document_details/left/index.tsx | 2 +-
.../public/flyout/document_details/left/tabs/insights_tab.tsx | 2 +-
.../document_details/right/components/alert_description.tsx | 2 +-
.../flyout/document_details/right/components/reason.tsx | 2 +-
.../public/flyout/document_details/right/index.tsx | 2 +-
.../public/flyout/document_details/right/navigation.tsx | 2 +-
10 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx
index 2a7f92076fa97..a4d43043b49e9 100644
--- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx
@@ -83,7 +83,7 @@ const CaseContainerComponent: React.FC = () => {
},
});
telemetry.reportDetailsFlyoutOpened({
- tableId: TimelineId.casePage,
+ location: TimelineId.casePage,
panel: 'right',
});
}
diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx
index 1ba141ffb5fc2..7454db7c1e484 100644
--- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx
@@ -122,7 +122,7 @@ const RowActionComponent = ({
},
});
telemetry.reportDetailsFlyoutOpened({
- tableId,
+ location: tableId,
panel: 'right',
});
}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts
index 5f036ec164936..ba59cf5130dc2 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/index.ts
@@ -11,7 +11,7 @@ import { TelemetryEventTypes } from '../../constants';
export const DocumentDetailsFlyoutOpenedEvent: TelemetryEvent = {
eventType: TelemetryEventTypes.DetailsFlyoutOpened,
schema: {
- tableId: {
+ location: {
type: 'text',
_meta: {
description: 'Table ID',
@@ -31,7 +31,7 @@ export const DocumentDetailsFlyoutOpenedEvent: TelemetryEvent = {
export const DocumentDetailsTabClickedEvent: TelemetryEvent = {
eventType: TelemetryEventTypes.DetailsFlyoutTabClicked,
schema: {
- tableId: {
+ location: {
type: 'text',
_meta: {
description: 'Table ID',
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts
index 04efb6544deb2..a090686c91267 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts
@@ -9,12 +9,12 @@ import type { RootSchema } from '@kbn/analytics-client';
import type { TelemetryEventTypes } from '../../constants';
export interface ReportDetailsFlyoutOpenedParams {
- tableId: string;
+ location: string;
panel: 'left' | 'right' | 'preview';
}
export interface ReportDetailsFlyoutTabClickedParams {
- tableId: string;
+ location: string;
panel: 'left' | 'right';
tabId: string;
}
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 5a2851b57c1a1..25b076eaa3d4a 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
@@ -69,7 +69,7 @@ export const LeftPanel: FC> = memo(({ path }) => {
},
});
telemetry.reportDetailsFlyoutTabClicked({
- tableId: scopeId,
+ location: scopeId,
panel: 'left',
tabId,
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx
index f73791849c4fc..d47631f4de98d 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/insights_tab.tsx
@@ -117,7 +117,7 @@ export const InsightsTab: React.FC = memo(() => {
},
});
telemetry.reportDetailsFlyoutTabClicked({
- tableId: scopeId,
+ location: scopeId,
panel: 'left',
tabId: optionId,
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx
index 494ddac21f6a4..b3c4ac7a2ce6b 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_description.tsx
@@ -59,7 +59,7 @@ export const AlertDescription: FC = () => {
},
});
telemetry.reportDetailsFlyoutOpened({
- tableId: scopeId,
+ location: scopeId,
panel: 'preview',
});
}, [eventId, openPreviewPanel, indexName, scopeId, ruleId, telemetry]);
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx
index 7b4ba1ced829f..0ac7e5f0eb2ff 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.tsx
@@ -55,7 +55,7 @@ export const Reason: FC = () => {
},
});
telemetry.reportDetailsFlyoutOpened({
- tableId: scopeId,
+ location: scopeId,
panel: 'preview',
});
}, [eventId, openPreviewPanel, indexName, scopeId, telemetry]);
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 0b4fcb4c6ba02..f1cbd4fbc9685 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
@@ -65,7 +65,7 @@ export const RightPanel: FC> = memo(({ path }) => {
storage.set(FLYOUT_STORAGE_KEYS.RIGHT_PANEL_SELECTED_TABS, tabId);
telemetry.reportDetailsFlyoutTabClicked({
- tableId: scopeId,
+ location: scopeId,
panel: 'right',
tabId,
});
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx
index b2879dcbaae44..d484e2e2a0204 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/navigation.tsx
@@ -36,7 +36,7 @@ export const PanelNavigation: FC = memo(({ flyoutIsExpanda
},
});
telemetry.reportDetailsFlyoutOpened({
- tableId: scopeId,
+ location: scopeId,
panel: 'left',
});
}, [eventId, openLeftPanel, indexName, scopeId, telemetry]);
From 89609fe596d79b7d2eb4f209c5388824f9b279c1 Mon Sep 17 00:00:00 2001
From: Andrew Macri
Date: Thu, 18 Apr 2024 15:01:27 -0400
Subject: [PATCH 20/96] [Security Solution] [AI Insights] Enable LangSmith
tracing in cloud deployments (#181159)
## [Security Solution] [AI Insights] Enable LangSmith tracing in cloud deployments
### Summary
This PR enables LangSmith tracing for the [AI Insights](https://github.com/elastic/kibana/pull/180611) feature in cloud deployments.
LangSmith project names and API keys are specified using the same UI and patterns introduced by @spong in
### Details
To enable LangSmith tracing in cloud deployments, configure the following `xpack.securitySolution.enableExperimental` feature flags:
```
xpack.securitySolution.enableExperimental: ['assistantModelEvaluation', 'assistantAlertsInsights']
```
- The `assistantModelEvaluation` feature flag enables the `Evaluation` settings category in the assistant. The LangSmith project name and API key are configured here
- The `assistantAlertsInsights` feature flag enables the AI Insights feature, which is off by default at the time of this writing
After enabling the feature flags above:
1) Click the `AI Assistant` button anywhere in the Security Solution to launch the assistant
2) Click the settings gear in the assistant
3) Click the `Evaluation` settings category
4) Click `Show Trace Options (for internal use only)`
5) Specify a `LangSmith Project` and `LangSmith API Key` per the screenshot below:
![langsmith_settings](https://github.com/elastic/kibana/assets/4459398/896c8155-3a06-4381-becf-b846b5aba426)
---
.../impl/llm/actions_client_llm.ts | 11 +++++++---
.../impl/llm/types.ts | 10 +++++++++
.../alerts/post_alerts_insights_route.gen.ts | 2 ++
.../post_alerts_insights_route.schema.yaml | 4 ++++
.../insights/alerts/post_alerts_insights.ts | 22 ++++++++++++++++++-
.../public/ai_insights/use_insights/index.tsx | 11 +++++++++-
6 files changed, 55 insertions(+), 5 deletions(-)
diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/llm/actions_client_llm.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/llm/actions_client_llm.ts
index ab77e90c1c3f4..8df4c60817c6c 100644
--- a/x-pack/packages/kbn-elastic-assistant-common/impl/llm/actions_client_llm.ts
+++ b/x-pack/packages/kbn-elastic-assistant-common/impl/llm/actions_client_llm.ts
@@ -5,13 +5,14 @@
* 2.0.
*/
-import { v4 as uuidv4 } from 'uuid';
-import { KibanaRequest, Logger } from '@kbn/core/server';
import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server';
+import { KibanaRequest, Logger } from '@kbn/core/server';
import { LLM } from '@langchain/core/language_models/llms';
import { get } from 'lodash/fp';
+import { v4 as uuidv4 } from 'uuid';
import { getMessageContentAndRole } from './helpers';
+import { TraceOptions } from './types';
const LLM_TYPE = 'ActionsClientLlm';
@@ -27,6 +28,7 @@ interface ActionsClientLlmParams {
model?: string;
temperature?: number;
traceId?: string;
+ traceOptions?: TraceOptions;
}
export class ActionsClientLlm extends LLM {
@@ -52,8 +54,11 @@ export class ActionsClientLlm extends LLM {
model,
request,
temperature,
+ traceOptions,
}: ActionsClientLlmParams) {
- super({});
+ super({
+ callbacks: [...(traceOptions?.tracers ?? [])],
+ });
this.#actions = actions;
this.#connectorId = connectorId;
diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/llm/types.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/llm/types.ts
index 85c970b1b948a..5fd08e6ee4e2d 100644
--- a/x-pack/packages/kbn-elastic-assistant-common/impl/llm/types.ts
+++ b/x-pack/packages/kbn-elastic-assistant-common/impl/llm/types.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { LangChainTracer } from '@langchain/core/tracers/tracer_langchain';
import {
ChatCompletionContentPart,
ChatCompletionCreateParamsNonStreaming,
@@ -38,3 +39,12 @@ export interface InvokeAIActionParamsSchema {
functions?: ChatCompletionCreateParamsNonStreaming['functions'];
signal?: AbortSignal;
}
+
+export interface TraceOptions {
+ evaluationId?: string;
+ exampleId?: string;
+ projectName?: string;
+ runName?: string;
+ tags?: string[];
+ tracers?: LangChainTracer[];
+}
diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.gen.ts
index 2f64f3c6e521d..cfae1a004a54d 100644
--- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.gen.ts
+++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.gen.ts
@@ -56,6 +56,8 @@ export const AlertsInsightsPostRequestBody = z.object({
anonymizationFields: z.array(AnonymizationFieldResponse),
connectorId: z.string(),
actionTypeId: z.string(),
+ langSmithProject: z.string().optional(),
+ langSmithApiKey: z.string().optional(),
model: z.string().optional(),
replacements: Replacements.optional(),
size: z.number(),
diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.schema.yaml
index 5ab58c6023ee5..a4a647784ee29 100644
--- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.schema.yaml
+++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/insights/alerts/post_alerts_insights_route.schema.yaml
@@ -73,6 +73,10 @@ paths:
type: string
actionTypeId:
type: string
+ langSmithProject:
+ type: string
+ langSmithApiKey:
+ type: string
model:
type: string
replacements:
diff --git a/x-pack/plugins/elastic_assistant/server/routes/insights/alerts/post_alerts_insights.ts b/x-pack/plugins/elastic_assistant/server/routes/insights/alerts/post_alerts_insights.ts
index 455f50703e836..e58e4cd0f8e20 100644
--- a/x-pack/plugins/elastic_assistant/server/routes/insights/alerts/post_alerts_insights.ts
+++ b/x-pack/plugins/elastic_assistant/server/routes/insights/alerts/post_alerts_insights.ts
@@ -19,6 +19,7 @@ import { transformError } from '@kbn/securitysolution-es-utils';
import { INSIGHTS_ALERTS } from '../../../../common/constants';
import { getAssistantToolParams, isInsightsFeatureEnabled } from './helpers';
import { DEFAULT_PLUGIN_NAME, getPluginNameFromRequest } from '../../helpers';
+import { getLangSmithTracer } from '../../evaluate/utils';
import { buildResponse } from '../../../lib/build_response';
import { ElasticAssistantRequestHandlerContext } from '../../../types';
import { getLlmType } from '../../utils';
@@ -73,7 +74,14 @@ export const postAlertsInsightsRoute = (router: IRouter
Date: Thu, 18 Apr 2024 15:19:48 -0400
Subject: [PATCH 21/96] skip failing test suite (#180982)
---
.../test_suites/observability/slos/delete_slo.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/slos/delete_slo.ts b/x-pack/test_serverless/api_integration/test_suites/observability/slos/delete_slo.ts
index 59d1faa8d4c10..c56b8255b4111 100644
--- a/x-pack/test_serverless/api_integration/test_suites/observability/slos/delete_slo.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/observability/slos/delete_slo.ts
@@ -47,7 +47,8 @@ export default function ({ getService }: FtrProviderContext) {
return {};
}
};
- describe('delete_slo', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/180982
+ describe.skip('delete_slo', () => {
// DATE_VIEW should match the index template:
// x-pack/packages/kbn-infra-forge/src/data_sources/composable/template.json
const DATE_VIEW = 'kbn-data-forge-fake_hosts';
From 1a83533ef12a3a311d287185a48f1aedbcd444ac Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Thu, 18 Apr 2024 20:21:37 +0100
Subject: [PATCH 22/96] skip flaky suite (#164032)
---
.../server/integration_tests/task_state_validation.test.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts b/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts
index a4ffa7514e02c..75e5c9d942398 100644
--- a/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts
+++ b/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts
@@ -258,7 +258,8 @@ describe('task state validation', () => {
});
});
- describe('allow_reading_invalid_state: false', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/164032
+ describe.skip('allow_reading_invalid_state: false', () => {
const taskIdsToRemove: string[] = [];
let esServer: TestElasticsearchUtils;
let kibanaServer: TestKibanaUtils;
From 98a73920a9ed495862a83efa45643ca4166fa5b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Thu, 18 Apr 2024 21:25:49 +0200
Subject: [PATCH 23/96] [EDR Workflows][chore] Fix `load_agent_policies` script
for cloud and serverless (#181183)
## Summary
For the script
`x-pack/plugins/security_solution/scripts/endpoint/load_agent_policies.js`:
- fixes the `self-signed certificate in certificate chain` error when
using with cloud environment (by simply *not* using certificate)
- adds a new option called `apikey` which makes it possible to use with
serverless environment
---
.../endpoint/agent_policy_generator/index.ts | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_policy_generator/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_policy_generator/index.ts
index f8677694bed34..104bf337be5d3 100644
--- a/x-pack/plugins/security_solution/scripts/endpoint/agent_policy_generator/index.ts
+++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_policy_generator/index.ts
@@ -38,13 +38,15 @@ export const cli = () => {
kibana: 'http://127.0.0.1:5601',
username: 'elastic',
password: 'changeme',
+ apikey: undefined,
},
help: `
- --count Number of agent policies to create. Default: 10
- --concurrency Number of concurrent agent policies can be created. Default: 10
- --kibana The URL to kibana including credentials. Default: http://127.0.0.1:5601
- --username The username to use for authentication. Default: elastic
- --password The password to use for authentication. Default: changeme
+ --count Number of agent policies to create. Default: 10
+ --concurrency Number of concurrent agent policies can be created. Default: 10
+ --kibana The URL to kibana including credentials. Default: http://127.0.0.1:5601
+ --username The username to use for authentication for local or cloud environment. Default: elastic
+ --password The password to use for authentication for local or cloud environment. Default: changeme
+ --apikey API key for authenticating for Serverless environment. Default: -
`,
},
}
@@ -56,6 +58,9 @@ const agentPolicyGenerator: RunFn = async ({ flags, log }) => {
url: flags.kibana as string,
username: flags.username as string,
password: flags.password as string,
+ apiKey: flags.apikey as string,
+ noCertForSsl: true,
+ log,
});
const totalPoliciesCount = flags.count as unknown as number;
From fec1df1c46d4ecc89fd94d3f9dc7db35152f150a Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Thu, 18 Apr 2024 20:58:16 +0100
Subject: [PATCH 24/96] skip flaky suite (#181102)
---
.../public/hooks/use_source_indices_fields.test.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx
index 754002f21d568..a066c33e412ca 100644
--- a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx
+++ b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx
@@ -20,7 +20,8 @@ let formHookSpy: jest.SpyInstance;
import { getIndicesWithNoSourceFields, useSourceIndicesFields } from './use_source_indices_field';
import { IndicesQuerySourceFields } from '../types';
-describe('useSourceIndicesFields Hook', () => {
+// FLAKY: https://github.com/elastic/kibana/issues/181102
+describe.skip('useSourceIndicesFields Hook', () => {
let postMock: jest.Mock;
const wrapper = ({ children }: { children: React.ReactNode }) => (
From 8f23f23f4d3263d2879df70446010f15359f8ebe Mon Sep 17 00:00:00 2001
From: Alexi Doak <109488926+doakalexi@users.noreply.github.com>
Date: Thu, 18 Apr 2024 15:40:59 -0700
Subject: [PATCH 25/96] [ResponseOps] Fix for flaky task state validation jest
integration test (#181206)
Resolves https://github.com/elastic/kibana/issues/164032#
## Summary
This PR unskips a flaky test caused by a race condition with a TM health
status log. We use a regex to find the log we care about instead of just
comparing the first log to the expected message.
### To verify
- Run the following command to run the test, and verify that it passes
```
node scripts/jest_integration.js x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts
```
---
.../integration_tests/task_state_validation.test.ts | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts b/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts
index 75e5c9d942398..7f1d9d5e4aae9 100644
--- a/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts
+++ b/x-pack/plugins/task_manager/server/integration_tests/task_state_validation.test.ts
@@ -258,8 +258,7 @@ describe('task state validation', () => {
});
});
- // FLAKY: https://github.com/elastic/kibana/issues/164032
- describe.skip('allow_reading_invalid_state: false', () => {
+ describe('allow_reading_invalid_state: false', () => {
const taskIdsToRemove: string[] = [];
let esServer: TestElasticsearchUtils;
let kibanaServer: TestKibanaUtils;
@@ -327,9 +326,11 @@ describe('task state validation', () => {
taskIdsToRemove.push(id);
await retry(async () => {
- expect(logSpy.mock.calls[0][0]).toBe(
- `Task (fooType/${id}) has a validation error: [foo]: expected value of type [string] but got [boolean]`
- );
+ const calls = logSpy.mock.calls as string[][];
+ const expected =
+ /^Task \(fooType\/.*\) has a validation error: \[foo\]: expected value of type \[string\] but got \[boolean\]/;
+ const found = calls.map((arr) => arr[0]).find((message) => message.match(expected) != null);
+ expect(found).toMatch(expected);
expect(updateSpy).toHaveBeenCalledWith(
expect.arrayContaining([expect.objectContaining({ id, taskType: 'fooType' })]),
{ validate: false }
From cbc68cd40ceff582083c2cf0514c29270ffe496b Mon Sep 17 00:00:00 2001
From: Cee Chen <549407+cee-chen@users.noreply.github.com>
Date: Thu, 18 Apr 2024 16:05:48 -0700
Subject: [PATCH 26/96] Upgrade EUI to v94.1.0 (major `EuiTable` refactors)
(#180514)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`v93.6.0` ⏩ `v94.1.0`
> [!important]
> 👋 Hello everyone - this is a special release containing `EuiTable`'s
conversion to Emotion, several long-overdue cleanups and breaking
changes, and one or two fun new features. First, let's address the big
questions:
### Q: I'm listed as a codeowner, how much should I manually QA/review?
Answer: It depends on what exactly in your code changed, but _in
general_ I would strongly suggest at least pulling this branch down and
doing a quick visual smoke test of all tables (_note: **not**
datagrids_) in your apps/plugins. You should not expect to see any major
visual regressions.
If your table contained any kind of custom styling or behavior (e.g.
custom CSS, etc.) I **strongly** recommend taking more time to QA
thoroughly to ensure your table still looks and behaves as expected.
Teams with very manual or specific updates will be flagged below in
comment threads.
### Q: When do I need to review by?
This PR will be merged **after** 8.14FF. Because this upgrade touches so
many files and teams, we're aiming for asking for an admin merge by EOD
4/18 regardless of full approval status.
As always, you're welcome to ping us after merge if you find any issues
later ([see our
FAQ](https://github.com/elastic/eui/blob/main/wiki/eui-team-processes/upgrading-kibana.md#faq-for-kibana-teams)),
as you will have until 8.15FF to catch any bugs.
### Q: What breaking changes were made, and why?
Here's a quick shortlist of all the changes made that affected the
majority of the commits in this PR:
#### Removed several top-level table props
- The `responsive` prop has been removed in favor of the new
`responsiveBreakpoint` prop (same `false` behavior as before)
- The following props were removed from basic and in-memory tables:
- `hasActions`, `isSelectable`, and `isExpandable`
- These props were not used for functionality and were only used for
styling tables in mobile/responsive views, which is not a best practice
pattern we wanted for our APIs. Mobile tables are now styled correctly
without needing consumers to pass these extra props.
- `textOnly`
- This prop was unused and had no meaningful impact on tables or table
content.
#### Removed hidden mobile vs. desktop DOM
Previously, EUI would set classes that applied `display: none` CSS for
content that was hidden for mobile vs. desktop. This is no longer the
case, and content that only displays for mobile or only displays for
desktop will no longer render to the DOM at all if the table is not in
that responsive state.
This is both more performant when rendering large quantities of
cells/content, and simpler to write test assertions for when comparing
what the user actually sees vs. what the DOM ‘sees’.
(c3eeb08441e4b6efe6505e7cddaa87b540ddb259,
78cefcd954a7b46eaccd05e431b5e24dc86071a3)
#### Removed direct usages of table `className`s
EuiTable `classNames` no longer have any styles attached to them, so
some instances where Kibana usages were applying the `className` for
styles have been replaced with direct component usage or removed
entirely (86ce80b61f92137df761d06a1d5f492d9f738dd7).
#### Custom table cell styles
Any custom CSS for table cells was previously being applied to the inner
`div.euiTableCellContent` wrapper. As of this latest release, the
`className` and `css` props will now be applied directly to the outer
`td.euiTableRowCell` element. If you were targeting custom styles table
cells, please re-QA your styles to ensure everything still looks as
expected.
---
Full changelog (click to collapse)
##
[`v94.1.0-backport.0`](https://github.com/elastic/eui/releases/v94.1.0-backport.0)
**This is a backport release only intended for use by Kibana.**
**Bug fixes**
- Fixed a visual text alignment regression in `EuiTableRowCell`s with
the `row` header scope
([#7681](https://github.com/elastic/eui/pull/7681))
**Accessibility**
- Improved `EuiBasicTable` and `EuiInMemoryTable`'s selection checkboxes
to have unique aria-labels per row
([#7672](https://github.com/elastic/eui/pull/7672))
## [`v94.1.0`](https://github.com/elastic/eui/releases/v94.1.0)
- Updated `EuiTableHeaderCell` to show a subdued `sortable` icon for
columns that are not currently sorted but can be
([#7656](https://github.com/elastic/eui/pull/7656))
- Updated `EuiBasicTable` and `EuiInMemoryTable`'s
`columns[].actions[]`'s to pass back click events to `onClick` callbacks
as the second callback
([#7667](https://github.com/elastic/eui/pull/7667))
## [`v94.0.0`](https://github.com/elastic/eui/releases/v94.0.0)
- Updated `EuiTable`, `EuiBasicTable`, and `EuiInMemoryTable` with a new
`responsiveBreakpoint` prop, which allows customizing the point at which
the table collapses into a mobile-friendly view with cards
([#7625](https://github.com/elastic/eui/pull/7625))
- Updated `EuiProvider`'s `componentDefaults` prop to allow configuring
`EuiTable.responsiveBreakpoint`
([#7625](https://github.com/elastic/eui/pull/7625))
**Bug fixes**
- `EuiBasicTable` & `EuiInMemoryTable` `isPrimary` actions are now
correctly shown on mobile views
([#7640](https://github.com/elastic/eui/pull/7640))
- Table `mobileOptions`:
([#7642](https://github.com/elastic/eui/pull/7642))
- `mobileOptions.align` is now respected instead of all cells being
forced to left alignment
- `textTruncate` and `textOnly` are now respected even if a `render`
function is not passed
**Breaking changes**
- Removed unused `EuiTableHeaderButton` component
([#7621](https://github.com/elastic/eui/pull/7621))
- Removed the `responsive` prop from `EuiTable`, `EuiBasicTable`, and
`EuiInMemoryTable`. Use the new `responsiveBreakpoint` prop instead
([#7625](https://github.com/elastic/eui/pull/7625))
- The following props are no longer needed by `EuiBasicTable` or
`EuiInMemoryTable` for responsive table behavior to work correctly, and
can be removed: ([#7632](https://github.com/elastic/eui/pull/7632))
- `isSelectable`
- `isExpandable`
- `hasActions`
- Removed the `showOnHover` prop from `EuiTableRowCell` /
`EuiBasicTable`/`EuiInMemoryTable`'s `columns` API. Use the new actions
`columns[].actions[].showOnHover` API instead.
([#7640](https://github.com/elastic/eui/pull/7640))
- Removed top-level `textOnly` prop from `EuiBasicTable` and
`EuiInMemoryTable`. Use `columns[].textOnly` instead.
([#7642](https://github.com/elastic/eui/pull/7642))
**DOM changes**
- `EuiTable` mobile headers no longer render in the DOM when not visible
(previously rendered with `display: none`). This may affect DOM testing
assertions. ([#7625](https://github.com/elastic/eui/pull/7625))
- `EuiTableRowCell` now applies passed `className`s to the parent ``
element, instead of to the inner cell content ``.
([#7631](https://github.com/elastic/eui/pull/7631))
- `EuiTableRow`s rendered by basic and memory tables now only render a
`.euiTableRow-isSelectable` className if the selection checkbox is not
disabled ([#7632](https://github.com/elastic/eui/pull/7632))
- `EuiTableRowCell`s with `textOnly` set to `false` will no longer
attempt to apply the `.euiTableCellContent__text` className to child
elements. ([#7641](https://github.com/elastic/eui/pull/7641))
- `EuiTableRowCell` no longer renders mobile headers to the DOM unless
the current table is displaying its responsive view.
([#7642](https://github.com/elastic/eui/pull/7642))
- `EuiTableHeaderCell` and `EuiTableRowCell` will no longer render in
the DOM at all on mobile if their columns' `mobileOptions.show` is set
to `false`. ([#7642](https://github.com/elastic/eui/pull/7642))
- `EuiTableHeaderCell` and `EuiTableRowCell` will no longer render in
the DOM at all on desktop if their columns' `mobileOptions.only` is set
to `true`. ([#7642](https://github.com/elastic/eui/pull/7642))
**CSS-in-JS conversions**
- Converted `EuiTable`, `EuiTableRow`, `EuiTableRowCell`, and all other
table subcomponents to Emotion
([#7654](https://github.com/elastic/eui/pull/7654))
- Removed the following `EuiTable` Sass variables:
([#7654](https://github.com/elastic/eui/pull/7654))
- `$euiTableCellContentPadding`
- `$euiTableCellContentPaddingCompressed`
- `$euiTableCellCheckboxWidth`
- `$euiTableHoverColor`
- `$euiTableSelectedColor`
- `$euiTableHoverSelectedColor`
- `$euiTableActionsBorderColor`
- `$euiTableHoverClickableColor`
- `$euiTableFocusClickableColor`
- Removed the following `EuiTable` Sass mixins:
([#7654](https://github.com/elastic/eui/pull/7654))
- `euiTableActionsBackgroundMobile`
- `euiTableCellCheckbox`
- `euiTableCell`
---
.../public/app.tsx | 12 +-
examples/field_formats_example/public/app.tsx | 53 +--
.../files_example/public/components/app.tsx | 5 +-
.../public/app/app.tsx | 62 ++--
package.json | 2 +-
.../src/components/table.tsx | 1 -
.../__snapshots__/status_table.test.tsx.snap | 2 -
.../src/status/components/status_table.tsx | 1 -
.../__snapshots__/i18n_service.test.tsx.snap | 2 +-
.../src/i18n_eui_mapping.tsx | 10 +-
.../documents_panel.test.tsx.snap | 1 -
.../__snapshots__/events_panel.test.tsx.snap | 2 -
.../pipeline_panel.test.tsx.snap | 1 -
.../components/sync_jobs/sync_jobs_table.tsx | 1 -
.../src/testbed/testbed.ts | 4 +-
.../src/query_history.test.tsx | 2 +-
.../src/query_history.tsx | 2 +-
.../field_stats/field_number_summary.tsx | 2 +-
src/dev/license_checker/config.ts | 2 +-
.../components/table/table.test.tsx | 15 +-
.../__snapshots__/data_view.test.tsx.snap | 314 +++++++----------
.../__snapshots__/indices_list.test.tsx.snap | 12 +-
.../indices_list/indices_list.tsx | 2 +-
.../color/__snapshots__/color.test.tsx.snap | 3 -
.../__snapshots__/static_lookup.test.tsx.snap | 2 -
.../url/__snapshots__/url.test.tsx.snap | 256 ++++++--------
.../__snapshots__/samples.test.tsx.snap | 1 -
.../table/__snapshots__/table.test.tsx.snap | 2 -
.../table/__snapshots__/table.test.tsx.snap | 1 -
.../table/__snapshots__/table.test.tsx.snap | 1 -
.../delete_modal_msg.test.tsx.snap | 4 -
.../index_pattern_table.tsx | 1 -
.../syntax_suggestions_popover.tsx | 1 -
.../clusters_table/clusters_table.test.tsx | 18 +-
.../clusters_table/clusters_table.tsx | 1 -
.../shards_view/shard_failure_table.tsx | 1 -
.../components/details/req_details_stats.tsx | 2 +-
.../__snapshots__/flyout.test.tsx.snap | 1 -
.../__snapshots__/relationships.test.tsx.snap | 6 -
.../__snapshots__/table.test.tsx.snap | 6 +-
.../components/delete_confirm_modal.test.tsx | 4 +-
.../objects_table/components/table.tsx | 2 +-
.../drilldown_table/drilldown_table.tsx | 4 +-
.../drilldown_template_table.tsx | 6 +-
.../doc_viewer_table/legacy/table.tsx | 3 +-
.../components/doc_viewer_table/table.scss | 12 +-
.../components/doc_viewer_table/table.tsx | 2 +-
test/functional/services/inspector.ts | 2 +-
test/functional/services/listing_table.ts | 10 +-
.../context_editor/index.tsx | 1 -
.../summary_table/helpers.test.tsx | 2 +-
.../summary_table/helpers.tsx | 2 +-
.../summary_table/index.tsx | 1 -
.../change_points_table.tsx | 7 +-
.../category_table/category_table.tsx | 2 -
.../components/maintenance_windows_list.tsx | 1 -
.../components/var_config/var_config.tsx | 1 -
.../all_cases/all_cases_list.test.tsx | 7 -
.../public/components/all_cases/table.tsx | 2 -
.../dashboard_sections/benchmarks_section.tsx | 2 +-
.../auto_follow_pattern_table.js | 1 -
.../follower_indices_table.js | 1 -
.../field_types_help_popover.tsx | 2 +-
.../data_visualizer_stats_table.tsx | 4 +-
.../data_drift/data_drift_overview_table.tsx | 1 -
.../analytics_tables/analytics_table.tsx | 2 -
.../analytics_tables/query_clicks_table.tsx | 1 -
.../analytics_tables/recent_queries_table.tsx | 2 -
.../api_logs/components/api_logs_table.tsx | 1 -
.../crawler/components/domains_table.test.tsx | 8 +-
.../curations/components/curations_table.tsx | 2 -
.../components/suggestions_table.tsx | 2 -
.../ignored_queries_panel.tsx | 1 -
.../tables/test_helpers/shared_columns.tsx | 8 +-
.../non_text_fields_body.test.tsx | 18 +-
.../result_settings_table.tsx | 2 +-
.../text_fields_body.test.tsx | 18 +-
.../search_application_schema.tsx | 5 +-
.../extraction_rules_table.tsx | 1 -
.../domain_management/domains_table.test.tsx | 6 +-
.../role_mapping/role_mappings_table.tsx | 2 +-
.../shared/sources_table/sources_table.tsx | 2 +-
.../group_source_prioritization.tsx | 2 +-
.../package_policies_table.tsx | 1 -
.../sections/agent_policy/list_page/index.tsx | 2 -
.../components/agent_list_table.tsx | 2 -
.../enrollment_token_list_page/index.tsx | 1 -
.../uninstall_token_list_page/index.tsx | 1 -
.../sections/data_stream/list_page/index.tsx | 1 -
.../detail/overview/markdown_renderers.tsx | 4 +-
.../__jest__/policy_table.test.tsx | 6 +-
.../__jest__/components/index_table.test.js | 2 +-
.../component_template_list/table.tsx | 1 -
.../field_parameters/relations_parameter.tsx | 1 -
.../data_stream_table/data_stream_table.tsx | 1 -
.../policies_table/policies_table.tsx | 1 -
.../index_list/index_table/index_table.js | 16 +-
.../template_table/template_table.tsx | 1 -
.../template_table/template_table.tsx | 1 -
.../sections/pipelines_list/table.tsx | 1 -
.../dimension_panel/dimension_editor.tsx | 2 +-
.../pipelines_table.test.js.snap | 2 -
.../pipeline_list/pipelines_table.js | 1 -
.../custom_selection_table.js | 2 +-
.../analytics_list/analytics_list.tsx | 3 -
.../analytics_id_selector.tsx | 2 -
.../components/jobs_list/jobs_list.js | 13 +-
.../advanced_detector_modal/function_help.tsx | 2 +-
.../nodes_overview/allocated_models.tsx | 3 -
.../nodes_overview/nodes_list.tsx | 3 -
.../model_management/models_list.tsx | 3 -
.../pipelines/expanded_row.tsx | 3 -
.../components/notifications_list.tsx | 3 -
.../components/analytics_panel/table.tsx | 3 -
.../anomaly_detection_panel/table.tsx | 3 -
.../__snapshots__/events_table.test.js.snap | 2 -
.../table/__snapshots__/table.test.js.snap | 2 -
.../settings/calendars/list/table/table.js | 1 -
.../list/__snapshots__/table.test.js.snap | 4 -
.../settings/filter_lists/list/table.js | 1 -
.../__snapshots__/latest_active.test.js.snap | 1 -
.../__snapshots__/latest_types.test.js.snap | 1 -
.../latest_versions.test.js.snap | 1 -
.../ccr/__snapshots__/ccr.test.js.snap | 1 -
.../logs/__snapshots__/logs.test.js.snap | 1 -
.../service_overview_errors_table/index.tsx | 17 +-
.../shared/overview_table_container/index.tsx | 5 +-
.../dataset_quality/table/table.tsx | 1 -
.../tabs/metadata/add_pin_to_row.tsx | 21 +-
.../asset_details/tabs/metadata/table.tsx | 5 +-
.../tabs/processes/processes_table.tsx | 5 +-
.../anomalies_table/anomalies_table.tsx | 1 -
.../sections/anomalies/table.tsx | 2 -
.../metrics/hosts/components/hosts_table.tsx | 3 +-
.../metrics/hosts/hooks/use_hosts_table.tsx | 6 +-
.../components/message_panel/message_text.tsx | 31 +-
.../journeys/private_locations.journey.ts | 8 +-
.../journeys/test_now_mode.journey.ts | 4 +-
.../browser_steps_list.tsx | 6 +-
.../monitor_summary/test_runs_table.tsx | 4 +-
.../monitor_list_table/monitor_list.tsx | 9 +-
.../settings/global_params/params_list.tsx | 1 -
.../waterfall_flyout_table.tsx | 4 +-
.../simple/ping_list/ping_list_table.tsx | 4 +-
.../monitor/ping_list/ping_list_table.tsx | 4 +-
.../availability_reporting.tsx | 2 +-
.../components/waterfall_flyout_table.tsx | 4 +-
.../overview/monitor_list/monitor_list.tsx | 4 +-
.../check_steps/steps_list.test.tsx | 27 +-
.../synthetics/check_steps/steps_list.tsx | 4 +-
.../legacy_uptime/lib/helper/rtl_helpers.tsx | 35 --
.../impactful_metrics/js_errors.tsx | 2 +-
.../cypress/e2e/all/packs_create_edit.cy.ts | 2 +-
.../form/pack_queries_status_table.tsx | 1 -
.../packs/pack_queries_status_table.tsx | 1 -
.../public/packs/pack_queries_table.tsx | 2 +-
.../remote_cluster_table.js | 1 -
.../management/report_listing_table.tsx | 1 -
.../message_list/citations_table.tsx | 1 -
.../sources_panel/indices_table.tsx | 1 -
.../api_keys_grid/api_keys_grid_page.tsx | 1 -
.../role_mappings_grid_page.test.tsx | 34 +-
.../role_mappings_grid_page.tsx | 176 ++++------
.../privilege_space_table.tsx | 1 -
.../roles/roles_grid/roles_grid_page.test.tsx | 22 +-
.../roles/roles_grid/roles_grid_page.tsx | 243 ++++++--------
.../public/management/table_utils.tsx | 37 --
.../users/users_grid/users_grid_page.tsx | 1 -
.../get_comments/stream/message_text.tsx | 31 +-
.../__snapshots__/index.test.tsx.snap | 3 -
.../event_fields_browser.test.tsx | 16 +-
.../ml_popover/jobs_table/jobs_table.tsx | 2 +-
.../execution_log_table.tsx | 1 -
.../linked_to_list/index.tsx | 1 -
.../linked_to_rule/index.test.tsx | 2 +-
.../add_prebuilt_rules_table.tsx | 1 -
.../components/rules_table/rules_tables.tsx | 1 -
.../upgrade_prebuilt_rules_table.tsx | 1 -
.../execution_events_table.tsx | 1 -
.../alerts_count_panel/alerts_count.test.tsx | 10 +-
.../alerts_count_panel/alerts_count.tsx | 8 +-
.../entity_analytics_anomalies/index.test.tsx | 2 +-
.../entity_analytics_anomalies/index.tsx | 2 +-
.../tabs/risk_inputs/risk_inputs.test.tsx | 4 +-
.../tabs/risk_inputs/risk_inputs_tab.tsx | 3 +-
.../components/risk_score_preview_table.tsx | 3 +-
.../risk_summary_flyout/risk_summary.test.tsx | 17 +-
.../risk_summary_flyout/risk_summary.tsx | 2 +-
.../authentications_host_table.test.tsx.snap | 316 +++++++++---------
.../authentications_user_table.test.tsx.snap | 316 +++++++++---------
.../uncommon_process_table/index.test.tsx | 26 +-
.../components/actions_log_table.tsx | 1 -
.../public/resolver/view/panel.test.tsx | 71 ++--
.../open_timeline/timelines_table/index.tsx | 2 -
.../row_renderers_browser.tsx | 1 -
.../policy_list/policy_table/policy_table.tsx | 1 -
.../repository_table/repository_table.tsx | 1 -
.../restore_table/restore_table.tsx | 1 -
.../components/snapshot_table.tsx | 1 -
.../spaces_grid/spaces_grid_page.tsx | 1 -
.../transform_list/transform_list.tsx | 3 -
.../translations/translations/fr-FR.json | 1 -
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
.../components/field_items/field_items.tsx | 7 -
.../components/field_items/index.ts | 2 +-
.../components/field_table/field_table.tsx | 4 +-
.../components/rule_error_log.test.tsx | 2 +-
.../components/rules_list_table.tsx | 1 -
.../watch_list_page/watch_list_page.tsx | 1 -
.../page_objects/tag_management_page.ts | 2 +-
.../services/ml/data_frame_analytics_table.ts | 8 +-
.../services/ml/stack_management_jobs.ts | 2 +-
yarn.lock | 8 +-
214 files changed, 1068 insertions(+), 1656 deletions(-)
delete mode 100644 x-pack/plugins/security/public/management/table_utils.tsx
diff --git a/examples/data_view_field_editor_example/public/app.tsx b/examples/data_view_field_editor_example/public/app.tsx
index 71e10634cd818..336c29aa2a1ca 100644
--- a/examples/data_view_field_editor_example/public/app.tsx
+++ b/examples/data_view_field_editor_example/public/app.tsx
@@ -7,6 +7,7 @@
*/
import {
+ EuiProvider,
DefaultItemAction,
EuiButton,
EuiCheckbox,
@@ -121,7 +122,6 @@ const DataViewFieldEditorExample = ({ dataView, dataViewFieldEditor }: Props) =>
items={fields}
columns={columns}
pagination={true}
- hasActions={true}
sorting={{
sort: {
field: 'name',
@@ -135,10 +135,12 @@ const DataViewFieldEditorExample = ({ dataView, dataViewFieldEditor }: Props) =>
);
return (
-
-
- {content}
-
+
+
+
+ {content}
+
+
);
};
diff --git a/examples/field_formats_example/public/app.tsx b/examples/field_formats_example/public/app.tsx
index 6aa2f2d5e6c75..2961925f0a160 100644
--- a/examples/field_formats_example/public/app.tsx
+++ b/examples/field_formats_example/public/app.tsx
@@ -14,6 +14,7 @@ import {
EuiCodeBlock,
EuiLink,
EuiPageTemplate,
+ EuiProvider,
EuiSpacer,
EuiText,
EuiTitle,
@@ -61,7 +62,6 @@ const UsingAnExistingFieldFormatExample: React.FC<{ deps: Deps }> = (props) => {
= (props) => {
= (props) => {
export const App: React.FC<{ deps: Deps }> = (props) => {
return (
-
-
-
-
- Using an existing field format
-
-
-
-
-
-
- Creating a custom field format
-
-
-
-
-
-
- Creating a custom field format editor
-
-
-
-
-
+
+
+
+
+
+ Using an existing field format
+
+
+
+
+
+
+ Creating a custom field format
+
+
+
+
+
+
+ Creating a custom field format editor
+
+
+
+
+
+
);
};
diff --git a/examples/files_example/public/components/app.tsx b/examples/files_example/public/components/app.tsx
index db0968d7b43f2..5fea8c5fbc42d 100644
--- a/examples/files_example/public/components/app.tsx
+++ b/examples/files_example/public/components/app.tsx
@@ -12,6 +12,7 @@ import type { FileJSON } from '@kbn/files-plugin/common';
import type { FilesClientResponses } from '@kbn/files-plugin/public';
import {
+ EuiProvider,
EuiPageTemplate,
EuiInMemoryTable,
EuiInMemoryTableProps,
@@ -131,7 +132,7 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
];
return (
- <>
+
@@ -185,6 +186,6 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
}}
/>
)}
- >
+
);
};
diff --git a/examples/partial_results_example/public/app/app.tsx b/examples/partial_results_example/public/app/app.tsx
index 333ce7f6c263e..fb0fe9e3dbef4 100644
--- a/examples/partial_results_example/public/app/app.tsx
+++ b/examples/partial_results_example/public/app/app.tsx
@@ -9,6 +9,7 @@
import React, { useContext, useEffect, useState } from 'react';
import { pluck } from 'rxjs';
import {
+ EuiProvider,
EuiBasicTable,
EuiCallOut,
EuiCodeBlock,
@@ -40,35 +41,36 @@ export function App() {
}, [expressions]);
return (
-
-
-
-
-
- This example listens for the window events and adds them to the table along with a
- trigger counter.
-
-
-
- {expression}
-
- {datatable ? (
- ({
- field,
- name,
- 'data-test-subj': `example-column-${field.toLowerCase()}`,
- }))}
- items={datatable.rows ?? []}
- />
- ) : (
-
- Click or press any key.
-
- )}
-
-
+
+
+
+
+
+
+ This example listens for the window events and adds them to the table along with a
+ trigger counter.
+
+
+
+ {expression}
+
+ {datatable ? (
+ ({
+ field,
+ name,
+ 'data-test-subj': `example-column-${field.toLowerCase()}`,
+ }))}
+ items={datatable.rows ?? []}
+ />
+ ) : (
+
+ Click or press any key.
+
+ )}
+
+
+
);
}
diff --git a/package.json b/package.json
index 19f84d4203033..f23061e31e692 100644
--- a/package.json
+++ b/package.json
@@ -107,7 +107,7 @@
"@elastic/ecs": "^8.11.1",
"@elastic/elasticsearch": "^8.13.0",
"@elastic/ems-client": "8.5.1",
- "@elastic/eui": "93.6.0",
+ "@elastic/eui": "94.1.0-backport.0",
"@elastic/filesaver": "1.1.2",
"@elastic/node-crypto": "1.2.1",
"@elastic/numeral": "^2.5.1",
diff --git a/packages/content-management/table_list_view_table/src/components/table.tsx b/packages/content-management/table_list_view_table/src/components/table.tsx
index 42bd676343362..a459bc26ede50 100644
--- a/packages/content-management/table_list_view_table/src/components/table.tsx
+++ b/packages/content-management/table_list_view_table/src/components/table.tsx
@@ -242,7 +242,6 @@ export function Table({
data-test-subj="itemsInMemTable"
rowHeader="attributes.title"
tableCaption={tableCaption}
- isSelectable
/>
);
}
diff --git a/packages/core/apps/core-apps-browser-internal/src/status/components/__snapshots__/status_table.test.tsx.snap b/packages/core/apps/core-apps-browser-internal/src/status/components/__snapshots__/status_table.test.tsx.snap
index cb10255eb9998..934027aa35ea7 100644
--- a/packages/core/apps/core-apps-browser-internal/src/status/components/__snapshots__/status_table.test.tsx.snap
+++ b/packages/core/apps/core-apps-browser-internal/src/status/components/__snapshots__/status_table.test.tsx.snap
@@ -38,7 +38,6 @@ exports[`StatusTable renders when statuses is provided 1`] = `
]
}
data-test-subj="statusBreakdown"
- isExpandable={true}
itemId={[Function]}
itemIdToExpandedRowMap={Object {}}
items={
@@ -58,7 +57,6 @@ exports[`StatusTable renders when statuses is provided 1`] = `
},
]
}
- responsive={true}
rowProps={[Function]}
searchFormat="eql"
sorting={
diff --git a/packages/core/apps/core-apps-browser-internal/src/status/components/status_table.tsx b/packages/core/apps/core-apps-browser-internal/src/status/components/status_table.tsx
index 37833ebfde923..977dd3efb3e0e 100644
--- a/packages/core/apps/core-apps-browser-internal/src/status/components/status_table.tsx
+++ b/packages/core/apps/core-apps-browser-internal/src/status/components/status_table.tsx
@@ -104,7 +104,6 @@ export const StatusTable: FunctionComponent = ({ statuses }) =
columns={tableColumns}
itemId={(item) => item.id}
items={statuses}
- isExpandable={true}
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
rowProps={({ state }) => ({
className: `status-table-row-${state.uiColor}`,
diff --git a/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap b/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap
index 44170ebcfb06e..d9fc8ecec8050 100644
--- a/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap
+++ b/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap
@@ -14,7 +14,7 @@ exports[`#start() returns \`Context\` component 1`] = `
"euiAutoRefresh.buttonLabelOn": [Function],
"euiBasicTable.noItemsMessage": "No items found",
"euiBasicTable.selectAllRows": "Select all rows",
- "euiBasicTable.selectThisRow": "Select this row",
+ "euiBasicTable.selectThisRow": [Function],
"euiBasicTable.tableAutoCaptionWithPagination": [Function],
"euiBasicTable.tableAutoCaptionWithoutPagination": [Function],
"euiBasicTable.tableCaptionWithPagination": [Function],
diff --git a/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx b/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx
index e4f769c8779c7..3ea767bc5b6bc 100644
--- a/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx
+++ b/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx
@@ -38,10 +38,12 @@ export const getEuiContextMapping = (): EuiTokensObject => {
defaultMessage: 'Select all rows',
description: 'ARIA and displayed label on a checkbox to select all table rows',
}),
- 'euiBasicTable.selectThisRow': i18n.translate('core.euiBasicTable.selectThisRow', {
- defaultMessage: 'Select this row',
- description: 'ARIA and displayed label on a checkbox to select a single table row',
- }),
+ 'euiBasicTable.selectThisRow': ({ index }: EuiValues) =>
+ i18n.translate('core.euiBasicTable.selectThisRow', {
+ defaultMessage: 'Select row {index}',
+ values: { index },
+ description: 'ARIA and displayed label on a checkbox to select a single table row',
+ }),
'euiBasicTable.tableCaptionWithPagination': ({ tableCaption, page, pageCount }: EuiValues) =>
i18n.translate('core.euiBasicTable.tableCaptionWithPagination', {
defaultMessage: '{tableCaption}; Page {page} of {pageCount}.',
diff --git a/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/documents_panel.test.tsx.snap b/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/documents_panel.test.tsx.snap
index 40f115c567f81..66caa5b55f567 100644
--- a/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/documents_panel.test.tsx.snap
+++ b/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/documents_panel.test.tsx.snap
@@ -119,7 +119,6 @@ exports[`DocumentsPanel renders 1`] = `
token="euiBasicTable.noItemsMessage"
/>
}
- responsive={true}
tableLayout="fixed"
/>
diff --git a/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/events_panel.test.tsx.snap b/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/events_panel.test.tsx.snap
index d4bdda45ccc3b..8320547cb2107 100644
--- a/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/events_panel.test.tsx.snap
+++ b/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/events_panel.test.tsx.snap
@@ -54,7 +54,6 @@ exports[`EventsPanel renders 1`] = `
token="euiBasicTable.noItemsMessage"
/>
}
- responsive={true}
tableLayout="fixed"
/>
@@ -110,7 +109,6 @@ exports[`EventsPanel renders with some values missing 1`] = `
token="euiBasicTable.noItemsMessage"
/>
}
- responsive={true}
tableLayout="fixed"
/>
diff --git a/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/pipeline_panel.test.tsx.snap b/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/pipeline_panel.test.tsx.snap
index cddd85fc851b9..66d241be8d92c 100644
--- a/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/pipeline_panel.test.tsx.snap
+++ b/packages/kbn-search-connectors/components/sync_jobs/__snapshots__/pipeline_panel.test.tsx.snap
@@ -43,7 +43,6 @@ exports[`PipelinePanel renders 1`] = `
token="euiBasicTable.noItemsMessage"
/>
}
- responsive={true}
tableLayout="fixed"
/>
diff --git a/packages/kbn-search-connectors/components/sync_jobs/sync_jobs_table.tsx b/packages/kbn-search-connectors/components/sync_jobs/sync_jobs_table.tsx
index ffe2c26d636ed..87f3559c3fab6 100644
--- a/packages/kbn-search-connectors/components/sync_jobs/sync_jobs_table.tsx
+++ b/packages/kbn-search-connectors/components/sync_jobs/sync_jobs_table.tsx
@@ -263,7 +263,6 @@ export const SyncJobsTable: React.FC = ({
data-test-subj={`entSearchContent-index-${type}-syncJobs-table`}
items={syncJobs}
columns={columns}
- hasActions
onChange={onPaginate}
pagination={pagination}
tableLayout="fixed"
diff --git a/packages/kbn-test-jest-helpers/src/testbed/testbed.ts b/packages/kbn-test-jest-helpers/src/testbed/testbed.ts
index 284e8d557de6f..4559924c28aff 100644
--- a/packages/kbn-test-jest-helpers/src/testbed/testbed.ts
+++ b/packages/kbn-test-jest-helpers/src/testbed/testbed.ts
@@ -263,11 +263,11 @@ export function registerTestBed ({
reactWrapper: row,
- columns: row.find('.euiTableCellContent').map((col) => ({
+ columns: row.find('div.euiTableCellContent').map((col) => ({
reactWrapper: col,
// We can't access the td value with col.text() because
// eui adds an extra div in td on mobile => (.euiTableRowCell__mobileHeader)
- value: col.find('.euiTableCellContent').text(),
+ value: col.find('div.euiTableCellContent').text(),
})),
}));
diff --git a/packages/kbn-text-based-editor/src/query_history.test.tsx b/packages/kbn-text-based-editor/src/query_history.test.tsx
index 6ee970321a9ae..d1b356e31eaa1 100644
--- a/packages/kbn-text-based-editor/src/query_history.test.tsx
+++ b/packages/kbn-text-based-editor/src/query_history.test.tsx
@@ -159,7 +159,7 @@ describe('QueryHistory', () => {
/>
);
expect(screen.getByRole('table')).toHaveTextContent(
- 'Time ranRecent queriesLast durationTime ranMar. 25, 24 08:45:27Recent queriesfrom kibana_sample_data_flights | limit 10Last duration2ms'
+ 'Time ranRecent queriesLast durationMar. 25, 24 08:45:27from kibana_sample_data_flights | limit 102ms'
);
});
});
diff --git a/packages/kbn-text-based-editor/src/query_history.tsx b/packages/kbn-text-based-editor/src/query_history.tsx
index 3e597cd1ed86f..0759c7fd7e816 100644
--- a/packages/kbn-text-based-editor/src/query_history.tsx
+++ b/packages/kbn-text-based-editor/src/query_history.tsx
@@ -380,7 +380,7 @@ export function QueryHistory({
defaultMessage: 'Queries history table',
}
)}
- responsive={false}
+ responsiveBreakpoint={false}
items={historyItems}
columns={columns}
sorting={sorting}
diff --git a/packages/kbn-unified-field-list/src/components/field_stats/field_number_summary.tsx b/packages/kbn-unified-field-list/src/components/field_stats/field_number_summary.tsx
index 8e4cdc3d0ff88..5d573e926f5ba 100755
--- a/packages/kbn-unified-field-list/src/components/field_stats/field_number_summary.tsx
+++ b/packages/kbn-unified-field-list/src/components/field_stats/field_number_summary.tsx
@@ -88,7 +88,7 @@ export const FieldNumberSummary: React.FC = ({
columns={summaryTableColumns}
tableCaption={summaryTableTitle}
data-test-subj={`${dataTestSubject}-numberSummary`}
- responsive={false}
+ responsiveBreakpoint={false}
css={css`
& .euiTableHeaderCell {
${euiScreenReaderOnly()}
diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts
index e0e4b2f1376fa..c708ab543476d 100644
--- a/src/dev/license_checker/config.ts
+++ b/src/dev/license_checker/config.ts
@@ -86,7 +86,7 @@ export const LICENSE_OVERRIDES = {
'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts
'@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint
'@elastic/ems-client@8.5.1': ['Elastic License 2.0'],
- '@elastic/eui@93.6.0': ['SSPL-1.0 OR Elastic License 2.0'],
+ '@elastic/eui@94.1.0-backport.0': ['SSPL-1.0 OR Elastic License 2.0'],
'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry
'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary
'@bufbuild/protobuf@1.2.1': ['Apache-2.0'], // license (Apache-2.0 AND BSD-3-Clause)
diff --git a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx
index de96c865b90e0..6394deeab843b 100644
--- a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx
+++ b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx
@@ -100,8 +100,7 @@ describe('Background Search Session Management Table', () => {
);
});
- expect(table.find('thead th .euiTableCellContent__text').map((node) => node.text()))
- .toMatchInlineSnapshot(`
+ expect(table.find('thead th').map((node) => node.text())).toMatchInlineSnapshot(`
Array [
"App",
"Name",
@@ -136,12 +135,12 @@ describe('Background Search Session Management Table', () => {
expect(table.find('tbody td').map((node) => node.text())).toMatchInlineSnapshot(`
Array [
- "App",
- "Namevery background search Info",
- "# Searches0",
- "StatusExpired",
- "Created2 Dec, 2020, 00:19:32",
- "Expiration--",
+ "",
+ "very background search Info",
+ "0",
+ "Expired",
+ "2 Dec, 2020, 00:19:32",
+ "--",
"",
"",
]
diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
index eb243025df2f7..77df622eaddbb 100644
--- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
+++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
@@ -200,123 +200,79 @@ Array [
class="euiBasicTable insDataTableFormat__table eui-xScroll css-1f59z3t"
data-test-subj="inspectorTable"
>
-
- |