[Logs UI] View log details for anomaly log examples (#75425)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
ddf99b64db
commit
686cde88af
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { LogEntryFlyout } from './log_entry_flyout';
|
||||
export * from './log_entry_flyout';
|
||||
|
|
|
@ -26,12 +26,10 @@ import { InfraLoadingPanel } from '../../loading';
|
|||
import { LogEntryActionsMenu } from './log_entry_actions_menu';
|
||||
import { LogEntriesItem, LogEntriesItemField } from '../../../../common/http_api';
|
||||
|
||||
interface Props {
|
||||
export interface LogEntryFlyoutProps {
|
||||
flyoutItem: LogEntriesItem | null;
|
||||
setFlyoutVisibility: (visible: boolean) => void;
|
||||
setFilter: (filter: string) => void;
|
||||
setTarget: (timeKey: TimeKey, flyoutItemId: string) => void;
|
||||
|
||||
setFilter: (filter: string, flyoutItemId: string, timeKey?: TimeKey) => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
|
@ -40,27 +38,27 @@ export const LogEntryFlyout = ({
|
|||
loading,
|
||||
setFlyoutVisibility,
|
||||
setFilter,
|
||||
setTarget,
|
||||
}: Props) => {
|
||||
}: LogEntryFlyoutProps) => {
|
||||
const createFilterHandler = useCallback(
|
||||
(field: LogEntriesItemField) => () => {
|
||||
const filter = `${field.field}:"${field.value}"`;
|
||||
setFilter(filter);
|
||||
|
||||
if (flyoutItem && flyoutItem.key) {
|
||||
const timestampMoment = moment(flyoutItem.key.time);
|
||||
if (timestampMoment.isValid()) {
|
||||
setTarget(
|
||||
{
|
||||
time: timestampMoment.valueOf(),
|
||||
tiebreaker: flyoutItem.key.tiebreaker,
|
||||
},
|
||||
flyoutItem.id
|
||||
);
|
||||
}
|
||||
if (!flyoutItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filter = `${field.field}:"${field.value}"`;
|
||||
const timestampMoment = moment(flyoutItem.key.time);
|
||||
let target;
|
||||
|
||||
if (timestampMoment.isValid()) {
|
||||
target = {
|
||||
time: timestampMoment.valueOf(),
|
||||
tiebreaker: flyoutItem.key.tiebreaker,
|
||||
};
|
||||
}
|
||||
|
||||
setFilter(filter, flyoutItem.id, target);
|
||||
},
|
||||
[flyoutItem, setFilter, setTarget]
|
||||
[flyoutItem, setFilter]
|
||||
);
|
||||
|
||||
const closeFlyout = useCallback(() => setFlyoutVisibility(false), [setFlyoutVisibility]);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_a
|
|||
import { LogEntryRateModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
|
||||
import { useLogSourceContext } from '../../../containers/logs/log_source';
|
||||
import { useActiveKibanaSpace } from '../../../hooks/use_kibana_space';
|
||||
import { LogFlyout } from '../../../containers/logs/log_flyout';
|
||||
|
||||
export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const { sourceId, sourceConfiguration } = useLogSourceContext();
|
||||
|
@ -23,20 +24,22 @@ export const LogEntryRatePageProviders: React.FunctionComponent = ({ children })
|
|||
}
|
||||
|
||||
return (
|
||||
<LogEntryRateModuleProvider
|
||||
indexPattern={sourceConfiguration?.configuration.logAlias ?? ''}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={sourceConfiguration?.configuration.fields.timestamp ?? ''}
|
||||
>
|
||||
<LogEntryCategoriesModuleProvider
|
||||
<LogFlyout.Provider>
|
||||
<LogEntryRateModuleProvider
|
||||
indexPattern={sourceConfiguration?.configuration.logAlias ?? ''}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={sourceConfiguration?.configuration.fields.timestamp ?? ''}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
</LogEntryRateModuleProvider>
|
||||
<LogEntryCategoriesModuleProvider
|
||||
indexPattern={sourceConfiguration?.configuration.logAlias ?? ''}
|
||||
sourceId={sourceId}
|
||||
spaceId={space.id}
|
||||
timestampField={sourceConfiguration?.configuration.fields.timestamp ?? ''}
|
||||
>
|
||||
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
|
||||
</LogEntryCategoriesModuleProvider>
|
||||
</LogEntryRateModuleProvider>
|
||||
</LogFlyout.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
import datemath from '@elastic/datemath';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel, EuiSuperDatePicker } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { encode, RisonValue } from 'rison-node';
|
||||
import { stringify } from 'query-string';
|
||||
import React, { useCallback, useEffect, useMemo, useState, useContext } from 'react';
|
||||
import { euiStyled, useTrackPageview } from '../../../../../observability/public';
|
||||
import { TimeRange } from '../../../../common/http_api/shared/time_range';
|
||||
import { bucketSpan } from '../../../../common/log_analysis';
|
||||
|
@ -29,6 +31,9 @@ import {
|
|||
StringTimeRange,
|
||||
useLogAnalysisResultsUrlState,
|
||||
} from './use_log_entry_rate_results_url_state';
|
||||
import { LogEntryFlyout, LogEntryFlyoutProps } from '../../../components/logging/log_entry_flyout';
|
||||
import { LogFlyout } from '../../../containers/logs/log_flyout';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
export const SORT_DEFAULTS = {
|
||||
direction: 'desc' as const,
|
||||
|
@ -42,6 +47,7 @@ export const PAGINATION_DEFAULTS = {
|
|||
export const LogEntryRateResultsContent: React.FunctionComponent = () => {
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results' });
|
||||
useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results', delay: 15000 });
|
||||
const navigateToApp = useKibana().services.application?.navigateToApp;
|
||||
|
||||
const { sourceId } = useLogSourceContext();
|
||||
|
||||
|
@ -79,6 +85,30 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => {
|
|||
lastChangedTime: Date.now(),
|
||||
}));
|
||||
|
||||
const linkToLogStream = useCallback<LogEntryFlyoutProps['setFilter']>(
|
||||
(filter, id, timeKey) => {
|
||||
const params = {
|
||||
logPosition: encode({
|
||||
end: moment(queryTimeRange.value.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
|
||||
position: timeKey as RisonValue,
|
||||
start: moment(queryTimeRange.value.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
|
||||
streamLive: false,
|
||||
}),
|
||||
flyoutOptions: encode({
|
||||
surroundingLogsId: id,
|
||||
}),
|
||||
logFilter: encode({
|
||||
expression: filter,
|
||||
kind: 'kuery',
|
||||
}),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
navigateToApp?.('logs', { path: `/stream?${stringify(params)}` });
|
||||
},
|
||||
[queryTimeRange, navigateToApp]
|
||||
);
|
||||
|
||||
const bucketDuration = useMemo(
|
||||
() => getBucketDuration(queryTimeRange.value.startTime, queryTimeRange.value.endTime),
|
||||
[queryTimeRange.value.endTime, queryTimeRange.value.startTime]
|
||||
|
@ -115,6 +145,10 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => {
|
|||
filteredDatasets: selectedDatasets,
|
||||
});
|
||||
|
||||
const { flyoutVisible, setFlyoutVisibility, flyoutItem, isLoading: isFlyoutLoading } = useContext(
|
||||
LogFlyout.Context
|
||||
);
|
||||
|
||||
const handleQueryTimeRangeChange = useCallback(
|
||||
({ start: startTime, end: endTime }: { start: string; end: string }) => {
|
||||
setQueryTimeRange({
|
||||
|
@ -198,75 +232,86 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<ResultsContentPage>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<DatasetsSelector
|
||||
availableDatasets={datasets}
|
||||
isLoading={isLoadingDatasets}
|
||||
selectedDatasets={selectedDatasets}
|
||||
onChangeDatasetSelection={setSelectedDatasets}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSuperDatePicker
|
||||
start={selectedTimeRange.startTime}
|
||||
end={selectedTimeRange.endTime}
|
||||
onTimeChange={handleSelectedTimeRangeChange}
|
||||
isPaused={autoRefresh.isPaused}
|
||||
refreshInterval={autoRefresh.interval}
|
||||
onRefreshChange={handleAutoRefreshChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogAnalysisJobProblemIndicator
|
||||
hasOutdatedJobConfigurations={hasOutdatedLogEntryRateJobConfigurations}
|
||||
hasOutdatedJobDefinitions={hasOutdatedLogEntryRateJobDefinitions}
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
hasStoppedJobs={hasStoppedLogEntryRateJobs}
|
||||
isFirstUse={false /* the first use message is already shown by the section below */}
|
||||
moduleName={logEntryRateModuleDescriptor.moduleName}
|
||||
onRecreateMlJobForReconfiguration={showLogEntryRateSetup}
|
||||
onRecreateMlJobForUpdate={showLogEntryRateSetup}
|
||||
/>
|
||||
<CategoryJobNoticesSection
|
||||
hasOutdatedJobConfigurations={hasOutdatedLogEntryCategoriesJobConfigurations}
|
||||
hasOutdatedJobDefinitions={hasOutdatedLogEntryCategoriesJobDefinitions}
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
hasStoppedJobs={hasStoppedLogEntryCategoriesJobs}
|
||||
isFirstUse={isFirstUse}
|
||||
moduleName={logEntryCategoriesModuleDescriptor.moduleName}
|
||||
onRecreateMlJobForReconfiguration={showLogEntryCategoriesSetup}
|
||||
onRecreateMlJobForUpdate={showLogEntryCategoriesSetup}
|
||||
qualityWarnings={categoryQualityWarnings}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel paddingSize="m">
|
||||
<AnomaliesResults
|
||||
isLoadingLogRateResults={isLoading}
|
||||
isLoadingAnomaliesResults={isLoadingLogEntryAnomalies}
|
||||
onViewModuleList={showModuleList}
|
||||
logEntryRateResults={logEntryRate}
|
||||
anomalies={logEntryAnomalies}
|
||||
setTimeRange={handleChartTimeRangeChange}
|
||||
timeRange={queryTimeRange.value}
|
||||
page={page}
|
||||
fetchNextPage={fetchNextPage}
|
||||
fetchPreviousPage={fetchPreviousPage}
|
||||
changeSortOptions={changeSortOptions}
|
||||
changePaginationOptions={changePaginationOptions}
|
||||
sortOptions={sortOptions}
|
||||
paginationOptions={paginationOptions}
|
||||
<>
|
||||
<ResultsContentPage>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<DatasetsSelector
|
||||
availableDatasets={datasets}
|
||||
isLoading={isLoadingDatasets}
|
||||
selectedDatasets={selectedDatasets}
|
||||
onChangeDatasetSelection={setSelectedDatasets}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSuperDatePicker
|
||||
start={selectedTimeRange.startTime}
|
||||
end={selectedTimeRange.endTime}
|
||||
onTimeChange={handleSelectedTimeRangeChange}
|
||||
isPaused={autoRefresh.isPaused}
|
||||
refreshInterval={autoRefresh.interval}
|
||||
onRefreshChange={handleAutoRefreshChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogAnalysisJobProblemIndicator
|
||||
hasOutdatedJobConfigurations={hasOutdatedLogEntryRateJobConfigurations}
|
||||
hasOutdatedJobDefinitions={hasOutdatedLogEntryRateJobDefinitions}
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
hasStoppedJobs={hasStoppedLogEntryRateJobs}
|
||||
isFirstUse={false /* the first use message is already shown by the section below */}
|
||||
moduleName={logEntryRateModuleDescriptor.moduleName}
|
||||
onRecreateMlJobForReconfiguration={showLogEntryRateSetup}
|
||||
onRecreateMlJobForUpdate={showLogEntryRateSetup}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</ResultsContentPage>
|
||||
<CategoryJobNoticesSection
|
||||
hasOutdatedJobConfigurations={hasOutdatedLogEntryCategoriesJobConfigurations}
|
||||
hasOutdatedJobDefinitions={hasOutdatedLogEntryCategoriesJobDefinitions}
|
||||
hasSetupCapabilities={hasLogAnalysisSetupCapabilities}
|
||||
hasStoppedJobs={hasStoppedLogEntryCategoriesJobs}
|
||||
isFirstUse={isFirstUse}
|
||||
moduleName={logEntryCategoriesModuleDescriptor.moduleName}
|
||||
onRecreateMlJobForReconfiguration={showLogEntryCategoriesSetup}
|
||||
onRecreateMlJobForUpdate={showLogEntryCategoriesSetup}
|
||||
qualityWarnings={categoryQualityWarnings}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel paddingSize="m">
|
||||
<AnomaliesResults
|
||||
isLoadingLogRateResults={isLoading}
|
||||
isLoadingAnomaliesResults={isLoadingLogEntryAnomalies}
|
||||
onViewModuleList={showModuleList}
|
||||
logEntryRateResults={logEntryRate}
|
||||
anomalies={logEntryAnomalies}
|
||||
setTimeRange={handleChartTimeRangeChange}
|
||||
timeRange={queryTimeRange.value}
|
||||
page={page}
|
||||
fetchNextPage={fetchNextPage}
|
||||
fetchPreviousPage={fetchPreviousPage}
|
||||
changeSortOptions={changeSortOptions}
|
||||
changePaginationOptions={changePaginationOptions}
|
||||
sortOptions={sortOptions}
|
||||
paginationOptions={paginationOptions}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</ResultsContentPage>
|
||||
|
||||
{flyoutVisible ? (
|
||||
<LogEntryFlyout
|
||||
flyoutItem={flyoutItem}
|
||||
setFlyoutVisibility={setFlyoutVisibility}
|
||||
loading={isFlyoutLoading}
|
||||
setFilter={linkToLogStream}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useCallback, useState } from 'react';
|
||||
import React, { useMemo, useCallback, useState, useContext } from 'react';
|
||||
import moment from 'moment';
|
||||
import { encode } from 'rison-node';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -37,6 +37,7 @@ import {
|
|||
} from '../../../../../utils/source_configuration';
|
||||
import { localizedDate } from '../../../../../../common/formatters/datetime';
|
||||
import { LogEntryAnomaly } from '../../../../../../common/http_api';
|
||||
import { LogFlyout } from '../../../../../containers/logs/log_flyout';
|
||||
|
||||
export const exampleMessageScale = 'medium' as const;
|
||||
export const exampleTimestampFormat = 'time' as const;
|
||||
|
@ -45,6 +46,13 @@ const MENU_LABEL = i18n.translate('xpack.infra.logAnomalies.logEntryExamplesMenu
|
|||
defaultMessage: 'View actions for log entry',
|
||||
});
|
||||
|
||||
const VIEW_DETAILS_LABEL = i18n.translate(
|
||||
'xpack.infra.logs.analysis.logEntryExamplesViewDetailsLabel',
|
||||
{
|
||||
defaultMessage: 'View details',
|
||||
}
|
||||
);
|
||||
|
||||
const VIEW_IN_STREAM_LABEL = i18n.translate(
|
||||
'xpack.infra.logs.analysis.logEntryExamplesViewInStreamLabel',
|
||||
{
|
||||
|
@ -80,6 +88,8 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
|
|||
const setItemIsHovered = useCallback(() => setIsHovered(true), []);
|
||||
const setItemIsNotHovered = useCallback(() => setIsHovered(false), []);
|
||||
|
||||
const { setFlyoutVisibility, setFlyoutId } = useContext(LogFlyout.Context);
|
||||
|
||||
// handle special cases for the dataset value
|
||||
const humanFriendlyDataset = getFriendlyNameForPartitionId(dataset);
|
||||
|
||||
|
@ -116,6 +126,13 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
|
|||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: VIEW_DETAILS_LABEL,
|
||||
onClick: () => {
|
||||
setFlyoutId(id);
|
||||
setFlyoutVisibility(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: VIEW_IN_STREAM_LABEL,
|
||||
onClick: viewInStreamLinkProps.onClick,
|
||||
|
@ -127,7 +144,13 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
|
|||
href: viewAnomalyInMachineLearningLinkProps.href,
|
||||
},
|
||||
];
|
||||
}, [viewInStreamLinkProps, viewAnomalyInMachineLearningLinkProps]);
|
||||
}, [
|
||||
id,
|
||||
setFlyoutId,
|
||||
setFlyoutVisibility,
|
||||
viewInStreamLinkProps,
|
||||
viewAnomalyInMachineLearningLinkProps,
|
||||
]);
|
||||
|
||||
return (
|
||||
<LogEntryRowWrapper
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useCallback } from 'react';
|
||||
import { euiStyled } from '../../../../../observability/public';
|
||||
import { AutoSizer } from '../../../components/auto_sizer';
|
||||
import { LogEntryFlyout } from '../../../components/logging/log_entry_flyout';
|
||||
|
@ -57,6 +57,18 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
|
|||
|
||||
const [, { setContextEntry }] = useContext(ViewLogInContext.Context);
|
||||
|
||||
const setFilter = useCallback(
|
||||
(filter, flyoutItemId, timeKey) => {
|
||||
applyLogFilterQuery(filter);
|
||||
if (timeKey) {
|
||||
jumpToTargetPosition(timeKey);
|
||||
}
|
||||
setSurroundingLogsId(flyoutItemId);
|
||||
stopLiveStreaming();
|
||||
},
|
||||
[applyLogFilterQuery, jumpToTargetPosition, setSurroundingLogsId, stopLiveStreaming]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithLogTextviewUrlState />
|
||||
|
@ -65,12 +77,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
|
|||
<PageViewLogInContext />
|
||||
{flyoutVisible ? (
|
||||
<LogEntryFlyout
|
||||
setFilter={applyLogFilterQuery}
|
||||
setTarget={(timeKey, flyoutItemId) => {
|
||||
jumpToTargetPosition(timeKey);
|
||||
setSurroundingLogsId(flyoutItemId);
|
||||
stopLiveStreaming();
|
||||
}}
|
||||
setFilter={setFilter}
|
||||
setFlyoutVisibility={setFlyoutVisibility}
|
||||
flyoutItem={flyoutItem}
|
||||
loading={isLoading}
|
||||
|
|
Loading…
Reference in a new issue