-
-
- {i18n.COPY_TO_CLIPBOARD}
-
-
+
+
<>
{sameFamilyMappings.length > 0 && (
@@ -102,6 +87,9 @@ const SameFamilyTabComponent: React.FC
= ({
>
)}
>
+
+ 0 ? 'm' : 'l'} />
+
);
};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/sticky_actions/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/sticky_actions/index.tsx
new file mode 100644
index 0000000000000..57b17a7453dd0
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/sticky_actions/index.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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, { FC } from 'react';
+import { EuiButtonEmpty } from '@elastic/eui';
+
+import styled from 'styled-components';
+import { Actions } from '../../actions';
+
+export const CopyToClipboardButton = styled(EuiButtonEmpty)`
+ margin-left: ${({ theme }) => theme.eui.euiSizeXS};
+`;
+
+interface Props {
+ markdownComment: string;
+ showAddToNewCaseAction?: boolean;
+ showCopyToClipboardAction?: boolean;
+ showChatAction?: boolean;
+ indexName?: string;
+}
+
+const StyledStickyContainer = styled.div`
+ padding: ${({ theme }) => theme.eui.euiSizeL} 0;
+ background: ${({ theme }) => theme.eui.euiColorEmptyShade};
+ position: sticky;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ border-top: 1px solid ${({ theme }) => theme.eui.euiBorderColor};
+`;
+
+const StickyActionsComponent: FC
= ({
+ indexName,
+ markdownComment,
+ showCopyToClipboardAction,
+ showAddToNewCaseAction,
+ showChatAction,
+}) => {
+ return (
+
+
+
+ );
+};
+
+StickyActionsComponent.displayName = 'StickyActionsComponent';
+
+export const StickyActions = React.memo(StickyActionsComponent);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx
index 2714d1002c40c..20976b1684003 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiButtonEmpty, EuiFlexItem, EuiLink } from '@elastic/eui';
+import { EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';
export const DEFAULT_LEGEND_HEIGHT = 300; // px
@@ -23,10 +23,6 @@ export const ChartFlexItem = styled(EuiFlexItem)<{
min-height: ${({ $minChartHeight }) => `${$minChartHeight}px`};
`;
-export const CopyToClipboardButton = styled(EuiButtonEmpty)`
- margin-left: ${({ theme }) => theme.eui.euiSizeXS};
-`;
-
export const LegendContainer = styled.div<{
$height?: number;
$width?: number;
@@ -37,11 +33,3 @@ export const LegendContainer = styled.div<{
scrollbar-width: thin;
${({ $width }) => ($width != null ? `width: ${$width}px;` : '')}
`;
-
-export const StorageTreemapContainer = styled.div`
- padding: ${({ theme }) => theme.eui.euiSizeM};
-`;
-
-export const ChartLegendLink = styled(EuiLink)`
- width: 100%;
-`;
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx
deleted file mode 100644
index 96b110a05807e..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx
+++ /dev/null
@@ -1,163 +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 { NewChat } from '@kbn/elastic-assistant';
-import { copyToClipboard, EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
-import React, { useCallback, useMemo } from 'react';
-
-import { MissingTimestampCallout } from '../../callouts/missing_timestamp_callout';
-import { IncompatibleCallout } from '../../callouts/incompatible_callout';
-import { showMissingTimestampCallout } from '../../helpers';
-import { getMarkdownComments } from '../helpers';
-import { showInvalidCallout } from '../../incompatible_tab/helpers';
-import { CopyToClipboardButton } from '../../styles';
-import * as i18n from '../../../index_properties/translations';
-import {
- COPIED_RESULTS_TOAST_TITLE,
- DATA_QUALITY_PROMPT_CONTEXT_PILL,
- DATA_QUALITY_PROMPT_CONTEXT_PILL_TOOLTIP,
- DATA_QUALITY_SUGGESTED_USER_PROMPT,
-} from '../../../../translations';
-import type { IlmPhase, PartitionedFieldMetadata } from '../../../../types';
-import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from './translations';
-import { useDataQualityContext } from '../../../data_quality_context';
-
-interface Props {
- addSuccessToast: (toast: { title: string }) => void;
- addToNewCaseDisabled: boolean;
- docsCount: number;
- formatBytes: (value: number | undefined) => string;
- formatNumber: (value: number | undefined) => string;
- ilmPhase: IlmPhase | undefined;
- indexName: string;
- isAssistantEnabled: boolean;
- onAddToNewCase: (markdownComment: string[]) => void;
- partitionedFieldMetadata: PartitionedFieldMetadata;
- pattern: string;
- patternDocsCount: number;
- sizeInBytes: number | undefined;
-}
-
-const CalloutSummaryComponent: React.FC = ({
- addSuccessToast,
- addToNewCaseDisabled,
- docsCount,
- formatBytes,
- formatNumber,
- ilmPhase,
- indexName,
- isAssistantEnabled,
- onAddToNewCase,
- partitionedFieldMetadata,
- pattern,
- patternDocsCount,
- sizeInBytes,
-}) => {
- const { isILMAvailable } = useDataQualityContext();
- const markdownComments: string[] = useMemo(
- () =>
- getMarkdownComments({
- docsCount,
- formatBytes,
- formatNumber,
- ilmPhase,
- indexName,
- isILMAvailable,
- partitionedFieldMetadata,
- pattern,
- patternDocsCount,
- sizeInBytes,
- }),
- [
- docsCount,
- formatBytes,
- formatNumber,
- ilmPhase,
- indexName,
- isILMAvailable,
- partitionedFieldMetadata,
- pattern,
- patternDocsCount,
- sizeInBytes,
- ]
- );
-
- const onClickAddToCase = useCallback(
- () => onAddToNewCase([markdownComments.join('\n')]),
- [markdownComments, onAddToNewCase]
- );
-
- const onCopy = useCallback(() => {
- copyToClipboard(markdownComments.join('\n'));
-
- addSuccessToast({
- title: COPIED_RESULTS_TOAST_TITLE,
- });
- }, [addSuccessToast, markdownComments]);
-
- const getPromptContext = useCallback(async () => markdownComments.join('\n'), [markdownComments]);
-
- const showActions =
- showInvalidCallout(partitionedFieldMetadata.incompatible) ||
- showMissingTimestampCallout(partitionedFieldMetadata.ecsCompliant);
-
- return (
- <>
- {showInvalidCallout(partitionedFieldMetadata.incompatible) && (
- <>
-
-
- >
- )}
- {showMissingTimestampCallout(partitionedFieldMetadata.ecsCompliant) && (
- <>
-
-
- >
- )}
- {showActions && (
- <>
-
-
-
- {i18n.ADD_TO_NEW_CASE}
-
-
-
-
-
- {i18n.COPY_TO_CLIPBOARD}
-
-
-
-
-
-
-
-
-
- >
- )}
- >
- );
-};
-
-CalloutSummaryComponent.displayName = 'CalloutSummaryComponent';
-
-export const CalloutSummary = React.memo(CalloutSummaryComponent);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts
deleted file mode 100644
index c89cf1d5158ec..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts
+++ /dev/null
@@ -1,255 +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 numeral from '@elastic/numeral';
-import { EcsVersion } from '@elastic/ecs';
-import { euiThemeVars } from '@kbn/ui-theme';
-import { EMPTY_STAT } from '../../../helpers';
-
-import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata';
-import { mockPartitionedFieldMetadataWithSameFamily } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family';
-import { PartitionedFieldMetadata } from '../../../types';
-import {
- ALL_TAB_ID,
- CUSTOM_TAB_ID,
- ECS_COMPLIANT_TAB_ID,
- INCOMPATIBLE_TAB_ID,
- SAME_FAMILY_TAB_ID,
-} from '../../index_properties/helpers';
-import {
- CUSTOM_FIELDS,
- ECS_COMPLIANT_FIELDS,
- INCOMPATIBLE_FIELDS,
- UNKNOWN,
-} from '../../index_properties/translations';
-import { SAME_FAMILY } from '../../stat_label/translations';
-import {
- CategoryId,
- getFillColor,
- getMarkdownComments,
- getNodeLabel,
- getSummaryData,
- getTabId,
-} from './helpers';
-import { EcsFlatTyped } from '../../../constants';
-
-describe('helpers', () => {
- describe('getSummaryData', () => {
- test('it returns the expected `SummaryData`', () => {
- expect(getSummaryData(mockPartitionedFieldMetadataWithSameFamily)).toEqual([
- { categoryId: 'incompatible', mappings: 3 },
- { categoryId: 'custom', mappings: 4 },
- { categoryId: 'ecs-compliant', mappings: 2 },
- { categoryId: 'same-family', mappings: 1 },
- ]);
- });
- });
-
- describe('getFillColor', () => {
- const invalid: CategoryId = 'invalid-category-id' as CategoryId;
-
- const categories: Array<{
- categoryId: CategoryId;
- expectedColor: string;
- }> = [
- {
- categoryId: 'incompatible',
- expectedColor: euiThemeVars.euiColorDanger,
- },
- {
- categoryId: 'custom',
- expectedColor: euiThemeVars.euiColorLightShade,
- },
- {
- categoryId: 'ecs-compliant',
- expectedColor: euiThemeVars.euiColorSuccess,
- },
- {
- categoryId: 'same-family',
- expectedColor: euiThemeVars.euiColorLightShade,
- },
- {
- categoryId: invalid,
- expectedColor: euiThemeVars.euiColorGhost,
- },
- ];
-
- categories.forEach(({ categoryId, expectedColor }) => {
- test(`it returns the expected color for category '${categoryId}'`, () => {
- expect(getFillColor(categoryId)).toEqual(expectedColor);
- });
- });
- });
-
- describe('getNodeLabel', () => {
- const invalid: CategoryId = 'invalid-category-id' as CategoryId;
-
- const categories: Array<{
- categoryId: CategoryId;
- expectedLabel: string;
- }> = [
- {
- categoryId: 'incompatible',
- expectedLabel: INCOMPATIBLE_FIELDS,
- },
- {
- categoryId: 'custom',
- expectedLabel: CUSTOM_FIELDS,
- },
- {
- categoryId: 'ecs-compliant',
- expectedLabel: ECS_COMPLIANT_FIELDS,
- },
- {
- categoryId: 'same-family',
- expectedLabel: SAME_FAMILY,
- },
- {
- categoryId: invalid,
- expectedLabel: UNKNOWN,
- },
- ];
-
- categories.forEach(({ categoryId, expectedLabel }) => {
- test(`it returns the expected label for category '${categoryId}'`, () => {
- expect(getNodeLabel(categoryId)).toEqual(expectedLabel);
- });
- });
- });
-
- describe('getTabId', () => {
- const groupByFields: Array<{
- groupByField: string;
- expectedTabId: string;
- }> = [
- {
- groupByField: 'incompatible',
- expectedTabId: INCOMPATIBLE_TAB_ID,
- },
- {
- groupByField: 'custom',
- expectedTabId: CUSTOM_TAB_ID,
- },
- {
- groupByField: 'ecs-compliant',
- expectedTabId: ECS_COMPLIANT_TAB_ID,
- },
- {
- groupByField: 'same-family',
- expectedTabId: SAME_FAMILY_TAB_ID,
- },
- {
- groupByField: 'some-other-group',
- expectedTabId: ALL_TAB_ID,
- },
- ];
-
- groupByFields.forEach(({ groupByField, expectedTabId }) => {
- test(`it returns the expected tab ID for groupByField '${groupByField}'`, () => {
- expect(getTabId(groupByField)).toEqual(expectedTabId);
- });
- });
- });
-
- describe('getMarkdownComments', () => {
- const defaultBytesFormat = '0,0.[0]b';
- const formatBytes = (value: number | undefined) =>
- value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT;
-
- const defaultNumberFormat = '0,0.[000]';
- const formatNumber = (value: number | undefined) =>
- value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT;
- const isILMAvailable = true;
-
- test('it returns the expected comment when the index has incompatible fields ', () => {
- expect(
- getMarkdownComments({
- docsCount: 4,
- formatBytes,
- formatNumber,
- ilmPhase: 'unmanaged',
- indexName: 'auditbeat-custom-index-1',
- isILMAvailable,
- partitionedFieldMetadata: mockPartitionedFieldMetadata,
- pattern: 'auditbeat-*',
- patternDocsCount: 57410,
- sizeInBytes: 28413,
- })
- ).toEqual([
- '### auditbeat-custom-index-1\n',
- '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` | 27.7KB |\n\n',
- '### **Incompatible fields** `3` **Same family** `0` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n',
- `#### 3 incompatible fields\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version ${EcsVersion}.\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n`,
- '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n',
- ]);
- });
-
- test('it returns an empty array when the index does NOT have incompatible fields ', () => {
- const noIncompatible: PartitionedFieldMetadata = {
- ...mockPartitionedFieldMetadata,
- incompatible: [], // <-- no incompatible fields
- };
-
- expect(
- getMarkdownComments({
- docsCount: 4,
- formatBytes,
- formatNumber,
- ilmPhase: 'unmanaged',
- indexName: 'auditbeat-custom-index-1',
- isILMAvailable,
- partitionedFieldMetadata: noIncompatible,
- pattern: 'auditbeat-*',
- patternDocsCount: 57410,
- sizeInBytes: 28413,
- })
- ).toEqual([]);
- });
-
- test('it returns a missing timestamp comment for an empty index', () => {
- const emptyIndex: PartitionedFieldMetadata = {
- all: [],
- ecsCompliant: [],
- custom: [],
- incompatible: [
- {
- ...EcsFlatTyped['@timestamp'],
- hasEcsMetadata: true,
- indexFieldName: '@timestamp',
- indexFieldType: '-',
- indexInvalidValues: [],
- isEcsCompliant: false,
- isInSameFamily: false,
- },
- ],
- sameFamily: [],
- };
-
- expect(
- getMarkdownComments({
- docsCount: 0,
- formatBytes,
- formatNumber,
- ilmPhase: 'unmanaged',
- indexName: 'auditbeat-custom-empty-index-1',
- isILMAvailable,
- partitionedFieldMetadata: emptyIndex,
- pattern: 'auditbeat-*',
- patternDocsCount: 57410,
- sizeInBytes: 247,
- })
- ).toEqual([
- '### auditbeat-custom-empty-index-1\n',
- '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | auditbeat-custom-empty-index-1 | 0 (0.0%) | 1 | `unmanaged` | 247B |\n\n',
- '### **Incompatible fields** `1` **Same family** `0` **Custom fields** `0` **ECS compliant fields** `0` **All fields** `0`\n',
- `#### 1 incompatible field\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version ${EcsVersion}.\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n`,
- '\n#### Incompatible field mappings - auditbeat-custom-empty-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| @timestamp | `date` | `-` |\n\n\n',
- '#### Missing an @timestamp (date) field mapping for this index\n\nConsider adding an @timestamp (date) field mapping to this index, as required by the Elastic Common Schema (ECS), because:\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n',
- ]);
- });
- });
-});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts
deleted file mode 100644
index 001fcbdcda779..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.ts
+++ /dev/null
@@ -1,131 +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 { euiThemeVars } from '@kbn/ui-theme';
-
-import { getMissingTimestampComment, showMissingTimestampCallout } from '../helpers';
-import {
- ALL_TAB_ID,
- ECS_COMPLIANT_TAB_ID,
- CUSTOM_TAB_ID,
- INCOMPATIBLE_TAB_ID,
- SAME_FAMILY_TAB_ID,
-} from '../../index_properties/helpers';
-import {
- getAllIncompatibleMarkdownComments,
- showInvalidCallout,
-} from '../incompatible_tab/helpers';
-import * as i18n from '../../index_properties/translations';
-import type { IlmPhase, PartitionedFieldMetadata } from '../../../types';
-
-export type CategoryId = 'incompatible' | 'custom' | 'ecs-compliant' | 'same-family';
-
-interface SummaryData {
- categoryId: CategoryId;
- mappings: number;
-}
-
-export const getSummaryData = (
- partitionedFieldMetadata: PartitionedFieldMetadata
-): SummaryData[] => [
- { categoryId: 'incompatible', mappings: partitionedFieldMetadata.incompatible.length },
- { categoryId: 'custom', mappings: partitionedFieldMetadata.custom.length },
- { categoryId: 'ecs-compliant', mappings: partitionedFieldMetadata.ecsCompliant.length },
- { categoryId: 'same-family', mappings: partitionedFieldMetadata.sameFamily.length },
-];
-
-export const getFillColor = (categoryId: CategoryId | string): string => {
- switch (categoryId) {
- case 'incompatible':
- return euiThemeVars.euiColorDanger;
- case 'same-family':
- return euiThemeVars.euiColorLightShade;
- case 'custom':
- return euiThemeVars.euiColorLightShade;
- case 'ecs-compliant':
- return euiThemeVars.euiColorSuccess;
- default:
- return euiThemeVars.euiColorGhost;
- }
-};
-
-export const getNodeLabel = (categoryId: CategoryId): string => {
- switch (categoryId) {
- case 'incompatible':
- return i18n.INCOMPATIBLE_FIELDS;
- case 'same-family':
- return i18n.SAME_FAMILY;
- case 'custom':
- return i18n.CUSTOM_FIELDS;
- case 'ecs-compliant':
- return i18n.ECS_COMPLIANT_FIELDS;
- default:
- return i18n.UNKNOWN;
- }
-};
-
-export const getTabId = (groupByField: string): string => {
- switch (groupByField) {
- case 'incompatible':
- return INCOMPATIBLE_TAB_ID;
- case 'same-family':
- return SAME_FAMILY_TAB_ID;
- case 'custom':
- return CUSTOM_TAB_ID;
- case 'ecs-compliant':
- return ECS_COMPLIANT_TAB_ID;
- default:
- return ALL_TAB_ID;
- }
-};
-
-const isString = (x: string | null): x is string => typeof x === 'string';
-
-export const getMarkdownComments = ({
- docsCount,
- formatBytes,
- formatNumber,
- ilmPhase,
- indexName,
- isILMAvailable,
- partitionedFieldMetadata,
- patternDocsCount,
- sizeInBytes,
-}: {
- docsCount: number;
- formatBytes: (value: number | undefined) => string;
- formatNumber: (value: number | undefined) => string;
- ilmPhase: IlmPhase | undefined;
- indexName: string;
- isILMAvailable: boolean;
- partitionedFieldMetadata: PartitionedFieldMetadata;
- pattern: string;
- patternDocsCount: number;
- sizeInBytes: number | undefined;
-}): string[] => {
- const invalidMarkdownComments = showInvalidCallout(partitionedFieldMetadata.incompatible)
- ? getAllIncompatibleMarkdownComments({
- docsCount,
- formatBytes,
- formatNumber,
- ilmPhase,
- indexName,
- isILMAvailable,
- partitionedFieldMetadata,
- patternDocsCount,
- sizeInBytes,
- })
- : [];
-
- const showMissingTimestampComment = showMissingTimestampCallout(
- partitionedFieldMetadata.ecsCompliant
- )
- ? getMissingTimestampComment()
- : null;
-
- return [...invalidMarkdownComments, showMissingTimestampComment].filter(isString);
-};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx
deleted file mode 100644
index eed0c5a69d3b3..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/index.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-
-import type {
- FlameElementEvent,
- HeatmapElementEvent,
- MetricElementEvent,
- PartialTheme,
- PartitionElementEvent,
- Theme,
- WordCloudElementEvent,
- XYChartElementEvent,
-} from '@elastic/charts';
-import { CalloutSummary } from './callout_summary';
-import { EcsSummaryDonutChart } from '../../../ecs_summary_donut_chart';
-import { ALL_TAB_ID } from '../../index_properties/helpers';
-import type { IlmPhase, PartitionedFieldMetadata } from '../../../types';
-
-interface Props {
- addSuccessToast: (toast: { title: string }) => void;
- addToNewCaseDisabled: boolean;
- docsCount: number;
- formatBytes: (value: number | undefined) => string;
- formatNumber: (value: number | undefined) => string;
- getGroupByFieldsOnClick: (
- elements: Array<
- | FlameElementEvent
- | HeatmapElementEvent
- | MetricElementEvent
- | PartitionElementEvent
- | WordCloudElementEvent
- | XYChartElementEvent
- >
- ) => {
- groupByField0: string;
- groupByField1: string;
- };
- ilmPhase: IlmPhase | undefined;
- indexName: string;
- isAssistantEnabled: boolean;
- onAddToNewCase: (markdownComments: string[]) => void;
- partitionedFieldMetadata: PartitionedFieldMetadata;
- pattern: string;
- patternDocsCount: number;
- setSelectedTabId: (tabId: string) => void;
- sizeInBytes: number | undefined;
- theme?: PartialTheme;
- baseTheme: Theme;
-}
-
-const SummaryTabComponent: React.FC = ({
- addSuccessToast,
- addToNewCaseDisabled,
- formatBytes,
- formatNumber,
- docsCount,
- getGroupByFieldsOnClick,
- ilmPhase,
- indexName,
- isAssistantEnabled,
- onAddToNewCase,
- partitionedFieldMetadata,
- pattern,
- patternDocsCount,
- setSelectedTabId,
- sizeInBytes,
- theme,
- baseTheme,
-}) => (
- <>
-
-
-
- >
-);
-
-SummaryTabComponent.displayName = 'SummaryTabComponent';
-
-export const SummaryTab = React.memo(SummaryTabComponent);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx
deleted file mode 100644
index 78f22eccfd3d2..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/chart_legend/index.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useCallback } from 'react';
-
-import { ChartLegendItem } from './chart_legend_item';
-import { getEcsCompliantColor } from '../../data_quality_panel/tabs/helpers';
-import {
- ECS_COMPLIANT_TAB_ID,
- CUSTOM_TAB_ID,
- INCOMPATIBLE_TAB_ID,
- SAME_FAMILY_TAB_ID,
-} from '../../data_quality_panel/index_properties/helpers';
-import { getCustomColor } from '../../data_quality_panel/tabs/custom_tab/helpers';
-import {
- getIncompatibleColor,
- getSameFamilyColor,
-} from '../../data_quality_panel/tabs/incompatible_tab/helpers';
-import type { PartitionedFieldMetadata } from '../../types';
-import * as i18n from '../../data_quality_panel/index_properties/translations';
-import { LegendContainer } from '../../data_quality_panel/tabs/styles';
-
-const LEGEND_WIDTH = 200; // px
-
-interface Props {
- partitionedFieldMetadata: PartitionedFieldMetadata;
- setSelectedTabId: (tabId: string) => void;
-}
-
-const ChartLegendComponent: React.FC = ({ partitionedFieldMetadata, setSelectedTabId }) => {
- const showIncompatibleTab = useCallback(
- () => setSelectedTabId(INCOMPATIBLE_TAB_ID),
- [setSelectedTabId]
- );
-
- const showSameFamilyTab = useCallback(
- () => setSelectedTabId(SAME_FAMILY_TAB_ID),
- [setSelectedTabId]
- );
-
- const showCustomTab = useCallback(() => setSelectedTabId(CUSTOM_TAB_ID), [setSelectedTabId]);
-
- const showEcsCompliantTab = useCallback(
- () => setSelectedTabId(ECS_COMPLIANT_TAB_ID),
- [setSelectedTabId]
- );
-
- return (
-
- {partitionedFieldMetadata.incompatible.length > 0 && (
-
- )}
-
- {partitionedFieldMetadata.sameFamily.length > 0 && (
-
- )}
-
- {partitionedFieldMetadata.custom.length > 0 && (
-
- )}
-
- {partitionedFieldMetadata.ecsCompliant.length > 0 && (
-
- )}
-
- );
-};
-
-ChartLegendComponent.displayName = 'ChartLegendComponent';
-
-export const ChartLegend = React.memo(ChartLegendComponent);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts
deleted file mode 100644
index 1ab8c650940d4..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/helpers.test.ts
+++ /dev/null
@@ -1,30 +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 { allMetadataIsEmpty } from './helpers';
-import { mockPartitionedFieldMetadata } from '../mock/partitioned_field_metadata/mock_partitioned_field_metadata';
-import { PartitionedFieldMetadata } from '../types';
-
-describe('helpers', () => {
- describe('allMetadataIsEmpty', () => {
- test('it returns false when `all` is NOT is empty', () => {
- expect(allMetadataIsEmpty(mockPartitionedFieldMetadata)).toBe(false);
- });
-
- test('it returns true when `all` is is empty', () => {
- const allIsEmpty: PartitionedFieldMetadata = {
- all: [], // <-- empty
- custom: [],
- ecsCompliant: [],
- incompatible: [],
- sameFamily: [],
- };
-
- expect(allMetadataIsEmpty(allIsEmpty)).toBe(true);
- });
- });
-});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/index.tsx
deleted file mode 100644
index bb5d274d0f825..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/index.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type {
- Datum,
- ElementClickListener,
- FlameElementEvent,
- HeatmapElementEvent,
- MetricElementEvent,
- PartialTheme,
- PartitionElementEvent,
- Theme,
- WordCloudElementEvent,
- XYChartElementEvent,
- PartitionLayer,
-} from '@elastic/charts';
-import { Chart, Partition, PartitionLayout, Settings } from '@elastic/charts';
-import {
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
- EuiText,
- EuiTitle,
-} from '@elastic/eui';
-import React, { useCallback, useMemo } from 'react';
-import styled from 'styled-components';
-
-import { i18n } from '@kbn/i18n';
-import { ChartLegend } from './chart_legend';
-import {
- getFillColor,
- getNodeLabel,
- getSummaryData,
- getTabId,
-} from '../data_quality_panel/tabs/summary_tab/helpers';
-import { allMetadataIsEmpty } from './helpers';
-import * as translations from './translations';
-import type { PartitionedFieldMetadata } from '../types';
-
-export const DEFAULT_HEIGHT = 180; // px
-
-const DonutTextWrapper = styled(EuiFlexGroup)`
- max-width: 77px;
- position: absolute;
- top: 40%;
- width: 100%;
- z-index: 1;
-`;
-
-const CenteredFlexItem = styled(EuiFlexItem)`
- align-items: center;
- position: relative;
-`;
-
-const donutTheme: PartialTheme = {
- chartMargins: { top: 0, bottom: 0, left: 0, right: 0 },
- partition: {
- idealFontSizeJump: 1.1,
- outerSizeRatio: 1,
- emptySizeRatio: 0.8,
- circlePadding: 4,
- },
-};
-
-interface Props {
- defaultTabId: string;
- getGroupByFieldsOnClick: (
- elements: Array<
- | FlameElementEvent
- | HeatmapElementEvent
- | MetricElementEvent
- | PartitionElementEvent
- | WordCloudElementEvent
- | XYChartElementEvent
- >
- ) => {
- groupByField0: string;
- groupByField1: string;
- };
- height?: number;
- partitionedFieldMetadata: PartitionedFieldMetadata;
- setSelectedTabId: (tabId: string) => void;
- theme?: PartialTheme;
- baseTheme: Theme;
-}
-
-const EcsSummaryDonutChartComponent: React.FC = ({
- defaultTabId,
- getGroupByFieldsOnClick,
- height = DEFAULT_HEIGHT,
- partitionedFieldMetadata,
- setSelectedTabId,
- theme = {},
- baseTheme,
-}) => {
- const summaryData = useMemo(
- () => getSummaryData(partitionedFieldMetadata),
- [partitionedFieldMetadata]
- );
- const valueAccessor = useCallback((d: Datum) => d.mappings as number, []);
- const valueFormatter = useCallback((d: number) => `${d}`, []);
- const layers = useMemo(
- (): PartitionLayer[] => [
- {
- groupByRollup: (d: Datum) => d.categoryId,
- nodeLabel: (d: Datum) => getNodeLabel(d),
- shape: {
- fillColor: getFillColor,
- },
- },
- ],
- []
- );
- const showDefaultTab = useCallback(
- () => setSelectedTabId(defaultTabId),
- [defaultTabId, setSelectedTabId]
- );
- const onElementClick: ElementClickListener = useCallback(
- (event) => {
- const { groupByField0 } = getGroupByFieldsOnClick(event);
-
- setSelectedTabId(getTabId(groupByField0));
- },
- [getGroupByFieldsOnClick, setSelectedTabId]
- );
-
- if (allMetadataIsEmpty(partitionedFieldMetadata)) {
- return null;
- }
-
- return (
- <>
-
- {translations.CHART_TITLE}
-
-
-
-
-
-
-
-
-
- {partitionedFieldMetadata.all.length}
-
- {translations.FIELDS}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-EcsSummaryDonutChartComponent.displayName = 'EcsSummaryDonutChartComponent';
-
-export const EcsSummaryDonutChart = React.memo(EcsSummaryDonutChartComponent);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts
deleted file mode 100644
index 4df2f41cfdbd3..0000000000000
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ecs_summary_donut_chart/translations.ts
+++ /dev/null
@@ -1,22 +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 CHART_TITLE = i18n.translate(
- 'securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle',
- {
- defaultMessage: 'Field mappings',
- }
-);
-
-export const FIELDS = i18n.translate(
- 'securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel',
- {
- defaultMessage: 'Fields',
- }
-);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts
index bfe3ffc0c5e55..0058e2436a526 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts
@@ -6,7 +6,6 @@
*/
import { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types';
-import { euiThemeVars } from '@kbn/ui-theme';
import { omit } from 'lodash/fp';
import {
@@ -19,13 +18,11 @@ import {
getFieldTypes,
getPatternIlmPhaseDescription,
getIlmPhaseDescription,
- getIncompatibleStatColor,
getIndexNames,
getIsInSameFamily,
getMissingTimestampFieldMetadata,
getPartitionedFieldMetadata,
getPartitionedFieldMetadataStats,
- getSameFamilyStatColor,
getSizeInBytes,
getTotalDocsCount,
getTotalPatternIncompatible,
@@ -161,32 +158,6 @@ describe('helpers', () => {
});
});
- describe('getSameFamilyStatColor', () => {
- it('returns the expected color when sameFamily is greater than zero', () => {
- const result = getSameFamilyStatColor(1);
-
- expect(result).toEqual(euiThemeVars.euiColorLightShade);
- });
-
- it('returns undefined when sameFamily is 0', () => {
- const result = getSameFamilyStatColor(0);
-
- expect(result).toBeUndefined();
- });
-
- it('returns undefined when sameFamily is negative', () => {
- const result = getSameFamilyStatColor(-1);
-
- expect(result).toBeUndefined();
- });
-
- it('returns undefined when sameFamily is undefined', () => {
- const result = getSameFamilyStatColor(undefined);
-
- expect(result).toBeUndefined();
- });
- });
-
describe('getIndexNames', () => {
const isILMAvailable = true;
const ilmPhases = ['hot', 'warm', 'unmanaged'];
@@ -1208,26 +1179,6 @@ describe('helpers', () => {
});
});
- describe('getIncompatibleStatColor', () => {
- test('it returns the expected color when incompatible is greater than zero', () => {
- const incompatible = 123;
-
- expect(getIncompatibleStatColor(incompatible)).toBe('#bd271e');
- });
-
- test('it returns undefined when incompatible is zero', () => {
- const incompatible = 0;
-
- expect(getIncompatibleStatColor(incompatible)).toBeUndefined();
- });
-
- test('it returns undefined when incompatible is undefined', () => {
- const incompatible = undefined;
-
- expect(getIncompatibleStatColor(incompatible)).toBeUndefined();
- });
- });
-
describe('getErrorSummary', () => {
test('it returns the expected error summary', () => {
const resultWithError: DataQualityCheckResult = {
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts
index 0980ac3763347..9a56e00ba547a 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts
@@ -10,7 +10,6 @@ import type { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch
import { has, sortBy } from 'lodash/fp';
import { IToasts } from '@kbn/core-notifications-browser';
import { getIlmPhase } from './data_quality_panel/pattern/helpers';
-import { getFillColor } from './data_quality_panel/tabs/summary_tab/helpers';
import * as i18n from './translations';
@@ -34,7 +33,6 @@ import { EcsFlatTyped } from './constants';
const EMPTY_INDEX_NAMES: string[] = [];
export const INTERNAL_API_VERSION = '1';
-
export const getIndexNames = ({
ilmExplain,
ilmPhases,
@@ -401,11 +399,8 @@ export const getTotalPatternSameFamily = (
return allResults.reduce((acc, { sameFamily }) => acc + (sameFamily ?? 0), 0);
};
-export const getIncompatibleStatColor = (incompatible: number | undefined): string | undefined =>
- incompatible != null && incompatible > 0 ? getFillColor('incompatible') : undefined;
-
-export const getSameFamilyStatColor = (sameFamily: number | undefined): string | undefined =>
- sameFamily != null && sameFamily > 0 ? getFillColor('same-family') : undefined;
+export const getIncompatibleStatBadgeColor = (incompatible: number | undefined): string =>
+ incompatible != null && incompatible > 0 ? 'danger' : 'hollow';
export const getErrorSummary = ({
error,
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx
index 417f1419a7ca5..329ff56cd8326 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx
@@ -8,15 +8,15 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
-import { TestProviders } from '../mock/test_providers/test_providers';
+import { TestExternalProviders } from '../mock/test_providers/test_providers';
import { IlmPhasesEmptyPrompt } from '.';
describe('IlmPhasesEmptyPrompt', () => {
beforeEach(() => {
render(
-
+
-
+
);
});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx
index 720f2fc61da6c..53d576ccf6f70 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx
@@ -9,76 +9,36 @@ import { DARK_THEME } from '@elastic/charts';
import { render, screen } from '@testing-library/react';
import React from 'react';
-import { TestProviders } from './mock/test_providers/test_providers';
+import { TestExternalProviders } from './mock/test_providers/test_providers';
import { DataQualityPanel } from '.';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
const { toasts } = notificationServiceMock.createSetupContract();
describe('DataQualityPanel', () => {
- describe('when ILM phases are provided', () => {
- const ilmPhases: string[] = ['hot', 'warm', 'unmanaged'];
-
- beforeEach(() => {
- render(
-
-
-
- );
- });
-
- test('it does NOT render the ILM phases empty prompt', () => {
- expect(screen.queryByTestId('ilmPhasesEmptyPrompt')).not.toBeInTheDocument();
- });
-
- test('it renders the body', () => {
- expect(screen.getByTestId('body')).toBeInTheDocument();
- });
+ beforeEach(() => {
+ render(
+
+
+
+ );
});
- describe('when ILM phases are NOT provided', () => {
- test('it renders the ILM phases empty prompt', () => {
- const ilmPhases: string[] = [];
-
- render(
-
-
-
- );
-
- expect(screen.getByTestId('ilmPhasesEmptyPrompt')).toBeInTheDocument();
- });
+ test('it renders the body', () => {
+ expect(screen.getByTestId('body')).toBeInTheDocument();
});
});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx
index 6db2d8991db82..1fcedc7f76a82 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx
@@ -7,23 +7,20 @@
import type { HttpHandler } from '@kbn/core-http-browser';
import numeral from '@elastic/numeral';
-import type {
- FlameElementEvent,
- HeatmapElementEvent,
- MetricElementEvent,
- PartialTheme,
- PartitionElementEvent,
- Theme,
- WordCloudElementEvent,
- XYChartElementEvent,
-} from '@elastic/charts';
-import React, { useCallback, useMemo } from 'react';
+import type { PartialTheme, Theme } from '@elastic/charts';
+import React, { useCallback, useMemo, useState } from 'react';
import type { IToasts } from '@kbn/core-notifications-browser';
+import { EuiComboBoxOptionOption } from '@elastic/eui';
import { Body } from './data_quality_panel/body';
import { DataQualityProvider } from './data_quality_panel/data_quality_context';
import { EMPTY_STAT } from './helpers';
import { ReportDataQualityCheckAllCompleted, ReportDataQualityIndexChecked } from './types';
+import { ResultsRollupContext } from './contexts/results_rollup_context';
+import { IndicesCheckContext } from './contexts/indices_check_context';
+import { useIndicesCheck } from './use_indices_check';
+import { useResultsRollup } from './use_results_rollup';
+import { ilmPhaseOptionsStatic } from './constants';
interface Props {
toasts: IToasts;
@@ -32,21 +29,7 @@ interface Props {
defaultNumberFormat: string;
defaultBytesFormat: string;
endDate?: string | null;
- getGroupByFieldsOnClick: (
- elements: Array<
- | FlameElementEvent
- | HeatmapElementEvent
- | MetricElementEvent
- | PartitionElementEvent
- | WordCloudElementEvent
- | XYChartElementEvent
- >
- ) => {
- groupByField0: string;
- groupByField1: string;
- };
httpFetch: HttpHandler;
- ilmPhases: string[];
isAssistantEnabled: boolean;
isILMAvailable: boolean;
lastChecked: string;
@@ -65,6 +48,10 @@ interface Props {
theme?: PartialTheme;
}
+const defaultSelectedIlmPhaseOptions: EuiComboBoxOptionOption[] = ilmPhaseOptionsStatic.filter(
+ (option) => !option.disabled
+);
+
/** Renders the `Data Quality` dashboard content */
const DataQualityPanelComponent: React.FC = ({
toasts,
@@ -73,9 +60,7 @@ const DataQualityPanelComponent: React.FC = ({
defaultBytesFormat,
defaultNumberFormat,
endDate,
- getGroupByFieldsOnClick,
httpFetch,
- ilmPhases,
isAssistantEnabled,
isILMAvailable,
lastChecked,
@@ -87,6 +72,9 @@ const DataQualityPanelComponent: React.FC = ({
startDate,
theme,
}) => {
+ const [selectedIlmPhaseOptions, setSelectedIlmPhaseOptions] = useState(
+ defaultSelectedIlmPhaseOptions
+ );
const formatBytes = useCallback(
(value: number | undefined): string =>
value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT,
@@ -110,6 +98,23 @@ const DataQualityPanelComponent: React.FC = ({
},
[toasts]
);
+ const ilmPhases: string[] = useMemo(
+ () => selectedIlmPhaseOptions.map(({ label }) => label),
+ [selectedIlmPhaseOptions]
+ );
+
+ const resultsRollupHookReturnValue = useResultsRollup({
+ ilmPhases,
+ patterns,
+ httpFetch,
+ toasts,
+ isILMAvailable,
+ telemetryEvents,
+ });
+
+ const indicesCheckHookReturnValue = useIndicesCheck({
+ onCheckCompleted: resultsRollupHookReturnValue.onCheckCompleted,
+ });
return (
= ({
telemetryEvents={telemetryEvents}
isILMAvailable={isILMAvailable}
toasts={toasts}
+ addSuccessToast={addSuccessToast}
+ canUserCreateAndReadCases={canUserCreateAndReadCases}
+ endDate={endDate}
+ formatBytes={formatBytes}
+ formatNumber={formatNumber}
+ isAssistantEnabled={isAssistantEnabled}
+ lastChecked={lastChecked}
+ openCreateCaseFlyout={openCreateCaseFlyout}
+ patterns={patterns}
+ setLastChecked={setLastChecked}
+ startDate={startDate}
+ theme={theme}
+ baseTheme={baseTheme}
+ ilmPhases={ilmPhases}
+ selectedIlmPhaseOptions={selectedIlmPhaseOptions}
+ setSelectedIlmPhaseOptions={setSelectedIlmPhaseOptions}
>
-
+
+
+
+
+
);
};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx
index b4579dd4bd50c..713b01e8ef71e 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx
@@ -13,29 +13,33 @@ import { I18nProvider } from '@kbn/i18n-react';
import { euiDarkVars } from '@kbn/ui-theme';
import React from 'react';
import { ThemeProvider } from 'styled-components';
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { DataQualityProvider } from '../../data_quality_panel/data_quality_context';
+import { Theme } from '@elastic/charts';
+
+import {
+ DataQualityProvider,
+ DataQualityProviderProps,
+} from '../../data_quality_panel/data_quality_context';
+import { ResultsRollupContext } from '../../contexts/results_rollup_context';
+import { IndicesCheckContext } from '../../contexts/indices_check_context';
+import { UseIndicesCheckReturnValue } from '../../use_indices_check/types';
+import { UseResultsRollupReturnValue } from '../../use_results_rollup/types';
+import { getMergeResultsRollupContextProps } from './utils/get_merged_results_rollup_context_props';
+import { getMergedDataQualityContextProps } from './utils/get_merged_data_quality_context_props';
+import { getMergedIndicesCheckContextProps } from './utils/get_merged_indices_check_context_props';
-interface Props {
+interface TestExternalProvidersProps {
children: React.ReactNode;
- isILMAvailable?: boolean;
}
window.scrollTo = jest.fn();
/** A utility for wrapping children in the providers required to run tests */
-export const TestProvidersComponent: React.FC = ({ children, isILMAvailable = true }) => {
- const http = httpServiceMock.createSetupContract({ basePath: '/test' });
- const { toasts } = notificationServiceMock.createSetupContract();
+const TestExternalProvidersComponent: React.FC = ({ children }) => {
const actionTypeRegistry = actionTypeRegistryMock.create();
const mockGetComments = jest.fn(() => []);
const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' });
const mockNavigateToApp = jest.fn();
- const mockTelemetryEvents = {
- reportDataQualityIndexChecked: jest.fn(),
- reportDataQualityCheckAllCompleted: jest.fn(),
- };
const mockAssistantAvailability: AssistantAvailability = {
hasAssistantPrivilege: false,
hasConnectorsAllPrivilege: true,
@@ -75,14 +79,7 @@ export const TestProvidersComponent: React.FC = ({ children, isILMAvailab
navigateToApp={mockNavigateToApp}
currentAppId={'securitySolutionUI'}
>
-
- {children}
-
+ {children}
@@ -90,6 +87,90 @@ export const TestProvidersComponent: React.FC = ({ children, isILMAvailab
);
};
-TestProvidersComponent.displayName = 'TestProvidersComponent';
+TestExternalProvidersComponent.displayName = 'TestExternalProvidersComponent';
+
+export const TestExternalProviders = React.memo(TestExternalProvidersComponent);
+
+export interface TestDataQualityProvidersProps {
+ children: React.ReactNode;
+ dataQualityContextProps?: Partial;
+ indicesCheckContextProps?: Partial;
+ resultsRollupContextProps?: Partial;
+}
+
+const TestDataQualityProvidersComponent: React.FC = ({
+ children,
+ dataQualityContextProps,
+ resultsRollupContextProps,
+ indicesCheckContextProps,
+}) => {
+ const http = httpServiceMock.createSetupContract({ basePath: '/test' });
+ const { toasts } = notificationServiceMock.createSetupContract();
+ const mockTelemetryEvents = {
+ reportDataQualityIndexChecked: jest.fn(),
+ reportDataQualityCheckAllCompleted: jest.fn(),
+ };
+
+ const {
+ isILMAvailable,
+ addSuccessToast,
+ canUserCreateAndReadCases,
+ endDate,
+ formatBytes,
+ formatNumber,
+ isAssistantEnabled,
+ lastChecked,
+ openCreateCaseFlyout,
+ patterns,
+ setLastChecked,
+ startDate,
+ theme,
+ baseTheme,
+ ilmPhases,
+ selectedIlmPhaseOptions,
+ setSelectedIlmPhaseOptions,
+ } = getMergedDataQualityContextProps(dataQualityContextProps);
+
+ const mergedResultsRollupContextProps =
+ getMergeResultsRollupContextProps(resultsRollupContextProps);
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+};
+
+TestDataQualityProvidersComponent.displayName = 'TestDataQualityProvidersComponent';
-export const TestProviders = React.memo(TestProvidersComponent);
+export const TestDataQualityProviders = React.memo(TestDataQualityProvidersComponent);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_data_quality_context_props.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_data_quality_context_props.ts
new file mode 100644
index 0000000000000..d4cac9198e9c8
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_data_quality_context_props.ts
@@ -0,0 +1,97 @@
+/*
+ * 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 numeral from '@elastic/numeral';
+
+import { DataQualityProviderProps } from '../../../data_quality_panel/data_quality_context';
+import { EMPTY_STAT } from '../../../helpers';
+
+export const getMergedDataQualityContextProps = (
+ dataQualityContextProps?: Partial
+) => {
+ const {
+ isILMAvailable,
+ addSuccessToast,
+ canUserCreateAndReadCases,
+ endDate,
+ formatBytes,
+ formatNumber,
+ isAssistantEnabled,
+ lastChecked,
+ openCreateCaseFlyout,
+ patterns,
+ setLastChecked,
+ startDate,
+ theme,
+ baseTheme,
+ ilmPhases,
+ selectedIlmPhaseOptions,
+ setSelectedIlmPhaseOptions,
+ } = {
+ isILMAvailable: true,
+ addSuccessToast: jest.fn(),
+ canUserCreateAndReadCases: jest.fn(() => true),
+ endDate: null,
+ formatBytes: (value: number | undefined) =>
+ value != null ? numeral(value).format('0,0.[0]b') : EMPTY_STAT,
+ formatNumber: (value: number | undefined) =>
+ value != null ? numeral(value).format('0,0.[000]') : EMPTY_STAT,
+ isAssistantEnabled: true,
+ lastChecked: '2023-03-28T22:27:28.159Z',
+ openCreateCaseFlyout: jest.fn(),
+ patterns: ['auditbeat-*'],
+ setLastChecked: jest.fn(),
+ startDate: null,
+ theme: {
+ background: {
+ color: '#000',
+ },
+ },
+ baseTheme: {
+ background: {
+ color: '#000',
+ },
+ },
+ ilmPhases: ['hot', 'warm', 'unmanaged'],
+ selectedIlmPhaseOptions: [
+ {
+ label: 'hot',
+ value: 'hot',
+ },
+ {
+ label: 'warm',
+ value: 'warm',
+ },
+ {
+ label: 'unmanaged',
+ value: 'unmanaged',
+ },
+ ],
+ setSelectedIlmPhaseOptions: jest.fn(),
+ ...dataQualityContextProps,
+ };
+
+ return {
+ isILMAvailable,
+ addSuccessToast,
+ canUserCreateAndReadCases,
+ endDate,
+ formatBytes,
+ formatNumber,
+ isAssistantEnabled,
+ lastChecked,
+ openCreateCaseFlyout,
+ patterns,
+ setLastChecked,
+ startDate,
+ theme,
+ baseTheme,
+ ilmPhases,
+ selectedIlmPhaseOptions,
+ setSelectedIlmPhaseOptions,
+ };
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_indices_check_context_props.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_indices_check_context_props.ts
new file mode 100644
index 0000000000000..28129d7e8155f
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_indices_check_context_props.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 { getCheckState } from '../../../stub/get_check_state';
+import {
+ UseIndicesCheckCheckState,
+ UseIndicesCheckReturnValue,
+} from '../../../use_indices_check/types';
+
+export const getMergedIndicesCheckContextProps = (
+ patternIndexNames: Record,
+ indicesCheckContextProps?: Partial
+): UseIndicesCheckReturnValue => {
+ const checkState = Object.keys(patternIndexNames).reduce(
+ (acc, key) => {
+ for (const indexName of patternIndexNames[key]) {
+ acc[indexName] = {
+ ...getCheckState(indexName)[indexName],
+ };
+ }
+
+ return acc;
+ },
+ {}
+ );
+
+ return {
+ checkIndex: jest.fn(),
+ checkState,
+ ...indicesCheckContextProps,
+ };
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_results_rollup_context_props.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_results_rollup_context_props.ts
new file mode 100644
index 0000000000000..bb8e3e6967b4d
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_results_rollup_context_props.ts
@@ -0,0 +1,62 @@
+/*
+ * 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 { UseResultsRollupReturnValue } from '../../../use_results_rollup/types';
+import { auditbeatWithAllResults } from '../../pattern_rollup/mock_auditbeat_pattern_rollup';
+
+export const getMergeResultsRollupContextProps = (
+ resultsRollupContextProps?: Partial
+) => {
+ const {
+ onCheckCompleted,
+ patternIndexNames,
+ patternRollups,
+ totalDocsCount,
+ totalIncompatible,
+ totalIndices,
+ totalIndicesChecked,
+ totalSameFamily,
+ totalSizeInBytes,
+ updatePatternIndexNames,
+ updatePatternRollup,
+ } = {
+ onCheckCompleted: jest.fn(),
+ patternIndexNames: {
+ 'auditbeat-*': [
+ '.ds-auditbeat-8.6.1-2023.02.07-000001',
+ 'auditbeat-custom-index-1',
+ 'auditbeat-custom-empty-index-1',
+ ],
+ },
+ patternRollups: {
+ 'auditbeat-*': auditbeatWithAllResults,
+ },
+ totalDocsCount: 19127,
+ totalIncompatible: 4,
+ totalIndices: 3,
+ totalIndicesChecked: 3,
+ totalSameFamily: 0,
+ totalSizeInBytes: 18820446,
+ updatePatternIndexNames: jest.fn(),
+ updatePatternRollup: jest.fn(),
+ ...resultsRollupContextProps,
+ };
+
+ return {
+ onCheckCompleted,
+ patternIndexNames,
+ patternRollups,
+ totalDocsCount,
+ totalIncompatible,
+ totalIndices,
+ totalIndicesChecked,
+ totalSameFamily,
+ totalSizeInBytes,
+ updatePatternIndexNames,
+ updatePatternRollup,
+ };
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/stub/get_check_state/index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/stub/get_check_state/index.ts
new file mode 100644
index 0000000000000..c1ba27ac91376
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/stub/get_check_state/index.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types';
+
+import {
+ getMappingsProperties,
+ getSortedPartitionedFieldMetadata,
+} from '../../data_quality_panel/index_properties/helpers';
+import { mockMappingsResponse } from '../../mock/mappings_response/mock_mappings_response';
+import { UseIndicesCheckCheckState } from '../../use_indices_check/types';
+import { getUnallowedValues } from '../../use_unallowed_values/helpers';
+import { getUnallowedValueRequestItems } from '../../data_quality_panel/allowed_values/helpers';
+import { EcsFlatTyped } from '../../constants';
+import { mockUnallowedValuesResponse } from '../../mock/unallowed_values/mock_unallowed_values';
+import { UnallowedValueSearchResult } from '../../types';
+
+export const getCheckState = (
+ indexName: string,
+ indexCheckState?: Partial
+) => {
+ const mappingsProperties = getMappingsProperties({
+ indexName,
+ indexes: mockMappingsResponse as Record,
+ });
+ const unallowedValues = getUnallowedValues({
+ requestItems: getUnallowedValueRequestItems({
+ ecsMetadata: EcsFlatTyped,
+ indexName,
+ }),
+ searchResults: mockUnallowedValuesResponse as unknown as UnallowedValueSearchResult[],
+ });
+ const partitionedFieldMetadata = getSortedPartitionedFieldMetadata({
+ ecsMetadata: EcsFlatTyped,
+ loadingMappings: false,
+ mappingsProperties,
+ unallowedValues,
+ });
+ return {
+ [indexName]: {
+ isChecking: false,
+ isLoadingMappings: false,
+ isLoadingUnallowedValues: false,
+ indexes: mockMappingsResponse as Record,
+ partitionedFieldMetadata,
+ searchResults: mockUnallowedValuesResponse as unknown as UnallowedValueSearchResult[],
+ unallowedValues,
+ mappingsProperties,
+ genericError: null,
+ mappingsError: null,
+ unallowedValuesError: null,
+ isCheckComplete: true,
+ ...indexCheckState,
+ },
+ };
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx
index 6fbf130d01b8f..d54ea9d6316e2 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiCode, EuiText } from '@elastic/eui';
+import { EuiCode } from '@elastic/eui';
import { euiThemeVars } from '@kbn/ui-theme';
import styled from 'styled-components';
@@ -21,10 +21,3 @@ export const CodeSuccess = styled(EuiCode)`
export const CodeWarning = styled(EuiCode)`
color: ${euiThemeVars.euiColorWarning};
`;
-
-export const FixedWidthLegendText = styled(EuiText)<{
- $width: number | undefined;
-}>`
- text-align: left;
- ${({ $width }) => ($width != null ? `width: ${$width}px;` : '')}
-`;
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts
index b72a9fee96c57..6c78513274de8 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts
@@ -248,7 +248,7 @@ export const SELECT_ONE_OR_MORE_ILM_PHASES: string = i18n.translate(
export const INDEX_SIZE_TOOLTIP = i18n.translate(
'securitySolutionPackages.ecsDataQualityDashboard.indexSizeTooltip',
{
- defaultMessage: 'The size of the primary index (does not include replicas)',
+ defaultMessage: 'Size of index (exluding replicas)',
}
);
@@ -302,3 +302,42 @@ export const GET_RESULTS_ERROR_TITLE = i18n.translate(
'securitySolutionPackages.ecsDataQualityDashboard.getResultErrorTitle',
{ defaultMessage: 'Error reading saved data quality check results' }
);
+
+export const COLD = i18n.translate(
+ 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseCold',
+ {
+ defaultMessage: 'cold',
+ }
+);
+
+export const FROZEN = i18n.translate(
+ 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseFrozen',
+ {
+ defaultMessage: 'frozen',
+ }
+);
+
+export const HOT = i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseHot', {
+ defaultMessage: 'hot',
+});
+
+export const WARM = i18n.translate(
+ 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseWarm',
+ {
+ defaultMessage: 'warm',
+ }
+);
+
+export const UNMANAGED = i18n.translate(
+ 'securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseUnmanaged',
+ {
+ defaultMessage: 'unmanaged',
+ }
+);
+
+export const DATA_QUALITY_DASHBOARD_CONVERSATION_ID = i18n.translate(
+ 'securitySolutionPackages.ecsDataQualityDashboard.dataQualityDashboardConversationId',
+ {
+ defaultMessage: 'Data Quality dashboard',
+ }
+);
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts
index ed20efc3fd959..dd5dd6dd0a159 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts
@@ -200,15 +200,15 @@ export interface IndexToCheck {
export type OnCheckCompleted = (param: {
batchId: string;
+ isCheckAll: boolean;
+ isLastCheck: boolean;
checkAllStartTime: number;
error: string | null;
formatBytes: (value: number | undefined) => string;
formatNumber: (value: number | undefined) => string;
indexName: string;
- isLastCheck: boolean;
partitionedFieldMetadata: PartitionedFieldMetadata | null;
pattern: string;
- version: string;
requestTime?: number;
}) => void;
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.test.tsx
new file mode 100644
index 0000000000000..ffbeb191e5582
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.test.tsx
@@ -0,0 +1,64 @@
+/*
+ * 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 { renderHook, act } from '@testing-library/react-hooks';
+
+import { useCurrentWindowWidth } from '.';
+import { fireEvent } from '@testing-library/react';
+
+describe('useCurrentWidthWidth', () => {
+ beforeEach(() => {
+ Object.defineProperty(window, 'innerWidth', {
+ writable: true,
+ configurable: true,
+ value: 1024,
+ });
+ });
+ it('return current window width', () => {
+ const { result } = renderHook(() => useCurrentWindowWidth());
+ expect(result.error).toBeUndefined();
+ expect(result.current).toBe(window.innerWidth);
+ });
+
+ it('return last-throttled value of window.innerWidth with interval of 250ms', async () => {
+ jest.useFakeTimers();
+ const { result } = renderHook(() => useCurrentWindowWidth());
+
+ // first resize within throttle interval
+ fireEvent.resize(window, { target: { innerWidth: 500 } });
+ act(() => {
+ jest.advanceTimersByTime(200);
+ });
+
+ expect(result.current).toBe(1024);
+
+ // second resize within throttle interval
+ fireEvent.resize(window, { target: { innerWidth: 400 } });
+ act(() => {
+ jest.advanceTimersByTime(49);
+ });
+
+ expect(result.current).toBe(1024);
+
+ // third and final resize after throttle interval
+ fireEvent.resize(window, { target: { innerWidth: 600 } });
+ act(() => {
+ jest.advanceTimersByTime(200);
+ });
+
+ expect(result.current).toBe(600);
+
+ // release all timers to confirm the final value
+ act(() => {
+ jest.runAllTimers();
+ });
+
+ expect(result.current).toBe(600);
+
+ jest.useRealTimers();
+ });
+});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.tsx
new file mode 100644
index 0000000000000..0ef0febcded4d
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 throttle from 'lodash/throttle';
+import { useEffect, useState } from 'react';
+
+export const useCurrentWindowWidth = () => {
+ const [currentWidth, setCurrentWidth] = useState(() => window.innerWidth);
+
+ useEffect(() => {
+ const handleWindowResize = throttle(
+ () => {
+ setCurrentWidth(window.innerWidth);
+ },
+ 250,
+ { leading: false }
+ );
+
+ window.addEventListener('resize', handleWindowResize);
+
+ return () => window.removeEventListener('resize', handleWindowResize);
+ }, []);
+
+ return currentWidth;
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.test.tsx
index f715936501736..b8c6092fea0d0 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.test.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.test.tsx
@@ -13,6 +13,7 @@ import { mockIlmExplain } from '../mock/ilm_explain/mock_ilm_explain';
import { ERROR_LOADING_ILM_EXPLAIN } from '../translations';
import { useIlmExplain, UseIlmExplain } from '.';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
+import { Theme } from '@elastic/charts';
const mockHttpFetch = jest.fn();
const mockReportDataQualityIndexChecked = jest.fn();
@@ -31,6 +32,45 @@ const ContextWrapper: React.FC<{ children: React.ReactNode; isILMAvailable: bool
telemetryEvents={mockTelemetryEvents}
isILMAvailable={isILMAvailable}
toasts={toasts}
+ addSuccessToast={jest.fn()}
+ canUserCreateAndReadCases={jest.fn(() => true)}
+ endDate={null}
+ formatBytes={jest.fn()}
+ formatNumber={jest.fn()}
+ isAssistantEnabled={true}
+ lastChecked={'2023-03-28T22:27:28.159Z'}
+ openCreateCaseFlyout={jest.fn()}
+ patterns={['auditbeat-*']}
+ setLastChecked={jest.fn()}
+ startDate={null}
+ theme={{
+ background: {
+ color: '#000',
+ },
+ }}
+ baseTheme={
+ {
+ background: {
+ color: '#000',
+ },
+ } as Theme
+ }
+ ilmPhases={['hot', 'warm', 'unmanaged']}
+ selectedIlmPhaseOptions={[
+ {
+ label: 'Hot',
+ value: 'hot',
+ },
+ {
+ label: 'Warm',
+ value: 'warm',
+ },
+ {
+ label: 'Unmanaged',
+ value: 'unmanaged',
+ },
+ ]}
+ setSelectedIlmPhaseOptions={jest.fn()}
>
{children}
@@ -80,6 +120,45 @@ describe('useIlmExplain', () => {
telemetryEvents={mockTelemetryEvents}
isILMAvailable={false}
toasts={toasts}
+ addSuccessToast={jest.fn()}
+ canUserCreateAndReadCases={jest.fn(() => true)}
+ endDate={null}
+ formatBytes={jest.fn()}
+ formatNumber={jest.fn()}
+ isAssistantEnabled={true}
+ lastChecked={'2023-03-28T22:27:28.159Z'}
+ openCreateCaseFlyout={jest.fn()}
+ patterns={['auditbeat-*']}
+ setLastChecked={jest.fn()}
+ startDate={null}
+ theme={{
+ background: {
+ color: '#000',
+ },
+ }}
+ baseTheme={
+ {
+ background: {
+ color: '#000',
+ },
+ } as Theme
+ }
+ ilmPhases={['hot', 'warm', 'unmanaged']}
+ selectedIlmPhaseOptions={[
+ {
+ label: 'Hot',
+ value: 'hot',
+ },
+ {
+ label: 'Warm',
+ value: 'warm',
+ },
+ {
+ label: 'Unmanaged',
+ value: 'unmanaged',
+ },
+ ]}
+ setSelectedIlmPhaseOptions={jest.fn()}
>
{children}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx
index 4e95549338874..8477d710fea76 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx
@@ -11,6 +11,7 @@ import { useEffect, useState } from 'react';
import { useDataQualityContext } from '../data_quality_panel/data_quality_context';
import { INTERNAL_API_VERSION } from '../helpers';
import * as i18n from '../translations';
+import { useIsMounted } from '../use_is_mounted';
const ILM_EXPLAIN_ENDPOINT = '/internal/ecs_data_quality_dashboard/ilm_explain';
@@ -22,6 +23,7 @@ export interface UseIlmExplain {
export const useIlmExplain = (pattern: string): UseIlmExplain => {
const { httpFetch, isILMAvailable } = useDataQualityContext();
+ const { isMountedRef } = useIsMounted();
const [ilmExplain, setIlmExplain] = useState {
);
if (!abortController.signal.aborted) {
- setIlmExplain(response);
+ if (isMountedRef.current) {
+ setIlmExplain(response);
+ }
}
} catch (e) {
if (!abortController.signal.aborted) {
- setError(i18n.ERROR_LOADING_ILM_EXPLAIN(e.message));
+ if (isMountedRef.current) {
+ setError(i18n.ERROR_LOADING_ILM_EXPLAIN(e.message));
+ }
}
} finally {
- setLoading(false);
+ if (isMountedRef.current) {
+ setLoading(false);
+ }
}
}
@@ -65,7 +73,7 @@ export const useIlmExplain = (pattern: string): UseIlmExplain => {
return () => {
abortController.abort();
};
- }, [httpFetch, isILMAvailable, pattern, setError]);
+ }, [httpFetch, isILMAvailable, isMountedRef, pattern, setError]);
return { ilmExplain, error, loading };
};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.test.tsx
new file mode 100644
index 0000000000000..b129102e5516e
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.test.tsx
@@ -0,0 +1,386 @@
+/*
+ * 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 { act, renderHook } from '@testing-library/react-hooks';
+
+import { useIndicesCheck } from '.';
+
+import * as utilsCheckIndex from '../utils/check_index';
+import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values';
+import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_response';
+import { HttpHandler } from '@kbn/core-http-browser';
+import { MappingsError } from '../use_mappings/helpers';
+import { UnallowedValuesError } from '../use_unallowed_values/helpers';
+import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types';
+import { UnallowedValueSearchResult } from '../types';
+import { getInitialCheckStateValue } from './reducer';
+
+const getSpies = () => {
+ return {
+ checkIndexSpy: jest.spyOn(utilsCheckIndex, 'checkIndex').mockImplementation(jest.fn()),
+ };
+};
+
+describe('useIndicesCheck', () => {
+ beforeEach(() => {
+ jest.restoreAllMocks();
+ jest.clearAllMocks();
+ });
+
+ it('should return checkIndex and checkState', () => {
+ const { result } = renderHook(() => useIndicesCheck({ onCheckCompleted: jest.fn() }));
+
+ expect(result.current).toEqual({
+ checkIndex: expect.any(Function),
+ checkState: expect.any(Object),
+ });
+ });
+
+ describe('checkIndex', () => {
+ it('should call checkIndex with the correct arguments', () => {
+ const { checkIndexSpy } = getSpies();
+
+ const { result } = renderHook(() => useIndicesCheck({ onCheckCompleted: jest.fn() }));
+
+ const props = {
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'indexName',
+ pattern: 'pattern',
+ httpFetch: jest.fn(),
+ };
+
+ act(() => {
+ result.current.checkIndex(props);
+ });
+
+ expect(checkIndexSpy).toHaveBeenCalledWith({
+ ...props,
+ onCheckCompleted: expect.any(Function),
+ onLoadMappingsStart: expect.any(Function),
+ onLoadMappingsSuccess: expect.any(Function),
+ onLoadUnallowedValuesStart: expect.any(Function),
+ onLoadUnallowedValuesSuccess: expect.any(Function),
+ onStart: expect.any(Function),
+ onSuccess: expect.any(Function),
+ onError: expect.any(Function),
+ });
+ });
+ });
+
+ describe('checkState', () => {
+ it('should be empty by default', () => {
+ const { result } = renderHook(() => useIndicesCheck({ onCheckCompleted: jest.fn() }));
+
+ expect(result.current.checkState).toEqual({});
+ });
+
+ describe('when checkIndex completes', () => {
+ it('should set correct data', async () => {
+ const { result, waitFor } = renderHook(() =>
+ useIndicesCheck({ onCheckCompleted: jest.fn() })
+ );
+
+ const httpFetchMock = jest.fn((route) => {
+ if (route.startsWith('/internal/ecs_data_quality_dashboard/mappings')) {
+ return Promise.resolve(mockMappingsResponse);
+ }
+
+ if (route.startsWith('/internal/ecs_data_quality_dashboard/unallowed_field_values')) {
+ return Promise.resolve(mockUnallowedValuesResponse);
+ }
+ });
+
+ act(() => {
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: httpFetchMock as unknown as HttpHandler,
+ });
+ });
+
+ await waitFor(() =>
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ indexes: expect.any(Object),
+ partitionedFieldMetadata: expect.any(Object),
+ searchResults: expect.any(Object),
+ unallowedValues: expect.any(Object),
+ mappingsProperties: expect.any(Object),
+ isCheckComplete: true,
+ })
+ );
+ });
+ });
+
+ describe('errors', () => {
+ describe('when mappings request errors', () => {
+ it('should set mappingsError', async () => {
+ const { result, waitFor } = renderHook(() =>
+ useIndicesCheck({ onCheckCompleted: jest.fn() })
+ );
+
+ const httpFetchMock = jest.fn((route) => {
+ if (route.startsWith('/internal/ecs_data_quality_dashboard/mappings')) {
+ return Promise.reject(new Error('mappings error'));
+ }
+
+ if (route.startsWith('/internal/ecs_data_quality_dashboard/unallowed_field_values')) {
+ return Promise.reject(new Error('unallowed values error'));
+ }
+ });
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: httpFetchMock as unknown as HttpHandler,
+ })
+ );
+
+ await waitFor(() =>
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ mappingsError: expect.any(MappingsError),
+ })
+ );
+ });
+ });
+
+ describe('when unallowed values request errors', () => {
+ it('should set unallowedValuesError', async () => {
+ const { result, waitFor } = renderHook(() =>
+ useIndicesCheck({ onCheckCompleted: jest.fn() })
+ );
+
+ const httpFetchMock = jest.fn((route) => {
+ if (route.startsWith('/internal/ecs_data_quality_dashboard/mappings')) {
+ return Promise.resolve(mockMappingsResponse);
+ }
+
+ if (route.startsWith('/internal/ecs_data_quality_dashboard/unallowed_field_values')) {
+ return Promise.reject(new Error('unallowed values error'));
+ }
+ });
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: httpFetchMock as unknown as HttpHandler,
+ })
+ );
+
+ await waitFor(() =>
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ unallowedValuesError: expect.any(UnallowedValuesError),
+ })
+ );
+ });
+ });
+
+ describe('when anything else errors', () => {
+ it('should set genericError', () => {
+ const { checkIndexSpy } = getSpies();
+
+ checkIndexSpy.mockImplementation(async ({ onError }) => {
+ onError?.(new Error('generic error'));
+ });
+
+ const { result } = renderHook(() => useIndicesCheck({ onCheckCompleted: jest.fn() }));
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: jest.fn(),
+ })
+ );
+
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ genericError: expect.any(Error),
+ });
+ });
+ });
+ });
+
+ describe('lifecycle states', () => {
+ describe('when check is started', () => {
+ it('it should set isChecking to true', async () => {
+ const { checkIndexSpy } = getSpies();
+
+ checkIndexSpy.mockImplementation(async ({ onStart }) => {
+ onStart?.();
+ });
+
+ const { result, waitFor } = renderHook(() =>
+ useIndicesCheck({ onCheckCompleted: jest.fn() })
+ );
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: jest.fn(),
+ })
+ );
+
+ await waitFor(() =>
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ isChecking: true,
+ })
+ );
+ });
+ });
+
+ describe('when mappings are loading', () => {
+ it('it should set isLoadingMappings to true', () => {
+ const { checkIndexSpy } = getSpies();
+
+ checkIndexSpy.mockImplementation(async ({ onStart, onLoadMappingsStart }) => {
+ onStart?.();
+ onLoadMappingsStart?.();
+ });
+
+ const { result, waitFor } = renderHook(() =>
+ useIndicesCheck({ onCheckCompleted: jest.fn() })
+ );
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: jest.fn(),
+ })
+ );
+
+ waitFor(() =>
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ isChecking: true,
+ isLoadingMappings: true,
+ })
+ );
+ });
+ });
+
+ describe('when unallowed values are loading', () => {
+ it('it should set isLoadingUnallowedValues to true', () => {
+ const { checkIndexSpy } = getSpies();
+
+ checkIndexSpy.mockImplementation(async ({ onStart, onLoadUnallowedValuesStart }) => {
+ onStart?.();
+ onLoadUnallowedValuesStart?.();
+ });
+
+ const { result } = renderHook(() => useIndicesCheck({ onCheckCompleted: jest.fn() }));
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: jest.fn(),
+ })
+ );
+
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ isChecking: true,
+ isLoadingUnallowedValues: true,
+ });
+ });
+ });
+
+ describe('when mappings are loaded', () => {
+ it('should set indexes', () => {
+ const { checkIndexSpy } = getSpies();
+
+ checkIndexSpy.mockImplementation(async ({ onStart, onLoadMappingsSuccess }) => {
+ onStart?.();
+ onLoadMappingsSuccess?.(
+ mockMappingsResponse as Record
+ );
+ });
+
+ const { result } = renderHook(() => useIndicesCheck({ onCheckCompleted: jest.fn() }));
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: jest.fn(),
+ })
+ );
+
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ indexes: mockMappingsResponse,
+ });
+ });
+ });
+
+ describe('when unallowed values are loaded', () => {
+ it('should set searchResults', () => {
+ const { checkIndexSpy } = getSpies();
+
+ checkIndexSpy.mockImplementation(async ({ onStart, onLoadUnallowedValuesSuccess }) => {
+ onStart?.();
+ onLoadUnallowedValuesSuccess?.(
+ mockUnallowedValuesResponse as unknown as UnallowedValueSearchResult[]
+ );
+ });
+
+ const { result } = renderHook(() => useIndicesCheck({ onCheckCompleted: jest.fn() }));
+
+ act(() =>
+ result.current.checkIndex({
+ abortController: new AbortController(),
+ formatBytes: jest.fn(),
+ formatNumber: jest.fn(),
+ indexName: 'auditbeat-custom-index-1',
+ pattern: 'auditbeat-*',
+ httpFetch: jest.fn(),
+ })
+ );
+
+ expect(result.current.checkState['auditbeat-custom-index-1']).toEqual({
+ ...getInitialCheckStateValue(),
+ searchResults: mockUnallowedValuesResponse,
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.tsx
new file mode 100644
index 0000000000000..acbad56a613c5
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.tsx
@@ -0,0 +1,105 @@
+/*
+ * 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 { useReducer, useCallback } from 'react';
+import { OnCheckCompleted } from '../types';
+import { MappingsError } from '../use_mappings/helpers';
+import { UnallowedValuesError } from '../use_unallowed_values/helpers';
+import { checkIndex as _checkIndex, CheckIndexProps } from '../utils/check_index';
+import { initialState, reducer } from './reducer';
+import { UseIndicesCheckReturnValue } from './types';
+import { useIsMounted } from '../use_is_mounted';
+
+export const useIndicesCheck = ({
+ onCheckCompleted,
+}: {
+ onCheckCompleted: OnCheckCompleted;
+}): UseIndicesCheckReturnValue => {
+ const { isMountedRef } = useIsMounted();
+ const [state, dispatch] = useReducer(reducer, initialState);
+
+ const checkIndex = useCallback(
+ ({
+ abortController,
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ pattern,
+ batchId,
+ checkAllStartTime,
+ isCheckAll,
+ isLastCheck,
+ }: Omit) => {
+ _checkIndex({
+ batchId,
+ abortController,
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ onCheckCompleted,
+ pattern,
+ checkAllStartTime,
+ isCheckAll,
+ isLastCheck,
+ onStart: () => {
+ if (!isMountedRef.current) return;
+ dispatch({ type: 'START', data: { indexName } });
+ },
+ onLoadMappingsStart: () => {
+ if (!isMountedRef.current) return;
+ dispatch({ type: 'LOAD_MAPPINGS_START', data: { indexName } });
+ },
+ onLoadUnallowedValuesStart: () => {
+ if (!isMountedRef.current) return;
+ dispatch({ type: 'LOAD_UNALLOWED_VALUES_START', data: { indexName } });
+ },
+ onSuccess: ({ partitionedFieldMetadata, mappingsProperties, unallowedValues }) => {
+ if (!isMountedRef.current) return;
+ dispatch({
+ type: 'SUCCESS',
+ data: {
+ indexName,
+ partitionedFieldMetadata,
+ mappingsProperties,
+ unallowedValues,
+ },
+ });
+ },
+ onLoadMappingsSuccess: (indexes) => {
+ if (!isMountedRef.current) return;
+ dispatch({ type: 'LOAD_MAPPINGS_SUCCESS', data: { indexName, indexes } });
+ },
+ onLoadUnallowedValuesSuccess: (searchResults) => {
+ if (!isMountedRef.current) return;
+ dispatch({ type: 'LOAD_UNALLOWED_VALUES_SUCCESS', data: { indexName, searchResults } });
+ },
+ onError: (error) => {
+ if (!isMountedRef.current) return;
+ if (error instanceof MappingsError) {
+ dispatch({ type: 'LOAD_MAPPINGS_ERROR', data: { indexName, error } });
+ } else if (error instanceof UnallowedValuesError) {
+ dispatch({ type: 'LOAD_UNALLOWED_VALUES_ERROR', data: { indexName, error } });
+ } else {
+ if (error instanceof Error) {
+ dispatch({ type: 'GENERIC_ERROR', data: { indexName, error } });
+ } else {
+ dispatch({ type: 'GENERIC_ERROR', data: { indexName, error: 'Unknown error' } });
+ }
+ }
+ },
+ });
+ },
+ [isMountedRef, onCheckCompleted]
+ );
+
+ return {
+ ...state,
+ checkIndex,
+ };
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/reducer.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/reducer.ts
new file mode 100644
index 0000000000000..ca596643605ce
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/reducer.ts
@@ -0,0 +1,194 @@
+/*
+ * 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 {
+ IndicesGetMappingIndexMappingRecord,
+ MappingProperty,
+} from '@elastic/elasticsearch/lib/api/types';
+
+import {
+ PartitionedFieldMetadata,
+ UnallowedValueCount,
+ UnallowedValueSearchResult,
+} from '../types';
+import { MappingsError } from '../use_mappings/helpers';
+import { UnallowedValuesError } from '../use_unallowed_values/helpers';
+import { UseIndicesCheckState } from './types';
+
+type Action = { data: { indexName: string } } & (
+ | {
+ type: 'START' | 'LOAD_MAPPINGS_START' | 'LOAD_UNALLOWED_VALUES_START';
+ }
+ | {
+ type: 'SUCCESS';
+ data: {
+ partitionedFieldMetadata: PartitionedFieldMetadata;
+ mappingsProperties: Record | null;
+ unallowedValues: Record;
+ };
+ }
+ | {
+ type: 'LOAD_MAPPINGS_SUCCESS';
+ data: { indexes: Record };
+ }
+ | {
+ type: 'LOAD_UNALLOWED_VALUES_SUCCESS';
+ data: { searchResults: UnallowedValueSearchResult[] };
+ }
+ | { type: 'GENERIC_ERROR'; data: { error: string | Error } }
+ | { type: 'LOAD_MAPPINGS_ERROR'; data: { error: MappingsError } }
+ | { type: 'LOAD_UNALLOWED_VALUES_ERROR'; data: { error: UnallowedValuesError } }
+);
+
+// intentionally returning a new object every time
+// instead of caching the initial state
+// to avoid potential mutations when spreaded
+// across actions
+export const getInitialCheckStateValue = () => ({
+ isChecking: false,
+ isLoadingMappings: false,
+ isLoadingUnallowedValues: false,
+ indexes: null,
+ partitionedFieldMetadata: null,
+ searchResults: null,
+ unallowedValues: null,
+ mappingsProperties: null,
+ genericError: null,
+ mappingsError: null,
+ unallowedValuesError: null,
+ isCheckComplete: false,
+});
+
+export const initialState: UseIndicesCheckState = {
+ checkState: {},
+};
+
+export const reducer = (state: UseIndicesCheckState, action: Action): UseIndicesCheckState => {
+ switch (action.type) {
+ case 'START':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...getInitialCheckStateValue(),
+ isChecking: true,
+ },
+ },
+ };
+ case 'LOAD_MAPPINGS_START':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...state.checkState[action.data.indexName],
+ isChecking: true,
+ isLoadingMappings: true,
+ },
+ },
+ };
+ case 'LOAD_UNALLOWED_VALUES_START':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...state.checkState[action.data.indexName],
+ isChecking: true,
+ isLoadingUnallowedValues: true,
+ },
+ },
+ };
+ case 'SUCCESS':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...state.checkState[action.data.indexName],
+ isChecking: false,
+ isLoadingMappings: false,
+ isLoadingUnallowedValues: false,
+ genericError: null,
+ mappingsError: null,
+ unallowedValuesError: null,
+ partitionedFieldMetadata: action.data.partitionedFieldMetadata,
+ unallowedValues: action.data.unallowedValues,
+ mappingsProperties: action.data.mappingsProperties,
+ isCheckComplete: true,
+ },
+ },
+ };
+ case 'LOAD_MAPPINGS_SUCCESS':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...state.checkState[action.data.indexName],
+ isChecking: false,
+ isLoadingMappings: false,
+ isLoadingUnallowedValues: false,
+ genericError: null,
+ mappingsError: null,
+ indexes: action.data.indexes,
+ },
+ },
+ };
+ case 'LOAD_UNALLOWED_VALUES_SUCCESS':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...state.checkState[action.data.indexName],
+ isChecking: false,
+ isLoadingUnallowedValues: false,
+ genericError: null,
+ unallowedValuesError: null,
+ searchResults: action.data.searchResults,
+ },
+ },
+ };
+ case 'GENERIC_ERROR':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...getInitialCheckStateValue(),
+ genericError: action.data.error,
+ },
+ },
+ };
+ case 'LOAD_MAPPINGS_ERROR':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...getInitialCheckStateValue(),
+ mappingsError: action.data.error,
+ },
+ },
+ };
+ case 'LOAD_UNALLOWED_VALUES_ERROR':
+ return {
+ ...state,
+ checkState: {
+ ...state.checkState,
+ [action.data.indexName]: {
+ ...getInitialCheckStateValue(),
+ unallowedValuesError: action.data.error,
+ },
+ },
+ };
+ default:
+ throw new Error('Invalid action type');
+ }
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/types.ts
new file mode 100644
index 0000000000000..c7f0145f68f15
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/types.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 {
+ IndicesGetMappingIndexMappingRecord,
+ MappingProperty,
+} from '@elastic/elasticsearch/lib/api/types';
+import {
+ PartitionedFieldMetadata,
+ UnallowedValueCount,
+ UnallowedValueSearchResult,
+} from '../types';
+import { MappingsError } from '../use_mappings/helpers';
+import { UnallowedValuesError } from '../use_unallowed_values/helpers';
+import { CheckIndexProps } from '../utils/check_index';
+
+export interface UseIndicesCheckCheckState {
+ [indexName: string]: {
+ isChecking: boolean;
+ isLoadingMappings: boolean;
+ isLoadingUnallowedValues: boolean;
+ indexes: Record | null;
+ partitionedFieldMetadata: PartitionedFieldMetadata | null;
+ searchResults: UnallowedValueSearchResult[] | null;
+ unallowedValues: Record | null;
+ mappingsProperties: Record | null;
+ genericError: string | Error | null;
+ mappingsError: MappingsError | null;
+ unallowedValuesError: UnallowedValuesError | null;
+ isCheckComplete: boolean;
+ };
+}
+
+export interface UseIndicesCheckState {
+ checkState: UseIndicesCheckCheckState;
+}
+
+export interface UseIndicesCheckReturnValue extends UseIndicesCheckState {
+ checkIndex: (props: Omit) => void;
+}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.test.tsx
new file mode 100644
index 0000000000000..d29aaf8ace201
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.test.tsx
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { act, renderHook } from '@testing-library/react-hooks';
+
+import { useIsMounted } from '.';
+
+describe('useIsMounted', () => {
+ it('should return a ref that is true when mounted and false when unmounted', () => {
+ const { result, unmount } = renderHook(() => useIsMounted());
+
+ expect(result.current.isMountedRef.current).toBe(true);
+
+ act(() => {
+ unmount();
+ });
+
+ expect(result.current.isMountedRef.current).toBe(false);
+ });
+});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.tsx
new file mode 100644
index 0000000000000..6ba5ebacf8ea0
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.tsx
@@ -0,0 +1,25 @@
+/*
+ * 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 { MutableRefObject, useEffect, useRef } from 'react';
+
+/**
+ * Hook that returns a ref that is true when mounted and false when unmounted.
+ *
+ * Main use case is to avoid setting state on an unmounted component.
+ */
+export const useIsMounted = (): { isMountedRef: MutableRefObject } => {
+ const isMountedRef = useRef(true);
+
+ useEffect(() => {
+ return () => {
+ isMountedRef.current = false;
+ };
+ }, []);
+
+ return { isMountedRef };
+};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts
index 809f543c0c0ae..6bee012883c05 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts
@@ -34,6 +34,16 @@ export async function fetchMappings({
}
);
} catch (e) {
- throw new Error(i18n.ERROR_LOADING_MAPPINGS({ details: e.message, patternOrIndexName }));
+ throw new MappingsError(patternOrIndexName, e.message);
+ }
+}
+
+export class MappingsError extends Error {
+ constructor(patternOrIndexName: string, details: string) {
+ const message = i18n.ERROR_LOADING_MAPPINGS({ details, patternOrIndexName });
+ super(message);
+
+ this.name = 'MappingsError';
+ this.message = message;
}
}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.test.tsx
index 8ccac0df997e7..86b3ef1688e6a 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.test.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.test.tsx
@@ -13,6 +13,7 @@ import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_re
import { ERROR_LOADING_MAPPINGS } from '../translations';
import { useMappings, UseMappings } from '.';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
+import { Theme } from '@elastic/charts';
const mockHttpFetch = jest.fn();
const mockReportDataQualityIndexChecked = jest.fn();
@@ -29,6 +30,45 @@ const ContextWrapper: FC> = ({ children }) => (
telemetryEvents={mockTelemetryEvents}
isILMAvailable={true}
toasts={toasts}
+ addSuccessToast={jest.fn()}
+ canUserCreateAndReadCases={jest.fn(() => true)}
+ endDate={null}
+ formatBytes={jest.fn()}
+ formatNumber={jest.fn()}
+ isAssistantEnabled={true}
+ lastChecked={'2023-03-28T22:27:28.159Z'}
+ openCreateCaseFlyout={jest.fn()}
+ patterns={['auditbeat-*']}
+ setLastChecked={jest.fn()}
+ startDate={null}
+ theme={{
+ background: {
+ color: '#000',
+ },
+ }}
+ baseTheme={
+ {
+ background: {
+ color: '#000',
+ },
+ } as Theme
+ }
+ ilmPhases={['hot', 'warm', 'unmanaged']}
+ selectedIlmPhaseOptions={[
+ {
+ label: 'Hot',
+ value: 'hot',
+ },
+ {
+ label: 'Warm',
+ value: 'warm',
+ },
+ {
+ label: 'Unmanaged',
+ value: 'unmanaged',
+ },
+ ]}
+ setSelectedIlmPhaseOptions={jest.fn()}
>
{children}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx
index f0f676ff24cac..f20809a248086 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx
@@ -10,6 +10,7 @@ import { useEffect, useState } from 'react';
import { useDataQualityContext } from '../data_quality_panel/data_quality_context';
import { fetchMappings } from './helpers';
+import { useIsMounted } from '../use_is_mounted';
export interface UseMappings {
indexes: Record | null;
@@ -18,6 +19,7 @@ export interface UseMappings {
}
export const useMappings = (patternOrIndexName: string): UseMappings => {
+ const { isMountedRef } = useIsMounted();
const [indexes, setIndexes] = useState {
const response = await fetchMappings({ abortController, httpFetch, patternOrIndexName });
if (!abortController.signal.aborted) {
- setIndexes(response);
+ if (isMountedRef.current) {
+ setIndexes(response);
+ }
}
} catch (e) {
if (!abortController.signal.aborted) {
- setError(e.message);
+ if (isMountedRef.current) {
+ setError(e.message);
+ }
}
} finally {
if (!abortController.signal.aborted) {
- setLoading(false);
+ if (isMountedRef.current) {
+ setLoading(false);
+ }
}
}
}
@@ -52,7 +60,7 @@ export const useMappings = (patternOrIndexName: string): UseMappings => {
return () => {
abortController.abort();
};
- }, [httpFetch, patternOrIndexName, setError]);
+ }, [httpFetch, isMountedRef, patternOrIndexName, setError]);
return { indexes, error, loading };
};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx
index 7a53eedc3c562..f0c6f9d28e501 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx
@@ -9,6 +9,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { EcsVersion } from '@elastic/ecs';
import { isEmpty } from 'lodash/fp';
+import { IToasts } from '@kbn/core-notifications-browser';
+import { HttpHandler } from '@kbn/core-http-browser';
import {
getTotalDocsCount,
getTotalIncompatible,
@@ -19,7 +21,12 @@ import {
updateResultOnCheckCompleted,
} from './helpers';
-import type { DataQualityCheckResult, OnCheckCompleted, PatternRollup } from '../types';
+import type {
+ DataQualityCheckResult,
+ OnCheckCompleted,
+ PatternRollup,
+ TelemetryEvents,
+} from '../types';
import {
getDocsCount,
getIndexId,
@@ -31,39 +38,24 @@ import {
formatResultFromStorage,
} from '../helpers';
import { getIlmPhase, getIndexIncompatible } from '../data_quality_panel/pattern/helpers';
-import { useDataQualityContext } from '../data_quality_panel/data_quality_context';
import {
getIncompatibleMappingsFields,
getIncompatibleValuesFields,
getSameFamilyFields,
} from '../data_quality_panel/tabs/incompatible_tab/helpers';
+import { UseResultsRollupReturnValue } from './types';
+import { useIsMounted } from '../use_is_mounted';
interface Props {
ilmPhases: string[];
patterns: string[];
+ toasts: IToasts;
+ httpFetch: HttpHandler;
+ telemetryEvents: TelemetryEvents;
+ isILMAvailable: boolean;
}
-interface UseResultsRollup {
- onCheckCompleted: OnCheckCompleted;
- patternIndexNames: Record;
- patternRollups: Record;
- totalDocsCount: number | undefined;
- totalIncompatible: number | undefined;
- totalIndices: number | undefined;
- totalIndicesChecked: number | undefined;
- totalSameFamily: number | undefined;
- totalSizeInBytes: number | undefined;
- updatePatternIndexNames: ({
- indexNames,
- pattern,
- }: {
- indexNames: string[];
- pattern: string;
- }) => void;
- updatePatternRollup: (patternRollup: PatternRollup) => void;
-}
-
-const useStoredPatternResults = (patterns: string[]) => {
- const { httpFetch, toasts } = useDataQualityContext();
+const useStoredPatternResults = (patterns: string[], toasts: IToasts, httpFetch: HttpHandler) => {
+ const { isMountedRef } = useIsMounted();
const [storedPatternResults, setStoredPatternResults] = useState<
Array<{ pattern: string; results: Record }>
>([]);
@@ -89,7 +81,9 @@ const useStoredPatternResults = (patterns: string[]) => {
);
const patternResults = await Promise.all(requests);
if (patternResults?.length && !ignore) {
- setStoredPatternResults(patternResults);
+ if (isMountedRef.current) {
+ setStoredPatternResults(patternResults);
+ }
}
};
@@ -97,17 +91,23 @@ const useStoredPatternResults = (patterns: string[]) => {
return () => {
ignore = true;
};
- }, [httpFetch, patterns, toasts]);
+ }, [httpFetch, isMountedRef, patterns, toasts]);
return storedPatternResults;
};
-export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRollup => {
- const { httpFetch, toasts } = useDataQualityContext();
+export const useResultsRollup = ({
+ httpFetch,
+ toasts,
+ ilmPhases,
+ patterns,
+ isILMAvailable,
+ telemetryEvents,
+}: Props): UseResultsRollupReturnValue => {
const [patternIndexNames, setPatternIndexNames] = useState>({});
const [patternRollups, setPatternRollups] = useState>({});
- const storedPatternsResults = useStoredPatternResults(patterns);
+ const storedPatternsResults = useStoredPatternResults(patterns, toasts, httpFetch);
useEffect(() => {
if (!isEmpty(storedPatternsResults)) {
@@ -127,7 +127,6 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll
}
}, [storedPatternsResults]);
- const { telemetryEvents, isILMAvailable } = useDataQualityContext();
const updatePatternRollup = useCallback((patternRollup: PatternRollup) => {
setPatternRollups((current) => ({
...current,
@@ -167,6 +166,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll
pattern,
requestTime,
isLastCheck,
+ isCheckAll,
}) => {
setPatternRollups((currentPatternRollups) => {
const updatedRollups = updateResultOnCheckCompleted({
@@ -198,7 +198,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll
ilmPhase: getIlmPhase(ilmExplain?.[indexName], isILMAvailable),
indexId,
indexName,
- isCheckAll: true,
+ isCheckAll,
numberOfDocuments: getDocsCount({ indexName, stats }),
numberOfFields: partitionedFieldMetadata.all.length,
numberOfIncompatibleFields: getIndexIncompatible({
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/types.ts
new file mode 100644
index 0000000000000..e8f0124cd4a3f
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/types.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { OnCheckCompleted, PatternRollup } from '../types';
+
+export interface UseResultsRollupReturnValue {
+ onCheckCompleted: OnCheckCompleted;
+ patternIndexNames: Record;
+ patternRollups: Record;
+ totalDocsCount: number | undefined;
+ totalIncompatible: number | undefined;
+ totalIndices: number | undefined;
+ totalIndicesChecked: number | undefined;
+ totalSameFamily: number | undefined;
+ totalSizeInBytes: number | undefined;
+ updatePatternIndexNames: ({
+ indexNames,
+ pattern,
+ }: {
+ indexNames: string[];
+ pattern: string;
+ }) => void;
+ updatePatternRollup: (patternRollup: PatternRollup) => void;
+}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.test.tsx
index d084f168ac450..c07bf9b8afd76 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.test.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.test.tsx
@@ -13,6 +13,7 @@ import { mockStatsAuditbeatIndex } from '../mock/stats/mock_stats_packetbeat_ind
import { ERROR_LOADING_STATS } from '../translations';
import { useStats, UseStats } from '.';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
+import { Theme } from '@elastic/charts';
const mockHttpFetch = jest.fn();
const mockReportDataQualityIndexChecked = jest.fn();
@@ -29,6 +30,45 @@ const ContextWrapper: FC> = ({ children }) => (
telemetryEvents={mockTelemetryEvents}
isILMAvailable={true}
toasts={toasts}
+ addSuccessToast={jest.fn()}
+ canUserCreateAndReadCases={jest.fn(() => true)}
+ endDate={null}
+ formatBytes={jest.fn()}
+ formatNumber={jest.fn()}
+ isAssistantEnabled={true}
+ lastChecked={'2023-03-28T22:27:28.159Z'}
+ openCreateCaseFlyout={jest.fn()}
+ patterns={['auditbeat-*']}
+ setLastChecked={jest.fn()}
+ startDate={null}
+ theme={{
+ background: {
+ color: '#000',
+ },
+ }}
+ baseTheme={
+ {
+ background: {
+ color: '#000',
+ },
+ } as Theme
+ }
+ ilmPhases={['hot', 'warm', 'unmanaged']}
+ selectedIlmPhaseOptions={[
+ {
+ label: 'Hot',
+ value: 'hot',
+ },
+ {
+ label: 'Warm',
+ value: 'warm',
+ },
+ {
+ label: 'Unmanaged',
+ value: 'unmanaged',
+ },
+ ]}
+ setSelectedIlmPhaseOptions={jest.fn()}
>
{children}
@@ -40,6 +80,45 @@ const ContextWrapperILMNotAvailable: FC> = ({ childre
telemetryEvents={mockTelemetryEvents}
isILMAvailable={false}
toasts={toasts}
+ addSuccessToast={jest.fn()}
+ canUserCreateAndReadCases={jest.fn(() => true)}
+ endDate={null}
+ formatBytes={jest.fn()}
+ formatNumber={jest.fn()}
+ isAssistantEnabled={true}
+ lastChecked={'2023-03-28T22:27:28.159Z'}
+ openCreateCaseFlyout={jest.fn()}
+ patterns={['auditbeat-*']}
+ setLastChecked={jest.fn()}
+ startDate={null}
+ theme={{
+ background: {
+ color: '#000',
+ },
+ }}
+ baseTheme={
+ {
+ background: {
+ color: '#000',
+ },
+ } as Theme
+ }
+ ilmPhases={['hot', 'warm', 'unmanaged']}
+ selectedIlmPhaseOptions={[
+ {
+ label: 'Hot',
+ value: 'hot',
+ },
+ {
+ label: 'Warm',
+ value: 'warm',
+ },
+ {
+ label: 'Unmanaged',
+ value: 'unmanaged',
+ },
+ ]}
+ setSelectedIlmPhaseOptions={jest.fn()}
>
{children}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx
index 90af7ded0ba00..0de57ccd54568 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx
@@ -12,6 +12,7 @@ import { useDataQualityContext } from '../data_quality_panel/data_quality_contex
import * as i18n from '../translations';
import { INTERNAL_API_VERSION } from '../helpers';
import { MeteringStatsIndex } from '../types';
+import { useIsMounted } from '../use_is_mounted';
const STATS_ENDPOINT = '/internal/ecs_data_quality_dashboard/stats';
@@ -30,6 +31,7 @@ export const useStats = ({
pattern: string;
startDate?: string | null;
}): UseStats => {
+ const { isMountedRef } = useIsMounted();
const { httpFetch, isILMAvailable } = useDataQualityContext();
const [stats, setStats] = useState | null>(null);
const [error, setError] = useState(null);
@@ -62,15 +64,21 @@ export const useStats = ({
);
if (!abortController.signal.aborted) {
- setStats(response);
+ if (isMountedRef.current) {
+ setStats(response);
+ }
}
} catch (e) {
if (!abortController.signal.aborted) {
- setError(i18n.ERROR_LOADING_STATS(e.message));
+ if (isMountedRef.current) {
+ setError(i18n.ERROR_LOADING_STATS(e.message));
+ }
}
} finally {
if (!abortController.signal.aborted) {
- setLoading(false);
+ if (isMountedRef.current) {
+ setLoading(false);
+ }
}
}
}
@@ -80,7 +88,7 @@ export const useStats = ({
return () => {
abortController.abort();
};
- }, [endDate, httpFetch, isILMAvailable, pattern, setError, startDate]);
+ }, [endDate, httpFetch, isILMAvailable, isMountedRef, pattern, setError, startDate]);
return { stats, error, loading };
};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts
index a193456d4afa9..aa25a5fe00b44 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts
@@ -85,11 +85,16 @@ export async function fetchUnallowedValues({
version: INTERNAL_API_VERSION,
});
} catch (e) {
- throw new Error(
- i18n.ERROR_LOADING_UNALLOWED_VALUES({
- details: e.message,
- indexName,
- })
- );
+ throw new UnallowedValuesError(indexName, e.message);
+ }
+}
+
+export class UnallowedValuesError extends Error {
+ constructor(indexName: string, details: string) {
+ const message = i18n.ERROR_LOADING_UNALLOWED_VALUES({ details, indexName });
+ super(message);
+
+ this.name = 'UnallowedValuesError';
+ this.message = message;
}
}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx
index fcbb8aae9337f..d2d216fd12293 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx
@@ -16,6 +16,7 @@ import { UnallowedValueRequestItem } from '../types';
import { useUnallowedValues, UseUnallowedValues } from '.';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { EcsFlatTyped } from '../constants';
+import { Theme } from '@elastic/charts';
const mockHttpFetch = jest.fn();
const mockReportDataQualityIndexChecked = jest.fn();
@@ -32,6 +33,45 @@ const ContextWrapper: FC> = ({ children }) => (
telemetryEvents={mockTelemetryEvents}
isILMAvailable={true}
toasts={toasts}
+ addSuccessToast={jest.fn()}
+ canUserCreateAndReadCases={jest.fn(() => true)}
+ endDate={null}
+ formatBytes={jest.fn()}
+ formatNumber={jest.fn()}
+ isAssistantEnabled={true}
+ lastChecked={'2023-03-28T22:27:28.159Z'}
+ openCreateCaseFlyout={jest.fn()}
+ patterns={['auditbeat-*']}
+ setLastChecked={jest.fn()}
+ startDate={null}
+ theme={{
+ background: {
+ color: '#000',
+ },
+ }}
+ baseTheme={
+ {
+ background: {
+ color: '#000',
+ },
+ } as Theme
+ }
+ ilmPhases={['hot', 'warm', 'unmanaged']}
+ selectedIlmPhaseOptions={[
+ {
+ label: 'Hot',
+ value: 'hot',
+ },
+ {
+ label: 'Warm',
+ value: 'warm',
+ },
+ {
+ label: 'Unmanaged',
+ value: 'unmanaged',
+ },
+ ]}
+ setSelectedIlmPhaseOptions={jest.fn()}
>
{children}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.tsx
index de0ce82fb8527..7f35da714ecd9 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.tsx
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.tsx
@@ -10,6 +10,7 @@ import { useEffect, useState } from 'react';
import { useDataQualityContext } from '../data_quality_panel/data_quality_context';
import { fetchUnallowedValues, getUnallowedValues } from './helpers';
import type { UnallowedValueCount, UnallowedValueRequestItem } from '../types';
+import { useIsMounted } from '../use_is_mounted';
export interface UseUnallowedValues {
unallowedValues: Record | null;
@@ -25,6 +26,7 @@ export const useUnallowedValues = ({
indexName: string;
requestItems: UnallowedValueRequestItem[];
}): UseUnallowedValues => {
+ const { isMountedRef } = useIsMounted();
const [unallowedValues, setUnallowedValues] = useState {
abortController.abort();
};
- }, [httpFetch, indexName, requestItems, setError]);
+ }, [httpFetch, indexName, isMountedRef, requestItems, setError]);
return { unallowedValues, error, loading, requestTime };
};
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.test.ts
new file mode 100644
index 0000000000000..9ea197360356f
--- /dev/null
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.test.ts
@@ -0,0 +1,466 @@
+/*
+ * 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 { checkIndex, EMPTY_PARTITIONED_FIELD_METADATA } from './check_index';
+import { EMPTY_STAT } from '../helpers';
+import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_response';
+import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values';
+import { UnallowedValueRequestItem, UnallowedValueSearchResult } from '../types';
+import {
+ getMappingsProperties,
+ getSortedPartitionedFieldMetadata,
+} from '../data_quality_panel/index_properties/helpers';
+import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types';
+import { getUnallowedValues } from '../use_unallowed_values/helpers';
+import { getUnallowedValueRequestItems } from '../data_quality_panel/allowed_values/helpers';
+import { EcsFlatTyped } from '../constants';
+
+let mockFetchMappings = jest.fn(
+ (_: { abortController: AbortController; patternOrIndexName: string }) =>
+ Promise.resolve(mockMappingsResponse)
+);
+
+jest.mock('../use_mappings/helpers', () => ({
+ fetchMappings: ({
+ abortController,
+ patternOrIndexName,
+ }: {
+ abortController: AbortController;
+ patternOrIndexName: string;
+ }) =>
+ mockFetchMappings({
+ abortController,
+ patternOrIndexName,
+ }),
+}));
+
+const mockFetchUnallowedValues = jest.fn(
+ (_: {
+ abortController: AbortController;
+ indexName: string;
+ requestItems: UnallowedValueRequestItem[];
+ }) => Promise.resolve(mockUnallowedValuesResponse)
+);
+
+jest.mock('../use_unallowed_values/helpers', () => {
+ const original = jest.requireActual('../use_unallowed_values/helpers');
+
+ return {
+ ...original,
+ fetchUnallowedValues: ({
+ abortController,
+ indexName,
+ requestItems,
+ }: {
+ abortController: AbortController;
+ indexName: string;
+ requestItems: UnallowedValueRequestItem[];
+ }) =>
+ mockFetchUnallowedValues({
+ abortController,
+ indexName,
+ requestItems,
+ }),
+ };
+});
+
+describe('checkIndex', () => {
+ const defaultBytesFormat = '0,0.[0]b';
+ const formatBytes = (value: number | undefined) =>
+ value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT;
+
+ const defaultNumberFormat = '0,0.[000]';
+ const formatNumber = (value: number | undefined) =>
+ value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT;
+
+ const indexName = 'auditbeat-custom-index-1';
+ const pattern = 'auditbeat-*';
+ const httpFetch = jest.fn();
+
+ describe('when `checkIndex` successfully completes the check', () => {
+ const onCheckCompleted = jest.fn();
+
+ beforeEach(async () => {
+ jest.clearAllMocks();
+
+ await checkIndex({
+ abortController: new AbortController(),
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted,
+ pattern,
+ });
+ });
+
+ test('it invokes onCheckCompleted with a null `error`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].error).toBeNull();
+ });
+
+ test('it invokes onCheckCompleted with the expected `indexName`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName);
+ });
+
+ test('it invokes onCheckCompleted with the non-default `partitionedFieldMetadata`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).not.toEqual(
+ EMPTY_PARTITIONED_FIELD_METADATA
+ );
+ });
+
+ test('it invokes onCheckCompleted with the expected`pattern`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern);
+ });
+ });
+
+ describe('lifecycle hooks', () => {
+ const orderOfCalls: string[] = [];
+ const onStart = jest.fn(() => orderOfCalls.push('onStart'));
+ const onSuccess = jest.fn(() => orderOfCalls.push('onSuccess'));
+ const onError = jest.fn(() => orderOfCalls.push('onError'));
+ const onLoadMappingsStart = jest.fn(() => orderOfCalls.push('onLoadMappingsStart'));
+ const onLoadMappingsSuccess = jest.fn(() => orderOfCalls.push('onLoadMappingsSuccess'));
+ const onLoadUnallowedValuesStart = jest.fn(() =>
+ orderOfCalls.push('onLoadUnallowedValuesStart')
+ );
+ const onLoadUnallowedValuesSuccess = jest.fn(() =>
+ orderOfCalls.push('onLoadUnallowedValuesSuccess')
+ );
+
+ beforeEach(async () => {
+ orderOfCalls.length = 0;
+ jest.clearAllMocks();
+
+ await checkIndex({
+ abortController: new AbortController(),
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted: jest.fn(),
+ pattern,
+ onError,
+ onLoadMappingsStart,
+ onLoadMappingsSuccess,
+ onLoadUnallowedValuesStart,
+ onLoadUnallowedValuesSuccess,
+ onStart,
+ onSuccess,
+ });
+ });
+
+ test('it invokes `onStart`', () => {
+ expect(onStart).toBeCalled();
+ });
+
+ test('it invokes `onLoadMappingsStart`', () => {
+ expect(onLoadMappingsStart).toBeCalled();
+ });
+
+ test('it invokes `onLoadMappingsSuccess` with mappings response', () => {
+ expect(onLoadMappingsSuccess).toBeCalledWith(mockMappingsResponse);
+ });
+
+ test('it invokes `onLoadUnallowedValuesStart`', () => {
+ expect(onLoadUnallowedValuesStart).toBeCalled();
+ });
+
+ test('it invokes `onLoadUnallowedValuesSuccess` with unallowed value search results', () => {
+ expect(onLoadUnallowedValuesSuccess).toBeCalledWith(mockUnallowedValuesResponse);
+ });
+
+ test('it invokes `onSuccess` with the expected arguments', () => {
+ const mappingsProperties = getMappingsProperties({
+ indexName,
+ indexes: mockMappingsResponse as Record,
+ });
+ const unallowedValues = getUnallowedValues({
+ requestItems: getUnallowedValueRequestItems({
+ ecsMetadata: EcsFlatTyped,
+ indexName,
+ }),
+ searchResults: mockUnallowedValuesResponse as unknown as UnallowedValueSearchResult[],
+ });
+ const partitionedFieldMetadata = getSortedPartitionedFieldMetadata({
+ ecsMetadata: EcsFlatTyped,
+ loadingMappings: false,
+ mappingsProperties,
+ unallowedValues,
+ });
+ expect(onSuccess).toBeCalledWith({
+ partitionedFieldMetadata,
+ mappingsProperties,
+ unallowedValues,
+ });
+ });
+
+ test('it does NOT invoke `onError`', () => {
+ expect(onError).not.toBeCalled();
+ });
+
+ test('it invokes the lifecycle hooks in the expected order', () => {
+ expect(orderOfCalls).toEqual([
+ 'onStart',
+ 'onLoadMappingsStart',
+ 'onLoadMappingsSuccess',
+ 'onLoadUnallowedValuesStart',
+ 'onLoadUnallowedValuesSuccess',
+ 'onSuccess',
+ ]);
+ });
+
+ describe('when load mappings error occurs', () => {
+ const error = 'simulated fetch mappings error';
+ const onCheckCompleted = jest.fn();
+
+ beforeEach(async () => {
+ orderOfCalls.length = 0;
+ jest.clearAllMocks();
+
+ mockFetchMappings.mockRejectedValueOnce(new Error(error));
+
+ await checkIndex({
+ abortController: new AbortController(),
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted,
+ pattern,
+ onError,
+ onLoadMappingsStart,
+ onLoadMappingsSuccess,
+ onLoadUnallowedValuesStart,
+ onLoadUnallowedValuesSuccess,
+ onStart,
+ onSuccess,
+ });
+ });
+
+ test('it invokes `onError` with mappings error', () => {
+ expect(onError).toBeCalledWith(new Error(error));
+ });
+
+ test('it does NOT invoke `onSuccess`', () => {
+ expect(onSuccess).not.toBeCalled();
+ });
+
+ test('it invokes the lifecycle hooks in the expected order', () => {
+ expect(orderOfCalls).toEqual(['onStart', 'onLoadMappingsStart', 'onError']);
+ });
+ });
+
+ describe('when load unallowed values error occurs', () => {
+ const error = 'simulated fetch unallowed values error';
+ const onCheckCompleted = jest.fn();
+
+ beforeEach(async () => {
+ orderOfCalls.length = 0;
+ jest.clearAllMocks();
+
+ mockFetchUnallowedValues.mockRejectedValueOnce(new Error(error));
+
+ await checkIndex({
+ abortController: new AbortController(),
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted,
+ pattern,
+ onError,
+ onLoadMappingsStart,
+ onLoadMappingsSuccess,
+ onLoadUnallowedValuesStart,
+ onLoadUnallowedValuesSuccess,
+ onStart,
+ onSuccess,
+ });
+ });
+
+ test('it invokes `onError` with unallowed values error', () => {
+ expect(onError).toBeCalledWith(new Error(error));
+ });
+
+ test('it does NOT invoke `onSuccess`', () => {
+ expect(onSuccess).not.toBeCalled();
+ });
+
+ test('it invokes the lifecycle hooks in the expected order', () => {
+ expect(orderOfCalls).toEqual([
+ 'onStart',
+ 'onLoadMappingsStart',
+ 'onLoadMappingsSuccess',
+ 'onLoadUnallowedValuesStart',
+ 'onError',
+ ]);
+ });
+ });
+ });
+
+ describe('happy path, when the signal is aborted', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('it does NOT invoke onCheckCompleted', async () => {
+ const onCheckCompleted = jest.fn();
+
+ const abortController = new AbortController();
+ abortController.abort();
+
+ await checkIndex({
+ abortController,
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted,
+ pattern,
+ });
+
+ expect(onCheckCompleted).not.toBeCalled();
+ });
+ });
+
+ describe('when an error occurs', () => {
+ const onCheckCompleted = jest.fn();
+ const error = 'simulated fetch mappings error';
+
+ beforeEach(async () => {
+ jest.clearAllMocks();
+
+ mockFetchMappings = jest.fn(
+ (_: { abortController: AbortController; patternOrIndexName: string }) =>
+ Promise.reject(new Error(error))
+ );
+
+ await checkIndex({
+ abortController: new AbortController(),
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted,
+ pattern,
+ });
+ });
+
+ test('it invokes onCheckCompleted with the expected `error`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].error).toEqual(error);
+ });
+
+ test('it invokes onCheckCompleted with the expected `indexName`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName);
+ });
+
+ test('it invokes onCheckCompleted with null `partitionedFieldMetadata`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).toBeNull();
+ });
+
+ test('it invokes onCheckCompleted with the expected `pattern`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern);
+ });
+ });
+
+ describe('when an error occurs, but the error does not have a toString', () => {
+ const onCheckCompleted = jest.fn();
+
+ beforeEach(async () => {
+ jest.clearAllMocks();
+
+ mockFetchMappings = jest.fn(
+ (_: { abortController: AbortController; patternOrIndexName: string }) =>
+ // eslint-disable-next-line prefer-promise-reject-errors
+ Promise.reject(undefined)
+ );
+
+ await checkIndex({
+ abortController: new AbortController(),
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted,
+ pattern,
+ });
+ });
+
+ test('it invokes onCheckCompleted with the fallback `error`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].error).toEqual(
+ `An error occurred checking index ${indexName}`
+ );
+ });
+
+ test('it invokes onCheckCompleted with the expected `indexName`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName);
+ });
+
+ test('it invokes onCheckCompleted with null `partitionedFieldMetadata`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).toBeNull();
+ });
+
+ test('it invokes onCheckCompleted with the expected `pattern`', () => {
+ expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern);
+ });
+ });
+
+ describe('when an error occurs, and the signal is aborted', () => {
+ const onCheckCompleted = jest.fn();
+ const abortController = new AbortController();
+ abortController.abort();
+
+ const error = 'simulated fetch mappings error';
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('it does NOT invoke onCheckCompleted', async () => {
+ mockFetchMappings = jest.fn(
+ (_: { abortController: AbortController; patternOrIndexName: string }) =>
+ Promise.reject(new Error(error))
+ );
+
+ await checkIndex({
+ abortController,
+ batchId: 'batch-id',
+ checkAllStartTime: Date.now(),
+ formatBytes,
+ formatNumber,
+ httpFetch,
+ indexName,
+ isLastCheck: false,
+ onCheckCompleted,
+ pattern,
+ });
+
+ expect(onCheckCompleted).not.toBeCalled();
+ });
+ });
+});
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.ts
similarity index 50%
rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts
rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.ts
index a9216b9d09fdd..8dd282c4121f0 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.ts
@@ -6,16 +6,27 @@
*/
import type { HttpHandler } from '@kbn/core-http-browser';
-import { getUnallowedValueRequestItems } from '../../../allowed_values/helpers';
+import {
+ IndicesGetMappingIndexMappingRecord,
+ MappingProperty,
+} from '@elastic/elasticsearch/lib/api/types';
+import { v4 as uuidv4 } from 'uuid';
+
+import { getUnallowedValueRequestItems } from '../data_quality_panel/allowed_values/helpers';
import {
getMappingsProperties,
getSortedPartitionedFieldMetadata,
-} from '../../../index_properties/helpers';
-import * as i18n from './translations';
-import type { OnCheckCompleted, PartitionedFieldMetadata } from '../../../../types';
-import { fetchMappings } from '../../../../use_mappings/helpers';
-import { fetchUnallowedValues, getUnallowedValues } from '../../../../use_unallowed_values/helpers';
-import { EcsFlatTyped } from '../../../../constants';
+} from '../data_quality_panel/index_properties/helpers';
+import * as i18n from '../data_quality_panel/data_quality_summary/summary_actions/check_all/translations';
+import type {
+ OnCheckCompleted,
+ PartitionedFieldMetadata,
+ UnallowedValueCount,
+ UnallowedValueSearchResult,
+} from '../types';
+import { fetchMappings } from '../use_mappings/helpers';
+import { fetchUnallowedValues, getUnallowedValues } from '../use_unallowed_values/helpers';
+import { EcsFlatTyped } from '../constants';
export const EMPTY_PARTITIONED_FIELD_METADATA: PartitionedFieldMetadata = {
all: [],
@@ -25,46 +36,74 @@ export const EMPTY_PARTITIONED_FIELD_METADATA: PartitionedFieldMetadata = {
sameFamily: [],
};
+export interface CheckIndexProps {
+ abortController: AbortController;
+ formatBytes: (value: number | undefined) => string;
+ formatNumber: (value: number | undefined) => string;
+ httpFetch: HttpHandler;
+ indexName: string;
+ onCheckCompleted: OnCheckCompleted;
+ pattern: string;
+ onLoadMappingsSuccess?: (indexes: Record) => void;
+ onLoadMappingsStart?: () => void;
+ onLoadUnallowedValuesSuccess?: (searchResults: UnallowedValueSearchResult[]) => void;
+ onLoadUnallowedValuesStart?: () => void;
+ onStart?: () => void;
+ onSuccess?: ({
+ partitionedFieldMetadata,
+ mappingsProperties,
+ unallowedValues,
+ }: {
+ partitionedFieldMetadata: PartitionedFieldMetadata;
+ mappingsProperties: Record | null;
+ unallowedValues: Record;
+ }) => void;
+ onError?: (error: unknown) => void;
+ batchId?: string;
+ checkAllStartTime?: number;
+ isCheckAll?: boolean;
+ isLastCheck?: boolean;
+}
+
export async function checkIndex({
abortController,
- batchId,
- checkAllStartTime,
- ecsMetadata,
formatBytes,
formatNumber,
httpFetch,
indexName,
- isLastCheck,
onCheckCompleted,
pattern,
- version,
-}: {
- abortController: AbortController;
- batchId: string;
- checkAllStartTime: number;
- ecsMetadata: EcsFlatTyped;
- formatBytes: (value: number | undefined) => string;
- formatNumber: (value: number | undefined) => string;
- httpFetch: HttpHandler;
- indexName: string;
- isLastCheck: boolean;
- onCheckCompleted: OnCheckCompleted;
- pattern: string;
- version: string;
-}) {
+ onLoadMappingsSuccess,
+ onLoadMappingsStart,
+ onLoadUnallowedValuesSuccess,
+ onLoadUnallowedValuesStart,
+ onStart,
+ onSuccess,
+ onError,
+ isLastCheck = false,
+ batchId = uuidv4(),
+ checkAllStartTime = Date.now(),
+ isCheckAll = false,
+}: CheckIndexProps) {
try {
const startTime = Date.now();
+
+ onStart?.();
+ onLoadMappingsStart?.();
const indexes = await fetchMappings({
abortController,
httpFetch,
patternOrIndexName: indexName,
});
+ onLoadMappingsSuccess?.(indexes);
+
const requestItems = getUnallowedValueRequestItems({
- ecsMetadata,
+ ecsMetadata: EcsFlatTyped,
indexName,
});
+ onLoadUnallowedValuesStart?.();
const searchResults = await fetchUnallowedValues({
abortController,
httpFetch,
@@ -72,6 +111,8 @@ export async function checkIndex({
requestItems,
});
+ onLoadUnallowedValuesSuccess?.(searchResults);
+
const unallowedValues = getUnallowedValues({
requestItems,
searchResults,
@@ -84,39 +125,47 @@ export async function checkIndex({
const partitionedFieldMetadata =
getSortedPartitionedFieldMetadata({
- ecsMetadata,
+ ecsMetadata: EcsFlatTyped,
loadingMappings: false,
mappingsProperties,
unallowedValues,
}) ?? EMPTY_PARTITIONED_FIELD_METADATA;
+ onSuccess?.({
+ partitionedFieldMetadata,
+ mappingsProperties,
+ unallowedValues,
+ });
if (!abortController.signal.aborted) {
onCheckCompleted({
checkAllStartTime,
batchId,
error: null,
formatBytes,
+ isCheckAll,
formatNumber,
indexName,
partitionedFieldMetadata,
pattern,
requestTime: Date.now() - startTime,
- version,
isLastCheck,
});
}
- } catch (error) {
+ } catch (error: unknown) {
+ onError?.(error);
+
if (!abortController.signal.aborted) {
onCheckCompleted({
checkAllStartTime,
batchId,
- error: error != null ? error.message : i18n.AN_ERROR_OCCURRED_CHECKING_INDEX(indexName),
+ error:
+ error instanceof Error ? error.message : i18n.AN_ERROR_OCCURRED_CHECKING_INDEX(indexName),
formatBytes,
formatNumber,
+ isCheckAll,
indexName,
partitionedFieldMetadata: null,
pattern,
- version,
isLastCheck,
});
}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts
index 87c32ba78d5e6..5f9ab020ea21f 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts
@@ -17,6 +17,7 @@ export {
ILM_PHASE,
INDEX_LIFECYCLE_MANAGEMENT_PHASES,
SELECT_ONE_OR_MORE_ILM_PHASES,
+ DATA_QUALITY_DASHBOARD_CONVERSATION_ID,
} from './impl/data_quality/translations';
export { ECS_REFERENCE_URL } from './impl/data_quality/data_quality_panel/index_properties/markdown/helpers';
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js b/x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js
index e017b0ceaf369..5f6f7ab9bce1f 100644
--- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js
+++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/jest.config.js
@@ -11,11 +11,11 @@ module.exports = {
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/**/*.{ts,tsx}',
- '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
- '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*mock*.{ts,tsx}',
- '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*.test.{ts,tsx}',
- '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*.d.ts',
- '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/*.config.ts',
+ '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/**/{__test__,__snapshots__,__examples__,*mock*,stub,tests,test_helpers,integration_tests,types}/**/*',
+ '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/**/*mock*.{ts,tsx}',
+ '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/**/*.test.{ts,tsx}',
+ '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/**/*.d.ts',
+ '!/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/**/*.config.ts',
],
preset: '@kbn/test',
rootDir: '../../../..',
diff --git a/x-pack/plugins/security_solution/public/assistant/content/conversations/index.tsx b/x-pack/plugins/security_solution/public/assistant/content/conversations/index.tsx
index 53fb26d205720..9234095990016 100644
--- a/x-pack/plugins/security_solution/public/assistant/content/conversations/index.tsx
+++ b/x-pack/plugins/security_solution/public/assistant/content/conversations/index.tsx
@@ -7,7 +7,8 @@
import { WELCOME_CONVERSATION_TITLE } from '@kbn/elastic-assistant';
import type { Conversation } from '@kbn/elastic-assistant';
-import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/translations';
+import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard';
+
import { DETECTION_RULES_CONVERSATION_ID } from '../../../detections/pages/detection_engine/rules/translations';
import {
ALERT_SUMMARY_CONVERSATION_ID,
diff --git a/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx b/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx
index 7fee44d0178b1..99836af47a7a8 100644
--- a/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.test.tsx
@@ -8,10 +8,10 @@ import { renderHook } from '@testing-library/react-hooks';
import { useBaseConversations } from '.';
import { useLinkAuthorized } from '../../common/links';
import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__';
-import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/translations';
import { useKibana } from '../../common/lib/kibana';
import { BASE_SECURITY_CONVERSATIONS } from '../content/conversations';
import { unset } from 'lodash/fp';
+import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard';
const BASE_CONVERSATIONS_WITHOUT_DATA_QUALITY = unset(
DATA_QUALITY_DASHBOARD_CONVERSATION_ID,
diff --git a/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.tsx b/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.tsx
index 3cc2d47682ec0..89fe80f4d29a3 100644
--- a/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.tsx
+++ b/x-pack/plugins/security_solution/public/assistant/use_conversation_store/index.tsx
@@ -8,8 +8,9 @@
import { type Conversation } from '@kbn/elastic-assistant';
import { unset } from 'lodash/fp';
-import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/translations';
import { useMemo } from 'react';
+import { DATA_QUALITY_DASHBOARD_CONVERSATION_ID } from '@kbn/ecs-data-quality-dashboard';
+
import { BASE_SECURITY_CONVERSATIONS } from '../content/conversations';
import { useLinkAuthorized } from '../../common/links';
import { SecurityPageName } from '../../../common';
diff --git a/x-pack/plugins/security_solution/public/overview/pages/data_quality.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/data_quality.test.tsx
index 16e610aa3f625..e39e2abd24169 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/data_quality.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/data_quality.test.tsx
@@ -12,7 +12,6 @@ import { MemoryRouter } from 'react-router-dom';
import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__';
import { TestProviders } from '../../common/mock';
import { DataQuality } from './data_quality';
-import { HOT, WARM, UNMANAGED } from './translations';
import { useKibana } from '../../common/lib/kibana';
const mockedUseKibana = mockUseKibana();
@@ -56,7 +55,7 @@ jest.mock('../../detections/containers/detection_engine/alerts/use_signal_index'
}));
describe('DataQuality', () => {
- const defaultIlmPhases = `${HOT}${WARM}${UNMANAGED}`;
+ const defaultIlmPhases = 'hotwarmunmanaged';
beforeEach(() => {
jest.clearAllMocks();
diff --git a/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx b/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx
index 50593a0569fcc..37fc927094993 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx
+++ b/x-pack/plugins/security_solution/public/overview/pages/data_quality.tsx
@@ -10,29 +10,13 @@ import {
DataQualityPanel,
DATA_QUALITY_SUBTITLE,
ECS_REFERENCE_URL,
- getIlmPhaseDescription,
- ILM_PHASE,
- INDEX_LIFECYCLE_MANAGEMENT_PHASES,
- SELECT_ONE_OR_MORE_ILM_PHASES,
} from '@kbn/ecs-data-quality-dashboard';
-import type { EuiComboBoxOptionOption, OnTimeChangeProps } from '@elastic/eui';
-import {
- EuiComboBox,
- EuiFormControlLayout,
- EuiFormLabel,
- EuiLink,
- EuiLoadingSpinner,
- EuiText,
- EuiToolTip,
- useGeneratedHtmlId,
- EuiSuperDatePicker,
-} from '@elastic/eui';
+import type { OnTimeChangeProps } from '@elastic/eui';
+import { EuiLink, EuiLoadingSpinner, EuiText, EuiToolTip, EuiSuperDatePicker } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import styled from 'styled-components';
import { useAssistantAvailability } from '../../assistant/use_assistant_availability';
import { SecurityPageName } from '../../app/types';
-import { getGroupByFieldsOnClick } from '../../detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/helpers';
import { useThemes } from '../../common/components/charts/common';
import { HeaderPage } from '../../common/components/header_page';
import { EmptyPrompt } from '../../common/components/empty_prompt';
@@ -51,85 +35,9 @@ import type {
const LOCAL_STORAGE_KEY = 'dataQualityDashboardLastChecked';
-const comboBoxStyle: React.CSSProperties = {
- width: '322px',
-};
-
-const FormControlLayout = styled(EuiFormControlLayout)`
- max-width: 500px;
- height: 42px;
-
- .euiFormControlLayout__childrenWrapper {
- overflow: visible;
- }
-`;
-
-const Option = styled.div`
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- width: 100%;
-`;
-
-const OptionLabel = styled.span`
- font-weight: bold;
-`;
-
-const options: EuiComboBoxOptionOption[] = [
- {
- label: i18n.HOT,
- value: 'hot',
- },
- {
- label: i18n.WARM,
- value: 'warm',
- },
- {
- disabled: true,
- label: i18n.COLD,
- value: 'cold',
- },
- {
- disabled: true,
- label: i18n.FROZEN,
- value: 'frozen',
- },
- {
- label: i18n.UNMANAGED,
- value: 'unmanaged',
- },
-];
-
-const defaultOptions: EuiComboBoxOptionOption[] = [
- {
- label: i18n.HOT,
- value: 'hot',
- },
- {
- label: i18n.WARM,
- value: 'warm',
- },
- {
- label: i18n.UNMANAGED,
- value: 'unmanaged',
- },
-];
-
const DEFAULT_START_TIME = 'now-7d';
const DEFAULT_END_TIME = 'now';
-const renderOption = (
- option: EuiComboBoxOptionOption
-): React.ReactNode => (
-
-
-
-);
-
const DataQualityComponent: React.FC = () => {
const { isAssistantEnabled } = useAssistantAvailability();
const httpFetch = KibanaServices.get().http.fetch;
@@ -138,8 +46,6 @@ const DataQualityComponent: React.FC = () => {
const [defaultBytesFormat] = useUiSetting$(DEFAULT_BYTES_FORMAT);
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
- const labelInputId = useGeneratedHtmlId({ prefix: 'labelInput' });
- const [selectedOptions, setSelectedOptions] = useState(defaultOptions);
const { indicesExist, loading: isSourcererLoading, selectedPatterns } = useSourcererDataView();
const { signalIndexName, loading: isSignalIndexNameLoading } = useSignalIndex();
const { configSettings, cases, telemetry } = useKibana().services;
@@ -181,16 +87,6 @@ const DataQualityComponent: React.FC = () => {
[]
);
- const ilmPhases: string[] = useMemo(
- () => selectedOptions.map(({ label }) => label),
- [selectedOptions]
- );
-
- const ilmFormLabel = useMemo(
- () => {ILM_PHASE},
- [labelInputId]
- );
-
const [lastChecked, setLastChecked] = useLocalStorage({
defaultValue: '',
key: LOCAL_STORAGE_KEY,
@@ -243,22 +139,6 @@ const DataQualityComponent: React.FC = () => {
{indicesExist ? (
- {isILMAvailable && (
-
-
-
-
-
- )}
{!isILMAvailable && startDate && endDate && (
{
defaultBytesFormat={defaultBytesFormat}
defaultNumberFormat={defaultNumberFormat}
endDate={endDate}
- getGroupByFieldsOnClick={getGroupByFieldsOnClick}
reportDataQualityCheckAllCompleted={reportDataQualityCheckAllCompleted}
reportDataQualityIndexChecked={reportDataQualityIndexChecked}
httpFetch={httpFetch}
- ilmPhases={ilmPhases}
isAssistantEnabled={isAssistantEnabled}
isILMAvailable={isILMAvailable}
lastChecked={lastChecked}
diff --git a/x-pack/plugins/security_solution/public/overview/pages/translations.ts b/x-pack/plugins/security_solution/public/overview/pages/translations.ts
index 54b82db440a90..e46bd59e1a632 100644
--- a/x-pack/plugins/security_solution/public/overview/pages/translations.ts
+++ b/x-pack/plugins/security_solution/public/overview/pages/translations.ts
@@ -18,10 +18,6 @@ export const BETA = i18n.translate('xpack.securitySolution.dataQualityDashboard.
defaultMessage: 'Beta',
});
-export const COLD = i18n.translate('xpack.securitySolution.overview.ilmPhaseCold', {
- defaultMessage: 'cold',
-});
-
export const DATA_QUALITY_TITLE = i18n.translate(
'xpack.securitySolution.dataQualityDashboard.pageTitle',
{
@@ -56,14 +52,6 @@ export const EVENTS = i18n.translate('xpack.securitySolution.overview.eventsTitl
defaultMessage: 'Event count',
});
-export const FROZEN = i18n.translate('xpack.securitySolution.overview.ilmPhaseFrozen', {
- defaultMessage: 'frozen',
-});
-
-export const HOT = i18n.translate('xpack.securitySolution.overview.ilmPhaseHot', {
- defaultMessage: 'hot',
-});
-
export const NEWS_FEED_TITLE = i18n.translate(
'xpack.securitySolution.overview.newsFeedSidebarTitle',
{
@@ -88,10 +76,6 @@ export const TOP = (fieldName: string) =>
defaultMessage: `Top {fieldName}`,
});
-export const UNMANAGED = i18n.translate('xpack.securitySolution.overview.ilmPhaseUnmanaged', {
- defaultMessage: 'unmanaged',
-});
-
export const VIEW_ALERTS = i18n.translate('xpack.securitySolution.overview.viewAlertsButtonLabel', {
defaultMessage: 'View alerts',
});
@@ -100,10 +84,6 @@ export const VIEW_EVENTS = i18n.translate('xpack.securitySolution.overview.viewE
defaultMessage: 'View events',
});
-export const WARM = i18n.translate('xpack.securitySolution.overview.ilmPhaseWarm', {
- defaultMessage: 'warm',
-});
-
export const DETECTION_RESPONSE_TITLE = i18n.translate(
'xpack.securitySolution.detectionResponse.pageTitle',
{
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index c52ee4b30782b..6e59be904f43c 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -6838,8 +6838,6 @@
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "Ajoutez ce rapport de Qualité des données comme contexte",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "Expliquez les résultats ci-dessus et donnez des options pour résoudre les incompatibilités.",
"securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "Vérifier les mappings d'index",
- "securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle": "Mappings de champs",
- "securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel": "Champs",
"securitySolutionPackages.ecsDataQualityDashboard.ecsVersionStat": "Version ECS",
"securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody": "Un problème est survenu lors du chargement des mappings : {error}",
"securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle": "Impossible de charger les mappings d'index",
@@ -6874,6 +6872,9 @@
"securitySolutionPackages.ecsDataQualityDashboard.getResultErrorTitle": "Erreur lors de la lecture des résultats d'examen qualité des données sauvegardées",
"securitySolutionPackages.ecsDataQualityDashboard.hotDescription": "L'index est mis à jour et interrogé de façon active",
"securitySolutionPackages.ecsDataQualityDashboard.hotPatternTooltip": "{indices} {indices, plural, =1 {L'index correspondant} other {Les index correspondants}} au modèle {pattern} {indices, plural, =1 {est} other {sont}} \"hot\". Les index \"hot\" sont mis à jour et interrogés de façon active.",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseCold": "froid",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseFrozen": "frozen",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseHot": "hot",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseLabel": "Phase ILM",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptBody": "La qualité des données sera vérifiée pour les index comprenant ces phases de gestion du cycle de vie des index (ILM, Index Lifecycle Management)",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel": "froid",
@@ -6885,7 +6886,8 @@
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle": "Sélectionner une ou plusieurs phases ILM",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel": "non géré",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel": "warm",
- "securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.dataQualityDashboardConversationId": "Tableau de bord de Qualité des données",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseUnmanaged": "non géré",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseWarm": "warm",
"securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle": "Mappings de champ incompatibles – {indexName}",
"securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle": "Valeurs de champ incompatibles – {indexName}",
"securitySolutionPackages.ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip": "La qualité des données sera vérifiée pour les index comprenant ces phases de gestion du cycle de vie des index (ILM, Index Lifecycle Management)",
@@ -6938,13 +6940,10 @@
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription": "Parfois, les index créés par des intégrations plus anciennes comporteront des mappings ou des valeurs qui étaient conformes, mais ne le sont plus.",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription": "L'index `{indexName}` a des [mappings]({mappingUrl}) ou des valeurs de champ différentes de l'[Elastic Common Schema]({ecsReferenceUrl}) (ECS), [définitions]({ecsFieldReferenceUrl}).de version `{version}`.",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle": "Qualité des données",
- "securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryTab": "Résumé",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.unknownCategoryLabel": "Inconnu",
"securitySolutionPackages.ecsDataQualityDashboard.indexSizeTooltip": "La taille de l'index principal (n'inclut pas de répliques)",
"securitySolutionPackages.ecsDataQualityDashboard.lastCheckedLabel": "Dernière vérification",
"securitySolutionPackages.ecsDataQualityDashboard.patternLabel.allPassedTooltip": "Tous les index correspondant à ce modèle ont réussi les vérifications de qualité des données",
- "securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someFailedTooltip": "Certains index correspondant à ce modèle ont échoué aux vérifications de qualité des données",
- "securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someUncheckedTooltip": "La qualité des données n'a pas été vérifiée pour certains index correspondant à ce modèle",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.docsLabel": "Documents",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.indicesLabel": "Index",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip": "Modèle, ou index spécifique",
@@ -6958,35 +6957,19 @@
"securitySolutionPackages.ecsDataQualityDashboard.selectAnIndexPrompt": "Sélectionner un index pour le comparer à la version ECS",
"securitySolutionPackages.ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder": "Sélectionner une ou plusieurs phases ILM",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.checkedLabel": "vérifié",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customIndexToolTip": "Décompte des mappings d'index personnalisés dans l'index {indexName}",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customLabel": "Personnalisé",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customPatternToolTip": "Nombre total de mappings d'index personnalisés, dans les index correspondant au modèle {pattern}",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.docsLabel": "Documents",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.fieldsLabel": "champs",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip": "Mappings et valeurs incompatibles avec ECS, dans l'index {indexName}",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleLabel": "Incompatible",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip": "Nombre total de champs incompatibles avec ECS, dans les index correspondant au modèle {pattern}",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsCountToolTip": "Nombre de documents dans l'index {indexName}",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip": "Nombre total de documents, dans les index correspondant au modèle {pattern}",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesLabel": "Index",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesSizePatternToolTip": "Taille totale de tous les index principaux correspondant au modèle {pattern} (n'inclut pas les répliques)",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.sameFamilyLabel": "Même famille",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.sameFamilyPatternToolTip": "Le nombre total de champs de la même famille tel que le type spécifié par l'ECS, dans les index correspondant au modèle {pattern}",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.sizeLabel": "Taille",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip": "Nombre total d'index vérifiés correspondant au modèle {pattern}",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip": "Nombre total d'index correspondant au modèle {pattern}",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalDocsToolTip": "Nombre total de documents, dans tous les index",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip": "Nombre total de champs incompatibles avec ECS, dans tous les index qui ont été vérifiés",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip": "Nombre total de tous les index vérifiés",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesToolTip": "Nombre total de tous les index",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalSameFamilyToolTip": "Nombre total de champs de la même famille que le type ECS, dans tous les index qui ont été vérifiés",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalSizeToolTip": "La taille totale de tous les index principaux (n'inclut pas de répliques)",
"securitySolutionPackages.ecsDataQualityDashboard.storage.docs.unit": "{totalCount, plural, =1 {Document} other {Documents}}",
"securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataLabel": "Aucune donnée à afficher",
"securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataReasonLabel": "Le champ {stackByField1} n'était présent dans aucun groupe",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.collapseLabel": "Réduire",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.docsColumn": "Documents",
- "securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandLabel": "Développer",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandRowsColumn": "Développer les lignes",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.failedTooltip": "Échoué",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.ilmPhaseColumn": "Phase ILM",
@@ -40259,11 +40242,6 @@
"xpack.securitySolution.overview.hostStatGroupFilebeat": "Filebeat",
"xpack.securitySolution.overview.hostStatGroupWinlogbeat": "Winlogbeat",
"xpack.securitySolution.overview.hostsTitle": "Événements d'hôte",
- "xpack.securitySolution.overview.ilmPhaseCold": "froid",
- "xpack.securitySolution.overview.ilmPhaseFrozen": "frozen",
- "xpack.securitySolution.overview.ilmPhaseHot": "hot",
- "xpack.securitySolution.overview.ilmPhaseUnmanaged": "non géré",
- "xpack.securitySolution.overview.ilmPhaseWarm": "warm",
"xpack.securitySolution.overview.informationAriaLabel": "Informations",
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "En savoir plus",
"xpack.securitySolution.overview.networkAction": "Afficher le réseau",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index d212feaf16ec6..4c7495ef5734f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -6833,8 +6833,6 @@
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "このデータ品質レポートをコンテキストとして追加",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "上記の結果を説明し、一部のオプションを記述して非互換性を修正します。",
"securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "インデックスマッピングの確認",
- "securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle": "フィールドマッピング",
- "securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel": "フィールド",
"securitySolutionPackages.ecsDataQualityDashboard.ecsVersionStat": "ECSバージョン",
"securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody": "マッピングの読み込み中に問題が発生しました:{error}",
"securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle": "インデックスマッピングを読み込めません",
@@ -6869,6 +6867,9 @@
"securitySolutionPackages.ecsDataQualityDashboard.getResultErrorTitle": "保存されたデータ品質チェック結果の読み取りエラー",
"securitySolutionPackages.ecsDataQualityDashboard.hotDescription": "インデックスはアクティブに更新されており、照会されます",
"securitySolutionPackages.ecsDataQualityDashboard.hotPatternTooltip": "{pattern}パターンと一致する{indices} {indices, plural, other {インデックス}}{indices, plural, other {は}}ホットです。ホットインデックスはアクティブに更新されており、照会されます。",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseCold": "コールド",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseFrozen": "凍結",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseHot": "ホット",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseLabel": "ILMフェーズ",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptBody": "これらのインデックスライフサイクル管理(ILM)フェーズのインデックスはデータ品質が確認されます",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel": "コールド",
@@ -6880,7 +6881,8 @@
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle": "1つ以上のILMフェーズを選択",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel": "管理対象外",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel": "ウォーム",
- "securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.dataQualityDashboardConversationId": "データ品質ダッシュボード",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseUnmanaged": "管理対象外",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseWarm": "ウォーム",
"securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle": "非互換フィールドマッピング - {indexName}",
"securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle": "非互換フィールド値 - {indexName}",
"securitySolutionPackages.ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip": "これらのインデックスライフサイクル管理(ILM)フェーズのインデックスはデータ品質が確認されます",
@@ -6933,13 +6935,10 @@
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription": "場合によって、古い統合で作成されたインデックスには、以前あった互換性がなくなったマッピングまたは値が含まれることがあります。",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription": "`{indexName}`インデックスには、[Elastic Common Schema]({ecsReferenceUrl})(ECS)バージョン`{version}` [definitions]({ecsFieldReferenceUrl})とは異なる[マッピング]({mappingUrl})またはフィールド値があります。",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle": "データ品質",
- "securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryTab": "まとめ",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.unknownCategoryLabel": "不明",
"securitySolutionPackages.ecsDataQualityDashboard.indexSizeTooltip": "プライマリインデックスのサイズ(レプリカは含まない)",
"securitySolutionPackages.ecsDataQualityDashboard.lastCheckedLabel": "前回確認日時",
"securitySolutionPackages.ecsDataQualityDashboard.patternLabel.allPassedTooltip": "このパターンと一致するすべてのインデックスは、データ品質チェックに合格しました",
- "securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someFailedTooltip": "このパターンと一致する一部のインデックスは、データ品質チェックに失敗しました",
- "securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someUncheckedTooltip": "このパターンと一致する一部のインデックスは、データ品質が確認されませんでした",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.docsLabel": "ドキュメント",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.indicesLabel": "インデックス",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip": "パターンまたは特定のインデックス",
@@ -6953,35 +6952,19 @@
"securitySolutionPackages.ecsDataQualityDashboard.selectAnIndexPrompt": "ECSバージョンと比較するインデックスを選択",
"securitySolutionPackages.ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder": "1つ以上のILMフェーズを選択",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.checkedLabel": "確認済み",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customIndexToolTip": "{indexName}インデックスのカスタムフィールドマッピングの件数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customLabel": "カスタム",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customPatternToolTip": "{pattern}パターンと一致するインデックスのカスタムフィールドマッピングの合計件数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.docsLabel": "ドキュメント",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.fieldsLabel": "フィールド",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip": "{indexName}インデックスのESCと互換性があるマッピングと値",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleLabel": "非互換",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip": "{pattern}パターンと一致するインデックスのECSと互換性があるフィールドの合計件数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsCountToolTip": "{indexName}インデックスのドキュメントの件数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip": "{pattern}パターンと一致するインデックスのドキュメントの合計件数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesLabel": "インデックス",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesSizePatternToolTip": "{pattern}パターンと一致するプライマリインデックスの合計サイズ(レプリカを含まない)",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.sameFamilyLabel": "同じファミリー",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.sameFamilyPatternToolTip": "ECSで指定された型と同じファミリーに属する、{pattern}パターンと一致するインデックスのフィールドの総数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.sizeLabel": "サイズ",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip": "{pattern}パターンと一致する確認されたインデックスの合計件数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip": "{pattern}パターンと一致するインデックスの合計件数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalDocsToolTip": "すべてのインデックスのドキュメントの合計数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip": "確認されたすべてのインデックスのECSと互換性がないフィールドの合計件数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip": "確認されたすべてのインデックスの合計数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesToolTip": "すべてのインデックスの合計数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalSameFamilyToolTip": "確認されたすべてのインデックスにおける、ECSの型と同じファミリーのフィールドの総数。",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalSizeToolTip": "すべてのプライマリインデックスの合計サイズ(レプリカを含まない)",
"securitySolutionPackages.ecsDataQualityDashboard.storage.docs.unit": "{totalCount, plural, other {ドキュメント}}",
"securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataLabel": "表示するデータがありません",
"securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataReasonLabel": "{stackByField1}フィールドがどのグループにも存在しませんでした",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.collapseLabel": "縮小",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.docsColumn": "ドキュメント",
- "securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandLabel": "拡張",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandRowsColumn": "行を展開",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.failedTooltip": "失敗",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.ilmPhaseColumn": "ILMフェーズ",
@@ -40242,11 +40225,6 @@
"xpack.securitySolution.overview.hostStatGroupFilebeat": "Filebeat",
"xpack.securitySolution.overview.hostStatGroupWinlogbeat": "Winlogbeat",
"xpack.securitySolution.overview.hostsTitle": "ホストイベント",
- "xpack.securitySolution.overview.ilmPhaseCold": "コールド",
- "xpack.securitySolution.overview.ilmPhaseFrozen": "凍結",
- "xpack.securitySolution.overview.ilmPhaseHot": "ホット",
- "xpack.securitySolution.overview.ilmPhaseUnmanaged": "管理対象外",
- "xpack.securitySolution.overview.ilmPhaseWarm": "ウォーム",
"xpack.securitySolution.overview.informationAriaLabel": "情報",
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "詳細情報",
"xpack.securitySolution.overview.networkAction": "ネットワークを表示",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 0c8effa03f31d..0928a41fb596a 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -6844,8 +6844,6 @@
"securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "将此数据质量报告添加为上下文",
"securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "解释上述结果,并说明某些选项以解决不兼容问题。",
"securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "检查索引映射",
- "securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.chartTitle": "字段映射",
- "securitySolutionPackages.ecsDataQualityDashboard.ecsSummaryDonutChart.fieldsLabel": "字段",
"securitySolutionPackages.ecsDataQualityDashboard.ecsVersionStat": "ECS 版本",
"securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsBody": "加载映射时出现问题:{error}",
"securitySolutionPackages.ecsDataQualityDashboard.emptyErrorPrompt.errorLoadingMappingsTitle": "无法加载索引映射",
@@ -6880,6 +6878,9 @@
"securitySolutionPackages.ecsDataQualityDashboard.getResultErrorTitle": "读取保存的数据质量检查结果时出错",
"securitySolutionPackages.ecsDataQualityDashboard.hotDescription": "该索引会被主动地更新和查询",
"securitySolutionPackages.ecsDataQualityDashboard.hotPatternTooltip": "{indices} 个匹配 {pattern} 模式的{indices, plural, other {索引}}{indices, plural, other {为}}热索引。热索引会被主动地更新和查询。",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseCold": "冷",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseFrozen": "冻结",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseHot": "热",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseLabel": "ILM 阶段",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptBody": "将检查具有这些索引生命周期管理 (ILM) 阶段的索引以了解数据质量",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptColdLabel": "冷",
@@ -6891,7 +6892,8 @@
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptTitle": "选择一个或多个 ILM 阶段",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptUnmanagedLabel": "未受管",
"securitySolutionPackages.ecsDataQualityDashboard.ilmPhasesEmptyPromptWarmLabel": "温",
- "securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.dataQualityDashboardConversationId": "数据质量仪表板",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseUnmanaged": "未受管",
+ "securitySolutionPackages.ecsDataQualityDashboard.ilmPhaseWarm": "温",
"securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldMappingsTableTitle": "不兼容的字段映射 - {indexName}",
"securitySolutionPackages.ecsDataQualityDashboard.incompatibleTab.incompatibleFieldValuesTableTitle": "不兼容的字段值 - {indexName}",
"securitySolutionPackages.ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip": "将检查具有这些索引生命周期管理 (ILM) 阶段的索引以了解数据质量",
@@ -6944,13 +6946,10 @@
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.sometimesIndicesCreatedByOlderDescription": "有时候,用较旧集成创建的索引的映射或值可能过去符合规范,但现在不再符合。",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownDescription": "`{indexName}` 索引具有与 [Elastic Common Schema] ({ecsReferenceUrl}) (ECS) 版本 `{version}` [定义]({ecsFieldReferenceUrl}) 不同的[映射]({mappingUrl}) 或字段值。",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryMarkdownTitle": "数据质量",
- "securitySolutionPackages.ecsDataQualityDashboard.indexProperties.summaryTab": "摘要",
"securitySolutionPackages.ecsDataQualityDashboard.indexProperties.unknownCategoryLabel": "未知",
"securitySolutionPackages.ecsDataQualityDashboard.indexSizeTooltip": "主要索引(不包括副本)的大小",
"securitySolutionPackages.ecsDataQualityDashboard.lastCheckedLabel": "上次检查时间",
"securitySolutionPackages.ecsDataQualityDashboard.patternLabel.allPassedTooltip": "与此模式匹配的所有索引均通过了数据质量检查",
- "securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someFailedTooltip": "与此模式匹配的某些索引未通过数据质量检查",
- "securitySolutionPackages.ecsDataQualityDashboard.patternLabel.someUncheckedTooltip": "与此模式匹配的某些索引尚未进行数据质量检查",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.docsLabel": "文档",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.indicesLabel": "索引",
"securitySolutionPackages.ecsDataQualityDashboard.patternSummary.patternOrIndexTooltip": "模式或特定索引",
@@ -6964,35 +6963,19 @@
"securitySolutionPackages.ecsDataQualityDashboard.selectAnIndexPrompt": "选择索引以将其与 ECS 版本进行比较",
"securitySolutionPackages.ecsDataQualityDashboard.selectOneOrMorPhasesPlaceholder": "选择一个或多个 ILM 阶段",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.checkedLabel": "已检查",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customIndexToolTip": "{indexName} 索引中定制字段映射的计数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customLabel": "定制",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.customPatternToolTip": "与 {pattern} 模式匹配的索引中定制字段映射的总计数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.docsLabel": "文档",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.fieldsLabel": "字段",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleIndexToolTip": "{indexName} 索引中与 ECS 不兼容的映射和值",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatibleLabel": "不兼容",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.incompatiblePatternToolTip": "与 {pattern} 模式匹配的索引中与 ECS 不兼容的字段的总计数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsCountToolTip": "{indexName} 索引中的文档计数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indexDocsPatternToolTip": "与 {pattern} 模式匹配的索引中文档的总计数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesLabel": "索引",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.indicesSizePatternToolTip": "与 {pattern} 模式匹配的主要索引(不包括副本)的总大小",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.sameFamilyLabel": "同一系列",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.sameFamilyPatternToolTip": "与 {pattern} 模式匹配的索引中类型由 ECS 指定的同一系列中的字段总计数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.sizeLabel": "大小",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesCheckedMatchingPatternToolTip": "经检查与 {pattern} 模式匹配的索引的总计数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalCountOfIndicesMatchingPatternToolTip": "与 {pattern} 模式匹配的索引的总计数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalDocsToolTip": "所有索引中文档的总计数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIncompatibleToolTip": "检查的所有索引中与 ECS 不兼容的字段的总计数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesCheckedToolTip": "检查的所有索引的总计数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalIndicesToolTip": "所有索引的总计数",
- "securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalSameFamilyToolTip": "检查的所有索引中与 ECS 类型相同的系列中字段的总计数",
"securitySolutionPackages.ecsDataQualityDashboard.statLabels.totalSizeToolTip": "所有主要索引(不包括副本)的总大小",
"securitySolutionPackages.ecsDataQualityDashboard.storage.docs.unit": "{totalCount, plural, other {个文档}}",
"securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataLabel": "没有可显示的数据",
"securitySolutionPackages.ecsDataQualityDashboard.storageTreemap.noDataReasonLabel": "任何组中都不存在 {stackByField1} 字段",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.collapseLabel": "折叠",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.docsColumn": "文档",
- "securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandLabel": "展开",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.expandRowsColumn": "展开行",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.failedTooltip": "失败",
"securitySolutionPackages.ecsDataQualityDashboard.summaryTable.ilmPhaseColumn": "ILM 阶段",
@@ -40285,11 +40268,6 @@
"xpack.securitySolution.overview.hostStatGroupFilebeat": "Filebeat",
"xpack.securitySolution.overview.hostStatGroupWinlogbeat": "Winlogbeat",
"xpack.securitySolution.overview.hostsTitle": "主机事件",
- "xpack.securitySolution.overview.ilmPhaseCold": "冷",
- "xpack.securitySolution.overview.ilmPhaseFrozen": "冻结",
- "xpack.securitySolution.overview.ilmPhaseHot": "热",
- "xpack.securitySolution.overview.ilmPhaseUnmanaged": "未受管",
- "xpack.securitySolution.overview.ilmPhaseWarm": "温",
"xpack.securitySolution.overview.informationAriaLabel": "信息",
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "了解详情",
"xpack.securitySolution.overview.networkAction": "查看网络",