[8.15] [Fleet] Display view in logs button when logs app is available (#187871) (#188000)

# 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:
Kibana Machine 2024-07-10 19:24:56 +02:00 committed by GitHub
parent c30c0ea608
commit 494668ea1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 166 additions and 113 deletions

View file

@ -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

View file

@ -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', () => {

View file

@ -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>

View file

@ -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;
};

View file

@ -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(() => {

View file

@ -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();
});
});

View file

@ -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>
);
},

View file

@ -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');

View file

@ -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);
}

View file

@ -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",

View file

@ -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",

View file

@ -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": "エージェントログレベルの更新エラー",

View file

@ -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": "更新代理日志记录级别时出错",