diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index dd33535c2562a..fc46fa24f257f 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -272,6 +272,7 @@ enabled: - x-pack/test/functional/config.firefox.js - x-pack/test/functional/config.upgrade_assistant.ts - x-pack/test/functional_cloud/config.ts + - x-pack/test/functional_solution_sidenav/config.ts - x-pack/test/kubernetes_security/basic/config.ts - x-pack/test/licensing_plugin/config.public.ts - x-pack/test/licensing_plugin/config.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 01af7203dc02b..23ebc16c8d922 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1762,6 +1762,8 @@ x-pack/plugins/observability_solution/observability_shared/public/components/pro # Shared UX packages/react @elastic/appex-sharedux +test/functional/page_objects/solution_navigation.ts @elastic/appex-sharedux +/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts @elastic/appex-sharedux # OpenAPI spec files /x-pack/plugins/fleet/common/openapi @elastic/platform-docs diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts index 0a1292be9b3f1..0c40edfc26292 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts @@ -20,6 +20,7 @@ import type { } from '@kbn/core-chrome-browser'; import type { InternalHttpStart } from '@kbn/core-http-browser-internal'; import { + Subject, BehaviorSubject, combineLatest, map, @@ -32,6 +33,7 @@ import { of, type Observable, type Subscription, + timer, } from 'rxjs'; import { type Location, createLocation } from 'history'; import deepEqual from 'react-fast-compare'; @@ -326,20 +328,50 @@ export class ProjectNavigationService { } const { sideNavComponent, homePage = '' } = definition; - const homePageLink = this.navLinksService?.get(homePage); if (sideNavComponent) { this.setSideNavComponent(sideNavComponent); } - if (homePageLink) { - this.setProjectHome(homePageLink.href); - } + this.waitForLink(homePage, (navLink: ChromeNavLink) => { + this.setProjectHome(navLink.href); + }); this.initNavigation(nextId, definition.navigationTree$); }); } + /** + * This method waits for the chrome nav link to be available and then calls the callback. + * This is necessary to avoid race conditions when we register the solution navigation + * before the deep links are available (plugins can register them later). + * + * @param linkId The chrome nav link id + * @param cb The callback to call when the link is found + * @returns + */ + private waitForLink(linkId: string, cb: (chromeNavLink: ChromeNavLink) => undefined): void { + if (!this.navLinksService) return; + + let navLink: ChromeNavLink | undefined = this.navLinksService.get(linkId); + if (navLink) { + cb(navLink); + return; + } + + const stop$ = new Subject(); + const tenSeconds = timer(10000); + + this.deepLinksMap$.pipe(takeUntil(tenSeconds), takeUntil(stop$)).subscribe((navLinks) => { + navLink = navLinks[linkId]; + + if (navLink) { + cb(navLink); + stop$.next(); + } + }); + } + private setProjectHome(homeHref: string) { this.projectHome$.next(homeHref); } diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts index a5ca11ad20203..c9956dc8a666e 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts @@ -8,6 +8,7 @@ /* eslint-disable max-classes-per-file */ import { Entity, Fields } from '../entity'; import { Serializable } from '../serializable'; +import { k8sNode } from './k8s_node'; import { pod } from './pod'; interface HostDocument extends Fields { @@ -20,17 +21,20 @@ interface HostDocument extends Fields { 'host.ip'?: string; 'host.os.name'?: string; 'host.os.version'?: string; + 'host.os.platform'?: string; 'cloud.provider'?: string; } class Host extends Entity { - cpu() { + cpu({ cpuTotalValue }: { cpuTotalValue?: number } = {}) { return new HostMetrics({ ...this.fields, - 'system.cpu.total.norm.pct': 0.094, + 'system.cpu.total.norm.pct': cpuTotalValue ?? 0.98, 'system.cpu.user.pct': 0.805, 'system.cpu.system.pct': 0.704, 'system.cpu.cores': 16, + 'process.cpu.pct': 0.1, + 'system.cpu.nice.pct': 0.1, 'metricset.period': 10000, 'metricset.name': 'cpu', }); @@ -45,6 +49,7 @@ class Host extends Entity { 'system.memory.total': 68719476736, 'system.memory.used.bytes': 39964708864, 'system.memory.used.pct': 0.582, + 'process.memory.pct': 0.1, 'metricset.period': 10000, 'metricset.name': 'memory', }); @@ -72,6 +77,22 @@ class Host extends Entity { }); } + core() { + return new HostMetrics({ + ...this.fields, + 'system.core.total.pct': 0.98, + 'system.core.user.pct': 0.805, + 'system.core.nice.pct': 0.704, + 'system.core.idle.pct': 0.1, + 'system.core.iowait.pct': 0.1, + 'system.core.irq.pct': 0.1, + 'system.core.softirq.pct': 0.1, + 'system.core.steal.pct': 0.1, + 'metricset.period': 10000, + 'metricset.name': 'core', + }); + } + filesystem() { return new HostMetrics({ ...this.fields, @@ -96,6 +117,10 @@ class Host extends Entity { pod(uid: string) { return pod(uid, this.fields['host.hostname']); } + + node(podUid: string) { + return k8sNode(this.fields['host.hostname'], podUid); + } } export interface HostMetricsDocument extends HostDocument { @@ -120,6 +145,17 @@ export interface HostMetricsDocument extends HostDocument { 'system.load'?: { 1: number; cores: number }; 'host.network.ingress.bytes'?: number; 'host.network.egress.bytes'?: number; + 'process.cpu.pct'?: number; + 'process.memory.pct'?: number; + 'system.core.total.pct'?: number; + 'system.core.user.pct'?: number; + 'system.core.nice.pct'?: number; + 'system.core.idle.pct'?: number; + 'system.core.iowait.pct'?: number; + 'system.core.irq.pct'?: number; + 'system.core.softirq.pct'?: number; + 'system.core.steal.pct'?: number; + 'system.cpu.nice.pct'?: number; } class HostMetrics extends Serializable {} @@ -132,6 +168,7 @@ export function host(name: string): Host { 'host.name': name, 'host.ip': '10.128.0.2', 'host.os.name': 'Linux', + 'host.os.platform': 'ubuntu', 'host.os.version': '4.19.76-linuxkit', 'cloud.provider': 'gcp', }); diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts index c325635e27098..b8b0600fc1838 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts @@ -11,13 +11,15 @@ import { host, HostMetricsDocument } from './host'; import { k8sContainer, K8sContainerMetricsDocument } from './k8s_container'; import { pod, PodMetricsDocument } from './pod'; import { awsRds, AWSRdsMetricsDocument } from './aws/rds'; +import { k8sNode, K8sNodeMetricsDocument } from './k8s_node'; export type InfraDocument = | HostMetricsDocument | PodMetricsDocument | DockerContainerMetricsDocument | K8sContainerMetricsDocument - | AWSRdsMetricsDocument; + | AWSRdsMetricsDocument + | K8sNodeMetricsDocument; export const infra = { host, @@ -25,4 +27,5 @@ export const infra = { dockerContainer, k8sContainer, awsRds, + k8sNode, }; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts index 6aa813913c118..7135581f6129c 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts @@ -18,11 +18,13 @@ interface K8sContainerDocument extends Fields { 'container.name'?: string; 'container.image.name'?: string; 'container.runtime'?: string; - 'host.name'?: string; + 'host.name': string; + 'host.hostname': string; 'cloud.provider'?: string; 'cloud.instance.id'?: string; 'cloud.image.id'?: string; 'event.dataset'?: string; + 'agent.id': string; } export class K8sContainer extends Entity { @@ -31,6 +33,7 @@ export class K8sContainer extends Entity { ...this.fields, 'kubernetes.container.cpu.usage.limit.pct': 46, 'kubernetes.container.memory.usage.limit.pct': 30, + 'kubernetes.pod.cpu.usage.limit.pct': 46, }); } } @@ -38,6 +41,7 @@ export class K8sContainer extends Entity { export interface K8sContainerMetricsDocument extends K8sContainerDocument { 'kubernetes.container.cpu.usage.limit.pct': number; 'kubernetes.container.memory.usage.limit.pct': number; + 'kubernetes.pod.cpu.usage.limit.pct': number; } class K8sContainerMetrics extends Serializable {} @@ -51,6 +55,8 @@ export function k8sContainer(id: string, uid: string, nodeName: string): K8sCont 'container.runtime': 'containerd', 'container.image.name': 'image-1', 'host.name': 'host-1', + 'host.hostname': 'host-1', + 'agent.id': 'synthtrace', 'cloud.instance.id': 'instance-1', 'cloud.image.id': 'image-1', 'cloud.provider': 'aws', diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_node.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_node.ts new file mode 100644 index 0000000000000..8b596a6591669 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_node.ts @@ -0,0 +1,58 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +/* eslint-disable max-classes-per-file */ +import { Entity, Fields } from '../entity'; +import { Serializable } from '../serializable'; + +interface K8sNodeDocument extends Fields { + 'kubernetes.node.name': string; + 'kubernetes.pod.uid'?: string; + 'agent.id': string; + 'host.hostname': string; + 'host.name': string; + 'metricset.name'?: string; + 'event.dataset'?: string; +} + +export class K8sNode extends Entity { + metrics() { + return new K8sNodeMetrics({ + ...this.fields, + 'kubernetes.node.cpu.allocatable.cores': 0.53, + 'kubernetes.node.cpu.usage.nanocores': 0.32, + 'kubernetes.node.memory.allocatable.bytes': 0.46, + 'kubernetes.node.memory.usage.bytes': 0.86, + 'kubernetes.node.fs.capacity.bytes': 100, + 'kubernetes.node.fs.used.bytes': 100, + 'kubernetes.node.pod.allocatable.total': 10, + }); + } +} + +export interface K8sNodeMetricsDocument extends K8sNodeDocument { + 'kubernetes.node.cpu.allocatable.cores': number; + 'kubernetes.node.cpu.usage.nanocores': number; + 'kubernetes.node.memory.allocatable.bytes': number; + 'kubernetes.node.memory.usage.bytes': number; + 'kubernetes.node.fs.capacity.bytes': number; + 'kubernetes.node.fs.used.bytes': number; + 'kubernetes.node.pod.allocatable.total': number; +} + +class K8sNodeMetrics extends Serializable {} + +export function k8sNode(name: string, podUid: string) { + return new K8sNode({ + 'kubernetes.node.name': name, + 'kubernetes.pod.uid': podUid, + 'agent.id': 'synthtrace', + 'host.hostname': name, + 'host.name': name, + 'event.dataset': 'kubernetes.node', + }); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts index 35ebe94ba6ee1..b885fd1aeb606 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts @@ -12,6 +12,9 @@ import { Serializable } from '../serializable'; import { k8sContainer } from './k8s_container'; interface PodDocument extends Fields { + 'agent.id': string; + 'host.hostname': string; + 'host.name': string; 'kubernetes.pod.uid': string; 'kubernetes.node.name': string; 'metricset.name'?: string; @@ -40,5 +43,8 @@ export function pod(uid: string, nodeName: string) { return new Pod({ 'kubernetes.pod.uid': uid, 'kubernetes.node.name': nodeName, + 'agent.id': 'synthtrace', + 'host.hostname': nodeName, + 'host.name': nodeName, }); } diff --git a/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_es_client.ts index 6c42d7051a9b0..dcd5e6da2d261 100644 --- a/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_es_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_es_client.ts @@ -22,7 +22,14 @@ export class InfraSynthtraceEsClient extends SynthtraceEsClient { ...options, pipeline: infraPipeline(), }); - this.dataStreams = ['metrics-*', 'logs-*']; + this.dataStreams = [ + 'metrics-system*', + 'metrics-kubernetes*', + 'metrics-docker*', + 'metrics-aws*', + 'metricbeat-*', + 'logs-*', + ]; } } @@ -60,7 +67,10 @@ function getRoutingTransform() { document._index = 'metrics-system.filesystem-default'; } else if (metricset === 'diskio') { document._index = 'metrics-system.diskio-default'; + } else if (metricset === 'core') { + document._index = 'metrics-system.core-default'; } else if ('container.id' in document) { + document._index = 'metrics-docker.container-default'; document._index = 'metrics-kubernetes.container-default'; } else if ('kubernetes.pod.uid' in document) { document._index = 'metrics-kubernetes.pod-default'; diff --git a/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_kibana_client.ts b/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_kibana_client.ts index b39efada2abff..5c6e02aaedc3a 100644 --- a/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_kibana_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_kibana_client.ts @@ -70,4 +70,27 @@ export class InfraSynthtraceKibanaClient { this.logger.info(`Installed System package ${packageVersion}`); } + + async uninstallSystemPackage(packageVersion: string) { + this.logger.debug(`Uninstalling System package ${packageVersion}`); + + const url = join(this.target, `/api/fleet/epm/packages/system/${packageVersion}`); + const response = await pRetry(() => { + return fetch(url, { + method: 'DELETE', + headers: kibanaHeaders(), + body: '{"force":true}', + }); + }); + + const responseJson = await response.json(); + + if (!responseJson.items) { + throw new Error( + `Failed to uninstall System package version ${packageVersion}, received HTTP ${response.status} and message: ${responseJson.message} for url ${url}` + ); + } + + this.logger.info(`System package ${packageVersion} uninstalled`); + } } diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx index 76c36c03c0ac6..8a811c95ff298 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx @@ -73,6 +73,7 @@ export const NavigationItemOpenPanel: FC = ({ item, navigateToUrl, active [`nav-item-isActive`]: isActive, }); const buttonDataTestSubj = classNames(`panelOpener`, `panelOpener-${path}`, { + [`panelOpener-id-${id}`]: id, [`panelOpener-deepLinkId-${deepLink?.id}`]: !!deepLink, }); diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx index ff79824176268..19757bc533d68 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/navigation_panel.tsx @@ -19,6 +19,17 @@ import classNames from 'classnames'; import { usePanel } from './context'; import { getNavPanelStyles, getPanelWrapperStyles } from './styles'; +import { PanelNavNode } from './types'; + +const getTestSubj = (selectedNode: PanelNavNode | null): string | undefined => { + if (!selectedNode) return; + + const deeplinkId = selectedNode.deepLink?.id; + return classNames(`sideNavPanel`, { + [`sideNavPanel-id-${selectedNode.id}`]: selectedNode.id, + [`sideNavPanel-deepLinkId-${deeplinkId}`]: !!deeplinkId, + }); +}; export const NavigationPanel: FC = () => { const { euiTheme } = useEuiTheme(); @@ -67,7 +78,7 @@ export const NavigationPanel: FC = () => { hasShadow borderRadius="none" paddingSize="m" - data-test-subj="sideNavPanel" + data-test-subj={getTestSubj(selectedNode)} > {getContent()} diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts b/packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts index 25b2e00c83a81..04ebf2ec0b76b 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/components/panel/types.ts @@ -27,7 +27,7 @@ export type ContentProvider = (nodeId: string) => PanelContent | void; export type PanelNavNode = Pick< ChromeProjectNavigationNode, - 'id' | 'children' | 'path' | 'sideNavStatus' + 'id' | 'children' | 'path' | 'sideNavStatus' | 'deepLink' > & { title: string | ReactNode; }; diff --git a/test/functional/page_objects/embedded_console.ts b/test/functional/page_objects/embedded_console.ts new file mode 100644 index 0000000000000..3f656969e79e2 --- /dev/null +++ b/test/functional/page_objects/embedded_console.ts @@ -0,0 +1,49 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { FtrProviderContext } from '../ftr_provider_context'; + +export function EmbeddedConsoleProvider(ctx: FtrProviderContext) { + const testSubjects = ctx.getService('testSubjects'); + + return { + async expectEmbeddedConsoleControlBarExists() { + await testSubjects.existOrFail('consoleEmbeddedSection'); + }, + async expectEmbeddedConsoleToBeOpen() { + await testSubjects.existOrFail('consoleEmbeddedBody'); + }, + async expectEmbeddedConsoleToBeClosed() { + await testSubjects.missingOrFail('consoleEmbeddedBody'); + }, + async clickEmbeddedConsoleControlBar() { + await testSubjects.click('consoleEmbeddedControlBar'); + }, + async expectEmbeddedConsoleNotebooksButtonExists() { + await testSubjects.existOrFail('consoleEmbeddedNotebooksButton'); + }, + async clickEmbeddedConsoleNotebooksButton() { + await testSubjects.click('consoleEmbeddedNotebooksButton'); + }, + async expectEmbeddedConsoleNotebooksToBeOpen() { + await testSubjects.existOrFail('consoleEmbeddedNotebooksContainer'); + }, + async expectEmbeddedConsoleNotebooksToBeClosed() { + await testSubjects.missingOrFail('consoleEmbeddedNotebooksContainer'); + }, + async expectEmbeddedConsoleNotebookListItemToBeAvailable(id: string) { + await testSubjects.existOrFail(`console-embedded-notebook-select-btn-${id}`); + }, + async clickEmbeddedConsoleNotebook(id: string) { + await testSubjects.click(`console-embedded-notebook-select-btn-${id}`); + }, + async expectEmbeddedConsoleNotebookToBeAvailable(id: string) { + await testSubjects.click(`console-embedded-notebook-select-btn-${id}`); + }, + }; +} diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index 34859cfe943d3..00947dfc33695 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -36,6 +36,8 @@ import { UnifiedSearchPageObject } from './unified_search_page'; import { UnifiedFieldListPageObject } from './unified_field_list'; import { FilesManagementPageObject } from './files_management'; import { AnnotationEditorPageObject } from './annotation_library_editor_page'; +import { SolutionNavigationProvider } from './solution_navigation'; +import { EmbeddedConsoleProvider } from './embedded_console'; export const pageObjects = { annotationEditor: AnnotationEditorPageObject, @@ -46,12 +48,14 @@ export const pageObjects = { dashboardControls: DashboardPageControls, dashboardLinks: DashboardPageLinks, discover: DiscoverPageObject, + embeddedConsole: EmbeddedConsoleProvider, error: ErrorPageObject, header: HeaderPageObject, home: HomePageObject, newsfeed: NewsfeedPageObject, settings: SettingsPageObject, share: SharePageObject, + solutionNavigation: SolutionNavigationProvider, legacyDataTableVis: LegacyDataTableVisPageObject, login: LoginPageObject, timelion: TimelionPageObject, @@ -69,3 +73,5 @@ export const pageObjects = { unifiedFieldList: UnifiedFieldListPageObject, filesManagement: FilesManagementPageObject, }; + +export { SolutionNavigationProvider } from './solution_navigation'; diff --git a/test/functional/page_objects/solution_navigation.ts b/test/functional/page_objects/solution_navigation.ts new file mode 100644 index 0000000000000..d42cad60641e9 --- /dev/null +++ b/test/functional/page_objects/solution_navigation.ts @@ -0,0 +1,338 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import type { AppDeepLinkId } from '@kbn/core-chrome-browser'; + +import type { NavigationID as MlNavId } from '@kbn/default-nav-ml'; +import type { NavigationID as AlNavId } from '@kbn/default-nav-analytics'; +import type { NavigationID as MgmtNavId } from '@kbn/default-nav-management'; +import type { NavigationID as DevNavId } from '@kbn/default-nav-devtools'; + +// use this for nicer type suggestions, but allow any string anyway +type NavigationId = MlNavId | AlNavId | MgmtNavId | DevNavId | string; + +import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; +import type { FtrProviderContext } from '../ftr_provider_context'; + +const getSectionIdTestSubj = (sectionId: NavigationId) => `~nav-item-${sectionId}`; + +const TIMEOUT_CHECK = 3000; + +export function SolutionNavigationProvider(ctx: Pick) { + const testSubjects = ctx.getService('testSubjects'); + const browser = ctx.getService('browser'); + const retry = ctx.getService('retry'); + const log = ctx.getService('log'); + + async function getByVisibleText( + selector: string | (() => Promise), + text: string + ) { + const subjects = + typeof selector === 'string' ? await testSubjects.findAll(selector) : await selector(); + let found: WebElementWrapper | null = null; + for (const subject of subjects) { + const visibleText = await subject.getVisibleText(); + if (visibleText === text) { + found = subject; + break; + } + } + return found; + } + + return { + // check that chrome ui is in project/solution mode + async expectExists() { + await testSubjects.existOrFail('kibanaProjectHeader'); + }, + async clickLogo() { + await testSubjects.click('nav-header-logo'); + }, + // side nav related actions + sidenav: { + async expectLinkExists( + by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string } + ) { + if ('deepLinkId' in by) { + await testSubjects.existOrFail(`~nav-item-deepLinkId-${by.deepLinkId}`, { + timeout: TIMEOUT_CHECK, + }); + } else if ('navId' in by) { + await testSubjects.existOrFail(`~nav-item-id-${by.navId}`, { timeout: TIMEOUT_CHECK }); + } else { + expect(await getByVisibleText('~nav-item', by.text)).not.be(null); + } + }, + async expectLinkMissing( + by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string } + ) { + if ('deepLinkId' in by) { + await testSubjects.missingOrFail(`~nav-item-deepLinkId-${by.deepLinkId}`, { + timeout: TIMEOUT_CHECK, + }); + } else if ('navId' in by) { + await testSubjects.missingOrFail(`~nav-item-id-${by.navId}`, { timeout: TIMEOUT_CHECK }); + } else { + expect(await getByVisibleText('~nav-item', by.text)).be(null); + } + }, + async expectLinkActive( + by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string } + ) { + await this.expectLinkExists(by); + if ('deepLinkId' in by) { + await testSubjects.existOrFail( + `~nav-item-deepLinkId-${by.deepLinkId} & ~nav-item-isActive`, + { timeout: TIMEOUT_CHECK } + ); + } else if ('navId' in by) { + await testSubjects.existOrFail(`~nav-item-id-${by.navId} & ~nav-item-isActive`, { + timeout: TIMEOUT_CHECK, + }); + } else { + await retry.try(async () => { + const link = await getByVisibleText('~nav-item', by.text); + expect(await link!.elementHasClass(`nav-item-isActive`)).to.be(true); + }); + } + }, + async clickLink(by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }) { + await this.expectLinkExists(by); + if ('deepLinkId' in by) { + await testSubjects.click(`~nav-item-deepLinkId-${by.deepLinkId}`); + } else if ('navId' in by) { + await testSubjects.click(`~nav-item-id-${by.navId}`); + } else { + await retry.try(async () => { + const link = await getByVisibleText('~nav-item', by.text); + await link!.click(); + }); + } + }, + async findLink(by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }) { + await this.expectLinkExists(by); + if ('deepLinkId' in by) { + return testSubjects.find(`~nav-item-deepLinkId-${by.deepLinkId}`); + } else if ('navId' in by) { + return testSubjects.find(`~nav-item-id-${by.navId}`); + } else { + return retry.try(async () => { + const link = await getByVisibleText('~nav-item', by.text); + return link; + }); + } + }, + async expectSectionExists(sectionId: NavigationId) { + log.debug('SolutionNavigation.sidenav.expectSectionExists', sectionId); + await testSubjects.existOrFail(getSectionIdTestSubj(sectionId), { timeout: TIMEOUT_CHECK }); + }, + async isSectionOpen(sectionId: NavigationId) { + await this.expectSectionExists(sectionId); + const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`); + const isExpanded = await collapseBtn.getAttribute('aria-expanded'); + return isExpanded === 'true'; + }, + async expectSectionOpen(sectionId: NavigationId) { + log.debug('SolutionNavigation.sidenav.expectSectionOpen', sectionId); + await this.expectSectionExists(sectionId); + await retry.waitFor(`section ${sectionId} to be open`, async () => { + const isOpen = await this.isSectionOpen(sectionId); + return isOpen; + }); + }, + async expectSectionClosed(sectionId: NavigationId) { + await this.expectSectionExists(sectionId); + await retry.waitFor(`section ${sectionId} to be closed`, async () => { + const isOpen = await this.isSectionOpen(sectionId); + return !isOpen; + }); + }, + async openSection(sectionId: NavigationId) { + log.debug('SolutionNavigation.sidenav.openSection', sectionId); + await this.expectSectionExists(sectionId); + const isOpen = await this.isSectionOpen(sectionId); + if (isOpen) return; + const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`, TIMEOUT_CHECK); + await collapseBtn.click(); + await this.expectSectionOpen(sectionId); + }, + async closeSection(sectionId: NavigationId) { + await this.expectSectionExists(sectionId); + const isOpen = await this.isSectionOpen(sectionId); + if (!isOpen) return; + const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`, TIMEOUT_CHECK); + await collapseBtn.click(); + await this.expectSectionClosed(sectionId); + }, + async expectPanelExists(sectionId: NavigationId) { + log.debug('SolutionNavigation.sidenav.expectPanelExists', sectionId); + await testSubjects.existOrFail(`~sideNavPanel-id-${sectionId}`, { + timeout: TIMEOUT_CHECK, + }); + }, + async isPanelOpen(sectionId: NavigationId) { + try { + const panel = await testSubjects.find(`~sideNavPanel-id-${sectionId}`, TIMEOUT_CHECK); + return !!panel; + } catch (err) { + return false; + } + }, + async openPanel(sectionId: NavigationId) { + log.debug('SolutionNavigation.sidenav.openPanel', sectionId); + + const isOpen = await this.isPanelOpen(sectionId); + if (isOpen) return; + + const panelOpenerBtn = await testSubjects.find( + `~panelOpener-id-${sectionId}`, + TIMEOUT_CHECK + ); + + await panelOpenerBtn.click(); + }, + async isCollapsed() { + const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton', TIMEOUT_CHECK); + return (await collapseNavBtn.getAttribute('aria-expanded')) === 'false'; + }, + async isExpanded() { + return !(await this.isCollapsed()); + }, + /** + * Toggles collapsed state of sidenav + */ + async toggle(collapsed?: boolean) { + const currentlyCollapsed = await this.isCollapsed(); + const shouldBeCollapsed = collapsed ?? !currentlyCollapsed; + + if (currentlyCollapsed !== shouldBeCollapsed) { + log.debug( + 'SolutionNavigation.sidenav.toggle', + shouldBeCollapsed ? 'Collapsing' : 'Expanding' + ); + + const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton', TIMEOUT_CHECK); + await collapseNavBtn.click(); + } + }, + }, + breadcrumbs: { + async expectExists() { + await testSubjects.existOrFail('breadcrumbs', { timeout: TIMEOUT_CHECK }); + }, + async clickBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) { + if ('deepLinkId' in by) { + await testSubjects.click(`~breadcrumb-deepLinkId-${by.deepLinkId}`); + } else { + await (await getByVisibleText('~breadcrumb', by.text))?.click(); + } + }, + getBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) { + if ('deepLinkId' in by) { + return testSubjects.find(`~breadcrumb-deepLinkId-${by.deepLinkId}`, TIMEOUT_CHECK); + } else { + return getByVisibleText('~breadcrumb', by.text); + } + }, + async expectBreadcrumbExists(by: { deepLinkId: AppDeepLinkId } | { text: string }) { + log.debug('SolutionNavigation.breadcrumbs.expectBreadcrumbExists', JSON.stringify(by)); + if ('deepLinkId' in by) { + await testSubjects.existOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`, { + timeout: TIMEOUT_CHECK, + }); + } else { + await retry.try(async () => { + expect(await getByVisibleText('~breadcrumb', by.text)).not.be(null); + }); + } + }, + async expectBreadcrumbMissing(by: { deepLinkId: AppDeepLinkId } | { text: string }) { + if ('deepLinkId' in by) { + await testSubjects.missingOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`, { + timeout: TIMEOUT_CHECK, + }); + } else { + await retry.try(async () => { + expect(await getByVisibleText('~breadcrumb', by.text)).be(null); + }); + } + }, + async expectBreadcrumbTexts(expectedBreadcrumbTexts: string[]) { + log.debug( + 'SolutionNavigation.breadcrumbs.expectBreadcrumbTexts', + JSON.stringify(expectedBreadcrumbTexts) + ); + await retry.try(async () => { + const breadcrumbsContainer = await testSubjects.find('breadcrumbs', TIMEOUT_CHECK); + const breadcrumbs = await breadcrumbsContainer.findAllByTestSubject('~breadcrumb'); + breadcrumbs.shift(); // remove home + expect(expectedBreadcrumbTexts.length).to.eql(breadcrumbs.length); + const texts = await Promise.all(breadcrumbs.map((b) => b.getVisibleText())); + expect(expectedBreadcrumbTexts).to.eql(texts); + }); + }, + }, + recent: { + async expectExists() { + await testSubjects.existOrFail('nav-item-recentlyAccessed', { timeout: TIMEOUT_CHECK }); + }, + async expectHidden() { + await testSubjects.missingOrFail('nav-item-recentlyAccessed', { timeout: TIMEOUT_CHECK }); + }, + async expectLinkExists(text: string) { + await this.expectExists(); + let foundLink: WebElementWrapper | null = null; + await retry.try(async () => { + foundLink = await getByVisibleText( + async () => + ( + await testSubjects.find('nav-item-recentlyAccessed', TIMEOUT_CHECK) + ).findAllByTagName('a'), + text + ); + expect(!!foundLink).to.be(true); + }); + + return foundLink!; + }, + async clickLink(text: string) { + const link = await this.expectLinkExists(text); + await link!.click(); + }, + }, + + // helper to assert that the page did not reload + async createNoPageReloadCheck() { + const trackReloadTs = Date.now(); + await browser.execute( + ({ ts }) => { + // @ts-ignore + window.__testTrackReload__ = ts; + }, + { + ts: trackReloadTs, + } + ); + + return async () => { + const noReload = await browser.execute( + ({ ts }) => { + // @ts-ignore + return window.__testTrackReload__ && window.__testTrackReload__ === ts; + }, + { + ts: trackReloadTs, + } + ); + expect(noReload).to.be(true); + }; + }, + }; +} diff --git a/test/tsconfig.json b/test/tsconfig.json index 0f120b831abe7..b03067c0440ae 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -73,5 +73,10 @@ "@kbn/monaco", "@kbn/search-types", "@kbn/console-plugin", + "@kbn/core-chrome-browser", + "@kbn/default-nav-ml", + "@kbn/default-nav-analytics", + "@kbn/default-nav-management", + "@kbn/default-nav-devtools", ] } diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index ed8d76c1949a7..fde8e1f4537a7 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -170,7 +170,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables experimental JAMF integration data to be available in Analyzer */ - jamfDataInAnalyzerEnabled: false, + jamfDataInAnalyzerEnabled: true, /* * Disables discover esql tab within timeline diff --git a/x-pack/test/common/services/spaces.ts b/x-pack/test/common/services/spaces.ts index b4e99cacee571..9d11935bd7068 100644 --- a/x-pack/test/common/services/spaces.ts +++ b/x-pack/test/common/services/spaces.ts @@ -10,8 +10,22 @@ import Axios from 'axios'; import Https from 'https'; import { format as formatUrl } from 'url'; import util from 'util'; +import Chance from 'chance'; +import Url from 'url'; import { FtrProviderContext } from '../ftr_provider_context'; +const chance = new Chance(); + +interface SpaceCreate { + name?: string; + id?: string; + description?: string; + color?: string; + initials?: string; + solution?: 'es' | 'oblt' | 'security' | 'classic'; + disabledFeatures?: string[]; +} + export function SpacesServiceProvider({ getService }: FtrProviderContext) { const log = getService('log'); const config = getService('config'); @@ -35,7 +49,9 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) { }); return new (class SpacesService { - public async create(space: any) { + public async create(_space?: SpaceCreate) { + const space = { id: chance.guid(), name: 'foo', ..._space }; + log.debug(`creating space ${space.id}`); const { data, status, statusText } = await axios.post('/api/spaces/space', space); @@ -45,6 +61,15 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) { ); } log.debug(`created space ${space.id}`); + + const cleanUp = async () => { + return this.delete(space.id); + }; + + return { + cleanUp, + space, + }; } public async delete(spaceId: string) { @@ -72,5 +97,17 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) { return data; } + + /** Return the full URL that points to the root of the space */ + public getRootUrl(spaceId: string) { + const { protocol, hostname, port } = config.get('servers.kibana'); + + return Url.format({ + protocol, + hostname, + port, + pathname: `/s/${spaceId}`, + }); + } })(); } diff --git a/x-pack/test/common/utils/synthtrace/logs_es_client.ts b/x-pack/test/common/utils/synthtrace/logs_es_client.ts new file mode 100644 index 0000000000000..4d7222818bb9c --- /dev/null +++ b/x-pack/test/common/utils/synthtrace/logs_es_client.ts @@ -0,0 +1,17 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { LogsSynthtraceEsClient, createLogger, LogLevel } from '@kbn/apm-synthtrace'; + +export async function getLogsSynthtraceEsClient(client: Client) { + return new LogsSynthtraceEsClient({ + client, + logger: createLogger(LogLevel.info), + refreshAfterIndex: true, + }); +} diff --git a/x-pack/test/functional/apps/infra/constants.ts b/x-pack/test/functional/apps/infra/constants.ts index 4c1d0d29a6ca4..d5e929c1bcc0f 100644 --- a/x-pack/test/functional/apps/infra/constants.ts +++ b/x-pack/test/functional/apps/infra/constants.ts @@ -56,6 +56,16 @@ export const HOSTS_VIEW_PATH = 'metrics/hosts'; export const DATE_PICKER_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS'; +// synthtrace date constants + export const DATE_WITH_DOCKER_DATA_FROM = '2023-03-28T18:20:00.000Z'; export const DATE_WITH_DOCKER_DATA_TO = '2023-03-28T18:21:00.000Z'; export const DATE_WITH_DOCKER_DATA = '03/28/2023 6:20:59 PM'; + +export const DATE_WITH_HOSTS_DATA_FROM = '2023-03-28T18:20:00.000Z'; +export const DATE_WITH_HOSTS_DATA_TO = '2023-03-28T18:21:00.000Z'; +export const DATE_WITH_HOSTS_DATA = '03/28/2023 6:20:59 PM'; + +export const DATE_WITH_POD_DATA_FROM = '2023-09-19T07:20:00.000Z'; +export const DATE_WITH_POD_DATA_TO = '2023-09-19T07:21:00.000Z'; +export const DATE_WITH_POD_DATA = '09/19/2023 7:20:59 AM'; diff --git a/x-pack/test/functional/apps/infra/helpers.ts b/x-pack/test/functional/apps/infra/helpers.ts index 2ddabf314390f..48f94303a42cb 100644 --- a/x-pack/test/functional/apps/infra/helpers.ts +++ b/x-pack/test/functional/apps/infra/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { apm, timerange, infra } from '@kbn/apm-synthtrace-client'; +import { apm, timerange, infra, generateShortId, log } from '@kbn/apm-synthtrace-client'; const SERVICE_PREFIX = 'service'; // generates traces, metrics for services @@ -69,3 +69,141 @@ export function generateDockerContainersData({ containers.flatMap((container) => container.metrics().timestamp(timestamp)) ); } + +export function generateHostData({ + from, + to, + hosts, +}: { + from: string; + to: string; + hosts: Array<{ hostName: string; cpuValue: number }>; +}) { + const range = timerange(from, to); + + return range + .interval('30s') + .rate(1) + .generator((timestamp) => + hosts.flatMap(({ hostName, cpuValue }) => [ + infra.host(hostName).cpu({ cpuTotalValue: cpuValue }).timestamp(timestamp), + infra.host(hostName).memory().timestamp(timestamp), + infra.host(hostName).network().timestamp(timestamp), + infra.host(hostName).load().timestamp(timestamp), + infra.host(hostName).filesystem().timestamp(timestamp), + infra.host(hostName).diskio().timestamp(timestamp), + infra.host(hostName).core().timestamp(timestamp), + ]) + ); +} + +export function generateHostsWithK8sNodeData({ from, to }: { from: string; to: string }) { + const range = timerange(from, to); + + // cpuValue is sent to the generator to simulate different 'system.cpu.total.norm.pct' metric + // that is the default metric in inventory and hosts view and host details page + const hosts = [ + { + hostName: 'demo-stack-kubernetes-01', + cpuValue: 0.5, + }, + ]; + + return range + .interval('30s') + .rate(1) + .generator((timestamp) => + hosts.flatMap(({ hostName, cpuValue }) => [ + infra.host(hostName).cpu({ cpuTotalValue: cpuValue }).timestamp(timestamp), + infra.host(hostName).memory().timestamp(timestamp), + infra.host(hostName).network().timestamp(timestamp), + infra.host(hostName).load().timestamp(timestamp), + infra.host(hostName).filesystem().timestamp(timestamp), + infra.host(hostName).diskio().timestamp(timestamp), + infra.host(hostName).core().timestamp(timestamp), + infra.host(hostName).node('demo-stack-kubernetes-01').metrics().timestamp(timestamp), + infra.host(hostName).pod('pod-1').metrics().timestamp(timestamp), + ]) + ); +} + +export function generatePodsData({ + from, + to, + count = 1, +}: { + from: string; + to: string; + count: number; +}) { + const range = timerange(from, to); + + const pods = Array(count) + .fill(0) + .map((_, idx) => infra.pod(`pod-${idx}`, `node-name-${idx}`)); + + return range + .interval('30s') + .rate(1) + .generator((timestamp) => + pods.flatMap((pod, idx) => [ + pod.metrics().timestamp(timestamp), + pod.container(`container-${idx}`).metrics().timestamp(timestamp), + ]) + ); +} + +export function generateLogsDataForHosts({ + from, + to, + hosts, +}: { + from: string; + to: string; + hosts: Array<{ hostName: string }>; +}) { + const range = timerange(from, to); + + // Logs Data logic + const MESSAGE_LOG_LEVELS = [ + { message: 'A simple log', level: 'info' }, + { message: 'Yet another debug log', level: 'debug' }, + { message: 'Error with certificate: "ca_trusted_fingerprint"', level: 'error' }, + ]; + const CLOUD_PROVIDERS = ['gcp', 'aws', 'azure']; + const CLOUD_REGION = ['eu-central-1', 'us-east-1', 'area-51']; + + const CLUSTER = [ + { clusterId: generateShortId(), clusterName: 'synth-cluster-1' }, + { clusterId: generateShortId(), clusterName: 'synth-cluster-2' }, + { clusterId: generateShortId(), clusterName: 'synth-cluster-3' }, + ]; + + return range + .interval('30s') + .rate(1) + .generator((timestamp) => + hosts.flatMap(({ hostName }) => { + const index = Math.floor(Math.random() * MESSAGE_LOG_LEVELS.length); + return log + .create() + .message(MESSAGE_LOG_LEVELS[index].message) + .logLevel(MESSAGE_LOG_LEVELS[index].level) + .hostName(hostName) + .defaults({ + 'trace.id': generateShortId(), + 'agent.name': 'synth-agent', + 'orchestrator.cluster.name': CLUSTER[index].clusterName, + 'orchestrator.cluster.id': CLUSTER[index].clusterId, + 'orchestrator.resource.id': generateShortId(), + 'cloud.provider': CLOUD_PROVIDERS[index], + 'cloud.region': CLOUD_REGION[index], + 'cloud.availability_zone': `${CLOUD_REGION[index]}a`, + 'cloud.project.id': generateShortId(), + 'cloud.instance.id': generateShortId(), + 'log.file.path': `/logs/${generateShortId()}/error.txt`, + }) + .timestamp(timestamp); + }) + ); +} diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 163c91593b33b..50e055f5bc6bf 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -8,25 +8,51 @@ import expect from '@kbn/expect'; import { KUBERNETES_TOUR_STORAGE_KEY } from '@kbn/infra-plugin/public/pages/metrics/inventory_view/components/kubernetes_tour'; import { InfraSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import { enableInfrastructureContainerAssetView } from '@kbn/observability-plugin/common'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { DATES, INVENTORY_PATH } from './constants'; -import { generateDockerContainersData } from './helpers'; +import { + INVENTORY_PATH, + DATE_WITH_DOCKER_DATA_FROM, + DATE_WITH_DOCKER_DATA_TO, + DATE_WITH_DOCKER_DATA, + DATE_WITH_HOSTS_DATA_FROM, + DATE_WITH_HOSTS_DATA_TO, + DATE_WITH_HOSTS_DATA, + DATE_WITH_POD_DATA, + DATE_WITH_POD_DATA_FROM, + DATE_WITH_POD_DATA_TO, +} from './constants'; +import { generateDockerContainersData, generateHostData, generatePodsData } from './helpers'; import { getInfraSynthtraceEsClient } from '../../../common/utils/synthtrace/infra_es_client'; -const DATE_WITH_DATA = DATES.metricsAndLogs.hosts.withData; -const DATE_WITHOUT_DATA = DATES.metricsAndLogs.hosts.withoutData; -const DATE_WITH_POD_WITH_DATA = DATES.metricsAndLogs.pods.withData; -const DATE_WITH_DOCKER_DATA_FROM = '2023-03-28T18:20:00.000Z'; -const DATE_WITH_DOCKER_DATA_TO = '2023-03-28T18:21:00.000Z'; -const DATE_WITH_DOCKER_DATA = '03/28/2023 6:20:00 PM'; +const DATE_WITHOUT_DATA = '10/09/2018 10:00:00 PM'; + +const HOSTS = [ + { + hostName: 'host-1', + cpuValue: 0.5, + }, + { + hostName: 'host-2', + cpuValue: 0.7, + }, + { + hostName: 'host-3', + cpuValue: 0.9, + }, + { + hostName: 'host-4', + cpuValue: 0.3, + }, + { + hostName: 'host-5', + cpuValue: 0.1, + }, +]; export default ({ getPageObjects, getService }: FtrProviderContext) => { - const esArchiver = getService('esArchiver'); const browser = getService('browser'); const retry = getService('retry'); const esClient = getService('es'); - const infraSynthtraceKibanaClient = getService('infraSynthtraceKibanaClient'); const pageObjects = getPageObjects([ 'common', 'header', @@ -46,30 +72,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return !!currentUrl.match(path); }); - const setInfrastructureContainerAssetViewUiSetting = async (value: boolean = true) => { - await kibanaServer.uiSettings.update({ [enableInfrastructureContainerAssetView]: value }); - await browser.refresh(); - await pageObjects.header.waitUntilLoadingHasFinished(); - }; - describe('Home page', function () { this.tags('includeFirefox'); + before(async () => { await kibanaServer.savedObjects.cleanStandardList(); }); describe('without metrics present', () => { - before( - async () => - await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs') - ); - it('renders an empty data prompt and redirects to the onboarding page', async () => { await pageObjects.common.navigateToApp('infraOps'); await pageObjects.infraHome.noDataPromptExists(); await pageObjects.infraHome.noDataPromptAddDataClick(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`; @@ -94,16 +110,33 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('with metrics present', () => { + let synthEsClient: InfraSynthtraceEsClient; before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/pods_only'); + synthEsClient = await getInfraSynthtraceEsClient(esClient); + await synthEsClient.clean(); + await synthEsClient.index([ + generateHostData({ + from: DATE_WITH_HOSTS_DATA_FROM, + to: DATE_WITH_HOSTS_DATA_TO, + hosts: HOSTS, + }), + generateDockerContainersData({ + from: DATE_WITH_DOCKER_DATA_FROM, + to: DATE_WITH_DOCKER_DATA_TO, + count: 5, + }), + generatePodsData({ + from: DATE_WITH_POD_DATA_FROM, + to: DATE_WITH_POD_DATA_TO, + count: 1, + }), + ]); await pageObjects.common.navigateToApp('infraOps'); await pageObjects.infraHome.waitForLoading(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/infra/8.0.0/pods_only'); await browser.removeLocalStorageItem(KUBERNETES_TOUR_STORAGE_KEY); + await synthEsClient.clean(); }); it('renders the correct page title', async () => { @@ -137,7 +170,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await pageObjects.infraHome.ensureKubernetesTourIsClosed(); }); }); @@ -146,22 +179,23 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.goToTime(DATE_WITHOUT_DATA); await pageObjects.infraHome.getNoMetricsDataPrompt(); }); + it('renders the waffle map and tooltips for dates with data', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.getWaffleMap(); // await pageObjects.infraHome.getWaffleMapTooltips(); see https://github.com/elastic/kibana/issues/137903 }); describe('Asset Details flyout for a host', () => { before(async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.getWaffleMap(); - await pageObjects.infraHome.inputAddHostNameFilter('demo-stack-nginx-01'); + await pageObjects.infraHome.inputAddHostNameFilter('host-1'); await pageObjects.infraHome.clickOnNode(); }); after(async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await pageObjects.infraHome.clickCloseFlyoutButton(); }); }); @@ -172,10 +206,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); [ - { metric: 'cpuUsage', value: 'N/A' }, - { metric: 'normalizedLoad1m', value: '1.4%' }, - { metric: 'memoryUsage', value: '18.0%' }, - { metric: 'diskUsage', value: '35.0%' }, + { metric: 'cpuUsage', value: '50.0%' }, + { metric: 'normalizedLoad1m', value: '18.8%' }, + { metric: 'memoryUsage', value: '35.0%' }, + { metric: 'diskUsage', value: '1,223.0%' }, ].forEach(({ metric, value }) => { it(`${metric} tile should show ${value}`, async () => { await retry.tryForTime(3 * 1000, async () => { @@ -237,31 +271,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('Asset Details flyout for a container', () => { - let synthEsClient: InfraSynthtraceEsClient; before(async () => { - await setInfrastructureContainerAssetViewUiSetting(true); - const version = await infraSynthtraceKibanaClient.fetchLatestSystemPackageVersion(); - await infraSynthtraceKibanaClient.installSystemPackage(version); - synthEsClient = await getInfraSynthtraceEsClient(esClient); - await synthEsClient.index( - generateDockerContainersData({ - from: DATE_WITH_DOCKER_DATA_FROM, - to: DATE_WITH_DOCKER_DATA_TO, - count: 5, - }) - ); - - await pageObjects.infraHome.clickDismissKubernetesTourButton(); await pageObjects.infraHome.goToContainer(); await pageObjects.infraHome.goToTime(DATE_WITH_DOCKER_DATA); await pageObjects.infraHome.clickOnFirstNode(); }); - after(async () => { - await setInfrastructureContainerAssetViewUiSetting(false); - return await synthEsClient.clean(); - }); - describe('Overview Tab', () => { before(async () => { await pageObjects.assetDetails.clickOverviewTab(); @@ -272,7 +287,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { { metric: 'memoryUsage', value: '20.0%' }, ].forEach(({ metric, value }) => { it(`${metric} tile should show ${value}`, async () => { - await retry.tryForTime(3 * 1000, async () => { + await retry.tryForTime(5000, async () => { const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( metric ); @@ -327,7 +342,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); after(async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await pageObjects.infraHome.closeFlyout(); }); }); @@ -339,89 +354,85 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('shows query suggestions', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.clickQueryBar(); await pageObjects.infraHome.inputQueryData(); await pageObjects.infraHome.ensureSuggestionsPanelVisible(); await pageObjects.infraHome.clearSearchTerm(); }); - it.skip('sort nodes by descending value', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + it('sort nodes by descending value', async () => { + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.sortNodesBy('value'); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); expect(nodesWithValue).to.eql([ - { name: 'demo-stack-apache-01', value: 1.4, color: '#6092c0' }, - { name: 'demo-stack-mysql-01', value: 1.2, color: '#82a7cd' }, - { name: 'demo-stack-nginx-01', value: 1.1, color: '#93b1d3' }, - { name: 'demo-stack-redis-01', value: 1, color: '#a2bcd9' }, - { name: 'demo-stack-haproxy-01', value: 0.8, color: '#c2d2e6' }, - { name: 'demo-stack-client-01', value: 0.6, color: '#f0f4f9' }, + { name: 'host-3', value: 90, color: '#6092c0' }, + { name: 'host-2', value: 70, color: '#82a7cd' }, + { name: 'host-1', value: 50, color: '#a2bcd9' }, + { name: 'host-4', value: 30, color: '#d1ddec' }, + { name: 'host-5', value: 10, color: '#f0f4f9' }, ]); }); }); - it.skip('sort nodes by ascending value', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + it('sort nodes by ascending value', async () => { + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.sortNodesBy('value'); await pageObjects.infraHome.toggleReverseSort(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); expect(nodesWithValue).to.eql([ - { name: 'demo-stack-client-01', value: 0.6, color: '#f0f4f9' }, - { name: 'demo-stack-haproxy-01', value: 0.8, color: '#c2d2e6' }, - { name: 'demo-stack-redis-01', value: 1, color: '#a2bcd9' }, - { name: 'demo-stack-nginx-01', value: 1.1, color: '#93b1d3' }, - { name: 'demo-stack-mysql-01', value: 1.2, color: '#82a7cd' }, - { name: 'demo-stack-apache-01', value: 1.4, color: '#6092c0' }, + { name: 'host-5', value: 10, color: '#f0f4f9' }, + { name: 'host-4', value: 30, color: '#d1ddec' }, + { name: 'host-1', value: 50, color: '#a2bcd9' }, + { name: 'host-2', value: 70, color: '#82a7cd' }, + { name: 'host-3', value: 90, color: '#6092c0' }, ]); }); }); it('group nodes by custom field', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.getWaffleMap(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const groups = await pageObjects.infraHome.groupByCustomField('host.os.platform'); expect(groups).to.eql(['ubuntu']); }); }); - it.skip('filter nodes by search term', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + it('filter nodes by search term', async () => { + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.getWaffleMap(); - await pageObjects.infraHome.enterSearchTerm('host.name: "demo-stack-apache-01"'); - await retry.try(async () => { + await pageObjects.infraHome.enterSearchTerm('host.name: "host-1"'); + await retry.tryForTime(5000, async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); - expect(nodesWithValue).to.eql([ - { name: 'demo-stack-apache-01', value: 1.4, color: '#6092c0' }, - ]); + expect(nodesWithValue).to.eql([{ name: 'host-1', value: 50, color: '#6092c0' }]); }); await pageObjects.infraHome.clearSearchTerm(); }); - it.skip('change color palette', async () => { + it('change color palette', async () => { + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.openLegendControls(); await pageObjects.infraHome.changePalette('temperature'); await pageObjects.infraHome.applyLegendControls(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); expect(nodesWithValue).to.eql([ - { name: 'demo-stack-client-01', value: 0.6, color: '#6092c0' }, - { name: 'demo-stack-haproxy-01', value: 0.8, color: '#b5c9df' }, - { name: 'demo-stack-redis-01', value: 1, color: '#f1d9b9' }, - { name: 'demo-stack-nginx-01', value: 1.1, color: '#eec096' }, - { name: 'demo-stack-mysql-01', value: 1.2, color: '#eba47a' }, - { name: 'demo-stack-apache-01', value: 1.4, color: '#e7664c' }, + { name: 'host-5', value: 10, color: '#6092c0' }, + { name: 'host-4', value: 30, color: '#9ab6d5' }, + { name: 'host-1', value: 50, color: '#f1d9b9' }, + { name: 'host-2', value: 70, color: '#eba47a' }, + { name: 'host-3', value: 90, color: '#e7664c' }, ]); }); }); it('toggle the timeline', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.openTimeline(); await pageObjects.infraHome.closeTimeline(); @@ -438,62 +449,47 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('Should redirect to Host Details page', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); await pageObjects.infraHome.goToHost(); await pageObjects.infraHome.clickOnFirstNode(); await pageObjects.infraHome.clickOnNodeDetailsFlyoutOpenAsPage(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const documentTitle = await browser.getTitle(); expect(documentTitle).to.contain( - 'demo-stack-redis-01 - Inventory - Infrastructure - Observability - Elastic' + 'host-5 - Inventory - Infrastructure - Observability - Elastic' ); }); await returnTo(INVENTORY_PATH); }); - it('Should redirect to Pod Details page', async () => { - await pageObjects.infraHome.goToPods(); - await pageObjects.infraHome.goToTime(DATE_WITH_POD_WITH_DATA); - await pageObjects.infraHome.clickOnFirstNode(); - await pageObjects.infraHome.clickOnGoToNodeDetails(); + describe('Redirect to Pod Details page', () => { + it('should redirect to Pod Details page', async () => { + await pageObjects.infraHome.goToPods(); + await pageObjects.infraHome.goToTime(DATE_WITH_POD_DATA); + await pageObjects.infraHome.clickOnFirstNode(); + await pageObjects.infraHome.clickOnGoToNodeDetails(); - await retry.try(async () => { - const documentTitle = await browser.getTitle(); - expect(documentTitle).to.contain( - 'pod-0 - Inventory - Infrastructure - Observability - Elastic' - ); - }); + await retry.tryForTime(5000, async () => { + const documentTitle = await browser.getTitle(); + expect(documentTitle).to.contain( + 'pod-0 - Inventory - Infrastructure - Observability - Elastic' + ); + }); - await returnTo(INVENTORY_PATH); + await returnTo(INVENTORY_PATH); + }); }); describe('Redirect to Container Details page', () => { - let synthEsClient: InfraSynthtraceEsClient; - before(async () => { - const version = await infraSynthtraceKibanaClient.fetchLatestSystemPackageVersion(); - await infraSynthtraceKibanaClient.installSystemPackage(version); - synthEsClient = await getInfraSynthtraceEsClient(esClient); - await synthEsClient.index( - generateDockerContainersData({ - from: DATE_WITH_DOCKER_DATA_FROM, - to: DATE_WITH_DOCKER_DATA_TO, - count: 5, - }) - ); - }); - - after(async () => { - return await synthEsClient.clean(); - }); - it('Should redirect to Container Details page', async () => { + it('should redirect to Container Details page', async () => { await pageObjects.infraHome.goToContainer(); await pageObjects.infraHome.goToTime(DATE_WITH_DOCKER_DATA); await pageObjects.infraHome.clickOnFirstNode(); - await pageObjects.infraHome.clickOnGoToNodeDetails(); + await pageObjects.infraHome.clickOnNodeDetailsFlyoutOpenAsPage(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const documentTitle = await browser.getTitle(); expect(documentTitle).to.contain( 'container-id-4 - Inventory - Infrastructure - Observability - Elastic' @@ -534,105 +530,96 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // close await pageObjects.infraHome.clickCustomMetricDropdown(); }); - }); - describe('alerts flyouts', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.waitForLoading(); - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); - }); - after( - async () => - await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs') - ); - - it('should open and close inventory alert flyout', async () => { - await pageObjects.infraHome.openInventoryAlertFlyout(); - await pageObjects.infraHome.closeAlertFlyout(); - }); + describe('alerts flyouts', () => { + before(async () => { + await pageObjects.common.navigateToApp('infraOps'); + await pageObjects.infraHome.waitForLoading(); + await pageObjects.infraHome.goToTime(DATE_WITH_HOSTS_DATA); + }); - it('should open and close metrics threshold alert flyout', async () => { - await pageObjects.infraHome.openMetricsThresholdAlertFlyout(); - await pageObjects.infraHome.closeAlertFlyout(); - }); + it('should open and close inventory alert flyout', async () => { + await pageObjects.infraHome.openInventoryAlertFlyout(); + await pageObjects.infraHome.closeAlertFlyout(); + }); - it('should open and close alerts popover using button', async () => { - await pageObjects.infraHome.clickAlertsAndRules(); - await pageObjects.infraHome.ensurePopoverOpened(); - await pageObjects.infraHome.clickAlertsAndRules(); - await retry.try(async () => { - await pageObjects.infraHome.ensurePopoverClosed(); + it('should open and close metrics threshold alert flyout', async () => { + await pageObjects.infraHome.openMetricsThresholdAlertFlyout(); + await pageObjects.infraHome.closeAlertFlyout(); }); - }); - it('should not have an option to create custom threshold alert', async () => { - await pageObjects.infraHome.clickAlertsAndRules(); - await pageObjects.infraHome.ensurePopoverOpened(); - await pageObjects.infraHome.ensureCustomThresholdAlertMenuItemIsMissing(); - await pageObjects.infraHome.clickAlertsAndRules(); - }); - }); + it('should open and close alerts popover using button', async () => { + await pageObjects.infraHome.clickAlertsAndRules(); + await pageObjects.infraHome.ensurePopoverOpened(); + await pageObjects.infraHome.clickAlertsAndRules(); + await retry.tryForTime(5000, async () => { + await pageObjects.infraHome.ensurePopoverClosed(); + }); + }); - describe('Saved Views', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.infraHome.goToMetricExplorer(); + it('should not have an option to create custom threshold alert', async () => { + await pageObjects.infraHome.clickAlertsAndRules(); + await pageObjects.infraHome.ensurePopoverOpened(); + await pageObjects.infraHome.ensureCustomThresholdAlertMenuItemIsMissing(); + await pageObjects.infraHome.clickAlertsAndRules(); + }); }); + describe('Saved Views', () => { + before(async () => { + await pageObjects.infraHome.goToMetricExplorer(); + }); - after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); - - beforeEach(async () => { - await pageObjects.infraSavedViews.clickSavedViewsButton(); - }); - afterEach(async () => { - await pageObjects.infraSavedViews.closeSavedViewsPopover(); - }); + beforeEach(async () => { + await pageObjects.infraSavedViews.clickSavedViewsButton(); + }); + afterEach(async () => { + await pageObjects.infraSavedViews.closeSavedViewsPopover(); + }); - it('should render a button with the view name', async () => { - await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); - }); + it('should render a button with the view name', async () => { + await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); + }); - it('should open/close the views popover menu on button click', async () => { - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await testSubjects.existOrFail('savedViews-popover'); - await pageObjects.infraSavedViews.closeSavedViewsPopover(); - }); + it('should open/close the views popover menu on button click', async () => { + await pageObjects.infraSavedViews.clickSavedViewsButton(); + await testSubjects.existOrFail('savedViews-popover'); + await pageObjects.infraSavedViews.closeSavedViewsPopover(); + }); - it('should create a new saved view and load it', async () => { - await pageObjects.infraSavedViews.createView('view1'); - await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); - }); + it('should create a new saved view and load it', async () => { + await pageObjects.infraSavedViews.createView('view1'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view1'); + }); - it('should load a clicked view from the manage views section', async () => { - const views = await pageObjects.infraSavedViews.getManageViewsEntries(); - await views[0].click(); - await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); - }); + it('should load a clicked view from the manage views section', async () => { + const views = await pageObjects.infraSavedViews.getManageViewsEntries(); + await views[0].click(); + await pageObjects.infraSavedViews.ensureViewIsLoaded('Default view'); + }); - it('should update the current saved view and load it', async () => { - let views = await pageObjects.infraSavedViews.getManageViewsEntries(); - expect(views.length).to.equal(2); - await pageObjects.infraSavedViews.pressEsc(); + it('should update the current saved view and load it', async () => { + let views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(2); + await pageObjects.infraSavedViews.pressEsc(); - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await pageObjects.infraSavedViews.createView('view2'); - await pageObjects.infraSavedViews.ensureViewIsLoaded('view2'); + await pageObjects.infraSavedViews.clickSavedViewsButton(); + await pageObjects.infraSavedViews.createView('view2'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view2'); - await pageObjects.infraSavedViews.clickSavedViewsButton(); - views = await pageObjects.infraSavedViews.getManageViewsEntries(); - expect(views.length).to.equal(3); - await pageObjects.infraSavedViews.pressEsc(); + await pageObjects.infraSavedViews.clickSavedViewsButton(); + views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(3); + await pageObjects.infraSavedViews.pressEsc(); - await pageObjects.infraSavedViews.clickSavedViewsButton(); - await pageObjects.infraSavedViews.updateView('view3'); - await pageObjects.infraSavedViews.ensureViewIsLoaded('view3'); + await pageObjects.infraSavedViews.clickSavedViewsButton(); + await pageObjects.infraSavedViews.updateView('view3'); + await pageObjects.infraSavedViews.ensureViewIsLoaded('view3'); - await pageObjects.infraSavedViews.clickSavedViewsButton(); - views = await pageObjects.infraSavedViews.getManageViewsEntries(); - expect(views.length).to.equal(3); - await pageObjects.infraSavedViews.pressEsc(); + await pageObjects.infraSavedViews.clickSavedViewsButton(); + views = await pageObjects.infraSavedViews.getManageViewsEntries(); + expect(views.length).to.equal(3); + await pageObjects.infraSavedViews.pressEsc(); + }); }); }); }); diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index cc4c53594a3c3..06464b0f962f8 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -7,11 +7,12 @@ import moment from 'moment'; import expect from '@kbn/expect'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { - enableInfrastructureAssetCustomDashboards, - enableInfrastructureHostsView, -} from '@kbn/observability-plugin/common'; + ApmSynthtraceEsClient, + InfraSynthtraceEsClient, + LogsSynthtraceEsClient, +} from '@kbn/apm-synthtrace'; +import { enableInfrastructureAssetCustomDashboards } from '@kbn/observability-plugin/common'; import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -20,14 +21,24 @@ import { HOSTS_LINK_LOCAL_STORAGE_KEY, HOSTS_VIEW_PATH, DATE_PICKER_FORMAT, + DATE_WITH_HOSTS_DATA_FROM, + DATE_WITH_HOSTS_DATA_TO, } from './constants'; -import { generateAddServicesToExistingHost } from './helpers'; +import { + generateAddServicesToExistingHost, + generateHostData, + generateLogsDataForHosts, +} from './helpers'; import { getApmSynthtraceEsClient } from '../../../common/utils/synthtrace/apm_es_client'; +import { getInfraSynthtraceEsClient } from '../../../common/utils/synthtrace/infra_es_client'; +import { getLogsSynthtraceEsClient } from '../../../common/utils/synthtrace/logs_es_client'; const START_DATE = moment.utc(DATES.metricsAndLogs.hosts.min); const END_DATE = moment.utc(DATES.metricsAndLogs.hosts.max); -const START_HOST_PROCESSES_DATE = moment.utc(DATES.metricsAndLogs.hosts.processesDataStartDate); -const END_HOST_PROCESSES_DATE = moment.utc(DATES.metricsAndLogs.hosts.processesDataEndDate); + +// synthtrace data dates +const START_SYNTHTRACE_DATE = moment.utc(DATE_WITH_HOSTS_DATA_FROM); +const END_SYNTHTRACE_DATE = moment.utc(DATE_WITH_HOSTS_DATA_TO); const tableEntries = [ { @@ -98,6 +109,96 @@ const tableEntries = [ }, ]; +const synthtraceHostsTableEntries = [ + { + title: 'host-1', + cpuUsage: '90%', + normalizedLoad: '18.8%', + memoryUsage: '35%', + memoryFree: '44.7 GB', + diskSpaceUsage: '1,223%', + rx: '1.5 Mbit/s', + tx: '1.5 Mbit/s', + }, + { + title: 'host-2', + cpuUsage: '70%', + normalizedLoad: '18.8%', + memoryUsage: '35%', + memoryFree: '44.7 GB', + diskSpaceUsage: '1,223%', + rx: '1.5 Mbit/s', + tx: '1.5 Mbit/s', + }, + { + title: 'host-3', + cpuUsage: '50%', + normalizedLoad: '18.8%', + memoryUsage: '35%', + memoryFree: '44.7 GB', + diskSpaceUsage: '1,223%', + rx: '1.5 Mbit/s', + tx: '1.5 Mbit/s', + }, + { + title: 'host-4', + cpuUsage: '40%', + normalizedLoad: '18.8%', + memoryUsage: '35%', + memoryFree: '44.7 GB', + diskSpaceUsage: '1,223%', + rx: '1.5 Mbit/s', + tx: '1.5 Mbit/s', + }, + { + title: 'host-5', + cpuUsage: '30%', + normalizedLoad: '18.8%', + memoryUsage: '35%', + memoryFree: '44.7 GB', + diskSpaceUsage: '1,223%', + rx: '1.5 Mbit/s', + tx: '1.5 Mbit/s', + }, + { + title: 'host-6', + cpuUsage: '10%', + normalizedLoad: '18.8%', + memoryUsage: '35%', + memoryFree: '44.7 GB', + diskSpaceUsage: '1,223%', + rx: '1.5 Mbit/s', + tx: '1.5 Mbit/s', + }, +]; + +const SYNTH_HOSTS = [ + { + hostName: 'host-1', + cpuValue: 0.9, + }, + { + hostName: 'host-2', + cpuValue: 0.7, + }, + { + hostName: 'host-3', + cpuValue: 0.5, + }, + { + hostName: 'host-4', + cpuValue: 0.4, + }, + { + hostName: 'host-5', + cpuValue: 0.3, + }, + { + hostName: 'host-6', + cpuValue: 0.1, + }, +]; + export default ({ getPageObjects, getService }: FtrProviderContext) => { const browser = getService('browser'); const security = getService('security'); @@ -126,7 +227,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const loginWithReadOnlyUserAndNavigateToHostsFlyout = async () => { await security.role.create('global_hosts_read_privileges_role', { elasticsearch: { - indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], + indices: [ + { names: ['metrics-*'], privileges: ['read', 'view_index_metadata'] }, + { names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }, + ], }, kibana: [ { @@ -158,8 +262,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); await pageObjects.header.waitUntilLoadingHasFinished(); await pageObjects.timePicker.setAbsoluteRange( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), - END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) + START_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT), + END_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT) ); await waitForPageToLoad(); @@ -175,9 +279,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); }; - const setHostViewEnabled = (value: boolean = true) => - kibanaServer.uiSettings.update({ [enableInfrastructureHostsView]: value }); - const setCustomDashboardsEnabled = (value: boolean = true) => kibanaServer.uiSettings.update({ [enableInfrastructureAssetCustomDashboards]: value }); @@ -198,9 +299,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); describe('Hosts View', function () { + let synthEsInfraClient: InfraSynthtraceEsClient; + let syntEsLogsClient: LogsSynthtraceEsClient; + describe('#Onboarding', function () { before(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + synthEsInfraClient = await getInfraSynthtraceEsClient(esClient); + await synthEsInfraClient.clean(); await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); }); @@ -208,7 +313,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.noDataPromptExists(); await pageObjects.infraHome.noDataPromptAddDataClick(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`; @@ -221,6 +326,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('#With data', function () { let synthtraceApmClient: ApmSynthtraceEsClient; before(async () => { + synthEsInfraClient = await getInfraSynthtraceEsClient(esClient); + syntEsLogsClient = await getLogsSynthtraceEsClient(esClient); const version = (await apmSynthtraceKibanaClient.installApmPackage()).version; synthtraceApmClient = await getApmSynthtraceEsClient({ client: esClient, @@ -228,19 +335,30 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); const services = generateAddServicesToExistingHost({ - from: DATES.metricsAndLogs.hosts.processesDataStartDate, - to: DATES.metricsAndLogs.hosts.processesDataEndDate, - hostName: 'Jennys-MBP.fritz.box', + from: DATE_WITH_HOSTS_DATA_FROM, + to: DATE_WITH_HOSTS_DATA_TO, + hostName: 'host-1', servicesPerHost: 3, }); + const logs = generateLogsDataForHosts({ + from: DATE_WITH_HOSTS_DATA_FROM, + to: DATE_WITH_HOSTS_DATA_TO, + hosts: SYNTH_HOSTS, + }); + await browser.setWindowSize(1600, 1200); return Promise.all([ synthtraceApmClient.index(services), - esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), - esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), - esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), + synthEsInfraClient.index( + generateHostData({ + from: DATE_WITH_HOSTS_DATA_FROM, + to: DATE_WITH_HOSTS_DATA_TO, + hosts: SYNTH_HOSTS, + }) + ), + syntEsLogsClient.index(logs), ]); }); @@ -248,9 +366,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return Promise.all([ apmSynthtraceKibanaClient.uninstallApmPackage(), synthtraceApmClient.clean(), - esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'), - esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'), - esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), + synthEsInfraClient.clean(), browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY), ]); }); @@ -269,7 +385,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('#Single Host Flyout', () => { before(async () => { - await setHostViewEnabled(true); await setCustomDashboardsEnabled(true); await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -278,8 +393,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Tabs', () => { before(async () => { await pageObjects.timePicker.setAbsoluteRange( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), - END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) + START_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT), + END_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT) ); await waitForPageToLoad(); @@ -288,7 +403,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); after(async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await pageObjects.infraHome.clickCloseFlyoutButton(); }); }); @@ -299,13 +414,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); [ - { metric: 'cpuUsage', value: '13.9%' }, + { metric: 'cpuUsage', value: '48.3%' }, { metric: 'normalizedLoad1m', value: '18.8%' }, - { metric: 'memoryUsage', value: '94.9%' }, - { metric: 'diskUsage', value: 'N/A' }, + { metric: 'memoryUsage', value: '35.0%' }, + { metric: 'diskUsage', value: '1,223.0%' }, ].forEach(({ metric, value }) => { it(`${metric} tile should show ${value}`, async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( metric ); @@ -374,7 +489,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.header.waitUntilLoadingHasFinished(); const addedFilter = await pageObjects.assetDetails.getMetadataAppliedFilter(); - expect(addedFilter).to.contain('host.architecture: arm64'); + expect(addedFilter).to.contain('host.name: host-1'); const removeFilterExists = await pageObjects.assetDetails.metadataRemoveFilterExists(); expect(removeFilterExists).to.be(true); @@ -403,8 +518,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.assetDetails.clickProcessesTab(); }); - it('should show processes table', async () => { - await pageObjects.assetDetails.processesTableExists(); + it('should show processes content', async () => { + await pageObjects.assetDetails.processesContentExist(); }); }); @@ -432,10 +547,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should navigate to Host Details page after click', async () => { await pageObjects.assetDetails.clickOpenAsPageLink(); const dateRange = await pageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); - expect(dateRange.start).to.equal( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) - ); - expect(dateRange.end).to.equal(END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT)); + expect(dateRange.start).to.equal(START_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT)); + expect(dateRange.end).to.equal(END_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT)); await returnTo(HOSTS_VIEW_PATH); }); @@ -443,14 +556,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('#Page Content', () => { + describe('#Page Content without alerts', () => { before(async () => { - await setHostViewEnabled(true); await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); await pageObjects.header.waitUntilLoadingHasFinished(); await pageObjects.timePicker.setAbsoluteRange( - START_DATE.format(DATE_PICKER_FORMAT), - END_DATE.format(DATE_PICKER_FORMAT) + START_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT), + END_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT) ); await waitForPageToLoad(); @@ -479,7 +591,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should render the computed metrics for each host entry', async () => { for (let i = 0; i < hostRows.length; i++) { const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); - expect(hostRowData).to.eql(tableEntries[i]); + expect(hostRowData).to.eql(synthtraceHostsTableEntries[i]); } }); @@ -488,8 +600,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHostsView.selectedHostsButtonExist(); expect(selectHostsButtonExistsOnLoad).to.be(false); - await pageObjects.infraHostsView.clickHostCheckbox('demo-stack-client-01', '-'); - await pageObjects.infraHostsView.clickHostCheckbox('demo-stack-apache-01', '-'); + await pageObjects.infraHostsView.clickHostCheckbox('host-1', 'Linux'); + await pageObjects.infraHostsView.clickHostCheckbox('host-2', 'Linux'); const selectHostsButtonExistsOnSelection = await pageObjects.infraHostsView.selectedHostsButtonExist(); @@ -503,7 +615,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(hostRowsAfterFilter.length).to.equal(2); const deleteFilterButton = await find.byCssSelector( - `[title="Delete host.name: demo-stack-client-01 OR host.name: demo-stack-apache-01"]` + `[title="Delete host.name: host-1 OR host.name: host-2"]` ); await deleteFilterButton.click(); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -518,16 +630,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); await pageObjects.header.waitUntilLoadingHasFinished(); await pageObjects.timePicker.setAbsoluteRange( - START_DATE.format(DATE_PICKER_FORMAT), - END_DATE.format(DATE_PICKER_FORMAT) + START_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT), + END_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT) ); await waitForPageToLoad(); }); it('should maintain the selected date range when navigating to the individual host details', async () => { - const start = START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); - const end = END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); + const start = START_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT); + const end = END_SYNTHTRACE_DATE.format(DATE_PICKER_FORMAT); await pageObjects.timePicker.setAbsoluteRange(start, end); @@ -549,13 +661,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('KPIs', () => { [ { metric: 'hostsCount', value: '6' }, - { metric: 'cpuUsage', value: 'N/A' }, - { metric: 'normalizedLoad1m', value: '0.3%' }, - { metric: 'memoryUsage', value: '16.8%' }, - { metric: 'diskUsage', value: '35.7%' }, + { metric: 'cpuUsage', value: '48.3%' }, + { metric: 'normalizedLoad1m', value: '18.8%' }, + { metric: 'memoryUsage', value: '35.0%' }, + { metric: 'diskUsage', value: '1,223.0%' }, ].forEach(({ metric, value }) => { it(`${metric} tile should show ${value}`, async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const tileValue = metric === 'hostsCount' ? await pageObjects.infraHostsView.getKPITileValue(metric) @@ -583,7 +695,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should have an option to open the chart in lens', async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await pageObjects.infraHostsView.clickAndValidateMetricChartActionOptions(); await browser.pressKeys(browser.keys.ESCAPE); }); @@ -605,7 +717,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should load the Logs tab with the right columns', async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const columnLabels = await pageObjects.infraHostsView.getLogsTableColumnHeaders(); expect(columnLabels).to.eql(['Timestamp', 'host.name', 'Message']); @@ -613,6 +725,115 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); + describe('Pagination and Sorting', () => { + before(async () => { + await browser.scrollTop(); + }); + + after(async () => { + await browser.scrollTop(); + }); + + beforeEach(async () => { + await retry.tryForTime(5000, async () => { + await pageObjects.infraHostsView.changePageSize(5); + }); + }); + + it('should show 5 rows on the first page', async () => { + const hostRows = await pageObjects.infraHostsView.getHostsTableData(); + + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(synthtraceHostsTableEntries[i]); + } + }); + + it('should paginate to the last page', async () => { + await pageObjects.infraHostsView.paginateTo(2); + const hostRows = await pageObjects.infraHostsView.getHostsTableData(); + + expect(hostRows.length).to.equal(1); + + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostRowData).to.eql(synthtraceHostsTableEntries[5]); + }); + + it('should show all hosts on the same page', async () => { + await pageObjects.infraHostsView.changePageSize(10); + const hostRows = await pageObjects.infraHostsView.getHostsTableData(); + + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(synthtraceHostsTableEntries[i]); + } + }); + + it('should sort by a numeric field asc', async () => { + await pageObjects.infraHostsView.sortByCpuUsage(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(synthtraceHostsTableEntries[5]); + + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(synthtraceHostsTableEntries[0]); + }); + + it('should sort by a numeric field desc', async () => { + await pageObjects.infraHostsView.sortByCpuUsage(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(synthtraceHostsTableEntries[0]); + + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(synthtraceHostsTableEntries[5]); + }); + + it('should sort by text field asc', async () => { + await pageObjects.infraHostsView.sortByTitle(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(synthtraceHostsTableEntries[0]); + + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(synthtraceHostsTableEntries[5]); + }); + + it('should sort by text field desc', async () => { + await pageObjects.infraHostsView.sortByTitle(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(synthtraceHostsTableEntries[5]); + + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(synthtraceHostsTableEntries[0]); + }); + }); + }); + + describe('#Page Content with alerts', () => { + before(async () => { + return Promise.all([ + esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), + esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), + ]); + }); + + after(async () => { + return Promise.all([ + esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'), + esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'), + ]); + }); + describe('Alerts Tab', () => { const ACTIVE_ALERTS = 6; const RECOVERED_ALERTS = 4; @@ -620,6 +841,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const COLUMNS = 11; before(async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_DATE.format(DATE_PICKER_FORMAT), + END_DATE.format(DATE_PICKER_FORMAT) + ); + + await waitForPageToLoad(); await browser.scrollTop(); await pageObjects.infraHostsView.visitAlertTab(); }); @@ -641,7 +870,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('#FilterButtonGroup', () => { it('can be filtered to only show "all" alerts using the filter button', async () => { await pageObjects.infraHostsView.setAlertStatusFilter(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const tableRows = await observability.alerts.common.getTableCellsInRows(); expect(tableRows.length).to.be(ALL_ALERTS); }); @@ -649,7 +878,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('can be filtered to only show "active" alerts using the filter button', async () => { await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_ACTIVE); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const tableRows = await observability.alerts.common.getTableCellsInRows(); expect(tableRows.length).to.be(ACTIVE_ALERTS); }); @@ -657,7 +886,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('can be filtered to only show "recovered" alerts using the filter button', async () => { await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_RECOVERED); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const tableRows = await observability.alerts.common.getTableCellsInRows(); expect(tableRows.length).to.be(RECOVERED_ALERTS); }); @@ -671,7 +900,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should renders the correct number of cells', async () => { await pageObjects.infraHostsView.setAlertStatusFilter(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const cells = await observability.alerts.common.getTableCells(); expect(cells.length).to.be(ALL_ALERTS * COLUMNS); }); @@ -685,6 +914,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const query = filtererEntries.map((entry) => `host.name :"${entry.title}"`).join(' or '); before(async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_DATE.format(DATE_PICKER_FORMAT), + END_DATE.format(DATE_PICKER_FORMAT) + ); + + await waitForPageToLoad(); await browser.scrollTop(); await pageObjects.infraHostsView.submitQuery(query); await await waitForPageToLoad(); @@ -701,7 +938,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(hostRows.length).to.equal(3); for (let i = 0; i < hostRows.length; i++) { - const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + const hostRowData = await pageObjects.infraHostsView.getHostsRowDataWithAlerts( + hostRows[i] + ); expect(hostRowData).to.eql(filtererEntries[i]); } }); @@ -715,7 +954,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { { metric: 'memoryUsage', value: '17.5%' }, { metric: 'diskUsage', value: '35.7%' }, ].map(async ({ metric, value }) => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const tileValue = metric === 'hostsCount' ? await pageObjects.infraHostsView.getKPITileValue(metric) @@ -741,7 +980,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHostsView.visitAlertTab(); await pageObjects.infraHostsView.setAlertStatusFilter(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const cells = await observability.alerts.common.getTableCells(); expect(cells.length).to.be(ALL_ALERTS * COLUMNS); }); @@ -757,104 +996,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await waitForPageToLoad(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await testSubjects.exists('hostsViewTableNoData'); }); }); }); - - describe('Pagination and Sorting', () => { - before(async () => { - await browser.scrollTop(); - }); - - after(async () => { - await browser.scrollTop(); - }); - - beforeEach(async () => { - await retry.try(async () => { - await pageObjects.infraHostsView.changePageSize(5); - }); - }); - - it('should show 5 rows on the first page', async () => { - const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - - for (let i = 0; i < hostRows.length; i++) { - const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); - expect(hostRowData).to.eql(tableEntries[i]); - } - }); - - it('should paginate to the last page', async () => { - await pageObjects.infraHostsView.paginateTo(2); - const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - - expect(hostRows.length).to.equal(1); - - const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostRowData).to.eql(tableEntries[5]); - }); - - it('should show all hosts on the same page', async () => { - await pageObjects.infraHostsView.changePageSize(10); - const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - - for (let i = 0; i < hostRows.length; i++) { - const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); - expect(hostRowData).to.eql(tableEntries[i]); - } - }); - - it('should sort by a numeric field asc', async () => { - await pageObjects.infraHostsView.sortByMemoryUsage(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[3]); - - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[0]); - }); - - it('should sort by a numeric field desc', async () => { - await pageObjects.infraHostsView.sortByMemoryUsage(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[0]); - - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[3]); - }); - - it('should sort by text field asc', async () => { - await pageObjects.infraHostsView.sortByTitle(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[0]); - - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[2]); - }); - - it('should sort by text field desc', async () => { - await pageObjects.infraHostsView.sortByTitle(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[2]); - - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[0]); - }); - }); }); describe('#Permissions: Read Only User - Single Host Flyout', () => { @@ -866,7 +1012,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); after(async () => { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await pageObjects.infraHome.clickCloseFlyoutButton(); }); await logoutAndDeleteReadOnlyUser(); @@ -875,7 +1021,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should render dashboards tab splash screen with disabled option to add dashboard', async () => { await pageObjects.assetDetails.addDashboardExists(); const elementToHover = await pageObjects.assetDetails.getAddDashboardButton(); - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await elementToHover.moveMouseTo(); await testSubjects.existOrFail('infraCannotAddDashboardTooltip'); }); diff --git a/x-pack/test/functional/apps/infra/node_details.ts b/x-pack/test/functional/apps/infra/node_details.ts index 2fe8901323db1..4d31091caabb5 100644 --- a/x-pack/test/functional/apps/infra/node_details.ts +++ b/x-pack/test/functional/apps/infra/node_details.ts @@ -25,24 +25,47 @@ import { DATE_PICKER_FORMAT, DATE_WITH_DOCKER_DATA_FROM, DATE_WITH_DOCKER_DATA_TO, + DATE_WITH_HOSTS_DATA_FROM, + DATE_WITH_HOSTS_DATA_TO, } from './constants'; import { getInfraSynthtraceEsClient } from '../../../common/utils/synthtrace/infra_es_client'; -import { generateDockerContainersData } from './helpers'; +import { + generateDockerContainersData, + generateHostData, + generateHostsWithK8sNodeData, +} from './helpers'; const START_HOST_ALERTS_DATE = moment.utc(DATES.metricsAndLogs.hosts.min); const END_HOST_ALERTS_DATE = moment.utc(DATES.metricsAndLogs.hosts.max); const START_HOST_PROCESSES_DATE = moment.utc(DATES.metricsAndLogs.hosts.processesDataStartDate); const END_HOST_PROCESSES_DATE = moment.utc(DATES.metricsAndLogs.hosts.processesDataEndDate); - -const START_HOST_KUBERNETES_SECTION_DATE = moment.utc( - DATES.metricsAndLogs.hosts.kubernetesSectionStartDate -); -const END_HOST_KUBERNETES_SECTION_DATE = moment.utc( - DATES.metricsAndLogs.hosts.kubernetesSectionEndDate -); +const START_HOST_DATE = moment.utc(DATE_WITH_HOSTS_DATA_FROM); +const END_HOST_DATE = moment.utc(DATE_WITH_HOSTS_DATA_TO); const START_CONTAINER_DATE = moment.utc(DATE_WITH_DOCKER_DATA_FROM); const END_CONTAINER_DATE = moment.utc(DATE_WITH_DOCKER_DATA_TO); +const HOSTS = [ + { + hostName: 'host-1', + cpuValue: 0.5, + }, + { + hostName: 'host-2', + cpuValue: 0.7, + }, + { + hostName: 'host-3', + cpuValue: 0.9, + }, + { + hostName: 'host-4', + cpuValue: 0.3, + }, + { + hostName: 'host-5', + cpuValue: 0.1, + }, +]; interface QueryParams { name?: string; alertMetric?: string; @@ -53,7 +76,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); - const infraSynthtraceKibanaClient = getService('infraSynthtraceKibanaClient'); const esClient = getService('es'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -114,28 +136,52 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }; describe('Node Details', () => { - describe('#With Asset Details', () => { - before(async () => { - await Promise.all([ - esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), - esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), - esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), - kibanaServer.savedObjects.cleanStandardList(), - ]); - await browser.setWindowSize(1600, 1200); + let synthEsClient: InfraSynthtraceEsClient; + before(async () => { + synthEsClient = await getInfraSynthtraceEsClient(esClient); + await synthEsClient.clean(); + await kibanaServer.savedObjects.cleanStandardList(); + await browser.setWindowSize(1600, 1200); + }); + + after(async () => { + await synthEsClient.clean(); + }); - await navigateToNodeDetails('Jennys-MBP.fritz.box', 'host', { - name: 'Jennys-MBP.fritz.box', + describe('#Asset Type: host', () => { + before(async () => { + synthEsClient = await getInfraSynthtraceEsClient(esClient); + await synthEsClient.clean(); + await synthEsClient.index( + generateHostData({ + from: DATE_WITH_HOSTS_DATA_FROM, + to: DATE_WITH_HOSTS_DATA_TO, + hosts: HOSTS, + }) + ); + await navigateToNodeDetails('host-1', 'host', { + name: 'host-1', }); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_DATE.format(DATE_PICKER_FORMAT), + END_HOST_DATE.format(DATE_PICKER_FORMAT) + ); }); after(async () => { - await Promise.all([ - esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'), - esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'), - esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), - ]); + await synthEsClient.clean(); + }); + + it('preserves selected tab between page reloads', async () => { + await testSubjects.missingOrFail('infraAssetDetailsMetadataTable'); + await pageObjects.assetDetails.clickMetadataTab(); + await pageObjects.assetDetails.metadataTableExists(); + + await refreshPageWithDelay(); + + await pageObjects.assetDetails.metadataTableExists(); }); describe('#Date picker: host', () => { @@ -143,8 +189,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.assetDetails.clickOverviewTab(); await pageObjects.timePicker.setAbsoluteRange( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), - END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) + START_HOST_DATE.format(DATE_PICKER_FORMAT), + END_HOST_DATE.format(DATE_PICKER_FORMAT) ); }); @@ -172,18 +218,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const datePickerValue = await pageObjects.timePicker.getTimeConfig(); expect(await pageObjects.timePicker.timePickerExists()).to.be(true); - expect(datePickerValue.start).to.equal( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) - ); - expect(datePickerValue.end).to.equal( - END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) - ); + expect(datePickerValue.start).to.equal(START_HOST_DATE.format(DATE_PICKER_FORMAT)); + expect(datePickerValue.end).to.equal(END_HOST_DATE.format(DATE_PICKER_FORMAT)); }); }); it('preserves selected date range between page reloads', async () => { - const start = moment.utc(START_HOST_ALERTS_DATE).format(DATE_PICKER_FORMAT); - const end = moment.utc(END_HOST_ALERTS_DATE).format(DATE_PICKER_FORMAT); + const start = moment.utc(START_HOST_DATE).format(DATE_PICKER_FORMAT); + const end = moment.utc(END_HOST_DATE).format(DATE_PICKER_FORMAT); await pageObjects.timePicker.setAbsoluteRange(start, end); await refreshPageWithDelay(); @@ -195,614 +237,634 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('#Asset Type: host', () => { + describe('Overview Tab', () => { before(async () => { + await pageObjects.assetDetails.clickOverviewTab(); await pageObjects.timePicker.setAbsoluteRange( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), - END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) + START_HOST_DATE.format(DATE_PICKER_FORMAT), + END_HOST_DATE.format(DATE_PICKER_FORMAT) ); }); - it('preserves selected tab between page reloads', async () => { - await testSubjects.missingOrFail('infraAssetDetailsMetadataTable'); - await pageObjects.assetDetails.clickMetadataTab(); - await pageObjects.assetDetails.metadataTableExists(); + [ + { metric: 'cpuUsage', value: '50.0%' }, + { metric: 'normalizedLoad1m', value: '18.8%' }, + { metric: 'memoryUsage', value: '35.0%' }, + { metric: 'diskUsage', value: '1,223.0%' }, + ].forEach(({ metric, value }) => { + it(`${metric} tile should show ${value}`, async () => { + await retry.tryForTime(3 * 1000, async () => { + const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue(metric); + expect(tileValue).to.eql(value); + }); + }); + }); - await refreshPageWithDelay(); + [ + { metric: 'cpu', chartsCount: 2 }, + { metric: 'memory', chartsCount: 1 }, + { metric: 'disk', chartsCount: 2 }, + { metric: 'network', chartsCount: 1 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { + const hosts = await pageObjects.assetDetails.getOverviewTabHostMetricCharts(metric); + expect(hosts.length).to.equal(chartsCount); + }); + }); - await pageObjects.assetDetails.metadataTableExists(); + it('should show all section as collapsable', async () => { + await pageObjects.assetDetails.metadataSectionCollapsibleExist(); + await pageObjects.assetDetails.alertsSectionCollapsibleExist(); + await pageObjects.assetDetails.metricsSectionCollapsibleExist(); + await pageObjects.assetDetails.servicesSectionCollapsibleExist(); }); - describe('Overview Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickOverviewTab(); - }); + it('should show alerts', async () => { + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.assetDetails.overviewAlertsTitleExists(); + }); - [ - { metric: 'cpuUsage', value: '13.9%' }, - { metric: 'normalizedLoad1m', value: '18.8%' }, - { metric: 'memoryUsage', value: '94.9%' }, - { metric: 'diskUsage', value: 'N/A' }, - ].forEach(({ metric, value }) => { - it(`${metric} tile should show ${value}`, async () => { - await retry.tryForTime(3 * 1000, async () => { - const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( - metric - ); - expect(tileValue).to.eql(value); - }); - }); - }); + it('should show / hide alerts section with no alerts and show / hide closed section content', async () => { + await pageObjects.assetDetails.alertsSectionCollapsibleExist(); + // Collapsed by default + await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsExist(); + // Expand + await pageObjects.assetDetails.alertsSectionCollapsibleClick(); + await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsMissing(); + }); - [ - { metric: 'cpu', chartsCount: 2 }, - { metric: 'memory', chartsCount: 1 }, - { metric: 'disk', chartsCount: 2 }, - { metric: 'network', chartsCount: 1 }, - ].forEach(({ metric, chartsCount }) => { - it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { - const hosts = await pageObjects.assetDetails.getOverviewTabHostMetricCharts(metric); - expect(hosts.length).to.equal(chartsCount); + it('shows the CPU Profiling prompt if UI setting for Profiling integration is enabled', async () => { + await setInfrastructureProfilingIntegrationUiSetting(true); + await pageObjects.assetDetails.cpuProfilingPromptExists(); + }); + + it('hides the CPU Profiling prompt if UI setting for Profiling integration is disabled', async () => { + await setInfrastructureProfilingIntegrationUiSetting(false); + await pageObjects.assetDetails.cpuProfilingPromptMissing(); + }); + + describe('Alerts Section with alerts', () => { + const ACTIVE_ALERTS = 2; + const RECOVERED_ALERTS = 2; + const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; + const COLUMNS = 11; + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'); + await navigateToNodeDetails('demo-stack-apache-01', 'host', { + name: 'demo-stack-apache-01', }); - }); + await pageObjects.header.waitUntilLoadingHasFinished(); - it('should show all section as collapsable', async () => { - await pageObjects.assetDetails.metadataSectionCollapsibleExist(); - await pageObjects.assetDetails.alertsSectionCollapsibleExist(); - await pageObjects.assetDetails.metricsSectionCollapsibleExist(); - await pageObjects.assetDetails.servicesSectionCollapsibleExist(); + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT), + END_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT) + ); + + await pageObjects.assetDetails.clickOverviewTab(); }); - it('should show alerts', async () => { + after(async () => { + await navigateToNodeDetails('host-1', 'host', { + name: 'host-1', + }); await pageObjects.header.waitUntilLoadingHasFinished(); - await pageObjects.assetDetails.overviewAlertsTitleExists(); + await esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'); }); - it('should show / hide alerts section with no alerts and show / hide closed section content', async () => { + it('should show / hide alerts section with active alerts and show / hide closed section content', async () => { await pageObjects.assetDetails.alertsSectionCollapsibleExist(); - // Collapsed by default - await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsExist(); + // Expanded by default + await pageObjects.assetDetails.alertsSectionClosedContentMissing(); + // Collapse + await pageObjects.assetDetails.alertsSectionCollapsibleClick(); + await pageObjects.assetDetails.alertsSectionClosedContentExist(); // Expand await pageObjects.assetDetails.alertsSectionCollapsibleClick(); - await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsMissing(); - }); - - it('shows the CPU Profiling prompt if UI setting for Profiling integration is enabled', async () => { - await setInfrastructureProfilingIntegrationUiSetting(true); - await pageObjects.assetDetails.cpuProfilingPromptExists(); + await pageObjects.assetDetails.alertsSectionClosedContentMissing(); }); - it('hides the CPU Profiling prompt if UI setting for Profiling integration is disabled', async () => { - await setInfrastructureProfilingIntegrationUiSetting(false); - await pageObjects.assetDetails.cpuProfilingPromptMissing(); + it('should show alert summary ', async () => { + await pageObjects.assetDetails.setAlertStatusFilter(); + await retry.tryForTime(5000, async () => { + const cells = await observability.alerts.common.getTableCells(); + expect(cells.length).to.be(ALL_ALERTS * COLUMNS); + }); }); - describe('Alerts Section with alerts', () => { - const ACTIVE_ALERTS = 2; - const RECOVERED_ALERTS = 2; - const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; - const COLUMNS = 11; - before(async () => { - await navigateToNodeDetails('demo-stack-apache-01', 'host', { - name: 'demo-stack-apache-01', - }); - await pageObjects.header.waitUntilLoadingHasFinished(); - - await pageObjects.timePicker.setAbsoluteRange( - START_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT), - END_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT) - ); - - await pageObjects.assetDetails.clickOverviewTab(); + it('can be filtered to only show "all" alerts using the filter button', async () => { + await pageObjects.assetDetails.setAlertStatusFilter(); + await retry.tryForTime(5000, async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ALL_ALERTS); }); + }); - after(async () => { - await navigateToNodeDetails('Jennys-MBP.fritz.box', 'host', { - name: 'Jennys-MBP.fritz.box', - }); - await pageObjects.header.waitUntilLoadingHasFinished(); - - await pageObjects.timePicker.setAbsoluteRange( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), - END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) - ); + it('can be filtered to only show "active" alerts using the filter button', async () => { + await pageObjects.assetDetails.setAlertStatusFilter(ALERT_STATUS_ACTIVE); + await retry.tryForTime(5000, async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ACTIVE_ALERTS); }); + const pageUrl = await browser.getCurrentUrl(); + expect(pageUrl).to.contain('alertStatus%3Aactive'); + }); - it('should show / hide alerts section with active alerts and show / hide closed section content', async () => { - await pageObjects.assetDetails.alertsSectionCollapsibleExist(); - // Expanded by default - await pageObjects.assetDetails.alertsSectionClosedContentMissing(); - // Collapse - await pageObjects.assetDetails.alertsSectionCollapsibleClick(); - await pageObjects.assetDetails.alertsSectionClosedContentExist(); - // Expand - await pageObjects.assetDetails.alertsSectionCollapsibleClick(); - await pageObjects.assetDetails.alertsSectionClosedContentMissing(); + it('can be filtered to only show "recovered" alerts using the filter button', async () => { + await pageObjects.assetDetails.setAlertStatusFilter(ALERT_STATUS_RECOVERED); + await retry.tryForTime(5000, async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(RECOVERED_ALERTS); }); + const pageUrl = await browser.getCurrentUrl(); + expect(pageUrl).to.contain('alertStatus%3Arecovered'); + }); - it('should show alert summary ', async () => { - await pageObjects.assetDetails.setAlertStatusFilter(); - await retry.try(async () => { - const cells = await observability.alerts.common.getTableCells(); - expect(cells.length).to.be(ALL_ALERTS * COLUMNS); - }); - }); + it('can be filtered to only show "untracked" alerts using the filter button', async () => { + await pageObjects.assetDetails.setAlertStatusFilter(ALERT_STATUS_UNTRACKED); + await observability.alerts.common.getNoDataStateOrFail(); + const pageUrl = await browser.getCurrentUrl(); + expect(pageUrl).to.contain('alertStatus%3Auntracked'); + }); - it('can be filtered to only show "all" alerts using the filter button', async () => { - await pageObjects.assetDetails.setAlertStatusFilter(); - await retry.try(async () => { - const tableRows = await observability.alerts.common.getTableCellsInRows(); - expect(tableRows.length).to.be(ALL_ALERTS); - }); - }); + it('should render alerts count for a host inside a flyout', async () => { + await pageObjects.assetDetails.clickOverviewTab(); - it('can be filtered to only show "active" alerts using the filter button', async () => { - await pageObjects.assetDetails.setAlertStatusFilter(ALERT_STATUS_ACTIVE); - await retry.try(async () => { - const tableRows = await observability.alerts.common.getTableCellsInRows(); - expect(tableRows.length).to.be(ACTIVE_ALERTS); - }); - const pageUrl = await browser.getCurrentUrl(); - expect(pageUrl).to.contain('alertStatus%3Aactive'); + await retry.tryForTime(30 * 1000, async () => { + await observability.components.alertSummaryWidget.getFullSizeComponentSelectorOrFail(); }); - it('can be filtered to only show "recovered" alerts using the filter button', async () => { - await pageObjects.assetDetails.setAlertStatusFilter(ALERT_STATUS_RECOVERED); - await retry.try(async () => { - const tableRows = await observability.alerts.common.getTableCellsInRows(); - expect(tableRows.length).to.be(RECOVERED_ALERTS); - }); - const pageUrl = await browser.getCurrentUrl(); - expect(pageUrl).to.contain('alertStatus%3Arecovered'); - }); + const activeAlertsCount = + await observability.components.alertSummaryWidget.getActiveAlertCount(); + const totalAlertsCount = + await observability.components.alertSummaryWidget.getTotalAlertCount(); - it('can be filtered to only show "untracked" alerts using the filter button', async () => { - await pageObjects.assetDetails.setAlertStatusFilter(ALERT_STATUS_UNTRACKED); - await observability.alerts.common.getNoDataStateOrFail(); - const pageUrl = await browser.getCurrentUrl(); - expect(pageUrl).to.contain('alertStatus%3Auntracked'); - }); + expect(activeAlertsCount.trim()).to.equal('2'); + expect(totalAlertsCount.trim()).to.equal('4'); }); - }); - describe('Metadata Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickMetadataTab(); + it('should render "N/A" when processes summary is not available in flyout', async () => { + await pageObjects.assetDetails.clickProcessesTab(); + const processesTotalValue = + await pageObjects.assetDetails.getProcessesTabContentTotalValue(); + const processValue = await processesTotalValue.getVisibleText(); + expect(processValue).to.eql('N/A'); }); + }); + }); - it('should show metadata table', async () => { - await pageObjects.assetDetails.metadataTableExists(); - }); + describe('Metadata Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetadataTab(); + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_DATE.format(DATE_PICKER_FORMAT), + END_HOST_DATE.format(DATE_PICKER_FORMAT) + ); + }); - it('should render metadata tab, pin and unpin table row', async () => { - // Add Pin - await pageObjects.assetDetails.clickAddMetadataPin(); - expect(await pageObjects.assetDetails.metadataRemovePinExists()).to.be(true); + it('should show metadata table', async () => { + await pageObjects.assetDetails.metadataTableExists(); + }); - // Persist pin after refresh - await browser.refresh(); - await retry.try(async () => { - // Temporary until URL state isn't implemented - await pageObjects.assetDetails.clickMetadataTab(); - await pageObjects.infraHome.waitForLoading(); - const removePinExist = await pageObjects.assetDetails.metadataRemovePinExists(); - expect(removePinExist).to.be(true); - }); + it('should render metadata tab, pin and unpin table row', async () => { + // Add Pin + await pageObjects.assetDetails.clickAddMetadataPin(); + expect(await pageObjects.assetDetails.metadataRemovePinExists()).to.be(true); - // Remove Pin - await pageObjects.assetDetails.clickRemoveMetadataPin(); - expect(await pageObjects.assetDetails.metadataRemovePinExists()).to.be(false); + // Persist pin after refresh + await browser.refresh(); + await retry.tryForTime(5000, async () => { + // Temporary until URL state isn't implemented + await pageObjects.assetDetails.clickMetadataTab(); + await pageObjects.infraHome.waitForLoading(); + const removePinExist = await pageObjects.assetDetails.metadataRemovePinExists(); + expect(removePinExist).to.be(true); }); - it('preserves search term between page reloads', async () => { - const searchInput = await pageObjects.assetDetails.getMetadataSearchField(); + // Remove Pin + await pageObjects.assetDetails.clickRemoveMetadataPin(); + expect(await pageObjects.assetDetails.metadataRemovePinExists()).to.be(false); + }); - expect(await searchInput.getAttribute('value')).to.be(''); + it('preserves search term between page reloads', async () => { + const searchInput = await pageObjects.assetDetails.getMetadataSearchField(); - await searchInput.type('test'); - await refreshPageWithDelay(); + expect(await searchInput.getAttribute('value')).to.be(''); - await retry.try(async () => { - expect(await searchInput.getAttribute('value')).to.be('test'); - }); - await searchInput.clearValue(); + await searchInput.type('test'); + await refreshPageWithDelay(); + + await retry.tryForTime(5000, async () => { + expect(await searchInput.getAttribute('value')).to.be('test'); }); + await searchInput.clearValue(); }); + }); - describe('Metrics Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickMetricsTab(); - }); + describe('Metrics Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetricsTab(); + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_DATE.format(DATE_PICKER_FORMAT), + END_HOST_DATE.format(DATE_PICKER_FORMAT) + ); + }); - [ - { metric: 'cpu', chartsCount: 4 }, - { metric: 'memory', chartsCount: 2 }, - { metric: 'disk', chartsCount: 3 }, - { metric: 'network', chartsCount: 1 }, - { metric: 'log', chartsCount: 1 }, - ].forEach(({ metric, chartsCount }) => { - it(`should render ${chartsCount} ${metric} chart(s)`, async () => { - const charts = await pageObjects.assetDetails.getMetricsTabHostCharts(metric); - expect(charts.length).to.equal(chartsCount); - }); + [ + { metric: 'cpu', chartsCount: 4 }, + { metric: 'memory', chartsCount: 2 }, + { metric: 'disk', chartsCount: 3 }, + { metric: 'network', chartsCount: 1 }, + { metric: 'log', chartsCount: 1 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart(s)`, async () => { + const charts = await pageObjects.assetDetails.getMetricsTabHostCharts(metric); + expect(charts.length).to.equal(chartsCount); + }); - it(`should render a quick access for ${metric} in the side panel`, async () => { - await pageObjects.assetDetails.quickAccessItemExists(metric); - }); + it(`should render a quick access for ${metric} in the side panel`, async () => { + await pageObjects.assetDetails.quickAccessItemExists(metric); }); }); + }); - describe('Processes Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickProcessesTab(); - await pageObjects.header.waitUntilLoadingHasFinished(); + describe('Processes Tab', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'); + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await navigateToNodeDetails('Jennys-MBP.fritz.box', 'host', { + name: 'Jennys-MBP.fritz.box', }); + await pageObjects.assetDetails.clickProcessesTab(); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), + END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) + ); + }); + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/infra/metrics_hosts_processes' + ); + await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await navigateToNodeDetails('host-1', 'host', { name: 'host-1' }); + }); - it('should render processes tab and with Total Value summary', async () => { - const processesTotalValue = - await pageObjects.assetDetails.getProcessesTabContentTotalValue(); - const processValue = await processesTotalValue.getVisibleText(); - expect(processValue).to.eql('313'); - }); + it('should render processes tab and with Total Value summary', async () => { + const processesTotalValue = + await pageObjects.assetDetails.getProcessesTabContentTotalValue(); + const processValue = await processesTotalValue.getVisibleText(); + expect(processValue).to.eql('313'); + }); - it('should expand processes table row', async () => { - await pageObjects.assetDetails.processesTableExists(); - await pageObjects.assetDetails.getProcessesTableBody(); - await pageObjects.assetDetails.clickProcessesTableExpandButton(); - }); + it('should expand processes table row', async () => { + await pageObjects.assetDetails.processesTableExists(); + await pageObjects.assetDetails.getProcessesTableBody(); + await pageObjects.assetDetails.clickProcessesTableExpandButton(); + }); - it('preserves search term between page reloads', async () => { - const searchInput = await pageObjects.assetDetails.getProcessesSearchField(); + it('preserves search term between page reloads', async () => { + const searchInput = await pageObjects.assetDetails.getProcessesSearchField(); - expect(await searchInput.getAttribute('value')).to.be(''); + expect(await searchInput.getAttribute('value')).to.be(''); - await searchInput.type('test'); - await refreshPageWithDelay(); + await searchInput.type('test'); + await refreshPageWithDelay(); - await retry.try(async () => { - expect(await searchInput.getAttribute('value')).to.be('test'); - }); - await searchInput.clearValue(); + await retry.tryForTime(5000, async () => { + expect(await searchInput.getAttribute('value')).to.be('test'); }); + await searchInput.clearValue(); + }); - it('shows an error message when typing invalid term into the search input', async () => { - const searchInput = await pageObjects.assetDetails.getProcessesSearchField(); + it('shows an error message when typing invalid term into the search input', async () => { + const searchInput = await pageObjects.assetDetails.getProcessesSearchField(); - await pageObjects.assetDetails.processesSearchInputErrorMissing(); - await searchInput.type(','); - await pageObjects.assetDetails.processesSearchInputErrorExists(); - }); + await pageObjects.assetDetails.processesSearchInputErrorMissing(); + await searchInput.type(','); + await pageObjects.assetDetails.processesSearchInputErrorExists(); }); + }); - describe('Logs Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickLogsTab(); - }); + describe('Logs Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickLogsTab(); + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_DATE.format(DATE_PICKER_FORMAT), + END_HOST_DATE.format(DATE_PICKER_FORMAT) + ); + }); - it('should render logs tab', async () => { - await pageObjects.assetDetails.logsExists(); - }); + it('should render logs tab', async () => { + await pageObjects.assetDetails.logsExists(); + }); - it('preserves search term between page reloads', async () => { - const searchInput = await pageObjects.assetDetails.getLogsSearchField(); + it('preserves search term between page reloads', async () => { + const searchInput = await pageObjects.assetDetails.getLogsSearchField(); - expect(await searchInput.getAttribute('value')).to.be(''); + expect(await searchInput.getAttribute('value')).to.be(''); - await searchInput.type('test'); - await refreshPageWithDelay(); + await searchInput.type('test'); + await refreshPageWithDelay(); - await retry.try(async () => { - expect(await searchInput.getAttribute('value')).to.be('test'); - }); - await searchInput.clearValue(); + await retry.tryForTime(5000, async () => { + expect(await searchInput.getAttribute('value')).to.be('test'); }); + await searchInput.clearValue(); }); + }); - describe('Osquery Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickOsqueryTab(); - }); + describe('Osquery Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickOsqueryTab(); + }); - it('should show a date picker', async () => { - expect(await pageObjects.timePicker.timePickerExists()).to.be(false); - }); + it('should show a date picker', async () => { + expect(await pageObjects.timePicker.timePickerExists()).to.be(false); }); + }); - describe('Profiling tab', () => { - it('shows the Profiling tab if Profiling integration UI setting is enabled', async () => { - await setInfrastructureProfilingIntegrationUiSetting(true); - await pageObjects.assetDetails.profilingTabExists(); - }); + describe('Profiling tab', () => { + it('shows the Profiling tab if Profiling integration UI setting is enabled', async () => { + await setInfrastructureProfilingIntegrationUiSetting(true); + await pageObjects.assetDetails.profilingTabExists(); + }); - it('hides the Profiling tab if Profiling integration UI setting is disabled', async () => { - await setInfrastructureProfilingIntegrationUiSetting(false); - await pageObjects.assetDetails.profilingTabMissing(); - }); + it('hides the Profiling tab if Profiling integration UI setting is disabled', async () => { + await setInfrastructureProfilingIntegrationUiSetting(false); + await pageObjects.assetDetails.profilingTabMissing(); }); + }); - describe('Host with alerts and no processes', () => { - before(async () => { - await navigateToNodeDetails('demo-stack-mysql-01', 'host', { - name: 'demo-stack-mysql-01', + describe('Callouts', () => { + describe('Legacy alert metric callout', () => { + [{ metric: 'cpu' }, { metric: 'rx' }, { metric: 'tx' }].forEach(({ metric }) => { + it(`Should show for: ${metric}`, async () => { + await navigateToNodeDetails('host-1', 'host', { + name: 'host-1', + alertMetric: metric, + }); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await retry.tryForTime(5000, async () => { + expect(await pageObjects.assetDetails.legacyMetricAlertCalloutExists()).to.be(true); + }); }); - await pageObjects.timePicker.setAbsoluteRange( - START_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT), - END_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT) - ); }); - it('should render alerts count for a host inside a flyout', async () => { - await pageObjects.assetDetails.clickOverviewTab(); + [{ metric: 'cpuV2' }, { metric: 'rxV2' }, { metric: 'txV2' }].forEach(({ metric }) => { + it(`Should not show for: ${metric}`, async () => { + await navigateToNodeDetails('host-1', 'host', { + name: 'host-1', + alertMetric: metric, + }); - await retry.tryForTime(30 * 1000, async () => { - await observability.components.alertSummaryWidget.getFullSizeComponentSelectorOrFail(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await retry.tryForTime(5000, async () => { + expect(await pageObjects.assetDetails.legacyMetricAlertCalloutExists()).to.be( + false + ); + }); }); + }); + }); + }); + }); - const activeAlertsCount = - await observability.components.alertSummaryWidget.getActiveAlertCount(); - const totalAlertsCount = - await observability.components.alertSummaryWidget.getTotalAlertCount(); + describe('#Asset type: host with kubernetes section', () => { + before(async () => { + synthEsClient = await getInfraSynthtraceEsClient(esClient); + await synthEsClient.clean(); + await synthEsClient.index( + generateHostsWithK8sNodeData({ + from: DATE_WITH_HOSTS_DATA_FROM, + to: DATE_WITH_HOSTS_DATA_TO, + }) + ); + await navigateToNodeDetails('demo-stack-kubernetes-01', 'host', { + name: 'demo-stack-kubernetes-01', + }); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_DATE.format(DATE_PICKER_FORMAT), + END_HOST_DATE.format(DATE_PICKER_FORMAT) + ); + }); - expect(activeAlertsCount.trim()).to.equal('2'); - expect(totalAlertsCount.trim()).to.equal('3'); - }); + after(async () => { + await synthEsClient.clean(); + }); - it('should render "N/A" when processes summary is not available in flyout', async () => { - await pageObjects.assetDetails.clickProcessesTab(); - const processesTotalValue = - await pageObjects.assetDetails.getProcessesTabContentTotalValue(); - const processValue = await processesTotalValue.getVisibleText(); - expect(processValue).to.eql('N/A'); - }); + describe('Overview Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickOverviewTab(); }); - describe('#With Kubernetes section', () => { - before(async () => { - await navigateToNodeDetails('demo-stack-kubernetes-01', 'host', { - name: 'demo-stack-kubernetes-01', + [ + { metric: 'cpuUsage', value: '50.0%' }, + { metric: 'normalizedLoad1m', value: '18.8%' }, + { metric: 'memoryUsage', value: '35.0%' }, + { metric: 'diskUsage', value: '1,223.0%' }, + ].forEach(({ metric, value }) => { + it(`${metric} tile should show ${value}`, async () => { + await retry.tryForTime(3 * 1000, async () => { + const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue(metric); + expect(tileValue).to.eql(value); }); - await pageObjects.header.waitUntilLoadingHasFinished(); }); + }); - describe('Overview Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickOverviewTab(); + [ + { metric: 'cpu', chartsCount: 2 }, + { metric: 'memory', chartsCount: 1 }, + { metric: 'disk', chartsCount: 2 }, + { metric: 'network', chartsCount: 1 }, + { metric: 'kubernetes', chartsCount: 2 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart`, async () => { + await retry.tryForTime(5000, async () => { + const charts = await (metric === 'kubernetes' + ? pageObjects.assetDetails.getOverviewTabKubernetesMetricCharts() + : pageObjects.assetDetails.getOverviewTabHostMetricCharts(metric)); - await pageObjects.timePicker.setAbsoluteRange( - START_HOST_KUBERNETES_SECTION_DATE.format(DATE_PICKER_FORMAT), - END_HOST_KUBERNETES_SECTION_DATE.format(DATE_PICKER_FORMAT) - ); + expect(charts.length).to.equal(chartsCount); }); + }); + }); + }); - [ - { metric: 'cpuUsage', value: '100.0%' }, - { metric: 'normalizedLoad1m', value: '1,300.3%' }, - { metric: 'memoryUsage', value: '42.2%' }, - { metric: 'diskUsage', value: '36.0%' }, - ].forEach(({ metric, value }) => { - it(`${metric} tile should show ${value}`, async () => { - await retry.tryForTime(3 * 1000, async () => { - const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( - metric - ); - expect(tileValue).to.eql(value); - }); - }); - }); + describe('Metrics Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetricsTab(); + }); - [ - { metric: 'cpu', chartsCount: 2 }, - { metric: 'memory', chartsCount: 1 }, - { metric: 'disk', chartsCount: 2 }, - { metric: 'network', chartsCount: 1 }, - { metric: 'kubernetes', chartsCount: 2 }, - ].forEach(({ metric, chartsCount }) => { - it(`should render ${chartsCount} ${metric} chart`, async () => { - await retry.try(async () => { - const charts = await (metric === 'kubernetes' - ? pageObjects.assetDetails.getOverviewTabKubernetesMetricCharts() - : pageObjects.assetDetails.getOverviewTabHostMetricCharts(metric)); - - expect(charts.length).to.equal(chartsCount); - }); - }); - }); - }); + [ + { metric: 'cpu', chartsCount: 4 }, + { metric: 'memory', chartsCount: 2 }, + { metric: 'disk', chartsCount: 3 }, + { metric: 'network', chartsCount: 1 }, + { metric: 'log', chartsCount: 1 }, + { metric: 'kubernetes', chartsCount: 4 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart(s)`, async () => { + await retry.tryForTime(5000, async () => { + const charts = await (metric === 'kubernetes' + ? pageObjects.assetDetails.getMetricsTabKubernetesCharts() + : pageObjects.assetDetails.getMetricsTabHostCharts(metric)); - describe('Metrics Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickMetricsTab(); + expect(charts.length).to.equal(chartsCount); }); + }); - [ - { metric: 'cpu', chartsCount: 4 }, - { metric: 'memory', chartsCount: 2 }, - { metric: 'disk', chartsCount: 3 }, - { metric: 'network', chartsCount: 1 }, - { metric: 'log', chartsCount: 1 }, - { metric: 'kubernetes', chartsCount: 4 }, - ].forEach(({ metric, chartsCount }) => { - it(`should render ${chartsCount} ${metric} chart(s)`, async () => { - await retry.try(async () => { - const charts = await (metric === 'kubernetes' - ? pageObjects.assetDetails.getMetricsTabKubernetesCharts() - : pageObjects.assetDetails.getMetricsTabHostCharts(metric)); - - expect(charts.length).to.equal(chartsCount); - }); - }); - - it(`should render a quick access for ${metric} in the side panel`, async () => { - await retry.try(async () => { - await pageObjects.assetDetails.quickAccessItemExists(metric); - }); - }); + it(`should render a quick access for ${metric} in the side panel`, async () => { + await retry.tryForTime(5000, async () => { + await pageObjects.assetDetails.quickAccessItemExists(metric); }); }); }); + }); + }); - describe('Callouts', () => { - describe('Legacy alert metric callout', () => { - [{ metric: 'cpu' }, { metric: 'rx' }, { metric: 'tx' }].forEach(({ metric }) => { - it(`Should show for: ${metric}`, async () => { - await navigateToNodeDetails('Jennys-MBP.fritz.box', 'host', { - name: 'Jennys-MBP.fritz.box', - alertMetric: metric, - }); - await pageObjects.header.waitUntilLoadingHasFinished(); - - await retry.try(async () => { - expect(await pageObjects.assetDetails.legacyMetricAlertCalloutExists()).to.be( - true - ); - }); - }); - }); + describe('#Asset Type: container', () => { + before(async () => { + synthEsClient = await getInfraSynthtraceEsClient(esClient); + await synthEsClient.clean(); + await synthEsClient.index( + generateDockerContainersData({ + from: DATE_WITH_DOCKER_DATA_FROM, + to: DATE_WITH_DOCKER_DATA_TO, + count: 1, + }) + ); + await navigateToNodeDetails('container-id-0', 'container', { name: 'container-id-0' }); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_CONTAINER_DATE.format(DATE_PICKER_FORMAT), + END_CONTAINER_DATE.format(DATE_PICKER_FORMAT) + ); + }); - [{ metric: 'cpuV2' }, { metric: 'rxV2' }, { metric: 'txV2' }].forEach(({ metric }) => { - it(`Should not show for: ${metric}`, async () => { - await navigateToNodeDetails('Jennys-MBP.fritz.box', 'host', { - name: 'Jennys-MBP.fritz.box', - alertMetric: metric, - }); + after(async () => { + await synthEsClient.clean(); + }); - await pageObjects.header.waitUntilLoadingHasFinished(); + describe('when container asset view is disabled', () => { + before(async () => { + await setInfrastructureContainerAssetViewUiSetting(false); + await navigateToNodeDetails('container-id-0', 'container', { name: 'container-id-0' }); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_CONTAINER_DATE.format(DATE_PICKER_FORMAT), + END_CONTAINER_DATE.format(DATE_PICKER_FORMAT) + ); + }); - await retry.try(async () => { - expect(await pageObjects.assetDetails.legacyMetricAlertCalloutExists()).to.be( - false - ); - }); - }); - }); - }); + it('should show old view of container details', async () => { + await testSubjects.find('metricsEmptyViewState'); }); }); - describe('#Asset Type: container', () => { - let synthEsClient: InfraSynthtraceEsClient; + describe('when container asset view is enabled', () => { before(async () => { - const version = await infraSynthtraceKibanaClient.fetchLatestSystemPackageVersion(); - await infraSynthtraceKibanaClient.installSystemPackage(version); - synthEsClient = await getInfraSynthtraceEsClient(esClient); - await synthEsClient.index( - generateDockerContainersData({ - from: DATE_WITH_DOCKER_DATA_FROM, - to: DATE_WITH_DOCKER_DATA_TO, - count: 1, - }) + await setInfrastructureContainerAssetViewUiSetting(true); + await navigateToNodeDetails('container-id-0', 'container', { name: 'container-id-0' }); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_CONTAINER_DATE.format(DATE_PICKER_FORMAT), + END_CONTAINER_DATE.format(DATE_PICKER_FORMAT) ); }); - - after(async () => { - await synthEsClient.clean(); + it('should show asset container details page', async () => { + await pageObjects.assetDetails.getOverviewTab(); }); - describe('when container asset view is disabled', () => { - it('should show old view of container details', async () => { - await setInfrastructureContainerAssetViewUiSetting(false); - await navigateToNodeDetails('container-id-0', 'container', { - name: 'container-id-0', + [ + { metric: 'cpuUsage', value: '25.0%' }, + { metric: 'memoryUsage', value: '20.0%' }, + ].forEach(({ metric, value }) => { + it(`${metric} tile should show ${value}`, async () => { + await retry.tryForTime(3 * 1000, async () => { + const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue(metric); + expect(tileValue).to.eql(value); }); - await pageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.find('metricsEmptyViewState'); }); }); - describe('when container asset view is enabled', () => { - before(async () => { - await setInfrastructureContainerAssetViewUiSetting(true); - await navigateToNodeDetails('container-id-0', 'container', { - name: 'container-id-0', - }); - await pageObjects.header.waitUntilLoadingHasFinished(); - await pageObjects.timePicker.setAbsoluteRange( - START_CONTAINER_DATE.format(DATE_PICKER_FORMAT), - END_CONTAINER_DATE.format(DATE_PICKER_FORMAT) - ); - }); - it('should show asset container details page', async () => { - await pageObjects.assetDetails.getOverviewTab(); + [ + { metric: 'cpu', chartsCount: 1 }, + { metric: 'memory', chartsCount: 1 }, + { metric: 'disk', chartsCount: 1 }, + { metric: 'network', chartsCount: 1 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { + const charts = await pageObjects.assetDetails.getOverviewTabDockerMetricCharts(metric); + expect(charts.length).to.equal(chartsCount); }); + }); - [ - { metric: 'cpu', chartsCount: 1 }, - { metric: 'memory', chartsCount: 1 }, - { metric: 'disk', chartsCount: 1 }, - { metric: 'network', chartsCount: 1 }, - ].forEach(({ metric, chartsCount }) => { - it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { - const charts = await pageObjects.assetDetails.getOverviewTabDockerMetricCharts( - metric - ); - expect(charts.length).to.equal(chartsCount); - }); - }); + it('should show / hide alerts section with no alerts and show / hide closed section content', async () => { + await pageObjects.assetDetails.alertsSectionCollapsibleExist(); + // Collapsed by default + await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsExist(); + // Expand + await pageObjects.assetDetails.alertsSectionCollapsibleClick(); + await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsMissing(); + // Check if buttons exist + await pageObjects.assetDetails.overviewLinkToAlertsExist(); + await pageObjects.assetDetails.overviewOpenAlertsFlyoutExist(); + }); - it('should show / hide alerts section with no alerts and show / hide closed section content', async () => { - await pageObjects.assetDetails.alertsSectionCollapsibleExist(); - // Collapsed by default - await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsExist(); - // Expand - await pageObjects.assetDetails.alertsSectionCollapsibleClick(); - await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsMissing(); - // Check if buttons exist - await pageObjects.assetDetails.overviewLinkToAlertsExist(); - await pageObjects.assetDetails.overviewOpenAlertsFlyoutExist(); + describe('Metadata Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetadataTab(); }); - describe('Metadata Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickMetadataTab(); - }); - - it('should show metadata table', async () => { - await pageObjects.assetDetails.metadataTableExists(); - }); + it('should show metadata table', async () => { + await pageObjects.assetDetails.metadataTableExists(); + }); + }); + describe('Logs Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickLogsTab(); }); - describe('Logs Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickLogsTab(); - }); - it('should render logs tab', async () => { - await pageObjects.assetDetails.logsExists(); - }); + it('should render logs tab', async () => { + await pageObjects.assetDetails.logsExists(); + }); - it('preserves search term between page reloads', async () => { - const searchInput = await pageObjects.assetDetails.getLogsSearchField(); + it('preserves search term between page reloads', async () => { + const searchInput = await pageObjects.assetDetails.getLogsSearchField(); - expect(await searchInput.getAttribute('value')).to.be(''); + expect(await searchInput.getAttribute('value')).to.be(''); - await searchInput.type('test'); - await refreshPageWithDelay(); + await searchInput.type('test'); + await refreshPageWithDelay(); - await retry.try(async () => { - expect(await searchInput.getAttribute('value')).to.be('test'); - }); - await searchInput.clearValue(); + await retry.tryForTime(5000, async () => { + expect(await searchInput.getAttribute('value')).to.be('test'); }); + await searchInput.clearValue(); }); + }); - describe('Metrics Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickMetricsTab(); - }); + describe('Metrics Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetricsTab(); + }); - [ - { metric: 'cpu', chartsCount: 1 }, - { metric: 'memory', chartsCount: 1 }, - { metric: 'disk', chartsCount: 1 }, - { metric: 'network', chartsCount: 1 }, - ].forEach(({ metric, chartsCount }) => { - it(`should render ${chartsCount} ${metric} chart(s)`, async () => { - const charts = await pageObjects.assetDetails.getMetricsTabDockerCharts(metric); - expect(charts.length).to.equal(chartsCount); - }); + [ + { metric: 'cpu', chartsCount: 1 }, + { metric: 'memory', chartsCount: 1 }, + { metric: 'disk', chartsCount: 1 }, + { metric: 'network', chartsCount: 1 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart(s)`, async () => { + const charts = await pageObjects.assetDetails.getMetricsTabDockerCharts(metric); + expect(charts.length).to.equal(chartsCount); + }); - it(`should render a quick access for ${metric} in the side panel`, async () => { - await pageObjects.assetDetails.quickAccessItemExists(metric); - }); + it(`should render a quick access for ${metric} in the side panel`, async () => { + await pageObjects.assetDetails.quickAccessItemExists(metric); }); }); }); diff --git a/x-pack/test/functional/page_objects/asset_details.ts b/x-pack/test/functional/page_objects/asset_details.ts index 4e3da871a91b6..95a7819fb11a6 100644 --- a/x-pack/test/functional/page_objects/asset_details.ts +++ b/x-pack/test/functional/page_objects/asset_details.ts @@ -202,8 +202,8 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { async getMetadataAppliedFilter() { const filter = await testSubjects.find( `filter-badge-${stringHash( - 'host.architecture: arm64' - )} filter filter-enabled filter-key-host.architecture filter-value-arm64 filter-unpinned filter-id-0` + 'host.name: host-1' + )} filter filter-enabled filter-key-host.name filter-value-host-1 filter-unpinned filter-id-0` ); return filter.getVisibleText(); }, @@ -263,6 +263,10 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { return processesListElements[index].findByCssSelector('dt'); }, + async processesContentExist() { + return testSubjects.existOrFail('infraAssetDetailsProcessesTabContent'); + }, + async getProcessesTabContentTotalValue() { const processesListElements = await testSubjects.findAll( 'infraAssetDetailsProcessesSummaryTableItem' diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index ec7908d916e9b..0a85972f48d9f 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -37,7 +37,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide }, async getWaffleMap() { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const element = await testSubjects.find('waffleMap'); if (!element) { throw new Error(); @@ -98,13 +98,13 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide }, async clickOnGoToNodeDetails() { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await testSubjects.click('viewAssetDetailsContextMenuItem'); }); }, async clickOnNodeDetailsFlyoutOpenAsPage() { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { await testSubjects.click('infraAssetDetailsOpenAsPageButton'); }); }, @@ -139,7 +139,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide // wait for input value to echo the input before submitting // this ensures the React state has caught up with the events - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const value = await input.getAttribute('value'); expect(value).to.eql(query); }); diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index d92cf51892a5f..86c7f330e081c 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -57,7 +57,7 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { return table.findAllByTestSubject('hostsView-tableRow'); }, - async getHostsRowData(row: WebElementWrapper) { + async getHostsRowDataWithAlerts(row: WebElementWrapper) { // Find all the row cells const cells = await row.findAllByCssSelector('[data-test-subj*="hostsView-tableRow-"]'); @@ -87,6 +87,26 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { }; }, + async getHostsRowData(row: WebElementWrapper) { + // Find all the row cells + const cells = await row.findAllByCssSelector('[data-test-subj*="hostsView-tableRow-"]'); + + // Retrieve content for each cell + const [title, cpuUsage, normalizedLoad, memoryUsage, memoryFree, diskSpaceUsage, rx, tx] = + await Promise.all(cells.map((cell) => this.getHostsCellContent(cell))); + + return { + title, + cpuUsage, + normalizedLoad, + memoryUsage, + memoryFree, + diskSpaceUsage, + rx, + tx, + }; + }, + async getHostsCellContent(cell: WebElementWrapper) { const cellContent = await cell.findByClassName('euiTableCellContent'); return cellContent.getVisibleText(); @@ -247,17 +267,17 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { }, // Sorting - getMemoryHeader() { - return testSubjects.find('tableHeaderCell_memory_5'); + getCpuHeader() { + return testSubjects.find('tableHeaderCell_cpuV2_2'); }, getTitleHeader() { - return testSubjects.find('tableHeaderCell_title_2'); + return testSubjects.find('tableHeaderCell_title_1'); }, - async sortByMemoryUsage() { - const memory = await this.getMemoryHeader(); - const button = await testSubjects.findDescendant('tableHeaderSortButton', memory); + async sortByCpuUsage() { + const cpu = await this.getCpuHeader(); + const button = await testSubjects.findDescendant('tableHeaderSortButton', cpu); await button.click(); }, diff --git a/x-pack/test/functional/page_objects/infra_saved_views.ts b/x-pack/test/functional/page_objects/infra_saved_views.ts index 3a843974043c2..2f19c959f355f 100644 --- a/x-pack/test/functional/page_objects/infra_saved_views.ts +++ b/x-pack/test/functional/page_objects/infra_saved_views.ts @@ -71,7 +71,7 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) { }, async ensureViewIsLoaded(name: string) { - await retry.try(async () => { + await retry.tryForTime(5000, async () => { const subject = await testSubjects.find('savedViews-openPopover'); expect(await subject.getVisibleText()).to.be(name); }); diff --git a/x-pack/test/functional_solution_sidenav/config.ts b/x-pack/test/functional_solution_sidenav/config.ts new file mode 100644 index 0000000000000..f997aaea7c5e2 --- /dev/null +++ b/x-pack/test/functional_solution_sidenav/config.ts @@ -0,0 +1,31 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +/** + * NOTE: The solution view is currently only available in the cloud environment. + * This test suite fakes a cloud environement by setting the cloud.id and cloud.base_url + */ + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../functional/config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + // Note: the base64 string in the cloud.id config contains the ES endpoint required in the functional tests + '--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=', + '--xpack.cloud.base_url=https://cloud.elastic.co', + ], + }, + }; +} diff --git a/x-pack/test/functional_solution_sidenav/ftr_provider_context.ts b/x-pack/test/functional_solution_sidenav/ftr_provider_context.ts new file mode 100644 index 0000000000000..d6c0afa5ceffd --- /dev/null +++ b/x-pack/test/functional_solution_sidenav/ftr_provider_context.ts @@ -0,0 +1,13 @@ +/* + * 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 { GenericFtrProviderContext } from '@kbn/test'; +import { pageObjects } from '../functional/page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; +export { pageObjects }; diff --git a/x-pack/test/functional_solution_sidenav/index.ts b/x-pack/test/functional_solution_sidenav/index.ts new file mode 100644 index 0000000000000..9056551e235d5 --- /dev/null +++ b/x-pack/test/functional_solution_sidenav/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ +/* eslint-disable import/no-default-export */ + +import { FtrProviderContext } from './ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Solution navigation smoke tests', function () { + loadTestFile(require.resolve('./tests/observability_sidenav')); + loadTestFile(require.resolve('./tests/search_sidenav')); + loadTestFile(require.resolve('./tests/security_sidenav')); + }); +}; diff --git a/x-pack/test/functional_solution_sidenav/services.ts b/x-pack/test/functional_solution_sidenav/services.ts new file mode 100644 index 0000000000000..9508ce5eba16d --- /dev/null +++ b/x-pack/test/functional_solution_sidenav/services.ts @@ -0,0 +1,10 @@ +/* + * 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 { services as functionalServices } from '../functional/services'; + +export const services = functionalServices; diff --git a/x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts b/x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts new file mode 100644 index 0000000000000..9e4db0c33da52 --- /dev/null +++ b/x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts @@ -0,0 +1,79 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']); + const spaces = getService('spaces'); + const browser = getService('browser'); + + describe('observability solution', () => { + let cleanUp: () => Promise; + let spaceCreated: { id: string } = { id: '' }; + + before(async () => { + // Navigate to the spaces management page which will log us in Kibana + await common.navigateToUrl('management', 'kibana/spaces', { + shouldUseHashForSubUrl: false, + }); + + // Create a space with the observability solution and navigate to its home page + ({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'oblt' })); + await browser.navigateTo(spaces.getRootUrl(spaceCreated.id)); + }); + + after(async () => { + // Clean up space created + await cleanUp(); + }); + + describe('sidenav & breadcrumbs', () => { + it('renders the correct nav and navigate to links', async () => { + const expectNoPageReload = await solutionNavigation.createNoPageReloadCheck(); + + await solutionNavigation.expectExists(); + await solutionNavigation.breadcrumbs.expectExists(); + + // check side nav links + await solutionNavigation.sidenav.expectSectionExists('observability_project_nav'); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'observabilityOnboarding', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'observabilityOnboarding', + }); + + // check the AI & ML subsection + await solutionNavigation.sidenav.openSection('observability_project_nav.aiMl'); // open AI & ML subsection + await solutionNavigation.sidenav.clickLink({ deepLinkId: 'ml:anomalyDetection' }); + await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'ml:anomalyDetection' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'AI & ML' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'ml:anomalyDetection', + }); + + // navigate to a different section + await solutionNavigation.sidenav.openSection('project_settings_project_nav'); + await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' }); + await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' }); + + // navigate back to the home page using header logo + await solutionNavigation.clickLogo(); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'observabilityOnboarding', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'observabilityOnboarding', + }); + + await expectNoPageReload(); + }); + }); + }); +} diff --git a/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts b/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts new file mode 100644 index 0000000000000..bf1dfe993e1ae --- /dev/null +++ b/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts @@ -0,0 +1,82 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']); + const spaces = getService('spaces'); + const browser = getService('browser'); + + describe('search solution', () => { + let cleanUp: () => Promise; + let spaceCreated: { id: string } = { id: '' }; + + before(async () => { + // Navigate to the spaces management page which will log us in Kibana + await common.navigateToUrl('management', 'kibana/spaces', { + shouldUseHashForSubUrl: false, + }); + + // Create a space with the search solution and navigate to its home page + ({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'es' })); + await browser.navigateTo(spaces.getRootUrl(spaceCreated.id)); + }); + + after(async () => { + // Clean up space created + await cleanUp(); + }); + + describe('sidenav & breadcrumbs', () => { + it('renders the correct nav and navigate to links', async () => { + const expectNoPageReload = await solutionNavigation.createNoPageReloadCheck(); + + await solutionNavigation.expectExists(); + await solutionNavigation.breadcrumbs.expectExists(); + + // check side nav links + await solutionNavigation.sidenav.expectSectionExists('search_project_nav'); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'enterpriseSearch', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'enterpriseSearch', + }); + + // check the Content > Indices section + await solutionNavigation.sidenav.clickLink({ + deepLinkId: 'enterpriseSearchContent:searchIndices', + }); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'enterpriseSearchContent:searchIndices', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'enterpriseSearchContent:searchIndices', + }); + + // navigate to a different section + await solutionNavigation.sidenav.openSection('project_settings_project_nav'); + await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' }); + await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' }); + + // navigate back to the home page using header logo + await solutionNavigation.clickLogo(); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'enterpriseSearch', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'enterpriseSearch', + }); + + await expectNoPageReload(); + }); + }); + }); +} diff --git a/x-pack/test/functional_solution_sidenav/tests/security_sidenav.ts b/x-pack/test/functional_solution_sidenav/tests/security_sidenav.ts new file mode 100644 index 0000000000000..153c809ff715b --- /dev/null +++ b/x-pack/test/functional_solution_sidenav/tests/security_sidenav.ts @@ -0,0 +1,74 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']); + const spaces = getService('spaces'); + const browser = getService('browser'); + const testSubjects = getService('testSubjects'); + + describe('security solution', () => { + let cleanUp: () => Promise; + let spaceCreated: { id: string } = { id: '' }; + + before(async () => { + // Navigate to the spaces management page which will log us in Kibana + await common.navigateToUrl('management', 'kibana/spaces', { + shouldUseHashForSubUrl: false, + }); + + // Create a space with the security solution and navigate to its home page + ({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'security' })); + await browser.navigateTo(spaces.getRootUrl(spaceCreated.id)); + }); + + after(async () => { + // Clean up space created + await cleanUp(); + }); + + describe('sidenav & breadcrumbs', () => { + it('renders the correct nav and navigate to links', async () => { + const expectNoPageReload = await solutionNavigation.createNoPageReloadCheck(); + + await solutionNavigation.expectExists(); + await solutionNavigation.breadcrumbs.expectExists(); + + // check side nav links + await solutionNavigation.sidenav.expectSectionExists('security_solution_nav'); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'securitySolutionUI:get_started', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'securitySolutionUI:get_started', + }); + + // check the Investigations subsection + await solutionNavigation.sidenav.openPanel('investigations'); // open Investigations panel + await testSubjects.click(`~solutionSideNavPanelLink-timelines`); + await solutionNavigation.sidenav.expectLinkActive({ navId: 'investigations' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Timelines' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'securitySolutionUI:timelines', + }); + + // navigate back to the home page using header logo + await solutionNavigation.clickLogo(); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'securitySolutionUI:get_started', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'securitySolutionUI:get_started', + }); + + await expectNoPageReload(); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts index 4b8cad3b2c2ef..72c98a41b85f7 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts @@ -5,334 +5,17 @@ * 2.0. */ -import expect from '@kbn/expect'; -import type { AppDeepLinkId } from '@kbn/core-chrome-browser'; +import { SolutionNavigationProvider } from '@kbn/test-suites-src/functional/page_objects'; -import type { NavigationID as MlNavId } from '@kbn/default-nav-ml'; -import type { NavigationID as AlNavId } from '@kbn/default-nav-analytics'; -import type { NavigationID as MgmtNavId } from '@kbn/default-nav-management'; -import type { NavigationID as DevNavId } from '@kbn/default-nav-devtools'; - -// use this for nicer type suggestions, but allow any string anyway -type NavigationId = MlNavId | AlNavId | MgmtNavId | DevNavId | string; - -import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; import { NavigationalSearchPageObject } from '@kbn/test-suites-xpack/functional/page_objects/navigational_search'; import type { FtrProviderContext } from '../ftr_provider_context'; -const getSectionIdTestSubj = (sectionId: NavigationId) => `~nav-item-${sectionId}`; - export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { - const testSubjects = ctx.getService('testSubjects'); - const browser = ctx.getService('browser'); - const retry = ctx.getService('retry'); - const log = ctx.getService('log'); - - async function getByVisibleText( - selector: string | (() => Promise), - text: string - ) { - const subjects = - typeof selector === 'string' ? await testSubjects.findAll(selector) : await selector(); - let found: WebElementWrapper | null = null; - for (const subject of subjects) { - const visibleText = await subject.getVisibleText(); - if (visibleText === text) { - found = subject; - break; - } - } - return found; - } + const solutionNavigation = SolutionNavigationProvider(ctx); return { - // check that chrome ui is in the serverless (project) mode - async expectExists() { - await testSubjects.existOrFail('kibanaProjectHeader'); - }, - async clickLogo() { - await testSubjects.click('nav-header-logo'); - }, - // side nav related actions - sidenav: { - async expectLinkExists( - by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string } - ) { - if ('deepLinkId' in by) { - await testSubjects.existOrFail(`~nav-item-deepLinkId-${by.deepLinkId}`); - } else if ('navId' in by) { - await testSubjects.existOrFail(`~nav-item-id-${by.navId}`); - } else { - expect(await getByVisibleText('~nav-item', by.text)).not.be(null); - } - }, - async expectLinkMissing( - by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string } - ) { - if ('deepLinkId' in by) { - await testSubjects.missingOrFail(`~nav-item-deepLinkId-${by.deepLinkId}`); - } else if ('navId' in by) { - await testSubjects.missingOrFail(`~nav-item-id-${by.navId}`); - } else { - expect(await getByVisibleText('~nav-item', by.text)).be(null); - } - }, - async expectLinkActive( - by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string } - ) { - await this.expectLinkExists(by); - if ('deepLinkId' in by) { - await testSubjects.existOrFail( - `~nav-item-deepLinkId-${by.deepLinkId} & ~nav-item-isActive` - ); - } else if ('navId' in by) { - await testSubjects.existOrFail(`~nav-item-id-${by.navId} & ~nav-item-isActive`); - } else { - await retry.try(async () => { - const link = await getByVisibleText('~nav-item', by.text); - expect(await link!.elementHasClass(`nav-item-isActive`)).to.be(true); - }); - } - }, - async clickLink(by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }) { - await this.expectLinkExists(by); - if ('deepLinkId' in by) { - await testSubjects.click(`~nav-item-deepLinkId-${by.deepLinkId}`); - } else if ('navId' in by) { - await testSubjects.click(`~nav-item-id-${by.navId}`); - } else { - await retry.try(async () => { - const link = await getByVisibleText('~nav-item', by.text); - await link!.click(); - }); - } - }, - async findLink(by: { deepLinkId: AppDeepLinkId } | { navId: string } | { text: string }) { - await this.expectLinkExists(by); - if ('deepLinkId' in by) { - return testSubjects.find(`~nav-item-deepLinkId-${by.deepLinkId}`); - } else if ('navId' in by) { - return testSubjects.find(`~nav-item-id-${by.navId}`); - } else { - return retry.try(async () => { - const link = await getByVisibleText('~nav-item', by.text); - return link; - }); - } - }, - async expectSectionExists(sectionId: NavigationId) { - log.debug('ServerlessCommonNavigation.sidenav.expectSectionExists', sectionId); - await testSubjects.existOrFail(getSectionIdTestSubj(sectionId)); - }, - async isSectionOpen(sectionId: NavigationId) { - await this.expectSectionExists(sectionId); - const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`); - const isExpanded = await collapseBtn.getAttribute('aria-expanded'); - return isExpanded === 'true'; - }, - async expectSectionOpen(sectionId: NavigationId) { - log.debug('ServerlessCommonNavigation.sidenav.expectSectionOpen', sectionId); - await this.expectSectionExists(sectionId); - await retry.waitFor(`section ${sectionId} to be open`, async () => { - const isOpen = await this.isSectionOpen(sectionId); - return isOpen; - }); - }, - async expectSectionClosed(sectionId: NavigationId) { - await this.expectSectionExists(sectionId); - await retry.waitFor(`section ${sectionId} to be closed`, async () => { - const isOpen = await this.isSectionOpen(sectionId); - return !isOpen; - }); - }, - async openSection(sectionId: NavigationId) { - log.debug('ServerlessCommonNavigation.sidenav.openSection', sectionId); - await this.expectSectionExists(sectionId); - const isOpen = await this.isSectionOpen(sectionId); - if (isOpen) return; - const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`); - await collapseBtn.click(); - await this.expectSectionOpen(sectionId); - }, - async closeSection(sectionId: NavigationId) { - await this.expectSectionExists(sectionId); - const isOpen = await this.isSectionOpen(sectionId); - if (!isOpen) return; - const collapseBtn = await testSubjects.find(`~accordionArrow-${sectionId}`); - await collapseBtn.click(); - await this.expectSectionClosed(sectionId); - }, - async isCollapsed() { - const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton'); - return (await collapseNavBtn.getAttribute('aria-expanded')) === 'false'; - }, - async isExpanded() { - return !(await this.isCollapsed()); - }, - /** - * Toggles collapsed state of sidenav - */ - async toggle(collapsed?: boolean) { - const currentlyCollapsed = await this.isCollapsed(); - const shouldBeCollapsed = collapsed ?? !currentlyCollapsed; - - if (currentlyCollapsed !== shouldBeCollapsed) { - log.debug( - 'ServerlessCommonNavigation.sidenav.toggle', - shouldBeCollapsed ? 'Collapsing' : 'Expanding' - ); - - const collapseNavBtn = await testSubjects.find('euiCollapsibleNavButton'); - await collapseNavBtn.click(); - } - }, - }, - breadcrumbs: { - async expectExists() { - await testSubjects.existOrFail('breadcrumbs'); - }, - async clickBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) { - if ('deepLinkId' in by) { - await testSubjects.click(`~breadcrumb-deepLinkId-${by.deepLinkId}`); - } else { - await (await getByVisibleText('~breadcrumb', by.text))?.click(); - } - }, - getBreadcrumb(by: { deepLinkId: AppDeepLinkId } | { text: string }) { - if ('deepLinkId' in by) { - return testSubjects.find(`~breadcrumb-deepLinkId-${by.deepLinkId}`); - } else { - return getByVisibleText('~breadcrumb', by.text); - } - }, - async expectBreadcrumbExists(by: { deepLinkId: AppDeepLinkId } | { text: string }) { - log.debug( - 'ServerlessCommonNavigation.breadcrumbs.expectBreadcrumbExists', - JSON.stringify(by) - ); - if ('deepLinkId' in by) { - await testSubjects.existOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`); - } else { - await retry.try(async () => { - expect(await getByVisibleText('~breadcrumb', by.text)).not.be(null); - }); - } - }, - async expectBreadcrumbMissing(by: { deepLinkId: AppDeepLinkId } | { text: string }) { - if ('deepLinkId' in by) { - await testSubjects.missingOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`); - } else { - await retry.try(async () => { - expect(await getByVisibleText('~breadcrumb', by.text)).be(null); - }); - } - }, - async expectBreadcrumbTexts(expectedBreadcrumbTexts: string[]) { - log.debug( - 'ServerlessCommonNavigation.breadcrumbs.expectBreadcrumbTexts', - JSON.stringify(expectedBreadcrumbTexts) - ); - await retry.try(async () => { - const breadcrumbsContainer = await testSubjects.find('breadcrumbs'); - const breadcrumbs = await breadcrumbsContainer.findAllByTestSubject('~breadcrumb'); - breadcrumbs.shift(); // remove home - expect(expectedBreadcrumbTexts.length).to.eql(breadcrumbs.length); - const texts = await Promise.all(breadcrumbs.map((b) => b.getVisibleText())); - expect(expectedBreadcrumbTexts).to.eql(texts); - }); - }, - }, + ...solutionNavigation, search: new SvlNavigationSearchPageObject(ctx), - recent: { - async expectExists() { - await testSubjects.existOrFail('nav-item-recentlyAccessed'); - }, - async expectHidden() { - await testSubjects.missingOrFail('nav-item-recentlyAccessed', { timeout: 1000 }); - }, - async expectLinkExists(text: string) { - await this.expectExists(); - let foundLink: WebElementWrapper | null = null; - await retry.try(async () => { - foundLink = await getByVisibleText( - async () => - (await testSubjects.find('nav-item-recentlyAccessed')).findAllByTagName('a'), - text - ); - expect(!!foundLink).to.be(true); - }); - - return foundLink!; - }, - async clickLink(text: string) { - const link = await this.expectLinkExists(text); - await link!.click(); - }, - }, - - // helper to assert that the page did not reload - async createNoPageReloadCheck() { - const trackReloadTs = Date.now(); - await browser.execute( - ({ ts }) => { - // @ts-ignore - window.__testTrackReload__ = ts; - }, - { - ts: trackReloadTs, - } - ); - - return async () => { - const noReload = await browser.execute( - ({ ts }) => { - // @ts-ignore - return window.__testTrackReload__ && window.__testTrackReload__ === ts; - }, - { - ts: trackReloadTs, - } - ); - expect(noReload).to.be(true); - }; - }, - - // embedded dev console - devConsole: { - async expectEmbeddedConsoleControlBarExists() { - await testSubjects.existOrFail('consoleEmbeddedSection'); - }, - async expectEmbeddedConsoleToBeOpen() { - await testSubjects.existOrFail('consoleEmbeddedBody'); - }, - async expectEmbeddedConsoleToBeClosed() { - await testSubjects.missingOrFail('consoleEmbeddedBody'); - }, - async clickEmbeddedConsoleControlBar() { - await testSubjects.click('consoleEmbeddedControlBar'); - }, - async expectEmbeddedConsoleNotebooksButtonExists() { - await testSubjects.existOrFail('consoleEmbeddedNotebooksButton'); - }, - async clickEmbeddedConsoleNotebooksButton() { - await testSubjects.click('consoleEmbeddedNotebooksButton'); - }, - async expectEmbeddedConsoleNotebooksToBeOpen() { - await testSubjects.existOrFail('consoleEmbeddedNotebooksContainer'); - }, - async expectEmbeddedConsoleNotebooksToBeClosed() { - await testSubjects.missingOrFail('consoleEmbeddedNotebooksContainer'); - }, - async expectEmbeddedConsoleNotebookListItemToBeAvailable(id: string) { - await testSubjects.existOrFail(`console-embedded-notebook-select-btn-${id}`); - }, - async clickEmbeddedConsoleNotebook(id: string) { - await testSubjects.click(`console-embedded-notebook-select-btn-${id}`); - }, - async expectEmbeddedConsoleNotebookToBeAvailable(id: string) { - await testSubjects.click(`console-embedded-notebook-select-btn-${id}`); - }, - }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts b/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts index bc9879a032357..5ada9a50d45c3 100644 --- a/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts @@ -14,6 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'svlCommonNavigation', 'common', 'svlSearchConnectorsPage', + 'embeddedConsole', ]); const testSubjects = getService('testSubjects'); const browser = getService('browser'); diff --git a/x-pack/test_serverless/functional/test_suites/search/console_notebooks.ts b/x-pack/test_serverless/functional/test_suites/search/console_notebooks.ts index 696e1fd693ae7..5cf4a4ceeb856 100644 --- a/x-pack/test_serverless/functional/test_suites/search/console_notebooks.ts +++ b/x-pack/test_serverless/functional/test_suites/search/console_notebooks.ts @@ -7,8 +7,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation']); +export default function ({ getPageObjects }: FtrProviderContext) { + const pageObjects = getPageObjects(['svlCommonPage', 'embeddedConsole']); describe('Console Notebooks', function () { before(async () => { @@ -17,39 +17,39 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('has notebooks view available', async () => { // Expect Console Bar & Notebooks button to exist - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleControlBarExists(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksButtonExists(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleControlBarExists(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksButtonExists(); // Click the Notebooks button to open console to See Notebooks - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeOpen(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeOpen(); // Click the Notebooks button again to switch to the dev console - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeClosed(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeClosed(); // Clicking control bar should close the console - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeClosed(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeClosed(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed(); // Re-open console and then open Notebooks - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen(); - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeOpen(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeOpen(); // Close the console - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeClosed(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeClosed(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed(); }); it('can open notebooks', async () => { // Click the Notebooks button to open console to See Notebooks - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebooksButton(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebooksToBeOpen(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebooksButton(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebooksToBeOpen(); const defaultNotebooks = [ '00_quick_start', @@ -59,13 +59,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { '04_multilingual', ]; for (const notebookId of defaultNotebooks) { - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebookListItemToBeAvailable( - notebookId - ); - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleNotebook(notebookId); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleNotebookToBeAvailable( + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebookListItemToBeAvailable( notebookId ); + await pageObjects.embeddedConsole.clickEmbeddedConsoleNotebook(notebookId); + await pageObjects.embeddedConsole.expectEmbeddedConsoleNotebookToBeAvailable(notebookId); } }); }); diff --git a/x-pack/test_serverless/functional/test_suites/search/embedded_console.ts b/x-pack/test_serverless/functional/test_suites/search/embedded_console.ts index 9ae2c37b6075c..2a14fab24f81a 100644 --- a/x-pack/test_serverless/functional/test_suites/search/embedded_console.ts +++ b/x-pack/test_serverless/functional/test_suites/search/embedded_console.ts @@ -7,13 +7,13 @@ import { FtrProviderContext } from '../../ftr_provider_context'; -type PageObjects = Pick, 'svlCommonNavigation'>; +type PageObjects = Pick, 'embeddedConsole'>; export async function testHasEmbeddedConsole(pageObjects: PageObjects) { - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleControlBarExists(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed(); - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen(); - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleControlBarExists(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed(); } diff --git a/x-pack/test_serverless/functional/test_suites/search/index_management.ts b/x-pack/test_serverless/functional/test_suites/search/index_management.ts index dc8d147c9d348..7d8464bf5b128 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index_management.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index_management.ts @@ -12,7 +12,7 @@ import { testHasEmbeddedConsole } from './embedded_console'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const pageObjects = getPageObjects([ 'svlCommonPage', - 'svlCommonNavigation', + 'embeddedConsole', 'common', 'header', 'indexManagement', diff --git a/x-pack/test_serverless/functional/test_suites/search/landing_page.ts b/x-pack/test_serverless/functional/test_suites/search/landing_page.ts index 0bac8fc80f111..643d163bf7672 100644 --- a/x-pack/test_serverless/functional/test_suites/search/landing_page.ts +++ b/x-pack/test_serverless/functional/test_suites/search/landing_page.ts @@ -10,11 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { testHasEmbeddedConsole } from './embedded_console'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects([ - 'svlSearchLandingPage', - 'svlCommonPage', - 'svlCommonNavigation', - ]); + const pageObjects = getPageObjects(['svlSearchLandingPage', 'svlCommonPage', 'embeddedConsole']); const svlSearchNavigation = getService('svlSearchNavigation'); describe('landing page', function () { diff --git a/x-pack/test_serverless/functional/test_suites/search/pipelines.ts b/x-pack/test_serverless/functional/test_suites/search/pipelines.ts index 6f7c52af51f83..9cb53310e5405 100644 --- a/x-pack/test_serverless/functional/test_suites/search/pipelines.ts +++ b/x-pack/test_serverless/functional/test_suites/search/pipelines.ts @@ -14,6 +14,7 @@ export default function ({ getPageObjects }: FtrProviderContext) { 'common', 'svlIngestPipelines', 'svlManagementPage', + 'embeddedConsole', ]); describe('ingest pipelines', function () { before(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts b/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts index 868413cc3b7b5..036751ef970da 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts @@ -11,7 +11,12 @@ import { RoleCredentials } from '../../../shared/services'; import { testHasEmbeddedConsole } from './embedded_console'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation', 'svlSearchHomePage']); + const pageObjects = getPageObjects([ + 'svlCommonPage', + 'svlCommonNavigation', + 'svlSearchHomePage', + 'embeddedConsole', + ]); const svlUserManager = getService('svlUserManager'); const uiSettings = getService('uiSettings'); let roleAuthc: RoleCredentials; @@ -56,11 +61,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('has console quickstart link on page', async () => { await pageObjects.svlSearchHomePage.expectConsoleLinkExists(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed(); await pageObjects.svlSearchHomePage.clickConsoleLink(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeOpen(); - await pageObjects.svlCommonNavigation.devConsole.clickEmbeddedConsoleControlBar(); - await pageObjects.svlCommonNavigation.devConsole.expectEmbeddedConsoleToBeClosed(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeOpen(); + await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar(); + await pageObjects.embeddedConsole.expectEmbeddedConsoleToBeClosed(); }); it('has endpoints link and flyout', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts index 476b47b060b2c..9545707c51540 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts @@ -15,7 +15,12 @@ import { createLlmProxy, LlmProxy } from './utils/create_llm_proxy'; const esArchiveIndex = 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation', 'searchPlayground']); + const pageObjects = getPageObjects([ + 'svlCommonPage', + 'svlCommonNavigation', + 'searchPlayground', + 'embeddedConsole', + ]); const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); const supertestWithoutAuth = getService('supertestWithoutAuth'); diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index b1f73748b5e0d..36aaf983bae08 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -39,10 +39,6 @@ "@kbn/apm-plugin", "@kbn/server-route-repository", "@kbn/core-chrome-browser", - "@kbn/default-nav-ml", - "@kbn/default-nav-analytics", - "@kbn/default-nav-management", - "@kbn/default-nav-devtools", "@kbn/security-plugin", "@kbn/security-solution-plugin", "@kbn/security-solution-plugin/public/management/cypress",