[Security Solution][RAC][Timeline] Timeline plugin skeleton and test plugin harness (#95683)

* [RAC][Security Solution] Initial timeline and test plugin harness

* Change plugin name from timeline to timelines
This commit is contained in:
Kevin Qualters 2021-03-31 15:33:19 -04:00 committed by GitHub
parent fe17879ae3
commit 03b104cc61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 411 additions and 0 deletions

1
.github/CODEOWNERS vendored
View file

@ -348,6 +348,7 @@
# Security Solution sub teams
/x-pack/plugins/case @elastic/security-threat-hunting
/x-pack/plugins/timelines @elastic/security-threat-hunting
/x-pack/test/case_api_integration @elastic/security-threat-hunting
/x-pack/plugins/lists @elastic/security-detections-response

View file

@ -537,6 +537,10 @@ Documentation: https://www.elastic.co/guide/en/kibana/master/task-manager-produc
|Gathers all usage collection, retrieving them from both: OSS and X-Pack plugins.
|{kib-repo}blob/{branch}/x-pack/plugins/timelines/README.md[timelines]
|Timelines is a plugin that provides a grid component with accompanying server side apis to help users identify events of interest and perform root cause analysis within Kibana.
|{kib-repo}blob/{branch}/x-pack/plugins/transform/readme.md[transform]
|This plugin provides access to the transforms features provided by Elastic.

View file

@ -108,3 +108,4 @@ pageLoadAssetSize:
fileUpload: 25664
banners: 17946
mapsEms: 26072
timelines: 28613

View file

@ -123,6 +123,7 @@
{ "path": "./x-pack/plugins/stack_alerts/tsconfig.json" },
{ "path": "./x-pack/plugins/task_manager/tsconfig.json" },
{ "path": "./x-pack/plugins/telemetry_collection_xpack/tsconfig.json" },
{ "path": "./x-pack/plugins/timelines/tsconfig.json" },
{ "path": "./x-pack/plugins/transform/tsconfig.json" },
{ "path": "./x-pack/plugins/translations/tsconfig.json" },
{ "path": "./x-pack/plugins/triggers_actions_ui/tsconfig.json" },

View file

@ -53,6 +53,7 @@
"xpack.spaces": "plugins/spaces",
"xpack.savedObjectsTagging": ["plugins/saved_objects_tagging"],
"xpack.taskManager": "legacy/plugins/task_manager",
"xpack.timelines": "plugins/timelines",
"xpack.transform": "plugins/transform",
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
"xpack.upgradeAssistant": "plugins/upgrade_assistant",

View file

@ -0,0 +1,7 @@
module.exports = {
root: true,
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
rules: {
'@kbn/eslint/require-license-header': 'off',
},
};

View file

@ -0,0 +1,7 @@
{
"prefix": "timelines",
"paths": {
"timelines": "."
},
"translations": ["translations/ja-JP.json"]
}

View file

@ -0,0 +1,11 @@
# timelines
Timelines is a plugin that provides a grid component with accompanying server side apis to help users identify events of interest and perform root cause analysis within Kibana.
## Using timelines in another plugin
- Add `TimelinesPluginSetup` to Kibana plugin `SetupServices` dependencies:
```ts
timelines: TimelinesPluginSetup;
```
- Once `timelines` is added as a required plugin in the consuming plugin's kibana.json, timeline functionality will be available as any other kibana plugin, ie PluginSetupDependencies.timelines.getTimeline()

View file

@ -0,0 +1,2 @@
export const PLUGIN_ID = 'timelines';
export const PLUGIN_NAME = 'timelines';

View file

@ -0,0 +1,10 @@
{
"id": "timelines",
"version": "1.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "timelines"],
"server": true,
"ui": true,
"requiredPlugins": [],
"optionalPlugins": []
}

View file

@ -0,0 +1,22 @@
import React from 'react';
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { PLUGIN_NAME } from '../../common';
import { TimelineProps } from '../types';
export const Timeline = (props: TimelineProps) => {
return (
<I18nProvider>
<div data-test-subj="timeline-wrapper">
<FormattedMessage
id="xpack.timelines.placeholder"
defaultMessage="Plugin: {name} Timeline: {timelineId}"
values={{ name: PLUGIN_NAME, timelineId: props.timelineId }}
/>
</div>
</I18nProvider>
);
};
// eslint-disable-next-line import/no-default-export
export { Timeline as default };

View file

@ -0,0 +1,11 @@
import './index.scss';
import { PluginInitializerContext } from 'src/core/public';
import { TimelinesPlugin } from './plugin';
// This exports static code and TypeScript types,
// as well as, Kibana Platform `plugin()` initializer.
export function plugin(initializerContext: PluginInitializerContext) {
return new TimelinesPlugin(initializerContext);
}
export { TimelinesPluginSetup } from './types';

View file

@ -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.
*/
import React, { lazy, Suspense } from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { TimelineProps } from '../types';
export const getTimelineLazy = (props: TimelineProps) => {
const TimelineLazy = lazy(() => import('../components'));
return (
<Suspense fallback={<EuiLoadingSpinner />}>
<TimelineLazy {...props} />
</Suspense>
);
};

View file

@ -0,0 +1,24 @@
import { CoreSetup, Plugin, PluginInitializerContext } from '../../../../src/core/public';
import { TimelinesPluginSetup, TimelineProps } from './types';
import { getTimelineLazy } from './methods';
export class TimelinesPlugin implements Plugin<TimelinesPluginSetup> {
constructor(private readonly initializerContext: PluginInitializerContext) {}
public setup(core: CoreSetup): TimelinesPluginSetup {
const config = this.initializerContext.config.get<{ enabled: boolean }>();
if (!config.enabled) {
return {};
}
return {
getTimeline: (props: TimelineProps) => {
return getTimelineLazy(props);
},
};
}
public start() {}
public stop() {}
}

View file

@ -0,0 +1,9 @@
import { ReactElement } from 'react';
export interface TimelinesPluginSetup {
getTimeline?: (props: TimelineProps) => ReactElement<TimelineProps>;
}
export interface TimelineProps {
timelineId: string;
}

View file

@ -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 { TypeOf, schema } from '@kbn/config-schema';
export const ConfigSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
});
export type ConfigType = TypeOf<typeof ConfigSchema>;

