Skip to content

Commit

Permalink
[Logs UI] Allow Logs/ML integration result access with machine… (#55884)
Browse files Browse the repository at this point in the history
This makes the "Log rate" and "Categories" tab visible on clusters with a suitable license for users which don't have the the `machine_learning_admin` role.
  • Loading branch information
weltenwort authored Jan 29, 2020
1 parent 7f63118 commit 16b4ff4
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ export * from './setup_page';
export * from './initial_configuration_step';
export * from './process_step';

export * from './missing_results_privileges_prompt';
export * from './missing_setup_privileges_prompt';
export * from './ml_unavailable_prompt';
export * from './setup_status_unknown_prompt';
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { EuiEmptyPrompt, EuiCode } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';

import euiStyled from '../../../../../../common/eui_styled_components';
import { UserManagementLink } from './user_management_link';

export const MissingResultsPrivilegesPrompt: React.FunctionComponent = () => (
<EmptyPrompt
title={
<h2>
<FormattedMessage
id="xpack.infra.logs.analysis.missingMlResultsPrivilegesTitle"
defaultMessage="Additional Machine Learning privileges required"
/>
</h2>
}
body={
<p>
<FormattedMessage
id="xpack.infra.logs.analysis.missingMlResultsPrivilegesBody"
defaultMessage="This feature makes use of Machine Learning jobs, which require at least the {machineLearningUserRole} role in order to access their status and results."
values={{
machineLearningUserRole: <EuiCode>machine_learning_user</EuiCode>,
}}
/>
</p>
}
actions={<UserManagementLink />}
/>
);

const EmptyPrompt = euiStyled(EuiEmptyPrompt)`
align-self: center;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { EuiEmptyPrompt, EuiCode } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';

import euiStyled from '../../../../../../common/eui_styled_components';
import { UserManagementLink } from './user_management_link';

export const MissingSetupPrivilegesPrompt: React.FunctionComponent = () => (
<EmptyPrompt
title={
<h2>
<FormattedMessage
id="xpack.infra.logs.analysis.missingMlSetupPrivilegesTitle"
defaultMessage="Additional Machine Learning privileges required"
/>
</h2>
}
body={
<p>
<FormattedMessage
id="xpack.infra.logs.analysis.missingMlSetupPrivilegesBody"
defaultMessage="This feature makes use of Machine Learning jobs, which require the {machineLearningAdminRole} role in order to be set up."
values={{
machineLearningAdminRole: <EuiCode>machine_learning_admin</EuiCode>,
}}
/>
</p>
}
actions={<UserManagementLink />}
/>
);

const EmptyPrompt = euiStyled(EuiEmptyPrompt)`
align-self: center;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { EuiButton, EuiButtonProps } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';

export const UserManagementLink: React.FunctionComponent<EuiButtonProps> = props => (
<EuiButton href="kibana#/management/security/users" color="primary" fill {...props}>
<FormattedMessage
id="xpack.infra.logs.analysis.userManagementButtonLabel"
defaultMessage="Manage users"
/>
</EuiButton>
);
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,22 @@ export const useLogAnalysisCapabilities = () => {
fetchMlCapabilitiesRequest.state,
]);

const hasLogAnalysisSetupCapabilities = mlCapabilities.capabilities.canCreateJob;
const hasLogAnalysisReadCapabilities = mlCapabilities.capabilities.canGetJobs;
const hasLogAnalysisCapabilites =
mlCapabilities.isPlatinumOrTrialLicense && mlCapabilities.mlFeatureEnabledInSpace;

return {
hasLogAnalysisCapabilites: mlCapabilities.capabilities.canCreateJob,
hasLogAnalysisCapabilites,
hasLogAnalysisReadCapabilities,
hasLogAnalysisSetupCapabilities,
isLoading,
};
};

export const LogAnalysisCapabilities = createContainer(useLogAnalysisCapabilities);
export const [LogAnalysisCapabilitiesProvider, useLogAnalysisCapabilitiesContext] = createContainer(
useLogAnalysisCapabilities
);

