Skip to content

Commit

Permalink
Extract button on a separate component
Browse files Browse the repository at this point in the history
  • Loading branch information
criamico committed Nov 20, 2023
1 parent 922a592 commit e145ebb
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import url from 'url';
import { stringify } from 'querystring';

import React, { memo, useMemo, useState, useCallback, useEffect } from 'react';
import styled from 'styled-components';
import { encode } from '@kbn/rison';
import {
EuiFlexGroup,
EuiFlexItem,
EuiSuperDatePicker,
EuiFilterGroup,
EuiPanel,
EuiButton,
EuiButtonEmpty,
EuiCallOut,
EuiLink,
} from '@elastic/eui';
Expand All @@ -42,6 +35,7 @@ import { LogLevelFilter } from './filter_log_level';
import { LogQueryBar } from './query_bar';
import { buildQuery } from './build_query';
import { SelectLogLevel } from './select_log_level';
import { ViewLogsButton } from './view_logs_button';

const WrapperFlexGroup = styled(EuiFlexGroup)`
height: 100%;
Expand Down Expand Up @@ -118,7 +112,7 @@ const AgentPolicyLogsNotEnabledCallout: React.FunctionComponent<{ agentPolicy: A

export const AgentLogsUI: React.FunctionComponent<AgentLogsProps> = memo(
({ agent, agentPolicy, state }) => {
const { data, application, http, cloud } = useStartServices();
const { data, application, cloud } = useStartServices();
const { update: updateState } = AgentLogsUrlStateHelper.useTransitions();
const isLogsUIAvailable = !cloud?.isServerlessEnabled;

Expand Down Expand Up @@ -218,39 +212,6 @@ export const AgentLogsUI: React.FunctionComponent<AgentLogsProps> = memo(
[agent.id, state.datasets, state.logLevels, state.query]
);

// Generate URL to pass page state to Logs UI
const viewInLogsUrl = useMemo(
() =>
http.basePath.prepend(
url.format({
pathname: '/app/logs/stream',
search: stringify({
logPosition: encode({
start: state.start,
end: state.end,
streamLive: false,
}),
logFilter: encode({
expression: logStreamQuery,
kind: 'kuery',
}),
}),
})
),
[http.basePath, state.start, state.end, logStreamQuery]
);

const viewInDiscoverUrl = useMemo(() => {
const index = 'logs-*';
const query = encode({
query: logStreamQuery,
language: 'kuery',
});
return http.basePath.prepend(
`/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:'${state.start}',to:'${state.end}'))&_a=(columns:!(event.dataset,message),index:'${index}',query:${query})`
);
}, [logStreamQuery, http.basePath, state.start, state.end]);

const agentVersion = agent.local_metadata?.elastic?.agent?.version;
const isLogFeatureAvailable = useMemo(() => {
if (!agentVersion) {
Expand Down Expand Up @@ -359,30 +320,12 @@ export const AgentLogsUI: React.FunctionComponent<AgentLogsProps> = memo(
application,
}}
>
{isLogsUIAvailable ? (
<EuiButtonEmpty
href={viewInLogsUrl}
iconType="popout"
flush="both"
data-test-subj="viewInLogsBtn"
>
<FormattedMessage
id="xpack.fleet.agentLogs.openInLogsUiLinkText"
defaultMessage="Open in Logs"
/>
</EuiButtonEmpty>
) : (
<EuiButton
href={viewInDiscoverUrl}
iconType="popout"
data-test-subj="viewInDiscoverBtn"
>
<FormattedMessage
id="xpack.fleet.agentLogs.openInDiscoverUiLinkText"
defaultMessage="Open in Discover"
/>
</EuiButton>
)}
<ViewLogsButton
viewInLogs={isLogsUIAvailable}
logStreamQuery={logStreamQuery}
startTime={state.start}
endTime={state.end}
/>
</RedirectAppLinks>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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 url from 'url';
import { stringify } from 'querystring';

import React, { useMemo } from 'react';
import { encode } from '@kbn/rison';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';

import { useStartServices } from '../../../../../hooks';

interface ViewLogsProps {
viewInLogs: boolean;
logStreamQuery: string;
startTime: string;
endTime: string;
}

/*
Button that takes to the Logs view Ui when that is available, otherwise fallback to the Discover UI
The urls are built using same logStreamQuery (provided by a prop), startTime and endTime, ensuring that they'll both will target same log lines
*/
export const ViewLogsButton: React.FunctionComponent<ViewLogsProps> = ({
viewInLogs,
logStreamQuery,
startTime,
endTime,
}) => {
const { http } = useStartServices();

// Generate URL to pass page state to Logs UI
const viewInLogsUrl = useMemo(
() =>
http.basePath.prepend(
url.format({
pathname: '/app/logs/stream',
search: stringify({
logPosition: encode({
start: startTime,
end: endTime,
streamLive: false,
}),
logFilter: encode({
expression: logStreamQuery,
kind: 'kuery',
}),
}),
})
),
[http.basePath, startTime, endTime, logStreamQuery]
);

const viewInDiscoverUrl = useMemo(() => {
const index = 'logs-*';
const query = encode({
query: logStreamQuery,
language: 'kuery',
});
return http.basePath.prepend(
`/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:'${startTime}',to:'${endTime}'))&_a=(columns:!(event.dataset,message),index:'${index}',query:${query})`
);
}, [logStreamQuery, http.basePath, startTime, endTime]);

return (
<>
{viewInLogs ? (
<EuiButton href={viewInLogsUrl} iconType="popout" data-test-subj="viewInLogsBtn">
<FormattedMessage
id="xpack.fleet.agentLogs.openInLogsUiLinkText"
defaultMessage="Open in Logs"
/>
</EuiButton>
) : (
<EuiButton href={viewInDiscoverUrl} iconType="popout" data-test-subj="viewInDiscoverBtn">
<FormattedMessage
id="xpack.fleet.agentLogs.openInDiscoverUiLinkText"
defaultMessage="Open in Discover"
/>
</EuiButton>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@

import styled from 'styled-components';
import React from 'react';
import { encode } from '@kbn/rison';
import type { EuiBasicTableProps } from '@elastic/eui';
import { EuiButton, EuiAccordion, EuiToolTip, EuiText, EuiBasicTable } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiAccordion, EuiToolTip, EuiText, EuiBasicTable } from '@elastic/eui';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';

import { i18n } from '@kbn/i18n';

import type { ActionErrorResult } from '../../../../../../../common/types';

import { buildQuery } from '../../agent_details_page/components/agent_logs/build_query';
import { ViewLogsButton } from '../../agent_details_page/components/agent_logs/view_logs_button';

import type { ActionStatus } from '../../../../types';
import { useStartServices } from '../../../../hooks';
Expand All @@ -30,6 +29,7 @@ const TruncatedEuiText = styled(EuiText)`

