Skip to content

Commit

Permalink
[One Discover] Add row indicators for log documents (elastic#190676)
Browse files Browse the repository at this point in the history
## 📓 Summary

Closes elastic#190457 

This change extracts the logic around the logs document
controls/indicator from Logs Explorer and registers the same controls in
Discover through the extension point added in elastic#188762.

The controls are registered only for the `logs-data-source-profile`
contextual profile.


https://github.com/user-attachments/assets/40adfb19-357f-46e1-9d69-fc9c0860c832

## 👣 Next steps
- [ ] elastic#190670
- [ ] elastic#190460

---------

Co-authored-by: Marco Antonio Ghiani <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored Sep 11, 2024
1 parent c4dd4d1 commit ac66e7c
Show file tree
Hide file tree
Showing 43 changed files with 406 additions and 226 deletions.
2 changes: 2 additions & 0 deletions packages/kbn-discover-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export {
buildDataTableRecord,
buildDataTableRecordList,
createLogsContextService,
createDegradedDocsControl,
createStacktraceControl,
fieldConstants,
formatFieldValue,
formatHit,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiCode, EuiSpacer } from '@elastic/eui';
import {
RowControlColumn,
RowControlComponent,
RowControlProps,
RowControlRowProps,
} from './types';
import { DEGRADED_DOCS_FIELDS } from '../../field_constants';

interface DegradedDocsControlProps extends Partial<RowControlProps> {
enabled?: boolean;
}

/**
* Degraded docs control factory function.
* @param props Optional props for the generated Control component, useful to override onClick, etc
*/
export const createDegradedDocsControl = (props?: DegradedDocsControlProps): RowControlColumn => ({
id: 'connectedDegradedDocs',
headerAriaLabel: actionsHeaderAriaLabelDegradedAction,
renderControl: (Control, rowProps) => {
return <DegradedDocs Control={Control} rowProps={rowProps} {...props} />;
},
});

const actionsHeaderAriaLabelDegradedAction = i18n.translate(
'discover.customControl.degradedDocArialLabel',
{ defaultMessage: 'Access to degraded docs' }
);

const degradedDocButtonLabelWhenPresent = i18n.translate(
'discover.customControl.degradedDocPresent',
{
defaultMessage:
"This document couldn't be parsed correctly. Not all fields are properly populated",
}
);

const degradedDocButtonLabelWhenNotPresent = i18n.translate(
'discover.customControl.degradedDocNotPresent',
{ defaultMessage: 'All fields in this document were parsed correctly' }
);

const degradedDocButtonLabelWhenDisabled = i18n.translate(
'discover.customControl.degradedDocDisabled',
{
defaultMessage:
'Degraded document field detection is currently disabled for this search. To enable it, include the METADATA directive for the `_ignored` field in your ES|QL query. For example:',
}
);

const DegradedDocs = ({
Control,
enabled = true,
rowProps: { record },
...props
}: {
Control: RowControlComponent;
rowProps: RowControlRowProps;
} & DegradedDocsControlProps) => {
const isDegradedDocumentExists = DEGRADED_DOCS_FIELDS.some(
(field) => field in record.raw && record.raw[field] !== null
);

if (!enabled) {
const codeSample = 'FROM logs-* METADATA _ignored';

const tooltipContent = (
<div>
{degradedDocButtonLabelWhenDisabled}
<EuiSpacer size="s" />
<EuiCode>{codeSample}</EuiCode>
</div>
);

return (
<Control
disabled
data-test-subj="docTableDegradedDocDisabled"
tooltipContent={tooltipContent}
label={`${degradedDocButtonLabelWhenDisabled} ${codeSample}`}
iconType="indexClose"
onClick={undefined}
{...props}
/>
);
}

return isDegradedDocumentExists ? (
<Control
data-test-subj="docTableDegradedDocExist"
color="danger"
tooltipContent={degradedDocButtonLabelWhenPresent}
label={degradedDocButtonLabelWhenPresent}
iconType="indexClose"
onClick={undefined}
{...props}
/>
) : (
<Control
data-test-subj="docTableDegradedDocDoesNotExist"
color="text"
tooltipContent={degradedDocButtonLabelWhenNotPresent}
label={degradedDocButtonLabelWhenNotPresent}
iconType="indexClose"
onClick={undefined}
{...props}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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".
*/

export { createDegradedDocsControl } from './degraded_docs_control';
export { createStacktraceControl } from './stacktrace_control';
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import {
RowControlColumn,
RowControlComponent,
RowControlProps,
RowControlRowProps,
} from './types';
import { LogDocument } from '../../data_types';
import { getStacktraceFields } from '../../utils/get_stack_trace_fields';

/**
* Stacktrace control factory function.
* @param props Optional props for the generated Control component, useful to override onClick, etc
*/
export const createStacktraceControl = (props?: Partial<RowControlProps>): RowControlColumn => ({
id: 'connectedStacktraceDocs',
headerAriaLabel: actionsHeaderAriaLabelStacktraceAction,
renderControl: (Control, rowProps) => {
return <Stacktrace Control={Control} rowProps={rowProps} {...props} />;
},
});

const actionsHeaderAriaLabelStacktraceAction = i18n.translate(
'discover.customControl.stacktraceArialLabel',
{ defaultMessage: 'Access to available stacktraces' }
);

const stacktraceAvailableControlButton = i18n.translate(
'discover.customControl.stacktrace.available',
{ defaultMessage: 'Stacktraces available' }
);

const stacktraceNotAvailableControlButton = i18n.translate(
'discover.customControl.stacktrace.notAvailable',
{ defaultMessage: 'Stacktraces not available' }
);

const Stacktrace = ({
Control,
rowProps: { record },
...props
}: {
Control: RowControlComponent;
rowProps: RowControlRowProps;
} & Partial<RowControlProps>) => {
const stacktrace = getStacktraceFields(record as LogDocument);
const hasValue = Object.values(stacktrace).some(Boolean);

return hasValue ? (
<Control
data-test-subj="docTableStacktraceExist"
label={stacktraceAvailableControlButton}
tooltipContent={stacktraceAvailableControlButton}
iconType="apmTrace"
onClick={undefined}
{...props}
/>
) : (
<Control
disabled
data-test-subj="docTableStacktraceDoesNotExist"
label={stacktraceNotAvailableControlButton}
tooltipContent={stacktraceNotAvailableControlButton}
iconType="apmTrace"
onClick={undefined}
{...props}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 { EuiButtonIconProps, EuiDataGridControlColumn, IconType } from '@elastic/eui';
import React, { FC, ReactElement } from 'react';
import { DataTableRecord } from '../../types';

export interface RowControlRowProps {
rowIndex: number;
record: DataTableRecord;
}

export interface RowControlProps {
'data-test-subj'?: string;
color?: EuiButtonIconProps['color'];
disabled?: boolean;
label: string;
iconType: IconType;
onClick: ((props: RowControlRowProps) => void) | undefined;
tooltipContent?: React.ReactNode;
}

export type RowControlComponent = FC<RowControlProps>;

export interface RowControlColumn {
id: string;
headerAriaLabel: string;
headerCellRender?: EuiDataGridControlColumn['headerCellRender'];
renderControl: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement;
}
2 changes: 1 addition & 1 deletion packages/kbn-discover-utils/src/field_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const CONTAINER_NAME_FIELD = 'container.name';
export const CONTAINER_ID_FIELD = 'container.id';

// Degraded Docs
export const DEGRADED_DOCS_FIELD = 'ignored_field_values';
export const DEGRADED_DOCS_FIELDS = ['ignored_field_values', '_ignored'] as const;

// Error Stacktrace
export const ERROR_STACK_TRACE = 'error.stack_trace';
Expand Down
2 changes: 2 additions & 0 deletions packages/kbn-discover-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export * as fieldConstants from './field_constants';
export * from './hooks';
export * from './utils';
export * from './data_types';

export * from './components/custom_control_columns';
6 changes: 6 additions & 0 deletions packages/kbn-discover-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import type { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'
import type { DatatableColumnMeta } from '@kbn/expressions-plugin/common';

export type { IgnoredReason, ShouldShowFieldInTableHandler } from './utils';
export type {
RowControlColumn,
RowControlComponent,
RowControlProps,
RowControlRowProps,
} from './components/custom_control_columns/types';

type DiscoverSearchHit = SearchHit<Record<string, unknown>>;

Expand Down
27 changes: 27 additions & 0 deletions packages/kbn-discover-utils/src/utils/get_stack_trace_fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { getFieldFromDoc, LogDocument, StackTraceFields } from '..';
import {
ERROR_EXCEPTION_STACKTRACE,
ERROR_LOG_STACKTRACE,
ERROR_STACK_TRACE,
} from '../field_constants';

export const getStacktraceFields = (doc: LogDocument): StackTraceFields => {
const errorStackTrace = getFieldFromDoc(doc, ERROR_STACK_TRACE);
const errorExceptionStackTrace = getFieldFromDoc(doc, ERROR_EXCEPTION_STACKTRACE);
const errorLogStackTrace = getFieldFromDoc(doc, ERROR_LOG_STACKTRACE);

return {
[ERROR_STACK_TRACE]: errorStackTrace,
[ERROR_EXCEPTION_STACKTRACE]: errorExceptionStackTrace,
[ERROR_LOG_STACKTRACE]: errorLogStackTrace,
};
};
2 changes: 2 additions & 0 deletions packages/kbn-discover-utils/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
*/

export * from './build_data_record';
export * from './calc_field_counts';
export * from './format_hit';
export * from './format_value';
export * from './get_doc_id';
export * from './get_ignored_reason';
export * from './get_log_document_overview';
export * from './get_message_field_with_fallbacks';
export * from './get_should_show_field_handler';
export * from './get_stack_trace_fields';
export * from './nested_fields';
export * from './get_field_value';
export * from './calc_field_counts';
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-discover-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.tsx"
],
"exclude": [
"target/**/*"
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-esql-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ This package contains utilities for ES|QL.
- *removeDropCommandsFromESQLQuery*: Use this function to remove all the occurences of the `drop` command from the query.
- *appendToESQLQuery*: Use this function to append more pipes in an existing ES|QL query. It adds the additional commands in a new line.
- *appendWhereClauseToESQLQuery*: Use this function to append where clause in an existing query.
- *retieveMetadataColumns*: Use this function to get if there is a metadata option in the from command, and retrieve the columns if so
- *retrieveMetadataColumns*: Use this function to get if there is a metadata option in the from command, and retrieve the columns if so
2 changes: 1 addition & 1 deletion packages/kbn-esql-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export {
hasStartEndParams,
prettifyQuery,
isQueryWrappedByPipes,
retieveMetadataColumns,
retrieveMetadataColumns,
TextBasedLanguages,
} from './src';

Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-esql-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export {
getTimeFieldFromESQLQuery,
prettifyQuery,
isQueryWrappedByPipes,
retieveMetadataColumns,
retrieveMetadataColumns,
} from './utils/query_parsing_helpers';
export { appendToESQLQuery, appendWhereClauseToESQLQuery } from './utils/append_to_query';
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
getTimeFieldFromESQLQuery,
prettifyQuery,
isQueryWrappedByPipes,
retieveMetadataColumns,
retrieveMetadataColumns,
} from './query_parsing_helpers';

describe('esql query helpers', () => {
Expand Down Expand Up @@ -217,16 +217,16 @@ describe('esql query helpers', () => {
});
});

describe('retieveMetadataColumns', () => {
describe('retrieveMetadataColumns', () => {
it('should return metadata columns if they exist', () => {
expect(retieveMetadataColumns('from a metadata _id, _ignored | eval b = 1')).toStrictEqual([
expect(retrieveMetadataColumns('from a metadata _id, _ignored | eval b = 1')).toStrictEqual([
'_id',
'_ignored',
]);
});

it('should return empty columns if metadata doesnt exist', () => {
expect(retieveMetadataColumns('from a | eval b = 1')).toStrictEqual([]);
expect(retrieveMetadataColumns('from a | eval b = 1')).toStrictEqual([]);
});
});
});
Loading

0 comments on commit ac66e7c

Please sign in to comment.