diff --git a/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts b/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts index 0fa0b16641594..07b90b3d3359e 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/common/api_types.ts @@ -50,6 +50,8 @@ export const dataStreamStatRt = rt.intersection([ totalDocs: rt.number, creationDate: rt.number, hasFailureStore: rt.boolean, + customRetentionPeriod: rt.string, + defaultRetentionPeriod: rt.string, }), ]); diff --git a/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/data_stream_stat.ts b/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/data_stream_stat.ts index a0be790fd46e7..5b99515403177 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/data_stream_stat.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/common/data_streams_stats/data_stream_stat.ts @@ -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; @@ -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) { @@ -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); diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/table/columns.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/table/columns.tsx index 287f62b40686b..489207b594dd3 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/table/columns.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/table/columns.tsx @@ -14,9 +14,7 @@ import { EuiIconTip, EuiLink, EuiSkeletonRectangle, - EuiTableHeader, EuiText, - EuiToolTip, formatNumber, } from '@elastic/eui'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -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', @@ -196,9 +195,8 @@ export const getDatasetQualityTableColumns = ({ }): Array> => { return [ { - name: ( - {nameColumnName} - ), + name: nameColumnName, + 'data-test-subj': 'datasetQualityNameColumn', field: 'title', sortable: true, render: (title: string, dataStreamStat: DataStreamStat) => { @@ -226,11 +224,8 @@ export const getDatasetQualityTableColumns = ({ }, }, { - name: ( - - {namespaceColumnName} - - ), + name: namespaceColumnName, + 'data-test-subj': 'datasetQualityNamespaceColumn', field: 'namespace', sortable: true, render: (_, dataStreamStat: DataStreamStat) => ( @@ -250,11 +245,8 @@ export const getDatasetQualityTableColumns = ({ ...(canUserMonitorAnyDataset && canUserMonitorAnyDataStream ? [ { - name: ( - - {sizeColumnName} - - ), + name: sizeColumnName, + 'data-test-subj': 'datasetQualitySizeColumn', field: 'sizeBytes', sortable: true, render: (_: any, dataStreamStat: DataStreamStat) => { @@ -282,11 +274,8 @@ export const getDatasetQualityTableColumns = ({ ] : []), { - name: ( - - {datasetQualityColumnName} - - ), + name: datasetQualityColumnName, + 'data-test-subj': 'datasetQualityQualityColumn', nameTooltip: { content: datasetQualityColumnTooltip, icon: 'question', @@ -302,11 +291,8 @@ export const getDatasetQualityTableColumns = ({ width: '140px', }, { - name: ( - - {degradedDocsColumnName} - - ), + name: degradedDocsColumnName, + 'data-test-subj': 'datasetQualityPercentageColumn', nameTooltip: { content: degradedDocsColumnTooltip, icon: 'question', @@ -336,11 +322,8 @@ export const getDatasetQualityTableColumns = ({ ...(canReadFailureStore ? [ { - name: ( - - {failedDocsColumnName} - - ), + name: failedDocsColumnName, + 'data-test-subj': 'datasetQualityFailedPercentageColumn', nameTooltip: { content: failedDocsColumnTooltip, icon: 'question', @@ -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 ( - - 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', - })} - - - ); - }; - return ; + return ; } return ( - {lastActivityColumnName} - - ), + name: lastActivityColumnName, + 'data-test-subj': 'datasetQualityLastActivityColumn', field: 'lastActivity', render: (timestamp: number) => ( ({ + 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(); + + const button = screen.getByTestId('datasetQualitySetFailureStoreLink'); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('N/A'); + }); + + it('shows tooltip with correct message', async () => { + renderWithI18n(); + + 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(); + + 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(); + + 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(); + + const button = screen.getByTestId('datasetQualitySetFailureStoreLink'); + fireEvent.click(button); + + expect(screen.getByTestId('editFailureStoreModal')).toBeInTheDocument(); + }); + + it('calls updateFailureStore with custom retention period when provided', async () => { + renderWithI18n(); + + 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', + }); + }); + }); + }); +}); diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/table/failure_store_link.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/table/failure_store_link.tsx new file mode 100644 index 0000000000000..14f726614f4e7 --- /dev/null +++ b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality/table/failure_store_link.tsx @@ -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 ( + <> + + 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', + })} + + + {isFailureStoreModalOpen && ( + + )} + + ); +}; diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx index 2ce297ab68d81..466aefc4e560b 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_dataset_quality_table.tsx @@ -208,6 +208,31 @@ export const useDatasetQualityTable = () => { ); }, [rowsPerPage, page, renderedItems.length, datasets.length]); + const updateFailureStore = useCallback( + ({ + failureStoreEnabled, + customRetentionPeriod, + dataStreamName, + }: { + failureStoreEnabled: boolean; + customRetentionPeriod?: string; + dataStreamName: string; + }) => { + const dataStream = datasets.find((ds) => ds.rawName === dataStreamName); + if (!dataStream) return; + + service.send({ + type: 'UPDATE_FAILURE_STORE', + dataStream: { + ...dataStream, + hasFailureStore: failureStoreEnabled, + customRetentionPeriod, + }, + }); + }, + [service, datasets] + ); + return { sort: { sort }, onTableChange, @@ -223,5 +248,6 @@ export const useDatasetQualityTable = () => { canUserMonitorAnyDataStream, toggleInactiveDatasets, toggleFullDatasetNames, + updateFailureStore, }; }; diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/plugin.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/plugin.tsx index 6288fe088b303..a1b881bc883fb 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/plugin.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/plugin.tsx @@ -41,6 +41,7 @@ export class DatasetQualityPlugin const dataStreamStatsService = new DataStreamsStatsService().start({ http: core.http, + telemetryClient, }); const dataStreamDetailsService = new DataStreamDetailsService().start({ diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts index cd22c484b4a1d..5f0e6b7105496 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts @@ -14,6 +14,7 @@ import type { DataStreamTotalDocsResponse, IntegrationsResponse, NonAggregatableDatasets, + UpdateFailureStoreResponse, } from '../../../common/api_types'; import { getDataStreamDegradedDocsResponseRt, @@ -23,6 +24,7 @@ import { getDataStreamTotalDocsResponseRt, getIntegrationsResponseRt, getNonAggregatableDatasetsRt, + updateFailureStoreResponseRt, } from '../../../common/api_types'; import { KNOWN_TYPES } from '../../../common/constants'; import type { @@ -39,9 +41,13 @@ import type { import { Integration } from '../../../common/data_streams_stats/integration'; import { DatasetQualityError } from '../../../common/errors'; import type { IDataStreamsStatsClient } from './types'; +import type { ITelemetryClient } from '../telemetry'; export class DataStreamsStatsClient implements IDataStreamsStatsClient { - constructor(private readonly http: HttpStart) {} + constructor( + private readonly http: HttpStart, + private readonly telemetryClient?: ITelemetryClient + ) {} public async getDataStreamsTypesPrivileges( params: GetDataStreamsTypesPrivilegesQuery @@ -211,4 +217,40 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { return integrations.map(Integration.create); } + + public async updateFailureStore({ + dataStream, + failureStoreEnabled, + customRetentionPeriod, + }: { + dataStream: string; + failureStoreEnabled: boolean; + customRetentionPeriod?: string; + }): Promise { + const response = await this.http + .put( + `/internal/dataset_quality/data_streams/${dataStream}/update_failure_store`, + { + body: JSON.stringify({ + failureStoreEnabled, + customRetentionPeriod, + }), + } + ) + .catch((error) => { + throw new DatasetQualityError(`Failed to update failure store": ${error}`, error); + }); + + this.telemetryClient?.trackFailureStoreUpdated({ + data_stream_name: dataStream, + failure_store_enabled: failureStoreEnabled, + custom_retention_period: customRetentionPeriod, + }); + + return decodeOrThrow( + updateFailureStoreResponseRt, + (message: string) => + new DatasetQualityError(`Failed to decode update failure store response: ${message}"`) + )(response); + } } diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_service.ts b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_service.ts index 2ac93e0ab41ec..76c07a4f8948a 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_service.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/data_streams_stats_service.ts @@ -17,16 +17,19 @@ export class DataStreamsStatsService { public setup(): DataStreamsStatsServiceSetup {} - public start({ http }: DataStreamsStatsServiceStartDeps): DataStreamsStatsServiceStart { + public start({ + http, + telemetryClient, + }: DataStreamsStatsServiceStartDeps): DataStreamsStatsServiceStart { return { - getClient: () => this.getClient({ http }), + getClient: () => this.getClient({ http, telemetryClient }), }; } - private async getClient({ http }: DataStreamsStatsServiceStartDeps) { + private async getClient({ http, telemetryClient }: DataStreamsStatsServiceStartDeps) { if (!this.client) { const { DataStreamsStatsClient } = await import('./data_streams_stats_client'); - const client = new DataStreamsStatsClient(http); + const client = new DataStreamsStatsClient(http, telemetryClient); this.client = client; } diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts index 40b9e7477f1a1..7e7a6d16c12cf 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/services/data_streams_stats/types.ts @@ -6,7 +6,12 @@ */ import type { HttpStart } from '@kbn/core/public'; -import type { DataStreamDocsStat, NonAggregatableDatasets } from '../../../common/api_types'; +import type { UpdateFailureStoreParams } from '../../../common/data_stream_details'; +import type { + DataStreamDocsStat, + NonAggregatableDatasets, + UpdateFailureStoreResponse, +} from '../../../common/api_types'; import type { DataStreamStatServiceResponse, GetDataStreamsDegradedDocsStatsQuery, @@ -18,6 +23,7 @@ import type { GetNonAggregatableDataStreamsParams, } from '../../../common/data_streams_stats'; import type { Integration } from '../../../common/data_streams_stats/integration'; +import type { ITelemetryClient } from '../telemetry'; export type DataStreamsStatsServiceSetup = void; @@ -27,6 +33,7 @@ export interface DataStreamsStatsServiceStart { export interface DataStreamsStatsServiceStartDeps { http: HttpStart; + telemetryClient?: ITelemetryClient; } export interface IDataStreamsStatsClient { @@ -45,4 +52,5 @@ export interface IDataStreamsStatsClient { getNonAggregatableDatasets( params: GetNonAggregatableDataStreamsParams ): Promise; + updateFailureStore(params: UpdateFailureStoreParams): Promise; } diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts index 5cd07f165e834..4f5f9a9a7f217 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts @@ -65,3 +65,20 @@ export const fetchFailedStatsFailedNotifier = (toasts: IToasts, error: Error) => text: error.message, }); }; + +export const updateFailureStoreFailedNotifier = (toasts: IToasts, error: Error) => { + toasts.addDanger({ + title: i18n.translate('xpack.datasetQuality.updateFailureStoreFailed', { + defaultMessage: "We couldn't update the failure store settings.", + }), + text: error.message, + }); +}; + +export const updateFailureStoreSuccessNotifier = (toasts: IToasts) => { + toasts.addSuccess({ + title: i18n.translate('xpack.datasetQuality.updateFailureStoreSuccess', { + defaultMessage: 'Failure store settings saved', + }), + }); +}; diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts index 6e19feb3c9075..d851c8a6c9c3e 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts @@ -33,6 +33,8 @@ import { fetchFailedStatsFailedNotifier, fetchIntegrationsFailedNotifier, fetchTotalDocsFailedNotifier, + updateFailureStoreFailedNotifier, + updateFailureStoreSuccessNotifier, } from './notifications'; import type { DatasetQualityControllerContext, @@ -351,6 +353,31 @@ export const createPureDatasetQualityControllerStateMachine = ( }, }, }, + failureStoreUpdate: { + initial: 'idle', + states: { + idle: { + on: { + UPDATE_FAILURE_STORE: { + target: 'updating', + }, + }, + }, + updating: { + invoke: { + src: 'updateFailureStore', + onDone: { + target: '#DatasetQualityController.main.stats.datasets.fetching', + actions: ['notifyUpdateFailureStoreSuccess'], + }, + onError: { + target: 'idle', + actions: ['notifyUpdateFailureStoreFailed'], + }, + }, + }, + }, + }, }, }, }, @@ -587,6 +614,9 @@ export const createDatasetQualityControllerStateMachine = ({ fetchTotalDocsFailedNotifier(toasts, event.data, meta), notifyFetchFailedStatsFailed: (_context, event: DoneInvokeEvent) => fetchFailedStatsFailedNotifier(toasts, event.data), + notifyUpdateFailureStoreSuccess: () => updateFailureStoreSuccessNotifier(toasts), + notifyUpdateFailureStoreFailed: (_context, event: DoneInvokeEvent) => + updateFailureStoreFailedNotifier(toasts, event.data), }, services: { loadDatasetTypesPrivileges: () => { @@ -657,6 +687,20 @@ export const createDatasetQualityControllerStateMachine = ({ loadIntegrations: () => { return dataStreamStatsClient.getIntegrations(); }, + updateFailureStore: (_context, event) => { + if ( + 'dataStream' in event && + event.dataStream && + event.dataStream.hasFailureStore !== undefined + ) { + return dataStreamStatsClient.updateFailureStore({ + dataStream: event.dataStream.rawName, + failureStoreEnabled: event.dataStream.hasFailureStore, + customRetentionPeriod: event.dataStream.customRetentionPeriod, + }); + } + return Promise.resolve(); + }, }, }); diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts index 9df294b5b653d..1bea5daa2c2e4 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts @@ -184,6 +184,10 @@ export type DatasetQualityControllerEvent = type: 'UPDATE_TYPES'; types: DataStreamType[]; } + | { + type: 'UPDATE_FAILURE_STORE'; + dataStream: DataStreamStat; + } | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent diff --git a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts index 6cd1c0a4c79f9..5ed374079e4ff 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/server/routes/data_streams/routes.ts @@ -140,6 +140,11 @@ const statsRoute = createDatasetQualityServerRoute({ : ({} as Record), ]); + const clusterDefaultRetentionPeriod = isServerless + ? undefined + : await getDataStreamDefaultRetentionPeriod({ + esClient, + }); return { datasetUserPrivileges, dataStreamsStats: dataStreams.map((dataStream: DataStreamStat) => { @@ -147,6 +152,8 @@ const statsRoute = createDatasetQualityServerRoute({ dataStream.sizeBytes = dataStreamsStats[dataStream.name]?.sizeBytes; dataStream.totalDocs = dataStreamsStats[dataStream.name]?.totalDocs; dataStream.creationDate = dataStreamsCreationDate[dataStream.name]; + dataStream.defaultRetentionPeriod = + dataStream.defaultRetentionPeriod || clusterDefaultRetentionPeriod; return dataStream; }), diff --git a/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_table.ts b/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_table.ts index 8735044daaff4..34750e76ff50b 100644 --- a/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_table.ts +++ b/x-pack/solutions/observability/test/functional/apps/dataset_quality/dataset_quality_table.ts @@ -225,8 +225,9 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid }); it('changes link text on hover when failure store is not enabled', async () => { - const linkSelector = 'datasetQualitySetFailureStoreLink'; - const links = await testSubjects.findAll(linkSelector); + const links = await testSubjects.findAll( + PageObjects.datasetQuality.testSubjectSelectors.enableFailureStoreFromTableButton + ); expect(links.length).to.be.greaterThan(0); const link = links[links.length - 1]; @@ -241,6 +242,32 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const table = await PageObjects.datasetQuality.getDatasetsTable(); await table.moveMouseTo(); }); + + it('enables failure store through modal and removes link from table', async () => { + const { + editFailureStoreModal, + failureStoreModalSaveButton, + enableFailureStoreToggle, + enableFailureStoreFromTableButton, + } = PageObjects.datasetQuality.testSubjectSelectors; + + const originalLinks = await testSubjects.findAll(enableFailureStoreFromTableButton); + expect(originalLinks.length).to.be.greaterThan(0); + + const link = originalLinks[0]; + await link.click(); + + await testSubjects.existOrFail(editFailureStoreModal); + + const saveModalButton = await testSubjects.find(failureStoreModalSaveButton); + await testSubjects.click(enableFailureStoreToggle); + expect(await saveModalButton.isEnabled()).to.be(true); + await testSubjects.click(failureStoreModalSaveButton); + await testSubjects.missingOrFail(editFailureStoreModal); + + const updatedLinks = await testSubjects.findAll(enableFailureStoreFromTableButton); + expect(updatedLinks.length).to.be.lessThan(originalLinks.length); + }); }); }); } diff --git a/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts b/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts index 224214216735c..cd4205c23b5d9 100644 --- a/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts +++ b/x-pack/solutions/observability/test/functional/page_objects/dataset_quality.ts @@ -185,6 +185,7 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv enableFailureStoreToggle: 'enableFailureStoreToggle', failureStoreModalSaveButton: 'failureStoreModalSaveButton', editFailureStoreIcon: 'datasetQualityDetailsEditFailureStore', + enableFailureStoreFromTableButton: 'datasetQualitySetFailureStoreLink', }; return { diff --git a/x-pack/solutions/observability/test/serverless/functional/test_suites/dataset_quality/dataset_quality_table.ts b/x-pack/solutions/observability/test/serverless/functional/test_suites/dataset_quality/dataset_quality_table.ts index f66102089616f..4b66035eeefe5 100644 --- a/x-pack/solutions/observability/test/serverless/functional/test_suites/dataset_quality/dataset_quality_table.ts +++ b/x-pack/solutions/observability/test/serverless/functional/test_suites/dataset_quality/dataset_quality_table.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import originalExpect from 'expect'; +import { IndexTemplateName } from '@kbn/apm-synthtrace/src/lib/logs/custom_logsdb_index_templates'; import type { FtrProviderContext } from '../../ftr_provider_context'; import { datasetNames, @@ -14,6 +15,7 @@ import { getInitialTestLogs, getLogsForDataset, productionNamespace, + processors, } from './data'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -35,6 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { name: 'apache', version: '1.14.0', }; + const testSubjects = getService('testSubjects'); describe('Dataset quality table', function () { before(async () => { @@ -174,5 +177,76 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const rows = await PageObjects.datasetQuality.getDatasetTableRows(); expect(rows.length).to.eql(activeDatasets.length); }); + + describe('Failed docs', () => { + before(async () => { + await synthtrace.createCustomPipeline(processors, 'synth.no-fs@pipeline'); + await synthtrace.createComponentTemplate({ + name: 'synth.no-fs@custom', + dataStreamOptions: { + failure_store: { + enabled: false, + }, + }, + }); + await synthtrace.createIndexTemplate(IndexTemplateName.NoFailureStore); + + await synthtrace.index([getLogsForDataset({ to, count: 5, dataset: 'synth.no-fs' })]); + + await PageObjects.datasetQuality.navigateTo(); + await PageObjects.datasetQuality.waitUntilTableLoaded(); + }); + + after(async () => { + await synthtrace.clean(); + await synthtrace.deleteIndexTemplate(IndexTemplateName.NoFailureStore); + await synthtrace.deleteComponentTemplate('synth.no-fs@custom'); + await synthtrace.deleteCustomPipeline('synth.no-fs@pipeline'); + }); + it('changes link text on hover when failure store is not enabled', async () => { + const links = await testSubjects.findAll( + PageObjects.datasetQuality.testSubjectSelectors.enableFailureStoreFromTableButton + ); + expect(links.length).to.be.greaterThan(0); + const link = links[links.length - 1]; + + expect(await link.getVisibleText()).to.eql('N/A'); + + await link.moveMouseTo(); + + await retry.try(async () => { + expect(await link.getVisibleText()).to.eql('Set failure store'); + }); + + const table = await PageObjects.datasetQuality.getDatasetsTable(); + await table.moveMouseTo(); + }); + + it('enables failure store through modal and removes link from table', async () => { + const { + editFailureStoreModal, + failureStoreModalSaveButton, + enableFailureStoreToggle, + enableFailureStoreFromTableButton, + } = PageObjects.datasetQuality.testSubjectSelectors; + + const originalLinks = await testSubjects.findAll(enableFailureStoreFromTableButton); + expect(originalLinks.length).to.be.greaterThan(0); + + const link = originalLinks[0]; + await link.click(); + + await testSubjects.existOrFail(editFailureStoreModal); + + const saveModalButton = await testSubjects.find(failureStoreModalSaveButton); + await testSubjects.click(enableFailureStoreToggle); + expect(await saveModalButton.isEnabled()).to.be(true); + await testSubjects.click(failureStoreModalSaveButton); + await testSubjects.missingOrFail(editFailureStoreModal); + + const updatedLinks = await testSubjects.findAll(enableFailureStoreFromTableButton); + expect(updatedLinks.length).to.be.lessThan(originalLinks.length); + }); + }); }); }