Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export const dataStreamStatRt = rt.intersection([
totalDocs: rt.number,
creationDate: rt.number,
hasFailureStore: rt.boolean,
customRetentionPeriod: rt.string,
defaultRetentionPeriod: rt.string,
}),
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export class DataStreamStat {
degradedDocs: QualityStat;
failedDocs: QualityStat;
hasFailureStore?: DataStreamStatType['hasFailureStore'];
defaultRetentionPeriod?: DataStreamStatType['defaultRetentionPeriod'];
customRetentionPeriod?: DataStreamStatType['customRetentionPeriod'];

private constructor(dataStreamStat: DataStreamStat) {
this.rawName = dataStreamStat.rawName;
Expand All @@ -51,6 +53,8 @@ export class DataStreamStat {
this.degradedDocs = dataStreamStat.degradedDocs;
this.failedDocs = dataStreamStat.failedDocs;
this.hasFailureStore = dataStreamStat.hasFailureStore;
this.defaultRetentionPeriod = dataStreamStat.defaultRetentionPeriod;
this.customRetentionPeriod = dataStreamStat.customRetentionPeriod;
}

public static create(dataStreamStat: DataStreamStatType) {
Expand All @@ -71,6 +75,8 @@ export class DataStreamStat {
quality: DEFAULT_DATASET_QUALITY,
degradedDocs: DEFAULT_QUALITY_DOC_STATS,
failedDocs: DEFAULT_QUALITY_DOC_STATS,
defaultRetentionPeriod: dataStreamStat.defaultRetentionPeriod,
customRetentionPeriod: dataStreamStat.customRetentionPeriod,
};

return new DataStreamStat(dataStreamStatProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import {
EuiIconTip,
EuiLink,
EuiSkeletonRectangle,
EuiTableHeader,
EuiText,
EuiToolTip,
formatNumber,
} from '@elastic/eui';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
Expand All @@ -39,6 +37,7 @@ import { IntegrationIcon, PrivilegesWarningIconWrapper } from '../../common';
import { DatasetQualityIndicator, QualityIndicator } from '../../quality_indicator';
import { DatasetQualityDetailsLink } from './dataset_quality_details_link';
import { QualityStatPercentageLink } from './quality_stat_percentage_link';
import { FailureStoreHoverLink } from './failure_store_link';

const nameColumnName = i18n.translate('xpack.datasetQuality.nameColumnName', {
defaultMessage: 'Data set name',
Expand Down Expand Up @@ -196,9 +195,8 @@ export const getDatasetQualityTableColumns = ({
}): Array<EuiBasicTableColumn<DataStreamStat>> => {
return [
{
name: (
<EuiTableHeader data-test-subj="datasetQualityNameColumn">{nameColumnName}</EuiTableHeader>
),
name: nameColumnName,
'data-test-subj': 'datasetQualityNameColumn',
Comment on lines +198 to +199
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related with this issue but it has been changed to avoid this console error:
Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.

field: 'title',
sortable: true,
render: (title: string, dataStreamStat: DataStreamStat) => {
Expand Down Expand Up @@ -226,11 +224,8 @@ export const getDatasetQualityTableColumns = ({
},
},
{
name: (
<EuiTableHeader data-test-subj="datasetQualityNamespaceColumn">
{namespaceColumnName}
</EuiTableHeader>
),
name: namespaceColumnName,
'data-test-subj': 'datasetQualityNamespaceColumn',
Comment on lines +227 to +228
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related with this issue but it has been changed to avoid this console error:
Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.

field: 'namespace',
sortable: true,
render: (_, dataStreamStat: DataStreamStat) => (
Expand All @@ -250,11 +245,8 @@ export const getDatasetQualityTableColumns = ({
...(canUserMonitorAnyDataset && canUserMonitorAnyDataStream
? [
{
name: (
<EuiTableHeader data-test-subj="datasetQualitySizeColumn">
{sizeColumnName}
</EuiTableHeader>
),
name: sizeColumnName,
'data-test-subj': 'datasetQualitySizeColumn',
Comment on lines +248 to +249
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related with this issue but it has been changed to avoid this console error:
Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.

field: 'sizeBytes',
sortable: true,
render: (_: any, dataStreamStat: DataStreamStat) => {
Expand Down Expand Up @@ -282,11 +274,8 @@ export const getDatasetQualityTableColumns = ({
]
: []),
{
name: (
<EuiTableHeader data-test-subj="datasetQualityQualityColumn">
<span>{datasetQualityColumnName}</span>
</EuiTableHeader>
),
name: datasetQualityColumnName,
'data-test-subj': 'datasetQualityQualityColumn',
Comment on lines +277 to +278
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related with this issue but it has been changed to avoid this console error:
Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.

nameTooltip: {
content: datasetQualityColumnTooltip,
icon: 'question',
Expand All @@ -302,11 +291,8 @@ export const getDatasetQualityTableColumns = ({
width: '140px',
},
{
name: (
<EuiTableHeader data-test-subj="datasetQualityPercentageColumn">
<span>{degradedDocsColumnName}</span>
</EuiTableHeader>
),
name: degradedDocsColumnName,
'data-test-subj': 'datasetQualityPercentageColumn',
Comment on lines +294 to +295
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related with this issue but it has been changed to avoid this console error:
Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.

nameTooltip: {
content: degradedDocsColumnTooltip,
icon: 'question',
Expand Down Expand Up @@ -336,11 +322,8 @@ export const getDatasetQualityTableColumns = ({
...(canReadFailureStore
? [
{
name: (
<EuiTableHeader data-test-subj="datasetQualityFailedPercentageColumn">
<span>{failedDocsColumnName}</span>
</EuiTableHeader>
),
name: failedDocsColumnName,
'data-test-subj': 'datasetQualityFailedPercentageColumn',
Comment on lines +325 to +326
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related with this issue but it has been changed to avoid this console error:
Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.

nameTooltip: {
content: failedDocsColumnTooltip,
icon: 'question',
Expand All @@ -352,42 +335,7 @@ export const getDatasetQualityTableColumns = ({
!dataStreamStat.hasFailureStore &&
dataStreamStat.userPrivileges?.canReadFailureStore
) {
const FailureStoreHoverLink = () => {
const [hovered, setHovered] = React.useState(false);
const locator = urlService.locators.get('INDEX_MANAGEMENT_LOCATOR_ID');
const params = {
page: 'data_streams_details',
dataStreamName: dataStreamStat.rawName,
} as const;

return (
<EuiToolTip
content={i18n.translate('xpack.datasetQuality.failureStore.notEnabled', {
defaultMessage:
'Failure store is not enabled for this data stream. Enable failure store.',
})}
>
<EuiLink
href={locator?.getRedirectUrl(params)}
target="_blank"
external={false}
data-test-subj="datasetQualitySetFailureStoreLink"
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
css={{ fontWeight: 'normal' }}
>
{hovered
? i18n.translate('xpack.datasetQuality.failureStore.enable', {
defaultMessage: 'Set failure store',
})
: i18n.translate('xpack.datasetQuality.failureStore.notAvailable', {
defaultMessage: 'N/A',
})}
</EuiLink>
</EuiToolTip>
);
};
return <FailureStoreHoverLink />;
return <FailureStoreHoverLink dataStreamStat={dataStreamStat} />;
}
return (
<PrivilegesWarningIconWrapper
Expand Down Expand Up @@ -420,11 +368,8 @@ export const getDatasetQualityTableColumns = ({
...(canUserMonitorAnyDataset && canUserMonitorAnyDataStream
? [
{
name: (
<EuiTableHeader data-test-subj="datasetQualityLastActivityColumn">
{lastActivityColumnName}
</EuiTableHeader>
),
name: lastActivityColumnName,
'data-test-subj': 'datasetQualityLastActivityColumn',
field: 'lastActivity',
render: (timestamp: number) => (
<EuiSkeletonRectangle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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 { screen, fireEvent, waitFor } from '@testing-library/react';
import { renderWithI18n } from '@kbn/test-jest-helpers';
import { FailureStoreHoverLink } from './failure_store_link';
import type { DataStreamStat } from '../../../../common/data_streams_stats';

const mockUpdateFailureStore = jest.fn();
jest.mock('../../../hooks', () => ({
useDatasetQualityTable: () => ({
updateFailureStore: mockUpdateFailureStore,
}),
}));

describe('FailureStoreHoverLink', () => {
const mockDataStreamStat: DataStreamStat = {
rawName: 'logs-test-stream-default',
type: 'logs',
name: 'test-stream',
namespace: 'default',
title: 'Test Stream',
hasFailureStore: false,
defaultRetentionPeriod: '30d',
customRetentionPeriod: undefined,
} as DataStreamStat;

beforeEach(() => {
mockUpdateFailureStore.mockClear();
});

describe('table', () => {
it('renders the button with "N/A" text by default', () => {
renderWithI18n(<FailureStoreHoverLink dataStreamStat={mockDataStreamStat} />);

const button = screen.getByTestId('datasetQualitySetFailureStoreLink');
expect(button).toBeInTheDocument();
expect(button).toHaveTextContent('N/A');
});

it('shows tooltip with correct message', async () => {
renderWithI18n(<FailureStoreHoverLink dataStreamStat={mockDataStreamStat} />);

const button = screen.getByTestId('datasetQualitySetFailureStoreLink');
fireEvent.mouseEnter(button);

await waitFor(() => {
expect(screen.getByTestId('failureStoreNotEnabledTooltip')).toBeInTheDocument();
});
});

it('changes text to "Set failure store" on hover', () => {
renderWithI18n(<FailureStoreHoverLink dataStreamStat={mockDataStreamStat} />);

const button = screen.getByTestId('datasetQualitySetFailureStoreLink');
expect(button).toHaveTextContent('N/A');

fireEvent.mouseEnter(button);
expect(button).toHaveTextContent('Set failure store');

fireEvent.mouseLeave(button);
expect(button).toHaveTextContent('N/A');
});

it('opens modal when button is clicked', () => {
renderWithI18n(<FailureStoreHoverLink dataStreamStat={mockDataStreamStat} />);

const button = screen.getByTestId('datasetQualitySetFailureStoreLink');
fireEvent.click(button);

expect(screen.getByTestId('editFailureStoreModal')).toBeInTheDocument();
});

it('opens modal for a data stream with failure store disabled', () => {
const disabledFsDataStream = {
...mockDataStreamStat,
hasFailureStore: false,
} as DataStreamStat;

renderWithI18n(<FailureStoreHoverLink dataStreamStat={disabledFsDataStream} />);

const button = screen.getByTestId('datasetQualitySetFailureStoreLink');
fireEvent.click(button);

expect(screen.getByTestId('editFailureStoreModal')).toBeInTheDocument();
});

it('calls updateFailureStore with custom retention period when provided', async () => {
renderWithI18n(<FailureStoreHoverLink dataStreamStat={mockDataStreamStat} />);

fireEvent.click(screen.getByTestId('datasetQualitySetFailureStoreLink'));
fireEvent.click(screen.getByTestId('enableFailureStoreToggle'));
fireEvent.click(screen.getByTestId('failureStoreModalSaveButton'));

await waitFor(() => {
expect(mockUpdateFailureStore).toHaveBeenCalledWith({
failureStoreEnabled: true,
customRetentionPeriod: undefined,
dataStreamName: 'logs-test-stream-default',
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
import { FailureStoreModal } from '@kbn/failure-store-modal';
import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { useDatasetQualityTable } from '../../../hooks';
import type { DataStreamStat } from '../../../../common/data_streams_stats';

export const FailureStoreHoverLink: React.FC<{
dataStreamStat: DataStreamStat;
}> = ({ dataStreamStat }) => {
const [hovered, setHovered] = React.useState(false);
const [isFailureStoreModalOpen, setIsFailureStoreModalOpen] = useState(false);
const { updateFailureStore } = useDatasetQualityTable();

const closeModal = () => {
setIsFailureStoreModalOpen(false);
};
const handleSaveModal = async (data: {
failureStoreEnabled: boolean;
customRetentionPeriod?: string;
}) => {
updateFailureStore({
dataStreamName: dataStreamStat.rawName,
failureStoreEnabled: data.failureStoreEnabled,
customRetentionPeriod: data.customRetentionPeriod,
});
closeModal();
};

const onClick = () => {
setIsFailureStoreModalOpen(true);
};

return (
<>
<EuiToolTip
content={i18n.translate('xpack.datasetQuality.failureStore.notEnabled', {
defaultMessage:
'Failure store is not enabled for this data stream. Enable failure store.',
})}
data-test-subj="failureStoreNotEnabledTooltip"
>
<EuiButtonEmpty
data-test-subj="datasetQualitySetFailureStoreLink"
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
css={{ fontWeight: 'normal' }}
aria-label={i18n.translate('xpack.datasetQuality.failureStore.setAriaLabel', {
defaultMessage: 'Set failure store for data stream {dataStreamName}',
values: { dataStreamName: dataStreamStat.rawName },
})}
onClick={onClick}
>
{hovered
? i18n.translate('xpack.datasetQuality.failureStore.enable', {
defaultMessage: 'Set failure store',
})
: i18n.translate('xpack.datasetQuality.failureStore.notAvailable', {
defaultMessage: 'N/A',
})}
</EuiButtonEmpty>
</EuiToolTip>
{isFailureStoreModalOpen && (
<FailureStoreModal
onCloseModal={closeModal}
onSaveModal={handleSaveModal}
failureStoreProps={{
failureStoreEnabled: dataStreamStat.hasFailureStore ?? false,
defaultRetentionPeriod: dataStreamStat.defaultRetentionPeriod,
customRetentionPeriod: dataStreamStat.customRetentionPeriod,
}}
/>
)}
</>
);
};
Loading