mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Check Dashboard capabilities at dashboards landing page (#137186)
* new useCapabilities kibana hook * check dashboard capabilities on security dashboard landing table and button
This commit is contained in:
parent
17a0f7fcfd
commit
5124d6c94d
5 changed files with 95 additions and 41 deletions
|
@ -13,6 +13,7 @@
|
|||
"alerting",
|
||||
"cases",
|
||||
"cloudSecurityPosture",
|
||||
"dashboard",
|
||||
"data",
|
||||
"embeddable",
|
||||
"eventLog",
|
||||
|
@ -35,7 +36,6 @@
|
|||
"encryptedSavedObjects",
|
||||
"fleet",
|
||||
"ml",
|
||||
"dashboard",
|
||||
"newsfeed",
|
||||
"security",
|
||||
"spaces",
|
||||
|
|
|
@ -90,3 +90,9 @@ export const useNavigateTo = jest.fn().mockReturnValue({
|
|||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const useCapabilities = jest.fn((featureId?: string) =>
|
||||
featureId
|
||||
? mockStartServicesMock.application.capabilities[featureId]
|
||||
: mockStartServicesMock.application.capabilities
|
||||
);
|
||||
|
|
|
@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { camelCase, isArray, isObject } from 'lodash';
|
||||
import { set } from '@elastic/safer-lodash-set';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
|
||||
import type { NavigateToAppOptions } from '@kbn/core/public';
|
||||
import type { Capabilities, NavigateToAppOptions } from '@kbn/core/public';
|
||||
import type { CasesPermissions } from '@kbn/cases-plugin/common/ui';
|
||||
import {
|
||||
APP_UI_ID,
|
||||
|
@ -182,18 +182,16 @@ export const useGetUserCasesPermissions = () => {
|
|||
return casesPermissions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a full URL to the provided page path by using
|
||||
* kibana's `getUrlForApp()`
|
||||
*/
|
||||
|
||||
export type GetAppUrl = (param: {
|
||||
appId?: string;
|
||||
deepLinkId?: string;
|
||||
path?: string;
|
||||
absolute?: boolean;
|
||||
}) => string;
|
||||
|
||||
/**
|
||||
* The `getAppUrl` function returns a full URL to the provided page path by using
|
||||
* kibana's `getUrlForApp()`
|
||||
*/
|
||||
export const useAppUrl = () => {
|
||||
const { getUrlForApp } = useKibana().services.application;
|
||||
|
||||
|
@ -204,18 +202,16 @@ export const useAppUrl = () => {
|
|||
return { getAppUrl };
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to any app using kibana's `navigateToApp()`
|
||||
* or by url using `navigateToUrl()`
|
||||
*/
|
||||
|
||||
export type NavigateTo = (
|
||||
param: {
|
||||
url?: string;
|
||||
appId?: string;
|
||||
} & NavigateToAppOptions
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* The `navigateTo` function navigates to any app using kibana's `navigateToApp()`.
|
||||
* When the `{ url: string }` parameter is passed it will navigate using `navigateToUrl()`.
|
||||
*/
|
||||
export const useNavigateTo = () => {
|
||||
const { navigateToApp, navigateToUrl } = useKibana().services.application;
|
||||
|
||||
|
@ -233,11 +229,27 @@ export const useNavigateTo = () => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Returns navigateTo and getAppUrl navigation hooks
|
||||
*
|
||||
* Returns `navigateTo` and `getAppUrl` navigation hooks
|
||||
*/
|
||||
export const useNavigation = () => {
|
||||
const { navigateTo } = useNavigateTo();
|
||||
const { getAppUrl } = useAppUrl();
|
||||
return { navigateTo, getAppUrl };
|
||||
};
|
||||
|
||||
// Get the type for any feature capability
|
||||
export type FeatureCapability = Capabilities[string];
|
||||
interface UseCapabilities {
|
||||
(): Capabilities;
|
||||
<T extends FeatureCapability = FeatureCapability>(featureId: string): T;
|
||||
}
|
||||
/**
|
||||
* Returns the feature capability when the `featureId` parameter is defined,
|
||||
* or the entire kibana `Capabilities` object when the parameter is omitted.
|
||||
*/
|
||||
export const useCapabilities: UseCapabilities = <T extends FeatureCapability = FeatureCapability>(
|
||||
featureId?: string
|
||||
) => {
|
||||
const { capabilities } = useKibana().services.application;
|
||||
return featureId ? (capabilities[featureId] as T) : capabilities;
|
||||
};
|
||||
|
|
|
@ -11,12 +11,18 @@ import { SecurityPageName } from '../../app/types';
|
|||
import { TestProviders } from '../../common/mock';
|
||||
import { DashboardsLandingPage } from './dashboards';
|
||||
import type { NavLinkItem } from '../../common/components/navigation/types';
|
||||
import { useCapabilities } from '../../common/lib/kibana';
|
||||
|
||||
jest.mock('../../common/lib/kibana');
|
||||
jest.mock('../../common/utils/route/spy_routes', () => ({ SpyRoute: () => null }));
|
||||
jest.mock('../../common/components/dashboards/dashboards_table', () => ({
|
||||
DashboardsTable: () => <span data-test-subj="dashboardsTable" />,
|
||||
}));
|
||||
|
||||
const DEFAULT_DASHBOARD_CAPABILITIES = { show: true, createNew: true };
|
||||
const mockUseCapabilities = useCapabilities as jest.Mock;
|
||||
mockUseCapabilities.mockReturnValue(DEFAULT_DASHBOARD_CAPABILITIES);
|
||||
|
||||
const OVERVIEW_ITEM_LABEL = 'Overview';
|
||||
const DETECTION_RESPONSE_ITEM_LABEL = 'Detection & Response';
|
||||
|
||||
|
@ -99,6 +105,16 @@ describe('Dashboards landing', () => {
|
|||
expect(result.getByTestId('dashboardsTable')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render dashboards table if no read capability', () => {
|
||||
mockUseCapabilities.mockReturnValueOnce({
|
||||
...DEFAULT_DASHBOARD_CAPABILITIES,
|
||||
show: false,
|
||||
});
|
||||
const result = renderDashboardLanding();
|
||||
|
||||
expect(result.queryByTestId('dashboardsTable')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Create Security Dashboard button', () => {
|
||||
it('should render', () => {
|
||||
const result = renderDashboardLanding();
|
||||
|
@ -106,6 +122,16 @@ describe('Dashboards landing', () => {
|
|||
expect(result.getByTestId('createDashboardButton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render if no write capability', () => {
|
||||
mockUseCapabilities.mockReturnValueOnce({
|
||||
...DEFAULT_DASHBOARD_CAPABILITIES,
|
||||
createNew: false,
|
||||
});
|
||||
const result = renderDashboardLanding();
|
||||
|
||||
expect(result.queryByTestId('createDashboardButton')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should be enabled when link loaded', () => {
|
||||
const result = renderDashboardLanding();
|
||||
|
||||
|
|
|
@ -13,19 +13,21 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import type { DashboardCapabilities } from '@kbn/dashboard-plugin/common/types';
|
||||
import { DashboardConstants } from '@kbn/dashboard-plugin/public';
|
||||
import { SecurityPageName } from '../../app/types';
|
||||
import { DashboardsTable } from '../../common/components/dashboards/dashboards_table';
|
||||
import { Title } from '../../common/components/header_page/title';
|
||||
import { useAppRootNavLink } from '../../common/components/navigation/nav_links';
|
||||
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
|
||||
import { useCreateSecurityDashboardLink } from '../../common/containers/dashboards/use_create_security_dashboard_link';
|
||||
import { useNavigateTo } from '../../common/lib/kibana';
|
||||
import { useCapabilities, useNavigateTo } from '../../common/lib/kibana';
|
||||
import { SpyRoute } from '../../common/utils/route/spy_routes';
|
||||
import { LandingImageCards } from '../components/landing_links_images';
|
||||
import * as i18n from './translations';
|
||||
|
||||
/* eslint-disable @elastic/eui/href-or-on-click */
|
||||
const Header = () => {
|
||||
const Header: React.FC<{ canCreateDashboard: boolean }> = ({ canCreateDashboard }) => {
|
||||
const { isLoading, url } = useCreateSecurityDashboardLink();
|
||||
const { navigateTo } = useNavigateTo();
|
||||
return (
|
||||
|
@ -33,32 +35,36 @@ const Header = () => {
|
|||
<EuiFlexItem>
|
||||
<Title title={i18n.DASHBOARDS_PAGE_TITLE} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isDisabled={isLoading}
|
||||
color="primary"
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
href={url}
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault();
|
||||
navigateTo({ url });
|
||||
}}
|
||||
data-test-subj="createDashboardButton"
|
||||
>
|
||||
{i18n.DASHBOARDS_PAGE_CREATE_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
{canCreateDashboard && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isDisabled={isLoading}
|
||||
color="primary"
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
href={url}
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault();
|
||||
navigateTo({ url });
|
||||
}}
|
||||
data-test-subj="createDashboardButton"
|
||||
>
|
||||
{i18n.DASHBOARDS_PAGE_CREATE_BUTTON}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const DashboardsLandingPage = () => {
|
||||
const dashboardLinks = useAppRootNavLink(SecurityPageName.dashboardsLanding)?.links ?? [];
|
||||
const { show: canReadDashboard, createNew: canCreateDashboard } =
|
||||
useCapabilities<DashboardCapabilities>(DashboardConstants.DASHBOARD_ID);
|
||||
|
||||
return (
|
||||
<SecuritySolutionPageWrapper>
|
||||
<Header />
|
||||
<Header canCreateDashboard={canCreateDashboard} />
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiTitle size="xxxs">
|
||||
|
@ -68,12 +74,16 @@ export const DashboardsLandingPage = () => {
|
|||
<LandingImageCards items={dashboardLinks} />
|
||||
<EuiSpacer size="xxl" />
|
||||
|
||||
<EuiTitle size="xxxs">
|
||||
<h2>{i18n.DASHBOARDS_PAGE_SECTION_CUSTOM}</h2>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<EuiSpacer size="m" />
|
||||
<DashboardsTable />
|
||||
{canReadDashboard && (
|
||||
<>
|
||||
<EuiTitle size="xxxs">
|
||||
<h2>{i18n.DASHBOARDS_PAGE_SECTION_CUSTOM}</h2>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<EuiSpacer size="m" />
|
||||
<DashboardsTable />
|
||||
</>
|
||||
)}
|
||||
|
||||
<SpyRoute pageName={SecurityPageName.dashboardsLanding} />
|
||||
</SecuritySolutionPageWrapper>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue