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

[Endpoint] Register endpoint app #53527

Merged
merged 14 commits into from
Dec 20, 2019
2 changes: 1 addition & 1 deletion x-pack/plugins/endpoint/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "endpoint"],
"requiredPlugins": ["embeddable"],
"requiredPlugins": ["features", "embeddable"],
"server": true,
"ui": true
}
31 changes: 31 additions & 0 deletions x-pack/plugins/endpoint/public/applications/endpoint/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 * as React from 'react';
import ReactDOM from 'react-dom';
import { CoreStart, AppMountParameters } from 'kibana/public';
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';

/**
* This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle.
*/
export function renderApp(coreStart: CoreStart, { element }: AppMountParameters) {
coreStart.http.get('/api/endpoint/hello-world');

ReactDOM.render(<AppRoot />, element);

return () => {
ReactDOM.unmountComponentAtNode(element);
};
}

const AppRoot = React.memo(() => (
<I18nProvider>
<h1 data-test-subj="welcomeTitle">
<FormattedMessage id="xpack.endpoint.welcomeTitle" defaultMessage="Hello World" />
</h1>
</I18nProvider>
));
15 changes: 14 additions & 1 deletion x-pack/plugins/endpoint/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { Plugin, CoreSetup } from 'kibana/public';
import { IEmbeddableSetup } from 'src/plugins/embeddable/public';
import { i18n } from '@kbn/i18n';
import { ResolverEmbeddableFactory } from './embeddables/resolver';

