Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Infrastructure UI] Integrated the logs tab to the Hosts View #152995

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
59b980a
[Infrastructure UI] Integrated the logs tab to the Hosts View
mohamedhamed-ahmed Mar 9, 2023
713573a
Save logs search in the url
mohamedhamed-ahmed Mar 9, 2023
98b8b18
Refactoring and adding link to logs stream
mohamedhamed-ahmed Mar 10, 2023
e838fcf
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Mar 10, 2023
2002943
Merge branch 'main' of https://github.com/elastic/kibana into 957-int…
mohamedhamed-ahmed Mar 20, 2023
f9c61ed
Refactoring code and fixing link to stream query param
mohamedhamed-ahmed Mar 21, 2023
14bd31b
Merge branch 'main' of https://github.com/elastic/kibana into 957-int…
mohamedhamed-ahmed Mar 21, 2023
6e21ac9
Resolving conflict
mohamedhamed-ahmed Mar 21, 2023
c926b0b
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Mar 21, 2023
7368a8a
Merge branch 'main' of https://github.com/elastic/kibana into 957-int…
mohamedhamed-ahmed Mar 21, 2023
e15a6cf
Refactoring code
mohamedhamed-ahmed Mar 21, 2023
2aa0ea1
Resolve conflict
mohamedhamed-ahmed Mar 21, 2023
4f2726f
Merge branch 'main' of https://github.com/elastic/kibana into 957-int…
mohamedhamed-ahmed Mar 21, 2023
c4b5531
Refactoring and fixing comments
mohamedhamed-ahmed Mar 22, 2023
c44b156
Added loading indicator to logs and fixed browser history navigation
mohamedhamed-ahmed Mar 22, 2023
c605756
Changed loading indicator
mohamedhamed-ahmed Mar 24, 2023
857298b
Resolve conflict
mohamedhamed-ahmed Mar 24, 2023
f1b23df
Refactoring and fixing comments
mohamedhamed-ahmed Mar 24, 2023
81afe21
Refactoring Code and fixing a bug
mohamedhamed-ahmed Mar 24, 2023
66dc2c3
Fix use_http_request and use_snapshot state management
crespocarlos Mar 24, 2023
0406e4d
Merge pull request #1 from crespocarlos/957-fix_http_request_state_ma…
mohamedhamed-ahmed Mar 24, 2023
53fb2c4
Refactoring Code
mohamedhamed-ahmed Mar 24, 2023
ca981ff
Merge branch 'main' into 957-integrate-logs-tab-in-hosts-view
mohamedhamed-ahmed Mar 24, 2023
02ef9db
Merge branch 'main' into 957-integrate-logs-tab-in-hosts-view
kibanamachine Mar 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions x-pack/plugins/infra/public/hooks/use_http_request.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { HttpHandler } from '@kbn/core/public';
import { ToastInput } from '@kbn/core/public';
Expand Down Expand Up @@ -83,7 +83,7 @@ export function useHTTPRequest<Response>(
};
}, [abortable]);

const [request, makeRequest] = useTrackedPromise<any, Response>(
const [request, makeRequest, resetRequestState] = useTrackedPromise<any, Response>(
{
cancelPreviousOn: 'resolution',
createPromise: () => {
Expand Down Expand Up @@ -117,17 +117,13 @@ export function useHTTPRequest<Response>(
[pathname, body, method, fetch, toast, onError]
);

const loading = useMemo(() => {
if (request.state === 'resolved' && response === null) {
return true;
}
return request.state === 'pending';
}, [request.state, response]);
const loading = request.state === 'uninitialized' || request.state === 'pending';

return {
response,
error,
loading,
makeRequest,
resetRequestState,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const updateContextInUrl =
positionStateKey,
positionStateInUrlRT.encode({
position: context.latestPosition ? pickTimeKey(context.latestPosition) : null,
})
}),
{ replace: true }
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export const updateContextInUrl =
filters: context.filters,
timeRange: context.timeRange,
refreshInterval: context.refreshInterval,
})
}),
{ replace: true }
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './logs_tab_content';
mohamedhamed-ahmed marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { stringify } from 'querystring';
import { encode } from '@kbn/rison';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana';

interface LogsLinkToStreamProps {
startTimestamp: number;
endTimestamp: number;
query: string;
}

