diff --git a/x-pack/platform/plugins/shared/observability_navigation/server/routes/internal/setup/route.ts b/x-pack/platform/plugins/shared/observability_navigation/server/routes/internal/setup/route.ts index 5fc8eab5b0e4d..4d856e2bbee85 100644 --- a/x-pack/platform/plugins/shared/observability_navigation/server/routes/internal/setup/route.ts +++ b/x-pack/platform/plugins/shared/observability_navigation/server/routes/internal/setup/route.ts @@ -33,6 +33,23 @@ const infraSideNavRoute = createServerRoute({ const [fleetStart, core] = await Promise.all([plugins.fleet?.start(), context.core]); const packageClient = fleetStart?.packageService.asScoped(request); + const otelData = await core.elasticsearch.client.asCurrentUser.search({ + index: 'metrics-*.otel-*', + ignore_unavailable: true, + allow_no_indices: true, + track_total_hits: true, + terminate_after: 1, + size: 0, + query: { + bool: { + filter: [{ term: { ['data_stream.dataset']: 'k8sclusterreceiver.otel' } }], + }, + }, + }); + + const totalHits = otelData?.hits?.total; + const hasOtelData = typeof totalHits === 'number' ? totalHits !== 0 : totalHits?.value !== 0; + const [installedPackage, ...navigationOverrides] = await Promise.all([ packageClient?.getInstallation(KUBERNETES), core.savedObjects.client.get( @@ -45,97 +62,85 @@ const infraSideNavRoute = createServerRoute({ ), ]); - if (!installedPackage && !navigationOverrides) { + const otelPackageInstalled = installedPackage ? [installedPackage] : []; + + if (hasOtelData && !installedPackage) { + // We will just install the otel package if it is not installed for now + await packageClient?.ensureInstalledPackage({ pkgName: 'kubernetes_otel' }); + const installedOtelKubernetes = await packageClient?.getInstallation('kubernetes_otel'); + if (installedOtelKubernetes) otelPackageInstalled.push(installedOtelKubernetes); + } + + if ((!installedPackage || otelPackageInstalled.length === 0) && !navigationOverrides) { return []; } - // Mock data simulating the installed package's items returned by installedPackage.installed_kibana - const mockInstalledPackage = installedPackage - ? [ - { - id: 'kubernetes-0a672d50-bcb1-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.cronjob', - sideNavTitle: 'Cron jobs', - sideNavOrder: 900, - type: 'dashboard', - }, - { - id: 'kubernetes-21694370-bcb2-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.statefulset', - sideNavTitle: 'Stateful sets', - sideNavOrder: 600, - type: 'dashboard', - }, - { - id: 'kubernetes-3912d9a0-bcb2-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.volume', - sideNavTitle: 'Volumes', - sideNavOrder: 500, - type: 'dashboard', - }, - { - id: 'kubernetes-3d4d9290-bcb1-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.pod', - sideNavTitle: 'Pods', - sideNavOrder: 300, - type: 'dashboard', - }, + // It will come from the entities definiition later + // For now we will just use the entities defined in the semconv + const k8sEntitiesSemConv = [ + { + id: 'entity.k8s.cluster', // -> Use to map to entityType? + type: 'entity', + stability: 'development', + name: 'k8s.cluster', + brief: 'A Kubernetes Cluster.', + attributes: [{ ref: 'k8s.cluster.name' }, { ref: 'k8s.cluster.uid' }], + }, + { + id: 'entity.k8s.node', + type: 'entity', + stability: 'development', + name: 'k8s.node', + brief: 'A Kubernetes Node object.', + attributes: [ + { ref: 'k8s.node.name' }, + { ref: 'k8s.node.uid' }, { - id: 'kubernetes-5be46210-bcb1-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.deployment', - sideNavTitle: 'Deployments', - sideNavOrder: 400, - type: 'dashboard', + ref: 'k8s.node.label', + requirement_level: 'opt_in', }, { - id: 'kubernetes-85879010-bcb1-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.daemonset', - sideNavTitle: 'Daemon sets', - sideNavOrder: 700, - type: 'dashboard', + ref: 'k8s.node.annotation', + requirement_level: 'opt_in', }, - { - id: 'kubernetes-9bf990a0-bcb1-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.job', - sideNavTitle: 'Jobs', - sideNavOrder: 800, - type: 'dashboard', - }, - { - id: 'kubernetes-b945b7b0-bcb1-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.node', - sideNavTitle: 'Nodes', - sideNavOrder: 200, - type: 'dashboard', - }, - { - id: 'kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c', - sideNavTitle: 'Overview', - sideNavOrder: 100, - type: 'dashboard', - }, - { - id: 'kubernetes-ff1b3850-bcb1-11ec-b64f-7dd6e8e82013', - entityType: 'k8s.service', - sideNavTitle: 'Services', - sideNavOrder: 800, - type: 'dashboard', - }, - ] - : []; + ], + }, + ]; + const otelMenuItems = k8sEntitiesSemConv.map((entity, index) => ({ + // id: `kubernetes_otel-${entity.id}`, + id: `kubernetes_otel-cluster-overview`, + entityType: entity.id, + sideNavTitle: entity.brief, + sideNavOrder: (index || 1) * 100, + type: 'dashboard', + })); + + // Mock data simulating the installed package's items returned by installedPackage.installed_kibana + const mockInstalledPackage = + installedPackage && otelPackageInstalled.length > 0 && hasOtelData + ? [ + { + id: 'kubernetes_otel-cluster-overview', + entityType: 'k8s.overview', + sideNavTitle: 'Overview (Otel)', + sideNavOrder: 100, + type: 'dashboard', + }, + ] + : []; - const integrationSubItems = - mockInstalledPackage + const otelNavigationItemsSorted = + [...otelMenuItems, ...mockInstalledPackage] .filter((p) => !!p.sideNavTitle) .sort((a, b) => (a.sideNavOrder ?? 0) - (b.sideNavOrder ?? 0)) ?? []; const integrationNavigation = - integrationSubItems.length > 0 + otelNavigationItemsSorted.length > 0 ? [ { id: `${KUBERNETES.toLowerCase().replace(/[\.\s]/g, '-')}`, title: KUBERNETES, - subItems: integrationSubItems.map((item) => { + subItems: otelNavigationItemsSorted.map((item) => { return { id: `${item.sideNavTitle.toLowerCase().replace(/[\.\s]/g, '-')}`, title: item.sideNavTitle, diff --git a/x-pack/platform/plugins/shared/observability_navigation/server/types.ts b/x-pack/platform/plugins/shared/observability_navigation/server/types.ts index 0347f458047e9..7fcc135268364 100644 --- a/x-pack/platform/plugins/shared/observability_navigation/server/types.ts +++ b/x-pack/platform/plugins/shared/observability_navigation/server/types.ts @@ -16,8 +16,10 @@ export interface ObservabilityNavigationServer { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityNavigationPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ObservabilityNavigationPluginStart {} + +export interface ObservabilityNavigationPluginStart { + core: CoreStart; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityNavigationPluginSetupDependencies {} diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/components/add_kubernetes_data/add_kubernetes_data.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/components/add_kubernetes_data/add_kubernetes_data.tsx new file mode 100644 index 0000000000000..0c0afc440a61d --- /dev/null +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/components/add_kubernetes_data/add_kubernetes_data.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import type { ObservabilityOnboardingLocatorParams } from '@kbn/deeplinks-observability'; +import { OBSERVABILITY_ONBOARDING_LOCATOR } from '@kbn/deeplinks-observability'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { SharePublicStart } from '@kbn/share-plugin/public/plugin'; +import { i18n } from '@kbn/i18n'; + +const ADD_DATA_KUBERNETES_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { + defaultMessage: 'Add Kubernetes data', +}); + +export const AddKubernetesDataLink = () => { + const { share } = useKibana<{ share: SharePublicStart }>().services; + const onboardingLocator = share?.url.locators.get( + OBSERVABILITY_ONBOARDING_LOCATOR + ); + + return ( + + {ADD_DATA_KUBERNETES_LABEL} + + ); +}; diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/components/dashboard/render_dashboard.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/components/dashboard/render_dashboard.tsx index 27cfe0985bf3d..4218e7cdfc32b 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/components/dashboard/render_dashboard.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/components/dashboard/render_dashboard.tsx @@ -12,8 +12,12 @@ import type { DashboardApi, DashboardCreationOptions } from '@kbn/dashboard-plug import { KUBERNETES_DASHBOARD_LOCATOR_ID } from '@kbn/observability-shared-plugin/common'; import type { SerializableRecord } from '@kbn/utility-types'; import type { DashboardState } from '@kbn/dashboard-plugin/common'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public'; import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; import { useDatePickerContext } from '../../hooks/use_date_picker'; +import { AddKubernetesDataLink } from '../add_kubernetes_data/add_kubernetes_data'; +import { useFetchDashboardById } from '../../hooks/use_fetch_dashboard_by_id'; export const RenderDashboard = ({ dashboardId }: { dashboardId: string }) => { const { @@ -61,6 +65,15 @@ export const RenderDashboard = ({ dashboardId }: { dashboardId: string }) => { dashboard.setQuery({ query: '', language: 'kuery' }); }, [dashboard, dashboardId, from, to]); + const { data: dashboardData, status } = useFetchDashboardById(dashboardId); + + if (!dashboardData && status === FETCH_STATUS.LOADING) { + return ; + } + if (!dashboardData && status !== FETCH_STATUS.LOADING && dashboardId.startsWith('kubernetes')) { + return ; + } + return ( { +export const PageContent = ({ dashboardId }: { dashboardId: string }) => { const { data, status } = useTimeRangeMetadataContext(); - const [currentDashboardId, setCurrentDashboardId] = useState( - hasMultipleDashboards ? OverviewDashboardsPerSchema.semconv : dashboardId - ); - - const onChange = (e: { target: { checked: React.SetStateAction } }) => { - setCurrentDashboardId( - e.target.checked - ? OverviewDashboardsPerSchema.semconv - : dashboardId ?? OverviewDashboardsPerSchema.ecs - ); - }; - - const shouldRenderMultipleDashboardsToggle = useMemo( - () => - Object.keys(OverviewDashboardsPerSchema).every((key) => - data?.schemas.includes(key as keyof typeof OverviewDashboardsPerSchema) - ), - [data?.schemas] - ); if (status === 'loading') { return ; @@ -52,31 +21,5 @@ export const PageContent = ({ return null; } - if (shouldRenderMultipleDashboardsToggle) { - return ( - <> - - {i18n.translate('xpack.infra.pageContent.span.otelfocusedDashboardLabel', { - defaultMessage: 'OTel-focused dashboard', - })} - - } - > - - - - - ); - } - return ; }; diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/index.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/index.tsx index cf4eec31a9fc5..c96516d7afd89 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/index.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/dashboard/index.tsx @@ -77,7 +77,7 @@ export const Dashboard = () => { data-test-subj="infraKubernetesPage" > - +