Skip to content

Commit

Permalink
[Logs UI] move ML job setup UI to a flyout (#68366)
Browse files Browse the repository at this point in the history
Co-authored-by: Felix Stürmer <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
3 people authored Jul 6, 2020
1 parent 81bd66d commit b172b5b
Show file tree
Hide file tree
Showing 12 changed files with 411 additions and 169 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/infra/common/log_analysis/log_analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type SetupStatus =
| {
type: 'skipped';
newlyCreated?: boolean;
}; // setup is hidden
}; // setup is not necessary

/**
* Maps a job status to the possibility that results have already been produced
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ const getSetupStatus = <JobType extends string>(everyJobStatus: Record<JobType,
Object.entries<JobStatus>(everyJobStatus).reduce<SetupStatus>((setupStatus, [, jobStatus]) => {
if (jobStatus === 'missing') {
return { type: 'required', reason: 'missing' };
} else if (setupStatus.type === 'required') {
} else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') {
return setupStatus;
} else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/

import { i18n } from '@kbn/i18n';
import React, { useEffect } from 'react';
import { isSetupStatusWithResults } from '../../../../common/log_analysis';
import React, { useEffect, useState, useCallback } from 'react';
import { isJobStatusWithResults } from '../../../../common/log_analysis';
import { LoadingPage } from '../../../components/loading_page';
import {
LogAnalysisSetupStatusUnknownPrompt,
Expand All @@ -21,6 +21,7 @@ import { useLogSourceContext } from '../../../containers/logs/log_source';
import { LogEntryCategoriesResultsContent } from './page_results_content';
import { LogEntryCategoriesSetupContent } from './page_setup_content';
import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module';
import { LogEntryCategoriesSetupFlyout } from './setup_flyout';

export const LogEntryCategoriesPageContent = () => {
const {
Expand All @@ -37,14 +38,25 @@ export const LogEntryCategoriesPageContent = () => {
hasLogAnalysisSetupCapabilities,
} = useLogAnalysisCapabilitiesContext();

const { fetchJobStatus, setupStatus } = useLogEntryCategoriesModuleContext();
const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryCategoriesModuleContext();

const [isFlyoutOpen, setIsFlyoutOpen] = useState<boolean>(false);
const openFlyout = useCallback(() => setIsFlyoutOpen(true), []);
const closeFlyout = useCallback(() => setIsFlyoutOpen(false), []);

useEffect(() => {
if (hasLogAnalysisReadCapabilities) {
fetchJobStatus();
}
}, [fetchJobStatus, hasLogAnalysisReadCapabilities]);

// Open flyout if there are no ML jobs
useEffect(() => {
if (setupStatus.type === 'required' && setupStatus.reason === 'missing') {
openFlyout();
}
}, [setupStatus, openFlyout]);

if (isLoading || isUninitialized) {
return <SourceLoadingPage />;
} else if (hasFailedLoadingSource) {
Expand All @@ -63,11 +75,21 @@ export const LogEntryCategoriesPageContent = () => {
);
} else if (setupStatus.type === 'unknown') {
return <LogAnalysisSetupStatusUnknownPrompt retry={fetchJobStatus} />;
} else if (isSetupStatusWithResults(setupStatus)) {
return <LogEntryCategoriesResultsContent />;
} else if (isJobStatusWithResults(jobStatus['log-entry-categories-count'])) {
return (
<>
<LogEntryCategoriesResultsContent onOpenSetup={openFlyout} />
<LogEntryCategoriesSetupFlyout isOpen={isFlyoutOpen} onClose={closeFlyout} />
</>
);
} else if (!hasLogAnalysisSetupCapabilities) {
return <MissingSetupPrivilegesPrompt />;
} else {
return <LogEntryCategoriesSetupContent />;
return (
<>
<LogEntryCategoriesSetupContent onOpenSetup={openFlyout} />
<LogEntryCategoriesSetupFlyout isOpen={isFlyoutOpen} onClose={closeFlyout} />
</>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import {

const JOB_STATUS_POLLING_INTERVAL = 30000;

export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => {
interface LogEntryCategoriesResultsContentProps {
onOpenSetup: () => void;
}

export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryCategoriesResultsContentProps> = ({
onOpenSetup,
}) => {
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' });
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 });

Expand Down Expand Up @@ -123,12 +129,25 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => {
[setAutoRefresh]
);

const viewSetupFlyoutForReconfiguration = useCallback(() => {
viewSetupForReconfiguration();
onOpenSetup();
}, [onOpenSetup, viewSetupForReconfiguration]);

const viewSetupFlyoutForUpdate = useCallback(() => {
viewSetupForUpdate();
onOpenSetup();
}, [onOpenSetup, viewSetupForUpdate]);

const hasResults = useMemo(() => topLogEntryCategories.length > 0, [
topLogEntryCategories.length,
]);

const isFirstUse = useMemo(
() => setupStatus.type === 'skipped' && !!setupStatus.newlyCreated && !hasResults,
() =>
((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) ||
setupStatus.type === 'succeeded') &&
!hasResults,
[hasResults, setupStatus]
);

Expand Down Expand Up @@ -184,8 +203,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => {
hasOutdatedJobDefinitions={hasOutdatedJobDefinitions}
hasStoppedJobs={hasStoppedJobs}
isFirstUse={isFirstUse}
onRecreateMlJobForReconfiguration={viewSetupForReconfiguration}
onRecreateMlJobForUpdate={viewSetupForUpdate}
onRecreateMlJobForReconfiguration={viewSetupFlyoutForReconfiguration}
onRecreateMlJobForUpdate={viewSetupFlyoutForUpdate}
qualityWarnings={categoryQualityWarnings}
/>
</EuiFlexItem>
Expand All @@ -197,7 +216,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => {
isLoadingTopCategories={isLoadingTopLogEntryCategories}
jobId={jobIds['log-entry-categories-count']}
onChangeDatasetSelection={setCategoryQueryDatasets}
onRequestRecreateMlJob={viewSetupForReconfiguration}
onRequestRecreateMlJob={viewSetupFlyoutForReconfiguration}
selectedDatasets={categoryQueryDatasets}
sourceId={sourceId}
timeRange={categoryQueryTimeRange.timeRange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,98 +4,51 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiSpacer, EuiSteps, EuiText } from '@elastic/eui';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui';

import { BetaBadge } from '../../../components/beta_badge';
import {
createInitialConfigurationStep,
createProcessStep,
LogAnalysisSetupPage,
LogAnalysisSetupPageContent,
LogAnalysisSetupPageHeader,
} from '../../../components/logging/log_analysis_setup';
import { useTrackPageview } from '../../../../../observability/public';
import { useLogEntryCategoriesSetup } from './use_log_entry_categories_setup';

export const LogEntryCategoriesSetupContent: React.FunctionComponent = () => {
interface LogEntryCategoriesSetupContentProps {
onOpenSetup: () => void;
}

export const LogEntryCategoriesSetupContent: React.FunctionComponent<LogEntryCategoriesSetupContentProps> = ({
onOpenSetup,
}) => {
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup' });
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup', delay: 15000 });

const {
cleanUpAndSetUp,
endTime,
isValidating,
lastSetupErrorMessages,
setEndTime,
setStartTime,
setValidatedIndices,
setUp,
setupStatus,
startTime,
validatedIndices,
validationErrors,
viewResults,
} = useLogEntryCategoriesSetup();

const steps = useMemo(
() => [
createInitialConfigurationStep({
setStartTime,
setEndTime,
startTime,
endTime,
isValidating,
validatedIndices,
setupStatus,
setValidatedIndices,
validationErrors,
}),
createProcessStep({
cleanUpAndSetUp,
errorMessages: lastSetupErrorMessages,
isConfigurationValid: validationErrors.length <= 0 && !isValidating,
setUp,
setupStatus,
viewResults,
}),
],
[
cleanUpAndSetUp,
endTime,
isValidating,
lastSetupErrorMessages,
setEndTime,
setStartTime,
setUp,
setValidatedIndices,
setupStatus,
startTime,
validatedIndices,
validationErrors,
viewResults,
]
);

return (
<LogAnalysisSetupPage data-test-subj="logEntryCategoriesSetupPage">
<LogAnalysisSetupPageHeader>
<FormattedMessage
id="xpack.infra.logs.logEntryCategories.setupTitle"
defaultMessage="Enable Machine Learning analysis"
/>{' '}
<BetaBadge />
defaultMessage="Set up log category analysis"
/>
</LogAnalysisSetupPageHeader>
<LogAnalysisSetupPageContent>
<EuiText size="s">
<FormattedMessage
id="xpack.infra.logs.logEntryCategories.setupDescription"
defaultMessage="Use Machine Learning to automatically categorize log messages."
/>
<p>
<FormattedMessage
id="xpack.infra.logs.logEntryCategories.setupDescription"
defaultMessage="To enable log categories, set up a machine learning job."
/>
</p>
</EuiText>
<EuiSpacer />
<EuiSteps steps={steps} />
<EuiButton fill onClick={onOpenSetup}>
<FormattedMessage
id="xpack.infra.logs.logEntryCategories.showAnalysisSetupButtonLabel"
defaultMessage="ML setup"
/>
</EuiButton>
</LogAnalysisSetupPageContent>
</LogAnalysisSetupPage>
);
Expand Down
Loading

0 comments on commit b172b5b

Please sign in to comment.