export const ViewErrors: React.FunctionComponent<{ action: ActionStatus }> = ({ action }) => {
const coreStart = useStartServices();
const isLogsUIAvailable = !coreStart.cloud?.isServerlessEnabled;

const addOrSubtractMinutes = (timestamp: string, interval: number, subtract?: boolean) => {
const date = new Date(timestamp);
Expand All @@ -41,26 +41,26 @@ export const ViewErrors: React.FunctionComponent<{ action: ActionStatus }> = ({
return date.toISOString();
};

const viewInDiscoverUrl = (agentId: string, timestamp: string) => {
const index = 'logs-*';

const getLogsButton = (agentId: string, timestamp: string, viewInLogs: boolean) => {
const startTime = addOrSubtractMinutes(timestamp, 5, true);
const endTime = addOrSubtractMinutes(timestamp, 5);

const query = encode({
query: buildQuery({
agentId,
datasets: ['elastic_agent'],
logLevels: ['error'],
userQuery: '',
}),
language: 'kuery',
const logStreamQuery = buildQuery({
agentId,
datasets: ['elastic_agent'],
logLevels: ['error'],
userQuery: '',
});

return coreStart.http.basePath.prepend(
`/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:'${startTime}',to:'${endTime}'))&_a=(columns:!(message,error.message,log.level),index:'${index}',query:${query})`
return (
<ViewLogsButton
viewInLogs={viewInLogs}
logStreamQuery={logStreamQuery}
startTime={startTime}
endTime={endTime}
/>
);
};

const columns: EuiBasicTableProps<ActionErrorResult>['columns'] = [
{
field: 'hostname',
Expand Down Expand Up @@ -95,16 +95,7 @@ export const ViewErrors: React.FunctionComponent<{ action: ActionStatus }> = ({
const errorItem = (action.latestErrors ?? []).find((item) => item.agentId === agentId);
return (
<RedirectAppLinks coreStart={coreStart}>
<EuiButton
href={viewInDiscoverUrl(agentId, errorItem!.timestamp)}
color="danger"
data-test-subj="viewInDiscoverBtn"
>
<FormattedMessage
id="xpack.fleet.agentActivityFlyout.reviewErrorLogs"
defaultMessage="Review error logs"
/>
</EuiButton>
{getLogsButton(agentId, errorItem!.timestamp, !!isLogsUIAvailable)}
</RedirectAppLinks>
);
},
Expand Down
8 changes: 6 additions & 2 deletions x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ import { useStartServices } from './hooks';
import type { PackageAssetsComponent } from './types';

export const CustomLogsAssetsExtension: PackageAssetsComponent = () => {
const { http } = useStartServices();
const logStreamUrl = http.basePath.prepend('/app/logs/stream');
const { http, cloud } = useStartServices();
const isLogsUIAvailable = !cloud?.isServerlessEnabled;
// if logs ui is not available, link to discover
const logStreamUrl = isLogsUIAvailable
? http.basePath.prepend('/app/logs/stream')
: http.basePath.prepend('/app/discover');

const views: CustomAssetsAccordionProps['views'] = [
{
Expand Down

0 comments on commit e145ebb

Please sign in to comment.