View file

@ -0,0 +1,21 @@
/*
* 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 { PluginInitializerContext } from '../../../../src/core/server';
import { TimelinesPlugin } from './plugin';
import { ConfigSchema } from './config';
export const config = {
schema: ConfigSchema,
exposeToBrowser: {
enabled: true,
},
};
export function plugin(initializerContext: PluginInitializerContext) {
return new TimelinesPlugin(initializerContext);
}
export { TimelinesPluginSetup, TimelinesPluginStart } from './types';

View file

@ -0,0 +1,35 @@
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
} from '../../../../src/core/server';
import { TimelinesPluginSetup, TimelinesPluginStart } from './types';
import { defineRoutes } from './routes';
export class TimelinesPlugin implements Plugin<TimelinesPluginSetup, TimelinesPluginStart> {
private readonly logger: Logger;
constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
}
public setup(core: CoreSetup) {
this.logger.debug('timelines: Setup');
const router = core.http.createRouter();
// Register server side APIs
defineRoutes(router);
return {};
}
public start(core: CoreStart) {
this.logger.debug('timelines: Started');
return {};
}
public stop() {}
}

View file

@ -0,0 +1,17 @@
import { IRouter } from '../../../../../src/core/server';
export function defineRoutes(router: IRouter) {
router.get(
{
path: '/api/timeline/example',
validate: false,
},
async (context, request, response) => {
return response.ok({
body: {
time: new Date().toISOString(),
},
});
}
);
}

View file

@ -0,0 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TimelinesPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TimelinesPluginStart {}

View file

@ -0,0 +1,19 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true
},
"include": [
// add all the folders contains files to be compiled
"common/**/*",
"public/**/*",
"server/**/*"
],
"references": [
{ "path": "../../../src/core/tsconfig.json" },
]
}

View file

