mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
# Backport This will backport the following commits from `main` to `8.15`: - [[Fleet] Display view in logs button when logs app is available (#187871)](https://github.com/elastic/kibana/pull/187871) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Cristina Amico","email":"criamico@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-07-10T15:17:31Z","message":"[Fleet] Display view in logs button when logs app is available (#187871)\n\nCloses https://github.com/elastic/kibana/issues/185711\r\n\r\n## Summary\r\nThis change fixes https://github.com/elastic/kibana/issues/185711, but\r\nwhile working on that I also realised that we should move away from\r\nusing hardcoded urls. So this PR does two things:\r\n- Displays the button only when the user has `authz.fleet.readAgents`\r\nprivilege\r\n- Refactors the button functionality to use the new locators that take\r\ncare of linking to the observability logs/discover app\r\n\r\n### Why the refactor\r\nWhile testing this button, I noticed that the functionality was broken\r\nin some cases, that's because we were manually routing the urls to Logs\r\nUI/Discover apps based if we are in serverless or not.\r\n\r\nI found a PR that already implements this functionality:\r\nhttps://github.com/elastic/kibana/pull/155156\r\nI also found https://github.com/elastic/kibana/pull/154145 that takes\r\ncare of the redirect to the correct app.\r\nSo I'm replacing the current manual functionality with these utilities\r\nso that `getLogsLocatorsFromUrlService` takes care of where the open in\r\nlogs button should link.\r\n\r\n\r\n\r\n### ESS\r\n\r\n\r\n3436cf5a
-36c9-425d-a114-e116ddaa1a03\r\n\r\n### Serverless\r\n\r\n84176f09
-96a4-4932-9508-5f7682d03aae\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"696bb88d7c33eebfeabec6064ea8a97a2e2bb1bb","branchLabelMapping":{"^v8.16.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","v8.15.0","v8.16.0"],"title":"[Fleet] Display view in logs button when logs app is available","number":187871,"url":"https://github.com/elastic/kibana/pull/187871","mergeCommit":{"message":"[Fleet] Display view in logs button when logs app is available (#187871)\n\nCloses https://github.com/elastic/kibana/issues/185711\r\n\r\n## Summary\r\nThis change fixes https://github.com/elastic/kibana/issues/185711, but\r\nwhile working on that I also realised that we should move away from\r\nusing hardcoded urls. So this PR does two things:\r\n- Displays the button only when the user has `authz.fleet.readAgents`\r\nprivilege\r\n- Refactors the button functionality to use the new locators that take\r\ncare of linking to the observability logs/discover app\r\n\r\n### Why the refactor\r\nWhile testing this button, I noticed that the functionality was broken\r\nin some cases, that's because we were manually routing the urls to Logs\r\nUI/Discover apps based if we are in serverless or not.\r\n\r\nI found a PR that already implements this functionality:\r\nhttps://github.com/elastic/kibana/pull/155156\r\nI also found https://github.com/elastic/kibana/pull/154145 that takes\r\ncare of the redirect to the correct app.\r\nSo I'm replacing the current manual functionality with these utilities\r\nso that `getLogsLocatorsFromUrlService` takes care of where the open in\r\nlogs button should link.\r\n\r\n\r\n\r\n### ESS\r\n\r\n\r\n3436cf5a
-36c9-425d-a114-e116ddaa1a03\r\n\r\n### Serverless\r\n\r\n84176f09
-96a4-4932-9508-5f7682d03aae\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"696bb88d7c33eebfeabec6064ea8a97a2e2bb1bb"}},"sourceBranch":"main","suggestedTargetBranches":["8.15"],"targetPullRequestStates":[{"branch":"8.15","label":"v8.15.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/187871","number":187871,"mergeCommit":{"message":"[Fleet] Display view in logs button when logs app is available (#187871)\n\nCloses https://github.com/elastic/kibana/issues/185711\r\n\r\n## Summary\r\nThis change fixes https://github.com/elastic/kibana/issues/185711, but\r\nwhile working on that I also realised that we should move away from\r\nusing hardcoded urls. So this PR does two things:\r\n- Displays the button only when the user has `authz.fleet.readAgents`\r\nprivilege\r\n- Refactors the button functionality to use the new locators that take\r\ncare of linking to the observability logs/discover app\r\n\r\n### Why the refactor\r\nWhile testing this button, I noticed that the functionality was broken\r\nin some cases, that's because we were manually routing the urls to Logs\r\nUI/Discover apps based if we are in serverless or not.\r\n\r\nI found a PR that already implements this functionality:\r\nhttps://github.com/elastic/kibana/pull/155156\r\nI also found https://github.com/elastic/kibana/pull/154145 that takes\r\ncare of the redirect to the correct app.\r\nSo I'm replacing the current manual functionality with these utilities\r\nso that `getLogsLocatorsFromUrlService` takes care of where the open in\r\nlogs button should link.\r\n\r\n\r\n\r\n### ESS\r\n\r\n\r\n3436cf5a
-36c9-425d-a114-e116ddaa1a03\r\n\r\n### Serverless\r\n\r\n84176f09
-96a4-4932-9508-5f7682d03aae\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"696bb88d7c33eebfeabec6064ea8a97a2e2bb1bb"}}]}] BACKPORT--> Co-authored-by: Cristina Amico <criamico@users.noreply.github.com>
This commit is contained in:
parent
c30c0ea608
commit
494668ea1a
13 changed files with 166 additions and 113 deletions
|
@ -8,6 +8,7 @@
|
|||
export const LOCATORS_IDS = {
|
||||
APM_LOCATOR: 'APM_LOCATOR',
|
||||
DASHBOARD_APP: 'DASHBOARD_APP_LOCATOR',
|
||||
DISCOVER_APP_LOCATOR: 'DISCOVER_APP_LOCATOR',
|
||||
} as const;
|
||||
|
||||
// Dashboards ids
|
||||
|
|
|
@ -12,8 +12,6 @@ import { createFleetTestRendererMock } from '../../../../../../../mock';
|
|||
|
||||
import { AgentLogsUI } from './agent_logs';
|
||||
|
||||
jest.mock('../../../../../../../hooks/use_authz');
|
||||
|
||||
jest.mock('@kbn/kibana-utils-plugin/public', () => {
|
||||
return {
|
||||
...jest.requireActual('@kbn/kibana-utils-plugin/public'),
|
||||
|
@ -28,6 +26,13 @@ jest.mock('@kbn/logs-shared-plugin/public', () => {
|
|||
LogStream: () => <div />,
|
||||
};
|
||||
});
|
||||
jest.mock('@kbn/logs-shared-plugin/common', () => {
|
||||
return {
|
||||
getLogsLocatorsFromUrlService: jest.fn().mockReturnValue({
|
||||
logsLocator: { getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') },
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@kbn/shared-ux-link-redirect-app', () => {
|
||||
return {
|
||||
|
@ -52,6 +57,13 @@ jest.mock('../../../../../hooks', () => {
|
|||
...jest.requireActual('../../../../../hooks'),
|
||||
useLink: jest.fn(),
|
||||
useStartServices: jest.fn(),
|
||||
useAuthz: jest.fn(),
|
||||
useDiscoverLocator: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
id: 'DISCOVER_APP_LOCATOR',
|
||||
getRedirectUrl: jest.fn().mockResolvedValue('app/discover/logs/someview'),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -62,6 +74,7 @@ describe('AgentLogsUI', () => {
|
|||
jest.mocked(useAuthz).mockReturnValue({
|
||||
fleet: {
|
||||
allAgents: true,
|
||||
readAgents: true,
|
||||
},
|
||||
} as any);
|
||||
});
|
||||
|
@ -100,34 +113,36 @@ describe('AgentLogsUI', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: (url: string) => 'http://localhost:5620' + url,
|
||||
share: {
|
||||
url: {
|
||||
locators: {
|
||||
get: () => ({
|
||||
useUrl: () => 'https://locator.url',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
cloud: {
|
||||
isServerlessEnabled,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('should render Open in Logs UI if capabilities not set', () => {
|
||||
it('should render Open in Logs button if privileges are set', () => {
|
||||
mockStartServices();
|
||||
const result = renderComponent();
|
||||
expect(result.getByTestId('viewInLogsBtn')).toHaveAttribute(
|
||||
'href',
|
||||
`http://localhost:5620/app/logs/stream?logPosition=(end%3A'2023-20-04T14%3A20%3A00.340Z'%2Cstart%3A'2023-20-04T14%3A00%3A00.340Z'%2CstreamLive%3A!f)&logFilter=(expression%3A'elastic_agent.id%3Aagent1%20and%20(data_stream.dataset%3Aelastic_agent)%20and%20(log.level%3Ainfo%20or%20log.level%3Aerror)'%2Ckind%3Akuery)`
|
||||
`https://discover-redirect-url`
|
||||
);
|
||||
});
|
||||
|
||||
it('should render Open in Discover if serverless enabled', () => {
|
||||
mockStartServices(true);
|
||||
it('should not render Open in Logs button if privileges are not set', () => {
|
||||
jest.mocked(useAuthz).mockReturnValue({
|
||||
fleet: {
|
||||
readAgents: false,
|
||||
},
|
||||
} as any);
|
||||
mockStartServices();
|
||||
const result = renderComponent();
|
||||
const viewInDiscover = result.getByTestId('viewInDiscoverBtn');
|
||||
expect(viewInDiscover).toHaveAttribute(
|
||||
'href',
|
||||
`http://localhost:5620/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:'2023-20-04T14:00:00.340Z',to:'2023-20-04T14:20:00.340Z'))&_a=(columns:!(event.dataset,message),index:'logs-*',query:(language:kuery,query:'elastic_agent.id:agent1 and (data_stream.dataset:elastic_agent) and (log.level:info or log.level:error)'))`
|
||||
);
|
||||
expect(result.queryByTestId('viewInLogsBtn')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show log level dropdown with correct value', () => {
|
||||
|
|
|
@ -35,7 +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';
|
||||
import { ViewLogsButton, getFormattedRange } from './view_logs_button';
|
||||
|
||||
const WrapperFlexGroup = styled(EuiFlexGroup)`
|
||||
height: 100%;
|
||||
|
@ -112,9 +112,8 @@ const AgentPolicyLogsNotEnabledCallout: React.FunctionComponent<{ agentPolicy: A
|
|||
|
||||
export const AgentLogsUI: React.FunctionComponent<AgentLogsProps> = memo(
|
||||
({ agent, agentPolicy, state }) => {
|
||||
const { data, application, cloud } = useStartServices();
|
||||
const { data, application } = useStartServices();
|
||||
const { update: updateState } = AgentLogsUrlStateHelper.useTransitions();
|
||||
const isLogsUIAvailable = !cloud?.isServerlessEnabled;
|
||||
|
||||
// Util to convert date expressions (returned by datepicker) to timestamps (used by LogStream)
|
||||
const getDateRangeTimestamps = useCallback(
|
||||
|
@ -321,10 +320,9 @@ export const AgentLogsUI: React.FunctionComponent<AgentLogsProps> = memo(
|
|||
}}
|
||||
>
|
||||
<ViewLogsButton
|
||||
viewInLogs={isLogsUIAvailable}
|
||||
logStreamQuery={logStreamQuery}
|
||||
startTime={state.start}
|
||||
endTime={state.end}
|
||||
startTime={getFormattedRange(state.start)}
|
||||
endTime={getFormattedRange(state.end)}
|
||||
/>
|
||||
</RedirectAppLinks>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -5,81 +5,61 @@
|
|||
* 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';
|
||||
import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common';
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import { useDiscoverLocator, useStartServices, useAuthz } from '../../../../../hooks';
|
||||
|
||||
interface ViewLogsProps {
|
||||
viewInLogs: boolean;
|
||||
logStreamQuery: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
}
|
||||
|
||||
export const getFormattedRange = (date: string) => new Date(date).getTime();
|
||||
|
||||
/*
|
||||
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
|
||||
Button that takes to the Logs view UI or the Discover logs, depending on what's available
|
||||
If none is available, don't display the button at all
|
||||
*/
|
||||
export const ViewLogsButton: React.FunctionComponent<ViewLogsProps> = ({
|
||||
viewInLogs,
|
||||
logStreamQuery,
|
||||
startTime,
|
||||
endTime,
|
||||
}) => {
|
||||
const { http } = useStartServices();
|
||||
const discoverLocator = useDiscoverLocator();
|
||||
|
||||
// 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 { share } = useStartServices();
|
||||
const { logsLocator } = getLogsLocatorsFromUrlService(share.url);
|
||||
const authz = useAuthz();
|
||||
|
||||
const viewInDiscoverUrl = useMemo(() => {
|
||||
const index = 'logs-*';
|
||||
const query = encode({
|
||||
query: logStreamQuery,
|
||||
language: 'kuery',
|
||||
const logsUrl = useMemo(() => {
|
||||
const now = moment().toISOString();
|
||||
const oneDayAgo = moment().subtract(1, 'day').toISOString();
|
||||
const defaultStartTime = getFormattedRange(oneDayAgo);
|
||||
const defaultEndTime = getFormattedRange(now);
|
||||
|
||||
return logsLocator.getRedirectUrl({
|
||||
time: endTime ? endTime : defaultEndTime,
|
||||
timeRange: {
|
||||
startTime: startTime ? startTime : defaultStartTime,
|
||||
endTime: endTime ? endTime : defaultEndTime,
|
||||
},
|
||||
filter: logStreamQuery,
|
||||
});
|
||||
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]);
|
||||
}, [endTime, logStreamQuery, logsLocator, startTime]);
|
||||
|
||||
return viewInLogs ? (
|
||||
<EuiButton href={viewInLogsUrl} iconType="popout" data-test-subj="viewInLogsBtn">
|
||||
return authz.fleet.readAgents && (logsLocator || discoverLocator) ? (
|
||||
<EuiButton href={logsUrl} 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>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import { act, render, fireEvent } from '@testing-library/react';
|
|||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { useActionStatus } from '../../hooks';
|
||||
import { useGetAgentPolicies, useStartServices } from '../../../../../hooks';
|
||||
import { useGetAgentPolicies, useStartServices, useAuthz } from '../../../../../hooks';
|
||||
|
||||
import { AgentActivityFlyout } from '.';
|
||||
|
||||
|
@ -25,6 +25,15 @@ jest.mock('@kbn/shared-ux-link-redirect-app', () => ({
|
|||
const mockUseActionStatus = useActionStatus as jest.Mock;
|
||||
const mockUseGetAgentPolicies = useGetAgentPolicies as jest.Mock;
|
||||
const mockUseStartServices = useStartServices as jest.Mock;
|
||||
const mockedUseAuthz = useAuthz as jest.Mock;
|
||||
|
||||
jest.mock('@kbn/logs-shared-plugin/common', () => {
|
||||
return {
|
||||
getLogsLocatorsFromUrlService: jest.fn().mockReturnValue({
|
||||
logsLocator: { getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') },
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('AgentActivityFlyout', () => {
|
||||
const mockOnClose = jest.fn();
|
||||
|
@ -65,7 +74,22 @@ describe('AgentActivityFlyout', () => {
|
|||
docLinks: { links: { fleet: { upgradeElasticAgent: 'https://elastic.co' } } },
|
||||
application: { navigateToUrl: jest.fn() },
|
||||
http: { basePath: { prepend: jest.fn() } },
|
||||
share: {
|
||||
url: {
|
||||
locators: {
|
||||
get: () => ({
|
||||
useUrl: () => 'https://locator.url',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockedUseAuthz.mockReturnValue({
|
||||
fleet: {
|
||||
readAgents: true,
|
||||
allAgents: true,
|
||||
},
|
||||
} as any);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { I18nProvider } from '@kbn/i18n-react';
|
|||
|
||||
import type { ActionStatus } from '../../../../../../../common/types';
|
||||
|
||||
import { useStartServices } from '../../../../hooks';
|
||||
import { useStartServices, useAuthz } from '../../../../hooks';
|
||||
|
||||
import { ViewErrors } from './view_errors';
|
||||
|
||||
|
@ -21,6 +21,13 @@ jest.mock('../../../../hooks', () => {
|
|||
...jest.requireActual('../../../../hooks'),
|
||||
useLink: jest.fn(),
|
||||
useStartServices: jest.fn(),
|
||||
useAuthz: jest.fn(),
|
||||
useDiscoverLocator: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
id: 'DISCOVER_APP_LOCATOR',
|
||||
getRedirectUrl: jest.fn().mockResolvedValue('app/discover/logs/someview'),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -32,6 +39,14 @@ jest.mock('@kbn/shared-ux-link-redirect-app', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/logs-shared-plugin/common', () => {
|
||||
return {
|
||||
getLogsLocatorsFromUrlService: jest.fn().mockReturnValue({
|
||||
logsLocator: { getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') },
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const mockStartServices = (isServerlessEnabled?: boolean) => {
|
||||
mockUseStartServices.mockReturnValue({
|
||||
application: {},
|
||||
|
@ -47,18 +62,27 @@ const mockStartServices = (isServerlessEnabled?: boolean) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: (url: string) => 'http://localhost:5620' + url,
|
||||
share: {
|
||||
url: {
|
||||
locators: {
|
||||
get: () => ({
|
||||
useUrl: () => 'https://locator.url',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
cloud: {
|
||||
isServerlessEnabled,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('ViewErrors', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(useAuthz).mockReturnValue({
|
||||
fleet: {
|
||||
allAgents: true,
|
||||
readAgents: true,
|
||||
},
|
||||
} as any);
|
||||
});
|
||||
const renderComponent = (action: ActionStatus) => {
|
||||
return render(
|
||||
<I18nProvider>
|
||||
|
@ -67,7 +91,7 @@ describe('ViewErrors', () => {
|
|||
);
|
||||
};
|
||||
|
||||
it('should render error message with btn to Logs view if serverless not enabled', () => {
|
||||
it('should render error message with btn to Logs view', () => {
|
||||
mockStartServices();
|
||||
const result = renderComponent({
|
||||
actionId: 'action1',
|
||||
|
@ -82,15 +106,10 @@ describe('ViewErrors', () => {
|
|||
|
||||
const errorText = result.getByTestId('errorText');
|
||||
expect(errorText.textContent).toEqual('Agent agent1 is not upgradeable');
|
||||
|
||||
const viewErrorBtn = result.getByTestId('viewInLogsBtn');
|
||||
expect(viewErrorBtn.getAttribute('href')).toEqual(
|
||||
`http://localhost:5620/app/logs/stream?logPosition=(end%3A'2023-03-06T14%3A56%3A24.709Z'%2Cstart%3A'2023-03-06T14%3A46%3A24.709Z'%2CstreamLive%3A!f)&logFilter=(expression%3A'elastic_agent.id%3Aagent1%20and%20(data_stream.dataset%3Aelastic_agent)%20and%20(log.level%3Aerror)'%2Ckind%3Akuery)`
|
||||
);
|
||||
});
|
||||
|
||||
it('should render error message with btn to Discover view if serverless enabled', () => {
|
||||
mockStartServices(true);
|
||||
it('should render open in Logs button if correct privileges are set', () => {
|
||||
mockStartServices();
|
||||
const result = renderComponent({
|
||||
actionId: 'action1',
|
||||
latestErrors: [
|
||||
|
@ -102,12 +121,28 @@ describe('ViewErrors', () => {
|
|||
],
|
||||
} as any);
|
||||
|
||||
const errorText = result.getByTestId('errorText');
|
||||
expect(errorText.textContent).toEqual('Agent agent1 is not upgradeable');
|
||||
const viewErrorBtn = result.getByTestId('viewInLogsBtn');
|
||||
expect(viewErrorBtn.getAttribute('href')).toEqual(`https://discover-redirect-url`);
|
||||
});
|
||||
|
||||
const viewErrorBtn = result.getByTestId('viewInDiscoverBtn');
|
||||
expect(viewErrorBtn.getAttribute('href')).toEqual(
|
||||
`http://localhost:5620/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:'2023-03-06T14:46:24.709Z',to:'2023-03-06T14:56:24.709Z'))&_a=(columns:!(event.dataset,message),index:'logs-*',query:(language:kuery,query:'elastic_agent.id:agent1 and (data_stream.dataset:elastic_agent) and (log.level:error)'))`
|
||||
);
|
||||
it('should not render open in Logs button if privileges are not set', () => {
|
||||
jest.mocked(useAuthz).mockReturnValue({
|
||||
fleet: {
|
||||
readAgents: false,
|
||||
},
|
||||
} as any);
|
||||
mockStartServices();
|
||||
const result = renderComponent({
|
||||
actionId: 'action1',
|
||||
latestErrors: [
|
||||
{
|
||||
agentId: 'agent1',
|
||||
error: 'Agent agent1 is not upgradeable',
|
||||
timestamp: '2023-03-06T14:51:24.709Z',
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
expect(result.queryByTestId('viewInLogsBtn')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,10 @@ 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 {
|
||||
ViewLogsButton,
|
||||
getFormattedRange,
|
||||
} from '../../agent_details_page/components/agent_logs/view_logs_button';
|
||||
|
||||
import type { ActionStatus } from '../../../../types';
|
||||
import { useStartServices } from '../../../../hooks';
|
||||
|
@ -30,11 +33,12 @@ const TruncatedEuiText = styled(EuiText)`
|
|||
|
||||
export const ViewErrors: React.FunctionComponent<{ action: ActionStatus }> = ({ action }) => {
|
||||
const coreStart = useStartServices();
|
||||
const isLogsUIAvailable = !coreStart.cloud?.isServerlessEnabled;
|
||||
|
||||
const getLogsButton = (agentId: string, timestamp: string, viewInLogs: boolean) => {
|
||||
const startTime = moment(timestamp).subtract(5, 'm').toISOString();
|
||||
const endTime = moment(timestamp).add(5, 'm').toISOString();
|
||||
const getLogsButton = (agentId: string, timestamp: string) => {
|
||||
const start = moment(timestamp).subtract(5, 'm').toISOString();
|
||||
const end = moment(timestamp).add(5, 'm').toISOString();
|
||||
const startTime = getFormattedRange(start);
|
||||
const endTime = getFormattedRange(end);
|
||||
|
||||
const logStreamQuery = buildQuery({
|
||||
agentId,
|
||||
|
@ -43,12 +47,7 @@ export const ViewErrors: React.FunctionComponent<{ action: ActionStatus }> = ({
|
|||
userQuery: '',
|
||||
});
|
||||
return (
|
||||
<ViewLogsButton
|
||||
viewInLogs={viewInLogs}
|
||||
logStreamQuery={logStreamQuery}
|
||||
startTime={startTime}
|
||||
endTime={endTime}
|
||||
/>
|
||||
<ViewLogsButton logStreamQuery={logStreamQuery} startTime={startTime} endTime={endTime} />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -86,7 +85,7 @@ export const ViewErrors: React.FunctionComponent<{ action: ActionStatus }> = ({
|
|||
const errorItem = (action.latestErrors ?? []).find((item) => item.agentId === agentId);
|
||||
return (
|
||||
<RedirectAppLinks coreStart={coreStart}>
|
||||
{getLogsButton(agentId, errorItem!.timestamp, !!isLogsUIAvailable)}
|
||||
{getLogsButton(agentId, errorItem!.timestamp)}
|
||||
</RedirectAppLinks>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -17,6 +17,7 @@ export const CustomLogsAssetsExtension: PackageAssetsComponent = () => {
|
|||
const { http, cloud } = useStartServices();
|
||||
const isLogsUIAvailable = !cloud?.isServerlessEnabled;
|
||||
// if logs ui is not available, link to discover
|
||||
// TODO: move away from hardcoded link and use locators instead
|
||||
const logStreamUrl = isLogsUIAvailable
|
||||
? http.basePath.prepend('/app/logs/stream')
|
||||
: http.basePath.prepend('/app/discover');
|
||||
|
|
|
@ -21,3 +21,7 @@ export function useLocator<T extends SerializableRecord>(
|
|||
export function useDashboardLocator() {
|
||||
return useLocator(LOCATORS_IDS.DASHBOARD_APP);
|
||||
}
|
||||
|
||||
export function useDiscoverLocator() {
|
||||
return useLocator(LOCATORS_IDS.DISCOVER_APP_LOCATOR);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,6 @@
|
|||
"@kbn/utility-types-jest",
|
||||
"@kbn/es-query",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/rison",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/telemetry-plugin",
|
||||
"@kbn/task-manager-plugin",
|
||||
|
|
|
@ -17675,7 +17675,6 @@
|
|||
"xpack.fleet.agentLogs.downloadLink": "télécharger",
|
||||
"xpack.fleet.agentLogs.logDisabledCallOutTitle": "La collecte de logs est désactivée",
|
||||
"xpack.fleet.agentLogs.logLevelSelectText": "Niveau du log",
|
||||
"xpack.fleet.agentLogs.openInDiscoverUiLinkText": "Ouvrir dans Discover",
|
||||
"xpack.fleet.agentLogs.openInLogsUiLinkText": "Ouvrir dans Logs",
|
||||
"xpack.fleet.agentLogs.searchPlaceholderText": "Rechercher dans les logs…",
|
||||
"xpack.fleet.agentLogs.selectLogLevel.errorTitleText": "Erreur lors de la mise à jour du niveau de logging de l'agent",
|
||||
|
|
|
@ -17653,7 +17653,6 @@
|
|||
"xpack.fleet.agentLogs.downloadLink": "ダウンロード",
|
||||
"xpack.fleet.agentLogs.logDisabledCallOutTitle": "ログ収集は無効です",
|
||||
"xpack.fleet.agentLogs.logLevelSelectText": "ログレベル",
|
||||
"xpack.fleet.agentLogs.openInDiscoverUiLinkText": "Discoverで開く",
|
||||
"xpack.fleet.agentLogs.openInLogsUiLinkText": "ログで開く",
|
||||
"xpack.fleet.agentLogs.searchPlaceholderText": "ログを検索…",
|
||||
"xpack.fleet.agentLogs.selectLogLevel.errorTitleText": "エージェントログレベルの更新エラー",
|
||||
|
|
|
@ -17682,7 +17682,6 @@
|
|||
"xpack.fleet.agentLogs.downloadLink": "下载",
|
||||
"xpack.fleet.agentLogs.logDisabledCallOutTitle": "日志收集已禁用",
|
||||
"xpack.fleet.agentLogs.logLevelSelectText": "日志级别",
|
||||
"xpack.fleet.agentLogs.openInDiscoverUiLinkText": "在 Discover 中打开",
|
||||
"xpack.fleet.agentLogs.openInLogsUiLinkText": "在日志中打开",
|
||||
"xpack.fleet.agentLogs.searchPlaceholderText": "搜索日志……",
|
||||
"xpack.fleet.agentLogs.selectLogLevel.errorTitleText": "更新代理日志记录级别时出错",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue