Skip to content

Commit

Permalink
[8.x] [ECO][Inventory v2] Ad hoc data view: Add get entities definiti…
Browse files Browse the repository at this point in the history
…on endpoint using sources (#204026) (#206610)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ECO][Inventory v2] Ad hoc data view: Add get entities definition
endpoint using sources
(#204026)](#204026)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"jennypavlova","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-01-14T15:30:39Z","message":"[ECO][Inventory
v2] Ad hoc data view: Add get entities definition endpoint using sources
(#204026)\n\nCloses #202298 \r\n\r\nThis PR changes the way we get the
entity index patterns to v2. It\r\ncreates an endpoint part of the
inventory API which returns the index\r\npatterns by entity
type.\r\n\r\n## Testing\r\n\r\n### Test the endpoint: \r\n- Open Dev
tools and add\r\n` GET
kbn:/internal/inventory/entity/definitions/sources`\r\n- Response:
\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/3346c36e-dbc2-4e56-9ed6-d3d3a8f7d1a5)\r\n\r\n\r\n###
Test in the UI\r\n- After the previous steps add some host data (oblt
cluster /\r\nmetricbeat) or use synthtrace (for example use `node
scripts/synthtrace\r\ninfra_hosts_with_apm_hosts
--scenarioOpts.numInstances=10` or `node\r\nscripts/synthtrace
logs_traces_hosts.ts`)\r\n- Go to Inventory and expand the host
group\r\n- Click on the actions button for any host and click on the
Discover\r\nlink\r\n- The correct dataview should be selected based on
the index patterns in\r\nthe source definition\r\nThe same can be done
for other entity types\r\n- Test the search bar as well (the suggestions
should be visible) and\r\nnow we should have 1 request to get the
sources (instead of doing it
on\r\nclick)\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/93b5ac6c-9d64-44e0-b26e-6133477e0840\r\n\r\n\r\n\r\n\r\n<!--ONMERGE
{\"backportTargets\":[\"8.x\"]}
ONMERGE-->\r\n\r\n---------\r\n\r\nCo-authored-by: Carlos Crespo
<[email protected]>\r\nCo-authored-by: Sergi Romeu
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"ffccfdc62cd1da5baf54568cbee20a9a3466178e","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services"],"title":"[ECO][Inventory
v2] Ad hoc data view: Add get entities definition endpoint using
sources","number":204026,"url":"https://github.com/elastic/kibana/pull/204026","mergeCommit":{"message":"[ECO][Inventory
v2] Ad hoc data view: Add get entities definition endpoint using sources
(#204026)\n\nCloses #202298 \r\n\r\nThis PR changes the way we get the
entity index patterns to v2. It\r\ncreates an endpoint part of the
inventory API which returns the index\r\npatterns by entity
type.\r\n\r\n## Testing\r\n\r\n### Test the endpoint: \r\n- Open Dev
tools and add\r\n` GET
kbn:/internal/inventory/entity/definitions/sources`\r\n- Response:
\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/3346c36e-dbc2-4e56-9ed6-d3d3a8f7d1a5)\r\n\r\n\r\n###
Test in the UI\r\n- After the previous steps add some host data (oblt
cluster /\r\nmetricbeat) or use synthtrace (for example use `node
scripts/synthtrace\r\ninfra_hosts_with_apm_hosts
--scenarioOpts.numInstances=10` or `node\r\nscripts/synthtrace
logs_traces_hosts.ts`)\r\n- Go to Inventory and expand the host
group\r\n- Click on the actions button for any host and click on the
Discover\r\nlink\r\n- The correct dataview should be selected based on
the index patterns in\r\nthe source definition\r\nThe same can be done
for other entity types\r\n- Test the search bar as well (the suggestions
should be visible) and\r\nnow we should have 1 request to get the
sources (instead of doing it
on\r\nclick)\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/93b5ac6c-9d64-44e0-b26e-6133477e0840\r\n\r\n\r\n\r\n\r\n<!--ONMERGE
{\"backportTargets\":[\"8.x\"]}
ONMERGE-->\r\n\r\n---------\r\n\r\nCo-authored-by: Carlos Crespo
<[email protected]>\r\nCo-authored-by: Sergi Romeu
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"ffccfdc62cd1da5baf54568cbee20a9a3466178e"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/204026","number":204026,"mergeCommit":{"message":"[ECO][Inventory
v2] Ad hoc data view: Add get entities definition endpoint using sources
(#204026)\n\nCloses #202298 \r\n\r\nThis PR changes the way we get the
entity index patterns to v2. It\r\ncreates an endpoint part of the
inventory API which returns the index\r\npatterns by entity
type.\r\n\r\n## Testing\r\n\r\n### Test the endpoint: \r\n- Open Dev
tools and add\r\n` GET
kbn:/internal/inventory/entity/definitions/sources`\r\n- Response:
\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/3346c36e-dbc2-4e56-9ed6-d3d3a8f7d1a5)\r\n\r\n\r\n###
Test in the UI\r\n- After the previous steps add some host data (oblt
cluster /\r\nmetricbeat) or use synthtrace (for example use `node
scripts/synthtrace\r\ninfra_hosts_with_apm_hosts
--scenarioOpts.numInstances=10` or `node\r\nscripts/synthtrace
logs_traces_hosts.ts`)\r\n- Go to Inventory and expand the host
group\r\n- Click on the actions button for any host and click on the
Discover\r\nlink\r\n- The correct dataview should be selected based on
the index patterns in\r\nthe source definition\r\nThe same can be done
for other entity types\r\n- Test the search bar as well (the suggestions
should be visible) and\r\nnow we should have 1 request to get the
sources (instead of doing it
on\r\nclick)\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/93b5ac6c-9d64-44e0-b26e-6133477e0840\r\n\r\n\r\n\r\n\r\n<!--ONMERGE
{\"backportTargets\":[\"8.x\"]}
ONMERGE-->\r\n\r\n---------\r\n\r\nCo-authored-by: Carlos Crespo
<[email protected]>\r\nCo-authored-by: Sergi Romeu
<[email protected]>\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"ffccfdc62cd1da5baf54568cbee20a9a3466178e"}}]}]
BACKPORT-->