const initialMlCapabilities = {
capabilities: {
Expand Down
130 changes: 1 addition & 129 deletions x-pack/legacy/plugins/infra/public/pages/logs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,132 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';
import React from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';

import { DocumentTitle } from '../../components/document_title';
import { HelpCenterContent } from '../../components/help_center_content';
import { Header } from '../../components/header';
import { RoutedTabs } from '../../components/navigation/routed_tabs';
import { ColumnarPage } from '../../components/page';
import { SourceLoadingPage } from '../../components/source_loading_page';
import { SourceErrorPage } from '../../components/source_error_page';
import { Source, useSource } from '../../containers/source';
import { StreamPage } from './stream';
import { LogsSettingsPage } from './settings';
import { AppNavigation } from '../../components/navigation/app_navigation';
import {
useLogAnalysisCapabilities,
LogAnalysisCapabilities,
} from '../../containers/logs/log_analysis';
import { useSourceId } from '../../containers/source_id';
import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params';
import { useKibana } from '../../../../../../..//src/plugins/kibana_react/public';
import { LogEntryCategoriesPage } from './log_entry_categories';
import { LogEntryRatePage } from './log_entry_rate';

export const LogsPage = ({ match }: RouteComponentProps) => {
const uiCapabilities = useKibana().services.application?.capabilities;
const [sourceId] = useSourceId();
const source = useSource({ sourceId });
const logAnalysisCapabilities = useLogAnalysisCapabilities();

const streamTab = {
title: streamTabTitle,
path: `${match.path}/stream`,
};

const logRateTab = {
title: logRateTabTitle,
path: `${match.path}/log-rate`,
};

const logCategoriesTab = {
title: logCategoriesTabTitle,
path: `${match.path}/log-categories`,
};

const settingsTab = {
title: settingsTabTitle,
path: `${match.path}/settings`,
};

return (
<Source.Context.Provider value={source}>
<LogAnalysisCapabilities.Context.Provider value={logAnalysisCapabilities}>
<ColumnarPage>
<DocumentTitle title={pageTitle} />

<HelpCenterContent feedbackLink={feedbackLinkUrl} appName={pageTitle} />

<Header
breadcrumbs={[
{
text: pageTitle,
},
]}
readOnlyBadge={!uiCapabilities?.logs?.save}
/>
{source.isLoadingSource ||
(!source.isLoadingSource &&
!source.hasFailedLoadingSource &&
source.source === undefined) ? (
<SourceLoadingPage />
) : source.hasFailedLoadingSource ? (
<SourceErrorPage
errorMessage={source.loadSourceFailureMessage || ''}
retry={source.loadSource}
/>
) : (
<>
<AppNavigation aria-label={pageTitle}>
<RoutedTabs
tabs={
logAnalysisCapabilities.hasLogAnalysisCapabilites
? [streamTab, logRateTab, logCategoriesTab, settingsTab]
: [streamTab, settingsTab]
}
/>
</AppNavigation>

<Switch>
<Route path={streamTab.path} component={StreamPage} />
<Route path={logRateTab.path} component={LogEntryRatePage} />
<Route path={logCategoriesTab.path} component={LogEntryCategoriesPage} />
<Route path={settingsTab.path} component={LogsSettingsPage} />
<RedirectWithQueryParams
from={`${match.path}/analysis`}
to={logRateTab.path}
exact
/>
</Switch>
</>
)}
</ColumnarPage>
</LogAnalysisCapabilities.Context.Provider>
</Source.Context.Provider>
);
};

const pageTitle = i18n.translate('xpack.infra.header.logsTitle', {
defaultMessage: 'Logs',
});

const streamTabTitle = i18n.translate('xpack.infra.logs.index.streamTabTitle', {
defaultMessage: 'Stream',
});

const logRateTabTitle = i18n.translate('xpack.infra.logs.index.logRateBetaBadgeTitle', {
defaultMessage: 'Log Rate',
});

const logCategoriesTabTitle = i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', {
defaultMessage: 'Categories',
});

const settingsTabTitle = i18n.translate('xpack.infra.logs.index.settingsTabTitle', {
defaultMessage: 'Settings',
});

