Skip to content

Commit

Permalink
[8.x] [Inventory] Entity names redirect on click to respective pages (#…
Browse files Browse the repository at this point in the history
…193602) (#194315)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Inventory] Entity names redirect on click to respective pages
(#193602)](#193602)

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

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

<!--BACKPORT [{"author":{"name":"Gonçalo Rica Pais da
Silva","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-27T15:23:12Z","message":"[Inventory]
Entity names redirect on click to respective pages (#193602)\n\n##
Summary\r\n\r\nAdds the ability to click through to the overview pages
for entities on\r\nthe Entity Name cell for the Entity Grid on the new
Inventory
page.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/e712d3ef-370f-4353-a398-2365176eb582\r\n\r\nCloses
#192676 \r\n\r\n### How to test\r\n\r\n- Go to Inventory Page.\r\n-
Click on an Entity Name.\r\n\r\n**Expected**: Should redirect to the
overview page of that Entity,\r\nregardless it is a `host`, `container`,
or `service`.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"9561698109fb8382625a58f43ab9e66fd2a1642a","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.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","v8.16.0"],"title":"[Inventory]
Entity names redirect on click to respective
pages","number":193602,"url":"https://github.com/elastic/kibana/pull/193602","mergeCommit":{"message":"[Inventory]
Entity names redirect on click to respective pages (#193602)\n\n##
Summary\r\n\r\nAdds the ability to click through to the overview pages
for entities on\r\nthe Entity Name cell for the Entity Grid on the new
Inventory
page.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/e712d3ef-370f-4353-a398-2365176eb582\r\n\r\nCloses
#192676 \r\n\r\n### How to test\r\n\r\n- Go to Inventory Page.\r\n-
Click on an Entity Name.\r\n\r\n**Expected**: Should redirect to the
overview page of that Entity,\r\nregardless it is a `host`, `container`,
or `service`.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"9561698109fb8382625a58f43ab9e66fd2a1642a"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193602","number":193602,"mergeCommit":{"message":"[Inventory]
Entity names redirect on click to respective pages (#193602)\n\n##
Summary\r\n\r\nAdds the ability to click through to the overview pages
for entities on\r\nthe Entity Name cell for the Entity Grid on the new
Inventory
page.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/e712d3ef-370f-4353-a398-2365176eb582\r\n\r\nCloses
#192676 \r\n\r\n### How to test\r\n\r\n- Go to Inventory Page.\r\n-
Click on an Entity Name.\r\n\r\n**Expected**: Should redirect to the
overview page of that Entity,\r\nregardless it is a `host`, `container`,
or `service`.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"9561698109fb8382625a58f43ab9e66fd2a1642a"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Gonçalo Rica Pais da Silva <[email protected]>
  • Loading branch information
kibanamachine and Bluefinger authored Sep 27, 2024
1 parent 6df7d26 commit 9b406b4
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import {
ASSET_DETAILS_LOCATOR_ID,
type AssetDetailsLocatorParams,
type ServiceOverviewParams,
} from '@kbn/observability-shared-plugin/common';

import { last } from 'lodash';
import React, { useCallback, useState } from 'react';
import { EntityType } from '../../../common/entities';
Expand All @@ -27,10 +35,14 @@ import {
} from '../../../common/es_fields/entities';
import { APIReturnType } from '../../api';
import { getEntityTypeLabel } from '../../utils/get_entity_type_label';
import { parseServiceParams } from '../../utils/parse_service_params';
import { BadgeFilterWithPopover } from '../badge_filter_with_popover';

type InventoryEntitiesAPIReturnType = APIReturnType<'GET /internal/inventory/entities'>;

type LatestEntities = InventoryEntitiesAPIReturnType['entities'];
type LatestEntity = LatestEntities extends Array<infer Entity> ? Entity : never;

export type EntityColumnIds =
| typeof ENTITY_DISPLAY_NAME
| typeof ENTITY_LAST_SEEN
Expand Down Expand Up @@ -103,7 +115,7 @@ const columns: EuiDataGridColumn[] = [

interface Props {
loading: boolean;
entities: InventoryEntitiesAPIReturnType['entities'];
entities: LatestEntities;
sortDirection: 'asc' | 'desc';
sortField: string;
pageIndex: number;
Expand All @@ -125,6 +137,13 @@ export function EntitiesGrid({
onFilterByType,
}: Props) {
const [visibleColumns, setVisibleColumns] = useState(columns.map(({ id }) => id));
const { services } = useKibana<{ share?: SharePluginStart }>();

const assetDetailsLocator =
services.share?.url.locators.get<AssetDetailsLocatorParams>(ASSET_DETAILS_LOCATOR_ID);

const serviceOverviewLocator =
services.share?.url.locators.get<ServiceOverviewParams>('serviceOverviewLocator');

const onSort: EuiDataGridSorting['onSort'] = useCallback(
(newSortingColumns) => {
Expand All @@ -136,6 +155,31 @@ export function EntitiesGrid({
[onChangeSort]
);

const getEntityRedirectUrl = useCallback(
(entity: LatestEntity) => {
const type = entity[ENTITY_TYPE] as EntityType;

// Any unrecognised types will always return undefined
switch (type) {
case 'host':
case 'container':
return assetDetailsLocator?.getRedirectUrl({
assetId: entity[ENTITY_DISPLAY_NAME],
assetType: type,
});

case 'service':
// For services, the format of the display name is `service.name:service.environment`.
// We just want the first part of the name for the locator.
// TODO: Replace this with a better approach for handling service names. See https://github.com/elastic/kibana/issues/194131
return serviceOverviewLocator?.getRedirectUrl(
parseServiceParams(entity[ENTITY_DISPLAY_NAME])
);
}
},
[assetDetailsLocator, serviceOverviewLocator]
);

const renderCellValue = useCallback(
({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => {
const entity = entities[rowIndex];
Expand Down Expand Up @@ -183,16 +227,19 @@ export function EntitiesGrid({
);
case ENTITY_DISPLAY_NAME:
return (
// TODO: link to the appropriate page based on entity type https://github.com/elastic/kibana/issues/192676
<EuiLink data-test-subj="inventoryCellValueLink" className="eui-textTruncate">
<EuiLink
data-test-subj="inventoryCellValueLink"
className="eui-textTruncate"
href={getEntityRedirectUrl(entity)}
>
{entity[columnEntityTableId]}
</EuiLink>
);
default:
return entity[columnId as EntityColumnIds] || '';
}
},
[entities, onFilterByType]
[entities, onFilterByType, getEntityRedirectUrl]
);

if (loading) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 { parseServiceParams } from './parse_service_params';

describe('parseServiceParams', () => {
it('should return only serviceName with a simple name string', () => {
const params = parseServiceParams('service.name');

expect(params).toEqual({ serviceName: 'service.name' });
});

it('should return both serviceName and environment with a full name string', () => {
const params = parseServiceParams('service.name:service.environment');

expect(params).toEqual({ serviceName: 'service.name', environment: 'service.environment' });
});

it('should ignore multiple colons in the environment portion of the displayName', () => {
const params = parseServiceParams('service.name:synthtrace: service.environment');

expect(params).toEqual({
serviceName: 'service.name',
environment: 'synthtrace: service.environment',
});
});

it('should ignore empty environment names and return only the service.name', () => {
const params = parseServiceParams('service.name:');

expect(params).toEqual({
serviceName: 'service.name',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 type { ServiceOverviewParams } from '@kbn/observability-shared-plugin/common';

/**
* Parses a displayName string with the format `service.name:service.environment`,
* returning a valid `ServiceOverviewParams` object.
* @param displayName A string from a `entity.displayName` field.
* @returns
*/
export const parseServiceParams = (displayName: string): ServiceOverviewParams => {
const separatorIndex = displayName.indexOf(':');

const hasEnvironmentName = separatorIndex !== -1;

const serviceName = hasEnvironmentName ? displayName.slice(0, separatorIndex) : displayName;
// Exclude the separator from the sliced string for the environment name.
// If the string is empty however, then we default to undefined.
const environment = (hasEnvironmentName && displayName.slice(separatorIndex + 1)) || undefined;

return {
serviceName,
environment,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'

export interface ServiceOverviewParams extends SerializableRecord {
serviceName: string;
environment?: string;
rangeFrom?: string;
rangeTo?: string;
}
Expand All @@ -23,8 +24,9 @@ export class ServiceOverviewLocatorDefinition implements LocatorDefinition<Servi
rangeFrom,
rangeTo,
serviceName,
environment,
}: ServiceOverviewParams) => {
const params = { rangeFrom, rangeTo };
const params = { rangeFrom, rangeTo, environment };
return {
app: 'apm',
path: `/services/${serviceName}/overview?${qs.stringify(params)}`,
Expand Down

0 comments on commit 9b406b4

Please sign in to comment.