From 600d85c5321443ca988e235e50ec8fbdfe0a5f08 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Thu, 26 Mar 2020 20:54:22 -0400
Subject: [PATCH] [Endpoint] Common PageView and LinkToApp components (#60819)
(#61555)
* a common PageView component
* Policy List cleanup + improvements to PageView
* Policy details refactored to use PageView layout
* Fix bug: header nav - ensure section sub-routes set nav to `isSelected`
* Added useNavigateToAppEventHandler hook
* Remove use of `appBasePath` and use `history` for initializing router
- For details - see https://github.com/elastic/kibana/pull/56705
* Removed `hello-world` API
* Added `LinkToApp` component + refactor policy list to use it
* Add icon to plugin registration
---
.../__snapshots__/link_to_app.test.tsx.snap | 40 ++
.../__snapshots__/page_view.test.tsx.snap | 646 ++++++++++++++++++
.../endpoint/components/header_nav.tsx | 56 +-
.../endpoint/components/link_to_app.test.tsx | 144 ++++
.../endpoint/components/link_to_app.tsx | 35 +
.../endpoint/components/page_view.test.tsx | 63 ++
.../endpoint/components/page_view.tsx | 96 +++
.../use_navigate_to_app_event_handler.ts | 75 ++
.../public/applications/endpoint/index.tsx | 19 +-
.../endpoint/view/policy/policy_details.tsx | 90 +--
.../endpoint/view/policy/policy_list.tsx | 92 +--
x-pack/plugins/endpoint/public/plugin.ts | 1 +
x-pack/plugins/endpoint/server/plugin.ts | 2 -
.../plugins/endpoint/server/routes/index.ts | 26 -
.../feature_controls/endpoint_spaces.ts | 4 +-
.../functional/apps/endpoint/header_nav.ts | 2 +-
16 files changed, 1209 insertions(+), 182 deletions(-)
create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/link_to_app.test.tsx.snap
create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/page_view.test.tsx.snap
create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.test.tsx
create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx
create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.test.tsx
create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.tsx
create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/hooks/use_navigate_to_app_event_handler.ts
delete mode 100644 x-pack/plugins/endpoint/server/routes/index.ts
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/link_to_app.test.tsx.snap b/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/link_to_app.test.tsx.snap
new file mode 100644
index 0000000000000..6838b673b90d8
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/link_to_app.test.tsx.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`LinkToApp component should render with href 1`] = `
+
+
+
+ link
+
+
+
+`;
+
+exports[`LinkToApp component should render with minimum input 1`] = `
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/page_view.test.tsx.snap b/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/page_view.test.tsx.snap
new file mode 100644
index 0000000000000..34420e653049c
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/__snapshots__/page_view.test.tsx.snap
@@ -0,0 +1,646 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PageView component should display body header custom element 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+ body header
+
+ }
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ body content
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`PageView component should display body header wrapped in EuiTitle 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ body header
+
+
+
+
+
+
+
+
+ body content
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`PageView component should display header left and right 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ page title
+
+
+
+
+
+
+ right side actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`PageView component should display only body if not header props used 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+
+
+
+
+
+
+`;
+
+exports[`PageView component should display only header left 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ page title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`PageView component should display only header right but include an empty left side 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ right side actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`PageView component should pass through EuiPage props 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+
+
+
+
+
+
+`;
+
+exports[`PageView component should use custom element for header left and not wrap in EuiTitle 1`] = `
+.c0 {
+ padding: 0;
+}
+
+.c0 .endpoint-header {
+ padding: 24px;
+}
+
+.c0 .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+}
+
+
+ title here
+
+ }
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/header_nav.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/components/header_nav.tsx
index 1bafcbec93f5f..3fb06d6b4a56e 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/components/header_nav.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/header_nav.tsx
@@ -4,10 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { MouseEvent } from 'react';
+import React, { MouseEvent, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiTabs, EuiTab } from '@elastic/eui';
import { useHistory, useLocation } from 'react-router-dom';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
export interface NavTabs {
name: string;
@@ -46,30 +47,33 @@ export const navTabs: NavTabs[] = [
},
];
-export const HeaderNavigation: React.FunctionComponent<{ basename: string }> = React.memo(
- ({ basename }) => {
- const history = useHistory();
- const location = useLocation();
+export const HeaderNavigation: React.FunctionComponent = React.memo(() => {
+ const history = useHistory();
+ const location = useLocation();
+ const { services } = useKibana();
+ const BASE_PATH = services.application.getUrlForApp('endpoint');
- function renderNavTabs(tabs: NavTabs[]) {
- return tabs.map((tab, index) => {
- return (
- {
- event.preventDefault();
- history.push(tab.href);
- }}
- isSelected={tab.href === location.pathname}
- >
- {tab.name}
-
- );
- });
- }
+ const tabList = useMemo(() => {
+ return navTabs.map((tab, index) => {
+ return (
+ {
+ event.preventDefault();
+ history.push(tab.href);
+ }}
+ isSelected={
+ tab.href === location.pathname ||
+ (tab.href !== '/' && location.pathname.startsWith(tab.href))
+ }
+ >
+ {tab.name}
+
+ );
+ });
+ }, [BASE_PATH, history, location.pathname]);
- return {renderNavTabs(navTabs)};
- }
-);
+ return {tabList};
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.test.tsx
new file mode 100644
index 0000000000000..902c215434aac
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.test.tsx
@@ -0,0 +1,144 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { LinkToApp } from './link_to_app';
+import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
+import { coreMock } from '../../../../../../../src/core/public/mocks';
+import { CoreStart } from 'kibana/public';
+
+describe('LinkToApp component', () => {
+ let fakeCoreStart: jest.Mocked;
+ const render = (ui: Parameters[0]) =>
+ mount(ui, {
+ wrappingComponent: KibanaContextProvider,
+ wrappingComponentProps: {
+ services: { application: fakeCoreStart.application },
+ },
+ });
+
+ beforeEach(() => {
+ fakeCoreStart = coreMock.createStart();
+ });
+
+ it('should render with minimum input', () => {
+ expect(render(link)).toMatchSnapshot();
+ });
+ it('should render with href', () => {
+ expect(
+ render(
+
+ link
+
+ )
+ ).toMatchSnapshot();
+ });
+ it('should support onClick prop', () => {
+ const spyOnClickHandler = jest.fn();
+ const renderResult = render(
+
+ link
+
+ );
+
+ renderResult.find('EuiLink').simulate('click', { button: 0 });
+ const clickEventArg = spyOnClickHandler.mock.calls[0][0];
+
+ expect(spyOnClickHandler).toHaveBeenCalled();
+ expect(clickEventArg.preventDefault).toBeInstanceOf(Function);
+ expect(clickEventArg.isDefaultPrevented()).toBe(true);
+ expect(fakeCoreStart.application.navigateToApp).toHaveBeenCalledWith('ingestManager', {
+ path: undefined,
+ state: undefined,
+ });
+ });
+ it('should navigate to App with specific path', () => {
+ const renderResult = render(
+
+ link
+
+ );
+ renderResult.find('EuiLink').simulate('click', { button: 0 });
+ expect(fakeCoreStart.application.navigateToApp).toHaveBeenCalledWith('ingestManager', {
+ path: '/some/path',
+ state: undefined,
+ });
+ });
+ it('should passes through EuiLinkProps', () => {
+ const renderResult = render(
+
+ link
+
+ );
+ expect(renderResult.find('EuiLink').props()).toEqual({
+ children: 'link',
+ className: 'my-class',
+ color: 'primary',
+ 'data-test-subj': 'my-test-subject',
+ href: '/app/ingest',
+ onClick: expect.any(Function),
+ });
+ });
+ it('should still preventDefault if onClick callback throws', () => {
+ const spyOnClickHandler = jest.fn(ev => {
+ throw new Error('test');
+ });
+ const renderResult = render(
+
+ link
+
+ );
+ expect(() => renderResult.find('EuiLink').simulate('click')).toThrow();
+ const clickEventArg = spyOnClickHandler.mock.calls[0][0];
+ expect(clickEventArg.isDefaultPrevented()).toBe(true);
+ });
+ it('should not navigate if onClick callback prevents defalut', () => {
+ const spyOnClickHandler = jest.fn(ev => {
+ ev.preventDefault();
+ });
+ const renderResult = render(
+
+ link
+
+ );
+ renderResult.find('EuiLink').simulate('click', { button: 0 });
+ expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
+ });
+ it('should not to navigate if it was not left click', () => {
+ const renderResult = render(link);
+ renderResult.find('EuiLink').simulate('click', { button: 1 });
+ expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
+ });
+ it('should not to navigate if it includes an anchor target', () => {
+ const renderResult = render(
+
+ link
+
+ );
+ renderResult.find('EuiLink').simulate('click', { button: 0 });
+ expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
+ });
+ it('should not to navigate if if meta|alt|ctrl|shift keys are pressed', () => {
+ const renderResult = render(
+
+ link
+
+ );
+ const euiLink = renderResult.find('EuiLink');
+ ['meta', 'alt', 'ctrl', 'shift'].forEach(key => {
+ euiLink.simulate('click', { button: 0, [`${key}Key`]: true });
+ expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx
new file mode 100644
index 0000000000000..b110d32442c2c
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { memo, MouseEventHandler } from 'react';
+import { EuiLink } from '@elastic/eui';
+import { EuiLinkProps } from '@elastic/eui';
+import { useNavigateToAppEventHandler } from '../hooks/use_navigate_to_app_event_handler';
+
+export type LinkToAppProps = EuiLinkProps & {
+ /** the app id - normally the value of the `id` in that plugin's `kibana.json` */
+ appId: string;
+ /** Any app specic path (route) */
+ appPath?: string;
+ appState?: any;
+ onClick?: MouseEventHandler;
+};
+
+/**
+ * An `EuiLink` that will use Kibana's `.application.navigateToApp()` to redirect the user to the
+ * a given app without causing a full browser refresh
+ */
+export const LinkToApp = memo(
+ ({ appId, appPath: path, appState: state, onClick, children, ...otherProps }) => {
+ const handleOnClick = useNavigateToAppEventHandler(appId, { path, state, onClick });
+ return (
+ // eslint-disable-next-line @elastic/eui/href-or-on-click
+
+ {children}
+
+ );
+ }
+);
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.test.tsx
new file mode 100644
index 0000000000000..867c9101fe79b
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.test.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { PageView } from './page_view';
+import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components';
+
+describe('PageView component', () => {
+ const render = (ui: Parameters[0]) =>
+ mount(ui, { wrappingComponent: EuiThemeProvider });
+
+ it('should display only body if not header props used', () => {
+ expect(render(body content)).toMatchSnapshot();
+ });
+ it('should display header left and right', () => {
+ expect(
+ render(
+
+ body content
+
+ )
+ ).toMatchSnapshot();
+ });
+ it('should display only header left', () => {
+ expect(render(body content)).toMatchSnapshot();
+ });
+ it('should display only header right but include an empty left side', () => {
+ expect(
+ render(body content)
+ ).toMatchSnapshot();
+ });
+ it(`should use custom element for header left and not wrap in EuiTitle`, () => {
+ expect(
+ render(title here}>body content)
+ ).toMatchSnapshot();
+ });
+ it('should display body header wrapped in EuiTitle', () => {
+ expect(render(body content)).toMatchSnapshot();
+ });
+ it('should display body header custom element', () => {
+ expect(
+ render(body header}>body content)
+ ).toMatchSnapshot();
+ });
+ it('should pass through EuiPage props', () => {
+ expect(
+ render(
+
+ body content
+
+ )
+ ).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.tsx
new file mode 100644
index 0000000000000..04401f06d344a
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/page_view.tsx
@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ EuiPage,
+ EuiPageBody,
+ EuiPageContent,
+ EuiPageContentBody,
+ EuiPageContentHeader,
+ EuiPageContentHeaderSection,
+ EuiPageHeader,
+ EuiPageHeaderSection,
+ EuiPageProps,
+ EuiTitle,
+} from '@elastic/eui';
+import React, { memo, ReactNode } from 'react';
+import styled from 'styled-components';
+
+const StyledEuiPage = styled(EuiPage)`
+ padding: 0;
+
+ .endpoint-header {
+ padding: ${props => props.theme.eui.euiSizeL};
+ }
+ .endpoint-page-content {
+ border-left: none;
+ border-right: none;
+ }
+`;
+
+const isStringOrNumber = /(string|number)/;
+
+/**
+ * Page View layout for use in Endpoint
+ */
+export const PageView = memo<
+ EuiPageProps & {
+ /**
+ * content to be placed on the left side of the header. If a `string` is used, then it will
+ * be wrapped with ``, else it will just be used as is.
+ */
+ headerLeft?: ReactNode;
+ /** Content for the right side of the header */
+ headerRight?: ReactNode;
+ /**
+ * body (sub-)header section. If a `string` is used, then it will be wrapped with
+ * ``
+ */
+ bodyHeader?: ReactNode;
+ children?: ReactNode;
+ }
+>(({ children, headerLeft, headerRight, bodyHeader, ...otherProps }) => {
+ return (
+
+
+ {(headerLeft || headerRight) && (
+
+
+ {isStringOrNumber.test(typeof headerLeft) ? (
+
+ {headerLeft}
+
+ ) : (
+ headerLeft
+ )}
+
+ {headerRight && (
+
+ {headerRight}
+
+ )}
+
+ )}
+
+ {bodyHeader && (
+
+
+ {isStringOrNumber.test(typeof bodyHeader) ? (
+
+ {bodyHeader}
+
+ ) : (
+ bodyHeader
+ )}
+
+
+ )}
+ {children}
+
+
+
+ );
+});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/hooks/use_navigate_to_app_event_handler.ts b/x-pack/plugins/endpoint/public/applications/endpoint/hooks/use_navigate_to_app_event_handler.ts
new file mode 100644
index 0000000000000..5fbfa5e0e58a8
--- /dev/null
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/hooks/use_navigate_to_app_event_handler.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { MouseEventHandler, useCallback } from 'react';
+import { ApplicationStart } from 'kibana/public';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+
+type NavigateToAppHandlerProps = Parameters;
+type EventHandlerCallback = MouseEventHandler;
+
+/**
+ * Provides an event handlers that can be used with (for example) `onClick` to prevent the
+ * event's default behaviour and instead use Kibana's `navigateToApp()` to send user to a
+ * different app. Use of `navigateToApp()` prevents a full browser refresh for apps that have
+ * been converted to the New Platform.
+ *
+ * @param appId
+ * @param [options]
+ *
+ * @example
+ *
+ * const handleOnClick = useNavigateToAppEventHandler('ingestManager', {path: '#/configs'})
+ * return See configs
+ */
+export const useNavigateToAppEventHandler = (
+ /** the app id - normally the value of the `id` in that plugin's `kibana.json` */
+ appId: NavigateToAppHandlerProps[0],
+
+ /** Options, some of which are passed along to the app route */
+ options?: NavigateToAppHandlerProps[1] & {
+ onClick?: EventHandlerCallback;
+ }
+): EventHandlerCallback => {
+ const { services } = useKibana();
+ const { path, state, onClick } = options || {};
+ return useCallback(
+ ev => {
+ try {
+ if (onClick) {
+ onClick(ev);
+ }
+ } catch (error) {
+ ev.preventDefault();
+ throw error;
+ }
+
+ if (ev.defaultPrevented) {
+ return;
+ }
+
+ if (ev.button !== 0) {
+ return;
+ }
+
+ if (
+ ev.currentTarget instanceof HTMLAnchorElement &&
+ ev.currentTarget.target !== '' &&
+ ev.currentTarget.target !== '_self'
+ ) {
+ return;
+ }
+
+ if (ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey) {
+ return;
+ }
+
+ ev.preventDefault();
+ services.application.navigateToApp(appId, { path, state });
+ },
+ [appId, onClick, path, services.application, state]
+ );
+};
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx
index 884646369b4b1..fa9055e0d9bbd 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx
@@ -6,9 +6,9 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
-import { CoreStart, AppMountParameters } from 'kibana/public';
+import { CoreStart, AppMountParameters, ScopedHistory } from 'kibana/public';
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
-import { Route, Switch, BrowserRouter } from 'react-router-dom';
+import { Route, Switch, Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import { useObservable } from 'react-use';
@@ -29,12 +29,11 @@ import { EuiThemeProvider } from '../../../../../legacy/common/eui_styled_compon
export function renderApp(
coreStart: CoreStart,
depsStart: EndpointPluginStartDependencies,
- { appBasePath, element }: AppMountParameters
+ { element, history }: AppMountParameters
) {
- coreStart.http.get('/api/endpoint/hello-world');
const store = appStoreFactory({ coreStart, depsStart });
ReactDOM.render(
- ,
+ ,
element
);
return () => {
@@ -43,7 +42,7 @@ export function renderApp(
}
interface RouterProps {
- basename: string;
+ history: ScopedHistory;
store: Store;
coreStart: CoreStart;
depsStart: EndpointPluginStartDependencies;
@@ -51,7 +50,7 @@ interface RouterProps {
const AppRoot: React.FunctionComponent = React.memo(
({
- basename,
+ history,
store,
coreStart: { http, notifications, uiSettings, application },
depsStart: { data },
@@ -63,9 +62,9 @@ const AppRoot: React.FunctionComponent = React.memo(
-
+
-
+
= React.memo(
/>
-
+
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
index 7317bd5e03ed9..a64b3293ec6cd 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx
@@ -6,11 +6,6 @@
import React from 'react';
import {
- EuiTitle,
- EuiPage,
- EuiPageBody,
- EuiPageHeader,
- EuiPageHeaderSection,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
@@ -19,64 +14,49 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
import { usePolicyDetailsSelector } from './policy_hooks';
import { policyDetails } from '../../store/policy_details/selectors';
import { WindowsEventing } from './policy_forms/eventing/windows';
+import { PageView } from '../../components/page_view';
export const PolicyDetails = React.memo(() => {
const policyItem = usePolicyDetailsSelector(policyDetails);
- function policyName() {
- if (policyItem) {
- return {policyItem.name};
- } else {
- return (
-
-
-
- );
- }
- }
+ const headerLeftContent =
+ policyItem?.name ??
+ i18n.translate('xpack.endpoint.policyDetails.notFound', {
+ defaultMessage: 'Policy Not Found',
+ });
+
+ const headerRightContent = (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
return (
-
-
-
-
-
- {policyName()}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
});
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx
index f949efa46a2bd..7af302de8576e 100644
--- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx
+++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx
@@ -4,20 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { SyntheticEvent, useCallback, useEffect, useMemo } from 'react';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPageContentBody,
- EuiPageContentHeader,
- EuiPageContentHeaderSection,
- EuiTitle,
- EuiBasicTable,
- EuiText,
- EuiTableFieldDataColumnType,
- EuiLink,
-} from '@elastic/eui';
+import React, { useCallback, useEffect, useMemo } from 'react';
+import { EuiBasicTable, EuiText, EuiTableFieldDataColumnType, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useDispatch } from 'react-redux';
@@ -35,6 +23,8 @@ import { usePolicyListSelector } from './policy_hooks';
import { PolicyListAction } from '../../store/policy_list';
import { PolicyData } from '../../types';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
+import { PageView } from '../../components/page_view';
+import { LinkToApp } from '../../components/link_to_app';
interface TableChangeCallbackArguments {
page: { index: number; size: number };
@@ -145,18 +135,13 @@ export const PolicyList = React.memo(() => {
}),
render(version: string) {
return (
- // eslint-disable-next-line @elastic/eui/href-or-on-click
- {
- ev.preventDefault();
- services.application.navigateToApp('ingestManager', {
- path: `#/configs/${version}`,
- });
- }}
>
{version}
-
+
);
},
},
@@ -165,42 +150,29 @@ export const PolicyList = React.memo(() => {
);
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ }
+ >
+
+
);
});
diff --git a/x-pack/plugins/endpoint/public/plugin.ts b/x-pack/plugins/endpoint/public/plugin.ts
index 2759db26bb6c8..ee5bbe71ae8aa 100644
--- a/x-pack/plugins/endpoint/public/plugin.ts
+++ b/x-pack/plugins/endpoint/public/plugin.ts
@@ -47,6 +47,7 @@ export class EndpointPlugin
title: i18n.translate('xpack.endpoint.pluginTitle', {
defaultMessage: 'Endpoint',
}),
+ euiIconType: 'securityApp',
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./applications/endpoint');
diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts
index 4b4afd8088744..6d2e9e510551a 100644
--- a/x-pack/plugins/endpoint/server/plugin.ts
+++ b/x-pack/plugins/endpoint/server/plugin.ts
@@ -9,7 +9,6 @@ import { PluginSetupContract as FeaturesPluginSetupContract } from '../../featur
import { createConfig$, EndpointConfigType } from './config';
import { EndpointAppContext } from './types';
-import { addRoutes } from './routes';
import { registerEndpointRoutes } from './routes/metadata';
import { registerAlertRoutes } from './routes/alerts';
import { registerResolverRoutes } from './routes/resolver';
@@ -71,7 +70,6 @@ export class EndpointPlugin
},
} as EndpointAppContext;
const router = core.http.createRouter();
- addRoutes(router);
registerEndpointRoutes(router, endpointContext);
registerResolverRoutes(router, endpointContext);
registerAlertRoutes(router, endpointContext);
diff --git a/x-pack/plugins/endpoint/server/routes/index.ts b/x-pack/plugins/endpoint/server/routes/index.ts
deleted file mode 100644
index 8b0476ea7b229..0000000000000
--- a/x-pack/plugins/endpoint/server/routes/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import { IRouter } from 'kibana/server';
-
-export function addRoutes(router: IRouter) {
- router.get(
- {
- path: '/api/endpoint/hello-world',
- validate: false,
- options: {
- tags: ['access:resolver'],
- },
- },
- async function greetingIndex(_context, _request, response) {
- return response.ok({
- body: { hello: 'world' },
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- }
- );
-}
diff --git a/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts b/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts
index bf3d642307d8c..c543046031e9f 100644
--- a/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts
+++ b/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts
@@ -31,7 +31,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(link => link.text);
- expect(navLinks).to.contain('EEndpoint');
+ expect(navLinks).to.contain('Endpoint');
});
it(`endpoint app shows 'Hello World'`, async () => {
@@ -69,7 +69,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(link => link.text);
- expect(navLinks).not.to.contain('EEndpoint');
+ expect(navLinks).not.to.contain('Endpoint');
});
});
});
diff --git a/x-pack/test/functional/apps/endpoint/header_nav.ts b/x-pack/test/functional/apps/endpoint/header_nav.ts
index d1fa7311d61e8..c2c4068212484 100644
--- a/x-pack/test/functional/apps/endpoint/header_nav.ts
+++ b/x-pack/test/functional/apps/endpoint/header_nav.ts
@@ -41,7 +41,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('renders the policy page when Policy tab is selected', async () => {
await (await testSubjects.find('policiesEndpointTab')).click();
- await testSubjects.existOrFail('policyViewTitle');
+ await testSubjects.existOrFail('policyListPage');
});
it('renders the home page when Home tab is selected after selecting another tab', async () => {