export const LogsLinkToStream = ({
startTimestamp,
endTimestamp,
query,
}: LogsLinkToStreamProps) => {
const { services } = useKibanaContextForPlugin();
const { http } = services;

const queryString = new URLSearchParams(
stringify({
logPosition: encode({
start: new Date(startTimestamp),
end: new Date(endTimestamp),
streamLive: false,
}),
logFilter: encode({
kind: 'kuery',
expression: query,
}),
})
);

const viewInLogsUrl = http.basePath.prepend(`/app/logs/stream?${queryString}`);

return (
<RedirectAppLinks coreStart={services}>
<EuiButtonEmpty
href={viewInLogsUrl}
data-test-subj="hostsView-logs-link-to-stream-button"
iconType="popout"
flush="both"
>
<FormattedMessage
id="xpack.infra.hostsViewPage.tabs.logs.openInLogsUiLinkText"
defaultMessage="Open in Logs"
/>
</EuiButtonEmpty>
</RedirectAppLinks>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useCallback, useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import { i18n } from '@kbn/i18n';
import { EuiFieldSearch } from '@elastic/eui';
import { useLogsSearchUrlState } from '../../../hooks/use_logs_search_url_state';

const debounceIntervalInMs = 1000;

export const LogsSearchBar = () => {
const [filterQuery, setFilterQuery] = useLogsSearchUrlState();
const [searchText, setSearchText] = useState(filterQuery.query);

const onQueryChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value);
}, []);

useDebounce(() => setFilterQuery({ ...filterQuery, query: searchText }), debounceIntervalInMs, [
searchText,
]);

