mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Infrastructure UI] Hosts technical preview discovery (#149351)
Closes #148235 ## Summary This PR adds an option for the user to enable the hosts view and makes the hosts navigation link always visible. ### Code Review I know that this PR looks big (it actually is 😅 ) and not "fun" to review but I will try to make it easier by splitting and pointing out the different parts here: - [Change in the Navigation](https://github.com/elastic/kibana/pull/149351/files#diff-1f9338fabab6f332f6b4b11dbcec00f10b89097720c94b6087c53b186659c976): Contains also a [component](https://github.com/elastic/kibana/pull/149351/files#diff-9f8d59f3f98855c2cc6c4bd8ad93f69ad06181809a8769121fc1a3714c848f5e) to show the icon next to the name - [Hosts view navigation link enabled by default](https://github.com/elastic/kibana/pull/149351/files#diff-f66e54f3274b7dd4c72b54011c1b4a1d2e4d91aa47341e14b048377437574655) - [The Hosts View Landing page](https://github.com/elastic/kibana/pull/149351/files#diff-c3aa95031634008b7384dd670ca12c8266bead5f2cd461fdc6070fe84ae9e4e4) : Using the [EnableHostViewPage](https://github.com/elastic/kibana/pull/149351/files#diff-a0704adf74ea9605858160dfbe6ee04021b8161a63a54852009847fb5caeb5e8) component passing different `actions` based on the use case. - [Link on the inventory page](https://github.com/elastic/kibana/pull/149351/files#diff-181e8eeab11591c40565d6636c1b0fc6099bc555af71efe612d1da3f406d7101) - [Functional Tests](https://github.com/elastic/kibana/pull/149351/files#diff-cdf2ef63d7f0db051d569b09fbd2ba39eb852d3d75bf3b29d85e57de2da3af35) ### Flow #### How to navigate to Hosts View - The link from the inventory page which will navigate the user to a separate page where he will have the option to enable hosts view (if the user has permission to enable it otherwise he will see a message to contact his administrator to do it for him). In order to enable the hosts view the user should have permission to modify advanced settings. - The navigation link will be always visible. After enabling the host view the user will see the hosts view page (links highlighted in green): <img width="2055" alt="image" src="https://user-images.githubusercontent.com/14139027/214123141-e3d8a156-839f-4dbf-ae6f-b564eccb16f7.png"> #### After navigating to host view - When hosts view is disabled | Admin User | User with read-only permission | | ------------ | ------------------------------- | |  |  | - When hosts view is enabled (Both Admin User and User with read-only permission ) <img width="1713" alt="image" src="https://user-images.githubusercontent.com/14139027/214125673-8976047c-7653-4345-bfee-41413f080e05.png"> ## Testing - Remove the override (if any) in the`kibana.yml` for `observability:enableInfrastructureHostsView` - Use `elastic` user (or user with permission to modify advanced settings) to enable the host view (navigating from the inventory page or from the nav menu) - to disable the host view you should set `observability:enableInfrastructureHostsView` to `false` in Stack Management > Advanced Settings: <img width="1724" alt="image" src="https://user-images.githubusercontent.com/14139027/214127034-fc245d12-0e56-4aa3-9b35-bdfeb7760960.png"> - create a new role and set the `Management` permissions to `none` and observability permissions to `read`: <img width="1726" alt="image" src="https://user-images.githubusercontent.com/14139027/214127744-27e3014f-c181-4cd0-9b91-8868580a909b.png"> - Assign this role to a user - Open hosts view (for both cases `observability:enableInfrastructureHostsView` set to `false`/`true`) ❓ Testing telemetry Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
088a6bb5af
commit
0e3ae210d7
15 changed files with 1420 additions and 39 deletions
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { EuiPageTemplate, EuiImage, EuiSpacer } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { useEuiBackgroundColor } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useTrackPageview } from '@kbn/observability-plugin/public';
|
||||
import { MetricsPageTemplate } from '../../../page_template';
|
||||
import hostsLandingBeta from './hosts_landing_beta.svg';
|
||||
import { ExperimentalBadge } from '../experimental_badge';
|
||||
|
||||
interface Props {
|
||||
actions?: ReactNode;
|
||||
}
|
||||
|
||||
export const EnableHostsViewPage = ({ actions }: Props) => {
|
||||
const backgroundColor = useEuiBackgroundColor('subdued');
|
||||
|
||||
useTrackPageview({ app: 'infra_metrics', path: 'hosts_feature_enable_landing_page' });
|
||||
useTrackPageview({
|
||||
app: 'infra_metrics',
|
||||
path: 'hosts_feature_enable_landing_page',
|
||||
delay: 15000,
|
||||
});
|
||||
|
||||
return (
|
||||
<MetricsPageTemplate isEmptyState>
|
||||
<EuiPageTemplate.EmptyPrompt
|
||||
data-test-subj="hostsLandingPage"
|
||||
title={
|
||||
<h2>
|
||||
{i18n.translate('xpack.infra.hostsViewPage.landing.introTitle', {
|
||||
defaultMessage: 'Introducing: Host Analysis',
|
||||
})}
|
||||
</h2>
|
||||
}
|
||||
alignment="center"
|
||||
icon={<EuiImage size="fullWidth" src={hostsLandingBeta} alt="" />}
|
||||
color="plain"
|
||||
layout="horizontal"
|
||||
body={
|
||||
<>
|
||||
<ExperimentalBadge />
|
||||
<EuiSpacer />
|
||||
<p>
|
||||
{i18n.translate('xpack.infra.hostsViewPage.landing.introMessage', {
|
||||
defaultMessage: `Introducing our new 'Hosts' feature, now available in technical preview!
|
||||
With this powerful tool, you can easily view and analyse your hosts and identify any
|
||||
issues so you address them quickly. Get a detailed view of metrics for your hosts, see
|
||||
which ones are triggering the most alerts and filter the hosts you want to analyse
|
||||
using any KQL filter and easy breakdowns such as cloud provider and operating system.`,
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.translate('xpack.infra.hostsViewPage.landing.tryTheFeatureMessage', {
|
||||
defaultMessage: `This is an early version of the feature and we would love your feedback as we continue
|
||||
to develop and improve it. To access the feature, simply enable below. Don't miss
|
||||
out on this powerful new addition to our platform - try it out today!`,
|
||||
})}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
css={css`
|
||||
background-color: ${backgroundColor};
|
||||
`}
|
||||
actions={actions}
|
||||
/>
|
||||
</MetricsPageTemplate>
|
||||
);
|
||||
};
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 695 KiB |
|
@ -6,16 +6,16 @@
|
|||
*/
|
||||
|
||||
import { EuiBetaBadge } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import type { IconType, ToolTipPositions } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
export const ExperimentalBadge = () => (
|
||||
interface Props {
|
||||
iconType?: IconType;
|
||||
tooltipPosition?: ToolTipPositions;
|
||||
}
|
||||
export const ExperimentalBadge = ({ iconType, tooltipPosition }: Props) => (
|
||||
<EuiBetaBadge
|
||||
css={css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`}
|
||||
label={i18n.translate('xpack.infra.hostsViewPage.experimentalBadgeLabel', {
|
||||
defaultMessage: 'Technical preview',
|
||||
})}
|
||||
|
@ -23,5 +23,7 @@ export const ExperimentalBadge = () => (
|
|||
defaultMessage:
|
||||
'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.',
|
||||
})}
|
||||
iconType={iconType}
|
||||
tooltipPosition={tooltipPosition}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton, EuiCallOut } from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { enableInfrastructureHostsView } from '@kbn/observability-plugin/public';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { Observable } from 'rxjs';
|
||||
import { InfraClientStartDeps } from '../../../types';
|
||||
import { EnableHostsViewPage } from './components/enable_hosts_view_page/enable_hosts_view_page';
|
||||
import { HostsPage } from '.';
|
||||
|
||||
export const HostsLandingPage = () => {
|
||||
const {
|
||||
services: { uiSettings, application },
|
||||
} = useKibana<InfraClientStartDeps>();
|
||||
const canEditAdvancedSettings = application?.capabilities.advancedSettings.save;
|
||||
const isHostViewEnabled = useObservable<boolean>(
|
||||
uiSettings?.get$<boolean>(enableInfrastructureHostsView) ??
|
||||
new Observable((subs) => subs.next(false))
|
||||
);
|
||||
|
||||
if (isHostViewEnabled) {
|
||||
return <HostsPage />;
|
||||
}
|
||||
|
||||
if (canEditAdvancedSettings) {
|
||||
return (
|
||||
<EnableHostsViewPage
|
||||
actions={
|
||||
<EuiButton
|
||||
iconType="check"
|
||||
color="primary"
|
||||
data-test-subj="hostsView-enable-feature-button"
|
||||
onClick={() => {
|
||||
uiSettings?.set(enableInfrastructureHostsView, true);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.infra.hostsViewPage.landing.enableHostsView', {
|
||||
defaultMessage: 'Enable hosts view',
|
||||
})}
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EnableHostsViewPage
|
||||
actions={
|
||||
<EuiCallOut data-test-subj="hostView-no-enable-access" size="s" color="warning">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.infra.hostsViewPage.landing.calloutReachOutToYourKibanaAdministrator',
|
||||
{
|
||||
defaultMessage: `Your user role doesn’t have sufficient privileges to enable this feature - please
|
||||
reach out to your Kibana Administrator and ask them to visit this page to enable this feature.`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.hostsViewPage.landing.calloutRoleClarificationWithDocsLink"
|
||||
defaultMessage="They will need a role with access to Advanced settings in Kibana. {docsLink}"
|
||||
values={{
|
||||
docsLink: (
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
data-test-subj="hostsView-docs-link"
|
||||
href="https://www.elastic.co/guide/en/kibana/current/kibana-privileges.html#kibana-feature-privileges"
|
||||
>
|
||||
{i18n.translate('xpack.infra.hostsViewPage.landing.learnMore', {
|
||||
defaultMessage: 'Learn more',
|
||||
})}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -29,7 +29,7 @@ import { MetricsExplorerPage } from './metrics_explorer';
|
|||
import { SnapshotPage } from './inventory_view';
|
||||
import { MetricDetail } from './metric_detail';
|
||||
import { MetricsSettingsPage } from './settings';
|
||||
import { HostsPage } from './hosts';
|
||||
import { HostsLandingPage } from './hosts/hosts_landing_page';
|
||||
import { SourceLoadingPage } from '../../components/source_loading_page';
|
||||
import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options';
|
||||
import { WaffleTimeProvider } from './inventory_view/hooks/use_waffle_time';
|
||||
|
@ -120,7 +120,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
|||
)}
|
||||
/>
|
||||
<Route path="/detail/:type/:node" component={MetricDetail} />
|
||||
<Route path={'/hosts'} component={HostsPage} />
|
||||
<Route path={'/hosts'} component={HostsLandingPage} />
|
||||
<Route path={'/settings'} component={MetricsSettingsPage} />
|
||||
</Switch>
|
||||
</InfraMLCapabilitiesProvider>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiBetaBadge, EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useLinkProps } from '@kbn/observability-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { ExperimentalBadge } from '../../hosts/components/experimental_badge';
|
||||
|
||||
export const HostViewIntroPanel = () => {
|
||||
const link = useLinkProps({
|
||||
app: 'metrics',
|
||||
pathname: '/hosts',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
css={css`
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
color={'accent'}
|
||||
href={link.href ?? ''}
|
||||
data-test-subj="inventory-hostsView-badge"
|
||||
label={i18n.translate('xpack.infra.layout.tryIt', {
|
||||
defaultMessage: 'Try it',
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExperimentalBadge iconType="beaker" tooltipPosition="top" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink data-test-subj="inventory-hostsView-link" {...link}>
|
||||
{i18n.translate('xpack.infra.layout.hostsLandingPageLink', {
|
||||
defaultMessage: 'Introducing a new Hosts analysis experience',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -31,6 +31,7 @@ import { createLegend } from '../lib/create_legend';
|
|||
import { useWaffleViewState } from '../hooks/use_waffle_view_state';
|
||||
import { BottomDrawer } from './bottom_drawer';
|
||||
import { LegendControls } from './waffle/legend_controls';
|
||||
import { HostViewIntroPanel } from './hosts_view_intro_panel';
|
||||
|
||||
interface Props {
|
||||
shouldLoadDefault: boolean;
|
||||
|
@ -178,6 +179,7 @@ export const Layout = React.memo(
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
</TopActionContainer>
|
||||
<HostViewIntroPanel />
|
||||
<AutoSizer bounds>
|
||||
{({ measureRef, bounds: { height = 0 } }) => (
|
||||
<>
|
||||
|
|
|
@ -92,8 +92,8 @@ export class Plugin implements InfraClientPluginClass {
|
|||
const infraEntries = [
|
||||
{ label: 'Inventory', app: 'metrics', path: '/inventory' },
|
||||
{ label: 'Metrics Explorer', app: 'metrics', path: '/explorer' },
|
||||
{ label: 'Hosts', isBeta: true, app: 'metrics', path: '/hosts' },
|
||||
];
|
||||
const hostInfraEntry = { label: 'Hosts', app: 'metrics', path: '/hosts' };
|
||||
pluginsSetup.observability.navigation.registerSections(
|
||||
startDep$AndHostViewFlag$.pipe(
|
||||
map(
|
||||
|
@ -103,7 +103,6 @@ export class Plugin implements InfraClientPluginClass {
|
|||
application: { capabilities },
|
||||
},
|
||||
],
|
||||
isInfrastructureHostsViewEnabled,
|
||||
]) => [
|
||||
...(capabilities.logs.show
|
||||
? [
|
||||
|
@ -123,9 +122,7 @@ export class Plugin implements InfraClientPluginClass {
|
|||
{
|
||||
label: 'Infrastructure',
|
||||
sortKey: 300,
|
||||
entries: isInfrastructureHostsViewEnabled
|
||||
? [hostInfraEntry, ...infraEntries]
|
||||
: infraEntries,
|
||||
entries: infraEntries,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
@ -197,6 +194,13 @@ export class Plugin implements InfraClientPluginClass {
|
|||
}),
|
||||
path: '/inventory',
|
||||
},
|
||||
{
|
||||
id: 'metrics-hosts',
|
||||
title: i18n.translate('xpack.infra.homePage.metricsHostsTabTitle', {
|
||||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
path: '/hosts',
|
||||
},
|
||||
{
|
||||
id: 'metrics-explorer',
|
||||
title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', {
|
||||
|
@ -212,13 +216,6 @@ export class Plugin implements InfraClientPluginClass {
|
|||
path: '/settings',
|
||||
},
|
||||
];
|
||||
const hostInfraDeepLink = {
|
||||
id: 'metrics-hosts',
|
||||
title: i18n.translate('xpack.infra.homePage.metricsHostsTabTitle', {
|
||||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
path: '/hosts',
|
||||
};
|
||||
core.application.register({
|
||||
id: 'metrics',
|
||||
title: i18n.translate('xpack.infra.metrics.pluginTitle', {
|
||||
|
@ -229,9 +226,7 @@ export class Plugin implements InfraClientPluginClass {
|
|||
appRoute: '/app/metrics',
|
||||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
updater$: this.appUpdater$,
|
||||
deepLinks: core.uiSettings.get<boolean>(enableInfrastructureHostsView)
|
||||
? [hostInfraDeepLink, ...infraDeepLinks]
|
||||
: infraDeepLinks,
|
||||
deepLinks: infraDeepLinks,
|
||||
mount: async (params: AppMountParameters) => {
|
||||
// mount callback should not use setup dependencies, get start dependencies instead
|
||||
const [coreStart, pluginsStart, pluginStart] = await core.getStartServices();
|
||||
|
@ -242,14 +237,9 @@ export class Plugin implements InfraClientPluginClass {
|
|||
});
|
||||
|
||||
startDep$AndHostViewFlag$.subscribe(
|
||||
([_startServices, isInfrastructureHostsViewEnabled]: [
|
||||
[CoreStart, InfraClientStartDeps, InfraClientStartExports],
|
||||
boolean
|
||||
]) => {
|
||||
([_startServices]: [[CoreStart, InfraClientStartDeps, InfraClientStartExports], boolean]) => {
|
||||
this.appUpdater$.next(() => ({
|
||||
deepLinks: isInfrastructureHostsViewEnabled
|
||||
? [hostInfraDeepLink, ...infraDeepLinks]
|
||||
: infraDeepLinks,
|
||||
deepLinks: infraDeepLinks,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
|
|
@ -46,6 +46,8 @@ export interface NavigationEntry {
|
|||
ignoreTrailingSlash?: boolean;
|
||||
// shows NEW badge besides the navigation label, which will automatically disappear when menu item is clicked.
|
||||
isNewFeature?: boolean;
|
||||
// shows beta badge lab icon if the feature is still beta besides the navigation label
|
||||
isBeta?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiBetaBadge } from '@elastic/eui';
|
||||
import type { IconType } from '@elastic/eui/src/components/icon/icon';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
iconType: IconType;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const LabelContainer = styled.span`
|
||||
max-width: 72%;
|
||||
float: left;
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledBetaBadge = styled(EuiBetaBadge)`
|
||||
margin-left: 5px;
|
||||
margin-bottom: -6px;
|
||||
`;
|
||||
|
||||
export function NavNameWithBetaBadge({ label, iconType }: Props) {
|
||||
return (
|
||||
<>
|
||||
<LabelContainer className="eui-textTruncate">
|
||||
<span>{label}</span>
|
||||
</LabelContainer>
|
||||
<StyledBetaBadge
|
||||
label={i18n.translate('xpack.observability.navigation.experimentalBadgeLabel', {
|
||||
defaultMessage: 'Technical preview',
|
||||
})}
|
||||
size="s"
|
||||
iconType={iconType}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -27,6 +27,7 @@ import { ObservabilityAppServices } from '../../../application/types';
|
|||
import type { NavigationSection } from '../../../services/navigation_registry';
|
||||
import { ObservabilityTour } from '../tour';
|
||||
import { NavNameWithBadge, hideBadge } from './nav_name_with_badge';
|
||||
import { NavNameWithBetaBadge } from './nav_name_with_beta_badge';
|
||||
|
||||
export type WrappedPageTemplateProps = Pick<
|
||||
KibanaPageTemplateProps,
|
||||
|
@ -103,6 +104,8 @@ export function ObservabilityPageTemplate({
|
|||
id: `${sectionIndex}.${entryIndex}`,
|
||||
name: entry.isNewFeature ? (
|
||||
<NavNameWithBadge label={entry.label} localStorageId={badgeLocalStorageId} />
|
||||
) : entry.isBeta ? (
|
||||
<NavNameWithBetaBadge label={entry.label} iconType="beaker" />
|
||||
) : (
|
||||
entry.label
|
||||
),
|
||||
|
|
|
@ -32,6 +32,8 @@ export interface NavigationEntry {
|
|||
onClick?: (event: React.MouseEvent<HTMLElement | HTMLButtonElement, MouseEvent>) => void;
|
||||
// shows NEW badge besides the navigation label, which will automatically disappear when menu item is clicked.
|
||||
isNewFeature?: boolean;
|
||||
// shows beta badge lab icon if the feature is still beta besides the navigation label
|
||||
isBeta?: boolean;
|
||||
// override default path matching logic to determine if nav entry is selected
|
||||
matchPath?: (path: string) => boolean;
|
||||
}
|
||||
|
|
|
@ -15,10 +15,88 @@ const END_DATE = moment.utc(DATES.metricsAndLogs.hosts.max);
|
|||
const timepickerFormat = 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const browser = getService('browser');
|
||||
const pageObjects = getPageObjects(['common', 'infraHome', 'timePicker', 'infraHostsView']);
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const security = getService('security');
|
||||
const pageObjects = getPageObjects([
|
||||
'common',
|
||||
'infraHome',
|
||||
'timePicker',
|
||||
'infraHostsView',
|
||||
'security',
|
||||
'settings',
|
||||
]);
|
||||
|
||||
// Helpers
|
||||
|
||||
const loginWithReadOnlyUserAndNavigateToInfra = async () => {
|
||||
await security.role.create('global_hosts_read_privileges_role', {
|
||||
elasticsearch: {
|
||||
indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
infrastructure: ['read'],
|
||||
advancedSettings: ['read'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await security.user.create('global_hosts_read_privileges_user', {
|
||||
password: 'global_hosts_read_privileges_user-password',
|
||||
roles: ['global_hosts_read_privileges_role'],
|
||||
full_name: 'test user',
|
||||
});
|
||||
|
||||
await pageObjects.security.forceLogout();
|
||||
|
||||
await pageObjects.security.login(
|
||||
'global_hosts_read_privileges_user',
|
||||
'global_hosts_read_privileges_user-password',
|
||||
{
|
||||
expectSpaceSelector: false,
|
||||
}
|
||||
);
|
||||
|
||||
await pageObjects.common.navigateToApp('infraOps');
|
||||
};
|
||||
|
||||
const logoutAndDeleteReadOnlyUser = async () => {
|
||||
await pageObjects.security.forceLogout();
|
||||
await Promise.all([
|
||||
security.role.delete('global_hosts_read_privileges_role'),
|
||||
security.user.delete('global_hosts_read_privileges_user'),
|
||||
]);
|
||||
};
|
||||
|
||||
const navigateAndDisableHostView = async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
await pageObjects.common.navigateToApp('infraOps');
|
||||
await pageObjects.common.navigateToUrl('management', 'kibana/settings', {
|
||||
basePath: `/s/default`,
|
||||
ensureCurrentUrl: false,
|
||||
shouldLoginIfPrompted: false,
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
await pageObjects.settings.toggleAdvancedSettingCheckbox(
|
||||
'observability:enableInfrastructureHostsView',
|
||||
false
|
||||
);
|
||||
return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
};
|
||||
|
||||
const navigateAndEnableHostView = async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
await pageObjects.common.navigateToApp('infraOps');
|
||||
await pageObjects.infraHostsView.clickTryHostViewLink();
|
||||
await pageObjects.infraHostsView.clickEnableHostViewButton();
|
||||
};
|
||||
|
||||
// Tests
|
||||
|
||||
describe('Hosts view', function () {
|
||||
this.tags('includeFirefox');
|
||||
|
@ -26,21 +104,106 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
describe('Basic functionality', () => {
|
||||
describe('shows hosts view landing page for admin', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
await pageObjects.common.navigateToApp('infraOps');
|
||||
await pageObjects.infraHome.goToHostsView();
|
||||
await pageObjects.infraHostsView.clickTryHostViewBadge();
|
||||
});
|
||||
after(async () => {
|
||||
return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
});
|
||||
|
||||
it('should show hosts landing page with enable button when the hosts view is disabled', async () => {
|
||||
const landingPageEnableButton =
|
||||
await pageObjects.infraHostsView.getHostsLandingPageEnableButton();
|
||||
const landingPageEnableButtonText = await landingPageEnableButton.getVisibleText();
|
||||
expect(landingPageEnableButtonText).to.eql('Enable hosts view');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should show hosts view landing page for user with read permission', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
await loginWithReadOnlyUserAndNavigateToInfra();
|
||||
await pageObjects.infraHostsView.clickTryHostViewBadge();
|
||||
});
|
||||
after(async () => {
|
||||
// NOTE: Logout needs to happen before anything else to avoid flaky behavior
|
||||
await logoutAndDeleteReadOnlyUser();
|
||||
return esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
});
|
||||
|
||||
it('should show hosts landing page with callout when the hosts view is disabled', async () => {
|
||||
const landingPageDisabled = await pageObjects.infraHostsView.getHostsLandingPageDisabled();
|
||||
const learnMoreDocsUrl = await pageObjects.infraHostsView.getHostsLandingPageDocsLink();
|
||||
const parsedUrl = new URL(learnMoreDocsUrl);
|
||||
|
||||
expect(parsedUrl.host).to.be('www.elastic.co');
|
||||
expect(parsedUrl.pathname).to.be('/guide/en/kibana/current/kibana-privileges.html');
|
||||
expect(landingPageDisabled).to.contain(
|
||||
'Your user role doesn’t have sufficient privileges to enable this feature'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enables hosts view page and checks content', () => {
|
||||
before(async () => {
|
||||
await navigateAndEnableHostView();
|
||||
await pageObjects.timePicker.setAbsoluteRange(
|
||||
START_DATE.format(timepickerFormat),
|
||||
END_DATE.format(timepickerFormat)
|
||||
);
|
||||
});
|
||||
after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'));
|
||||
after(async () => {
|
||||
await navigateAndDisableHostView();
|
||||
});
|
||||
|
||||
it('should render the correct page title', async () => {
|
||||
const documentTitle = await browser.getTitle();
|
||||
expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic');
|
||||
describe('should show hosts page for admin user and see the page content', async () => {
|
||||
it('should render the correct page title', async () => {
|
||||
const documentTitle = await browser.getTitle();
|
||||
expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic');
|
||||
});
|
||||
|
||||
it('should have six hosts', async () => {
|
||||
const hosts = await pageObjects.infraHostsView.getHostsTableData();
|
||||
expect(hosts.length).to.equal(6);
|
||||
});
|
||||
|
||||
it('should load 5 metrics trend tiles', async () => {
|
||||
const hosts = await pageObjects.infraHostsView.getAllMetricsTrendTiles();
|
||||
expect(hosts.length).to.equal(5);
|
||||
});
|
||||
|
||||
[
|
||||
{ metric: 'hosts', value: '6' },
|
||||
{ metric: 'cpu', value: '0.8%' },
|
||||
{ metric: 'memory', value: '16.8%' },
|
||||
{ metric: 'tx', value: '0 bit/s' },
|
||||
{ metric: 'rx', value: '0 bit/s' },
|
||||
].forEach(({ metric, value }) => {
|
||||
it(`${metric} tile should show ${value}`, async () => {
|
||||
const tileValue = await pageObjects.infraHostsView.getMetricsTrendTileValue(metric);
|
||||
expect(tileValue).to.eql(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should show hosts page for read only user and see the page content', async () => {
|
||||
before(async () => {
|
||||
await navigateAndEnableHostView();
|
||||
await loginWithReadOnlyUserAndNavigateToInfra();
|
||||
await pageObjects.infraHostsView.clickTryHostViewLink();
|
||||
await pageObjects.timePicker.setAbsoluteRange(
|
||||
START_DATE.format(timepickerFormat),
|
||||
END_DATE.format(timepickerFormat)
|
||||
);
|
||||
});
|
||||
after(async () => {
|
||||
// NOTE: Logout needs to happen before anything else to avoid flaky behavior
|
||||
await logoutAndDeleteReadOnlyUser();
|
||||
await navigateAndDisableHostView();
|
||||
});
|
||||
|
||||
it('should have six hosts', async () => {
|
||||
|
|
|
@ -55,7 +55,6 @@ export default async function ({ readConfigFile }) {
|
|||
'--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true',
|
||||
'--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects,
|
||||
'--uiSettings.overrides.observability:enableNewSyntheticsView=true', // for OSS test management/_import_objects,
|
||||
'--uiSettings.overrides.observability:enableInfrastructureHostsView=true', // for Infra/Hosts View test,
|
||||
],
|
||||
},
|
||||
uiSettings: {
|
||||
|
|
|
@ -11,6 +11,35 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
|
|||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
async clickTryHostViewLink() {
|
||||
return await testSubjects.click('inventory-hostsView-link');
|
||||
},
|
||||
|
||||
async clickTryHostViewBadge() {
|
||||
return await testSubjects.click('inventory-hostsView-badge');
|
||||
},
|
||||
|
||||
async getHostsLandingPageDisabled() {
|
||||
const container = await testSubjects.find('hostView-no-enable-access');
|
||||
const containerText = await container.getVisibleText();
|
||||
return containerText;
|
||||
},
|
||||
|
||||
async getHostsLandingPageDocsLink() {
|
||||
const container = await testSubjects.find('hostsView-docs-link');
|
||||
const containerText = await container.getAttribute('href');
|
||||
return containerText;
|
||||
},
|
||||
|
||||
async getHostsLandingPageEnableButton() {
|
||||
const container = await testSubjects.find('hostsView-enable-feature-button');
|
||||
return container;
|
||||
},
|
||||
|
||||
async clickEnableHostViewButton() {
|
||||
return await testSubjects.click('hostsView-enable-feature-button');
|
||||
},
|
||||
|
||||
async getHostsTableData() {
|
||||
const table = await testSubjects.find('hostsView-table');
|
||||
return table.findAllByTestSubject('hostsView-tableRow');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue