[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:
Alejandro Fernández Gómez 2020-08-26 10:38:54 +02:00 committed by GitHub
parent ddf99b64db
commit 686cde88af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 186 additions and 110 deletions

View file

@ -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';

View file

@ -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]);

View file

@ -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>
);
};

View file

@ -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}
</>
);
};

View file

@ -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

View file

@ -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}