Skip to content

Commit

Permalink
[One Discover] Custom Service Name Cell (#192381)
Browse files Browse the repository at this point in the history
closes #190456

## πŸ“  Summary

This PR adds the agent icon as a prefix to the service name if an agent
name is available.

## πŸŽ₯ Demo


https://github.com/user-attachments/assets/4fad743a-6806-4440-91eb-fdfa35785a19

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
mohamedhamed-ahmed and kibanamachine authored Sep 15, 2024
1 parent 4797dc4 commit 7b3fa3a
Show file tree
Hide file tree
Showing 11 changed files with 625 additions and 227 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { generateShortId } from '@kbn/apm-synthtrace-client';
import { ELASTIC_AGENT_NAMES } from '@kbn/elastic-agent-utils';
import { faker } from '@faker-js/faker';
import { randomInt } from 'crypto';
import moment from 'moment';
Expand Down Expand Up @@ -65,6 +66,8 @@ export const getGeoCoordinate = (index?: number) => getAtIndexOrRandom(GEO_COORD
export const getCloudProvider = (index?: number) => getAtIndexOrRandom(CLOUD_PROVIDERS, index);
export const getCloudRegion = (index?: number) => getAtIndexOrRandom(CLOUD_REGION, index);
export const getServiceName = (index?: number) => getAtIndexOrRandom(SERVICE_NAMES, index);
export const getAgentName = (index?: number) => getAtIndexOrRandom(ELASTIC_AGENT_NAMES, index);

export const getJavaLog = () =>
`${moment().format('YYYY-MM-DD HH:mm:ss,SSS')} ${getAtIndexOrRandom(
LOG_LEVELS
Expand Down
5 changes: 2 additions & 3 deletions packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getCluster,
getCloudProvider,
getCloudRegion,
getAgentName,
} from './helpers/logs_mock_data';
import { parseLogsScenarioOpts } from './helpers/logs_scenario_opts_parser';

Expand All @@ -44,7 +45,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {

const commonLongEntryFields: LogDocument = {
'trace.id': generateShortId(),
'agent.name': 'nodejs',
'agent.name': getAgentName(),
'orchestrator.cluster.name': clusterName,
'orchestrator.cluster.id': clusterId,
'orchestrator.namespace': namespace,
Expand Down Expand Up @@ -82,7 +83,6 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
.fill(0)
.map(() => {
const {
serviceName,
logMessage: { level, message },
commonLongEntryFields,
} = constructLogsCommonData();
Expand All @@ -91,7 +91,6 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
.create({ isLogsDb })
.message(message.replace('<random>', generateShortId()))
.logLevel(level)
.service(serviceName)
.setGeoLocation(getGeoCoordinate())
.setHostIp(getIpAddress())
.defaults(commonLongEntryFields)
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-apm-synthtrace/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@kbn/datemath",
"@kbn/apm-synthtrace-client",
"@kbn/dev-utils",
"@kbn/elastic-agent-utils",
],
"exclude": [
"target/**/*",
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/discover/common/data_types/logs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ export const DEFAULT_ALLOWED_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'win
export const DEFAULT_ALLOWED_LOGS_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'winlogbeat'];

export const LOG_LEVEL_FIELDS = ['log.level', 'log_level'];
export const SERVICE_NAME_FIELDS = ['service.name', 'service_name'];
export const AGENT_NAME_FIELD = 'agent.name';
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { buildDataTableRecord, DataTableRecord } from '@kbn/discover-utils';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { getServiceNameCell } from './service_name_cell';

const renderCell = (serviceNameField: string, record: DataTableRecord) => {
const ServiceNameCell = getServiceNameCell(serviceNameField);
render(
<ServiceNameCell
rowIndex={0}
colIndex={0}
columnId="service.name"
isExpandable={false}
isExpanded={false}
isDetails={false}
row={record}
dataView={dataViewMock}
fieldFormats={fieldFormatsMock}
setCellProps={() => {}}
closePopover={() => {}}
/>
);
};

describe('getServiceNameCell', () => {
it('renders icon if agent name is recognized', () => {
const record = buildDataTableRecord(
{ fields: { 'service.name': 'test-service', 'agent.name': 'nodejs' } },
dataViewMock
);
renderCell('service.name', record);
expect(screen.getByTestId('serviceNameCell-nodejs')).toBeInTheDocument();
});

it('renders default icon with unknwon test subject if agent name is missing', () => {
const record = buildDataTableRecord(
{ fields: { 'service.name': 'test-service' } },
dataViewMock
);
renderCell('service.name', record);
expect(screen.getByTestId('serviceNameCell-unknown')).toBeInTheDocument();
});

it('does not render if service name is missing', () => {
const record = buildDataTableRecord({ fields: { 'agent.name': 'nodejs' } }, dataViewMock);
renderCell('service.name', record);
expect(screen.queryByTestId('serviceNameCell-nodejs')).not.toBeInTheDocument();
expect(screen.queryByTestId('serviceNameCell-unknown')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
import type { AgentName } from '@kbn/elastic-agent-utils';
import { dynamic } from '@kbn/shared-ux-utility';
import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
import React from 'react';
import { getFieldValue } from '@kbn/discover-utils';
import { AGENT_NAME_FIELD } from '../../../../common/data_types/logs/constants';

const dataTestSubj = 'serviceNameCell';
const AgentIcon = dynamic(() => import('@kbn/custom-icons/src/components/agent_icon'));

export const getServiceNameCell =
(serviceNameField: string) => (props: DataGridCellValueElementProps) => {
const serviceNameValue = getFieldValue(props.row, serviceNameField);
const agentName = getFieldValue(props.row, AGENT_NAME_FIELD) as AgentName;

if (!serviceNameValue) {
return <span data-test-subj={`${dataTestSubj}-empty`}>-</span>;
}

return (
<EuiFlexGroup
gutterSize="xs"
data-test-subj={`${dataTestSubj}-${agentName || 'unknown'}`}
responsive={false}
alignItems="center"
>
<EuiFlexItem grow={false}>
<EuiToolTip position="left" content={agentName} repositionOnScroll={true}>
<AgentIcon agentName={agentName} size="m" />
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>{serviceNameValue}</EuiFlexItem>
</EuiFlexGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { LOG_LEVEL_FIELDS } from '../../../../../../common/data_types/logs/constants';
import {
LOG_LEVEL_FIELDS,
SERVICE_NAME_FIELDS,
} from '../../../../../../common/data_types/logs/constants';
import { getLogLevelBadgeCell } from '../../../../../components/data_types/logs/log_level_badge_cell';
import type { DataSourceProfileProvider } from '../../../../profiles';
import { getServiceNameCell } from '../../../../../components/data_types/logs/service_name_cell';
import { DataSourceProfileProvider } from '../../../../profiles';

export const getCellRenderers: DataSourceProfileProvider['profile']['getCellRenderers'] =
(prev) => () => ({
Expand All @@ -22,4 +26,12 @@ export const getCellRenderers: DataSourceProfileProvider['profile']['getCellRend
}),
{}
),
...SERVICE_NAME_FIELDS.reduce(
(acc, field) => ({
...acc,
[field]: getServiceNameCell(field),
[`${field}.keyword`]: getServiceNameCell(`${field}.keyword`),
}),
{}
),
});
Loading

0 comments on commit 7b3fa3a

Please sign in to comment.