const feedbackLinkUrl = 'https://discuss.elastic.co/c/logs';
export * from './page';
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@
*/

import { i18n } from '@kbn/i18n';
import React, { useContext, useEffect } from 'react';
import React, { useEffect } from 'react';

import { isSetupStatusWithResults } from '../../../../common/log_analysis';
import { LoadingPage } from '../../../components/loading_page';
import {
LogAnalysisSetupStatusUnknownPrompt,
MissingResultsPrivilegesPrompt,
MissingSetupPrivilegesPrompt,
MlUnavailablePrompt,
} from '../../../components/logging/log_analysis_setup';
import { LogAnalysisCapabilities } from '../../../containers/logs/log_analysis';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { LogEntryCategoriesResultsContent } from './page_results_content';
import { LogEntryCategoriesSetupContent } from './page_setup_content';
import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module';

export const LogEntryCategoriesPageContent = () => {
const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context);
const {
hasLogAnalysisCapabilites,
hasLogAnalysisReadCapabilities,
hasLogAnalysisSetupCapabilities,
} = useLogAnalysisCapabilitiesContext();

const {
fetchJobStatus,
Expand All @@ -28,12 +34,16 @@ export const LogEntryCategoriesPageContent = () => {
} = useLogEntryCategoriesModuleContext();

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

if (!hasLogAnalysisCapabilites) {
return <MlUnavailablePrompt />;
} else if (!hasLogAnalysisReadCapabilities) {
return <MissingResultsPrivilegesPrompt />;
} else if (setupStatus === 'initializing') {
return (
<LoadingPage
Expand All @@ -46,6 +56,8 @@ export const LogEntryCategoriesPageContent = () => {
return <LogAnalysisSetupStatusUnknownPrompt retry={fetchJobStatus} />;
} else if (isSetupStatusWithResults(setupStatus)) {
return <LogEntryCategoriesResultsContent />;
} else if (!hasLogAnalysisSetupCapabilities) {
return <MissingSetupPrivilegesPrompt />;
} else {
return <LogEntryCategoriesSetupContent />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,41 @@
*/

import { i18n } from '@kbn/i18n';
import React, { useContext, useEffect } from 'react';
import React, { useEffect } from 'react';

import { isSetupStatusWithResults } from '../../../../common/log_analysis';
import { LoadingPage } from '../../../components/loading_page';
import {
LogAnalysisSetupStatusUnknownPrompt,
MissingResultsPrivilegesPrompt,
MissingSetupPrivilegesPrompt,
MlUnavailablePrompt,
} from '../../../components/logging/log_analysis_setup';
import { LogAnalysisCapabilities } from '../../../containers/logs/log_analysis';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { LogEntryRateResultsContent } from './page_results_content';
import { LogEntryRateSetupContent } from './page_setup_content';
import { useLogEntryRateModuleContext } from './use_log_entry_rate_module';

export const LogEntryRatePageContent = () => {
const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context);
const {
hasLogAnalysisCapabilites,
hasLogAnalysisReadCapabilities,
hasLogAnalysisSetupCapabilities,
} = useLogAnalysisCapabilitiesContext();

const { fetchJobStatus, fetchModuleDefinition, setupStatus } = useLogEntryRateModuleContext();

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

if (!hasLogAnalysisCapabilites) {
return <MlUnavailablePrompt />;
} else if (!hasLogAnalysisReadCapabilities) {
return <MissingResultsPrivilegesPrompt />;
} else if (setupStatus === 'initializing') {
return (
<LoadingPage
Expand All @@ -42,6 +52,8 @@ export const LogEntryRatePageContent = () => {
return <LogAnalysisSetupStatusUnknownPrompt retry={fetchJobStatus} />;
} else if (isSetupStatusWithResults(setupStatus)) {
return <LogEntryRateResultsContent />;
} else if (!hasLogAnalysisSetupCapabilities) {
return <MissingSetupPrivilegesPrompt />;
} else {
return <LogEntryRateSetupContent />;
}
Expand Down
Loading

0 comments on commit 16b4ff4

Please sign in to comment.