mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
1a9b241229
commit
f233933562
12 changed files with 307 additions and 171 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 };
|
||||
};
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue