mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Fleet] Surface new overview dashboards in fleet (#154914)
Closes https://github.com/elastic/kibana/issues/153848
## Summary
- Adds two link buttons on top of agent list page to access "Ingest
overview" and "Agent Info" dashboards
<img width="1444" alt="Screenshot 2023-04-13 at 15 22 53"
src="https://user-images.githubusercontent.com/16084106/231772174-00c00a8e-62f1-43ea-a935-bc12f56f3e50.png">
The links are built using the new URL service
[locator](e80abe8108/x-pack/plugins/fleet/public/hooks/use_locator.ts (L14)
)
and the
[getRedirectLink](https://github.com/elastic/kibana/blob/main/src/plugins/share/README.mdx#using-locator-of-another-app)
method;
- Refactoring existing instances of `useKibanaLink` to use the url
locator instead;
These new dashboards were already accessible from the ` elastic_agent.*`
datastreams table actions, however I replaced the `useKibanaLink` hook
there as well:
<img width="1412" alt="Screenshot 2023-04-13 at 16 03 47"
src="https://user-images.githubusercontent.com/16084106/231784273-693c7f36-4545-4c06-a05e-4f09e53bd903.png">
TODO: I don't know where to add the "Integrations" dashboard yet, I'm
not sure it should go on the Integrations details page.
### Checklist
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f892faceb4
commit
970de9147e
14 changed files with 140 additions and 36 deletions
|
@ -22,9 +22,6 @@ export const FLEET_CLOUD_SECURITY_POSTURE_KSPM_POLICY_TEMPLATE = 'kspm';
|
|||
export const PACKAGE_TEMPLATE_SUFFIX = '@package';
|
||||
export const USER_SETTINGS_TEMPLATE_SUFFIX = '@custom';
|
||||
|
||||
export const FLEET_ELASTIC_AGENT_DETAILS_DASHBOARD_ID =
|
||||
'elastic_agent-f47f18cc-9c7d-4278-b2ea-a6dee816d395';
|
||||
|
||||
export const DATASET_VAR_NAME = 'data_stream.dataset';
|
||||
/*
|
||||
Package rules:
|
||||
|
|
|
@ -20,6 +20,7 @@ export * from './fleet_server_policy_config';
|
|||
export * from './authz';
|
||||
export * from './file_storage';
|
||||
export * from './message_signing_keys';
|
||||
export * from './locators';
|
||||
|
||||
// TODO: This is the default `index.max_result_window` ES setting, which dictates
|
||||
// the maximum amount of results allowed to be returned from a search. It's possible
|
||||
|
|
19
x-pack/plugins/fleet/common/constants/locators.ts
Normal file
19
x-pack/plugins/fleet/common/constants/locators.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 const LOCATORS_IDS = {
|
||||
APM_LOCATOR: 'APM_LOCATOR',
|
||||
DASHBOARD_APP: 'DASHBOARD_APP_LOCATOR',
|
||||
} as const;
|
||||
|
||||
// Dashboards ids
|
||||
export const DASHBOARD_LOCATORS_IDS = {
|
||||
ELASTIC_AGENT_OVERVIEW: 'elastic_agent-a148dc70-6b3c-11ed-98de-67bdecd21824',
|
||||
ELASTIC_AGENT_AGENT_INFO: 'elastic_agent-0600ffa0-6b5e-11ed-98de-67bdecd21824',
|
||||
ELASTIC_AGENT_AGENT_METRICS: 'elastic_agent-f47f18cc-9c7d-4278-b2ea-a6dee816d395',
|
||||
ELASTIC_AGENT_INTEGRATIONS: 'elastic_agent-1a4e7280-6b5e-11ed-98de-67bdecd21824',
|
||||
} as const;
|
|
@ -52,6 +52,8 @@ export {
|
|||
// Statuses
|
||||
// Authz
|
||||
ENDPOINT_PRIVILEGES,
|
||||
// dashboards ids
|
||||
DASHBOARD_LOCATORS_IDS,
|
||||
} from './constants';
|
||||
export {
|
||||
// Route services
|
||||
|
|
|
@ -25,6 +25,17 @@ jest.mock('../../../../../../hooks/use_fleet_status', () => ({
|
|||
|
||||
jest.mock('../../../../../../hooks/use_request/epm');
|
||||
|
||||
jest.mock('../../../../../../hooks/use_locator', () => {
|
||||
return {
|
||||
useDashboardLocator: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
id: 'DASHBOARD_APP_LOCATOR',
|
||||
getRedirectUrl: jest.fn().mockResolvedValue('app/dashboards#/view/elastic_agent-a0001'),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('AgentDashboardLink', () => {
|
||||
it('should enable the button if elastic_agent package is installed and policy has monitoring enabled', async () => {
|
||||
mockedUseGetPackageInfoByKeyQuery.mockReturnValue({
|
||||
|
|
|
@ -10,21 +10,26 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { EuiButton, EuiToolTip } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useGetPackageInfoByKeyQuery, useKibanaLink, useLink } from '../../../../hooks';
|
||||
import { useGetPackageInfoByKeyQuery, useLink, useDashboardLocator } from '../../../../hooks';
|
||||
import type { Agent, AgentPolicy } from '../../../../types';
|
||||
import {
|
||||
FLEET_ELASTIC_AGENT_PACKAGE,
|
||||
FLEET_ELASTIC_AGENT_DETAILS_DASHBOARD_ID,
|
||||
DASHBOARD_LOCATORS_IDS,
|
||||
} from '../../../../../../../common/constants';
|
||||
|
||||
function useAgentDashboardLink(agent: Agent) {
|
||||
const { isLoading, data } = useGetPackageInfoByKeyQuery(FLEET_ELASTIC_AGENT_PACKAGE);
|
||||
|
||||
const isInstalled = data?.item.status === 'installed';
|
||||
const dashboardLocator = useDashboardLocator();
|
||||
|
||||
const dashboardLink = useKibanaLink(`/dashboard/${FLEET_ELASTIC_AGENT_DETAILS_DASHBOARD_ID}`);
|
||||
const query = `_a=(query:(language:kuery,query:'elastic_agent.id:${agent.id}'))`;
|
||||
const link = `${dashboardLink}?${query}`;
|
||||
const link = dashboardLocator?.getRedirectUrl({
|
||||
dashboardId: DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_METRICS,
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: `elastic_agent.id:${agent.id}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
@ -50,7 +55,12 @@ export const AgentDashboardLink: React.FunctionComponent<{
|
|||
!isInstalled || isLoading || !isLogAndMetricsEnabled ? { disabled: true } : { href: link };
|
||||
|
||||
const button = (
|
||||
<EuiButtonCompressed {...buttonArgs} isLoading={isLoading} color="primary">
|
||||
<EuiButtonCompressed
|
||||
{...buttonArgs}
|
||||
isLoading={isLoading}
|
||||
color="primary"
|
||||
iconType="dashboardApp"
|
||||
>
|
||||
<FormattedMessage
|
||||
data-test-subj="agentDetails.viewMoreMetricsButton"
|
||||
id="xpack.fleet.agentDetails.viewDashboardButtonLabel"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { DASHBOARD_LOCATORS_IDS } from '../../../../../../../common/constants';
|
||||
|
||||
import { useDashboardLocator } from '../../../../hooks';
|
||||
|
||||
export const DashboardsButtons: React.FunctionComponent = () => {
|
||||
const dashboardLocator = useDashboardLocator();
|
||||
|
||||
const getDashboardHref = (dashboardId: string) => {
|
||||
return dashboardLocator?.getRedirectUrl({ dashboardId }) || '';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="dashboardApp"
|
||||
href={getDashboardHref(DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_OVERVIEW)}
|
||||
data-test-subj="ingestOverviewLinkButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentList.ingestOverviewlinkButton"
|
||||
defaultMessage="Ingest Overview Metrics"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="dashboardApp"
|
||||
href={getDashboardHref(DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_INFO)}
|
||||
data-test-subj="agentInfoLinkButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentList.agentInfoLinkButton"
|
||||
defaultMessage="Agent Info Metrics"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -36,6 +36,17 @@ jest.mock('../../../../components', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../../../hooks/use_locator', () => {
|
||||
return {
|
||||
useDashboardLocator: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
id: 'DASHBOARD_APP_LOCATOR',
|
||||
getRedirectUrl: jest.fn().mockResolvedValue('app/dashboards#/view/elastic_agent-a0002'),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const TestComponent = (props: any) => (
|
||||
<KibanaContextProvider services={coreMock.createStart()}>
|
||||
<ConfigContext.Provider value={{ agents: { enabled: true, elasticsearch: {} }, enabled: true }}>
|
||||
|
|
|
@ -31,15 +31,12 @@ import { AgentBulkActions } from './bulk_actions';
|
|||
import type { SelectionMode } from './types';
|
||||
import { AgentActivityButton } from './agent_activity_button';
|
||||
import { AgentStatusFilter } from './agent_status_filter';
|
||||
import { DashboardsButtons } from './dashboards_buttons';
|
||||
|
||||
const ClearAllTagsFilterItem = styled(EuiFilterSelectItem)`
|
||||
padding: ${(props) => props.theme.eui.euiSizeS};
|
||||
`;
|
||||
|
||||
const FlexEndEuiFlexItem = styled(EuiFlexItem)`
|
||||
align-self: flex-end;
|
||||
`;
|
||||
|
||||
export const SearchAndFilterBar: React.FunctionComponent<{
|
||||
agentPolicies: AgentPolicy[];
|
||||
draftKuery: string;
|
||||
|
@ -118,17 +115,18 @@ export const SearchAndFilterBar: React.FunctionComponent<{
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* Search and filter bar */}
|
||||
<EuiFlexGroup direction="column">
|
||||
<FlexEndEuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
{/* Top Buttons and Links */}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>{totalAgents > 0 && <DashboardsButtons />}</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AgentActivityButton
|
||||
onClickAgentActivity={onClickAgentActivity}
|
||||
showAgentActivityTour={showAgentActivityTour}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
|
@ -145,7 +143,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{
|
|||
</EuiButton>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
|
@ -163,7 +161,8 @@ export const SearchAndFilterBar: React.FunctionComponent<{
|
|||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FlexEndEuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{/* Search and filters */}
|
||||
<EuiFlexItem grow={4}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={6}>
|
||||
|
|
|
@ -10,13 +10,15 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { DataStream } from '../../../../types';
|
||||
import { useKibanaLink } from '../../../../hooks';
|
||||
import { useDashboardLocator } from '../../../../hooks';
|
||||
import { ContextMenuActions } from '../../../../components';
|
||||
|
||||
import { useAPMServiceDetailHref } from '../../../../hooks/use_apm_service_href';
|
||||
|
||||
export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastream }) => {
|
||||
const { dashboards } = datastream;
|
||||
const dashboardLocator = useDashboardLocator();
|
||||
|
||||
const actionNameSingular = (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.dataStreamList.viewDashboardActionText"
|
||||
|
@ -82,8 +84,7 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre
|
|||
items: [
|
||||
{
|
||||
icon: 'dashboardApp',
|
||||
/* eslint-disable-next-line react-hooks/rules-of-hooks */
|
||||
href: useKibanaLink(`/dashboard/${dashboards[0].id || ''}`),
|
||||
href: dashboardLocator?.getRedirectUrl({ dashboardId: dashboards[0]?.id } || ''),
|
||||
name: actionNameSingular,
|
||||
},
|
||||
],
|
||||
|
@ -109,8 +110,7 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre
|
|||
items: dashboards.map((dashboard) => {
|
||||
return {
|
||||
icon: 'dashboardApp',
|
||||
/* eslint-disable-next-line react-hooks/rules-of-hooks */
|
||||
href: useKibanaLink(`/dashboard/${dashboard.id || ''}`),
|
||||
href: dashboardLocator?.getRedirectUrl({ dashboardId: dashboard?.id } || ''),
|
||||
name: dashboard.title,
|
||||
};
|
||||
}),
|
||||
|
|
|
@ -22,6 +22,7 @@ export {
|
|||
AUTO_UPDATE_PACKAGES,
|
||||
KEEP_POLICIES_UP_TO_DATE_PACKAGES,
|
||||
AUTO_UPGRADE_POLICIES_PACKAGES,
|
||||
LOCATORS_IDS,
|
||||
} from '../../common/constants';
|
||||
|
||||
export * from './page_paths';
|
||||
|
@ -37,7 +38,3 @@ export const DURATION_APM_SETTINGS_VARS = {
|
|||
TAIL_SAMPLING_INTERVAL: 'tail_sampling_interval',
|
||||
WRITE_TIMEOUT: 'write_timeout',
|
||||
};
|
||||
|
||||
export const LOCATORS_IDS = {
|
||||
APM_LOCATOR: 'APM_LOCATOR',
|
||||
} as const;
|
||||
|
|
|
@ -31,3 +31,4 @@ export * from './use_flyout_context';
|
|||
export * from './use_is_guided_onboarding_active';
|
||||
export * from './use_fleet_server_hosts_for_policy';
|
||||
export * from './use_fleet_server_standalone';
|
||||
export * from './use_locator';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import type { SerializableRecord } from '@kbn/utility-types';
|
||||
import type { ValuesType } from 'utility-types';
|
||||
|
||||
import type { LOCATORS_IDS } from '../constants';
|
||||
import { LOCATORS_IDS } from '../constants';
|
||||
|
||||
import { useStartServices } from './use_core';
|
||||
|
||||
|
@ -17,3 +17,7 @@ export function useLocator<T extends SerializableRecord>(
|
|||
const services = useStartServices();
|
||||
return services.share.url.locators.get<T>(locatorId);
|
||||
}
|
||||
|
||||
export function useDashboardLocator() {
|
||||
return useLocator(LOCATORS_IDS.DASHBOARD_APP);
|
||||
}
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import {
|
||||
FLEET_ELASTIC_AGENT_PACKAGE,
|
||||
FLEET_ELASTIC_AGENT_DETAILS_DASHBOARD_ID,
|
||||
} from '@kbn/fleet-plugin/common/constants/epm';
|
||||
import { FLEET_ELASTIC_AGENT_PACKAGE } from '@kbn/fleet-plugin/common/constants/epm';
|
||||
|
||||
import { DASHBOARD_LOCATORS_IDS } from '@kbn/fleet-plugin/common';
|
||||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { skipIfNoDockerRegistry } from '../../helpers';
|
||||
import { setupFleetAndAgents } from '../agents/services';
|
||||
|
@ -47,10 +46,10 @@ export default function (providerContext: FtrProviderContext) {
|
|||
it('Install elastic agent details dashboard with the correct id', async () => {
|
||||
const resDashboard = await kibanaServer.savedObjects.get({
|
||||
type: 'dashboard',
|
||||
id: FLEET_ELASTIC_AGENT_DETAILS_DASHBOARD_ID,
|
||||
id: DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_METRICS,
|
||||
});
|
||||
|
||||
expect(resDashboard.id).to.eql(FLEET_ELASTIC_AGENT_DETAILS_DASHBOARD_ID);
|
||||
expect(resDashboard.id).to.eql(DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_METRICS);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue