[7.5] [Logs UI] Fix chart and table state loss due to loading… (#49606)

Backports the following commits to 7.5:
 - [Logs UI] Fix chart and table state loss due to loading indica… (#49356)
This commit is contained in:
Felix Stürmer 2019-10-29 13:12:25 +01:00 committed by GitHub
parent 7f72b71b34
commit b76f1a4a4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 257 additions and 237 deletions

View file

@ -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;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiLoadingSpinner } from '@elastic/eui';
import { transparentize } from 'polished';
import React from 'react';
import { euiStyled } from '../../../../common/eui_styled_components';
export const LoadingOverlayWrapper: React.FC<
React.HTMLAttributes<HTMLDivElement> & {
isLoading: boolean;
loadingChildren?: React.ReactNode;
}
> = ({ children, isLoading, loadingChildren, ...rest }) => {
return (
<RelativeDiv {...rest}>
{children}
{isLoading ? <Overlay>{loadingChildren}</Overlay> : null}
</RelativeDiv>
);
};
const Overlay: React.FC = ({ children }) => (
<OverlayDiv>{children ? children : <EuiLoadingSpinner size="xl" />}</OverlayDiv>
);
const RelativeDiv = euiStyled.div`
position: relative;
`;
const OverlayDiv = euiStyled.div`
align-items: center;
background-color: ${props => transparentize(0.3, props.theme.eui.euiColorEmptyShade)};
display: flex;
height: 100%;
justify-content: center;
left: 0;
position: absolute;
top: 0;
width: 100%;
`;

View file

@ -6,23 +6,23 @@
import datemath from '@elastic/datemath';
import {
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiPage,
EuiPanel,
EuiSuperDatePicker,
EuiBadge,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import numeral from '@elastic/numeral';
import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment';
import React, { useCallback, useContext, useMemo, useState } from 'react';
import euiStyled from '../../../../../../common/eui_styled_components';
import { TimeRange } from '../../../../common/http_api/shared/time_range';
import { bucketSpan } from '../../../../common/log_analysis';
import { LoadingPage } from '../../../components/loading_page';
import { LoadingOverlayWrapper } from '../../../components/loading_overlay_wrapper';
import {
LogAnalysisJobs,
StringTimeRange,
@ -162,89 +162,77 @@ export const AnalysisResultsContent = ({
);
return (
<>
{isLoading && !logEntryRate ? (
<LoadingPage
message={i18n.translate('xpack.infra.logs.logsAnalysisResults.loadingMessage', {
defaultMessage: 'Loading results...',
})}
/>
) : (
<>
<ResultsContentPage>
<EuiFlexGroup direction="column">
<ResultsContentPage>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiPanel paddingSize="l">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<EuiPanel paddingSize="l">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
{!isLoading && logEntryRate ? (
<EuiText size="s">
<FormattedMessage
id="xpack.infra.logs.analysis.logRateResultsToolbarText"
defaultMessage="Analyzed {numberOfLogs} log entries from {startTime} to {endTime}"
values={{
numberOfLogs: (
<EuiBadge color="primary">
<EuiText size="s" color="ghost">
{numeral(logEntryRate.totalNumberOfLogEntries).format('0.00a')}
</EuiText>
</EuiBadge>
),
startTime: (
<b>{moment(queryTimeRange.value.startTime).format(dateFormat)}</b>
),
endTime: (
<b>{moment(queryTimeRange.value.endTime).format(dateFormat)}</b>
),
}}
/>
</EuiText>
) : null}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSuperDatePicker
start={selectedTimeRange.startTime}
end={selectedTimeRange.endTime}
onTimeChange={handleSelectedTimeRangeChange}
isPaused={autoRefresh.isPaused}
refreshInterval={autoRefresh.interval}
onRefreshChange={handleAutoRefreshChange}
{logEntryRate ? (
<LoadingOverlayWrapper isLoading={isLoading}>
<EuiText size="s">
<FormattedMessage
id="xpack.infra.logs.analysis.logRateResultsToolbarText"
defaultMessage="Analyzed {numberOfLogs} log entries from {startTime} to {endTime}"
values={{
numberOfLogs: (
<EuiBadge color="primary">
<EuiText size="s" color="ghost">
{numeral(logEntryRate.totalNumberOfLogEntries).format('0.00a')}
</EuiText>
</EuiBadge>
),
startTime: (
<b>{moment(queryTimeRange.value.startTime).format(dateFormat)}</b>
),
endTime: <b>{moment(queryTimeRange.value.endTime).format(dateFormat)}</b>,
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiText>
</LoadingOverlayWrapper>
) : null}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel paddingSize="l">
{isFirstUse && !hasResults ? <FirstUseCallout /> : null}
<LogRateResults
isLoading={isLoading}
results={logEntryRate}
setTimeRange={handleChartTimeRangeChange}
timeRange={queryTimeRange.value}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel paddingSize="l">
<AnomaliesResults
isLoading={isLoading}
jobStatus={jobStatus['log-entry-rate']}
viewSetupForReconfiguration={viewSetupForReconfiguration}
viewSetupForUpdate={viewSetupForUpdate}
results={logEntryRate}
setTimeRange={handleChartTimeRangeChange}
setupStatus={setupStatus}
timeRange={queryTimeRange.value}
jobId={jobIds['log-entry-rate']}
/>
</EuiPanel>
<EuiSuperDatePicker
start={selectedTimeRange.startTime}
end={selectedTimeRange.endTime}
onTimeChange={handleSelectedTimeRangeChange}
isPaused={autoRefresh.isPaused}
refreshInterval={autoRefresh.interval}
onRefreshChange={handleAutoRefreshChange}
/>
</EuiFlexItem>
</EuiFlexGroup>
</ResultsContentPage>
</>
)}
</>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel paddingSize="l">
{isFirstUse && !hasResults ? <FirstUseCallout /> : null}
<LogRateResults
isLoading={isLoading}
results={logEntryRate}
setTimeRange={handleChartTimeRangeChange}
timeRange={queryTimeRange.value}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPanel paddingSize="l">
<AnomaliesResults
isLoading={isLoading}
jobStatus={jobStatus['log-entry-rate']}
viewSetupForReconfiguration={viewSetupForReconfiguration}
viewSetupForUpdate={viewSetupForUpdate}
results={logEntryRate}
setTimeRange={handleChartTimeRangeChange}
setupStatus={setupStatus}
timeRange={queryTimeRange.value}
jobId={jobIds['log-entry-rate']}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</ResultsContentPage>
);
};

View file

@ -8,10 +8,10 @@ import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingChart,
EuiSpacer,
EuiStat,
EuiTitle,
EuiLoadingSpinner,
} from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
@ -31,6 +31,7 @@ import { AnomaliesChart } from './chart';
import { AnomaliesTable } from './table';
import { LogAnalysisJobProblemIndicator } from '../../../../../components/logging/log_analysis_job_status';
import { AnalyzeInMlButton } from '../analyze_in_ml_button';
import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper';
export const AnomaliesResults: React.FunctionComponent<{
isLoading: boolean;
@ -53,15 +54,6 @@ export const AnomaliesResults: React.FunctionComponent<{
viewSetupForUpdate,
jobId,
}) => {
const title = i18n.translate('xpack.infra.logs.analysis.anomaliesSectionTitle', {
defaultMessage: 'Anomalies',
});
const loadingAriaLabel = i18n.translate(
'xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel',
{ defaultMessage: 'Loading anomalies' }
);
const hasAnomalies = useMemo(() => {
return results && results.histogramBuckets
? results.histogramBuckets.some(bucket => {
@ -117,90 +109,91 @@ export const AnomaliesResults: React.FunctionComponent<{
onRecreateMlJobForUpdate={viewSetupForUpdate}
/>
<EuiSpacer size="m" />
{isLoading ? (
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<EuiLoadingChart size="xl" aria-label={loadingAriaLabel} />
</EuiFlexItem>
</EuiFlexGroup>
) : !results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
<EuiEmptyPrompt
title={
<h2>
{i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataTitle', {
defaultMessage: 'There is no data to display.',
})}
</h2>
}
titleSize="m"
body={
<p>
{i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataBody', {
defaultMessage: 'You may want to adjust your time range.',
})}
</p>
}
/>
) : !hasAnomalies ? (
<EuiEmptyPrompt
title={
<h2>
{i18n.translate('xpack.infra.logs.analysis.anomalySectionNoAnomaliesTitle', {
defaultMessage: 'No anomalies were detected.',
})}
</h2>
}
titleSize="m"
/>
) : (
<>
<EuiFlexGroup>
<EuiFlexItem grow={8}>
<AnomaliesChart
chartId="overall"
setTimeRange={setTimeRange}
timeRange={timeRange}
series={logEntryRateSeries}
annotations={anomalyAnnotations}
renderAnnotationTooltip={renderAnnotationTooltip}
/>
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiStat
title={numeral(results.totalNumberOfLogEntries).format('0.00a')}
description={i18n.translate(
'xpack.infra.logs.analysis.overallAnomaliesNumberOfLogEntriesDescription',
{
defaultMessage: 'Number of log entries',
}
)}
reverse
/>
<EuiStat
title={topAnomalyScore ? formatAnomalyScore(topAnomalyScore) : null}
description={i18n.translate(
'xpack.infra.logs.analysis.overallAnomaliesTopAnomalyScoreDescription',
{
defaultMessage: 'Max anomaly score',
}
)}
reverse
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="l" />
<AnomaliesTable
results={results}
setTimeRange={setTimeRange}
timeRange={timeRange}
jobId={jobId}
<LoadingOverlayWrapper isLoading={isLoading} loadingChildren={<LoadingOverlayContent />}>
{!results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
<EuiEmptyPrompt
title={
<h2>
{i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataTitle', {
defaultMessage: 'There is no data to display.',
})}
</h2>
}
titleSize="m"
body={
<p>
{i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataBody', {
defaultMessage: 'You may want to adjust your time range.',
})}
</p>
}
/>
</>
)}
) : !hasAnomalies ? (
<EuiEmptyPrompt
title={
<h2>
{i18n.translate('xpack.infra.logs.analysis.anomalySectionNoAnomaliesTitle', {
defaultMessage: 'No anomalies were detected.',
})}
</h2>
}
titleSize="m"
/>
) : (
<>
<EuiFlexGroup>
<EuiFlexItem grow={8}>
<AnomaliesChart
chartId="overall"
setTimeRange={setTimeRange}
timeRange={timeRange}
series={logEntryRateSeries}
annotations={anomalyAnnotations}
renderAnnotationTooltip={renderAnnotationTooltip}
/>
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiStat
title={numeral(results.totalNumberOfLogEntries).format('0.00a')}
description={i18n.translate(
'xpack.infra.logs.analysis.overallAnomaliesNumberOfLogEntriesDescription',
{
defaultMessage: 'Number of log entries',
}
)}
reverse
/>
<EuiStat
title={topAnomalyScore ? formatAnomalyScore(topAnomalyScore) : null}
description={i18n.translate(
'xpack.infra.logs.analysis.overallAnomaliesTopAnomalyScoreDescription',
{
defaultMessage: 'Max anomaly score',
}
)}
reverse
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="l" />
<AnomaliesTable
results={results}
setTimeRange={setTimeRange}
timeRange={timeRange}
jobId={jobId}
/>
</>
)}
</LoadingOverlayWrapper>
</>
);
};
const title = i18n.translate('xpack.infra.logs.analysis.anomaliesSectionTitle', {
defaultMessage: 'Anomalies',
});
interface ParsedAnnotationDetails {
anomalyScoresByPartition: Array<{ partitionId: string; maximumAnomalyScore: number }>;
}
@ -211,6 +204,7 @@ const overallAnomalyScoreLabel = i18n.translate(
defaultMessage: 'Max anomaly scores:',
}
);
const AnnotationTooltip: React.FunctionComponent<{ details: string }> = ({ details }) => {
const parsedDetails: ParsedAnnotationDetails = JSON.parse(details);
return (
@ -245,3 +239,10 @@ const renderAnnotationTooltip = (details?: string) => {
const TooltipWrapper = euiStyled('div')`
white-space: nowrap;
`;
const loadingAriaLabel = i18n.translate(
'xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel',
{ defaultMessage: 'Loading anomalies' }
);
const LoadingOverlayContent = () => <EuiLoadingSpinner size="xl" aria-label={loadingAriaLabel} />;

View file

@ -4,15 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingChart,
EuiSpacer,
EuiTitle,
EuiText,
} from '@elastic/eui';
import { EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
@ -20,6 +12,7 @@ import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/
import { TimeRange } from '../../../../../../common/http_api/shared/time_range';
import { LogEntryRateBarChart } from './bar_chart';
import { getLogEntryRatePartitionedSeries } from '../helpers/data_formatters';
import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper';
export const LogRateResults = ({
isLoading,
@ -32,15 +25,6 @@ export const LogRateResults = ({
setTimeRange: (timeRange: TimeRange) => void;
timeRange: TimeRange;
}) => {
const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', {
defaultMessage: 'Log entries',
});
const loadingAriaLabel = i18n.translate(
'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel',
{ defaultMessage: 'Loading log rate results' }
);
const logEntryRateSeries = useMemo(
() => (results && results.histogramBuckets ? getLogEntryRatePartitionedSeries(results) : []),
[results]
@ -51,57 +35,61 @@ export const LogRateResults = ({
<EuiTitle size="m" aria-label={title}>
<h2>{title}</h2>
</EuiTitle>
{isLoading ? (
<>
<EuiSpacer size="l" />
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<EuiLoadingChart size="xl" aria-label={loadingAriaLabel} />
</EuiFlexItem>
</EuiFlexGroup>
</>
) : !results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
<>
<EuiSpacer size="l" />
<EuiEmptyPrompt
title={
<h2>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataTitle', {
defaultMessage: 'There is no data to display.',
})}
</h2>
}
titleSize="m"
body={
<LoadingOverlayWrapper isLoading={isLoading} loadingChildren={<LoadingOverlayContent />}>
{!results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
<>
<EuiSpacer size="l" />
<EuiEmptyPrompt
title={
<h2>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataTitle', {
defaultMessage: 'There is no data to display.',
})}
</h2>
}
titleSize="m"
body={
<p>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataBody', {
defaultMessage: 'You may want to adjust your time range.',
})}
</p>
}
/>
</>
) : (
<>
<EuiText size="s">
<p>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataBody', {
defaultMessage: 'You may want to adjust your time range.',
<b>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanLabel', {
defaultMessage: 'Bucket span: ',
})}
</b>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanValue', {
defaultMessage: '15 minutes',
})}
</p>
}
/>
</>
) : (
<>
<EuiText size="s">
<p>
<b>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanLabel', {
defaultMessage: 'Bucket span: ',
})}
</b>
{i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanValue', {
defaultMessage: '15 minutes',
})}
</p>
</EuiText>
<LogEntryRateBarChart
setTimeRange={setTimeRange}
timeRange={timeRange}
series={logEntryRateSeries}
/>
</>
)}
</EuiText>
<LogEntryRateBarChart
setTimeRange={setTimeRange}
timeRange={timeRange}
series={logEntryRateSeries}
/>
</>
)}
</LoadingOverlayWrapper>
</>
);
};
const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', {
defaultMessage: 'Log entries',
});
const loadingAriaLabel = i18n.translate(
'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel',
{ defaultMessage: 'Loading log rate results' }
);
const LoadingOverlayContent = () => <EuiLoadingSpinner size="xl" aria-label={loadingAriaLabel} />;

View file

@ -5101,7 +5101,6 @@
"xpack.infra.logs.index.documentTitle": "ログ",
"xpack.infra.logs.index.settingsTabTitle": "設定",
"xpack.infra.logs.index.streamTabTitle": "ストリーム",
"xpack.infra.logs.logsAnalysisResults.loadingMessage": "結果を読み込み中...",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "機械学習ロボットがデータの収集を開始するまでしばらくお待ちください。",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessTitle": "成功!",
"xpack.infra.logs.streamPage.documentTitle": "{previousTitle} | ストリーム",

View file

@ -5102,7 +5102,6 @@
"xpack.infra.logs.index.documentTitle": "Logs",
"xpack.infra.logs.index.settingsTabTitle": "设置",
"xpack.infra.logs.index.streamTabTitle": "流式传输",
"xpack.infra.logs.logsAnalysisResults.loadingMessage": "正在加载结果......",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "请注意,我们的 Machine Learning 机器人若干分钟后才会开始收集数据。",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessTitle": "成功!",
"xpack.infra.logs.streamPage.documentTitle": "{previousTitle} | 流式传输",