mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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:
parent
fe17879ae3
commit
03b104cc61
28 changed files with 411 additions and 0 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -108,3 +108,4 @@ pageLoadAssetSize:
|
|||
fileUpload: 25664
|
||||
banners: 17946
|
||||
mapsEms: 26072
|
||||
timelines: 28613
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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",
|
||||
|
|
7
x-pack/plugins/timelines/.eslintrc.js
Normal file
7
x-pack/plugins/timelines/.eslintrc.js
Normal 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',
|
||||
},
|
||||
};
|
7
x-pack/plugins/timelines/.i18nrc.json
Normal file
7
x-pack/plugins/timelines/.i18nrc.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"prefix": "timelines",
|
||||
"paths": {
|
||||
"timelines": "."
|
||||
},
|
||||
"translations": ["translations/ja-JP.json"]
|
||||
}
|
11
x-pack/plugins/timelines/README.md
Normal file
11
x-pack/plugins/timelines/README.md
Normal 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()
|
2
x-pack/plugins/timelines/common/index.ts
Normal file
2
x-pack/plugins/timelines/common/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export const PLUGIN_ID = 'timelines';
|
||||
export const PLUGIN_NAME = 'timelines';
|
10
x-pack/plugins/timelines/kibana.json
Normal file
10
x-pack/plugins/timelines/kibana.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "timelines",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["xpack", "timelines"],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": [],
|
||||
"optionalPlugins": []
|
||||
}
|
22
x-pack/plugins/timelines/public/components/index.tsx
Normal file
22
x-pack/plugins/timelines/public/components/index.tsx
Normal 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 };
|
0
x-pack/plugins/timelines/public/index.scss
Normal file
0
x-pack/plugins/timelines/public/index.scss
Normal file
11
x-pack/plugins/timelines/public/index.ts
Normal file
11
x-pack/plugins/timelines/public/index.ts
Normal 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';
|
19
x-pack/plugins/timelines/public/methods/index.tsx
Normal file
19
x-pack/plugins/timelines/public/methods/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
24
x-pack/plugins/timelines/public/plugin.ts
Normal file
24
x-pack/plugins/timelines/public/plugin.ts
Normal 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() {}
|
||||
}
|
9
x-pack/plugins/timelines/public/types.ts
Normal file
9
x-pack/plugins/timelines/public/types.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ReactElement } from 'react';
|
||||
|
||||
export interface TimelinesPluginSetup {
|
||||
getTimeline?: (props: TimelineProps) => ReactElement<TimelineProps>;
|
||||
}
|
||||
|
||||
export interface TimelineProps {
|
||||
timelineId: string;
|
||||
}
|
13
x-pack/plugins/timelines/server/config.ts
Normal file
13
x-pack/plugins/timelines/server/config.ts
Normal 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>;
|
21
x-pack/plugins/timelines/server/index.ts
Normal file
21
x-pack/plugins/timelines/server/index.ts
Normal 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';
|
35
x-pack/plugins/timelines/server/plugin.ts
Normal file
35
x-pack/plugins/timelines/server/plugin.ts
Normal 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() {}
|
||||
}
|
17
x-pack/plugins/timelines/server/routes/index.ts
Normal file
17
x-pack/plugins/timelines/server/routes/index.ts
Normal 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(),
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
4
x-pack/plugins/timelines/server/types.ts
Normal file
4
x-pack/plugins/timelines/server/types.ts
Normal 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 {}
|
19
x-pack/plugins/timelines/tsconfig.json
Normal file
19
x-pack/plugins/timelines/tsconfig.json
Normal 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" },
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"id": "timelinesTest",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["xpack", "timelinesTest"],
|
||||
"requiredPlugins": ["timelines"],
|
||||
"requiredBundles": [
|
||||
"kibanaReact"
|
||||
],
|
||||
"server": false,
|
||||
"ui": true
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -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();
|
|
@ -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() {}
|
||||
}
|
25
x-pack/test/plugin_functional/test_suites/timelines/index.ts
Normal file
25
x-pack/test/plugin_functional/test_suites/timelines/index.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue