Skip to content

Commit

Permalink
[8.x] [ObsUx][Inventory] Add actions column with link to discover for…
Browse files Browse the repository at this point in the history
… inventory (elastic#199306) (elastic#199789)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ObsUx][Inventory] Add actions column with link to discover for
inventory (elastic#199306)](elastic#199306)

<!--- Backport version: 8.9.8 -->

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

<!--BACKPORT
[{"author":{"name":"jennypavlova","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-08T18:18:07Z","message":"[ObsUx][Inventory]
Add actions column with link to discover for inventory
(elastic#199306)\n\nCloses elastic#199025 \r\n## Summary\r\n\r\nThis PR adds an
actions column with a link to discover for inventory. It\r\nis available
in the inventory grid(s) in both the group view and the\r\nunified
inventory view.\r\n\r\nThe column header tooltip text will change when
it's
available:\r\n[issue](elastic#199500)
added\r\n\r\n⚠️ If the discover link is not available I added a logic to
hide the\r\nwhole actions column as this is the only available option
for now. Once\r\nwe add more actions we should refactor that to just not
add the action\r\nand to keep the column visible (which doesn't make
sense atm)\r\n\r\n## Testing\r\n- Enable the Inventory\r\n- Check
with/without grouping both the action link and the button\r\n -
combination of kuery / drop-down filter\r\n - without any filters\r\n -
With just one kuery or drop-down filter \r\n- When the link is clicked
from the table we should see a filter by\r\nidentity field in the query
in discover (like `service.name: 'test'`,\r\n`conteainer.id:
'test'`)\r\n
\r\n\r\n\r\nhttps://github.com/user-attachments/assets/bb4a89f5-2b30-457f-bf13-7580ff162a7e\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/2894ef5c-6622-4488-ab84-c453f5b6e318\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"fcc3b0654525d67d34cc5916b4b6f7351892f650","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:skip","v9.0.0","ci:project-deploy-observability","Team:obs-ux-infra_services"],"number":199306,"url":"https://github.com/elastic/kibana/pull/199306","mergeCommit":{"message":"[ObsUx][Inventory]
Add actions column with link to discover for inventory
(elastic#199306)\n\nCloses elastic#199025 \r\n## Summary\r\n\r\nThis PR adds an
actions column with a link to discover for inventory. It\r\nis available
in the inventory grid(s) in both the group view and the\r\nunified
inventory view.\r\n\r\nThe column header tooltip text will change when
it's
available:\r\n[issue](elastic#199500)
added\r\n\r\n⚠️ If the discover link is not available I added a logic to
hide the\r\nwhole actions column as this is the only available option
for now. Once\r\nwe add more actions we should refactor that to just not
add the action\r\nand to keep the column visible (which doesn't make
sense atm)\r\n\r\n## Testing\r\n- Enable the Inventory\r\n- Check
with/without grouping both the action link and the button\r\n -
combination of kuery / drop-down filter\r\n - without any filters\r\n -
With just one kuery or drop-down filter \r\n- When the link is clicked
from the table we should see a filter by\r\nidentity field in the query
in discover (like `service.name: 'test'`,\r\n`conteainer.id:
'test'`)\r\n
\r\n\r\n\r\nhttps://github.com/user-attachments/assets/bb4a89f5-2b30-457f-bf13-7580ff162a7e\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/2894ef5c-6622-4488-ab84-c453f5b6e318\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"fcc3b0654525d67d34cc5916b4b6f7351892f650"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199306","number":199306,"mergeCommit":{"message":"[ObsUx][Inventory]
Add actions column with link to discover for inventory
(elastic#199306)\n\nCloses elastic#199025 \r\n## Summary\r\n\r\nThis PR adds an
actions column with a link to discover for inventory. It\r\nis available
in the inventory grid(s) in both the group view and the\r\nunified
inventory view.\r\n\r\nThe column header tooltip text will change when
it's
available:\r\n[issue](elastic#199500)
added\r\n\r\n⚠️ If the discover link is not available I added a logic to
hide the\r\nwhole actions column as this is the only available option
for now. Once\r\nwe add more actions we should refactor that to just not
add the action\r\nand to keep the column visible (which doesn't make
sense atm)\r\n\r\n## Testing\r\n- Enable the Inventory\r\n- Check
with/without grouping both the action link and the button\r\n -
combination of kuery / drop-down filter\r\n - without any filters\r\n -
With just one kuery or drop-down filter \r\n- When the link is clicked
from the table we should see a filter by\r\nidentity field in the query
in discover (like `service.name: 'test'`,\r\n`conteainer.id:
'test'`)\r\n
\r\n\r\n\r\nhttps://github.com/user-attachments/assets/bb4a89f5-2b30-457f-bf13-7580ff162a7e\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/2894ef5c-6622-4488-ab84-c453f5b6e318\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"fcc3b0654525d67d34cc5916b4b6f7351892f650"}}]}]
BACKPORT-->

Co-authored-by: jennypavlova <[email protected]>
  • Loading branch information
cauemarcondes and jennypavlova authored Nov 12, 2024
1 parent 6c4b869 commit 94bde2c
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const entityColumnIdsRt = t.union([
t.literal(ENTITY_LAST_SEEN),
t.literal(ENTITY_TYPE),
t.literal('alertsCount'),
t.literal('actions'),
]);

export type EntityColumnIds = t.TypeOf<typeof entityColumnIdsRt>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,22 @@ describe('Home page', () => {
cy.getByTestSubj('inventoryGroup_entity.type_host').should('not.exist');
cy.getByTestSubj('inventoryGroup_entity.type_service').should('not.exist');
});

it('Navigates to discover with actions button in the entities list', () => {
cy.intercept('GET', '/internal/entities/managed/enablement', {
fixture: 'eem_enabled.json',
}).as('getEEMStatus');
cy.visitKibana('/app/inventory');
cy.wait('@getEEMStatus');
cy.contains('container');
cy.getByTestSubj('inventoryGroupTitle_entity.type_container').click();
cy.getByTestSubj('inventoryEntityActionsButton-foo').click();
cy.getByTestSubj('inventoryEntityActionOpenInDiscover').click();
cy.url().should(
'include',
"query:'container.id:%20foo%20AND%20entity.definition_id%20:%20builtin*"
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@ const entityLastSeenLabel = i18n.translate(
defaultMessage: 'Last seen',
}
);
const entityLastSeenToolip = i18n.translate(
const entityLastSeenTooltip = i18n.translate(
'xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip',
{
defaultMessage: 'Timestamp of last received data for entity (entity.lastSeenTimestamp)',
}
);

const entityActionsLabel = i18n.translate('xpack.inventory.entitiesGrid.euiDataGrid.actionsLabel', {
defaultMessage: 'Actions',
});

const CustomHeaderCell = ({ title, tooltipContent }: { title: string; tooltipContent: string }) => (
<>
<span>{title}</span>
Expand All @@ -68,8 +72,10 @@ const CustomHeaderCell = ({ title, tooltipContent }: { title: string; tooltipCon

export const getColumns = ({
showAlertsColumn,
showActions,
}: {
showAlertsColumn: boolean;
showActions: boolean;
}): EuiDataGridColumn[] => {
return [
...(showAlertsColumn
Expand Down Expand Up @@ -103,11 +109,24 @@ export const getColumns = ({
// keep it for accessibility purposes
displayAsText: entityLastSeenLabel,
display: (
<CustomHeaderCell title={entityLastSeenLabel} tooltipContent={entityLastSeenToolip} />
<CustomHeaderCell title={entityLastSeenLabel} tooltipContent={entityLastSeenTooltip} />
),
defaultSortDirection: 'desc',
isSortable: true,
schema: 'datetime',
},
...(showActions
? [
{
id: 'actions',
// keep it for accessibility purposes
displayAsText: entityActionsLabel,
display: (
<CustomHeaderCell title={entityActionsLabel} tooltipContent={entityActionsLabel} />
),
initialWidth: 100,
},
]
: []),
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ 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 { EntityActions } from '../entity_actions';
import { useDiscoverRedirect } from '../../hooks/use_discover_redirect';

type InventoryEntitiesAPIReturnType = APIReturnType<'GET /internal/inventory/entities'>;
type LatestEntities = InventoryEntitiesAPIReturnType['entities'];
Expand Down Expand Up @@ -53,6 +55,8 @@ export function EntitiesGrid({
onChangeSort,
onFilterByType,
}: Props) {
const { getDiscoverRedirectUrl } = useDiscoverRedirect();

const onSort: EuiDataGridSorting['onSort'] = useCallback(
(newSortingColumns) => {
const lastItem = last(newSortingColumns);
Expand All @@ -68,12 +72,14 @@ export function EntitiesGrid({
[entities]
);

const showActions = useMemo(() => !!getDiscoverRedirectUrl(), [getDiscoverRedirectUrl]);

const columnVisibility = useMemo(
() => ({
visibleColumns: getColumns({ showAlertsColumn }).map(({ id }) => id),
visibleColumns: getColumns({ showAlertsColumn, showActions }).map(({ id }) => id),
setVisibleColumns: () => {},
}),
[showAlertsColumn]
[showAlertsColumn, showActions]
);

const renderCellValue = useCallback(
Expand All @@ -85,6 +91,7 @@ export function EntitiesGrid({

const columnEntityTableId = columnId as EntityColumnIds;
const entityType = entity[ENTITY_TYPE];
const discoverUrl = getDiscoverRedirectUrl(entity);

switch (columnEntityTableId) {
case 'alertsCount':
Expand Down Expand Up @@ -127,11 +134,20 @@ export function EntitiesGrid({
);
case ENTITY_DISPLAY_NAME:
return <EntityName entity={entity} />;
case 'actions':
return (
discoverUrl && (
<EntityActions
discoverUrl={discoverUrl}
entityIdentifyingValue={entity[ENTITY_DISPLAY_NAME]}
/>
)
);
default:
return entity[columnId as EntityColumnIds] || '';
}
},
[entities, onFilterByType]
[entities, getDiscoverRedirectUrl, onFilterByType]
);

if (loading) {
Expand All @@ -146,7 +162,7 @@ export function EntitiesGrid({
'xpack.inventory.entitiesGrid.euiDataGrid.inventoryEntitiesGridLabel',
{ defaultMessage: 'Inventory entities grid' }
)}
columns={getColumns({ showAlertsColumn })}
columns={getColumns({ showAlertsColumn, showActions })}
columnVisibility={columnVisibility}
rowCount={entities.length}
renderCellValue={renderCellValue}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useBoolean } from '@kbn/react-hooks';

interface Props {
discoverUrl: string;
entityIdentifyingValue?: string;
}

export const EntityActions = ({ discoverUrl, entityIdentifyingValue }: Props) => {
const [isPopoverOpen, { toggle: togglePopover, off: closePopover }] = useBoolean(false);
const actionButtonTestSubject = entityIdentifyingValue
? `inventoryEntityActionsButton-${entityIdentifyingValue}`
: 'inventoryEntityActionsButton';

const actions = [
<EuiContextMenuItem
data-test-subj="inventoryEntityActionOpenInDiscover"
key={`openInDiscover-${entityIdentifyingValue}`}
color="text"
icon="discoverApp"
href={discoverUrl}
>
{i18n.translate('xpack.inventory.entityActions.discoverLink', {
defaultMessage: 'Open in discover',
})}
</EuiContextMenuItem>,
];

return (
<>
<EuiPopover
isOpen={isPopoverOpen}
panelPaddingSize="none"
anchorPosition="upCenter"
button={
<EuiButtonIcon
data-test-subj={actionButtonTestSubject}
aria-label={i18n.translate(
'xpack.inventory.entityActions.euiButtonIcon.showActionsLabel',
{ defaultMessage: 'Show actions' }
)}
iconType="boxesHorizontal"
color="text"
onClick={togglePopover}
/>
}
closePopover={closePopover}
>
<EuiContextMenuPanel items={actions} size="s" />
</EuiPopover>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,16 @@

import { EuiButton } from '@elastic/eui';
import { DataView } from '@kbn/data-views-plugin/public';
import { buildPhrasesFilter, PhrasesFilter } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';

import {
ENTITY_DEFINITION_ID,
ENTITY_DISPLAY_NAME,
ENTITY_LAST_SEEN,
ENTITY_TYPE,
} from '@kbn/observability-shared-plugin/common';
import { EntityColumnIds } from '../../../common/entities';
import { useInventoryParams } from '../../hooks/use_inventory_params';
import { useKibana } from '../../hooks/use_kibana';

const ACTIVE_COLUMNS: EntityColumnIds[] = [ENTITY_DISPLAY_NAME, ENTITY_TYPE, ENTITY_LAST_SEEN];
import React from 'react';
import { useDiscoverRedirect } from '../../hooks/use_discover_redirect';

export function DiscoverButton({ dataView }: { dataView: DataView }) {
const {
services: { share, application },
} = useKibana();
const {
query: { kuery, entityTypes },
} = useInventoryParams('/*');

const discoverLocator = useMemo(
() => share.url.locators.get('DISCOVER_APP_LOCATOR'),
[share.url.locators]
);

const filters: PhrasesFilter[] = [];

const entityTypeField = dataView.getFieldByName(ENTITY_TYPE);

if (entityTypes && entityTypeField) {
const entityTypeFilter = buildPhrasesFilter(entityTypeField, entityTypes, dataView);
filters.push(entityTypeFilter);
}

const kueryWithEntityDefinitionFilters = [kuery, `${ENTITY_DEFINITION_ID} : builtin*`]
.filter(Boolean)
.join(' AND ');
const { getDiscoverRedirectUrl } = useDiscoverRedirect();

const discoverLink = discoverLocator?.getRedirectUrl({
indexPatternId: dataView?.id ?? '',
columns: ACTIVE_COLUMNS,
query: { query: kueryWithEntityDefinitionFilters, language: 'kuery' },
filters,
});
const discoverLink = getDiscoverRedirectUrl();

if (!application.capabilities.discover?.show || !discoverLink) {
if (!discoverLink) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import React, { useCallback, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Query } from '@kbn/es-query';
import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider';
import { useAdHocInventoryDataView } from '../../hooks/use_adhoc_inventory_data_view';
import { useInventoryParams } from '../../hooks/use_inventory_params';
import { useKibana } from '../../hooks/use_kibana';
import { EntityTypesControls } from './entity_types_controls';
import { DiscoverButton } from './discover_button';
import { getKqlFieldsWithFallback } from '../../utils/get_kql_field_names_with_fallback';

export function SearchBar() {
const { searchBarContentSubject$, refreshSubject$ } = useInventorySearchBarContext();
const { refreshSubject$, searchBarContentSubject$, dataView } = useInventorySearchBarContext();
const {
services: {
unifiedSearch,
Expand All @@ -36,8 +35,6 @@ export function SearchBar() {

const { SearchBar: UnifiedSearchBar } = unifiedSearch.ui;

const { dataView } = useAdHocInventoryDataView();

const syncSearchBarWithUrl = useCallback(() => {
const query = kuery ? { query: kuery, language: 'kuery' } : undefined;
if (query && !deepEqual(queryStringService.getQuery(), query)) {
Expand Down Expand Up @@ -107,7 +104,7 @@ export function SearchBar() {
refreshSubject$.next();
}
},
[entityTypes, registerSearchSubmittedEvent, searchBarContentSubject$, refreshSubject$]
[searchBarContentSubject$, entityTypes, registerSearchSubmittedEvent, refreshSubject$]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
*/
import React, { createContext, useContext, type ReactChild } from 'react';
import { Subject } from 'rxjs';
import { DataView } from '@kbn/data-views-plugin/common';
import { useAdHocInventoryDataView } from '../../hooks/use_adhoc_inventory_data_view';

interface InventorySearchBarContextType {
searchBarContentSubject$: Subject<{
kuery?: string;
entityTypes?: string[];
}>;
refreshSubject$: Subject<void>;
dataView?: DataView;
}

const InventorySearchBarContext = createContext<InventorySearchBarContextType>({
Expand All @@ -21,9 +24,14 @@ const InventorySearchBarContext = createContext<InventorySearchBarContextType>({
});

export function InventorySearchBarContextProvider({ children }: { children: ReactChild }) {
const { dataView } = useAdHocInventoryDataView();
return (
<InventorySearchBarContext.Provider
value={{ searchBarContentSubject$: new Subject(), refreshSubject$: new Subject() }}
value={{
searchBarContentSubject$: new Subject(),
refreshSubject$: new Subject(),
dataView,
}}
>
{children}
</InventorySearchBarContext.Provider>
Expand Down
Loading

0 comments on commit 94bde2c

Please sign in to comment.