Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Surface new overview dashboards in fleet #154914

Merged
merged 8 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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