Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Inventory] List k8s entities in the grid #197292

Merged
merged 23 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 4 additions & 54 deletions x-pack/plugins/observability_solution/inventory/common/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
*/
import { ENTITY_LATEST, entitiesAliasPattern } from '@kbn/entities-schema';
import {
HOST_NAME,
SERVICE_ENVIRONMENT,
SERVICE_NAME,
AGENT_NAME,
CLOUD_PROVIDER,
CONTAINER_ID,
ENTITY_DEFINITION_ID,
ENTITY_DISPLAY_NAME,
ENTITY_ID,
Expand All @@ -22,12 +16,6 @@ import {
import { isRight } from 'fp-ts/lib/Either';
import * as t from 'io-ts';

export const entityTypeRt = t.union([
t.literal('service'),
t.literal('host'),
t.literal('container'),
]);

export const entityColumnIdsRt = t.union([
t.literal(ENTITY_DISPLAY_NAME),
t.literal(ENTITY_LAST_SEEN),
Expand All @@ -37,8 +25,6 @@ export const entityColumnIdsRt = t.union([

export type EntityColumnIds = t.TypeOf<typeof entityColumnIdsRt>;

export type EntityType = t.TypeOf<typeof entityTypeRt>;

export const defaultEntitySortField: EntityColumnIds = 'alertsCount';

export const MAX_NUMBER_OF_ENTITIES = 500;
Expand All @@ -48,20 +34,8 @@ export const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({
dataset: ENTITY_LATEST,
});

const BUILTIN_SERVICES_FROM_ECS_DATA = 'builtin_services_from_ecs_data';
const BUILTIN_HOSTS_FROM_ECS_DATA = 'builtin_hosts_from_ecs_data';
const BUILTIN_CONTAINERS_FROM_ECS_DATA = 'builtin_containers_from_ecs_data';

export const defaultEntityDefinitions = [
BUILTIN_SERVICES_FROM_ECS_DATA,
BUILTIN_HOSTS_FROM_ECS_DATA,
BUILTIN_CONTAINERS_FROM_ECS_DATA,
];

export const defaultEntityTypes: EntityType[] = ['service', 'host', 'container'];

const entityArrayRt = t.array(entityTypeRt);
export const entityTypesRt = new t.Type<EntityType[], string, unknown>(
const entityArrayRt = t.array(t.string);
export const entityTypesRt = new t.Type<string[], string, unknown>(
'entityTypesRt',
entityArrayRt.is,
(input, context) => {
Expand All @@ -83,37 +57,13 @@ export const entityTypesRt = new t.Type<EntityType[], string, unknown>(
(arr) => arr.join()
);

interface BaseEntity {
export interface Entity {
[ENTITY_LAST_SEEN]: string;
[ENTITY_ID]: string;
[ENTITY_TYPE]: EntityType;
[ENTITY_TYPE]: string;
[ENTITY_DISPLAY_NAME]: string;
[ENTITY_DEFINITION_ID]: string;
[ENTITY_IDENTITY_FIELDS]: string | string[];
alertsCount?: number;
[key: string]: any;
}

/**
* These types are based on service, host and container from the built in definition.
*/
export interface ServiceEntity extends BaseEntity {
[ENTITY_TYPE]: 'service';
[SERVICE_NAME]: string;
[SERVICE_ENVIRONMENT]?: string | string[] | null;
[AGENT_NAME]: string | string[] | null;
}

export interface HostEntity extends BaseEntity {
[ENTITY_TYPE]: 'host';
[HOST_NAME]: string;
[CLOUD_PROVIDER]: string | string[] | null;
}

export interface ContainerEntity extends BaseEntity {
[ENTITY_TYPE]: 'container';
[CONTAINER_ID]: string;
[CLOUD_PROVIDER]: string | string[] | null;
}

export type Entity = ServiceEntity | HostEntity | ContainerEntity;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/
import { isLeft, isRight } from 'fp-ts/lib/Either';
import { type EntityType, entityTypesRt } from './entities';
import { entityTypesRt } from './entities';

const validate = (input: unknown) => entityTypesRt.decode(input);

Expand All @@ -28,36 +28,12 @@ describe('entityTypesRt codec', () => {
}
});

it('should fail validation when the string contains invalid entity types', () => {
const input = 'service,invalidType,host';
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should fail validation when the array contains invalid entity types', () => {
const input = ['service', 'invalidType', 'host'];
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should fail validation when input is not a string or array', () => {
const input = 123;
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should fail validation when the array contains non-string elements', () => {
const input = ['service', 123, 'host'];
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should fail validation an empty string', () => {
const input = '';
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should validate an empty array as valid', () => {
const input: unknown[] = [];
const result = validate(input);
Expand All @@ -67,32 +43,14 @@ describe('entityTypesRt codec', () => {
}
});

it('should fail validation when the string contains only commas', () => {
const input = ',,,';
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should fail validation for partial valid entities in a string', () => {
const input = 'service,invalidType';
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should fail validation for partial valid entities in an array', () => {
const input = ['service', 'invalidType'];
const result = validate(input);
expect(isLeft(result)).toBe(true);
});

it('should serialize a valid array back to a string', () => {
const input: EntityType[] = ['service', 'host'];
const input = ['service', 'host'];
const serialized = entityTypesRt.encode(input);
expect(serialized).toBe('service,host');
});

it('should serialize an empty array back to an empty string', () => {
const input: EntityType[] = [];
const input: string[] = [];
const serialized = entityTypesRt.encode(input);
expect(serialized).toBe('');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ENTITY_ID,
ENTITY_LAST_SEEN,
} from '@kbn/observability-shared-plugin/common';
import { HostEntity, ServiceEntity } from '../entities';
import type { Entity } from '../entities';
import { parseIdentityFieldValuesToKql } from './parse_identity_field_values_to_kql';

const commonEntityFields = {
Expand All @@ -24,7 +24,7 @@ const commonEntityFields = {

describe('parseIdentityFieldValuesToKql', () => {
it('should return the value when identityFields is a single string', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'agent.name': 'node',
'entity.identityFields': 'service.name',
'service.name': 'my-service',
Expand All @@ -37,7 +37,7 @@ describe('parseIdentityFieldValuesToKql', () => {
});

it('should return values when identityFields is an array of strings', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'agent.name': 'node',
'entity.identityFields': ['service.name', 'service.environment'],
'service.name': 'my-service',
Expand All @@ -51,7 +51,7 @@ describe('parseIdentityFieldValuesToKql', () => {
});

it('should return an empty string if identityFields is empty string', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'agent.name': 'node',
'entity.identityFields': '',
'service.name': 'my-service',
Expand All @@ -63,7 +63,7 @@ describe('parseIdentityFieldValuesToKql', () => {
expect(result).toEqual('');
});
it('should return an empty array if identityFields is empty array', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'agent.name': 'node',
'entity.identityFields': [],
'service.name': 'my-service',
Expand All @@ -76,7 +76,7 @@ describe('parseIdentityFieldValuesToKql', () => {
});

it('should ignore fields that are not present in the entity', () => {
const entity: HostEntity = {
const entity: Entity = {
'entity.identityFields': ['host.name', 'foo.bar'],
'host.name': 'my-host',
'entity.type': 'host',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { type KibanaReactContextValue } from '@kbn/kibana-react-plugin/public';
import { render, screen } from '@testing-library/react';
import { AlertsBadge } from './alerts_badge';
import * as useKibana from '../../hooks/use_kibana';
import { HostEntity, ServiceEntity } from '../../../common/entities';
import type { Entity } from '../../../common/entities';

describe('AlertsBadge', () => {
jest.spyOn(useKibana, 'useKibana').mockReturnValue({
Expand All @@ -27,7 +27,7 @@ describe('AlertsBadge', () => {
});

it('render alerts badge for a host entity', () => {
const entity: HostEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'entity.id': '1',
'entity.type': 'host',
Expand All @@ -45,7 +45,7 @@ describe('AlertsBadge', () => {
expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('1');
});
it('render alerts badge for a service entity', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'agent.name': 'node',
'entity.id': '1',
Expand All @@ -64,7 +64,7 @@ describe('AlertsBadge', () => {
expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('5');
});
it('render alerts badge for a service entity with multiple identity fields', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'agent.name': 'node',
'entity.id': '1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import React, { useMemo, useState } from 'react';
import { ENTITY_LAST_SEEN, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common';
import { useArgs } from '@storybook/addons';
import { EntitiesGrid } from '.';
import { EntityType } from '../../../common/entities';
import { entitiesMock } from './mock/entities_mock';

interface EntityGridStoriesArgs {
entityType?: EntityType;
entityType?: string;
}

const entityTypeOptions: EntityType[] = ['host', 'container', 'service'];
const entityTypeOptions = ['host', 'container', 'service'];

const stories: Meta<EntityGridStoriesArgs> = {
title: 'app/inventory/entities_grid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { type KibanaReactContextValue } from '@kbn/kibana-react-plugin/public';
import * as useKibana from '../../../hooks/use_kibana';
import { EntityName } from '.';
import { ContainerEntity, HostEntity, ServiceEntity } from '../../../../common/entities';
import type { Entity } from '../../../../common/entities';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { ASSET_DETAILS_LOCATOR_ID } from '@kbn/observability-shared-plugin/common/locators/infra/asset_details_locator';
Expand Down Expand Up @@ -40,7 +40,7 @@ describe('EntityName', () => {
});

it('returns host link', () => {
const entity: HostEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'entity.id': '1',
'entity.type': 'host',
Expand All @@ -58,7 +58,7 @@ describe('EntityName', () => {
});

it('returns container link', () => {
const entity: ContainerEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'entity.id': '1',
'entity.type': 'container',
Expand All @@ -76,7 +76,7 @@ describe('EntityName', () => {
});

it('returns service link without environment', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'entity.id': '1',
'entity.type': 'service',
Expand All @@ -94,7 +94,7 @@ describe('EntityName', () => {
});

it('returns service link with environment', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'entity.id': '1',
'entity.type': 'service',
Expand All @@ -113,7 +113,7 @@ describe('EntityName', () => {
});

it('returns service link with first environment when it is an array', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'entity.id': '1',
'entity.type': 'service',
Expand All @@ -132,7 +132,7 @@ describe('EntityName', () => {
});

it('returns service link identity fields is an array', () => {
const entity: ServiceEntity = {
const entity: Entity = {
'entity.lastSeenTimestamp': 'foo',
'entity.id': '1',
'entity.type': 'service',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@ import {
ENTITY_LAST_SEEN,
ENTITY_TYPE,
} from '@kbn/observability-shared-plugin/common';
import { EntityColumnIds, EntityType } from '../../../common/entities';
import { EntityColumnIds } from '../../../common/entities';
import { APIReturnType } from '../../api';
import { BadgeFilterWithPopover } from '../badge_filter_with_popover';
import { getColumns } from './grid_columns';
import { AlertsBadge } from '../alerts_badge/alerts_badge';
import { EntityName } from './entity_name';
import { getEntityTypeLabel } from '../../utils/get_entity_type_label';

type InventoryEntitiesAPIReturnType = APIReturnType<'GET /internal/inventory/entities'>;
type LatestEntities = InventoryEntitiesAPIReturnType['entities'];
Expand All @@ -39,7 +38,7 @@ interface Props {
pageIndex: number;
onChangeSort: (sorting: EuiDataGridSorting['columns'][0]) => void;
onChangePage: (nextPage: number) => void;
onFilterByType: (entityType: EntityType) => void;
onFilterByType: (entityType: string) => void;
}

const PAGE_SIZE = 20;
Expand Down Expand Up @@ -95,7 +94,7 @@ export function EntitiesGrid({
<BadgeFilterWithPopover
field={ENTITY_TYPE}
value={entityType}
label={getEntityTypeLabel(entityType)}
label={entityType}
onFilter={() => onFilterByType(entityType)}
/>
);
Expand Down
Loading