Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
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',
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',
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',
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',
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',
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