Co-authored-by: jennypavlova <[email protected]>
  • Loading branch information
kibanamachine and jennypavlova authored Jan 14, 2025
1 parent 4c18bd7 commit c21864a
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,28 @@ export const EntityActions = ({ entity, setShowActions }: Props) => {
? `inventoryEntityActionsButton-${entity.entityDisplayName}`
: 'inventoryEntityActionsButton';

const { getDiscoverEntitiesRedirectUrl, isEntityDefinitionLoading } = useDiscoverRedirect(entity);
const { getDiscoverEntitiesRedirectUrl } = useDiscoverRedirect(entity);
const discoverUrl = getDiscoverEntitiesRedirectUrl();

const actions: React.ReactElement[] = [];
const actions = [
<EuiContextMenuItem
data-test-subj="inventoryEntityActionExploreInDiscover"
key={`exploreInDiscover-${entity.entityDisplayName}`}
color="text"
icon="discoverApp"
href={discoverUrl}
>
{i18n.translate('xpack.inventory.entityActions.exploreInDiscoverLink', {
defaultMessage: 'Explore in Discover',
})}
</EuiContextMenuItem>,
];

if (!discoverUrl && !isEntityDefinitionLoading) {
if (!discoverUrl) {
setShowActions(false);
return null;
}

if (!isEntityDefinitionLoading) {
actions.push(
<EuiContextMenuItem
data-test-subj="inventoryEntityActionExploreInDiscover"
key={`exploreInDiscover-${entity.entityDisplayName}`}
color="text"
icon="discoverApp"
href={discoverUrl}
>
{i18n.translate('xpack.inventory.entityActions.exploreInDiscoverLink', {
defaultMessage: 'Explore in Discover',
})}
</EuiContextMenuItem>
);
}

return (
<EuiPopover
isOpen={isPopoverOpen}
Expand All @@ -65,7 +61,6 @@ export const EntityActions = ({ entity, setShowActions }: Props) => {
iconType="boxesHorizontal"
color="text"
onClick={togglePopover}
isLoading={isEntityDefinitionLoading}
/>
}
closePopover={closePopover}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import React, { useCallback, useState } from 'react';
import { EntityCountBadge } from './entity_count_badge';
import { GroupedEntitiesGrid } from './grouped_entities_grid';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context';

const ENTITIES_COUNT_BADGE = i18n.translate(
'xpack.inventory.inventoryGroupPanel.entitiesBadgeLabel',
Expand All @@ -26,10 +27,12 @@ export interface Props {
export function EntityGroupAccordion({ groupValue, groupLabel, groupCount, isLoading }: Props) {
const { euiTheme } = useEuiTheme();
const [open, setOpen] = useState(false);
const { setSingleEntityType } = useUnifiedSearchContext();

const onToggle = useCallback(() => {
if (!open) setSingleEntityType(groupValue);
setOpen((opened) => !opened);
}, []);
}, [groupValue, open, setSingleEntityType]);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,17 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import type { InventoryEntity } from '../../common/entities';
import { useAdHocDataView } from './use_adhoc_data_view';
import { useFetchEntityDefinition } from './use_fetch_entity_definition';
import { useKibana } from './use_kibana';
import { useUnifiedSearchContext } from './use_unified_search_context';

export const useDiscoverRedirect = (entity: InventoryEntity) => {
const {
services: { share, application, entityManager },
} = useKibana();
const { entityDefinitions, isEntityDefinitionLoading } = useFetchEntityDefinition(
entity.entityDefinitionId as string
);

const title = useMemo(
() =>
!isEntityDefinitionLoading && entityDefinitions && entityDefinitions?.length > 0
? entityDefinitions[0]?.indexPatterns?.join(',')
: '',
[entityDefinitions, isEntityDefinitionLoading]
);

const { dataView } = useAdHocDataView(title);

const { discoverDataview } = useUnifiedSearchContext();
const { dataView } = discoverDataview;
const discoverLocator = share.url.locators.get('DISCOVER_APP_LOCATOR');

const getDiscoverEntitiesRedirectUrl = useCallback(() => {
Expand All @@ -37,19 +24,19 @@ export const useDiscoverRedirect = (entity: InventoryEntity) => {
})
: '';

return application.capabilities.discover?.show
return application.capabilities.discover?.show || !dataView
? discoverLocator?.getRedirectUrl({
indexPatternId: dataView?.id ?? '',
dataViewId: dataView?.id ?? '',
query: { query: entityKqlFilter, language: 'kuery' },
})
: undefined;
}, [
application.capabilities.discover?.show,
dataView?.id,
dataView,
discoverLocator,
entity,
entityManager.entityClient,
]);

return { getDiscoverEntitiesRedirectUrl, isEntityDefinitionLoading };
return { getDiscoverEntitiesRedirectUrl };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { useInventoryAbortableAsync } from './use_inventory_abortable_async';
import { useKibana } from './use_kibana';

export const useFetchEntityDefinitionIndexPattern = () => {
const {
services: { inventoryAPIClient },
} = useKibana();

const { value = { definitionIndexPatterns: {} }, loading } = useInventoryAbortableAsync(
({ signal }) => {
return inventoryAPIClient.fetch('GET /internal/inventory/entity/definitions/sources', {
signal,
});
},
[inventoryAPIClient]
);

return {
definitionIndexPatterns: value?.definitionIndexPatterns,
isIndexPatternsLoading: loading,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,73 @@
* 2.0.
*/
import createContainer from 'constate';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { Subject } from 'rxjs';
import { ENTITIES_LATEST_ALIAS } from '../../common/entities';
import { useAdHocDataView } from './use_adhoc_data_view';
import { useInventoryDecodedQueryParams } from './use_inventory_decoded_query_params';
import { useInventoryAbortableAsync } from './use_inventory_abortable_async';
import { groupEntityTypesByStatus } from '../utils/group_entity_types_by_status';
import { useKibana } from './use_kibana';
import { useInventoryParams } from './use_inventory_params';
import { useFetchEntityDefinitionIndexPattern } from './use_fetch_entity_definition_index_patterns';

function useUnifiedSearch() {
const { dataView } = useAdHocDataView(ENTITIES_LATEST_ALIAS);
const {
services: { inventoryAPIClient },
} = useKibana();
const {
query: { kuery },
} = useInventoryParams('/');
const { entityTypes } = useInventoryDecodedQueryParams();
const { definitionIndexPatterns, isIndexPatternsLoading } =
useFetchEntityDefinitionIndexPattern();
const [singleEntityType, setSingleEntityType] = useState<string>('');

const { value, refresh, loading } = useInventoryAbortableAsync(
({ signal }) => {
const { entityTypesOff, entityTypesOn } = groupEntityTypesByStatus(entityTypes);
return inventoryAPIClient.fetch('GET /internal/inventory/entities/types', {
params: {
query: {
includeEntityTypes: entityTypesOn.length ? JSON.stringify(entityTypesOn) : undefined,
excludeEntityTypes: entityTypesOff.length ? JSON.stringify(entityTypesOff) : undefined,
kuery,
},
},
signal,
});
},
[entityTypes, inventoryAPIClient, kuery]
);

const entityTypeIds = useMemo(
() => value?.entityTypes.map((entityType) => entityType.id) ?? [],
[value?.entityTypes]
);
const allDefinitionIndexPatterns = useMemo(() => {
const filteredDefinitionIndexPatterns = entityTypeIds.flatMap(
(id) => definitionIndexPatterns?.[id] ?? []
);

return Array.from(new Set(filteredDefinitionIndexPatterns)).join(',');
}, [definitionIndexPatterns, entityTypeIds]);

const { dataView } = useAdHocDataView(allDefinitionIndexPatterns ?? '');
const discoverDataview = useAdHocDataView(
(definitionIndexPatterns[singleEntityType ?? ''] ?? []).join(',') ?? ''
);

const [refreshSubject$] = useState<Subject<void>>(new Subject());

return {
dataView,
definitionIndexPatterns,
refreshSubject$,
loading: loading || isIndexPatternsLoading,
refresh,
value,
discoverDataview,
setSingleEntityType,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,11 @@ import React from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
import { EntitiesSummary } from '../../components/entities_summary';
import { EntityGroupAccordion } from '../../components/entity_group_accordion';
import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async';
import { useInventoryDecodedQueryParams } from '../../hooks/use_inventory_decoded_query_params';
import { useInventoryParams } from '../../hooks/use_inventory_params';
import { useKibana } from '../../hooks/use_kibana';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context';
import { GroupBySelector } from '../../components/group_by_selector';
import { groupEntityTypesByStatus } from '../../utils/group_entity_types_by_status';

export function InventoryPage() {
const {
services: { inventoryAPIClient },
} = useKibana();
const { refreshSubject$ } = useUnifiedSearchContext();
const {
query: { kuery },
} = useInventoryParams('/');
const { entityTypes } = useInventoryDecodedQueryParams();
const { value, refresh, loading } = useInventoryAbortableAsync(
({ signal }) => {
const { entityTypesOff, entityTypesOn } = groupEntityTypesByStatus(entityTypes);
return inventoryAPIClient.fetch('GET /internal/inventory/entities/types', {
params: {
query: {
includeEntityTypes: entityTypesOn.length ? JSON.stringify(entityTypesOn) : undefined,
excludeEntityTypes: entityTypesOff.length ? JSON.stringify(entityTypesOff) : undefined,
kuery,
},
},
signal,
});
},
[entityTypes, inventoryAPIClient, kuery]
);
const { refreshSubject$, value, refresh, loading } = useUnifiedSearchContext();

useEffectOnce(() => {
const refreshSubscription = refreshSubject$.subscribe(refresh);
Expand Down
Loading

0 comments on commit c21864a

Please sign in to comment.