diff --git a/x-pack/plugins/observability_solution/dataset_quality/README.md b/x-pack/plugins/observability_solution/dataset_quality/README.md index d000c5697b1d3..25f01ceb6fa60 100755 --- a/x-pack/plugins/observability_solution/dataset_quality/README.md +++ b/x-pack/plugins/observability_solution/dataset_quality/README.md @@ -47,7 +47,7 @@ Once the tests finish, the instances will be terminated. node x-pack/plugins/observability_solution/dataset_quality/scripts/api --server # run tests -node x-pack/plugins/observability_solution/dataset_quality/scripts/api --runner --grep-files=error_group_list +node x-pack/plugins/observability_solution/dataset_quality/scripts/api --runner --grep-files=data_stream_settings.spec.ts ``` diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts index ce84985a39405..a88b2d531b9bc 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts @@ -11,8 +11,6 @@ const userPrivilegesRt = rt.type({ canMonitor: rt.boolean, }); -export type DataStreamUserPrivileges = rt.TypeOf; - const datasetUserPrivilegesRt = rt.intersection([ userPrivilegesRt, rt.type({ @@ -44,14 +42,13 @@ export const dashboardRT = rt.type({ title: rt.string, }); +export type Dashboard = rt.TypeOf; + export const integrationDashboardsRT = rt.type({ dashboards: rt.array(dashboardRT), }); -export type IntegrationDashboards = rt.TypeOf; -export type Dashboard = rt.TypeOf; - -export const getIntegrationDashboardsResponseRt = rt.exact(integrationDashboardsRT); +export type IntegrationDashboardsResponse = rt.TypeOf; export const integrationIconRt = rt.intersection([ rt.type({ @@ -74,11 +71,10 @@ export const integrationRt = rt.intersection([ version: rt.string, icons: rt.array(integrationIconRt), datasets: rt.record(rt.string, rt.string), - dashboards: rt.array(dashboardRT), }), ]); -export type Integration = rt.TypeOf; +export type IntegrationType = rt.TypeOf; export const getIntegrationsResponseRt = rt.exact( rt.type({ @@ -86,6 +82,8 @@ export const getIntegrationsResponseRt = rt.exact( }) ); +export type IntegrationResponse = rt.TypeOf; + export const degradedDocsRt = rt.type({ dataset: rt.string, count: rt.number, @@ -117,6 +115,7 @@ export type DegradedFieldResponse = rt.TypeOf; diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_stream_details/types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_stream_details/types.ts new file mode 100644 index 0000000000000..74f0653396447 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_stream_details/types.ts @@ -0,0 +1,13 @@ +/* + * 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 { DataStreamType } from '../types'; + +export interface GetDataStreamIntegrationParams { + type: DataStreamType; + integrationName: string; +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/integration.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/integration.ts index ad8fecd91c7e0..8a3a9d4b5e8b0 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/integration.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/integration.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DashboardType, IntegrationType } from './types'; +import { IntegrationType } from '../api_types'; export class Integration { name: IntegrationType['name']; @@ -13,14 +13,12 @@ export class Integration { version: string; datasets: Record; icons?: IntegrationType['icons']; - dashboards?: DashboardType[]; private constructor(integration: Integration) { this.name = integration.name; this.title = integration.title || integration.name; this.version = integration.version || '1.0.0'; this.icons = integration.icons; - this.dashboards = integration.dashboards || []; this.datasets = integration.datasets || {}; } @@ -29,7 +27,6 @@ export class Integration { ...integration, title: integration.title || integration.name, version: integration.version || '1.0.0', - dashboards: integration.dashboards || [], datasets: integration.datasets || {}, }; diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts index 42fbeeb2cff52..9fe23f6ceef6f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts @@ -17,9 +17,6 @@ export type DataStreamStatServiceResponse = GetDataStreamsStatsResponse; export type GetIntegrationsParams = APIClientRequestParamsOf<`GET /internal/dataset_quality/integrations`>['params']; -export type GetIntegrationsResponse = APIReturnType<`GET /internal/dataset_quality/integrations`>; -export type IntegrationType = GetIntegrationsResponse['integrations'][0]; -export type IntegrationsResponse = IntegrationType[]; export type GetDataStreamsDegradedDocsStatsParams = APIClientRequestParamsOf<`GET /internal/dataset_quality/data_streams/degraded_docs`>['params']; @@ -69,9 +66,6 @@ export type GetNonAggregatableDataStreamsResponse = export type GetIntegrationDashboardsParams = APIClientRequestParamsOf<`GET /internal/dataset_quality/integrations/{integration}/dashboards`>['params']['path']; -export type GetIntegrationDashboardsResponse = - APIReturnType<`GET /internal/dataset_quality/integrations/{integration}/dashboards`>; -export type DashboardType = GetIntegrationDashboardsResponse['dashboards'][0]; export type { DataStreamStat } from './data_stream_stat'; export type { diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/types/common.ts b/x-pack/plugins/observability_solution/dataset_quality/common/types/common.ts index d7e5c4d78bbef..ca5c4632ec0d3 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/types/common.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/types/common.ts @@ -5,6 +5,9 @@ * 2.0. */ +import { DataStreamStatType } from '../data_streams_stats'; +import { Integration } from '../data_streams_stats/integration'; + export type SortDirection = 'asc' | 'desc'; export type Maybe = T | null | undefined; @@ -13,3 +16,12 @@ export interface Coordinate { x: number; y: Maybe; } + +export interface BasicDataStream { + type: string; + name: DataStreamStatType['name']; + rawName: string; + namespace: string; + title: string; + integration?: Integration; +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx index 1fb5134b0a92c..7411067a7317f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx @@ -26,6 +26,7 @@ import { Header } from './header'; import { IntegrationSummary } from './integration_summary'; import { FlyoutProps } from './types'; import { FlyoutSummary } from './flyout_summary/flyout_summary'; +import { BasicDataStream } from '../../../common/types'; // Allow for lazy loading // eslint-disable-next-line import/no-default-export @@ -39,8 +40,18 @@ export default function Flyout({ dataset, closeFlyout }: FlyoutProps) { timeRange, loadingState, flyoutLoading, + integration, } = useDatasetQualityFlyout(); + const titleAndLinkDetails: BasicDataStream = { + name: dataset.name, + rawName: dataset.rawName, + integration: integration?.integrationDetails, + type: dataset.type, + namespace: dataset.namespace, + title: integration?.integrationDetails?.datasets?.[dataset.name] ?? dataset.name, + }; + const { startTracking } = useDatasetDetailsTelemetry(); useEffect(() => { @@ -58,7 +69,10 @@ export default function Flyout({ dataset, closeFlyout }: FlyoutProps) { ) : ( <> -
+
- {dataStreamStat.integration && ( + {integration?.integrationDetails && ( <> )} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx index 60d88992c979e..5fc66f79b79ba 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx @@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFlyoutHeader, + EuiSkeletonTitle, EuiTitle, useEuiShadow, useEuiTheme, @@ -22,15 +23,21 @@ import { } from '../../../common/translations'; import { NavigationSource } from '../../services/telemetry'; import { useRedirectLink } from '../../hooks'; -import { FlyoutDataset } from '../../state_machines/dataset_quality_controller'; import { IntegrationIcon } from '../common'; +import { BasicDataStream } from '../../../common/types'; -export function Header({ dataStreamStat }: { dataStreamStat: FlyoutDataset }) { - const { integration, title } = dataStreamStat; +export function Header({ + titleAndLinkDetails, + loading, +}: { + titleAndLinkDetails: BasicDataStream; + loading: boolean; +}) { + const { integration, title } = titleAndLinkDetails; const euiShadow = useEuiShadow('s'); const { euiTheme } = useEuiTheme(); const redirectLinkProps = useRedirectLink({ - dataStreamStat, + dataStreamStat: titleAndLinkDetails, telemetry: { page: 'details', navigationSource: NavigationSource.Header, @@ -39,47 +46,55 @@ export function Header({ dataStreamStat }: { dataStreamStat: FlyoutDataset }) { return ( - - - - -

{title}

-
-
+ ) : ( + + + + +

{title}

+
+
+ +
+
+
+ + - -
-
-
- - - - {redirectLinkProps.isLogsExplorerAvailable - ? flyoutOpenInLogsExplorerText - : flyoutOpenInDiscoverText} - - - -
+ + {redirectLinkProps.isLogsExplorerAvailable + ? flyoutOpenInLogsExplorerText + : flyoutOpenInDiscoverText} + + + + + )}
); } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx index 9b0e45ce27e90..99afb7f269df5 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx @@ -18,9 +18,10 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { RouterLinkProps } from '@kbn/router-utils/src/get_router_link_props'; -import { Integration } from '../../../common/data_streams_stats/integration'; import { useDatasetQualityFlyout } from '../../hooks'; import { useFlyoutIntegrationActions } from '../../hooks/use_flyout_integration_actions'; +import { Integration } from '../../../common/data_streams_stats/integration'; +import { Dashboard } from '../../../common/api_types'; const integrationActionsText = i18n.translate('xpack.datasetQuality.flyoutIntegrationActionsText', { defaultMessage: 'Integration actions', @@ -40,15 +41,17 @@ const viewDashboardsText = i18n.translate('xpack.datasetQuality.flyoutViewDashbo export function IntegrationActionsMenu({ integration, + dashboards, dashboardsLoading, }: { integration: Integration; + dashboards: Dashboard[]; dashboardsLoading: boolean; }) { const { dataStreamStat, canUserAccessDashboards, canUserViewIntegrations } = useDatasetQualityFlyout(); + const { version, name: integrationName } = integration; const { type, name } = dataStreamStat!; - const { dashboards = [], version, name: integrationName } = integration; const { isOpen, handleCloseMenu, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_summary.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_summary.tsx index 1450e830eac1e..6ac94411098a6 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_summary.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_summary.tsx @@ -13,22 +13,29 @@ import { flyoutIntegrationNameText, flyoutIntegrationVersionText, } from '../../../common/translations'; -import { Integration } from '../../../common/data_streams_stats/integration'; import { IntegrationIcon } from '../common'; import { FieldsList } from './fields_list'; import { IntegrationActionsMenu } from './integration_actions_menu'; +import { Integration } from '../../../common/data_streams_stats/integration'; +import { Dashboard } from '../../../common/api_types'; export function IntegrationSummary({ integration, + dashboards, dashboardsLoading, }: { integration: Integration; + dashboards: Dashboard[]; dashboardsLoading: boolean; }) { const { name, version } = integration; const integrationActionsMenu = ( - + ); return ( { insightsTimeRange, breakdownField, isNonAggregatable, + integration, } = useSelector(service, (state) => state.context.flyout) ?? {}; const { timeRange } = useSelector(service, (state) => state.context.filters); @@ -30,12 +31,20 @@ export const useDatasetQualityFlyout = () => { const loadingState = useSelector(service, (state) => ({ dataStreamDetailsLoading: state.matches('flyout.initializing.dataStreamDetails.fetching'), dataStreamSettingsLoading: state.matches('flyout.initializing.dataStreamSettings.fetching'), - datasetIntegrationsLoading: state.matches('flyout.initializing.integrationDashboards.fetching'), + datasetIntegrationDashboardLoading: state.matches( + 'flyout.initializing.dataStreamSettings.initializeIntegrations.integrationDashboards.fetching' + ), + datasetIntegrationDone: state.matches( + 'flyout.initializing.dataStreamSettings.initializeIntegrations.integrationDetails.done' + ), })); const canUserAccessDashboards = useSelector( service, - (state) => !state.matches('flyout.initializing.integrationDashboards.unauthorized') + (state) => + !state.matches( + 'flyout.initializing.dataStreamSettings.initializeIntegrations.integrationDashboards.unauthorized' + ) ); const canUserViewIntegrations = useSelector( @@ -48,6 +57,7 @@ export const useDatasetQualityFlyout = () => { dataStreamSettings, dataStreamDetails, isNonAggregatable, + integration, fieldFormats, timeRange: insightsTimeRange ?? timeRange, breakdownField, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx index 518b8f84aedd8..b0f47716100b4 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx @@ -10,8 +10,8 @@ import { useMemo, useCallback } from 'react'; import useToggle from 'react-use/lib/useToggle'; import { MANAGEMENT_APP_LOCATOR } from '@kbn/deeplinks-management/constants'; import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; -import { DashboardType } from '../../common/data_streams_stats'; import { useKibanaContextForPlugin } from '../utils'; +import { Dashboard } from '../../common/api_types'; import { useDatasetDetailsTelemetry } from './use_telemetry'; export const useFlyoutIntegrationActions = () => { @@ -79,7 +79,7 @@ export const useFlyoutIntegrationActions = () => { ] ); const getDashboardLinkProps = useCallback( - (dashboard: DashboardType) => + (dashboard: Dashboard) => wrapLinkPropsForTelemetry( getRouterLinkProps({ href: dashboardLocator?.getRedirectUrl({ dashboardId: dashboard?.id } || ''), diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts index 5e2b39ba7a8b6..4a4c6772c412c 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts @@ -17,20 +17,20 @@ import { RouterLinkProps } from '@kbn/router-utils/src/get_router_link_props'; import { LocatorPublic } from '@kbn/share-plugin/common'; import { LocatorClient } from '@kbn/shared-ux-prompt-no-data-views-types'; import { useSelector } from '@xstate/react'; -import { DataStreamStat } from '../../common/data_streams_stats/data_stream_stat'; import { useDatasetQualityContext } from '../components/dataset_quality/context'; -import { FlyoutDataset, TimeRangeConfig } from '../state_machines/dataset_quality_controller'; +import { TimeRangeConfig } from '../state_machines/dataset_quality_controller'; import { useKibanaContextForPlugin } from '../utils'; +import { BasicDataStream } from '../../common/types'; import { useRedirectLinkTelemetry } from './use_telemetry'; -export const useRedirectLink = ({ +export const useRedirectLink = ({ dataStreamStat, query, timeRangeConfig, breakdownField, telemetry, }: { - dataStreamStat: DataStreamStat | FlyoutDataset; + dataStreamStat: T; query?: Query | AggregateQuery; timeRangeConfig?: TimeRangeConfig; breakdownField?: string; @@ -109,7 +109,7 @@ export const useRedirectLink = ({ ]); }; -const buildLogsExplorerConfig = ({ +const buildLogsExplorerConfig = ({ locator, dataStreamStat, query, @@ -118,7 +118,7 @@ const buildLogsExplorerConfig = ({ breakdownField, }: { locator: LocatorPublic; - dataStreamStat: DataStreamStat | FlyoutDataset; + dataStreamStat: T; query?: Query | AggregateQuery; from: string; to: string; @@ -158,7 +158,7 @@ const buildLogsExplorerConfig = ({ return { routerLinkProps: logsExplorerLinkProps, navigate: navigateToLogsExplorer }; }; -const buildDiscoverConfig = ({ +const buildDiscoverConfig = ({ locatorClient, dataStreamStat, query, @@ -167,7 +167,7 @@ const buildDiscoverConfig = ({ breakdownField, }: { locatorClient: LocatorClient; - dataStreamStat: DataStreamStat | FlyoutDataset; + dataStreamStat: T; query?: Query | AggregateQuery; from: string; to: string; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts index 52bdf3c53ea51..a5813b3190351 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts @@ -11,7 +11,10 @@ import { getDataStreamDegradedFieldsResponseRt, getDataStreamsDetailsResponseRt, getDataStreamsSettingsResponseRt, + getIntegrationsResponseRt, + IntegrationDashboardsResponse, integrationDashboardsRT, + IntegrationResponse, } from '../../../common/api_types'; import { DataStreamDetails, @@ -24,10 +27,11 @@ import { GetDataStreamSettingsResponse, GetDataStreamsStatsError, GetIntegrationDashboardsParams, - GetIntegrationDashboardsResponse, } from '../../../common/data_streams_stats'; import { IDataStreamDetailsClient } from './types'; import { GetDataStreamsDetailsError } from '../../../common/data_stream_details'; +import { Integration } from '../../../common/data_streams_stats/integration'; +import { GetDataStreamIntegrationParams } from '../../../common/data_stream_details/types'; export class DataStreamDetailsClient implements IDataStreamDetailsClient { constructor(private readonly http: HttpStart) {} @@ -107,7 +111,7 @@ export class DataStreamDetailsClient implements IDataStreamDetailsClient { public async getIntegrationDashboards({ integration }: GetIntegrationDashboardsParams) { const response = await this.http - .get( + .get( `/internal/dataset_quality/integrations/${integration}/dashboards` ) .catch((error) => { @@ -117,7 +121,7 @@ export class DataStreamDetailsClient implements IDataStreamDetailsClient { ); }); - const integrationDashboards = decodeOrThrow( + const { dashboards } = decodeOrThrow( integrationDashboardsRT, (message: string) => new GetDataStreamsStatsError( @@ -125,6 +129,32 @@ export class DataStreamDetailsClient implements IDataStreamDetailsClient { ) )(response); - return integrationDashboards; + return dashboards; + } + + public async getDataStreamIntegration( + params: GetDataStreamIntegrationParams + ): Promise { + const { type, integrationName } = params; + const response = await this.http + .get('/internal/dataset_quality/integrations', { + query: { type }, + }) + .catch((error) => { + throw new GetDataStreamsStatsError( + `Failed to fetch integrations: ${error}`, + error.body.statusCode + ); + }); + + const { integrations } = decodeOrThrow( + getIntegrationsResponseRt, + (message: string) => + new GetDataStreamsStatsError(`Failed to decode integrations response: ${message}`) + )(response); + + const integration = integrations.find((i) => i.name === integrationName); + + if (integration) return Integration.create(integration); } } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/types.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/types.ts index 708c1ba8a6c4c..40eaf499a3032 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/types.ts @@ -6,16 +6,18 @@ */ import { HttpStart } from '@kbn/core/public'; +import { Integration } from '../../../common/data_streams_stats/integration'; import { GetDataStreamSettingsParams, DataStreamSettings, GetDataStreamDetailsParams, DataStreamDetails, GetIntegrationDashboardsParams, - GetIntegrationDashboardsResponse, GetDataStreamDegradedFieldsParams, DegradedFieldResponse, } from '../../../common/data_streams_stats'; +import { GetDataStreamIntegrationParams } from '../../../common/data_stream_details/types'; +import { Dashboard } from '../../../common/api_types'; export type DataStreamDetailsServiceSetup = void; @@ -33,7 +35,8 @@ export interface IDataStreamDetailsClient { getDataStreamDegradedFields( params: GetDataStreamDegradedFieldsParams ): Promise; - getIntegrationDashboards( - params: GetIntegrationDashboardsParams - ): Promise; + getIntegrationDashboards(params: GetIntegrationDashboardsParams): Promise; + getDataStreamIntegration( + params: GetDataStreamIntegrationParams + ): Promise; } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts index 6ffa91e7151da..bbce86404e1dd 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts @@ -12,6 +12,7 @@ import { getDataStreamsStatsResponseRt, getIntegrationsResponseRt, getNonAggregatableDatasetsRt, + IntegrationResponse, } from '../../../common/api_types'; import { DEFAULT_DATASET_TYPE } from '../../../common/constants'; import { @@ -24,7 +25,6 @@ import { GetIntegrationsParams, GetNonAggregatableDataStreamsParams, GetNonAggregatableDataStreamsResponse, - IntegrationsResponse, } from '../../../common/data_streams_stats'; import { Integration } from '../../../common/data_streams_stats/integration'; import { IDataStreamsStatsClient } from './types'; @@ -113,9 +113,9 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { public async getIntegrations( params: GetIntegrationsParams['query'] = { type: DEFAULT_DATASET_TYPE } - ): Promise { + ): Promise { const response = await this.http - .get('/internal/dataset_quality/integrations', { + .get('/internal/dataset_quality/integrations', { query: params, }) .catch((error) => { diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts index 7e3ee958b4074..ba1f9077f45b0 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts @@ -14,8 +14,8 @@ import { GetIntegrationsParams, GetNonAggregatableDataStreamsParams, GetNonAggregatableDataStreamsResponse, - IntegrationsResponse, } from '../../../common/data_streams_stats'; +import { Integration } from '../../../common/data_streams_stats/integration'; export type DataStreamsStatsServiceSetup = void; @@ -32,7 +32,7 @@ export interface IDataStreamsStatsClient { getDataStreamsDegradedStats( params?: GetDataStreamsDegradedDocsStatsQuery ): Promise; - getIntegrations(params: GetIntegrationsParams['query']): Promise; + getIntegrations(params: GetIntegrationsParams['query']): Promise; getNonAggregatableDatasets( params: GetNonAggregatableDataStreamsParams ): Promise; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts index d2077f20ad9d2..f92db44eb516c 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts @@ -71,6 +71,22 @@ export const fetchIntegrationsFailedNotifier = (toasts: IToasts, error: Error) = }); }; +export const fetchDataStreamIntegrationFailedNotifier = ( + toasts: IToasts, + error: Error, + integrationName?: string +) => { + toasts.addDanger({ + title: i18n.translate('xpack.datasetQuality.flyout.fetchIntegrationsFailed', { + defaultMessage: "We couldn't get {integrationName} integration info.", + values: { + integrationName, + }, + }), + text: error.message, + }); +}; + export const noDatasetSelected = i18n.translate( 'xpack.datasetQuality.fetchDatasetDetailsFailed.noDatasetSelected', { diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts index 4c2b516fc4662..6cad5ae252c0f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts @@ -8,11 +8,10 @@ import { IToasts } from '@kbn/core/public'; import { getDateISORange } from '@kbn/timerange'; import { assign, createMachine, DoneInvokeEvent, InterpreterFrom } from 'xstate'; -import { DataStreamStat, DegradedFieldResponse } from '../../../../common/api_types'; +import { Dashboard, DataStreamStat, DegradedFieldResponse } from '../../../../common/api_types'; import { Integration } from '../../../../common/data_streams_stats/integration'; import { IDataStreamDetailsClient } from '../../../services/data_stream_details'; import { - DashboardType, DataStreamSettings, DataStreamDetails, GetDataStreamsStatsQuery, @@ -35,6 +34,7 @@ import { fetchIntegrationsFailedNotifier, noDatasetSelected, fetchNonAggregatableDatasetsFailedNotifier, + fetchDataStreamIntegrationFailedNotifier, } from './notifications'; import { DatasetQualityControllerContext, @@ -261,7 +261,7 @@ export const createPureDatasetQualityControllerStateMachine = ( invoke: { src: 'loadDataStreamSettings', onDone: { - target: 'done', + target: 'initializeIntegrations', actions: ['storeDataStreamSettings'], }, onError: { @@ -270,6 +270,62 @@ export const createPureDatasetQualityControllerStateMachine = ( }, }, }, + initializeIntegrations: { + type: 'parallel', + states: { + integrationDetails: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadDataStreamIntegration', + onDone: { + target: 'done', + actions: ['storeDataStreamIntegration'], + }, + onError: { + target: 'done', + actions: ['notifyFetchDatasetIntegrationsFailed'], + }, + }, + }, + done: { + type: 'final', + }, + }, + }, + integrationDashboards: { + initial: 'fetching', + states: { + fetching: { + invoke: { + src: 'loadIntegrationDashboards', + onDone: { + target: 'done', + actions: ['storeIntegrationDashboards'], + }, + onError: [ + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'done', + actions: ['notifyFetchIntegrationDashboardsFailed'], + }, + ], + }, + }, + done: { + type: 'final', + }, + unauthorized: { + type: 'final', + }, + }, + }, + }, + }, done: { type: 'final', }, @@ -304,36 +360,6 @@ export const createPureDatasetQualityControllerStateMachine = ( }, }, }, - integrationDashboards: { - initial: 'fetching', - states: { - fetching: { - invoke: { - src: 'loadIntegrationDashboards', - onDone: { - target: 'done', - actions: ['storeIntegrationDashboards'], - }, - onError: [ - { - target: 'unauthorized', - cond: 'checkIfActionForbidden', - }, - { - target: 'done', - actions: ['notifyFetchIntegrationDashboardsFailed'], - }, - ], - }, - }, - done: { - type: 'final', - }, - unauthorized: { - type: 'final', - }, - }, - }, dataStreamDegradedFields: { initial: 'fetching', states: { @@ -623,18 +649,28 @@ export const createPureDatasetQualityControllerStateMachine = ( integrations: [], }; }), - storeIntegrationDashboards: assign((context, event) => { - return 'data' in event && 'dashboards' in event.data + storeDataStreamIntegration: assign((context, event: DoneInvokeEvent) => { + return 'data' in event ? { flyout: { ...context.flyout, - dataset: { - ...context.flyout.dataset, - integration: { - ...context.flyout.dataset?.integration, - dashboards: event.data.dashboards as DashboardType[], - }, - } as FlyoutDataset, + integration: { + ...context.flyout.integration, + integrationDetails: event.data, + }, + }, + } + : {}; + }), + storeIntegrationDashboards: assign((context, event: DoneInvokeEvent) => { + return 'data' in event + ? { + flyout: { + ...context.flyout, + integration: { + ...context.flyout.integration, + dashboards: event.data, + }, }, } : {}; @@ -688,6 +724,10 @@ export const createDatasetQualityControllerStateMachine = ({ fetchIntegrationDashboardsFailedNotifier(toasts, event.data), notifyFetchIntegrationsFailed: (_context, event: DoneInvokeEvent) => fetchIntegrationsFailedNotifier(toasts, event.data), + notifyFetchDatasetIntegrationsFailed: (context, event: DoneInvokeEvent) => { + const integrationName = context.flyout.datasetSettings?.integration; + return fetchDataStreamIntegrationFailedNotifier(toasts, event.data, integrationName); + }, }, services: { loadDataStreamStats: (context) => @@ -757,6 +797,16 @@ export const createDatasetQualityControllerStateMachine = ({ }), }); }, + loadDataStreamIntegration: (context) => { + if (context.flyout.datasetSettings?.integration && context.flyout.dataset) { + const { type } = context.flyout.dataset; + return dataStreamDetailsClient.getDataStreamIntegration({ + type: type as DataStreamType, + integrationName: context.flyout.datasetSettings.integration, + }); + } + return Promise.resolve(); + }, loadDataStreamDetails: (context) => { if (!context.flyout.dataset || !context.flyout.insightsTimeRange) { fetchDatasetDetailsFailedNotifier(toasts, new Error(noDatasetSelected)); @@ -780,17 +830,13 @@ export const createDatasetQualityControllerStateMachine = ({ }); }, loadIntegrationDashboards: (context) => { - if (!context.flyout.dataset) { - fetchDatasetDetailsFailedNotifier(toasts, new Error(noDatasetSelected)); - - return Promise.resolve({}); + if (context.flyout.datasetSettings?.integration) { + return dataStreamDetailsClient.getIntegrationDashboards({ + integration: context.flyout.datasetSettings.integration, + }); } - const { integration } = context.flyout.dataset; - - return integration - ? dataStreamDetailsClient.getIntegrationDashboards({ integration: integration.name }) - : Promise.resolve({}); + return Promise.resolve(); }, loadDatasetIsNonAggregatable: async (context) => { if (!context.flyout.dataset || !context.flyout.insightsTimeRange) { diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts index c75c648020d61..1454c098f9783 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts @@ -8,17 +8,15 @@ import { DoneInvokeEvent } from 'xstate'; import { RefreshInterval, TimeRange } from '@kbn/data-plugin/common'; import { QualityIndicators, SortDirection } from '../../../../common/types'; -import { DatasetUserPrivileges } from '../../../../common/api_types'; +import { Dashboard, DatasetUserPrivileges } from '../../../../common/api_types'; import { Integration } from '../../../../common/data_streams_stats/integration'; import { DatasetTableSortField, DegradedFieldSortField } from '../../../hooks'; import { DegradedDocsStat } from '../../../../common/data_streams_stats/malformed_docs_stat'; import { - DashboardType, DataStreamDegradedDocsStatServiceResponse, DataStreamSettings, DataStreamDetails, DataStreamStatServiceResponse, - IntegrationsResponse, DataStreamStat, DataStreamStatType, GetNonAggregatableDataStreamsResponse, @@ -59,6 +57,11 @@ interface FiltersCriteria { query?: string; } +export interface DataStreamIntegrations { + integrationDetails?: Integration; + dashboards?: Dashboard[]; +} + export interface WithTableOptions { table: TableCriteria; } @@ -72,6 +75,7 @@ export interface WithFlyoutOptions { breakdownField?: string; degradedFields: DegradedFields; isNonAggregatable?: boolean; + integration?: DataStreamIntegrations; }; } @@ -146,6 +150,18 @@ export type DatasetQualityControllerTypeState = value: 'flyout.initializing.dataStreamSettings.fetching'; context: DefaultDatasetQualityStateContext; } + | { + value: 'flyout.initializing.dataStreamSettings.initializeIntegrations.integrationDashboards.fetching'; + context: DefaultDatasetQualityStateContext; + } + | { + value: 'flyout.initializing.dataStreamSettings.initializeIntegrations.integrationDashboards.unauthorized'; + context: DefaultDatasetQualityStateContext; + } + | { + value: 'flyout.initializing.dataStreamSettings.initializeIntegrations.integrationDetails.done'; + context: DefaultDatasetQualityStateContext; + } | { value: 'flyout.initializing.dataStreamDetails.fetching'; context: DefaultDatasetQualityStateContext; @@ -222,10 +238,10 @@ export type DatasetQualityControllerEvent = } | DoneInvokeEvent | DoneInvokeEvent - | DoneInvokeEvent + | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent - | DoneInvokeEvent + | DoneInvokeEvent | DoneInvokeEvent; diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_settings.test.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_settings.test.ts index dd2548d033c71..1a4ce17fbce3b 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_settings.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_settings.test.ts @@ -8,6 +8,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { getDataStreamSettings } from '.'; +import { IndicesGetDataStreamResponse } from '@elastic/elasticsearch/lib/api/types'; const accessLogsDataStream = 'logs-nginx.access-default'; const errorLogsDataStream = 'logs-nginx.error-default'; const dateStr1 = '1702998651925'; // .ds-logs-nginx.access-default-2023.12.19-000001 @@ -38,12 +39,13 @@ describe('getDataStreamSettings', () => { esClientMock.indices.getSettings.mockReturnValue( Promise.resolve(MOCK_NGINX_ERROR_INDEX_SETTINGS) ); + esClientMock.indices.getDataStream.mockReturnValue(Promise.resolve(MOCK_NGINX_DATA_STREAM)); const dataStreamSettings = await getDataStreamSettings({ esClient: esClientMock, dataStream: errorLogsDataStream, }); - expect(dataStreamSettings).toEqual({ createdOn: Number(dateStr3) }); + expect(dataStreamSettings).toEqual({ createdOn: Number(dateStr3), integration: 'apache' }); }); it('returns the earliest creation date of a data stream with multiple backing indices', async () => { @@ -51,12 +53,13 @@ describe('getDataStreamSettings', () => { esClientMock.indices.getSettings.mockReturnValue( Promise.resolve(MOCK_NGINX_ACCESS_INDEX_SETTINGS) ); + esClientMock.indices.getDataStream.mockReturnValue(Promise.resolve(MOCK_NGINX_DATA_STREAM)); const dataStreamSettings = await getDataStreamSettings({ esClient: esClientMock, dataStream: accessLogsDataStream, }); - expect(dataStreamSettings).toEqual({ createdOn: Number(dateStr1) }); + expect(dataStreamSettings).toEqual({ createdOn: Number(dateStr1), integration: 'apache' }); }); }); @@ -225,3 +228,40 @@ const MOCK_INDEX_ERROR = { }, status: 404, }; + +const MOCK_NGINX_DATA_STREAM: IndicesGetDataStreamResponse = { + data_streams: [ + { + name: 'logs-apache.access-production', + timestamp_field: { + name: '@timestamp', + }, + indices: [ + { + index_name: '.ds-logs-apache.access-production-2024.07.03-000001', + index_uuid: 'xrGjQ-GFSeqAlUbyS0Fx-A', + prefer_ilm: true, + ilm_policy: 'logs', + managed_by: 'Index Lifecycle Management', + }, + ], + generation: 1, + _meta: { + package: { + name: 'apache', + }, + managed: true, + managed_by: 'fleet', + }, + status: 'YELLOW', + template: 'logs-apache.access', + ilm_policy: 'logs', + next_generation_managed_by: 'Index Lifecycle Management', + prefer_ilm: true, + hidden: false, + system: false, + allow_custom_routing: false, + replicated: false, + }, + ], +}; diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts index 52804c5d9369c..d89bb83867d10 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts @@ -32,8 +32,13 @@ export async function getDataStreamSettings({ const createdOn = await getDataStreamCreatedOn(esClient, dataStream); + // Getting the 1st item from the data streams endpoint as we will be passing the exact DS name + const [dataStreamInfo] = await dataStreamService.getMatchingDataStreams(esClient, dataStream); + const integration = dataStreamInfo?._meta?.package?.name; + return { createdOn, + integration, }; } diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts index 41f89fa7ac156..8c7878f244862 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts @@ -24,7 +24,7 @@ export async function getDataStreamsStats({ }; } - const matchingDataStreamsStats = await dataStreamService.getStreamsStats(esClient, dataStreams); + const matchingDataStreamsStats = dataStreamService.getStreamsStats(esClient, dataStreams); const indicesDocsCount = sizeStatsAvailable ? indexStatsService.getIndicesDocCounts(esClient, dataStreams) diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/get_integrations.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/get_integrations.ts index 70aa86f08efc3..208ed7e70ab32 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/get_integrations.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/get_integrations.ts @@ -11,13 +11,13 @@ import { PackageNotFoundError } from '@kbn/fleet-plugin/server/errors'; import { PackageListItem, RegistryDataStream } from '@kbn/fleet-plugin/common'; import { DEFAULT_DATASET_TYPE } from '../../../common/constants'; import { DataStreamType } from '../../../common/types'; -import { Integration } from '../../../common/api_types'; +import { IntegrationType } from '../../../common/api_types'; export async function getIntegrations(options: { packageClient: PackageClient; logger: Logger; type?: DataStreamType; -}): Promise { +}): Promise { const { packageClient, logger, type = DEFAULT_DATASET_TYPE } = options; const packages = await packageClient.getPackages(); diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/routes.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/routes.ts index dfc0b8a2824e3..1a21d7e1a14c4 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/routes.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/integrations/routes.ts @@ -6,7 +6,7 @@ */ import * as t from 'io-ts'; -import { Integration, IntegrationDashboards } from '../../../common/api_types'; +import { IntegrationType, IntegrationDashboardsResponse } from '../../../common/api_types'; import { typeRt } from '../../types/default_api_types'; import { createDatasetQualityServerRoute } from '../create_datasets_quality_server_route'; import { getIntegrations } from './get_integrations'; @@ -21,7 +21,7 @@ const integrationsRoute = createDatasetQualityServerRoute({ tags: [], }, async handler(resources): Promise<{ - integrations: Integration[]; + integrations: IntegrationType[]; }> { const { params, plugins, logger } = resources; @@ -44,7 +44,7 @@ const integrationDashboardsRoute = createDatasetQualityServerRoute({ options: { tags: [], }, - async handler(resources): Promise { + async handler(resources): Promise { const { context, params, plugins } = resources; const { integration } = params.path; const { savedObjects } = await context.core; diff --git a/x-pack/test/dataset_quality_api_integration/common/config.ts b/x-pack/test/dataset_quality_api_integration/common/config.ts index e5a45b72d7f06..e297d8eaf0354 100644 --- a/x-pack/test/dataset_quality_api_integration/common/config.ts +++ b/x-pack/test/dataset_quality_api_integration/common/config.ts @@ -22,6 +22,7 @@ import { import { createDatasetQualityApiClient } from './dataset_quality_api_supertest'; import { RegistryProvider } from './registry'; import { DatasetQualityFtrConfigName } from '../configs'; +import { PackageService } from './package_service'; export interface DatasetQualityFtrConfig { name: DatasetQualityFtrConfigName; @@ -70,6 +71,7 @@ export interface CreateTest { context: InheritedFtrProviderContext ) => Promise; datasetQualityApiClient: (context: InheritedFtrProviderContext) => DatasetQualityApiClient; + packageService: ({ getService }: FtrProviderContext) => ReturnType; }; junit: { reportName: string }; esTestCluster: any; @@ -98,6 +100,7 @@ export function createTestConfig( servicesRequiredForTestAnalysis: ['datasetQualityFtrConfig', 'registry'], services: { ...services, + packageService: PackageService, datasetQualityFtrConfig: () => config, registry: RegistryProvider, logSynthtraceEsClient: (context: InheritedFtrProviderContext) => diff --git a/x-pack/test/dataset_quality_api_integration/common/ftr_provider_context.ts b/x-pack/test/dataset_quality_api_integration/common/ftr_provider_context.ts index 69de63c48402c..f48da8f57c18f 100644 --- a/x-pack/test/dataset_quality_api_integration/common/ftr_provider_context.ts +++ b/x-pack/test/dataset_quality_api_integration/common/ftr_provider_context.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; import { FtrProviderContext as InheritedFtrProviderContext } from '../../api_integration/ftr_provider_context'; import { DatasetQualityServices } from './config'; @@ -18,3 +18,4 @@ export type InheritedServices = InheritedFtrProviderContext extends GenericFtrPr export type { InheritedFtrProviderContext }; export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/x-pack/test/dataset_quality_api_integration/common/package_service.ts b/x-pack/test/dataset_quality_api_integration/common/package_service.ts new file mode 100644 index 0000000000000..0449fd9f921dd --- /dev/null +++ b/x-pack/test/dataset_quality_api_integration/common/package_service.ts @@ -0,0 +1,30 @@ +/* + * 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 { FtrProviderContext } from './ftr_provider_context'; + +interface IntegrationPackage { + name: string; + version: string; +} + +export function PackageService({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + function uninstallPackage({ name, version }: IntegrationPackage) { + return supertest.delete(`/api/fleet/epm/packages/${name}/${version}`).set('kbn-xsrf', 'xxxx'); + } + + function installPackage({ name, version }: IntegrationPackage) { + return supertest + .post(`/api/fleet/epm/packages/${name}/${version}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + } + + return { uninstallPackage, installPackage }; +} diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_settings.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_settings.spec.ts index e6f75d53b6c77..e5b38d2463cb0 100644 --- a/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_settings.spec.ts +++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_settings.spec.ts @@ -21,13 +21,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { const synthtrace = getService('logSynthtraceEsClient'); const esClient = getService('es'); const datasetQualityApiClient = getService('datasetQualityApiClient'); + const pkgService = getService('packageService'); const start = '2023-12-11T18:00:00.000Z'; const end = '2023-12-11T18:01:00.000Z'; const type = 'logs'; - const dataset = 'nginx.access'; + const dataset = 'synth.1'; + const integrationDataset = 'apache.access'; const namespace = 'default'; const serviceName = 'my-service'; const hostName = 'synth-host'; + const pkg = { + name: 'apache', + version: '1.14.0', + }; async function callApiAs(user: DatasetQualityApiClientKey, dataStream: string) { return await datasetQualityApiClient[user]({ @@ -43,6 +49,27 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('DataStream Settings', { config: 'basic' }, () => { describe('gets the data stream settings', () => { before(async () => { + // Install Integration and ingest logs for it + await pkgService.installPackage(pkg); + await synthtrace.index([ + timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => + log + .create() + .message('This is a log message') + .timestamp(timestamp) + .dataset(integrationDataset) + .namespace(namespace) + .defaults({ + 'log.file.path': '/my-service.log', + 'service.name': serviceName, + 'host.name': hostName, + }) + ), + ]); + // Ingest basic logs await synthtrace.index([ timerange(start, end) .interval('1m') @@ -98,8 +125,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(resp.body.createdOn).to.be(Number(dataStreamSettings?.index?.creation_date)); }); + it('returns "createdOn" and "integration" correctly when available', async () => { + const dataStreamSettings = await getDataStreamSettingsOfEarliestIndex( + esClient, + `${type}-${integrationDataset}-${namespace}` + ); + const resp = await callApiAs( + 'datasetQualityLogsUser', + `${type}-${integrationDataset}-${namespace}` + ); + expect(resp.body.createdOn).to.be(Number(dataStreamSettings?.index?.creation_date)); + expect(resp.body.integration).to.be('apache'); + }); + after(async () => { await synthtrace.clean(); + await pkgService.uninstallPackage(pkg); }); }); }); diff --git a/x-pack/test/functional/page_objects/dataset_quality.ts b/x-pack/test/functional/page_objects/dataset_quality.ts index dc580d2951c6a..7cb633a8113b6 100644 --- a/x-pack/test/functional/page_objects/dataset_quality.ts +++ b/x-pack/test/functional/page_objects/dataset_quality.ts @@ -96,6 +96,7 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv datasetQualityFlyoutFieldValue: 'datasetQualityFlyoutFieldValue', datasetQualityFlyoutFieldsListIntegrationDetails: 'datasetQualityFlyoutFieldsList-integration_details', + datasetQualityFlyoutIntegrationLoading: 'datasetQualityFlyoutIntegrationLoading', datasetQualityFlyoutIntegrationActionsButton: 'datasetQualityFlyoutIntegrationActionsButton', datasetQualityFlyoutIntegrationAction: (action: string) => `datasetQualityFlyoutIntegrationAction${action}`, @@ -163,6 +164,13 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv await find.waitForDeletedByCssSelector('.euiFlyoutBody .euiBasicTable-loading', 20 * 1000); }, + async waitUntilIntegrationsInFlyoutLoaded() { + await find.waitForDeletedByCssSelector( + '.euiSkeletonTitle .datasetQualityFlyoutIntegrationLoading', + 10 * 1000 + ); + }, + async waitUntilSummaryPanelLoaded(isStateful: boolean = true) { await testSubjects.missingOrFail(`datasetQuality-${texts.activeDatasets}-loading`); if (isStateful) { @@ -321,6 +329,8 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv if (isCollapsed) { await datasetExpandButton.click(); } + + await this.waitUntilIntegrationsInFlyoutLoaded(); }, async closeFlyout() {