Skip to content

Commit 06ee7e8

Browse files
authored
[9.2] [Data set quality] Improve failure store coverage (#238251) (#240416)
# Backport This will backport the following commits from `main` to `9.2`: - [[Data set quality] Improve failure store coverage (#238251)](#238251) <!--- Backport version: 10.1.0 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Sonia Sanz Vivas","email":"sonia.sanzvivas@elastic.co"},"sourceCommit":{"committedDate":"2025-10-22T08:22:22Z","message":"[Data set quality] Improve failure store coverage (#238251)\n\nAddresses https://github.com/elastic/kibana/issues/235387\n\n## Summary\nThis PR improves unit tests coverage of the components, hooks and routes\nrelated with Failure Store in Data Quality. It also adds some API\nIntegration tests for the Upgrade failure store and Datastream Details.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a5e2f6d861ebdb5ea645c9bf1a1a5d7fc5ecd97d","branchLabelMapping":{"^v9.3.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Kibana Management","release_note:skip","backport missing","backport:version","Feature:Streams","v9.2.0","v9.3.0"],"title":"[Data set quality] Improve failure store coverage","number":238251,"url":"https://github.com/elastic/kibana/pull/238251","mergeCommit":{"message":"[Data set quality] Improve failure store coverage (#238251)\n\nAddresses https://github.com/elastic/kibana/issues/235387\n\n## Summary\nThis PR improves unit tests coverage of the components, hooks and routes\nrelated with Failure Store in Data Quality. It also adds some API\nIntegration tests for the Upgrade failure store and Datastream Details.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a5e2f6d861ebdb5ea645c9bf1a1a5d7fc5ecd97d"}},"sourceBranch":"main","suggestedTargetBranches":["9.2"],"targetPullRequestStates":[{"branch":"9.2","label":"v9.2.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.3.0","branchLabelMappingKey":"^v9.3.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/238251","number":238251,"mergeCommit":{"message":"[Data set quality] Improve failure store coverage (#238251)\n\nAddresses https://github.com/elastic/kibana/issues/235387\n\n## Summary\nThis PR improves unit tests coverage of the components, hooks and routes\nrelated with Failure Store in Data Quality. It also adds some API\nIntegration tests for the Upgrade failure store and Datastream Details.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a5e2f6d861ebdb5ea645c9bf1a1a5d7fc5ecd97d"}}]}] BACKPORT-->
1 parent 47d9325 commit 06ee7e8

10 files changed

Lines changed: 1358 additions & 7 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { screen, render, fireEvent } from '@testing-library/react';
10+
import { Card } from './card';
11+
12+
describe('Card', () => {
13+
const defaultProps = {
14+
title: 'Test Card Title',
15+
kpiValue: '1,234',
16+
footer: <div>Test Footer</div>,
17+
};
18+
19+
it('renders basic card content correctly', () => {
20+
render(<Card {...defaultProps} />);
21+
22+
const card = screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title');
23+
expect(card).toBeTruthy();
24+
expect(card).toHaveTextContent('Test Card Title');
25+
expect(card).toHaveTextContent('1,234');
26+
expect(card).toHaveTextContent('Test Footer');
27+
});
28+
29+
it('renders as a div when onClick is not provided', () => {
30+
render(<Card {...defaultProps} />);
31+
32+
const card = screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title');
33+
expect(card.tagName).toBe('DIV');
34+
});
35+
36+
it('renders as a button when onClick is provided', () => {
37+
const onClick = jest.fn();
38+
render(<Card {...defaultProps} onClick={onClick} />);
39+
40+
const card = screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title');
41+
expect(card.tagName).toBe('BUTTON');
42+
expect(card.getAttribute('aria-label')).toBe('Test Card Title');
43+
});
44+
45+
it('calls onClick when card is clicked', () => {
46+
const onClick = jest.fn();
47+
render(<Card {...defaultProps} onClick={onClick} />);
48+
49+
const card = screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title');
50+
fireEvent.click(card);
51+
52+
expect(onClick).toHaveBeenCalledTimes(1);
53+
});
54+
55+
it('shows disabled state when isDisabled is true', () => {
56+
const onClick = jest.fn();
57+
render(<Card {...defaultProps} onClick={onClick} isDisabled />);
58+
59+
const card = screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title');
60+
expect(card.hasAttribute('disabled')).toBe(true);
61+
});
62+
63+
it('renders tooltip when titleTooltipContent is provided', () => {
64+
const tooltipContent = <div>Tooltip content</div>;
65+
render(<Card {...defaultProps} titleTooltipContent={tooltipContent} />);
66+
67+
const tooltipIcon = screen
68+
.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title')
69+
.querySelector('[data-euiicon-type="question"]');
70+
expect(tooltipIcon).toBeTruthy();
71+
});
72+
73+
it('renders title without tooltip when titleTooltipContent is not provided', () => {
74+
render(<Card {...defaultProps} />);
75+
76+
const tooltipIcon = screen
77+
.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title')
78+
.querySelector('[data-euiicon-type="question"]');
79+
expect(tooltipIcon).toBe(null);
80+
});
81+
82+
it('shows loading skeleton when isLoading is true', () => {
83+
render(<Card {...defaultProps} isLoading />);
84+
85+
// Check for skeleton elements
86+
const skeletonTitle = document.querySelector('.euiSkeletonTitle');
87+
const skeletonText = document.querySelector('.euiSkeletonText');
88+
89+
expect(skeletonTitle).toBeTruthy();
90+
expect(skeletonText).toBeTruthy();
91+
});
92+
93+
it('renders complex footer content', () => {
94+
const complexFooter = (
95+
<div>
96+
<span>Complex</span>
97+
<button>Footer</button>
98+
</div>
99+
);
100+
render(<Card {...defaultProps} footer={complexFooter} />);
101+
102+
expect(
103+
screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Test Card Title')
104+
).toHaveTextContent('Complex');
105+
expect(screen.getByRole('button', { name: 'Footer' })).toBeTruthy();
106+
});
107+
108+
it('handles empty kpiValue gracefully', () => {
109+
render(<Card {...defaultProps} kpiValue="" />);
110+
111+
const kpiValue = screen.getByTestId('datasetQualityDetailsSummaryKpiValue-Test Card Title');
112+
expect(kpiValue).toBeTruthy();
113+
expect(kpiValue.textContent).toBe('');
114+
});
115+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { screen, fireEvent } from '@testing-library/react';
10+
import { renderWithI18n } from '@kbn/test-jest-helpers';
11+
import QualitySummaryCards from '.';
12+
13+
jest.mock('../../../../hooks/use_overview_summary_panel', () => ({
14+
useOverviewSummaryPanel: jest.fn(),
15+
}));
16+
17+
jest.mock('../../../../hooks/use_quality_issues_docs_chart', () => ({
18+
useQualityIssuesDocsChart: jest.fn(),
19+
}));
20+
21+
jest.mock('../../../../hooks/use_dataset_quality_details_state', () => ({
22+
useDatasetQualityDetailsState: jest.fn(),
23+
}));
24+
25+
jest.mock('../../../../hooks/use_failure_store_modal', () => ({
26+
useFailureStoreModal: jest.fn(),
27+
}));
28+
29+
import { useOverviewSummaryPanel } from '../../../../hooks/use_overview_summary_panel';
30+
import { useQualityIssuesDocsChart } from '../../../../hooks/use_quality_issues_docs_chart';
31+
import { useDatasetQualityDetailsState } from '../../../../hooks/use_dataset_quality_details_state';
32+
import { useFailureStoreModal } from '../../../../hooks/use_failure_store_modal';
33+
34+
describe('QualitySummaryCards', () => {
35+
const mockUseOverviewSummaryPanel = useOverviewSummaryPanel as jest.Mock;
36+
const mockUseQualityIssuesDocsChart = useQualityIssuesDocsChart as jest.Mock;
37+
const mockUseDatasetQualityDetailsState = useDatasetQualityDetailsState as jest.Mock;
38+
const mockUseFailureStoreModal = useFailureStoreModal as jest.Mock;
39+
40+
const defaultSummaryPanelData = {
41+
totalDocsCount: '10000',
42+
isSummaryPanelLoading: false,
43+
totalDegradedDocsCount: '100',
44+
totalFailedDocsCount: '50',
45+
degradedPercentage: 1.0,
46+
failedPercentage: 0.5,
47+
degradedQuality: 'good',
48+
failedQuality: 'excellent',
49+
};
50+
51+
const defaultDocsTrendChartData = {
52+
handleDocsTrendChartChange: jest.fn(),
53+
};
54+
55+
const defaultDetailsState = {
56+
loadingState: {
57+
dataStreamSettingsLoading: false,
58+
dataStreamDetailsLoading: false,
59+
},
60+
};
61+
62+
const defaultFailureStoreModal = {
63+
openModal: jest.fn(),
64+
canUserReadFailureStore: true,
65+
canUserManageFailureStore: true,
66+
hasFailureStore: true,
67+
renderModal: jest.fn(() => null),
68+
};
69+
70+
const defaultProps = {
71+
selectedCard: 'degraded' as const,
72+
setSelectedCard: jest.fn(),
73+
};
74+
75+
beforeEach(() => {
76+
jest.clearAllMocks();
77+
mockUseOverviewSummaryPanel.mockReturnValue(defaultSummaryPanelData);
78+
mockUseQualityIssuesDocsChart.mockReturnValue(defaultDocsTrendChartData);
79+
mockUseDatasetQualityDetailsState.mockReturnValue(defaultDetailsState);
80+
mockUseFailureStoreModal.mockReturnValue(defaultFailureStoreModal);
81+
});
82+
83+
it('renders degraded docs card correctly', () => {
84+
renderWithI18n(<QualitySummaryCards {...defaultProps} />);
85+
86+
expect(
87+
screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Degraded documents')
88+
).toBeTruthy();
89+
expect(
90+
screen.getByTestId('datasetQualityDetailsSummaryKpiValue-Degraded documents')
91+
).toHaveTextContent(defaultSummaryPanelData.totalDegradedDocsCount);
92+
});
93+
94+
it('renders failed docs card when failure store is available and user has read permission', () => {
95+
renderWithI18n(<QualitySummaryCards {...defaultProps} />);
96+
97+
expect(screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Failed documents')).toBeTruthy();
98+
expect(
99+
screen.getByTestId('datasetQualityDetailsSummaryKpiValue-Failed documents')
100+
).toHaveTextContent(defaultSummaryPanelData.totalFailedDocsCount);
101+
});
102+
103+
it('renders no failure store card when failure store is not available', () => {
104+
mockUseFailureStoreModal.mockReturnValue({
105+
...defaultFailureStoreModal,
106+
hasFailureStore: false,
107+
canUserReadFailureStore: false,
108+
});
109+
110+
renderWithI18n(<QualitySummaryCards {...defaultProps} />);
111+
const noFailureStoreCard = screen.getByTestId(
112+
'datasetQualityDetailsSummaryKpiCard-noFailureStore'
113+
);
114+
115+
expect(noFailureStoreCard).toBeTruthy();
116+
expect(noFailureStoreCard).toHaveTextContent('No failure store');
117+
});
118+
119+
it('does not show enable failure store button when user cannot manage failure store', () => {
120+
mockUseFailureStoreModal.mockReturnValue({
121+
...defaultFailureStoreModal,
122+
hasFailureStore: false,
123+
canUserReadFailureStore: true,
124+
canUserManageFailureStore: false,
125+
});
126+
127+
renderWithI18n(<QualitySummaryCards {...defaultProps} />);
128+
const noFailureStoreCard = screen.getByTestId(
129+
'datasetQualityDetailsSummaryKpiCard-noFailureStore'
130+
);
131+
132+
expect(noFailureStoreCard).toBeTruthy();
133+
expect(noFailureStoreCard).toHaveTextContent('No failure store');
134+
expect(screen.queryByTestId('datasetQualityDetailsEnableFailureStoreButton')).toBe(null);
135+
});
136+
137+
it('calls handleDocsTrendChartChange and setSelectedCard when degraded card is clicked', () => {
138+
const setSelectedCard = jest.fn();
139+
const handleDocsTrendChartChange = jest.fn();
140+
141+
mockUseQualityIssuesDocsChart.mockReturnValue({
142+
handleDocsTrendChartChange,
143+
});
144+
145+
renderWithI18n(<QualitySummaryCards {...defaultProps} setSelectedCard={setSelectedCard} />);
146+
147+
const degradedCard = screen.getByTestId(
148+
'datasetQualityDetailsSummaryKpiCard-Degraded documents'
149+
);
150+
fireEvent.click(degradedCard);
151+
152+
expect(handleDocsTrendChartChange).toHaveBeenCalledWith('degraded');
153+
expect(setSelectedCard).toHaveBeenCalledWith('degraded');
154+
});
155+
156+
it('calls handleDocsTrendChartChange and setSelectedCard when failed card is clicked', () => {
157+
const setSelectedCard = jest.fn();
158+
const handleDocsTrendChartChange = jest.fn();
159+
160+
mockUseQualityIssuesDocsChart.mockReturnValue({
161+
handleDocsTrendChartChange,
162+
});
163+
164+
renderWithI18n(<QualitySummaryCards {...defaultProps} setSelectedCard={setSelectedCard} />);
165+
166+
const failedCard = screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Failed documents');
167+
fireEvent.click(failedCard);
168+
169+
expect(handleDocsTrendChartChange).toHaveBeenCalledWith('failed');
170+
expect(setSelectedCard).toHaveBeenCalledWith('failed');
171+
});
172+
173+
it('indicates when degraded card is selected', () => {
174+
renderWithI18n(<QualitySummaryCards {...defaultProps} selectedCard="degraded" />);
175+
176+
const degradedCard = screen.getByTestId(
177+
'datasetQualityDetailsSummaryKpiCard-Degraded documents'
178+
);
179+
const failedCard = screen.getByTestId('datasetQualityDetailsSummaryKpiCard-Failed documents');
180+
181+
// The degraded card should be selected (has primary color class, not text)
182+
expect(degradedCard.className.includes('primary')).toBe(true);
183+
expect(degradedCard.className.includes('text')).toBe(false);
184+
185+
// The failed card should not be selected (no primary color class but text)
186+
expect(failedCard.className.includes('primary')).toBe(false);
187+
expect(failedCard.className.includes('text')).toBe(true);
188+
});
189+
190+
it('shows enable failure store button when user can manage failure store but no failure store exists', () => {
191+
mockUseFailureStoreModal.mockReturnValue({
192+
...defaultFailureStoreModal,
193+
hasFailureStore: false,
194+
canUserReadFailureStore: false,
195+
canUserManageFailureStore: true,
196+
});
197+
198+
renderWithI18n(<QualitySummaryCards {...defaultProps} />);
199+
200+
expect(screen.getByTestId('datasetQualityDetailsEnableFailureStoreButton')).toBeTruthy();
201+
});
202+
203+
it('calls openModal when enable failure store button is clicked', () => {
204+
const openModal = jest.fn();
205+
206+
mockUseFailureStoreModal.mockReturnValue({
207+
...defaultFailureStoreModal,
208+
hasFailureStore: false,
209+
canUserReadFailureStore: false,
210+
canUserManageFailureStore: true,
211+
openModal,
212+
});
213+
214+
renderWithI18n(<QualitySummaryCards {...defaultProps} />);
215+
216+
const enableButton = screen.getByTestId('datasetQualityDetailsEnableFailureStoreButton');
217+
fireEvent.click(enableButton);
218+
219+
expect(openModal).toHaveBeenCalledTimes(1);
220+
});
221+
222+
it('renders failure store modal when renderModal is called', () => {
223+
const renderModal = jest.fn(() => <div data-testid="failure-store-modal">Modal</div>);
224+
225+
mockUseFailureStoreModal.mockReturnValue({
226+
...defaultFailureStoreModal,
227+
hasFailureStore: false,
228+
canUserReadFailureStore: true,
229+
canUserManageFailureStore: true,
230+
renderModal,
231+
});
232+
233+
renderWithI18n(<QualitySummaryCards {...defaultProps} />);
234+
235+
expect(renderModal).toHaveBeenCalled();
236+
});
237+
});

0 commit comments

Comments
 (0)