mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ECO][Inventory v2] Hosts entity summary endpoint changes (#203617)
## Summary Closes #202300 This PR changes the entity client function to v2 (`searchEntities`) in `getLatestEntity`. After the change to use `v2.searchEntities` the parameters are also updated to include the time range (`start` and `end` are required) ## Testing ~- We can create some definitions manually- in the Kibana DEV tools: ~ - Not needed after we merged the V2 PR - In a local environment enable the entities feature flag ( it should be a clean env as the entities should not be enabled before ): <img width="1911" alt="image" src="https://github.com/user-attachments/assets/75d6f77d-5039-41ca-80ca-34c3bf99844e" /> - Some hosts and containers are required - oblt cluster/metricbeat or - Create hosts using synthtrace: ``` node scripts/synthtrace infra_hosts_with_apm_hosts --scenarioOpts.numInstances=20 ``` - Create containers using synthtrace: ``` node scripts/synthtrace infra_docker_containers.ts ``` - In the UI - Open asset details view for hosts and containers and check the summary endpoint response: ⚠️ Updated:  - If the entities FF is disabled (default: no `logs` should be part of the `sourceDataStreams`):  --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Sergi Romeu <sergi.romeu@elastic.co>
This commit is contained in:
parent
a54045841c
commit
13582aa458
11 changed files with 101 additions and 50 deletions
|
@ -12,16 +12,21 @@ import {
|
|||
} from '@kbn/observability-shared-plugin/common';
|
||||
import { useFetcher } from '../../../hooks/use_fetcher';
|
||||
|
||||
const EntityTypeSchema = z.union([
|
||||
const EntityFilterTypeSchema = z.union([
|
||||
z.literal(BUILT_IN_ENTITY_TYPES.HOST),
|
||||
z.literal(BUILT_IN_ENTITY_TYPES.CONTAINER),
|
||||
]);
|
||||
const EntityTypeSchema = z.union([
|
||||
z.literal(BUILT_IN_ENTITY_TYPES.HOST_V2),
|
||||
z.literal(BUILT_IN_ENTITY_TYPES.CONTAINER_V2),
|
||||
]);
|
||||
const EntityDataStreamSchema = z.union([
|
||||
z.literal(EntityDataStreamType.METRICS),
|
||||
z.literal(EntityDataStreamType.LOGS),
|
||||
]);
|
||||
|
||||
const EntitySummarySchema = z.object({
|
||||
entityFilterType: EntityFilterTypeSchema,
|
||||
entityType: EntityTypeSchema,
|
||||
entityId: z.string(),
|
||||
sourceDataStreams: z.array(EntityDataStreamSchema),
|
||||
|
@ -32,9 +37,13 @@ export type EntitySummary = z.infer<typeof EntitySummarySchema>;
|
|||
export function useEntitySummary({
|
||||
entityType,
|
||||
entityId,
|
||||
from,
|
||||
to,
|
||||
}: {
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
from: string;
|
||||
to: string;
|
||||
}) {
|
||||
const { data, status } = useFetcher(
|
||||
async (callApi) => {
|
||||
|
@ -44,11 +53,15 @@ export function useEntitySummary({
|
|||
|
||||
const response = await callApi(`/api/infra/entities/${entityType}/${entityId}/summary`, {
|
||||
method: 'GET',
|
||||
query: {
|
||||
to,
|
||||
from,
|
||||
},
|
||||
});
|
||||
|
||||
return EntitySummarySchema.parse(response);
|
||||
},
|
||||
[entityType, entityId]
|
||||
[entityType, entityId, to, from]
|
||||
);
|
||||
|
||||
return { dataStreams: data?.sourceDataStreams ?? [], status };
|
||||
|
|
|
@ -28,6 +28,7 @@ import { useAssetDetailsRenderPropsContext } from './use_asset_details_render_pr
|
|||
import { useTabSwitcherContext } from './use_tab_switcher';
|
||||
import { useEntitySummary } from './use_entity_summary';
|
||||
import { isMetricsSignal } from '../utils/get_data_stream_types';
|
||||
import { useDatePickerContext } from './use_date_picker';
|
||||
|
||||
type TabItem = NonNullable<Pick<EuiPageHeaderProps, 'tabs'>['tabs']>[number];
|
||||
|
||||
|
@ -144,9 +145,12 @@ const useFeatureFlagTabs = () => {
|
|||
|
||||
const useMetricsTabs = () => {
|
||||
const { asset } = useAssetDetailsRenderPropsContext();
|
||||
const { dateRange } = useDatePickerContext();
|
||||
const { dataStreams } = useEntitySummary({
|
||||
entityType: asset.type,
|
||||
entityId: asset.id,
|
||||
from: new Date(dateRange.from).toISOString(),
|
||||
to: new Date(dateRange.to).toISOString(),
|
||||
});
|
||||
|
||||
const isMetrics = isMetricsSignal(dataStreams);
|
||||
|
|
|
@ -31,15 +31,19 @@ import type { AddMetricsCalloutKey } from '../../add_metrics_callout/constants';
|
|||
import { AddMetricsCallout } from '../../add_metrics_callout';
|
||||
import { useEntitySummary } from '../../hooks/use_entity_summary';
|
||||
import { isMetricsSignal } from '../../utils/get_data_stream_types';
|
||||
import { useDatePickerContext } from '../../hooks/use_date_picker';
|
||||
|
||||
export const MetricsTemplate = React.forwardRef<HTMLDivElement, { children: React.ReactNode }>(
|
||||
({ children }, ref) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { dateRange } = useDatePickerContext();
|
||||
const { asset, renderMode } = useAssetDetailsRenderPropsContext();
|
||||
const { scrollTo, setScrollTo } = useTabSwitcherContext();
|
||||
const { dataStreams, status: dataStreamsStatus } = useEntitySummary({
|
||||
entityType: asset.type,
|
||||
entityId: asset.id,
|
||||
from: new Date(dateRange.from).toISOString(),
|
||||
to: new Date(dateRange.to).toISOString(),
|
||||
});
|
||||
|
||||
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
|
|
@ -42,6 +42,8 @@ export const Overview = () => {
|
|||
const { dataStreams, status: dataStreamsStatus } = useEntitySummary({
|
||||
entityType: asset.type,
|
||||
entityId: asset.id,
|
||||
from: new Date(dateRange.from).toISOString(),
|
||||
to: new Date(dateRange.to).toISOString(),
|
||||
});
|
||||
const addMetricsCalloutId: AddMetricsCalloutKey =
|
||||
asset.type === 'host' ? 'hostOverview' : 'containerOverview';
|
||||
|
|
|
@ -60,6 +60,8 @@ export const Processes = () => {
|
|||
const { dataStreams, status: dataStreamsStatus } = useEntitySummary({
|
||||
entityType: BUILT_IN_ENTITY_TYPES.HOST,
|
||||
entityId: asset.name,
|
||||
from: new Date(getDateRangeInTimestamp().from).toISOString(),
|
||||
to: new Date(getDateRangeInTimestamp().to).toISOString(),
|
||||
});
|
||||
const addMetricsCalloutId: AddMetricsCalloutKey = 'hostProcesses';
|
||||
const [dismissedAddMetricsCallout, setDismissedAddMetricsCallout] = useLocalStorage(
|
||||
|
|
|
@ -27,6 +27,7 @@ import { OnboardingFlow } from '../../shared/templates/no_data_config';
|
|||
import { PageTitleWithPopover } from '../header/page_title_with_popover';
|
||||
import { useEntitySummary } from '../hooks/use_entity_summary';
|
||||
import { isLogsSignal, isMetricsSignal } from '../utils/get_data_stream_types';
|
||||
import { useDatePickerContext } from '../hooks/use_date_picker';
|
||||
|
||||
const DATA_AVAILABILITY_PER_TYPE: Partial<Record<InventoryItemType, string[]>> = {
|
||||
host: [SYSTEM_INTEGRATION],
|
||||
|
@ -37,10 +38,13 @@ export const Page = ({ tabs = [], links = [] }: ContentTemplateProps) => {
|
|||
const { metadata, loading: metadataLoading } = useMetadataStateContext();
|
||||
const { rightSideItems, tabEntries, breadcrumbs: headerBreadcrumbs } = usePageHeader(tabs, links);
|
||||
const { asset } = useAssetDetailsRenderPropsContext();
|
||||
const { getDateRangeInTimestamp } = useDatePickerContext();
|
||||
const trackOnlyOnce = React.useRef(false);
|
||||
const { dataStreams, status: entitySummaryStatus } = useEntitySummary({
|
||||
entityType: asset.type,
|
||||
entityId: asset.id,
|
||||
from: new Date(getDateRangeInTimestamp().from).toISOString(),
|
||||
to: new Date(getDateRangeInTimestamp().to).toISOString(),
|
||||
});
|
||||
const { isEntityCentricExperienceEnabled } = useEntityCentricExperienceSetting();
|
||||
const { activeTabId } = useTabSwitcherContext();
|
||||
|
|
|
@ -20,8 +20,6 @@ jest.mock('./get_latest_entity', () => ({
|
|||
getLatestEntity: jest.fn(),
|
||||
}));
|
||||
|
||||
type EntityType = 'host' | 'container';
|
||||
|
||||
describe('getDataStreamTypes', () => {
|
||||
let infraMetricsClient: jest.Mocked<InfraMetricsClient>;
|
||||
let obsEsClient: jest.Mocked<ObservabilityElasticsearchClient>;
|
||||
|
@ -40,12 +38,15 @@ describe('getDataStreamTypes', () => {
|
|||
|
||||
const params = {
|
||||
entityId: 'entity123',
|
||||
entityType: 'host' as EntityType,
|
||||
entityType: 'built_in_hosts_from_ecs_data',
|
||||
entityFilterType: 'host',
|
||||
entityCentricExperienceEnabled: false,
|
||||
infraMetricsClient,
|
||||
obsEsClient,
|
||||
entityManagerClient,
|
||||
logger,
|
||||
from: '2024-12-09T10:49:15Z',
|
||||
to: '2024-12-10T10:49:15Z',
|
||||
};
|
||||
|
||||
const result = await getDataStreamTypes(params);
|
||||
|
@ -63,12 +64,15 @@ describe('getDataStreamTypes', () => {
|
|||
|
||||
const params = {
|
||||
entityId: 'entity123',
|
||||
entityType: 'container' as EntityType,
|
||||
entityFilterType: 'container',
|
||||
entityType: 'built_in_containers_from_ecs_data',
|
||||
entityCentricExperienceEnabled: false,
|
||||
infraMetricsClient,
|
||||
obsEsClient,
|
||||
entityManagerClient,
|
||||
logger,
|
||||
from: '2024-12-09T10:49:15Z',
|
||||
to: '2024-12-10T10:49:15Z',
|
||||
};
|
||||
|
||||
const result = await getDataStreamTypes(params);
|
||||
|
@ -83,12 +87,15 @@ describe('getDataStreamTypes', () => {
|
|||
|
||||
const params = {
|
||||
entityId: 'entity123',
|
||||
entityType: 'host' as EntityType,
|
||||
entityType: 'built_in_hosts_from_ecs_data',
|
||||
entityFilterType: 'host',
|
||||
entityCentricExperienceEnabled: true,
|
||||
infraMetricsClient,
|
||||
obsEsClient,
|
||||
entityManagerClient,
|
||||
logger,
|
||||
from: '2024-12-09T10:49:15Z',
|
||||
to: '2024-12-10T10:49:15Z',
|
||||
};
|
||||
|
||||
const result = await getDataStreamTypes(params);
|
||||
|
@ -96,11 +103,12 @@ describe('getDataStreamTypes', () => {
|
|||
expect(result).toEqual(['metrics', 'logs']);
|
||||
expect(getHasMetricsData).toHaveBeenCalled();
|
||||
expect(getLatestEntity).toHaveBeenCalledWith({
|
||||
inventoryEsClient: obsEsClient,
|
||||
entityId: 'entity123',
|
||||
entityType: 'host',
|
||||
entityType: 'built_in_hosts_from_ecs_data',
|
||||
entityManagerClient,
|
||||
logger,
|
||||
from: '2024-12-09T10:49:15Z',
|
||||
to: '2024-12-10T10:49:15Z',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -110,12 +118,15 @@ describe('getDataStreamTypes', () => {
|
|||
|
||||
const params = {
|
||||
entityId: 'entity123',
|
||||
entityType: 'host' as EntityType,
|
||||
entityType: 'built_in_hosts_from_ecs_data',
|
||||
entityFilterType: 'host',
|
||||
entityCentricExperienceEnabled: true,
|
||||
infraMetricsClient,
|
||||
obsEsClient,
|
||||
entityManagerClient,
|
||||
logger,
|
||||
from: '2024-12-09T10:49:15Z',
|
||||
to: '2024-12-10T10:49:15Z',
|
||||
};
|
||||
|
||||
const result = await getDataStreamTypes(params);
|
||||
|
@ -130,12 +141,15 @@ describe('getDataStreamTypes', () => {
|
|||
|
||||
const params = {
|
||||
entityId: 'entity123',
|
||||
entityType: 'host' as EntityType,
|
||||
entityType: 'built_in_hosts_from_ecs_data',
|
||||
entityFilterType: 'host',
|
||||
entityCentricExperienceEnabled: true,
|
||||
infraMetricsClient,
|
||||
obsEsClient,
|
||||
entityManagerClient,
|
||||
logger,
|
||||
from: '2024-12-09T10:49:15Z',
|
||||
to: '2024-12-10T10:49:15Z',
|
||||
};
|
||||
|
||||
const result = await getDataStreamTypes(params);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { type EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
|
||||
import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
|
||||
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
|
||||
import { EntityDataStreamType } from '@kbn/observability-shared-plugin/common';
|
||||
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
|
||||
|
@ -17,12 +18,15 @@ import { getLatestEntity } from './get_latest_entity';
|
|||
|
||||
interface Params {
|
||||
entityId: string;
|
||||
entityType: 'host' | 'container';
|
||||
entityType: string;
|
||||
entityFilterType: string;
|
||||
entityCentricExperienceEnabled: boolean;
|
||||
infraMetricsClient: InfraMetricsClient;
|
||||
obsEsClient: ObservabilityElasticsearchClient;
|
||||
entityManagerClient: EntityClient;
|
||||
logger: Logger;
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export async function getDataStreamTypes({
|
||||
|
@ -30,14 +34,16 @@ export async function getDataStreamTypes({
|
|||
entityId,
|
||||
entityManagerClient,
|
||||
entityType,
|
||||
entityFilterType,
|
||||
infraMetricsClient,
|
||||
obsEsClient,
|
||||
from,
|
||||
to,
|
||||
logger,
|
||||
}: Params) {
|
||||
const hasMetricsData = await getHasMetricsData({
|
||||
infraMetricsClient,
|
||||
entityId,
|
||||
field: findInventoryFields(entityType).id,
|
||||
field: findInventoryFields(entityFilterType as InventoryItemType).id,
|
||||
});
|
||||
|
||||
const sourceDataStreams = new Set(hasMetricsData ? [EntityDataStreamType.METRICS] : []);
|
||||
|
@ -47,11 +53,12 @@ export async function getDataStreamTypes({
|
|||
}
|
||||
|
||||
const latestEntity = await getLatestEntity({
|
||||
inventoryEsClient: obsEsClient,
|
||||
entityId,
|
||||
entityType,
|
||||
entityManagerClient,
|
||||
logger,
|
||||
from,
|
||||
to,
|
||||
});
|
||||
|
||||
if (latestEntity) {
|
||||
|
|
|
@ -5,64 +5,55 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ENTITY_LATEST, entitiesAliasPattern } from '@kbn/entities-schema';
|
||||
import { type EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
|
||||
import { ENTITY_TYPE, SOURCE_DATA_STREAM_TYPE } from '@kbn/observability-shared-plugin/common';
|
||||
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
|
||||
import type { EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
|
||||
const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({
|
||||
type: '*',
|
||||
dataset: ENTITY_LATEST,
|
||||
});
|
||||
import { isArray } from 'lodash';
|
||||
|
||||
interface EntitySourceResponse {
|
||||
sourceDataStreamType?: string | string[];
|
||||
}
|
||||
|
||||
export async function getLatestEntity({
|
||||
inventoryEsClient,
|
||||
entityId,
|
||||
entityType,
|
||||
entityManagerClient,
|
||||
from,
|
||||
to,
|
||||
logger,
|
||||
}: {
|
||||
inventoryEsClient: ObservabilityElasticsearchClient;
|
||||
entityType: 'host' | 'container';
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
entityManagerClient: EntityClient;
|
||||
from: string;
|
||||
to: string;
|
||||
logger: Logger;
|
||||
}): Promise<EntitySourceResponse | undefined> {
|
||||
try {
|
||||
const { definitions } = await entityManagerClient.getEntityDefinitions({
|
||||
builtIn: true,
|
||||
const entityDefinitionsSource = await entityManagerClient.v2.readSourceDefinitions({
|
||||
type: entityType,
|
||||
});
|
||||
|
||||
const hostOrContainerIdentityField = definitions[0]?.identityFields?.[0]?.field;
|
||||
const hostOrContainerIdentityField = entityDefinitionsSource[0]?.identity_fields?.[0];
|
||||
if (hostOrContainerIdentityField === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const response = await inventoryEsClient.esql<
|
||||
{
|
||||
'source_data_stream.type'?: string | string;
|
||||
},
|
||||
{ transform: 'plain' }
|
||||
>(
|
||||
'get_latest_entities',
|
||||
{
|
||||
query: `FROM ${ENTITIES_LATEST_ALIAS}
|
||||
| WHERE ${ENTITY_TYPE} == ?
|
||||
| WHERE ${hostOrContainerIdentityField} == ?
|
||||
| KEEP ${SOURCE_DATA_STREAM_TYPE}
|
||||
`,
|
||||
params: [entityType, entityId],
|
||||
},
|
||||
{ transform: 'plain' }
|
||||
);
|
||||
const { entities } = await entityManagerClient.v2.searchEntities({
|
||||
type: entityType,
|
||||
limit: 1,
|
||||
metadata_fields: ['data_stream.type'],
|
||||
filters: [`${hostOrContainerIdentityField}: "${entityId}"`],
|
||||
start: from,
|
||||
end: to,
|
||||
});
|
||||
|
||||
return { sourceDataStreamType: response.hits[0]['source_data_stream.type'] };
|
||||
const entityDataStreamType = entities[0]['data_stream.type'];
|
||||
|
||||
return {
|
||||
sourceDataStreamType: isArray(entityDataStreamType)
|
||||
? entityDataStreamType.filter(Boolean)
|
||||
: entityDataStreamType,
|
||||
};
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
|
|
|
@ -29,13 +29,20 @@ export const initEntitiesConfigurationRoutes = (libs: InfraBackendLibs) => {
|
|||
]),
|
||||
entityId: schema.string(),
|
||||
}),
|
||||
query: schema.object({ from: schema.string(), to: schema.string() }),
|
||||
},
|
||||
options: {
|
||||
access: 'internal',
|
||||
},
|
||||
},
|
||||
async (requestContext, request, response) => {
|
||||
const { entityId, entityType } = request.params;
|
||||
const { entityId, entityType: entityFilterType } = request.params;
|
||||
const mapTypeToV2 = {
|
||||
[BUILT_IN_ENTITY_TYPES.HOST]: BUILT_IN_ENTITY_TYPES.HOST_V2,
|
||||
[BUILT_IN_ENTITY_TYPES.CONTAINER]: BUILT_IN_ENTITY_TYPES.CONTAINER_V2,
|
||||
};
|
||||
const entityType = mapTypeToV2[entityFilterType];
|
||||
const { from, to } = request.query;
|
||||
const [coreContext, infraContext] = await Promise.all([
|
||||
requestContext.core,
|
||||
requestContext.infra,
|
||||
|
@ -64,9 +71,12 @@ export const initEntitiesConfigurationRoutes = (libs: InfraBackendLibs) => {
|
|||
entityId,
|
||||
entityManagerClient,
|
||||
entityType,
|
||||
entityFilterType,
|
||||
infraMetricsClient,
|
||||
obsEsClient,
|
||||
logger,
|
||||
from,
|
||||
to,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
|
@ -74,6 +84,7 @@ export const initEntitiesConfigurationRoutes = (libs: InfraBackendLibs) => {
|
|||
sourceDataStreams: sourceDataStreamTypes,
|
||||
entityId,
|
||||
entityType,
|
||||
entityFilterType,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
@ -110,7 +110,6 @@
|
|||
"@kbn/shared-ux-page-no-data-types",
|
||||
"@kbn/xstate-utils",
|
||||
"@kbn/entityManager-plugin",
|
||||
"@kbn/entities-schema",
|
||||
"@kbn/zod",
|
||||
"@kbn/observability-utils-server",
|
||||
"@kbn/core-plugins-server",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue