- {i18n.translate('xpack.observability.slos.slo.timeWindow.calendar', {
+ {i18n.translate('xpack.observability.slo.slo.timeWindow.calendar', {
defaultMessage: '{elapsed}/{total} days',
values: {
elapsed: Math.min(elapsedDurationInDays, totalDurationInDays),
@@ -65,27 +65,27 @@ export function SloTimeWindowBadge({ slo }: Props) {
function toDurationLabel(duration: number, durationUnit: string) {
switch (durationUnit) {
case 'd':
- return i18n.translate('xpack.observability.slos.slo.timeWindow.days', {
+ return i18n.translate('xpack.observability.slo.slo.timeWindow.days', {
defaultMessage: '{duration} days',
values: { duration },
});
case 'w':
- return i18n.translate('xpack.observability.slos.slo.timeWindow.weeks', {
+ return i18n.translate('xpack.observability.slo.slo.timeWindow.weeks', {
defaultMessage: '{duration} weeks',
values: { duration },
});
case 'M':
- return i18n.translate('xpack.observability.slos.slo.timeWindow.months', {
+ return i18n.translate('xpack.observability.slo.slo.timeWindow.months', {
defaultMessage: '{duration} months',
values: { duration },
});
case 'Q':
- return i18n.translate('xpack.observability.slos.slo.timeWindow.quarterss', {
+ return i18n.translate('xpack.observability.slo.slo.timeWindow.quarterss', {
defaultMessage: '{duration} quarters',
values: { duration },
});
case 'Y':
- return i18n.translate('xpack.observability.slos.slo.timeWindow.years', {
+ return i18n.translate('xpack.observability.slo.slo.timeWindow.years', {
defaultMessage: '{duration} years',
values: { duration },
});
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx
index 2d753000d7d5d..4a292bef8c14b 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_delete_confirmation_modal.tsx
@@ -52,7 +52,7 @@ export function SloDeleteConfirmationModal({
onCancel={onCancel}
onConfirm={handleConfirm}
>
- {i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.descriptionText', {
+ {i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.descriptionText', {
defaultMessage: "You can't recover {name} after deleting.",
values: { name },
})}
@@ -61,24 +61,24 @@ export function SloDeleteConfirmationModal({
}
const getTitle = () =>
- i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.title', {
+ i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.title', {
defaultMessage: 'Are you sure?',
});
const getCancelButtonText = () =>
- i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.cancelButtonLabel', {
+ i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.cancelButtonLabel', {
defaultMessage: 'Cancel',
});
const getConfirmButtonText = (name: string) =>
- i18n.translate('xpack.observability.slos.slo.deleteConfirmationModal.deleteButtonLabel', {
+ i18n.translate('xpack.observability.slo.slo.deleteConfirmationModal.deleteButtonLabel', {
defaultMessage: 'Delete {name}',
values: { name },
});
const getDeleteSuccesfulMessage = (name: string) =>
i18n.translate(
- 'xpack.observability.slos.slo.deleteConfirmationModal.successNotification.descriptionText',
+ 'xpack.observability.slo.slo.deleteConfirmationModal.successNotification.descriptionText',
{
defaultMessage: 'Deleted {name}',
values: { name },
@@ -87,7 +87,7 @@ const getDeleteSuccesfulMessage = (name: string) =>
const getDeleteFailMessage = (name: string) =>
i18n.translate(
- 'xpack.observability.slos.slo.deleteConfirmationModal.errorNotification.descriptionText',
+ 'xpack.observability.slo.slo.deleteConfirmationModal.errorNotification.descriptionText',
{
defaultMessage: 'Failed to delete {name}',
values: { name },
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx
index eac54fdafec3d..cac36a8f3bcc9 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_empty.tsx
@@ -12,13 +12,13 @@ import { i18n } from '@kbn/i18n';
export function SloListEmpty() {
return (
- {i18n.translate('xpack.observability.slos.list.emptyMessage', {
+ {i18n.translate('xpack.observability.slo.list.emptyMessage', {
defaultMessage: 'There are no results for your criteria.',
})}
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx
index 47ae04b13c9ab..8e9729ba07621 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_error.tsx
@@ -16,14 +16,14 @@ export function SloListError() {
color="danger"
title={
- {i18n.translate('xpack.observability.slos.list.errorTitle', {
+ {i18n.translate('xpack.observability.slo.list.errorTitle', {
defaultMessage: 'Unable to load SLOs',
})}
}
body={
- {i18n.translate('xpack.observability.slos.list.errorMessage', {
+ {i18n.translate('xpack.observability.slo.list.errorMessage', {
defaultMessage:
'There was an error loading the SLOs. Contact your administrator for help.',
})}
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx
index 282b82bf41b7e..df2b62e0d5848 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx
@@ -13,6 +13,7 @@ import {
EuiContextMenuPanel,
EuiFlexGroup,
EuiFlexItem,
+ EuiLink,
EuiPanel,
EuiPopover,
EuiText,
@@ -62,6 +63,10 @@ export function SloListItem({
setIsActionsPopoverOpen(!isActionsPopoverOpen);
};
+ const handleViewDetails = () => {
+ navigateToUrl(basePath.prepend(paths.observability.sloDetails(slo.id)));
+ };
+
const handleEdit = () => {
navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id)));
};
@@ -102,7 +107,9 @@ export function SloListItem({
- {slo.name}
+
+ {slo.name}
+
@@ -139,6 +146,16 @@ export function SloListItem({
+ {i18n.translate('xpack.observability.slo.slo.item.actions.details', {
+ defaultMessage: 'Details',
+ })}
+ ,
- {i18n.translate('xpack.observability.slos.slo.item.actions.edit', {
+ {i18n.translate('xpack.observability.slo.slo.item.actions.edit', {
defaultMessage: 'Edit',
})}
,
@@ -157,7 +174,7 @@ export function SloListItem({
onClick={handleClone}
data-test-subj="sloActionsClone"
>
- {i18n.translate('xpack.observability.slos.slo.item.actions.clone', {
+ {i18n.translate('xpack.observability.slo.slo.item.actions.clone', {
defaultMessage: 'Clone',
})}
,
@@ -168,7 +185,7 @@ export function SloListItem({
onClick={handleDelete}
data-test-subj="sloActionsDelete"
>
- {i18n.translate('xpack.observability.slos.slo.item.actions.delete', {
+ {i18n.translate('xpack.observability.slo.slo.item.actions.delete', {
defaultMessage: 'Delete',
})}
,
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx
index 0e7ccc31d54ee..ebc01f94a99c2 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_filter_sort_bar.tsx
@@ -41,14 +41,14 @@ export type Item = EuiSelectableOption & {
const SORT_OPTIONS: Array- > = [
{
- label: i18n.translate('xpack.observability.slos.list.sortBy.name', {
+ label: i18n.translate('xpack.observability.slo.list.sortBy.name', {
defaultMessage: 'Name',
}),
type: 'name',
checked: 'on',
},
{
- label: i18n.translate('xpack.observability.slos.list.sortBy.indicatorType', {
+ label: i18n.translate('xpack.observability.slo.list.sortBy.indicatorType', {
defaultMessage: 'Indicator type',
}),
type: 'indicatorType',
@@ -57,19 +57,19 @@ const SORT_OPTIONS: Array
- > = [
const INDICATOR_TYPE_OPTIONS: Array
- > = [
{
- label: i18n.translate('xpack.observability.slos.list.indicatorTypeFilter.apmLatency', {
+ label: i18n.translate('xpack.observability.slo.list.indicatorTypeFilter.apmLatency', {
defaultMessage: 'APM latency',
}),
type: 'sli.apm.transactionDuration',
},
{
- label: i18n.translate('xpack.observability.slos.list.indicatorTypeFilter.apmAvailability', {
+ label: i18n.translate('xpack.observability.slo.list.indicatorTypeFilter.apmAvailability', {
defaultMessage: 'APM availability',
}),
type: 'sli.apm.transactionErrorRate',
},
{
- label: i18n.translate('xpack.observability.slos.list.indicatorTypeFilter.customKql', {
+ label: i18n.translate('xpack.observability.slo.list.indicatorTypeFilter.customKql', {
defaultMessage: 'Custom KQL',
}),
type: 'sli.kql.custom',
@@ -121,7 +121,7 @@ export function SloListSearchFilterSortBar({
fullWidth
isLoading={loading}
onChange={onChangeQuery}
- placeholder={i18n.translate('xpack.observability.slos.list.search', {
+ placeholder={i18n.translate('xpack.observability.slo.list.search', {
defaultMessage: 'Search',
})}
/>
@@ -137,7 +137,7 @@ export function SloListSearchFilterSortBar({
isSelected={isFilterPopoverOpen}
numFilters={selectedIndicatorTypeFilter.length}
>
- {i18n.translate('xpack.observability.slos.list.indicatorTypeFilter', {
+ {i18n.translate('xpack.observability.slo.list.indicatorTypeFilter', {
defaultMessage: 'Indicator type',
})}
@@ -149,7 +149,7 @@ export function SloListSearchFilterSortBar({
>
- {i18n.translate('xpack.observability.slos.list.indicatorTypeFilter', {
+ {i18n.translate('xpack.observability.slo.list.indicatorTypeFilter', {
defaultMessage: 'Indicator type',
})}
@@ -173,7 +173,7 @@ export function SloListSearchFilterSortBar({
onClick={handleToggleSortButton}
isSelected={isSortPopoverOpen}
>
- {i18n.translate('xpack.observability.slos.list.sortByType', {
+ {i18n.translate('xpack.observability.slo.list.sortByType', {
defaultMessage: 'Sort by {type}',
values: { type: selectedSort?.label.toLowerCase() || '' },
})}
@@ -186,7 +186,7 @@ export function SloListSearchFilterSortBar({
>
- {i18n.translate('xpack.observability.slos.list.sortBy', {
+ {i18n.translate('xpack.observability.slo.list.sortBy', {
defaultMessage: 'Sort by',
})}
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx
index 5231edb3d945c..8ab49befd627c 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_welcome_prompt.tsx
@@ -45,7 +45,7 @@ export function SloListWelcomePrompt() {
title={
- {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.title', {
+ {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.title', {
defaultMessage: 'Track and deliver on your SLOs',
})}
@@ -58,14 +58,14 @@ export function SloListWelcomePrompt() {
body={
<>
- {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.messageParagraph1', {
+ {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.messageParagraph1', {
defaultMessage:
'Measure key metrics important to the business, such as service-level indicators and service-level objectives (SLIs/SLOs) to deliver on SLAs.',
})}
- {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.messageParagraph2', {
+ {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.messageParagraph2', {
defaultMessage:
'Easily report the uptime and reliability of your services to stakeholders with real-time insights.',
})}
@@ -81,7 +81,7 @@ export function SloListWelcomePrompt() {
{i18n.translate(
- 'xpack.observability.slos.sloList.welcomePrompt.getStartedMessage',
+ 'xpack.observability.slo.sloList.welcomePrompt.getStartedMessage',
{
defaultMessage: 'To get started, create your first SLO.',
}
@@ -93,12 +93,9 @@ export function SloListWelcomePrompt() {
- {i18n.translate(
- 'xpack.observability.slos.sloList.welcomePrompt.buttonLabel',
- {
- defaultMessage: 'Create SLO',
- }
- )}
+ {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.buttonLabel', {
+ defaultMessage: 'Create SLO',
+ })}
@@ -109,7 +106,7 @@ export function SloListWelcomePrompt() {
{i18n.translate(
- 'xpack.observability.slos.sloList.welcomePrompt.needLicenseMessage',
+ 'xpack.observability.slo.sloList.welcomePrompt.needLicenseMessage',
{
defaultMessage:
'You need an Elastic Cloud subscription or Platinum license to use SLOs.',
@@ -129,7 +126,7 @@ export function SloListWelcomePrompt() {
data-test-subj="slosPageWelcomePromptSignupForCloudButton"
>
{i18n.translate(
- 'xpack.observability.slos.sloList.welcomePrompt.signupForCloud',
+ 'xpack.observability.slo.sloList.welcomePrompt.signupForCloud',
{
defaultMessage: 'Sign up for Elastic Cloud',
}
@@ -144,7 +141,7 @@ export function SloListWelcomePrompt() {
data-test-subj="slosPageWelcomePromptSignupForLicenseButton"
>
{i18n.translate(
- 'xpack.observability.slos.sloList.welcomePrompt.signupForLicense',
+ 'xpack.observability.slo.sloList.welcomePrompt.signupForLicense',
{
defaultMessage: 'Sign up for license',
}
@@ -161,14 +158,14 @@ export function SloListWelcomePrompt() {
<>
- {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.learnMore', {
+ {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.learnMore', {
defaultMessage: 'Want to learn more?',
})}
- {i18n.translate('xpack.observability.slos.sloList.welcomePrompt.learnMoreLink', {
+ {i18n.translate('xpack.observability.slo.sloList.welcomePrompt.learnMoreLink', {
defaultMessage: 'Read the docs',
})}
diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx
index ed5935139e15f..943b7f9742c58 100644
--- a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx
+++ b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx
@@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
+import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter';
import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n';
import { asPercentWithTwoDecimals } from '../../../../common/utils/formatters';
import { SloSparkline } from './slo_sparkline';
@@ -22,15 +23,8 @@ export interface Props {
export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoading }: Props) {
const isSloFailed = slo.summary.status === 'VIOLATED' || slo.summary.status === 'DEGRADING';
const titleColor = isSloFailed ? 'danger' : '';
-
- const historicalSliData = historicalSummary.map((data) => ({
- key: new Date(data.date).getTime(),
- value: data.status === 'NO_DATA' ? undefined : data.sliValue,
- }));
- const errorBudgetBurnDownData = historicalSummary.map((data) => ({
- key: new Date(data.date).getTime(),
- value: data.status === 'NO_DATA' ? undefined : data.errorBudget.remaining,
- }));
+ const errorBudgetBurnDownData = formatHistoricalData(historicalSummary, 'error_budget_remaining');
+ const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value');
return (
@@ -38,7 +32,7 @@ export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoadi
- {i18n.translate('xpack.observability.slos.sloList.pageHeader.createNewButtonLabel', {
+ {i18n.translate('xpack.observability.slo.sloList.pageHeader.createNewButtonLabel', {
defaultMessage: 'Create new SLO',
})}
,
diff --git a/x-pack/plugins/observability/public/typings/slo/index.ts b/x-pack/plugins/observability/public/typings/slo/index.ts
index 677ec38f84c53..180a92c6f77a2 100644
--- a/x-pack/plugins/observability/public/typings/slo/index.ts
+++ b/x-pack/plugins/observability/public/typings/slo/index.ts
@@ -22,4 +22,9 @@ interface BurnRateRuleParams extends RuleTypeParams {
shortWindow: Duration;
}
-export type { BurnRateRuleParams, Duration, DurationUnit };
+interface ChartData {
+ key: number;
+ value: number | undefined;
+}
+
+export type { BurnRateRuleParams, ChartData, Duration, DurationUnit };
diff --git a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts
index 00552c0adc6f1..0c9ad40784ec8 100644
--- a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts
+++ b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts
@@ -13,6 +13,7 @@ import type {
DataViewSpec,
} from '@kbn/data-views-plugin/public';
import { RuntimeField } from '@kbn/data-views-plugin/public';
+import { DataViewMissingIndices } from '@kbn/data-views-plugin/common';
import { DataTypesLabels } from '../../components/shared/exploratory_view/labels';
import { syntheticsRuntimeFields } from '../../components/shared/exploratory_view/configurations/synthetics/runtime_fields';
import { getApmDataViewTitle } from '../../components/shared/exploratory_view/utils/utils';
@@ -121,25 +122,33 @@ export class ObservabilityDataViews {
const { runtimeFields } = getFieldFormatsForApp(app);
- const dataView = await this.dataViews.create(
- {
- title: appIndicesPattern,
- id: getAppDataViewId(app, indices),
- timeFieldName: '@timestamp',
- fieldFormats: this.getFieldFormats(app),
- name: DataTypesLabels[app],
- },
- false,
- false
- );
-
- if (runtimeFields !== null) {
- runtimeFields.forEach(({ name, field }) => {
- dataView.addRuntimeField(name, field);
- });
- }
+ const id = getAppDataViewId(app, indices);
+
+ try {
+ const dataView = await this.dataViews.create(
+ {
+ id,
+ title: appIndicesPattern,
+ timeFieldName: '@timestamp',
+ fieldFormats: this.getFieldFormats(app),
+ name: DataTypesLabels[app],
+ },
+ false,
+ false
+ );
- return dataView;
+ if (runtimeFields !== null) {
+ runtimeFields.forEach(({ name, field }) => {
+ dataView.addRuntimeField(name, field);
+ });
+ }
+
+ return dataView;
+ } catch (e) {
+ if (e instanceof DataViewMissingIndices) {
+ this.dataViews.clearInstanceCache(id);
+ }
+ }
}
async createAndSavedDataView(app: AppDataType, indices: string) {
diff --git a/x-pack/plugins/observability/public/utils/slo/chart_data_formatter.ts b/x-pack/plugins/observability/public/utils/slo/chart_data_formatter.ts
new file mode 100644
index 0000000000000..40625fbdee0cb
--- /dev/null
+++ b/x-pack/plugins/observability/public/utils/slo/chart_data_formatter.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { FetchHistoricalSummaryResponse } from '@kbn/slo-schema';
+
+import { ChartData } from '../../typings/slo';
+
+type DataType = 'error_budget_remaining' | 'error_budget_consumed' | 'sli_value';
+
+export function formatHistoricalData(
+ historicalSummary: FetchHistoricalSummaryResponse[string] | undefined,
+ dataType: DataType
+): ChartData[] {
+ function getDataValue(data: FetchHistoricalSummaryResponse[string][number]) {
+ switch (dataType) {
+ case 'error_budget_consumed':
+ return data.errorBudget.consumed;
+ case 'error_budget_remaining':
+ return data.errorBudget.remaining;
+ default:
+ return data.sliValue;
+ }
+ }
+
+ if (!historicalSummary) {
+ return [];
+ }
+
+ return historicalSummary.map((data) => ({
+ key: new Date(data.date).getTime(),
+ value: data.status === 'NO_DATA' ? undefined : getDataValue(data),
+ }));
+}
diff --git a/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts b/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts
deleted file mode 100644
index a9cd9a7d233dc..0000000000000
--- a/x-pack/plugins/security_solution/public/common/components/grouping/translations.ts
+++ /dev/null
@@ -1,59 +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 { i18n } from '@kbn/i18n';
-
-export const GROUPS_UNIT = (totalCount: number) =>
- i18n.translate('xpack.securitySolution.grouping.total.unit', {
- values: { totalCount },
- defaultMessage: `{totalCount, plural, =1 {group} other {groups}}`,
- });
-
-export const TAKE_ACTION = i18n.translate(
- 'xpack.securitySolution.grouping.additionalActions.takeAction',
- {
- defaultMessage: 'Take actions',
- }
-);
-
-export const BETA = i18n.translate('xpack.securitySolution.grouping.betaLabel', {
- defaultMessage: 'Beta',
-});
-
-export const BETA_TOOL_TIP = i18n.translate('xpack.securitySolution.grouping.betaToolTip', {
- defaultMessage:
- 'Grouping may show only a subset of alerts while in beta. To see all alerts, use the list view by selecting "None"',
-});
-
-export const GROUP_BY = i18n.translate('xpack.securitySolution.selector.grouping.label', {
- defaultMessage: 'Group alerts by',
-});
-
-export const GROUP_BY_CUSTOM_FIELD = i18n.translate(
- 'xpack.securitySolution.groupsSelector.customGroupByPanelTitle',
- {
- defaultMessage: 'Group By Custom Field',
- }
-);
-
-export const SELECT_FIELD = i18n.translate(
- 'xpack.securitySolution.groupsSelector.groupByPanelTitle',
- {
- defaultMessage: 'Select Field',
- }
-);
-
-export const NONE = i18n.translate('xpack.securitySolution.groupsSelector.noneGroupByOptionName', {
- defaultMessage: 'None',
-});
-
-export const CUSTOM_FIELD = i18n.translate(
- 'xpack.securitySolution.groupsSelector.customGroupByOptionName',
- {
- defaultMessage: 'Custom field',
- }
-);
diff --git a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx
index 0e29b3edba7ab..2eae8e9e0b49b 100644
--- a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_get_group_selector.tsx
@@ -6,16 +6,15 @@
*/
import type { FieldSpec } from '@kbn/data-views-plugin/common';
-import React, { useCallback, useEffect, useMemo } from 'react';
+import { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { GROUP_BY } from '../../../components/grouping/translations';
+import { getGroupSelector, isNoneGroup } from '@kbn/securitysolution-grouping';
import type { TableId } from '../../../../../common/types';
import { getDefaultGroupingOptions } from '../../../../detections/components/alerts_table/grouping_settings';
import type { State } from '../../../store';
import { defaultGroup } from '../../../store/grouping/defaults';
import type { GroupOption } from '../../../store/grouping';
import { groupActions, groupSelectors } from '../../../store/grouping';
-import { GroupsSelector, isNoneGroup } from '../../../components/grouping';
export interface UseGetGroupSelectorArgs {
fields: FieldSpec[];
@@ -23,11 +22,7 @@ export interface UseGetGroupSelectorArgs {
tableId: TableId;
}
-export const useGetGroupingSelector = ({
- fields,
- groupingId,
- tableId,
-}: UseGetGroupSelectorArgs) => {
+export const useGetGroupSelector = ({ fields, groupingId, tableId }: UseGetGroupSelectorArgs) => {
const dispatch = useDispatch();
const getGroupByIdSelector = groupSelectors.getGroupByIdSelector();
@@ -76,45 +71,31 @@ export const useGetGroupingSelector = ({
);
}, [defaultGroupingOptions, selectedGroup, setOptions, options]);
- const groupsSelector = useMemo(
- () => (
- {
- if (groupSelection === selectedGroup) {
- return;
- }
- setGroupsActivePage(0);
- setSelectedGroup(groupSelection);
+ const groupsSelector = getGroupSelector({
+ groupSelected: selectedGroup,
+ 'data-test-subj': 'alerts-table-group-selector',
+ onGroupChange: (groupSelection: string) => {
+ if (groupSelection === selectedGroup) {
+ return;
+ }
+ setGroupsActivePage(0);
+ setSelectedGroup(groupSelection);
- if (!isNoneGroup(groupSelection) && !options.find((o) => o.key === groupSelection)) {
- setOptions([
- ...defaultGroupingOptions,
- {
- label: groupSelection,
- key: groupSelection,
- },
- ]);
- } else {
- setOptions(defaultGroupingOptions);
- }
- }}
- fields={fields}
- options={options}
- title={GROUP_BY}
- />
- ),
- [
- defaultGroupingOptions,
- fields,
- options,
- selectedGroup,
- setGroupsActivePage,
- setOptions,
- setSelectedGroup,
- ]
- );
+ if (!isNoneGroup(groupSelection) && !options.find((o) => o.key === groupSelection)) {
+ setOptions([
+ ...defaultGroupingOptions,
+ {
+ label: groupSelection,
+ key: groupSelection,
+ },
+ ]);
+ } else {
+ setOptions(defaultGroupingOptions);
+ }
+ },
+ fields,
+ options,
+ });
return groupsSelector;
};
diff --git a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_grouping_pagination.ts b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_grouping_pagination.ts
index c55246e39d509..75dc20b3ec916 100644
--- a/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_grouping_pagination.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/grouping/hooks/use_grouping_pagination.ts
@@ -7,6 +7,7 @@
import { useDispatch, useSelector } from 'react-redux';
import { useCallback, useMemo } from 'react';
+import { tableDefaults } from '../../../store/data_table/defaults';
import { groupActions, groupSelectors } from '../../../store/grouping';
import type { State } from '../../../store';
import { defaultGroup } from '../../../store/grouping/defaults';
@@ -45,6 +46,7 @@ export const useGroupingPagination = ({ groupingId }: UseGroupingPaginationArgs)
pageSize: itemsPerPage,
onChangeItemsPerPage: setGroupsItemsPerPage,
onChangePage: setGroupsActivePage,
+ itemsPerPageOptions: tableDefaults.itemsPerPageOptions,
}),
[activePage, itemsPerPage, setGroupsActivePage, setGroupsItemsPerPage]
);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
index 5a71b73d719ff..ca0c1fb9e868d 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
@@ -34,7 +34,7 @@ import { isTab } from '@kbn/timelines-plugin/public';
import type { DataViewListItem } from '@kbn/data-views-plugin/common';
import { AlertsTableComponent } from '../../../../detections/components/alerts_table';
-import { GroupedAlertsTable } from '../../../../detections/components/alerts_table/grouped_alerts';
+import { GroupedAlertsTable } from '../../../../detections/components/alerts_table/alerts_grouping';
import { useDataTableFilters } from '../../../../common/hooks/use_data_table_filters';
import { FILTER_OPEN, TableId } from '../../../../../common/types';
import { isMlRule } from '../../../../../common/machine_learning/helpers';
@@ -840,17 +840,19 @@ const RuleDetailsPageComponent: React.FC = ({
{ruleId != null && (
)}
>
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx
index fae192a479de4..41e0c8717a1fa 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx
@@ -535,12 +535,16 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
newCommentValue={newComment}
newCommentOnChange={setComment}
/>
-
-
+ {listType !== ExceptionListTypeEnum.ENDPOINT && (
+ <>
+
+
+ >
+ )}
{showAlertCloseOptions && (
<>
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx
index adcbd7ac36b5f..c0cbe1ac5caf4 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx
@@ -229,7 +229,7 @@ const ExceptionsViewerComponent = ({
);
const exceptionListFilter = useMemo(() => {
- if (exceptionsToShow.active && exceptionsToShow.expired) {
+ if (isEndpointSpecified || (exceptionsToShow.active && exceptionsToShow.expired)) {
return undefined;
}
const savedObjectPrefixes = getSavedObjectTypes({
@@ -241,7 +241,7 @@ const ExceptionsViewerComponent = ({
if (exceptionsToShow.expired) {
return buildShowExpiredExceptionsFilter(savedObjectPrefixes);
}
- }, [exceptionsToShow, namespaceTypes]);
+ }, [exceptionsToShow, namespaceTypes, isEndpointSpecified]);
const handleFetchItems = useCallback(
async (options?: GetExceptionItemProps) => {
@@ -516,6 +516,7 @@ const ExceptionsViewerComponent = ({
exceptionsToShow={exceptionsToShow}
onChangeExceptionsToShow={handleExceptionsToShow}
lastUpdated={lastUpdated}
+ isEndpoint={isEndpointSpecified}
/>
{
exceptionsToShow={{ active: true }}
onChangeExceptionsToShow={(optionId: string) => {}}
lastUpdated={1660534202}
+ isEndpoint={false}
/>
);
@@ -47,6 +48,7 @@ describe('ExceptionsViewerUtility', () => {
exceptionsToShow={{ active: true }}
onChangeExceptionsToShow={(optionId: string) => {}}
lastUpdated={Date.now()}
+ isEndpoint={false}
/>
);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx
index d7ea7acfd5f31..f1b909b050f87 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.tsx
@@ -42,6 +42,7 @@ interface ExceptionsViewerUtilityProps {
lastUpdated: string | number;
exceptionsToShow: { [id: string]: boolean };
onChangeExceptionsToShow: (optionId: string) => void;
+ isEndpoint: boolean;
}
/**
@@ -52,6 +53,7 @@ const ExceptionsViewerUtilityComponent: React.FC =
lastUpdated,
exceptionsToShow,
onChangeExceptionsToShow,
+ isEndpoint,
}): JSX.Element => {
return (
@@ -88,22 +90,24 @@ const ExceptionsViewerUtilityComponent: React.FC =
/>
-
+ {!isEndpoint && (
+
+ )}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx
index 04a350e328a26..1dd03d8bc9f9d 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx
@@ -400,12 +400,16 @@ const EditExceptionFlyoutComponent: React.FC = ({
newCommentValue={newComment}
newCommentOnChange={setComment}
/>
-
-
+ {listType !== ExceptionListTypeEnum.ENDPOINT && (
+ <>
+
+
+ >
+ )}
{showAlertCloseOptions && (
<>
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx
index e70844348c385..4d1570ba3b491 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx
@@ -66,11 +66,8 @@ export const enrichItemWithName =
*/
export const enrichItemWithExpireTime =
(expireTimeToAdd: Moment | undefined) =>
- (items: ExceptionsBuilderReturnExceptionItem[]): ExceptionsBuilderReturnExceptionItem[] => {
- return expireTimeToAdd != null
- ? enrichNewExceptionItemsWithExpireTime(items, expireTimeToAdd)
- : items;
- };
+ (items: ExceptionsBuilderReturnExceptionItem[]): ExceptionsBuilderReturnExceptionItem[] =>
+ enrichNewExceptionItemsWithExpireTime(items, expireTimeToAdd);
/**
* Modifies item entries to be in correct format and adds os selection to items
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx
index aaaffb0e70d2c..87d2e437baeed 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx
@@ -186,12 +186,13 @@ export const enrichNewExceptionItemsWithComments = (
*/
export const enrichNewExceptionItemsWithExpireTime = (
exceptionItems: ExceptionsBuilderReturnExceptionItem[],
- expireTime: Moment
+ expireTime: Moment | undefined
): ExceptionsBuilderReturnExceptionItem[] => {
+ const expireTimeDateString = expireTime !== undefined ? expireTime.toISOString() : undefined;
return exceptionItems.map((item: ExceptionsBuilderReturnExceptionItem) => {
return {
...item,
- expire_time: expireTime.toISOString(),
+ expire_time: expireTimeDateString,
};
});
};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts
index 1eadf14b02b8c..0f93d66efbf6f 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts
@@ -124,7 +124,6 @@ const MetaRule = t.intersection([
}),
]);
-// TODO: make a ticket
export const RuleSchema = t.intersection([
t.type({
author: RuleAuthorArray,
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
similarity index 70%
rename from x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx
rename to x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
index 6c5f68d546f97..fe895b93feecb 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouped_alerts.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx
@@ -8,32 +8,30 @@
import { isEmpty } from 'lodash/fp';
import React, { useCallback, useEffect, useMemo } from 'react';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
-import type { ConnectedProps } from 'react-redux';
-import { connect, useDispatch, useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { v4 as uuidv4 } from 'uuid';
-import type { Filter } from '@kbn/es-query';
+import type { Filter, Query } from '@kbn/es-query';
import { buildEsQuery } from '@kbn/es-query';
import { getEsQueryConfig } from '@kbn/data-plugin/common';
-import { useGetGroupingSelector } from '../../../common/containers/grouping/hooks/use_get_group_selector';
+import type {
+ GroupingFieldTotalAggregation,
+ GroupingAggregation,
+ RawBucket,
+} from '@kbn/securitysolution-grouping';
+import { getGrouping, isNoneGroup } from '@kbn/securitysolution-grouping';
+import { useGetGroupSelector } from '../../../common/containers/grouping/hooks/use_get_group_selector';
import type { Status } from '../../../../common/detection_engine/schemas/common';
import { defaultGroup } from '../../../common/store/grouping/defaults';
import { groupSelectors } from '../../../common/store/grouping';
import { InspectButton } from '../../../common/components/inspect';
import { defaultUnit } from '../../../common/components/toolbar/unit';
-import type {
- GroupingFieldTotalAggregation,
- GroupingTableAggregation,
- RawBucket,
-} from '../../../common/components/grouping';
-import { GroupingContainer, isNoneGroup } from '../../../common/components/grouping';
import { useGlobalTime } from '../../../common/containers/use_global_time';
import { combineQueries } from '../../../common/lib/kuery';
import type { TableIdLiteral } from '../../../../common/types';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
import { useKibana } from '../../../common/lib/kibana';
-import type { inputsModel, State } from '../../../common/store';
-import { inputsSelectors } from '../../../common/store';
+import type { State } from '../../../common/store';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { useInspectButton } from '../alerts_kpis/common/hooks';
@@ -51,26 +49,25 @@ import {
import { initGrouping } from '../../../common/store/grouping/actions';
import { useGroupingPagination } from '../../../common/containers/grouping/hooks/use_grouping_pagination';
-/** This local storage key stores the `Grid / Event rendered view` selection */
-export const ALERTS_TABLE_GROUPS_SELECTION_KEY = 'securitySolution.alerts.table.group-selection';
-
const ALERTS_GROUPING_ID = 'alerts-grouping';
interface OwnProps {
+ currentAlertStatusFilterValue?: Status;
defaultFilters?: Filter[];
from: string;
+ globalFilters: Filter[];
+ globalQuery: Query;
hasIndexMaintenance: boolean;
hasIndexWrite: boolean;
loading: boolean;
- tableId: TableIdLiteral;
- to: string;
+ renderChildComponent: (groupingFilters: Filter[]) => React.ReactElement;
runtimeMappings: MappingRuntimeFields;
signalIndexName: string | null;
- currentAlertStatusFilterValue?: Status;
- renderChildComponent: (groupingFilters: Filter[]) => React.ReactElement;
+ tableId: TableIdLiteral;
+ to: string;
}
-export type AlertsTableComponentProps = OwnProps & PropsFromRedux;
+export type AlertsTableComponentProps = OwnProps;
export const GroupedAlertsTableComponent: React.FC = ({
defaultFilters = [],
@@ -95,20 +92,18 @@ export const GroupedAlertsTableComponent: React.FC =
const { activeGroup: selectedGroup } =
useSelector((state: State) => getGroupByIdSelector(state, groupingId)) ?? defaultGroup;
- const {
- browserFields,
- indexPattern: indexPatterns,
- selectedPatterns,
- } = useSourcererDataView(SourcererScopeName.detections);
+ const { browserFields, indexPattern, selectedPatterns } = useSourcererDataView(
+ SourcererScopeName.detections
+ );
const kibana = useKibana();
const getGlobalQuery = useCallback(
(customFilters: Filter[]) => {
- if (browserFields != null && indexPatterns != null) {
+ if (browserFields != null && indexPattern != null) {
return combineQueries({
config: getEsQueryConfig(kibana.services.uiSettings),
dataProviders: [],
- indexPattern: indexPatterns,
+ indexPattern,
browserFields,
filters: [
...(defaultFilters ?? []),
@@ -122,7 +117,7 @@ export const GroupedAlertsTableComponent: React.FC =
}
return null;
},
- [browserFields, defaultFilters, globalFilters, globalQuery, indexPatterns, kibana, to, from]
+ [browserFields, defaultFilters, globalFilters, globalQuery, indexPattern, kibana, to, from]
);
useInvalidFilterQuery({
@@ -188,7 +183,7 @@ export const GroupedAlertsTableComponent: React.FC =
request,
response,
setQuery: setAlertsQuery,
- } = useQueryAlerts<{}, GroupingTableAggregation & GroupingFieldTotalAggregation>({
+ } = useQueryAlerts<{}, GroupingAggregation & GroupingFieldTotalAggregation>({
query: queryGroups,
indexName: signalIndexName,
queryName: ALERTS_QUERY_NAMES.ALERTS_GROUPING,
@@ -218,14 +213,14 @@ export const GroupedAlertsTableComponent: React.FC =
[uniqueQueryId]
);
- const groupsSelector = useGetGroupingSelector({
+ const groupsSelector = useGetGroupSelector({
tableId,
groupingId,
- fields: indexPatterns.fields,
+ fields: indexPattern.fields,
});
const takeActionItems = useGroupTakeActionsItems({
- indexName: indexPatterns.title,
+ indexName: indexPattern.title,
currentStatus: currentAlertStatusFilterValue,
showAlertStatusActions: hasIndexWrite && hasIndexMaintenance,
});
@@ -238,30 +233,25 @@ export const GroupedAlertsTableComponent: React.FC =
const groupedAlerts = useMemo(
() =>
- isNoneGroup(selectedGroup) ? (
- renderChildComponent([])
- ) : (
-
- getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket)
- }
- customMetricStats={(fieldBucket: RawBucket) =>
- getSelectedGroupCustomMetrics(selectedGroup, fieldBucket)
- }
- data={alertsGroupsData?.aggregations ?? {}}
- groupPanelRenderer={(fieldBucket: RawBucket) =>
- getSelectedGroupButtonContent(selectedGroup, fieldBucket)
- }
- groupsSelector={groupsSelector}
- inspectButton={inspect}
- isLoading={loading || isLoadingGroups}
- pagination={pagination}
- renderChildComponent={renderChildComponent}
- selectedGroup={selectedGroup}
- takeActionItems={getTakeActionItems}
- unit={defaultUnit}
- />
- ),
+ isNoneGroup(selectedGroup)
+ ? renderChildComponent([])
+ : getGrouping({
+ badgeMetricStats: (fieldBucket: RawBucket) =>
+ getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket),
+ customMetricStats: (fieldBucket: RawBucket) =>
+ getSelectedGroupCustomMetrics(selectedGroup, fieldBucket),
+ data: alertsGroupsData?.aggregations,
+ groupPanelRenderer: (fieldBucket: RawBucket) =>
+ getSelectedGroupButtonContent(selectedGroup, fieldBucket),
+ groupsSelector,
+ inspectButton: inspect,
+ isLoading: loading || isLoadingGroups,
+ pagination,
+ renderChildComponent,
+ selectedGroup,
+ takeActionItems: getTakeActionItems,
+ unit: defaultUnit,
+ }),
[
alertsGroupsData?.aggregations,
getTakeActionItems,
@@ -282,21 +272,4 @@ export const GroupedAlertsTableComponent: React.FC =
return groupedAlerts;
};
-const makeMapStateToProps = () => {
- const getGlobalInputs = inputsSelectors.globalSelector();
- const mapStateToProps = (state: State) => {
- const globalInputs: inputsModel.InputsRange = getGlobalInputs(state);
- const { query, filters } = globalInputs;
- return {
- globalQuery: query,
- globalFilters: filters,
- };
- };
- return mapStateToProps;
-};
-
-const connector = connect(makeMapStateToProps);
-
-type PropsFromRedux = ConnectedProps;
-
-export const GroupedAlertsTable = connector(React.memo(GroupedAlertsTableComponent));
+export const GroupedAlertsTable = React.memo(GroupedAlertsTableComponent);
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx
index ce400a4373b39..c75c7585a6860 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_panel_renderers.tsx
@@ -18,9 +18,9 @@ import {
import { euiThemeVars } from '@kbn/ui-theme';
import { isArray } from 'lodash/fp';
import React from 'react';
+import type { RawBucket } from '@kbn/securitysolution-grouping';
import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers';
import type { GenericBuckets } from '../../../../../common/search_strategy';
-import type { RawBucket } from '../../../../common/components/grouping';
import { PopoverItems } from '../../../../common/components/popover_items';
import { COLUMN_TAGS } from '../../../pages/detection_engine/rules/translations';
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx
index 51cbfc5d51553..d7f4d5d384560 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_stats.tsx
@@ -7,11 +7,7 @@
import { EuiIcon } from '@elastic/eui';
import React from 'react';
-import type {
- BadgeMetric,
- CustomMetric,
-} from '../../../../common/components/grouping/accordion_panel';
-import type { RawBucket } from '../../../../common/components/grouping';
+import type { RawBucket } from '@kbn/securitysolution-grouping';
import * as i18n from '../translations';
const getSingleGroupSeverity = (severity?: string) => {
@@ -67,10 +63,7 @@ const multiSeverity = (
>
);
-export const getSelectedGroupBadgeMetrics = (
- selectedGroup: string,
- bucket: RawBucket
-): BadgeMetric[] => {
+export const getSelectedGroupBadgeMetrics = (selectedGroup: string, bucket: RawBucket) => {
const defaultBadges = [
{
title: i18n.STATS_GROUP_ALERTS,
@@ -138,10 +131,7 @@ export const getSelectedGroupBadgeMetrics = (
];
};
-export const getSelectedGroupCustomMetrics = (
- selectedGroup: string,
- bucket: RawBucket
-): CustomMetric[] => {
+export const getSelectedGroupCustomMetrics = (selectedGroup: string, bucket: RawBucket) => {
const singleSeverityComponent =
bucket.severitiesSubAggregation?.buckets && bucket.severitiesSubAggregation?.buckets?.length
? getSingleGroupSeverity(bucket.severitiesSubAggregation?.buckets[0].key.toString())
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts
index 33f31d914e5a5..dbb1b15b8236f 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.test.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { MAX_QUERY_SIZE } from '../../../../common/components/grouping';
import { getAlertsGroupingQuery } from '.';
describe('getAlertsGroupingQuery', () => {
@@ -95,7 +94,7 @@ describe('getAlertsGroupingQuery', () => {
},
},
multi_terms: {
- size: MAX_QUERY_SIZE,
+ size: 10000,
terms: [
{
field: 'kibana.alert.rule.name',
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts
index ed20de72adab2..96e4dd047769d 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/query_builder.ts
@@ -7,8 +7,8 @@
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import type { BoolQuery } from '@kbn/es-query';
-import type { NamedAggregation } from '../../../../common/components/grouping';
-import { getGroupingQuery } from '../../../../common/components/grouping';
+import type { NamedAggregation } from '@kbn/securitysolution-grouping';
+import { getGroupingQuery } from '@kbn/securitysolution-grouping';
const getGroupFields = (groupValue: string) => {
if (groupValue === 'kibana.alert.rule.name') {
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx
index 8cb24295f62a4..0074f539b715e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx
@@ -18,8 +18,8 @@ import {
SUB_PLUGINS_REDUCER,
TestProviders,
} from '../../../common/mock';
-import type { AlertsTableComponentProps } from './grouped_alerts';
-import { GroupedAlertsTableComponent } from './grouped_alerts';
+import type { AlertsTableComponentProps } from './alerts_grouping';
+import { GroupedAlertsTableComponent } from './alerts_grouping';
import { TableId } from '../../../../common/types';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import type { UseFieldBrowserOptionsProps } from '../../../timelines/components/fields_browser';
@@ -181,7 +181,6 @@ const renderChildComponent = (groupingFilters: Filter[]) =>
diff --git a/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx
index 20a4fd280beec..4a82c748da97e 100644
--- a/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx
+++ b/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx
@@ -55,7 +55,7 @@ export const ManageRules: FC = memo(
>
- {i18n.MANAGE_RULES_HEADER}
+ {i18n.LINK_RULES_HEADER}
diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx
index d0ccd384156c7..cb76cf839f4e8 100644
--- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx
+++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx
@@ -125,7 +125,18 @@ export const useExceptionsListCard = ({
key: 'Export',
icon: 'exportAction',
label: i18n.EXPORT_EXCEPTION_LIST,
- onClick: (e: React.MouseEvent) => setShowExportModal(true),
+ onClick: (e: React.MouseEvent) => {
+ if (listType === ExceptionListTypeEnum.ENDPOINT) {
+ handleExport({
+ id: exceptionsList.id,
+ listId: exceptionsList.list_id,
+ namespaceType: exceptionsList.namespace_type,
+ includeExpiredExceptions: true,
+ })();
+ } else {
+ setShowExportModal(true);
+ }
+ },
},
{
key: 'Delete',
@@ -141,10 +152,10 @@ export const useExceptionsListCard = ({
},
},
{
- key: 'ManageRules',
+ key: 'LinkRules',
icon: 'gear',
disabled: listCannotBeEdited,
- label: 'Manage Rules',
+ label: i18n.LINK_RULES_OVERFLOW_BUTTON_TITLE,
onClick: (e: React.MouseEvent) => {
handleManageRules();
},
@@ -158,6 +169,8 @@ export const useExceptionsListCard = ({
setShowExportModal,
listCannotBeEdited,
handleManageRules,
+ handleExport,
+ listType,
]
);
diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx
index fc09a603b2e13..d4e2c8f4f0977 100644
--- a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx
+++ b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx
@@ -14,6 +14,7 @@ import {
} from '@kbn/securitysolution-exception-list-components';
import { EuiLoadingContent } from '@elastic/eui';
import { useParams } from 'react-router-dom';
+import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import { SecurityPageName } from '../../../../common/constants';
import { SpyRoute } from '../../../common/utils/route/spy_routes';
import { ReferenceErrorModal } from '../../../detections/components/value_lists_management_flyout/reference_error_modal';
@@ -67,6 +68,14 @@ export const ListsDetailViewComponent: FC = () => {
const onModalOpen = useCallback(() => setShowExportModal(true), [setShowExportModal]);
+ const handleExportList = useCallback(() => {
+ if (list?.type === ExceptionListTypeEnum.ENDPOINT) {
+ onExportList(true);
+ } else {
+ onModalOpen();
+ }
+ }, [onModalOpen, list, onExportList]);
+
const detailsViewContent = useMemo(() => {
if (viewerStatus === ViewerStatus.ERROR)
return ;
@@ -87,7 +96,7 @@ export const ListsDetailViewComponent: FC = () => {
backOptions={headerBackOptions}
securityLinkAnchorComponent={ListDetailsLinkAnchor}
onEditListDetails={onEditListDetails}
- onExportList={onModalOpen}
+ onExportList={handleExportList}
onDeleteList={handleDelete}
onManageRules={onManageRules}
/>
@@ -155,7 +164,7 @@ export const ListsDetailViewComponent: FC = () => {
handleDelete,
handleReferenceDelete,
onModalClose,
- onModalOpen,
+ handleExportList,
]);
return (
<>
diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts
index d37c5e29b9fc2..6d1f8c115fab3 100644
--- a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts
+++ b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts
@@ -112,10 +112,17 @@ export const MANAGE_RULES_SAVE = i18n.translate(
defaultMessage: 'Save',
}
);
-export const MANAGE_RULES_HEADER = i18n.translate(
- 'xpack.securitySolution.exceptions.list.manage_rules_header',
+export const LINK_RULES_HEADER = i18n.translate(
+ 'xpack.securitySolution.exceptions.list.link_rules_header',
{
- defaultMessage: 'Manage rules',
+ defaultMessage: 'Link rules',
+ }
+);
+
+export const LINK_RULES_OVERFLOW_BUTTON_TITLE = i18n.translate(
+ 'xpack.securitySolution.exceptions.list.link_rules_overflow_button_title',
+ {
+ defaultMessage: 'Link rules',
}
);
diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json
index 1cc1a355d3537..c5654f37517fb 100644
--- a/x-pack/plugins/security_solution/tsconfig.json
+++ b/x-pack/plugins/security_solution/tsconfig.json
@@ -144,5 +144,6 @@
"@kbn/shared-ux-router",
"@kbn/alerts-as-data-utils",
"@kbn/expandable-flyout",
+ "@kbn/securitysolution-grouping",
]
}
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 82a2c51216e42..aed7fe5ae463b 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -2477,7 +2477,6 @@
"exceptionList-components.exception_list_header_edit_modal_save_button": "Enregistrer",
"exceptionList-components.exception_list_header_export_action": "Exporter la liste d'exceptions",
"exceptionList-components.exception_list_header_list_id": "ID de liste",
- "exceptionList-components.exception_list_header_manage_rules_button": "Gérer les règles",
"exceptionList-components.exception_list_header_name": "Ajouter un nom",
"exceptionList-components.exception_list_header_Name_textbox": "Nom",
"exceptionList-components.exceptions.exceptionItem.card.conditions.and": "AND",
@@ -3880,7 +3879,6 @@
"indexPatternEditor.typeSelect.standardDescription": "Effectuer des agrégations complètes à partir de n'importe quelles données",
"indexPatternEditor.typeSelect.standardTitle": "Vue de données standard",
"indexPatternEditor.validations.noSingleAstriskPattern": "Un seul astérisque \"*\" n’est pas un modèle d'indexation autorisé",
- "indexPatternEditor.validations.titleHelpText": "Entrez un modèle d'indexation qui correspond à une ou plusieurs sources de données. Utilisez un astérisque (*) pour faire correspondre plusieurs caractères. Les espaces et les caractères , /, ?, \", <, >, | ne sont pas autorisés.",
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "Un modèle d'indexation est requis.",
"indexPatternFieldEditor.date.momentLabel": "Modèle de format Moment.js (par défaut : {defaultPattern})",
"indexPatternFieldEditor.defaultErrorMessage": "Une erreur s'est produite lors de l'utilisation de cette configuration de format : {message}.",
@@ -29580,7 +29578,6 @@
"xpack.securitySolution.exceptions.list.exceptionItemsFetchErrorDescription": "Une erreur s'est produite lors du chargement des éléments d'exception. Contactez votre administrateur pour obtenir de l'aide.",
"xpack.securitySolution.exceptions.list.manage_rules_cancel": "Annuler",
"xpack.securitySolution.exceptions.list.manage_rules_description": "Associez des règles à cette liste d'exceptions ou dissociez des règles de cette liste.",
- "xpack.securitySolution.exceptions.list.manage_rules_header": "Gérer les règles",
"xpack.securitySolution.exceptions.list.manage_rules_save": "Enregistrer",
"xpack.securitySolution.exceptions.list.utility.title": "exceptions de règle",
"xpack.securitySolution.exceptions.manageExceptions.createItemButton": "Créer un élément d'exception",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 9f1cb387004e9..efdd751897a6e 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -2475,7 +2475,6 @@
"exceptionList-components.exception_list_header_edit_modal_save_button": "保存",
"exceptionList-components.exception_list_header_export_action": "例外リストのエクスポート",
"exceptionList-components.exception_list_header_list_id": "リスト ID",
- "exceptionList-components.exception_list_header_manage_rules_button": "ルールの管理",
"exceptionList-components.exception_list_header_name": "名前を追加",
"exceptionList-components.exception_list_header_Name_textbox": "名前",
"exceptionList-components.exceptions.exceptionItem.card.conditions.and": "AND",
@@ -3877,7 +3876,6 @@
"indexPatternEditor.typeSelect.standardDescription": "すべてのデータに完全アグリゲーションを実行",
"indexPatternEditor.typeSelect.standardTitle": "標準データビュー",
"indexPatternEditor.validations.noSingleAstriskPattern": "単一の「*」は許可されたパターンではありません",
- "indexPatternEditor.validations.titleHelpText": "1つ以上のデータソースと一致するインデックスパターンを入力します。複数の文字の一致にアスタリスク(*)を使用します。ペースと / ? , \" < > | 文字は使用できません。",
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "インデックスパターンが必要です。",
"indexPatternFieldEditor.date.momentLabel": "Moment.jsのフォーマットパターン(デフォルト:{defaultPattern})",
"indexPatternFieldEditor.defaultErrorMessage": "このフォーマット構成の使用を試みた際にエラーが発生しました:{message}",
@@ -29549,7 +29547,6 @@
"xpack.securitySolution.exceptions.list.exceptionItemsFetchErrorDescription": "例外アイテムの読み込みエラーが発生しました。ヘルプについては、管理者にお問い合わせください。",
"xpack.securitySolution.exceptions.list.manage_rules_cancel": "キャンセル",
"xpack.securitySolution.exceptions.list.manage_rules_description": "ルールをこの例外リストに関連付けたり、関連付けを解除したりします。",
- "xpack.securitySolution.exceptions.list.manage_rules_header": "ルールの管理",
"xpack.securitySolution.exceptions.list.manage_rules_save": "保存",
"xpack.securitySolution.exceptions.list.utility.title": "ルール例外",
"xpack.securitySolution.exceptions.manageExceptions.createItemButton": "例外アイテムの作成",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index cc3498186a34a..8213052dd50de 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -2479,7 +2479,6 @@
"exceptionList-components.exception_list_header_edit_modal_save_button": "保存",
"exceptionList-components.exception_list_header_export_action": "导出例外列表",
"exceptionList-components.exception_list_header_list_id": "列表 ID",
- "exceptionList-components.exception_list_header_manage_rules_button": "管理规则",
"exceptionList-components.exception_list_header_name": "添加名称",
"exceptionList-components.exception_list_header_Name_textbox": "名称",
"exceptionList-components.exceptions.exceptionItem.card.conditions.and": "且",
@@ -3882,7 +3881,6 @@
"indexPatternEditor.typeSelect.standardDescription": "对任何数据执行完全聚合",
"indexPatternEditor.typeSelect.standardTitle": "标准数据视图",
"indexPatternEditor.validations.noSingleAstriskPattern": "不允许以单个“*”作为索引模式",
- "indexPatternEditor.validations.titleHelpText": "输入与一个或多个数据源匹配的索引模式。使用星号 (*) 匹配多个字符。不允许使用空格和字符 /、?、\"、<、>、|。",
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "“索引模式”必填。",
"indexPatternFieldEditor.date.momentLabel": "Moment.js 格式模式(默认值:{defaultPattern})",
"indexPatternFieldEditor.defaultErrorMessage": "尝试使用此格式配置时发生错误:{message}",
@@ -29583,7 +29581,6 @@
"xpack.securitySolution.exceptions.list.exceptionItemsFetchErrorDescription": "加载例外项时出现错误。请联系您的管理员寻求帮助。",
"xpack.securitySolution.exceptions.list.manage_rules_cancel": "取消",
"xpack.securitySolution.exceptions.list.manage_rules_description": "将规则链接到此例外列表或取消链接。",
- "xpack.securitySolution.exceptions.list.manage_rules_header": "管理规则",
"xpack.securitySolution.exceptions.list.manage_rules_save": "保存",
"xpack.securitySolution.exceptions.list.utility.title": "规则例外",
"xpack.securitySolution.exceptions.manageExceptions.createItemButton": "创建例外项",
diff --git a/yarn.lock b/yarn.lock
index dd63058020b48..373279a97253e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4841,6 +4841,10 @@
version "0.0.0"
uid ""
+"@kbn/securitysolution-grouping@link:packages/kbn-securitysolution-grouping":
+ version "0.0.0"
+ uid ""
+
"@kbn/securitysolution-autocomplete@link:packages/kbn-securitysolution-autocomplete":
version "0.0.0"
uid ""