[Dataset quality] UI improvements to failed flyout errors table (#208511)

Closes https://github.com/elastic/observability-design/issues/365.

## Summary
This PR aims to improve failure store errors table in the flyout. The
following acceptance criteria items were resolved

### Dataset quality page
- [x] Show upfront 200-208 characters in `message` column.
- [x] Provide link to Discover, filtered by error.type, in `type`
column.

 🎥 Demo 


https://github.com/user-attachments/assets/f318a54e-88d0-4801-af28-14e93a03e39d
This commit is contained in:
Yngrid Coello 2025-02-06 06:31:42 +01:00 committed by GitHub
parent 70b860b9ec
commit 242e319a9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 120 additions and 7 deletions

View file

@ -7,8 +7,10 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiBadge, EuiBasicTableColumn, EuiCode, EuiIcon, EuiToolTip } from '@elastic/eui';
import { EuiBasicTableColumn, EuiIcon, EuiToolTip } from '@elastic/eui';
import { FailedDocsError } from '../../../../../common/api_types';
import { ErrorStacktraceLink } from './error_stacktrace_link';
import { ErrorMessage } from './error_message';
const contentColumnName = i18n.translate(
'xpack.datasetQuality.details.qualityIssue.failedDocs.erros.contentLabel',
@ -36,7 +38,7 @@ export const getFailedDocsErrorsColumns = (): Array<EuiBasicTableColumn<FailedDo
name: contentColumnName,
field: 'message',
render: (_, { message }) => {
return <EuiCode language="js">{message}</EuiCode>;
return <ErrorMessage errorMessage={message} />;
},
},
{
@ -50,11 +52,7 @@ export const getFailedDocsErrorsColumns = (): Array<EuiBasicTableColumn<FailedDo
),
field: 'type',
render: (_, { type }) => {
return (
<EuiBadge color="hollow">
<strong>{type}</strong>
</EuiBadge>
);
return <ErrorStacktraceLink errorType={type} />;
},
sortable: true,
},

View file

@ -0,0 +1,60 @@
/*
* 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 { EuiCode, EuiFlexGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
const readMore = i18n.translate(
'xpack.datasetQuality.details.qualityIssue.failedDocs.erros.message.readMore',
{
defaultMessage: 'Read more',
}
);
const readLess = i18n.translate(
'xpack.datasetQuality.details.qualityIssue.failedDocs.erros.message.readLess',
{
defaultMessage: 'Read less',
}
);
const MAX_CHAR_LENGTH = 200;
const readMoreMessageContent = (message: string) => {
return `${message.slice(0, MAX_CHAR_LENGTH)}...`;
};
export const ErrorMessage = ({ errorMessage }: { errorMessage: string }) => {
const showReadMoreOrLess = errorMessage.length > MAX_CHAR_LENGTH;
const [message, setMessage] = React.useState(
showReadMoreOrLess ? readMoreMessageContent(errorMessage) : errorMessage
);
const handleReadMore = () => {
setMessage((prev) => {
return prev.length === errorMessage.length
? readMoreMessageContent(errorMessage)
: errorMessage;
});
};
return (
<EuiFlexGroup direction="column" gutterSize="none">
<EuiCode language="js" style={{ fontWeight: 'normal' }}>
{message}
</EuiCode>
{showReadMoreOrLess && (
<EuiCode>
<button onClick={handleReadMore} style={{ fontWeight: 'bold' }}>
{message.length === errorMessage.length ? readLess : readMore}
</button>
</EuiCode>
)}
</EuiFlexGroup>
);
};

View file

@ -0,0 +1,54 @@
/*
* 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 { EuiBadge, EuiLink } from '@elastic/eui';
import React from 'react';
import { NavigationSource } from '../../../../services/telemetry';
import { FAILURE_STORE_SELECTOR } from '../../../../../common/constants';
import {
useDatasetDetailsRedirectLinkTelemetry,
useDatasetQualityDetailsState,
useRedirectLink,
} from '../../../../hooks';
export const ErrorStacktraceLink = ({
errorType,
children,
}: {
errorType: string;
children?: React.ReactNode;
}) => {
const { datasetDetails, timeRange } = useDatasetQualityDetailsState();
const query = { language: 'kuery', query: `error.type: "${errorType}"` };
const { sendTelemetry } = useDatasetDetailsRedirectLinkTelemetry({
query,
navigationSource: NavigationSource.FailedDocsFlyoutErrorsTable,
});
const { linkProps } = useRedirectLink({
dataStreamStat: datasetDetails,
query,
sendTelemetry,
timeRangeConfig: timeRange,
selector: FAILURE_STORE_SELECTOR,
});
return (
<EuiBadge color="hollow">
<strong>{errorType}</strong>
<EuiLink
external
{...linkProps}
color="primary"
data-test-subj={`datasetQualityTableDetailsLink-${datasetDetails.name}`}
target="_blank"
>
{children}
</EuiLink>
</EuiBadge>
);
};

View file

@ -38,6 +38,7 @@ export enum NavigationSource {
Table = 'table',
ActionMenu = 'action_menu',
DegradedFieldFlyoutHeader = 'degraded_field_flyout_header',
FailedDocsFlyoutErrorsTable = 'failed_docs_flyout_errors_table',
}
export interface WithTrackingId {