return (
<EuiFieldSearch
data-test-subj="hostsView-logs-text-field-search"
fullWidth
isClearable
placeholder={i18n.translate('xpack.infra.hostsViewPage.tabs.logs.textFieldPlaceholder', {
defaultMessage: 'Search for log entries...',
})}
onChange={onQueryChange}
value={searchText}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { InfraLoadingPanel } from '../../../../../../components/loading';
import { SnapshotNode } from '../../../../../../../common/http_api';
import { LogStream } from '../../../../../../components/log_stream';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
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';

export const LogsTabContent = () => {
mohamedhamed-ahmed marked this conversation as resolved.
Show resolved Hide resolved
const [filterQuery] = useLogsSearchUrlState();
const { getDateRangeAsTimestamp } = useUnifiedSearchContext();
const { from, to } = useMemo(() => getDateRangeAsTimestamp(), [getDateRangeAsTimestamp]);
crespocarlos marked this conversation as resolved.
Show resolved Hide resolved
mohamedhamed-ahmed marked this conversation as resolved.
Show resolved Hide resolved
const { hostNodes, loading } = useHostsViewContext();

const hostsFilterQuery = useMemo(() => createHostsFilter(hostNodes), [hostNodes]);

const logsLinkToStreamQuery = useMemo(() => {
const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes);

if (filterQuery.query && hostsFilterQueryParam) {
return `${filterQuery.query} and ${hostsFilterQueryParam}`;
}

return filterQuery.query || hostsFilterQueryParam;
}, [filterQuery.query, hostNodes]);

if (loading) {
return (
<EuiFlexGroup style={{ height: 300 }} direction="column" alignItems="stretch">
<EuiFlexItem grow>
<InfraLoadingPanel
width="100%"
height="100%"
text={
<FormattedMessage
id="xpack.infra.hostsViewPage.tabs.logs.loadingEntriesLabel"
defaultMessage="Loading entries"
/>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}

return (
<EuiFlexGroup direction="column" gutterSize="m" data-test-subj="hostsView-logs">
<EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}>
<EuiFlexItem>
<LogsSearchBar />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LogsLinkToStream startTimestamp={from} endTimestamp={to} query={logsLinkToStreamQuery} />
mohamedhamed-ahmed marked this conversation as resolved.
Show resolved Hide resolved
</EuiFlexItem>
</EuiFlexGroup>

<EuiFlexItem>
<LogStream
height={500}
logView={{ type: 'log-view-reference', logViewId: 'default' }}
startTimestamp={from}
endTimestamp={to}
filters={[hostsFilterQuery]}
query={filterQuery}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};

const createHostsFilterQueryParam = (hostNodes: SnapshotNode[]): string => {
if (!hostNodes.length) {
return '';
}

const joinedHosts = hostNodes.map((p) => p.name).join(' or ');
const hostsQueryParam = `host.name:(${joinedHosts})`;

return hostsQueryParam;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { AlertsTabContent } from './alerts';

import { AlertsTabBadge } from './alerts_tab_badge';
import { TabIds, useTabId } from '../../hooks/use_tab_id';
import { LogsTabContent } from './logs';

const tabs = [
{
Expand All @@ -23,6 +24,13 @@ const tabs = [
}),
'data-test-subj': 'hostsView-tabs-metrics',
},
{
id: TabIds.LOGS,
name: i18n.translate('xpack.infra.hostsViewPage.tabs.logs.title', {
defaultMessage: 'Logs',
}),
'data-test-subj': 'hostsView-tabs-logs',
},
{
id: TabIds.ALERTS,
name: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.title', {
Expand Down Expand Up @@ -63,6 +71,11 @@ export const Tabs = () => {
<MetricsGrid />
</div>
)}
{renderedTabsSet.current.has(TabIds.LOGS) && (
<div hidden={selectedTabId !== TabIds.LOGS}>
<LogsTabContent />
</div>
)}
{renderedTabsSet.current.has(TabIds.ALERTS) && (
<div hidden={selectedTabId !== TabIds.ALERTS}>
<AlertsTabContent />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { HostsState } from './use_unified_search_url_state';
import { useHostsViewContext } from './use_hosts_view';
import { AlertStatus } from '../types';
import { ALERT_STATUS_QUERY } from '../constants';
import { createHostsFilter } from '../utils';

export interface AlertsEsQuery {
bool: BoolQuery;
Expand Down Expand Up @@ -80,12 +81,3 @@ const createDateFilter = (date: HostsState['dateRange']) =>

const createAlertStatusFilter = (status: AlertStatus = 'all'): Filter | null =>
ALERT_STATUS_QUERY[status] ? { query: ALERT_STATUS_QUERY[status], meta: {} } : null;

const createHostsFilter = (hosts: SnapshotNode[]): Filter => ({
query: {
terms: {
'host.name': hosts.map((p) => p.name),
},
},
meta: {},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 * as rt from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { constant, identity } from 'fp-ts/lib/function';
import { useUrlState } from '../../../../utils/use_url_state';

const DEFAULT_QUERY = {
language: 'kuery',
query: '',
};

type LogsUrlStateUpdater = (newState: LogsUrlState) => void;
mohamedhamed-ahmed marked this conversation as resolved.
Show resolved Hide resolved

const LogsQueryStateRT = rt.type({
language: rt.string,
query: rt.any,
});

const encodeUrlState = LogsQueryStateRT.encode;
const decodeUrlState = (defaultValue: LogsUrlState) => (value: unknown) => {
return pipe(LogsQueryStateRT.decode(value), fold(constant(defaultValue), identity));
};

export type LogsUrlState = rt.TypeOf<typeof LogsQueryStateRT>;

export const useLogsSearchUrlState = (): [LogsUrlState, LogsUrlStateUpdater] => {
return useUrlState<LogsUrlState>({
defaultState: DEFAULT_QUERY,
decodeUrlState: decodeUrlState(DEFAULT_QUERY),
encodeUrlState,
urlStateKey: 'logsQuery',
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ export const useTabId = (initialValue: TabId = TabIds.METRICS): [TabId, TabIdUpd
});
};

const TabIdRT = rt.union([rt.literal('alerts'), rt.literal('metrics')]);
const TabIdRT = rt.union([rt.literal('alerts'), rt.literal('logs'), rt.literal('metrics')]);

export enum TabIds {
ALERTS = 'alerts',
LOGS = 'logs',
METRICS = 'metrics',
}

Expand Down
20 changes: 20 additions & 0 deletions x-pack/plugins/infra/public/pages/metrics/hosts/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { Filter } from '@kbn/es-query';
import { SnapshotNode } from '../../../../common/http_api';

export const createHostsFilter = (hostNodes: SnapshotNode[]): Filter => {
return {
query: {
terms: {
'host.name': hostNodes.map((p) => p.name),
},
},
meta: {},
};
};
Loading