[Infrastructure UI] Add logs tab to hosts view flyout (#159969)

## Summary

This PR adds the logs tab to the flyout and enables the LogStream flyout
actions.

_Flyout_
<img width="1210" alt="image"
src="d5f93447-82cb-4f72-8b5e-988d32613138">


_Hosts View_
<img width="1210" alt="image"
src="ffff2e5b-9ce2-49a9-98a5-1bf6bc5dbb64">


### For reviewer
- The search bar is slightly broken. Sometimes, the results don't match
with what's typed in the search bar. Since it's an existing problem in
the logs component used in the flyout, we'll address this problem in
another PR

### How to test this PR 

- Start a local Kibana instance
- Navigate to `Inventory > Hosts`
- Open the flyout for a host
  - Click on logs tab
- Copy the URL with the logs tab open and open in a new tab (should keep
the state)
- Play with the search (It's a bit broken), copy the URL and open in a
new tab (should keep the state)
This commit is contained in:
Carlos Crespo 2023-06-20 18:40:54 +02:00 committed by GitHub
parent 1a9b241229
commit f233933562
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 307 additions and 171 deletions

View file

@ -36,7 +36,15 @@ export const Content = ({
<Anomalies nodeName={node.name} onClose={overrides?.anomalies?.onClose} />
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.LOGS}>
<Logs nodeId={node.id} nodeType={nodeType} currentTime={currentTimeRange.to} />
<Logs
nodeName={node.name}
nodeType={nodeType}
currentTime={currentTimeRange.to}
logViewReference={overrides?.logs?.logView?.reference}
logViewLoading={overrides?.logs?.logView?.loading}
search={overrides?.logs?.query}
onSearchChange={(query) => onChange({ logs: { query } })}
/>
</TabPanel>
<TabPanel activeWhen={FlyoutTabIds.METADATA}>
<Metadata
@ -66,7 +74,7 @@ export const Content = ({
nodeName={node.name}
nodeType={nodeType}
currentTime={currentTimeRange.to}
searchFilter={overrides?.processes?.query}
search={overrides?.processes?.query}
onSearchFilterChange={(query) => onChange({ processes: { query } })}
/>
</TabPanel>

View file

@ -11,31 +11,54 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { DEFAULT_LOG_VIEW, LogViewReference } from '../../../../../common/log_views';
import type { InventoryItemType } from '../../../../../common/inventory_models/types';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { LogStream } from '../../../log_stream';
import { findInventoryFields } from '../../../../../common/inventory_models';
import { InfraLoadingPanel } from '../../../loading';
export interface LogsProps {
currentTime: number;
nodeId: string;
logViewReference?: LogViewReference | null;
logViewLoading?: boolean;
nodeName: string;
nodeType: InventoryItemType;
search?: string;
onSearchChange?: (query: string) => void;
}
const TEXT_QUERY_THROTTLE_INTERVAL_MS = 1000;
const TEXT_QUERY_THROTTLE_INTERVAL_MS = 500;
export const Logs = ({ nodeId, nodeType, currentTime }: LogsProps) => {
export const Logs = ({
nodeName,
currentTime,
nodeType,
logViewReference,
search,
logViewLoading = false,
onSearchChange,
}: LogsProps) => {
const { services } = useKibanaContextForPlugin();
const { locators } = services;
const [textQuery, setTextQuery] = useState('');
const [textQueryDebounced, setTextQueryDebounced] = useState('');
const [textQuery, setTextQuery] = useState(search ?? '');
const [textQueryDebounced, setTextQueryDebounced] = useState(search ?? '');
const startTimestamp = currentTime - 60 * 60 * 1000; // 60 minutes
useDebounce(() => setTextQueryDebounced(textQuery), TEXT_QUERY_THROTTLE_INTERVAL_MS, [textQuery]);
useDebounce(
() => {
if (onSearchChange) {
onSearchChange(textQuery);
}
setTextQueryDebounced(textQuery);
},
TEXT_QUERY_THROTTLE_INTERVAL_MS,
[textQuery]
);
const filter = useMemo(() => {
const query = [
`${findInventoryFields(nodeType).id}: "${nodeId}"`,
`${findInventoryFields(nodeType).id}: "${nodeName}"`,
...(textQueryDebounced !== '' ? [textQueryDebounced] : []),
].join(' and ');
@ -43,59 +66,84 @@ export const Logs = ({ nodeId, nodeType, currentTime }: LogsProps) => {
language: 'kuery',
query,
};
}, [nodeType, nodeId, textQueryDebounced]);
}, [nodeType, nodeName, textQueryDebounced]);
const onQueryChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setTextQuery(e.target.value);
}, []);
const logView: LogViewReference = useMemo(
() => (logViewReference ? logViewReference : DEFAULT_LOG_VIEW),
[logViewReference]
);
const logsUrl = useMemo(() => {
return locators.nodeLogsLocator.getRedirectUrl({
nodeType,
nodeId,
nodeId: nodeName,
time: startTimestamp,
filter: textQueryDebounced,
logView,
});
}, [locators.nodeLogsLocator, nodeId, nodeType, startTimestamp, textQueryDebounced]);
}, [locators.nodeLogsLocator, nodeName, nodeType, startTimestamp, textQueryDebounced, logView]);
return (
<>
<EuiFlexGroup gutterSize={'m'} alignItems={'center'} responsive={false}>
<EuiFlexItem>
<EuiFieldSearch
data-test-subj="infraTabComponentFieldSearch"
fullWidth
placeholder={i18n.translate('xpack.infra.nodeDetails.logs.textFieldPlaceholder', {
defaultMessage: 'Search for log entries...',
})}
value={textQuery}
isClearable
onChange={onQueryChange}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<RedirectAppLinks coreStart={services}>
<EuiButtonEmpty
data-test-subj="infraTabComponentOpenInLogsButton"
size="xs"
flush="both"
iconType="popout"
href={logsUrl}
>
<EuiFlexGroup direction="column" data-test-subj="infraAssetDetailsLogsTabContent">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}>
<EuiFlexItem>
<EuiFieldSearch
data-test-subj="infraAssetDetailsLogsTabFieldSearch"
fullWidth
placeholder={i18n.translate('xpack.infra.nodeDetails.logs.textFieldPlaceholder', {
defaultMessage: 'Search for log entries...',
})}
value={textQuery}
isClearable
onChange={onQueryChange}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<RedirectAppLinks coreStart={services}>
<EuiButtonEmpty
data-test-subj="infraAssetDetailsLogsTabOpenInLogsButton"
size="xs"
flush="both"
iconType="popout"
href={logsUrl}
>
<FormattedMessage
id="xpack.infra.nodeDetails.logs.openLogsLink"
defaultMessage="Open in Logs"
/>
</EuiButtonEmpty>
</RedirectAppLinks>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
{logViewLoading || !logViewReference ? (
<InfraLoadingPanel
width="100%"
height="60vh"
text={
<FormattedMessage
id="xpack.infra.nodeDetails.logs.openLogsLink"
defaultMessage="Open in Logs"
id="xpack.infra.hostsViewPage.tabs.logs.loadingEntriesLabel"
defaultMessage="Loading entries"
/>
</EuiButtonEmpty>
</RedirectAppLinks>
</EuiFlexItem>
</EuiFlexGroup>
<LogStream
logView={{ type: 'log-view-reference', logViewId: 'default' }}
startTimestamp={startTimestamp}
endTimestamp={currentTime}
query={filter}
/>
</>
}
/>
) : (
<LogStream
logView={logView}
startTimestamp={startTimestamp}
endTimestamp={currentTime}
query={filter}
height="60vh"
showFlyoutAction
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -33,7 +33,7 @@ export interface ProcessesProps {
nodeName: string;
nodeType: InventoryItemType;
currentTime: number;
searchFilter?: string;
search?: string;
onSearchFilterChange?: (searchFilter: string) => void;
}
@ -46,10 +46,10 @@ export const Processes = ({
currentTime,
nodeName,
nodeType,
searchFilter,
search,
onSearchFilterChange,
}: ProcessesProps) => {
const [searchText, setSearchText] = useState(searchFilter ?? '');
const [searchText, setSearchText] = useState(search ?? '');
const [searchBarState, setSearchBarState] = useState<Query>(() =>
searchText ? Query.parse(searchText) : Query.MATCH_ALL
);

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { InventoryItemType } from '../../../common/inventory_models/types';
import { InfraAssetMetricType, SnapshotCustomMetricInput } from '../../../common/http_api';
import type { LogViewReference } from '../../../common/log_views';
import type { InventoryItemType } from '../../../common/inventory_models/types';
import type { InfraAssetMetricType, SnapshotCustomMetricInput } from '../../../common/http_api';
export type CloudProvider = 'gcp' | 'aws' | 'azure' | 'unknownProvider';
type HostMetrics = Record<InfraAssetMetricType, number | null>;
@ -55,6 +56,13 @@ export interface TabState {
alertRule?: {
onCreateRuleClick?: () => void;
};
logs?: {
query?: string;
logView?: {
reference?: LogViewReference | null;
loading?: boolean;
};
};
}
export interface FlyoutProps {

View file

@ -11,7 +11,8 @@ import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import type { HostNodeRow } from '../../hooks/use_hosts_table';
import { HostFlyout, useHostFlyoutUrlState } from '../../hooks/use_host_flyout_url_state';
import { AssetDetails } from '../../../../../components/asset_details/asset_details';
import { metadataTab, processesTab } from './tabs';
import { orderedFlyoutTabs } from './tabs';
import { useLogViewReference } from '../../hooks/use_log_view_reference';
export interface Props {
node: HostNodeRow;
@ -22,6 +23,9 @@ const NODE_TYPE = 'host' as InventoryItemType;
export const FlyoutWrapper = ({ node, closeFlyout }: Props) => {
const { getDateRangeAsTimestamp } = useUnifiedSearchContext();
const { logViewReference, loading } = useLogViewReference({
id: 'hosts-flyout-logs-view',
});
const currentTimeRange = useMemo(
() => ({
...getDateRangeAsTimestamp(),
@ -30,31 +34,39 @@ export const FlyoutWrapper = ({ node, closeFlyout }: Props) => {
[getDateRangeAsTimestamp]
);
const [hostFlyoutOpen, setHostFlyoutOpen] = useHostFlyoutUrlState();
const [hostFlyoutState, setHostFlyoutState] = useHostFlyoutUrlState();
return (
<AssetDetails
node={node}
nodeType={NODE_TYPE}
currentTimeRange={currentTimeRange}
activeTabId={hostFlyoutOpen?.selectedTabId}
activeTabId={hostFlyoutState?.tabId}
overrides={{
metadata: {
query: hostFlyoutOpen?.metadataSearch,
query: hostFlyoutState?.metadataSearch,
showActionsColumn: true,
},
processes: {
query: hostFlyoutOpen?.processSearch,
query: hostFlyoutState?.processSearch,
},
logs: {
query: hostFlyoutState?.logsSearch,
logView: {
reference: logViewReference,
loading,
},
},
}}
onTabsStateChange={(state) =>
setHostFlyoutOpen({
setHostFlyoutState({
metadataSearch: state.metadata?.query,
processSearch: state.processes?.query,
selectedTabId: state.activeTabId as HostFlyout['selectedTabId'],
logsSearch: state.logs?.query,
tabId: state.activeTabId as HostFlyout['tabId'],
})
}
tabs={[metadataTab, processesTab]}
tabs={orderedFlyoutTabs}
links={['apmServices', 'uptime']}
renderMode={{
showInFlyout: true,

View file

@ -8,18 +8,26 @@
import { i18n } from '@kbn/i18n';
import { FlyoutTabIds, type Tab } from '../../../../../components/asset_details/types';
export const processesTab: Tab = {
id: FlyoutTabIds.PROCESSES,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
'data-test-subj': 'hostsView-flyout-tabs-processes',
};
export const metadataTab: Tab = {
id: FlyoutTabIds.METADATA,
name: i18n.translate('xpack.infra.nodeDetails.tabs.metadata.title', {
defaultMessage: 'Metadata',
}),
'data-test-subj': 'hostsView-flyout-tabs-metadata',
};
export const orderedFlyoutTabs: Tab[] = [
{
id: FlyoutTabIds.METADATA,
name: i18n.translate('xpack.infra.nodeDetails.tabs.metadata.title', {
defaultMessage: 'Metadata',
}),
'data-test-subj': 'hostsView-flyout-tabs-metadata',
},
{
id: FlyoutTabIds.PROCESSES,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
'data-test-subj': 'hostsView-flyout-tabs-processes',
},
{
id: FlyoutTabIds.LOGS,
name: i18n.translate('xpack.infra.nodeDetails.tabs.logs.title', {
defaultMessage: 'Logs',
}),
'data-test-subj': 'hostsView-flyout-tabs-logs',
},
];

View file

@ -5,15 +5,9 @@
* 2.0.
*/
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { DEFAULT_LOG_VIEW } from '../../../../../../../common/log_views';
import type {
LogIndexReference,
LogViewReference,
} from '../../../../../../../common/log_views/types';
import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana';
import { InfraLoadingPanel } from '../../../../../../components/loading';
import { LogStream } from '../../../../../../components/log_stream';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
@ -22,6 +16,7 @@ import { useLogsSearchUrlState } from '../../../hooks/use_logs_search_url_state'
import { LogsLinkToStream } from './logs_link_to_stream';
import { LogsSearchBar } from './logs_search_bar';
import { createHostsFilter } from '../../../utils';
import { useLogViewReference } from '../../../hooks/use_log_view_reference';
export const LogsTabContent = () => {
const [filterQuery] = useLogsSearchUrlState();
@ -29,56 +24,15 @@ export const LogsTabContent = () => {
const { from, to } = useMemo(() => getDateRangeAsTimestamp(), [getDateRangeAsTimestamp]);
const { hostNodes, loading } = useHostsViewContext();
const [logViewIndices, setLogViewIndices] = useState<LogIndexReference>();
const {
services: {
logViews: { client },
},
} = useKibanaContextForPlugin();
useEffect(() => {
const getLogView = async () => {
const { attributes } = await client.getLogView(DEFAULT_LOG_VIEW);
setLogViewIndices(attributes.logIndices);
};
getLogView();
}, [client, setLogViewIndices]);
const hostsFilterQuery = useMemo(
() => createHostsFilter(hostNodes.map((p) => p.name)),
[hostNodes]
);
const logView: LogViewReference = useMemo(() => {
return {
type: 'log-view-inline',
id: 'hosts-logs-view',
attributes: {
name: 'Hosts Logs View',
description: 'Default view for hosts logs tab',
logIndices: logViewIndices!,
logColumns: [
{
timestampColumn: {
id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f',
},
},
{
fieldColumn: {
id: 'eb9777a8-fcd3-420e-ba7d-172fff6da7a2',
field: 'host.name',
},
},
{
messageColumn: {
id: 'b645d6da-824b-4723-9a2a-e8cece1645c0',
},
},
],
},
};
}, [logViewIndices]);
const { logViewReference: logView, loading: logViewLoading } = useLogViewReference({
id: 'hosts-logs-view',
extraFields: ['host.name'],
});
const logsLinkToStreamQuery = useMemo(() => {
const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes.map((p) => p.name));
@ -90,7 +44,7 @@ export const LogsTabContent = () => {
return filterQuery.query || hostsFilterQueryParam;
}, [filterQuery.query, hostNodes]);
if (loading || !logViewIndices) {
if (loading || logViewLoading || !logView) {
return (
<EuiFlexGroup style={{ height: 300 }} direction="column" alignItems="stretch">
<EuiFlexItem grow>
@ -133,6 +87,7 @@ export const LogsTabContent = () => {
endTimestamp={to}
filters={[hostsFilterQuery]}
query={filterQuery}
showFlyoutAction
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -15,12 +15,12 @@ import { FlyoutTabIds } from '../../../../components/asset_details/types';
import { useUrlState } from '../../../../utils/use_url_state';
export const DEFAULT_STATE: HostFlyout = {
clickedItemId: '',
selectedTabId: FlyoutTabIds.METADATA,
itemId: '',
tabId: FlyoutTabIds.METADATA,
processSearch: undefined,
metadataSearch: undefined,
};
const HOST_FLYOUT_URL_STATE_KEY = 'hostFlyoutOpen';
const HOST_FLYOUT_URL_STATE_KEY = 'flyout';
type SetHostFlyoutState = (newProp: Payload | null) => void;
@ -47,16 +47,18 @@ export const useHostFlyoutUrlState = (): [HostFlyoutUrl, SetHostFlyoutState] =>
const FlyoutTabIdRT = rt.union([
rt.literal(FlyoutTabIds.METADATA),
rt.literal(FlyoutTabIds.PROCESSES),
rt.literal(FlyoutTabIds.LOGS),
]);
const HostFlyoutStateRT = rt.intersection([
rt.type({
clickedItemId: rt.string,
selectedTabId: FlyoutTabIdRT,
itemId: rt.string,
tabId: FlyoutTabIdRT,
}),
rt.partial({
processSearch: rt.string,
metadataSearch: rt.string,
logsSearch: rt.string,
}),
]);

View file

@ -157,8 +157,8 @@ export const useHostsTable = () => {
const items = useMemo(() => buildItemsList(hostNodes), [hostNodes]);
const clickedItem = useMemo(
() => items.find(({ id }) => id === hostFlyoutState?.clickedItemId),
[hostFlyoutState?.clickedItemId, items]
() => items.find(({ id }) => id === hostFlyoutState?.itemId),
[hostFlyoutState?.itemId, items]
);
const currentPage = useMemo(() => {
@ -181,19 +181,17 @@ export const useHostsTable = () => {
name: TABLE_COLUMN_LABEL.toggleDialogAction,
description: TABLE_COLUMN_LABEL.toggleDialogAction,
icon: ({ id }) =>
hostFlyoutState?.clickedItemId && id === hostFlyoutState?.clickedItemId
? 'minimize'
: 'expand',
hostFlyoutState?.itemId && id === hostFlyoutState?.itemId ? 'minimize' : 'expand',
type: 'icon',
'data-test-subj': 'hostsView-flyout-button',
onClick: ({ id }) => {
setHostFlyoutState({
clickedItemId: id,
itemId: id,
});
if (id === hostFlyoutState?.clickedItemId) {
if (id === hostFlyoutState?.itemId) {
setHostFlyoutState(null);
} else {
setHostFlyoutState({ clickedItemId: id });
setHostFlyoutState({ itemId: id });
}
},
},
@ -323,7 +321,7 @@ export const useHostsTable = () => {
},
],
[
hostFlyoutState?.clickedItemId,
hostFlyoutState?.itemId,
reportHostEntryClick,
searchCriteria.dateRange,
setHostFlyoutState,
@ -337,7 +335,7 @@ export const useHostsTable = () => {
currentPage,
closeFlyout,
items,
isFlyoutOpen: !!hostFlyoutState?.clickedItemId,
isFlyoutOpen: !!hostFlyoutState?.itemId,
onTableChange,
pagination,
sorting,

View file

@ -0,0 +1,64 @@
/*
* 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 { useMemo } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { v4 as uuidv4 } from 'uuid';
import { useLazyRef } from '../../../../hooks/use_lazy_ref';
import { DEFAULT_LOG_VIEW, type LogViewReference } from '../../../../../common/log_views';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
interface Props {
id: string;
extraFields?: string[];
}
export const useLogViewReference = ({ id, extraFields = [] }: Props) => {
const {
services: {
logViews: { client },
},
} = useKibanaContextForPlugin();
const { loading, value: defaultLogView } = useAsync(
() => client.getLogView(DEFAULT_LOG_VIEW),
[]
);
const logViewReference = useLazyRef<LogViewReference | null>(() => {
return !defaultLogView
? null
: {
type: 'log-view-inline',
id,
attributes: {
name: 'Hosts Logs View',
description: 'Default view for hosts logs tab',
logIndices: defaultLogView.attributes.logIndices,
logColumns: [
{
timestampColumn: {
id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f',
},
},
...extraFields.map((fieldName) => ({
fieldColumn: {
id: uuidv4(),
field: fieldName,
},
})),
{
messageColumn: {
id: 'b645d6da-824b-4723-9a2a-e8cece1645c0',
},
},
],
},
};
});
return { logViewReference: logViewReference.current, loading };
};

View file

@ -266,24 +266,51 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
});
it('should render metadata tab, add and remove filter', async () => {
const metadataTab = await pageObjects.infraHostsView.getMetadataTabName();
expect(metadataTab).to.contain('Metadata');
describe('Metadata Tab', () => {
it('should render metadata tab, add and remove filter', async () => {
const metadataTab = await pageObjects.infraHostsView.getMetadataTabName();
expect(metadataTab).to.contain('Metadata');
await pageObjects.infraHostsView.clickAddMetadataFilter();
await pageObjects.header.waitUntilLoadingHasFinished();
await pageObjects.infraHostsView.clickAddMetadataFilter();
await pageObjects.header.waitUntilLoadingHasFinished();
// Add Filter
const addedFilter = await pageObjects.infraHostsView.getAppliedFilter();
expect(addedFilter).to.contain('host.architecture: arm64');
const removeFilterExists = await pageObjects.infraHostsView.getRemoveFilterExist();
expect(removeFilterExists).to.be(true);
// Add Filter
const addedFilter = await pageObjects.infraHostsView.getAppliedFilter();
expect(addedFilter).to.contain('host.architecture: arm64');
const removeFilterExists = await pageObjects.infraHostsView.getRemoveFilterExist();
expect(removeFilterExists).to.be(true);
// Remove filter
await pageObjects.infraHostsView.clickRemoveMetadataFilter();
await pageObjects.header.waitUntilLoadingHasFinished();
const removeFilterShouldNotExist = await pageObjects.infraHostsView.getRemoveFilterExist();
expect(removeFilterShouldNotExist).to.be(false);
// Remove filter
await pageObjects.infraHostsView.clickRemoveMetadataFilter();
await pageObjects.header.waitUntilLoadingHasFinished();
const removeFilterShouldNotExist =
await pageObjects.infraHostsView.getRemoveFilterExist();
expect(removeFilterShouldNotExist).to.be(false);
});
});
describe('Processes Tab', () => {
it('should render processes tab and with Total Value summary', async () => {
await pageObjects.infraHostsView.clickProcessesFlyoutTab();
const processesTotalValue =
await pageObjects.infraHostsView.getProcessesTabContentTotalValue();
const processValue = await processesTotalValue.getVisibleText();
expect(processValue).to.eql('313');
});
it('should expand processes table row', async () => {
await pageObjects.infraHostsView.clickProcessesFlyoutTab();
await pageObjects.infraHostsView.getProcessesTable();
await pageObjects.infraHostsView.getProcessesTableBody();
await pageObjects.infraHostsView.clickProcessesTableExpandButton();
});
});
describe('Logs Tab', () => {
it('should render logs tab', async () => {
await pageObjects.infraHostsView.clickLogsFlyoutTab();
await testSubjects.existOrFail('infraAssetDetailsLogsTabContent');
});
});
it('should navigate to Uptime after click', async () => {
@ -319,19 +346,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await returnTo(HOSTS_VIEW_PATH);
});
it('should render processes tab and with Total Value summary', async () => {
await pageObjects.infraHostsView.clickProcessesFlyoutTab();
const processesTotalValue =
await pageObjects.infraHostsView.getProcessesTabContentTotalValue();
const processValue = await processesTotalValue.getVisibleText();
expect(processValue).to.eql('313');
});
describe('Processes Tab', () => {
it('should render processes tab and with Total Value summary', async () => {
await pageObjects.infraHostsView.clickProcessesFlyoutTab();
const processesTotalValue =
await pageObjects.infraHostsView.getProcessesTabContentTotalValue();
const processValue = await processesTotalValue.getVisibleText();
expect(processValue).to.eql('313');
});
it('should expand processes table row', async () => {
await pageObjects.infraHostsView.clickProcessesFlyoutTab();
await pageObjects.infraHostsView.getProcessesTable();
await pageObjects.infraHostsView.getProcessesTableBody();
await pageObjects.infraHostsView.clickProcessesTableExpandButton();
it('should expand processes table row', async () => {
await pageObjects.infraHostsView.clickProcessesFlyoutTab();
await pageObjects.infraHostsView.getProcessesTable();
await pageObjects.infraHostsView.getProcessesTableBody();
await pageObjects.infraHostsView.clickProcessesTableExpandButton();
});
});
});

View file

@ -32,6 +32,10 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
return testSubjects.click('hostsView-flyout-tabs-processes');
},
async clickLogsFlyoutTab() {
return testSubjects.click('hostsView-flyout-tabs-logs');
},
async clickProcessesTableExpandButton() {
return testSubjects.click('infraProcessRowButton');
},