export type EndpointPluginStart = void;
Expand All @@ -24,8 +25,20 @@ export class EndpointPlugin
EndpointPluginSetupDependencies,
EndpointPluginStartDependencies
> {
public setup(_core: CoreSetup, plugins: EndpointPluginSetupDependencies) {
public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) {
const resolverEmbeddableFactory = new ResolverEmbeddableFactory();
core.application.register({
id: 'endpoint',
title: i18n.translate('xpack.endpoint.pluginTitle', {
defaultMessage: 'Endpoint',
}),
async mount(params) {
const [coreStart] = await core.getStartServices();
const { renderApp } = await import('./applications/endpoint');
return renderApp(coreStart, params);
},
});

plugins.embeddable.registerEmbeddableFactory(
resolverEmbeddableFactory.type,
resolverEmbeddableFactory
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/endpoint/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const config = {
};

export const plugin: PluginInitializer<
EndpointPluginStart,
EndpointPluginSetup,
EndpointPluginStartDependencies,
EndpointPluginSetupDependencies
EndpointPluginStart,
EndpointPluginSetupDependencies,
EndpointPluginStartDependencies
> = () => new EndpointPlugin();
42 changes: 35 additions & 7 deletions x-pack/plugins/endpoint/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,56 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { Plugin, CoreSetup } from 'kibana/server';
import { addRoutes } from './routes';
import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server';

export type EndpointPluginStart = void;
export type EndpointPluginSetup = void;
export interface EndpointPluginSetupDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface

export interface EndpointPluginStartDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface

export interface EndpointPluginSetupDependencies {
features: FeaturesPluginSetupContract;
}

export class EndpointPlugin
implements
Plugin<
EndpointPluginStart,
EndpointPluginSetup,
EndpointPluginStartDependencies,
EndpointPluginSetupDependencies
EndpointPluginStart,
EndpointPluginSetupDependencies,
EndpointPluginStartDependencies
> {
public setup(core: CoreSetup) {
public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) {
plugins.features.registerFeature({
id: 'endpoint',
name: 'Endpoint',
icon: 'bug',
navLinkId: 'endpoint',
app: ['endpoint', 'kibana'],
privileges: {
all: {
api: ['resolver'],
savedObject: {
all: [],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hint I see there's a ui capability here that indicates you may be saving something in the future. When that happens, be sure to update the saved object privileges here with the specific types you need to read and write. We're more than happy to help with this!

read: [],
},
ui: ['save'],
},
read: {
api: [],
savedObject: {
all: [],
read: [],
},
ui: [],
},
},
});
const router = core.http.createRouter();
addRoutes(router);
}

public start() {}
public stop() {}
}
3 changes: 3 additions & 0 deletions x-pack/plugins/endpoint/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export function addRoutes(router: IRouter) {
{
path: '/api/endpoint/hello-world',
validate: false,
options: {
tags: ['access:resolver'],
},
},
async function greetingIndex(_context, _request, response) {
return response.ok({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export default function({ getService }: FtrProviderContext) {
'maps',
'uptime',
'siem',
'endpoint',
].sort()
);
});
Expand Down
1 change: 1 addition & 0 deletions x-pack/test/api_integration/apis/security/privileges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default function({ getService }: FtrProviderContext) {
uptime: ['all', 'read'],
apm: ['all', 'read'],
siem: ['all', 'read'],
endpoint: ['all', 'read'],
},
global: ['all', 'read'],
space: ['all', 'read'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';

export default function({ getPageObjects, getService }: FtrProviderContext) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this test suite! 🥇

const pageObjects = getPageObjects(['common']);
const spacesService = getService('spaces');
const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');

describe('spaces', () => {
describe('space with no features disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: [],
});
});

after(async () => {
await spacesService.delete('custom_space');
});

it('shows endpoint navlink', async () => {
await pageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
);
expect(navLinks).to.contain('EEndpoint');
});

it(`endpoint app shows 'Hello World'`, async () => {
await pageObjects.common.navigateToApp('endpoint', {
basePath: '/s/custom_space',
});
await testSubjects.existOrFail('welcomeTitle');
});
});

describe('space with endpoint disabled', () => {
before(async () => {
await spacesService.create({
id: 'custom_space',
name: 'custom_space',
disabledFeatures: ['endpoint'],
});
});

after(async () => {
await spacesService.delete('custom_space');
});

it(`doesn't show dashboard navlink`, async () => {
kevinlog marked this conversation as resolved.
Show resolved Hide resolved
await pageObjects.common.navigateToApp('home', {
basePath: '/s/custom_space',
});
const navLinks = (await appsMenu.readLinks()).map(
(link: Record<string, string>) => link.text
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not that it hurts, but you probably shouldn't need to specify the type here. I removed it and the inference seems correct:

image

Makes sense because appsMenu.readLinks is typed like so:
image

);
expect(navLinks).not.to.contain('EEndpoint');
});
});
});
}
13 changes: 13 additions & 0 deletions x-pack/test/functional/apps/endpoint/feature_controls/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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 { FtrProviderContext } from '../../../ftr_provider_context';

export default function({ loadTestFile }: FtrProviderContext) {
describe('feature controls', function() {
this.tags('skipFirefox');
loadTestFile(require.resolve('./endpoint_spaces'));
});
}
14 changes: 14 additions & 0 deletions x-pack/test/functional/apps/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';

export default function({ loadTestFile }: FtrProviderContext) {
describe('endpoint', function() {
this.tags('ciGroup7');

loadTestFile(require.resolve('./feature_controls'));
});
}
5 changes: 5 additions & 0 deletions x-pack/test/functional/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default async function({ readConfigFile }) {
resolve(__dirname, './apps/cross_cluster_replication'),
resolve(__dirname, './apps/remote_clusters'),
resolve(__dirname, './apps/transform'),
resolve(__dirname, './apps/endpoint'),
// This license_management file must be last because it is destructive.
resolve(__dirname, './apps/license_management'),
],
Expand Down Expand Up @@ -86,6 +87,7 @@ export default async function({ readConfigFile }) {
'--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"',
'--telemetry.banner=false',
'--timelion.ui.enabled=true',
'--xpack.endpoint.enabled=true',
],
},
uiSettings: {
Expand Down Expand Up @@ -197,6 +199,9 @@ export default async function({ readConfigFile }) {
pathname: '/app/kibana/',
hash: '/management/elasticsearch/transform',
},
endpoint: {
pathname: '/app/endpoint',
},
},

// choose where esArchiver should load archives from
Expand Down