Skip to content

Commit

Permalink
[Fleet] Surface new overview dashboards in fleet (#154914)
Browse files Browse the repository at this point in the history
Closes #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](https://github.com/elastic/kibana/blob/e80abe810837eeeff7fdcd594c6f8950590b49cd/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 <[email protected]>
  • Loading branch information
criamico and kibanamachine authored Apr 18, 2023
1 parent f892fac commit 970de91
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 36 deletions.
3 changes: 0 additions & 3 deletions x-pack/plugins/fleet/common/constants/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions x-pack/plugins/fleet/common/constants/locators.ts
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export {
// Statuses
// Authz
ENDPOINT_PRIVILEGES,
// dashboards ids
DASHBOARD_LOCATORS_IDS,
} from './constants';
export {
// Route services
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -145,7 +143,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{
</EuiButton>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
content={
<FormattedMessage
Expand All @@ -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}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
},
],
Expand All @@ -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,
};
}),
Expand Down
5 changes: 1 addition & 4 deletions x-pack/plugins/fleet/public/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/public/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
6 changes: 5 additions & 1 deletion x-pack/plugins/fleet/public/hooks/use_locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 () => {
Expand Down

0 comments on commit 970de91

Please sign in to comment.