@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
testFiles: [
resolve(__dirname, './test_suites/resolver'),
resolve(__dirname, './test_suites/global_search'),
resolve(__dirname, './test_suites/timelines'),
],
services,
@ -47,6 +48,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
KIBANA_ROOT,
'test/plugin_functional/plugins/core_provider_plugin'
)}`,
'--xpack.timelines.enabled=true',
...plugins.map((pluginDir) => `--plugin-path=${resolve(__dirname, 'plugins', pluginDir)}`),
],
},
@ -60,6 +62,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
resolverTest: {
pathname: '/app/resolverTest',
},
timelineTest: {
pathname: '/app/timelinesTest',
},
},
// choose where esArchiver should load archives from

View file

@ -0,0 +1,12 @@
{
"id": "timelinesTest",
"version": "1.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "timelinesTest"],
"requiredPlugins": ["timelines"],
"requiredBundles": [
"kibanaReact"
],
"server": false,
"ui": true
}

View file

@ -0,0 +1,60 @@
/*
* 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 { Router } from 'react-router-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from 'kibana/public';
import { I18nProvider } from '@kbn/i18n/react';
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
import { TimelinesPluginSetup } from '../../../../../../../plugins/timelines/public';
/**
* Render the Timeline Test app. Returns a cleanup function.
*/
export function renderApp(
coreStart: CoreStart,
parameters: AppMountParameters,
timelinesPluginSetup: TimelinesPluginSetup
) {
ReactDOM.render(
<AppRoot
coreStart={coreStart}
parameters={parameters}
timelinesPluginSetup={timelinesPluginSetup}
/>,
parameters.element
);
return () => {
ReactDOM.unmountComponentAtNode(parameters.element);
};
}
const AppRoot = React.memo(
({
coreStart,
parameters,
timelinesPluginSetup,
}: {
coreStart: CoreStart;
parameters: AppMountParameters;
timelinesPluginSetup: TimelinesPluginSetup;
}) => {
return (
<I18nProvider>
<Router history={parameters.history}>
<KibanaContextProvider services={coreStart}>
{(timelinesPluginSetup.getTimeline &&
timelinesPluginSetup.getTimeline({ timelineId: 'test' })) ??
null}
</KibanaContextProvider>
</Router>
</I18nProvider>
);
}
);

View file

@ -0,0 +1,20 @@
/*
* 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 { PluginInitializer } from 'kibana/public';
import {
TimelinesTestPlugin,
TimelinesTestPluginSetupDependencies,
TimelinesTestPluginStartDependencies,
} from './plugin';
export const plugin: PluginInitializer<
void,
void,
TimelinesTestPluginSetupDependencies,
TimelinesTestPluginStartDependencies
> = () => new TimelinesTestPlugin();

View file

@ -0,0 +1,50 @@
/*
* 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 { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { TimelinesPluginSetup } from '../../../../../plugins/timelines/public';
import { renderApp } from './applications/timelines_test';
export type TimelinesTestPluginSetup = void;
export type TimelinesTestPluginStart = void;
export interface TimelinesTestPluginSetupDependencies {
timelines: TimelinesPluginSetup;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TimelinesTestPluginStartDependencies {}
export class TimelinesTestPlugin
implements
Plugin<
TimelinesTestPluginSetup,
void,
TimelinesTestPluginSetupDependencies,
TimelinesTestPluginStartDependencies
> {
public setup(
core: CoreSetup<TimelinesTestPluginStartDependencies, TimelinesTestPluginStart>,
setupDependencies: TimelinesTestPluginSetupDependencies
) {
core.application.register({
id: 'timelinesTest',
title: i18n.translate('xpack.timelinesTest.pluginTitle', {
defaultMessage: 'Timelines Test',
}),
mount: async (params: AppMountParameters<unknown>) => {
const startServices = await core.getStartServices();
const [coreStart] = startServices;
const { timelines } = setupDependencies;
return renderApp(coreStart, params, timelines);
},
});
}
public start() {}
}

View file

@ -0,0 +1,25 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
describe('Timelines plugin API', function () {
this.tags('ciGroup7');
const pageObjects = getPageObjects(['common']);
const testSubjects = getService('testSubjects');
describe('timelines plugin rendering', function () {
before(async () => {
await pageObjects.common.navigateToApp('timelineTest');
});
it('shows the timeline component on navigation', async () => {
await testSubjects.existOrFail('timeline-wrapper');
});
});
});
}